diff --git a/crawler/db_merge.py b/crawler/db_merge.py index c8464c3..78fbe63 100644 --- a/crawler/db_merge.py +++ b/crawler/db_merge.py @@ -41,6 +41,11 @@ def _ensure_tables(conn: sqlite3.Connection) -> None: conn.execute("ALTER TABLE combat_losses ADD COLUMN updated_at TEXT DEFAULT (datetime('now'))") except sqlite3.OperationalError: pass + for col in ("drones", "missiles", "helicopters", "submarines"): + try: + conn.execute(f"ALTER TABLE combat_losses ADD COLUMN {col} INTEGER NOT NULL DEFAULT 0") + except sqlite3.OperationalError: + pass conn.execute("CREATE TABLE IF NOT EXISTS wall_street_trend (id INTEGER PRIMARY KEY AUTOINCREMENT, time TEXT NOT NULL, value INTEGER NOT NULL)") conn.execute("CREATE TABLE IF NOT EXISTS retaliation_current (id INTEGER PRIMARY KEY CHECK (id = 1), value INTEGER NOT NULL)") conn.execute("CREATE TABLE IF NOT EXISTS retaliation_history (id INTEGER PRIMARY KEY AUTOINCREMENT, time TEXT NOT NULL, value INTEGER NOT NULL)") @@ -74,16 +79,19 @@ def merge(extracted: Dict[str, Any], db_path: Optional[str] = None) -> bool: continue try: row = conn.execute( - "SELECT personnel_killed,personnel_wounded,civilian_killed,civilian_wounded,bases_destroyed,bases_damaged,aircraft,warships,armor,vehicles FROM combat_losses WHERE side = ?", + "SELECT personnel_killed,personnel_wounded,civilian_killed,civilian_wounded,bases_destroyed,bases_damaged,aircraft,warships,armor,vehicles,drones,missiles,helicopters,submarines FROM combat_losses WHERE side = ?", (side,), ).fetchone() cur = {"personnel_killed": 0, "personnel_wounded": 0, "civilian_killed": 0, "civilian_wounded": 0, - "bases_destroyed": 0, "bases_damaged": 0, "aircraft": 0, "warships": 0, "armor": 0, "vehicles": 0} + "bases_destroyed": 0, "bases_damaged": 0, "aircraft": 0, "warships": 0, "armor": 0, "vehicles": 0, + "drones": 0, "missiles": 0, "helicopters": 0, "submarines": 0} if row: cur = { "personnel_killed": row[0], "personnel_wounded": row[1], "civilian_killed": row[2] or 0, "civilian_wounded": row[3] or 0, "bases_destroyed": row[4], "bases_damaged": row[5], "aircraft": row[6], "warships": row[7], "armor": row[8], "vehicles": row[9], + "drones": row[10] if len(row) > 10 else 0, "missiles": row[11] if len(row) > 11 else 0, + "helicopters": row[12] if len(row) > 12 else 0, "submarines": row[13] if len(row) > 13 else 0, } pk = max(0, (cur["personnel_killed"] or 0) + delta.get("personnel_killed", 0)) pw = max(0, (cur["personnel_wounded"] or 0) + delta.get("personnel_wounded", 0)) @@ -95,18 +103,23 @@ def merge(extracted: Dict[str, Any], db_path: Optional[str] = None) -> bool: ws = max(0, (cur["warships"] or 0) + delta.get("warships", 0)) ar = max(0, (cur["armor"] or 0) + delta.get("armor", 0)) vh = max(0, (cur["vehicles"] or 0) + delta.get("vehicles", 0)) + dr = max(0, (cur["drones"] or 0) + delta.get("drones", 0)) + ms = max(0, (cur["missiles"] or 0) + delta.get("missiles", 0)) + hp = max(0, (cur["helicopters"] or 0) + delta.get("helicopters", 0)) + sb = max(0, (cur["submarines"] or 0) + delta.get("submarines", 0)) ts = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.000Z") if row: conn.execute( """UPDATE combat_losses SET personnel_killed=?, personnel_wounded=?, civilian_killed=?, civilian_wounded=?, - bases_destroyed=?, bases_damaged=?, aircraft=?, warships=?, armor=?, vehicles=?, updated_at=? WHERE side=?""", - (pk, pw, ck, cw, bd, bm, ac, ws, ar, vh, ts, side), + bases_destroyed=?, bases_damaged=?, aircraft=?, warships=?, armor=?, vehicles=?, + drones=?, missiles=?, helicopters=?, submarines=?, updated_at=? WHERE side=?""", + (pk, pw, ck, cw, bd, bm, ac, ws, ar, vh, dr, ms, hp, sb, ts, side), ) else: conn.execute( """INSERT OR REPLACE INTO combat_losses (side, personnel_killed, personnel_wounded, civilian_killed, civilian_wounded, - bases_destroyed, bases_damaged, aircraft, warships, armor, vehicles, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", - (side, pk, pw, ck, cw, bd, bm, ac, ws, ar, vh, ts), + bases_destroyed, bases_damaged, aircraft, warships, armor, vehicles, drones, missiles, helicopters, submarines, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", + (side, pk, pw, ck, cw, bd, bm, ac, ws, ar, vh, dr, ms, hp, sb, ts), ) if conn.total_changes > 0: updated = True diff --git a/crawler/panel_schema.py b/crawler/panel_schema.py index 1f2029e..8dd1bcf 100644 --- a/crawler/panel_schema.py +++ b/crawler/panel_schema.py @@ -19,7 +19,7 @@ TimeSeriesPoint = Tuple[str, int] # (ISO time, value) # AI 可从新闻中提取的字段 EXTRACTABLE_FIELDS = { "situation_update": ["summary", "category", "severity", "timestamp"], - "combat_losses": ["personnel_killed", "personnel_wounded", "civilian_killed", "civilian_wounded", "bases_destroyed", "bases_damaged", "aircraft", "warships", "armor", "vehicles"], + "combat_losses": ["personnel_killed", "personnel_wounded", "civilian_killed", "civilian_wounded", "bases_destroyed", "bases_damaged", "aircraft", "warships", "armor", "vehicles", "drones", "missiles", "helicopters", "submarines"], "retaliation": ["value"], # 0-100 "wall_street_trend": ["time", "value"], # 0-100 "conflict_stats": ["estimated_casualties", "estimated_strike_count"], diff --git a/server/data.db b/server/data.db index 0ecd106..0754b25 100644 Binary files a/server/data.db and b/server/data.db differ diff --git a/server/data.db-shm b/server/data.db-shm index 5c447fe..39aaafb 100644 Binary files a/server/data.db-shm and b/server/data.db-shm differ diff --git a/server/data.db-wal b/server/data.db-wal index 268c306..4acc4df 100644 Binary files a/server/data.db-wal and b/server/data.db-wal differ diff --git a/server/db.js b/server/db.js index 70eb356..10febfe 100644 --- a/server/db.js +++ b/server/db.js @@ -145,6 +145,10 @@ try { if (!lossNames.includes('civilian_killed')) db.exec('ALTER TABLE combat_losses ADD COLUMN civilian_killed INTEGER NOT NULL DEFAULT 0') if (!lossNames.includes('civilian_wounded')) db.exec('ALTER TABLE combat_losses ADD COLUMN civilian_wounded INTEGER NOT NULL DEFAULT 0') if (!lossNames.includes('updated_at')) db.exec('ALTER TABLE combat_losses ADD COLUMN updated_at TEXT DEFAULT (datetime("now"))') + if (!lossNames.includes('drones')) db.exec('ALTER TABLE combat_losses ADD COLUMN drones INTEGER NOT NULL DEFAULT 0') + if (!lossNames.includes('missiles')) db.exec('ALTER TABLE combat_losses ADD COLUMN missiles INTEGER NOT NULL DEFAULT 0') + if (!lossNames.includes('helicopters')) db.exec('ALTER TABLE combat_losses ADD COLUMN helicopters INTEGER NOT NULL DEFAULT 0') + if (!lossNames.includes('submarines')) db.exec('ALTER TABLE combat_losses ADD COLUMN submarines INTEGER NOT NULL DEFAULT 0') } catch (_) {} // 迁移:所有表添加 updated_at 用于数据回放 diff --git a/server/seed.js b/server/seed.js index 83a70be..282c8a6 100644 --- a/server/seed.js +++ b/server/seed.js @@ -149,9 +149,9 @@ function seed() { try { db.exec(` - INSERT OR REPLACE INTO combat_losses (side, bases_destroyed, bases_damaged, personnel_killed, personnel_wounded, civilian_killed, civilian_wounded, aircraft, warships, armor, vehicles) VALUES - ('us', 0, 27, 127, 384, 18, 52, 2, 0, 0, 8), - ('iran', 3, 8, 2847, 5620, 412, 1203, 24, 12, 18, 42); + INSERT OR REPLACE INTO combat_losses (side, bases_destroyed, bases_damaged, personnel_killed, personnel_wounded, civilian_killed, civilian_wounded, aircraft, warships, armor, vehicles, drones, missiles, helicopters, submarines) VALUES + ('us', 0, 27, 127, 384, 18, 52, 2, 0, 0, 8, 4, 12, 1, 0), + ('iran', 3, 8, 2847, 5620, 412, 1203, 24, 12, 18, 42, 28, 156, 8, 2); `) } catch (_) { db.exec(` diff --git a/server/situationData.js b/server/situationData.js index 62cd1b9..0ba77e6 100644 --- a/server/situationData.js +++ b/server/situationData.js @@ -20,6 +20,10 @@ function toLosses(row) { warships: row.warships, armor: row.armor, vehicles: row.vehicles, + drones: row.drones ?? 0, + missiles: row.missiles ?? 0, + helicopters: row.helicopters ?? 0, + submarines: row.submarines ?? 0, } } @@ -31,6 +35,10 @@ const defaultLosses = { warships: 0, armor: 0, vehicles: 0, + drones: 0, + missiles: 0, + helicopters: 0, + submarines: 0, } function getSituation() { diff --git a/src/components/CombatLossesPanel.tsx b/src/components/CombatLossesPanel.tsx index 7c9d66a..a01387a 100644 --- a/src/components/CombatLossesPanel.tsx +++ b/src/components/CombatLossesPanel.tsx @@ -6,6 +6,10 @@ import { Ship, Shield, Car, + Scan, + Rocket, + Wind, + Anchor, TrendingDown, UserCircle, Activity, @@ -31,10 +35,14 @@ export function CombatLossesPanel({ usLosses, iranLosses, conflictStats, civilia { label: '战舰', icon: Ship, iconColor: 'text-blue-500', us: usLosses.warships, ir: iranLosses.warships }, { label: '装甲', icon: Shield, iconColor: 'text-emerald-500', us: usLosses.armor, ir: iranLosses.armor }, { label: '车辆', icon: Car, iconColor: 'text-slate-400', us: usLosses.vehicles, ir: iranLosses.vehicles }, + { label: '无人机', icon: Scan, iconColor: 'text-violet-400', us: usLosses.drones ?? 0, ir: iranLosses.drones ?? 0 }, + { label: '导弹', icon: Rocket, iconColor: 'text-orange-500', us: usLosses.missiles ?? 0, ir: iranLosses.missiles ?? 0 }, + { label: '直升机', icon: Wind, iconColor: 'text-teal-400', us: usLosses.helicopters ?? 0, ir: iranLosses.helicopters ?? 0 }, + { label: '潜艇', icon: Anchor, iconColor: 'text-indigo-400', us: usLosses.submarines ?? 0, ir: iranLosses.submarines ?? 0 }, ] return ( -