# 看板板块逐项调试指南

本文档按前端每个板块列出：**数据来源表**、**谁写入**、**如何验证**、**常见问题**，便于逐项排查。

---

## 数据流总览

```
前端 Dashboard
  → useReplaySituation() → situation (来自 WebSocket / GET /api/situation)
  → getSituation() 读 server/situationData.js
  → 从 SQLite (server/data.db) 多表 SELECT 后拼成 JSON
```

- **写入方**：`server/seed.js`（初始化）、爬虫流水线（`crawler/pipeline.py` + `db_merge.py`）、GDELT 服务（`gdelt_events` / `conflict_stats`）。
- **读入方**：仅 `server/situationData.js` 的 `getSituation()`，被 `/api/situation` 与 WebSocket 广播使用。

---

## 1. 顶栏 (HeaderPanel)

| 项目 | 数据来源 | 写入方 | 说明 |
|------|----------|--------|------|
| 最后更新时间 | `situation.lastUpdated` | `situation.updated_at`（表 `situation` id=1） | 爬虫 notify 时更新 |
| 在看/看过 | `stats.viewers` / `stats.cumulative` | `visits` / `visitor_count`，见 `POST /api/visit` | 与爬虫无关 |
| 美/伊战力条 | `usForces.powerIndex.overall` / `iranForces.powerIndex.overall` | `power_index` 表 | **仅 seed** |

**验证**

- `curl -s http://localhost:3001/api/situation | jq '.lastUpdated, .usForces.powerIndex.overall, .iranForces.powerIndex.overall'`
- 看板顶栏是否显示时间、双战力数值。

**常见问题**

- `lastUpdated` 不变：爬虫未调 `POST /api/crawler/notify` 或 Node 未执行 `reloadFromFile()`。
- 战力条为 0：未跑 seed 或 `power_index` 无数据。

---

## 2. 事件脉络 / 时间线 (TimelinePanel → EventTimelinePanel + RecentUpdatesPanel)

| 项目 | 数据来源 | 写入方 | 说明 |
|------|----------|--------|------|
| 近期更新列表 | `situation.recentUpdates` | `situation_update` 表（ORDER BY timestamp DESC LIMIT 50） | 爬虫 `write_updates(new_items)` + seed 若干条 |

**验证**

- `curl -s http://localhost:3001/api/situation | jq '.recentUpdates | length'`
- `curl -s http://localhost:3001/api/situation | jq '.recentUpdates[0]'`
- 或用调试接口：`curl -s -H "x-api-key: $API_ADMIN_KEY" http://localhost:3001/api/db/dashboard | jq '.situation_update | length'`

**常见问题**

- 条数为 0：未 seed 且爬虫未写入；或爬虫只跑 main.py（入口 A）未跑 gdelt（入口 B）时，仍会写 `situation_update`，但若 RSS 抓取 0 条则无新数据。
- 不更新：爬虫未启动；或未调 notify；或 Node 与爬虫用的不是同一个 `data.db`（路径/环境变量不一致）。

---

## 3. 地图 (WarMap)

| 项目 | 数据来源 | 写入方 | 说明 |
|------|----------|--------|------|
| 美军据点 | `usForces.keyLocations` | `key_location` WHERE side='us' | seed 全量；爬虫通过 `key_location_updates` 只更新 status/damage_level |
| 伊朗据点 | `iranForces.keyLocations` | `key_location` WHERE side='iran' | 同上 |
| 冲突点（绿/橙/红） | `situation.conflictEvents` | `gdelt_events` 表（ORDER BY event_time DESC LIMIT 30） | GDELT API 写入；或 GDELT 关闭时 RSS 回填 |

**验证**

- `curl -s http://localhost:3001/api/situation | jq '.usForces.keyLocations | length, .conflictEvents | length'`
- 地图上是否有基地/舰船点位、是否有冲突点图层。

**常见问题**

- 无冲突点：`gdelt_events` 为空；未跑 gdelt 或 GDELT 被墙且未用 RSS 回填（`_rss_to_gdelt_fallback`）。
- 基地状态不更新：爬虫提取的 `key_location_updates` 的 `name_keywords` 与 `key_location.name` 无法 LIKE 匹配（名称不一致）。

---

## 4. 美国基地状态 (BaseStatusPanel)

| 项目 | 数据来源 | 写入方 | 说明 |
|------|----------|--------|------|
| 基地列表 | `usForces.keyLocations` 中 `type === 'Base'` | `key_location` side='us' | 同 WarMap |

