Files
usa/server/situationData.js
2026-03-06 10:34:52 +08:00

196 lines
7.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const db = require('./db')
function toAsset(row) {
return {
id: row.id,
name: row.name,
type: row.type,
count: row.count,
status: row.status,
...(row.lat != null && { location: { lat: row.lat, lng: row.lng } }),
}
}
function toLosses(row) {
return {
bases: { destroyed: row.bases_destroyed, damaged: row.bases_damaged },
personnelCasualties: { killed: row.personnel_killed, wounded: row.personnel_wounded },
civilianCasualties: { killed: row.civilian_killed ?? 0, wounded: row.civilian_wounded ?? 0 },
aircraft: row.aircraft,
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,
carriers: row.carriers ?? row.tanks ?? 0,
civilianShips: row.civilian_ships ?? 0,
airportPort: row.airport_port ?? 0,
}
}
const defaultLosses = {
bases: { destroyed: 0, damaged: 0 },
personnelCasualties: { killed: 0, wounded: 0 },
civilianCasualties: { killed: 0, wounded: 0 },
aircraft: 0,
warships: 0,
armor: 0,
vehicles: 0,
drones: 0,
missiles: 0,
helicopters: 0,
submarines: 0,
carriers: 0,
civilianShips: 0,
airportPort: 0,
}
function getSituation() {
const summaryUs = db.prepare('SELECT * FROM force_summary WHERE side = ?').get('us')
const summaryIr = db.prepare('SELECT * FROM force_summary WHERE side = ?').get('iran')
const powerUs = db.prepare('SELECT * FROM power_index WHERE side = ?').get('us')
const powerIr = db.prepare('SELECT * FROM power_index WHERE side = ?').get('iran')
const assetsUs = db.prepare('SELECT * FROM force_asset WHERE side = ? ORDER BY id').all('us')
const assetsIr = db.prepare('SELECT * FROM force_asset WHERE side = ? ORDER BY id').all('iran')
const locUs = db.prepare('SELECT id, name, lat, lng, type, region, status, damage_level, attacked_at FROM key_location WHERE side = ?').all('us')
const locIr = db.prepare('SELECT id, name, lat, lng, type, region, status, damage_level, attacked_at FROM key_location WHERE side = ?').all('iran')
let mapStrikeSources = []
let mapStrikeLines = []
try {
mapStrikeSources = db.prepare('SELECT id, name, lng, lat FROM map_strike_source').all()
const lines = db.prepare('SELECT source_id, target_lng, target_lat, target_name, struck_at FROM map_strike_line ORDER BY source_id, struck_at').all()
const bySource = {}
for (const row of lines) {
if (!bySource[row.source_id]) bySource[row.source_id] = []
bySource[row.source_id].push({
lng: row.target_lng,
lat: row.target_lat,
name: row.target_name || '',
struck_at: row.struck_at || null,
})
}
mapStrikeLines = Object.entries(bySource).map(([sourceId, targets]) => ({ sourceId, targets }))
} catch (_) {}
const attackedTargets = (locUs || []).filter((l) => l.status === 'attacked').map((l) => [l.lng, l.lat])
const lossesUs = db.prepare('SELECT * FROM combat_losses WHERE side = ?').get('us')
const lossesIr = db.prepare('SELECT * FROM combat_losses WHERE side = ?').get('iran')
const trend = db.prepare('SELECT time, value FROM wall_street_trend ORDER BY time').all()
const retaliationCur = db.prepare('SELECT value FROM retaliation_current WHERE id = 1').get()
const retaliationHist = db.prepare('SELECT time, value FROM retaliation_history ORDER BY time').all()
// 反击情绪无记录时给默认 50避免爬虫未写入时前端显示 0
const retaliationValue = retaliationCur?.value ?? 50
const updates = db.prepare('SELECT * FROM situation_update ORDER BY timestamp DESC LIMIT 50').all()
// 数据更新时间:与前端「实时更新」一致,仅在爬虫 notify / 编辑保存时由 index.js 或 routes 更新
const meta = db.prepare('SELECT updated_at FROM situation WHERE id = 1').get()
let animationConfigRow = null
try {
animationConfigRow = db.prepare('SELECT strike_cutoff_days FROM animation_config WHERE id = 1').get()
} catch (_) {}
let conflictEvents = []
let conflictStats = { total_events: 0, high_impact_events: 0, estimated_casualties: 0, estimated_strike_count: 0 }
try {
conflictEvents = db.prepare('SELECT event_id, event_time, title, lat, lng, impact_score, url FROM gdelt_events ORDER BY event_time DESC LIMIT 30').all()
const statsRow = db.prepare('SELECT total_events, high_impact_events, estimated_casualties, estimated_strike_count FROM conflict_stats WHERE id = 1').get()
if (statsRow) conflictStats = statsRow
} catch (_) {}
// 平民伤亡:合计显示,不区分阵营
const civUsK = lossesUs?.civilian_killed ?? 0
const civUsW = lossesUs?.civilian_wounded ?? 0
const civIrK = lossesIr?.civilian_killed ?? 0
const civIrW = lossesIr?.civilian_wounded ?? 0
const dbKilled = civUsK + civIrK
const dbWounded = civUsW + civIrW
const est = conflictStats.estimated_casualties || 0
const civilianCasualtiesTotal = {
killed: est > 0 ? Math.max(dbKilled, est) : dbKilled,
wounded: dbWounded,
}
const usLossesBase = lossesUs ? toLosses(lossesUs) : defaultLosses
const irLossesBase = lossesIr ? toLosses(lossesIr) : defaultLosses
const usLosses = { ...usLossesBase, civilianCasualties: { killed: 0, wounded: 0 } }
const irLosses = { ...irLossesBase, civilianCasualties: { killed: 0, wounded: 0 } }
return {
lastUpdated: meta?.updated_at || new Date().toISOString(),
usForces: {
summary: {
totalAssets: summaryUs?.total_assets ?? 0,
personnel: summaryUs?.personnel ?? 0,
navalShips: summaryUs?.naval_ships ?? 0,
aircraft: summaryUs?.aircraft ?? 0,
groundUnits: summaryUs?.ground_units ?? 0,
uav: summaryUs?.uav ?? 0,
missileConsumed: summaryUs?.missile_consumed ?? 0,
missileStock: summaryUs?.missile_stock ?? 0,
},
powerIndex: {
overall: powerUs?.overall ?? 0,
militaryStrength: powerUs?.military_strength ?? 0,
economicPower: powerUs?.economic_power ?? 0,
geopoliticalInfluence: powerUs?.geopolitical_influence ?? 0,
},
assets: (assetsUs || []).map(toAsset),
keyLocations: locUs || [],
combatLosses: usLosses,
wallStreetInvestmentTrend: trend || [],
},
iranForces: {
summary: {
totalAssets: summaryIr?.total_assets ?? 0,
personnel: summaryIr?.personnel ?? 0,
navalShips: summaryIr?.naval_ships ?? 0,
aircraft: summaryIr?.aircraft ?? 0,
groundUnits: summaryIr?.ground_units ?? 0,
uav: summaryIr?.uav ?? 0,
missileConsumed: summaryIr?.missile_consumed ?? 0,
missileStock: summaryIr?.missile_stock ?? 0,
},
powerIndex: {
overall: powerIr?.overall ?? 0,
militaryStrength: powerIr?.military_strength ?? 0,
economicPower: powerIr?.economic_power ?? 0,
geopoliticalInfluence: powerIr?.geopolitical_influence ?? 0,
},
assets: (assetsIr || []).map(toAsset),
keyLocations: locIr || [],
combatLosses: irLosses,
retaliationSentiment: retaliationValue,
retaliationSentimentHistory: retaliationHist || [],
},
recentUpdates: (updates || []).map((u) => ({
id: u.id,
timestamp: u.timestamp,
category: u.category,
summary: u.summary,
severity: u.severity,
})),
conflictEvents: conflictEvents.map((e) => ({
event_id: e.event_id,
event_time: e.event_time,
title: e.title,
lat: e.lat,
lng: e.lng,
impact_score: e.impact_score,
url: e.url,
})),
conflictStats,
civilianCasualtiesTotal,
combatLosses: { us: usLosses, iran: irLosses },
mapData: {
attackedTargets,
strikeSources: mapStrikeSources,
strikeLines: mapStrikeLines,
},
animationConfig: {
strikeCutoffDays: animationConfigRow?.strike_cutoff_days ?? 5,
},
}
}
module.exports = { getSituation }