CounterDroneBackend/src/CounterDrone.Core/Services/ReportGenerator.cs
tian 69ec9ed0e2 ScenarioDrone/ScenarioUnit 表精简:外键引用基础数据,去冗余字段
- ScenarioDrone: 仅保留 Id/ScenarioId/DroneSpecId/WaveId/Quantity
- ScenarioUnit: 仅保留 Id/ScenarioId/FireUnitSpecId/SensorSpecId/Position 等想定字段
- DroneEntity/PlatformEntity 构造函数改为接收 DroneSpec/FireUnitSpec
- SimulationEngine 加载 spec 字典,build 方法通过 FK 查 spec
- ReportGenerator/ReportService 通过 spec 字典取值
- DefaultLaneDivider.Divide 从 DroneWave.Quantity 取值
- Core 编译通过,测试待修复
2026-06-18 16:47:30 +08:00

325 lines
17 KiB
C#
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.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CounterDrone.Core;
using CounterDrone.Core.Models;
using CounterDrone.Core.Simulation;
namespace CounterDrone.Core.Services
{
public class ReportGenerator
{
public string Generate(ScenarioConfig config, List<SimEvent> events,
Simulation.DroneStatus finalDroneStatus, float simulationDuration,
Dictionary<string, DroneSpec> droneSpecs,
Dictionary<string, FireUnitSpec> fireUnitSpecs,
Dictionary<string, SensorSpec> sensorSpecs)
{
var sb = new StringBuilder();
var platforms = config.Units.Where(e => e.EquipmentRole == (int)EquipmentRole.LaunchPlatform).ToList();
var target = config.Drones.FirstOrDefault();
var scene = config.Scene;
var destroyed = events.Count(e => e.Type == SimEventType.DroneDestroyed);
var reached = events.Count(e => e.Type == SimEventType.DroneReachedTarget);
var intruded = events.Count(e => e.Type == SimEventType.ZoneIntruded);
var totalDrones = config.Drones.Sum(t => t.Quantity);
var result = DetermineInterceptResult(destroyed, reached + intruded, totalDrones);
sb.AppendLine("# 仿真评估报告");
sb.AppendLine();
sb.AppendLine($"| 项目 | 值 |");
sb.AppendLine($"|------|-----|");
sb.AppendLine($"| 任务名称 | {config.Info.Name} |");
sb.AppendLine($"| 任务编号 | {config.Info.ScenarioNumber} |");
sb.AppendLine($"| 仿真耗时 | {simulationDuration:F1} 秒 |");
sb.AppendLine($"| 气溶胶类型 | {TranslateAerosol((AerosolType)config.Cloud.AerosolType)} |");
sb.AppendLine();
// === 一、仿真结果 ===
sb.AppendLine("## 一、仿真结果");
sb.AppendLine();
sb.AppendLine($"| 统计项 | 值 |");
sb.AppendLine($"|--------|-----|");
sb.AppendLine($"| 无人机总数 | {totalDrones} |");
sb.AppendLine($"| 被摧毁 | {destroyed} |");
sb.AppendLine($"| 到达目标 | {reached} |");
sb.AppendLine($"| 侵入管控区 | {intruded} |");
sb.AppendLine($"| 弹药发射 | {events.Count(e => e.Type == SimEventType.MunitionLaunched)} |");
sb.AppendLine($"| 目标发现 | {events.Count(e => e.Type == SimEventType.TargetDetected)} |");
sb.AppendLine($"| 云团生成 | {events.Count(e => e.Type == SimEventType.CloudGenerated)} |");
sb.AppendLine();
sb.AppendLine($"**对抗结果**{TranslateResult(result)}");
sb.AppendLine();
// === 二、作战环境 ===
sb.AppendLine("## 二、作战环境");
sb.AppendLine();
sb.AppendLine($"| 参数 | 值 |");
sb.AppendLine($"|------|-----|");
sb.AppendLine($"| 场景类型 | {TranslateScene((SceneType)scene.SceneType)} |");
sb.AppendLine($"| 时间段 | {scene.TimeOfDay} |");
sb.AppendLine($"| 天气 | {TranslateWeather((WeatherType)scene.WeatherType)} |");
sb.AppendLine($"| 风速 | {scene.WindSpeed:F1} m/s |");
sb.AppendLine($"| 风向 | {TranslateWindDirection((WindDirection)scene.WindDirection)} |");
sb.AppendLine($"| 温度 | {scene.Temperature:F1}°C |");
sb.AppendLine($"| 湿度 | {scene.Humidity:F0}%RH |");
sb.AppendLine($"| 气压 | {scene.Pressure:F0} hPa |");
sb.AppendLine($"| 能见度 | {scene.Visibility:F0} m |");
sb.AppendLine($"| 场景范围 | {scene.SceneWidth:F0} × {scene.SceneLength:F0} × {scene.SceneHeight:F0} m (W×L×H) |");
sb.AppendLine();
// === 三、威胁目标 ===
sb.AppendLine("## 三、威胁目标");
sb.AppendLine();
sb.AppendLine($"| 参数 | 值 |");
sb.AppendLine($"|------|-----|");
// Resolve specs
droneSpecs.TryGetValue(target?.DroneSpecId ?? "", out var droneSpec);
sb.AppendLine($"| 类型 | {TranslateTarget((DroneType)(droneSpec?.DroneType ?? 0))} |");
sb.AppendLine($"| 动力 | {TranslatePower((PowerType)(droneSpec?.PowerType ?? 0))} |");
sb.AppendLine($"| 数量 | {totalDrones} 架 |");
sb.AppendLine($"| 翼展 | {droneSpec?.Wingspan ?? 0:F1} m |");
sb.AppendLine($"| 速度 | {droneSpec?.TypicalSpeed ?? 0:F0} km/h |");
sb.AppendLine($"| 高度 | {droneSpec?.TypicalAltitude ?? 0:F0} m |");
sb.AppendLine();
// === 四、云团抛撒配置 ===
sb.AppendLine("## 四、云团抛撒配置");
sb.AppendLine();
sb.AppendLine($"| 参数 | 值 |");
sb.AppendLine($"|------|-----|");
sb.AppendLine($"| 气溶胶类型 | {TranslateAerosol((AerosolType)config.Cloud.AerosolType)} |");
sb.AppendLine($"| 抛撒高度 | {config.Cloud.DisperseHeight:F0} m |");
sb.AppendLine($"| 触发模式 | {TranslateTrigger((TriggerMode)config.Cloud.TriggerMode)} |");
sb.AppendLine($"| 释放方式 | {TranslateRelease((ReleaseMode)config.Cloud.ReleaseMode)} |");
sb.AppendLine($"| 扩散方向 | {config.Cloud.SpreadAngle:F0}° |");
sb.AppendLine($"| 初始规模 | {config.Cloud.InitialScale:F0} m³ |");
sb.AppendLine($"| 持续时间 | {config.Cloud.Duration:F0} s |");
sb.AppendLine($"| 释放总量 | {config.Cloud.TotalAmount:F1} kg |");
sb.AppendLine($"| 配置来源 | {config.Cloud.Source} |");
if (config.Cloud.Source == "Algorithm" && config.Cloud.EstimatedProbability.HasValue)
{
sb.AppendLine($"| **算法推荐** | |");
sb.AppendLine($"| 推荐弹药数 | {config.Cloud.SalvoRounds} 发 |");
sb.AppendLine($"| 云团间距 | {config.Cloud.SalvoSpacing:F0} m |");
sb.AppendLine($"| 拦截概率估计 | {config.Cloud.EstimatedProbability.Value:P0} |");
sb.AppendLine($"| 抛撒时机 | {config.Cloud.RecommendedTiming:F0}s |");
}
sb.AppendLine();
// 构建 PlatformId → 火力单元 映射(去重,五和六共用)
var platToEquip = new Dictionary<string, int>();
var unitLaunchCounts = new Dictionary<int, int>();
int platIdx = 1, unitNum = 0;
for (int ei = 0; ei < config.Units.Count; ei++)
{
var eq = config.Units[ei];
if (eq.EquipmentRole != (int)EquipmentRole.LaunchPlatform) continue;
fireUnitSpecs.TryGetValue(eq.FireUnitSpecId, out var fuSpec);
int channels = (fuSpec?.GunCount ?? 1) * (fuSpec?.ChannelsPerGun ?? 1);
for (int j = 0; j < eq.Quantity; j++)
{
unitNum++;
for (int c = 0; c < channels; c++)
platToEquip[$"plat_{platIdx++}"] = unitNum;
}
}
foreach (var e in events.Where(e => e.Type == SimEventType.MunitionLaunched))
{
if (platToEquip.TryGetValue(e.SourceId, out var u))
unitLaunchCounts[u] = unitLaunchCounts.GetValueOrDefault(u, 0) + 1;
}
// === 五、我方部署 ===
sb.AppendLine("## 五、我方部署");
sb.AppendLine();
sb.AppendLine($"### 发射平台");
sb.AppendLine();
sb.AppendLine($"| # | 平台类型 | 位置 (X,Y,Z) | 弹药类型 | 挂载量 | 发射数 |");
sb.AppendLine($"|---|---------|-------------|---------|:------:|:------:|");
int unitNum2 = 0;
foreach (var eq in config.Units.Where(e => e.EquipmentRole == (int)EquipmentRole.LaunchPlatform))
{
fireUnitSpecs.TryGetValue(eq.FireUnitSpecId, out var fuSpec);
int channels = (fuSpec?.GunCount ?? 1) * (fuSpec?.ChannelsPerGun ?? 1);
for (int j = 0; j < eq.Quantity; j++)
{
unitNum2++;
int fired = unitLaunchCounts.GetValueOrDefault(unitNum2, 0);
string ptype = ((PlatformType?)(fuSpec?.PlatformType ?? 0)) == PlatformType.AirBased ? "空基" : "地基";
string ammo = eq.AerosolType.HasValue ? TranslateAerosol((AerosolType)eq.AerosolType) : "-";
sb.AppendLine($"| #{unitNum2} | {ptype} | ({eq.PositionX + j * 50:F0}, {eq.PositionY:F0}, {eq.PositionZ:F0}) | {ammo} | {eq.MunitionCount ?? channels} | {fired} |");
}
}
sb.AppendLine();
// 探测设备
var detections = config.Units.Where(e => e.EquipmentRole == (int)EquipmentRole.Detection).ToList();
if (detections.Count > 0)
{
sb.AppendLine($"### 探测设备");
sb.AppendLine();
sb.AppendLine($"| # | 位置 (X,Y,Z) | 雷达 | 光电 | 红外 | 精度 | 数量 |");
sb.AppendLine($"|---|-------------|:----:|:----:|:----:|:----:|:----:|");
int detIdx = 0;
foreach (var d in detections)
{
sensorSpecs.TryGetValue(d.SensorSpecId, out var sensor);
for (int j = 0; j < d.Quantity; j++)
sb.AppendLine($"| D{++detIdx} | ({d.PositionX:F0}, {d.PositionY:F0}, {d.PositionZ:F0}) | {(sensor?.RadarRange ?? 0):F0} | {(sensor?.EORange ?? 0):F0} | {(sensor?.IRRange ?? 0):F0} | {(sensor?.Accuracy ?? 0):F0} m | 1 |");
}
sb.AppendLine();
}
// 管控区域
if (config.ControlZones.Count > 0)
{
sb.AppendLine($"### 管控区域");
sb.AppendLine();
sb.AppendLine($"| # | 名称 | 高度范围 |");
sb.AppendLine($"|---|------|---------|");
int zIdx = 0;
foreach (var z in config.ControlZones)
sb.AppendLine($"| Z{++zIdx} | {z.Name} | {(int)z.MinAltitude}-{(int)z.MaxAltitude} m |");
sb.AppendLine();
}
// === 六、事件时序 ===
sb.AppendLine("## 六、事件时序");
sb.AppendLine();
sb.AppendLine("| 时间(s) | 事件 | 详情 | 火力单元 |");
sb.AppendLine("|---------|------|------|:------:|");
int munitionIdx = 0, cloudIdx = 0;
foreach (var e in events.OrderBy(e => e.OccurredAt))
{
string unitCell = e.Type == SimEventType.MunitionLaunched && platToEquip.TryGetValue(e.SourceId, out var u)
? $"#{u}" : "-";
// 编号前缀 + 引擎生成的 Description含坐标等调试信息
string prefix = e.Type switch
{
SimEventType.MunitionLaunched => $"第 {++munitionIdx} 发",
SimEventType.CloudGenerated => $"云团 #{++cloudIdx}",
_ => string.Empty,
};
string desc = string.IsNullOrEmpty(prefix) ? e.Description : $"{prefix} · {e.Description}";
sb.AppendLine($"| {e.OccurredAt:F2} | {TranslateEvent(e.Type)} | {desc} | {unitCell} |");
}
sb.AppendLine();
// === 七、时间线 ===
var launches = events.Where(e => e.Type == SimEventType.MunitionLaunched).Select(e => e.OccurredAt).OrderBy(t => t).ToList();
var clouds = events.Where(e => e.Type == SimEventType.CloudGenerated).Select(e => e.OccurredAt).OrderBy(t => t).ToList();
var destroys = events.Where(e => e.Type == SimEventType.DroneDestroyed).Select(e => e.OccurredAt).OrderBy(t => t).ToList();
float launchFirst = launches.DefaultIfEmpty(0).FirstOrDefault();
float launchLast = launches.DefaultIfEmpty(0).LastOrDefault();
float cloudFirst = clouds.DefaultIfEmpty(0).FirstOrDefault();
float destroyFirst = destroys.DefaultIfEmpty(0).FirstOrDefault();
sb.AppendLine("## 七、时间线");
sb.AppendLine();
sb.AppendLine($"| 阶段 | 时间 | 耗时 |");
sb.AppendLine($"|------|------|------|");
if (launchFirst > 0)
{
sb.AppendLine($"| 首发发射 | {launchFirst:F1}s | - |");
if (launches.Count > 1)
sb.AppendLine($"| 末发发射 | {launchLast:F1}s | {launchLast - launchFirst:F1}s弹群展开 |");
sb.AppendLine($"| 发射 → 云团生成 | {launchFirst:F1}s → {cloudFirst:F1}s | {cloudFirst - launchFirst:F1}s弹药飞行 |");
}
if (destroyFirst > 0)
sb.AppendLine($"| 云团生成 → 首个击毁 | {cloudFirst:F1}s → {destroyFirst:F1}s | {destroyFirst - cloudFirst:F1}s暴露毁伤 |");
sb.AppendLine($"| 总耗时 | 0 → {simulationDuration:F1}s | {simulationDuration:F1}s |");
sb.AppendLine();
// 汇总统计(调试用)
sb.AppendLine("**统计汇总**");
sb.AppendLine($"- 弹药发射 {launches.Count} 发,云团生成 {clouds.Count} 朵,击毁 {destroys.Count} 架");
if (launchFirst > 0 && launchLast > 0)
sb.AppendLine($"- 发射窗口 {launchLast - launchFirst:F1}s弹药飞行 {(cloudFirst - launchFirst):F1}s");
sb.AppendLine();
return sb.ToString();
}
public InterceptResult DetermineInterceptResult(int destroyed, int reachedOrIntruded, int total)
{
if (total == 0) return InterceptResult.Failed;
if (destroyed >= total) return InterceptResult.Success;
if (destroyed > 0) return InterceptResult.Partial;
return InterceptResult.Failed;
}
private static string TranslateResult(InterceptResult r) => r switch
{
InterceptResult.Success => "✅ 成功拦截",
InterceptResult.Partial => "⚠️ 部分拦截",
InterceptResult.Failed => "❌ 拦截失败",
_ => "未知",
};
private static string TranslateAerosol(AerosolType t) => t switch
{
AerosolType.InertGas => "惰性气体(吸入式灭火)",
AerosolType.ActiveMaterial => "活性材料(爆燃式)",
AerosolType.ActiveFuel => "活性燃料(吸入式爆炸)",
_ => t.ToString(),
};
private static string TranslateWeather(WeatherType w) => w switch
{
WeatherType.Sunny => "晴天", WeatherType.Overcast => "阴天",
WeatherType.Fog => "雾天", WeatherType.Rain => "雨天",
WeatherType.Night => "夜间", _ => w.ToString(),
};
private static string TranslateWindDirection(WindDirection d) => d switch
{
WindDirection.N => "北 (N)", WindDirection.NE => "东北 (NE)",
WindDirection.E => "东 (E)", WindDirection.SE => "东南 (SE)",
WindDirection.S => "南 (S)", WindDirection.SW => "西南 (SW)",
WindDirection.W => "西 (W)", WindDirection.NW => "西北 (NW)",
_ => d.ToString(),
};
private static string TranslateScene(SceneType s) => s switch
{
SceneType.Urban => "城市", SceneType.Plain => "平原",
SceneType.Mountain => "山区", SceneType.Coast => "海岸",
_ => s.ToString(),
};
private static string TranslateTrigger(TriggerMode t) => t switch
{
TriggerMode.Time => "时间触发", TriggerMode.Area => "区域触发",
TriggerMode.Manual => "手动触发", _ => t.ToString(),
};
private static string TranslateTarget(DroneType t) => t switch
{
DroneType.Rotor => "旋翼", DroneType.FixedWing => "固定翼",
DroneType.HighSpeed => "高速目标", _ => t.ToString(),
};
private static string TranslatePower(PowerType p) => p switch
{
PowerType.Electric => "电推式", PowerType.Piston => "活塞式",
PowerType.Jet => "喷吸气式", _ => p.ToString(),
};
private static string TranslateRelease(ReleaseMode r) => r switch
{
ReleaseMode.Single => "单次齐射", ReleaseMode.Continuous => "连续释放",
ReleaseMode.Pulse => "脉冲释放", _ => r.ToString(),
};
private static string TranslateEvent(SimEventType t) => t switch
{
SimEventType.MunitionLaunched => "🚀 弹药发射",
SimEventType.CloudGenerated => "☁️ 云团生成",
SimEventType.DroneDestroyed => "💥 目标击毁",
SimEventType.DroneReachedTarget => "🏁 抵达目标",
SimEventType.ZoneIntruded => "🚨 侵入管控区",
SimEventType.SimulationEnd => "⏹️ 仿真结束",
SimEventType.TargetDetected => "👁️ 目标发现",
_ => t.ToString(),
};
}
}