**验证**

- `curl -s http://localhost:3001/api/situation | jq '[.usForces.keyLocations[] | select(.type == "Base")] | length'`
- 看板左侧「美国基地」是否展示且状态/损伤与预期一致。

**常见问题**

- 与「地图」一致；若 seed 的 key_location 有 type/region，而爬虫只更新 status/damage_level，名称必须能与 extractor 的 name_keywords 匹配。

---

## 5. 战损 (CombatLossesPanel + CombatLossesOtherPanel)

| 项目 | 数据来源 | 写入方 | 说明 |
|------|----------|--------|------|
| 美军/伊朗阵亡/受伤/装备等 | `usForces.combatLosses` / `iranForces.combatLosses` | `combat_losses` 表（side=us/iran） | seed 初始值；爬虫 AI 提取 `combat_losses_delta` 后 db_merge **增量**叠加 |
| 冲突统计（估计伤亡等） | `situation.conflictStats` | `conflict_stats` 表 id=1 | GDELT 或 RSS 回填时写入 |
| 平民伤亡合计 | `situation.civilianCasualtiesTotal` | 由 combat_losses 双方平民字段 + conflict_stats.estimated_casualties 计算 | 见 situationData.js |

**验证**

- `curl -s http://localhost:3001/api/situation | jq '.usForces.combatLosses, .iranForces.combatLosses, .conflictStats'`
- 看板战损数字是否与 API 一致。

**常见问题**

- 战损一直不变：新闻中无明确伤亡/装备数字；或未跑入口 B（gdelt）；或 AI 提取器未启用/报错（Ollama/通义/规则）；或 merge 时单次增量被上限截断。
- 数字异常大：提取器误把「累计总数」当成本条增量；已用 `MAX_DELTA_PER_MERGE` 做上限。

---

## 6. 伊朗基地状态 (IranBaseStatusPanel)

| 项目 | 数据来源 | 写入方 | 说明 |
|------|----------|--------|------|
| 基地/港/核/导弹等 | `iranForces.keyLocations` 中 type 为 Base/Port/Nuclear/Missile | `key_location` side='iran' | 同 WarMap |

**验证与常见问题**

- 同「美国基地」；确保 seed 中伊朗 key_location 的 name 与爬虫 extractor 的 name_keywords 能匹配（如德黑兰、伊斯法罕、布什尔等）。

---

## 7. 战力对比图 (PowerChart)

| 项目 | 数据来源 | 写入方 | 说明 |
|------|----------|--------|------|
| 美/伊战力指数 | `usForces.powerIndex` / `iranForces.powerIndex` | `power_index` 表 | **仅 seed**，爬虫不写 |

**验证**

- `curl -s http://localhost:3001/api/situation | jq '.usForces.powerIndex, .iranForces.powerIndex'`

**常见问题**

- 为 0 或缺失：未执行 seed；或 `power_index` 表空。

---

## 8. 华尔街/投资趋势 (InvestmentTrendChart)

| 项目 | 数据来源 | 写入方 | 说明 |
|------|----------|--------|------|
| 时间序列 | `usForces.wallStreetInvestmentTrend` | `wall_street_trend` 表（time, value） | seed 写入初始曲线；爬虫仅在提取出 `wall_street` 时 **INSERT 新点** |

**验证**

- `curl -s http://localhost:3001/api/situation | jq '.usForces.wallStreetInvestmentTrend | length'`
- 看板右侧美国下方趋势图是否有数据。

**常见问题**

- 无曲线：未 seed 或表空。
- 不随新闻更新：提取器未输出 `wall_street` 或新闻中无相关表述。

---

## 9. 美国力量摘要 (ForcePanel side=us)

| 项目 | 数据来源 | 写入方 | 说明 |
|------|----------|--------|------|
| 摘要数字 | `usForces.summary` | `force_summary` side='us' | **仅 seed** |
| 战力指数 | `usForces.powerIndex` | `power_index` | **仅 seed** |
| 资产列表 | `usForces.assets` | `force_asset` side='us' | **仅 seed** |

**验证**

- `curl -s http://localhost:3001/api/situation | jq '.usForces.summary, .usForces.assets | length'`

**常见问题**

- 全为 0 或空：未 seed；爬虫不更新这些表。

---

## 10. 报复情绪 (RetaliationGauge)

