- src/routes.py: 补充火箭炮分析API缺失的8个字段(射速/射程/发动机/机动参数) - AnalysisPage.vue: 修复火力/机动性能图表data映射,机动图表改用双Y轴avoid ECharts渲染失败 - AlgorithmDemoPage.vue: 移除算法卡片中的平均绝对误差和均方根误差显示 - scripts/calculate_features.py: 新增派生特征计算脚本(min-max归一化评分、巡飞弹缺失字段估算)
384 lines
14 KiB
Python
384 lines
14 KiB
Python
"""
|
||
计算并填充数据库中的派生/计算特征字段,包括:
|
||
1. 火箭炮:火力密度、射程比、各项评分
|
||
2. 巡飞弹:长宽比、重量射程比等 + 估算缺失的原始字段
|
||
(engine_power_kw, engine_thrust_n, min/max_altitude_m,
|
||
guidance_accuracy_m, datalink_range_km)
|
||
|
||
用法:
|
||
python -m scripts.calculate_features
|
||
"""
|
||
|
||
import sys
|
||
import os
|
||
import math
|
||
|
||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||
|
||
from src.database.db_connection import get_db_connection, init_db
|
||
from src.logger import setup_logger
|
||
|
||
logger = setup_logger(__name__)
|
||
|
||
|
||
def clamp(value, low=1, high=10):
|
||
return max(low, min(high, value))
|
||
|
||
|
||
def safe_float(val, default=0.0):
|
||
if val is None:
|
||
return default
|
||
return float(val)
|
||
|
||
|
||
# ────────────────────────────────────────────────
|
||
# 火箭炮
|
||
# ────────────────────────────────────────────────
|
||
|
||
def calculate_rocket_features(conn):
|
||
cursor = conn.cursor()
|
||
|
||
cursor.execute("""
|
||
SELECT rap.id, rap.equipment_id,
|
||
rap.rate_of_fire, rap.rocket_weight_kg, rap.rocket_length_m,
|
||
rap.rocket_diameter_mm, rap.min_range_km, rap.max_range_km,
|
||
rap.speed_kmh, rap.power_hp, rap.combat_weight_kg,
|
||
rap.travel_range_km, rap.firing_angle_horizontal,
|
||
rap.firing_angle_vertical,
|
||
cp.length_m, cp.width_m, cp.weight_kg
|
||
FROM rocket_artillery_params rap
|
||
LEFT JOIN common_params cp ON rap.equipment_id = cp.equipment_id
|
||
""")
|
||
|
||
rows = cursor.fetchall()
|
||
|
||
# ── 第一遍:收集原始值,计算 min/max(用于归一化)──
|
||
all_vals = {
|
||
'spd': [], 'power_ratio': [], 'tr': [],
|
||
'angle_cov': [], 'deploy_factor': [], 'rof': [],
|
||
'cw': []
|
||
}
|
||
for row in rows:
|
||
spd = safe_float(row['speed_kmh'])
|
||
pwr = safe_float(row['power_hp'])
|
||
cw = safe_float(row['combat_weight_kg'])
|
||
tr = safe_float(row['travel_range_km'])
|
||
ah = safe_float(row['firing_angle_horizontal'])
|
||
av = safe_float(row['firing_angle_vertical'])
|
||
rof = safe_float(row['rate_of_fire'])
|
||
|
||
all_vals['spd'].append(spd)
|
||
all_vals['power_ratio'].append(pwr / max(1, cw))
|
||
all_vals['tr'].append(tr)
|
||
all_vals['angle_cov'].append((ah / 360) * (av / 90))
|
||
all_vals['deploy_factor'].append(1.0 / max(1, cw)) # 越小越难部署
|
||
all_vals['rof'].append(rof)
|
||
all_vals['cw'].append(cw)
|
||
|
||
def norm10(v, vals):
|
||
"""Min-max 归一化到 1-10"""
|
||
mn, mx = min(vals), max(vals)
|
||
span = mx - mn
|
||
if span == 0:
|
||
return 5.0
|
||
return 1.0 + (v - mn) / span * 9.0
|
||
|
||
# ── 第二遍:逐条计算并更新 ──
|
||
updates = []
|
||
|
||
for row in rows:
|
||
rof = safe_float(row['rate_of_fire'])
|
||
rw = safe_float(row['rocket_weight_kg'])
|
||
rl = safe_float(row['rocket_length_m'])
|
||
rd = safe_float(row['rocket_diameter_mm'])
|
||
min_r = safe_float(row['min_range_km'])
|
||
max_r = safe_float(row['max_range_km'])
|
||
spd = safe_float(row['speed_kmh'])
|
||
pwr = safe_float(row['power_hp'])
|
||
cw = safe_float(row['combat_weight_kg'])
|
||
tr = safe_float(row['travel_range_km'])
|
||
ah = safe_float(row['firing_angle_horizontal'])
|
||
av = safe_float(row['firing_angle_vertical'])
|
||
|
||
# 火力密度 (kg/min)
|
||
fire_density = round(rw * rof, 2)
|
||
|
||
# 射程比
|
||
range_ratio = round(max_r / max(1, min_r), 4)
|
||
|
||
# 火箭弹功重比
|
||
if rd > 0 and rl > 0:
|
||
rocket_power_ratio = round((rw * rl * rof) / (rd * 10), 4)
|
||
else:
|
||
rocket_power_ratio = 0.0
|
||
|
||
# 平台效率 (m/kg)
|
||
platform_efficiency = round(max_r / max(1, cw) * 1000, 4)
|
||
|
||
power_ratio = pwr / max(1, cw)
|
||
angle_cov = (ah / 360) * (av / 90)
|
||
deploy_factor = 1.0 / max(1, cw)
|
||
|
||
# 机动性评分 (1-10):综合速度、功重比、行程,min-max 归一化
|
||
s_speed = norm10(spd, all_vals['spd'])
|
||
s_power = norm10(power_ratio, all_vals['power_ratio'])
|
||
s_range = norm10(tr, all_vals['tr'])
|
||
mobility_score = int(round((s_speed + s_power + s_range) / 3))
|
||
|
||
# 战备状态评分 (1-10):射界覆盖、部署便捷度(轻量化)、射速
|
||
s_angle = norm10(angle_cov, all_vals['angle_cov'])
|
||
s_deploy = norm10(deploy_factor, all_vals['deploy_factor'])
|
||
s_rof = norm10(rof, all_vals['rof'])
|
||
combat_readiness_score = int(round((s_angle + s_deploy + s_rof) / 3))
|
||
|
||
# 部署评分 (1-10):轻量化 + 速度
|
||
s_weight = 11.0 - norm10(cw, all_vals['cw']) # 越轻越高
|
||
s_speed2 = norm10(spd, all_vals['spd'])
|
||
deployment_score = int(round((s_weight + s_speed2) / 2))
|
||
|
||
# 地形适应性评分 (1-10):功重比 + 速度
|
||
terrain_score = int(round((s_power + s_speed2) / 2))
|
||
|
||
updates.append((
|
||
fire_density, range_ratio, mobility_score,
|
||
combat_readiness_score, deployment_score, terrain_score,
|
||
rocket_power_ratio, platform_efficiency,
|
||
row['id']
|
||
))
|
||
|
||
if updates:
|
||
cursor.executemany("""
|
||
UPDATE rocket_artillery_params
|
||
SET fire_density = ?, range_ratio = ?, mobility_score = ?,
|
||
combat_readiness_score = ?, deployment_score = ?,
|
||
terrain_adaptability_score = ?, rocket_power_ratio = ?,
|
||
platform_efficiency = ?
|
||
WHERE id = ?
|
||
""", updates)
|
||
logger.info(f"火箭炮:更新了 {len(updates)} 条记录")
|
||
|
||
return len(updates)
|
||
|
||
|
||
# ────────────────────────────────────────────────
|
||
# 巡飞弹
|
||
# ────────────────────────────────────────────────
|
||
|
||
def estimate_guidance_accuracy(guidance_system):
|
||
"""根据制导系统文字估算制导精度 (m)"""
|
||
gs = (guidance_system or '').lower()
|
||
# 统计制导模式数量作为精度指标
|
||
modes = [m.strip() for m in guidance_system.split('/') if m.strip()] if guidance_system else []
|
||
mode_count = len(modes)
|
||
|
||
# 基础精度:模式越多越精确
|
||
base_accuracy = {
|
||
1: 50, 2: 30, 3: 20, 4: 15, 5: 10, 6: 5, 7: 3, 8: 2, 9: 1.5, 10: 1,
|
||
}.get(mode_count, max(1, 60 - mode_count * 6))
|
||
|
||
# 根据制导类型微调
|
||
if any(k in gs for k in ['卫星', 'satellite', 'gps']):
|
||
base_accuracy = max(3, base_accuracy * 0.7)
|
||
if any(k in gs for k in ['激光', 'laser']):
|
||
base_accuracy = max(1, base_accuracy * 0.4)
|
||
if any(k in gs for k in ['红外', 'infrared', 'ir', '光电']):
|
||
base_accuracy = max(2, base_accuracy * 0.6)
|
||
if any(k in gs for k in ['雷达', 'radar']):
|
||
base_accuracy = max(5, base_accuracy * 0.8)
|
||
if any(k in gs for k in ['ai', '辅助']):
|
||
base_accuracy = base_accuracy * 0.8
|
||
|
||
return round(base_accuracy, 1)
|
||
|
||
|
||
def calculate_guidance_score(guidance_system, estimated_accuracy):
|
||
"""计算制导系统评分 (1-10)"""
|
||
gs = (guidance_system or '').lower()
|
||
modes = [m.strip() for m in guidance_system.split('/') if m.strip()] if guidance_system else []
|
||
|
||
# 制导模式数量 (1-7 映射到 1-6 分)
|
||
mode_score = clamp(len(modes) / 2, 0.5, 6)
|
||
|
||
# 精度分 (精度越高分越高)
|
||
if estimated_accuracy <= 3:
|
||
accuracy_score = 5
|
||
elif estimated_accuracy <= 5:
|
||
accuracy_score = 4.5
|
||
elif estimated_accuracy <= 10:
|
||
accuracy_score = 4
|
||
elif estimated_accuracy <= 20:
|
||
accuracy_score = 3
|
||
elif estimated_accuracy <= 30:
|
||
accuracy_score = 2
|
||
elif estimated_accuracy <= 50:
|
||
accuracy_score = 1
|
||
else:
|
||
accuracy_score = 0.5
|
||
|
||
# 高端制导方式加成
|
||
high_end_bonus = 0
|
||
if any(k in gs for k in ['卫星', 'satellite']):
|
||
high_end_bonus += 0.5
|
||
|
||
return int(round(clamp(mode_score + accuracy_score + high_end_bonus, 1, 10)))
|
||
|
||
|
||
def calculate_warhead_score(warhead_type, warhead_weight_kg):
|
||
"""计算战斗部威力评分 (1-10)"""
|
||
wt_lower = (warhead_type or '').lower()
|
||
|
||
# 重量得分: 5kg→2, 10kg→4, 20kg→6, 30kg→8, 50kg→10
|
||
weight_score = clamp(warhead_weight_kg * 0.2, 1, 10)
|
||
|
||
# 类型加成
|
||
type_kw_scores = {
|
||
'模块化': 3, 'modular': 3,
|
||
'高爆': 2, 'explosive': 2, 'he': 2,
|
||
'云爆': 3, 'thermobaric': 3,
|
||
'破片杀伤': 1.5, 'fragmentation': 1.5,
|
||
'穿甲': 2.5, 'armor': 2.5, 'penetrat': 2.5,
|
||
'动能': 2, 'kinetic': 2,
|
||
'双用': 2, 'dual': 2,
|
||
'破甲': 2, 'heat': 2,
|
||
'子母': 2, 'cluster': 2, 'submunition': 2
|
||
}
|
||
type_bonus = 0
|
||
for kw, val in type_kw_scores.items():
|
||
if kw in wt_lower:
|
||
type_bonus = max(type_bonus, val)
|
||
|
||
return int(round(clamp(weight_score + type_bonus, 1, 10)))
|
||
|
||
|
||
def calculate_loitering_features(conn):
|
||
"""计算巡飞弹的派生特征 + 估算缺失的原始字段"""
|
||
cursor = conn.cursor()
|
||
|
||
cursor.execute("""
|
||
SELECT lmp.id, lmp.equipment_id,
|
||
lmp.wingspan_m, lmp.warhead_weight_kg, lmp.max_speed_ms,
|
||
lmp.max_range_km, lmp.guidance_accuracy_m, lmp.datalink_range_km,
|
||
lmp.engine_power_kw, lmp.engine_thrust_n,
|
||
lmp.min_altitude_m, lmp.max_altitude_m,
|
||
lmp.guidance_system, lmp.warhead_type,
|
||
lmp.cruise_speed_kmh, lmp.endurance_min,
|
||
lmp.ceiling_altitude_m, lmp.max_payload_kg,
|
||
lmp.combat_radius_km,
|
||
cp.length_m, cp.width_m, cp.weight_kg
|
||
FROM loitering_munition_params lmp
|
||
LEFT JOIN common_params cp ON lmp.equipment_id = cp.equipment_id
|
||
""")
|
||
|
||
rows = cursor.fetchall()
|
||
updates = []
|
||
|
||
for row in rows:
|
||
length = safe_float(row['length_m'])
|
||
width = safe_float(row['width_m'])
|
||
weight = safe_float(row['weight_kg'])
|
||
warhead_weight = safe_float(row['warhead_weight_kg'])
|
||
max_speed = safe_float(row['max_speed_ms'])
|
||
cruise_speed = safe_float(row['cruise_speed_kmh'])
|
||
max_range = safe_float(row['max_range_km'])
|
||
endurance = safe_float(row['endurance_min'])
|
||
ceiling = safe_float(row['ceiling_altitude_m'])
|
||
payload = safe_float(row['max_payload_kg'])
|
||
combat_radius = safe_float(row['combat_radius_km'])
|
||
guidance_system = row['guidance_system'] or ''
|
||
warhead_type = row['warhead_type'] or ''
|
||
|
||
# ── 1. 长宽比 ──
|
||
length_width_ratio = round(length / max(0.1, width), 4)
|
||
|
||
# ── 2. 重量射程比 (kg/km) ──
|
||
weight_range_ratio = round(weight / max(1, max_range), 4)
|
||
|
||
# ── 3. 速度重量比 ──
|
||
speed_weight_ratio = round(max_speed / max(1, weight), 4)
|
||
|
||
# ── 4. 估算缺失的原始字段 ──
|
||
|
||
# 制导精度 (m):当前全为 0,根据制导系统文字估算
|
||
estimated_accuracy = estimate_guidance_accuracy(guidance_system)
|
||
|
||
# 数据链距离 (km):用战斗半径或最大射程的 80% 估算
|
||
base_ref = combat_radius if combat_radius > 0 else max_range
|
||
estimated_datalink = round(base_ref * 0.85, 1)
|
||
|
||
# 发动机功率 (kw):基于重量和速度估算
|
||
# 小型无人机/巡飞弹功率密度约 0.05-0.3 kW/kg
|
||
total_weight = weight + payload
|
||
power_density = 0.08 + (max_speed / 200) * 0.12 # 越快功率密度越高
|
||
power_density = min(power_density, 0.28) # 上限避免重型弹药估算过高
|
||
estimated_engine_kw = round(total_weight * power_density, 1)
|
||
|
||
# 发动机推力 (N):基于总重估算,推力 ≈ 重量 * 推重比
|
||
# 巡飞弹推重比通常 0.2-0.5,但重型滑翔弹药不具备大推力发动机
|
||
thrust_ratio = 0.15 + (max_speed / 150) * 0.20
|
||
thrust_ratio = min(thrust_ratio, 0.3) # 上限
|
||
estimated_thrust_n = round(total_weight * 9.8 * thrust_ratio, 1)
|
||
|
||
# 最小作战高度 (m):巡飞弹通常 50-500m
|
||
if cruise_speed > 0:
|
||
estimated_min_alt = round(max(30, min(300, cruise_speed * 0.8)), 0)
|
||
else:
|
||
estimated_min_alt = round(max(30, min(300, max_speed * 2.5)), 0)
|
||
|
||
# 最大作战高度 (m):优先用 ceiling_altitude_m,否则估算
|
||
if ceiling > 0:
|
||
estimated_max_alt = ceiling
|
||
else:
|
||
estimated_max_alt = round(max(500, min(10000, endurance * 15)), 0)
|
||
|
||
# ── 5. 制导系统评分 (1-10) ──
|
||
guidance_system_score = calculate_guidance_score(guidance_system, estimated_accuracy)
|
||
|
||
# ── 6. 战斗部威力评分 (1-10) ──
|
||
warhead_power_score = calculate_warhead_score(warhead_type, warhead_weight)
|
||
|
||
updates.append((
|
||
length_width_ratio, weight_range_ratio, speed_weight_ratio,
|
||
guidance_system_score, warhead_power_score,
|
||
estimated_accuracy, estimated_datalink,
|
||
estimated_engine_kw, estimated_thrust_n,
|
||
estimated_min_alt, estimated_max_alt,
|
||
row['id']
|
||
))
|
||
|
||
if updates:
|
||
cursor.executemany("""
|
||
UPDATE loitering_munition_params
|
||
SET length_width_ratio = ?, weight_range_ratio = ?,
|
||
speed_weight_ratio = ?, guidance_system_score = ?,
|
||
warhead_power_score = ?,
|
||
guidance_accuracy_m = ?, datalink_range_km = ?,
|
||
engine_power_kw = ?, engine_thrust_n = ?,
|
||
min_altitude_m = ?, max_altitude_m = ?
|
||
WHERE id = ?
|
||
""", updates)
|
||
logger.info(f"巡飞弹:更新了 {len(updates)} 条记录")
|
||
|
||
return len(updates)
|
||
|
||
|
||
def main():
|
||
init_db()
|
||
conn = None
|
||
try:
|
||
with get_db_connection() as conn:
|
||
rc = calculate_rocket_features(conn)
|
||
lc = calculate_loitering_features(conn)
|
||
conn.commit()
|
||
print(f"\n完成!")
|
||
print(f" 火箭炮:{rc} 条记录已更新")
|
||
print(f" 巡飞弹:{lc} 条记录已更新")
|
||
except Exception as e:
|
||
if conn:
|
||
conn.rollback()
|
||
logger.error(f"特征计算失败: {e}")
|
||
raise
|
||
|
||
|
||
if __name__ == '__main__':
|
||
main()
|