# Pop · pinride 数据模型与勾稽说明（DEV / pop.pinride.ai）

> 本文档只存在于 **动态站** `/var/www/pop.pinride.ai/`。  
> 稳定站 `popride.ai`（`/var/www/popride.ai`）已冻结，不含此文档。

## 1. 文件位置

| 文件 | 作用 |
|------|------|
| `data/places.json` | 地点／星球／城市主数据 |
| `data/trips.json` | 行程＝Pop（含座位、人员） |
| `data/meta.json` | 产品元信息、西部 MA 边界、默认 home |
| `/api/geo` | 服务端 IP 地理 → `suggestedHomePlaceId` |
| `/api/places` `/api/trips` | 同源 JSON 透传（也可直接读 `/data/*.json`） |

## 2. 实体定义

### 2.1 places（地点／星球）

```json
{
  "id": "pioneer_valley",
  "name_zh": "先锋谷 / 阿默斯特村",
  "name_en": "Pioneer Valley / Amherst",
  "kind": "moon_base",
  "metaphor": "月球基地",
  "lat": 42.3732,
  "lon": -72.5199,
  "defaultScale": 1.35,
  "appearWhenTrips": false
}
```

| 字段 | 说明 |
|------|------|
| `id` | 主键；被 trip.originPlaceId / destPlaceId 引用 |
| `name_zh` / `name_en` | 中英文显示名 |
| `kind` | `earth_base` \| `moon_base` \| `mars` \| `planet` \| `city` |
| `lat`/`lon` | 真实地理，用于距离缩放与 geo 匹配 |
| `metaphor` | 宇宙隐喻文案（月球基地／火星…） |
| `defaultScale` | 非 home 时的基准缩放 |
| `appearWhenTrips` | true 时：仅当存在涉及该地的 trip 才在宇宙中「现身」 |

**内置至少：** `pioneer_valley`（先锋谷/Amherst）、`boston`、`ny`、`keene`；可扩展 `philadelphia`、`washington_dc`、`pittsburgh`。

### 2.2 trips / pops（行程＝一次 Pop）

```json
{
  "id": "seed_bos_1",
  "date": "TODAY",
  "weekly": false,
  "originPlaceId": "pioneer_valley",
  "destPlaceId": "boston",
  "seats": 4,
  "people": [{"role":"driver","name":"Terry","intent":"...","color":0}],
  "status": "open"
}
```

| 字段 | 说明 |
|------|------|
| `originPlaceId` / `destPlaceId` | **勾稽 → places.id** |
| `date` | `YYYY-MM-DD` 或字面量 `TODAY`（相对 simToday） |
| `weekly` | true＝永不因日期归档，沉入隧道远端 |
| `seats` | 总座位数（默认 4） |
| `people[]` | 司机／乘客；driver 用白金视觉 |
| `status` | `open` \| `full` \| `archived` |

### 2.3 grapes（葡萄串）——派生，不单独存盘

**勾稽：** 同一 `date`（解析后）+ 同一 `originPlaceId`→`destPlaceId` 方向的 trips 聚成一串 grape。

前端：`clusterKey = date + '|' + originPlaceId + '|' + destPlaceId`。

### 2.4 groups（群组）——派生

**勾稽：** 某 `placeId` 上、某日所有 grapes 的集合（通常按出发地分组展示「今日 Pops」看板）。

### 2.5 edges / routes（虚线箭头）——派生

**勾稽：** 当 focusDay 存在 `origin→dest` 的可见 trip 时，在宇宙 SVG 画 `origin.baseXY → dest.baseXY` 虚线箭头。

## 3. 视图缩放勾稽（月球基地视觉）

```
view.scale(place) ← homePlaceId + haversine(home, place) + tripActivity(place)
```

规则（实现于 `app.js`）：

1. **选中的 home（月球基地）** → `scale ≈ 1.45`，置于前景（大、亮、带「月球基地」徽章）。
2. **其他地点** → `scale = defaultScale * distanceFactor * activityBoost`，整体小于 home。
3. **距离因子**：离 home 越远越小（远行星）。
4. **活跃度**：该地作为 origin/dest 的可见 trip 越多，略放大。
5. **appearWhenTrips**：无行程则不渲染（费城等扩展城市）。

> 正确行为：**选中的城市变大**，其余变小。  
> （用户曾写「选 NY 时 Boston 变大」属笔误，实现以「选中者变大」为准。）

## 4. 归档规则

| 条件 | 行为 |
|------|------|
| `weekly === true` | 永不因日期消失；可沉入隧道奇点 |
| `weekly === false` 且 `date < simToday` | **归档**，从宇宙／看板移除 |
| `date === "TODAY"` | 解析为当前 `simToday` |

`simToday` 由顶栏「今天 (ET)」控制，时区 `America/New_York`。

## 5. IP → 默认月球基地

```
客户端 IP
  → nginx X-Real-IP / X-Forwarded-For
  → /api/geo（Python）
  → ip-api.com 查询 city/region/lat/lon
  → 若落在西部 MA / Pioneer Valley / Springfield 一带
       → suggestedHomePlaceId = pioneer_valley
  → 否则尝试 places.regionHints 名称匹配
  → 失败 → pioneer_valley（fallback）
```

用户仍可用下拉框「月球基地」手动改 home；选择写入 `localStorage.pop_home_place_v2`。

## 6. 勾稽总览（一图胜千言）

```
places.id  ←——  trip.originPlaceId
           ←——  trip.destPlaceId
                │
                ▼
         grape(s)  ← 同日同向 trips 聚类
                │
                ▼
         group@place  ← 某地当日 grapes
                │
                ▼
         edge/route   ← 有活跃 trip 的 from→to 虚线
                │
                ▼
         view scale   ← homePlaceId + 距离 + 活跃度
                │
                ▼
         archive      ← date < simToday 且非 weekly
```

