chore: add reference docs, scripts, tests, demo, prototypes

- 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>
This commit is contained in:
2026-06-01 15:29:47 +08:00
co-authored by Claude Opus 4.7
parent f4909765af
commit e1d601a774
190 changed files with 11464 additions and 0 deletions
+85
View File
@@ -0,0 +1,85 @@
# 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 nodeat_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. **最后改前端**— 表单、详情、看板、时间线逐一适配