| 项目 | 数据来源 | 写入方 | 说明 |
|------|----------|--------|------|
| 当前值 | `iranForces.retaliationSentiment` | `retaliation_current` id=1 | seed 初始；爬虫提取 `retaliation` 时 **替换** 当前值并 **追加** history |
| 历史曲线 | `iranForces.retaliationSentimentHistory` | `retaliation_history` 表 | 同上 |

**验证**

- `curl -s http://localhost:3001/api/situation | jq '.iranForces.retaliationSentiment, .iranForces.retaliationSentimentHistory | length'`

**常见问题**

- 不更新：新闻中无报复相关表述；或提取器未输出 `retaliation`。

---

## 11. 伊朗力量摘要 (ForcePanel side=iran)

| 项目 | 数据来源 | 写入方 | 说明 |
|------|----------|--------|------|
| 同美国侧 | `iranForces.summary` / `powerIndex` / `assets` | `force_summary` / `power_index` / `force_asset` side='iran' | **仅 seed** |

**验证与常见问题**

- 同「美国力量摘要」。

---

## 12. 资讯列表 (GET /api/news，若有单独页面消费)

| 项目 | 数据来源 | 写入方 | 说明 |
|------|----------|--------|------|
| 资讯行 | `news_content` 表 | 爬虫 `save_and_dedup` 后写入 | 仅入口 B 流水线；事件脉络来自 situation_update，资讯表独立 |

**验证**

- `curl -s -H "x-api-key: $API_ADMIN_KEY" http://localhost:3001/api/news?limit=5 | jq '.items | length'`
- 若未配 ADMIN_KEY，部分环境可能不鉴权也可访问，视 routes 配置而定。

**常见问题**

- `items` 为 0：未跑入口 B；或去重后无新增；或 RSS 抓取 0 条。

---

## 快速检查命令汇总

```bash
# 1. API 与态势整体
curl -s http://localhost:3001/api/health
curl -s http://localhost:3001/api/situation | jq '{
  lastUpdated,
  recentUpdates: (.recentUpdates | length),
  conflictEvents: (.conflictEvents | length),
  usPower: .usForces.powerIndex.overall,
  iranPower: .iranForces.powerIndex.overall,
  usLosses: .usForces.combatLosses.personnelCasualties,
  iranLosses: .iranForces.combatLosses.personnelCasualties,
  usBases: (.usForces.keyLocations | length),
  iranBases: (.iranForces.keyLocations | length),
  wallStreetLen: (.usForces.wallStreetInvestmentTrend | length),
  retaliationCur: .iranForces.retaliationSentiment
}'

# 2. 各表行数（需 sqlite3）
DB="${DB_PATH:-server/data.db}"
for t in force_summary power_index force_asset key_location combat_losses wall_street_trend retaliation_current retaliation_history situation_update gdelt_events conflict_stats news_content; do
  echo -n "$t: "; sqlite3 "$DB" "SELECT COUNT(*) FROM $t" 2>/dev/null || echo "?"
done

# 3. 爬虫状态与通知
curl -s http://localhost:8000/crawler/status | jq .
curl -s -X POST http://localhost:3001/api/crawler/notify
```

---

## 建议调试顺序

1. **先确认 API 与 DB 一致**：`npm run api` 已起、`GET /api/situation` 返回 200，且 `lastUpdated`、`recentUpdates` 等存在。
2. **确认 seed**：若从未 seed，先跑 `node server/seed.js`（或项目提供的 seed 命令），再刷新看板，检查战力/摘要/基地/战损等是否有初始值。
3. **事件脉络**：确认爬虫已起（`npm run gdelt`）、RSS 能抓到条数、`situation_update` 条数增加、notify 后前端/API 的 `recentUpdates` 增加。
4. **战损/基地/报复/美股**：确认跑的是入口 B、提取器可用（Ollama 或 DASHSCOPE_API_KEY 或规则）、新闻内容包含可解析的伤亡/基地/报复表述；必要时用 crawler 的提取单测或 backfill 接口验证。
5. **地图冲突点**：确认 `gdelt_events` 有数据（GDELT 或 RSS 回填）；冲突统计看 `conflict_stats`。

按上述顺序逐板块对照「数据来源 → 写入方 → 验证命令 → 常见问题」，即可定位每个板块不更新或显示异常的原因。

**若只关心战损、基地、地图战区**：见 **docs/DEBUG_战损_基地_地图.md**，并运行 `./scripts/debug-panels-focus.sh` 做专项检查。
