196 lines
7.8 KiB
JavaScript
196 lines
7.8 KiB
JavaScript
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 }
|