- Reference docs: known issues, user journey, location redesign, mission merge plan - Scripts: fill_location_state, merge_missions, migrate_location, migrate_state - Tests: e2e test suite, asset entries unit tests - Demo: location board, hifi prototypes, test screenshots - Data backup: asset log entries and state nodes - Update .gitignore Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
86 lines
3.1 KiB
Markdown
86 lines
3.1 KiB
Markdown
# Location & State Label 从 Interval 迁移到 State Node
|
||
|
||
## 设计决策
|
||
|
||
**`location` 和 `state_label` 不再绑定到 `asset_log_entries`(state interval),而绑定到 `asset_state_nodes`(state node)。**
|
||
|
||
### 动机
|
||
|
||
合并 Outbound/Operations/Inbound 后,一个任务条目会跨越多个 location 和 state:
|
||
- 出发段 → location=Transfer, state=Transfer
|
||
- 到达后 → location=Mars, state=Surface Expedition
|
||
- 返航段 → location=Transfer, state=Transfer
|
||
|
||
如果把 location/state_label 保留在 interval 级别,合并后的条目只能有一个 location 和一个 state_label,无法表达任务过程中的变化。
|
||
|
||
移到 state node 后,每个时间节点可以独立标注位置和状态,时间线渲染也更精准。
|
||
|
||
### 数据库变更
|
||
|
||
#### `asset_log_entries` — 删除列
|
||
```sql
|
||
ALTER TABLE asset_log_entries DROP COLUMN location;
|
||
ALTER TABLE asset_log_entries DROP COLUMN state_label;
|
||
```
|
||
|
||
#### `asset_state_nodes` — 新增列
|
||
```sql
|
||
ALTER TABLE asset_state_nodes ADD COLUMN location VARCHAR(255);
|
||
ALTER TABLE asset_state_nodes ADD COLUMN state_label VARCHAR(120);
|
||
```
|
||
|
||
### 数据迁移
|
||
|
||
现有 interval 的 `location` 和 `state_label` 迁移到该 interval 的第一个 state node:
|
||
- 如果 interval 已有 state node → 将 location/state_label 写入按时间排序的第一个 node
|
||
- 如果 interval 无 state node → 创建一个 state node(at_time = interval.start_at, title = interval.title),写入 location/state_label
|
||
|
||
### 后端变更
|
||
|
||
#### Model 层 (`app/models.py`)
|
||
|
||
```python
|
||
# AssetLogEntry: 删除 location 和 state_label 字段
|
||
class AssetLogEntry(TimestampMixin, db.Model):
|
||
# 删除: location, state_label
|
||
...
|
||
|
||
# AssetStateNode: 新增 location 和 state_label 字段
|
||
class AssetStateNode(TimestampMixin, db.Model):
|
||
...
|
||
location: Mapped[str | None] = mapped_column(String(255))
|
||
state_label: Mapped[str | None] = mapped_column(String(120))
|
||
```
|
||
|
||
#### 表单 (`app/templates/asset_entry_form.html`)
|
||
|
||
- 移除 interval 级别的 location / state_label 输入
|
||
- 在 state node 行内增加 location / state_label 输入(每个 node 独立设置)
|
||
- Event point 保留 location 字段(作为该时间点的快照)
|
||
|
||
#### 资产详情 (`app/templates/asset_detail.html`)
|
||
|
||
- 当前位置:从最近的一个 state node 的 location 派生(而非当前活跃 interval)
|
||
- 当前状态:同上,从最近 state node 的 state_label 派生
|
||
|
||
#### 看板 / 时间线
|
||
|
||
- 时间线上每个 state node 显示其自身的 location 和 state_label
|
||
- 看板按 location/state 分组时,以最新 state node 为准
|
||
|
||
### 对现有数据的影响
|
||
|
||
| 表 | 操作 |
|
||
|----|------|
|
||
| `asset_log_entries` | DROP location, state_label |
|
||
| `asset_state_nodes` | ADD location, state_label |
|
||
| 现有 interval 数据 | 迁移到首个 state node |
|
||
| 表单模板 | 重构 |
|
||
| 详情/看板/时间线 | 改为从 state node 读取 |
|
||
|
||
### 实施顺序
|
||
|
||
1. **先做合并**(mission_merge_plan.md)— 减少条目数,简化后续迁移
|
||
2. **再做列迁移**(本文档)— 将 location/state_label 下沉到 state node
|
||
3. **最后改前端**— 表单、详情、看板、时间线逐一适配
|