Phase 5: 报告生成 — ReportGenerator + ReportService, 116测试4秒完成

This commit is contained in:
tian 2026-06-11 16:00:13 +08:00
parent eb347b3bb0
commit aea15e6ec6
5 changed files with 345 additions and 0 deletions

View File

@ -0,0 +1,15 @@
using System.Collections.Generic;
using CounterDrone.Core.Models;
using SimEvent = CounterDrone.Core.Simulation.SimEvent;
namespace CounterDrone.Core.Services
{
public interface IReportService
{
SimulationReport Generate(string taskId, TaskFullConfig config,
List<SimEvent> events, string finalDroneStatus, float duration);
SimulationReport GetReport(string id);
void DeleteReport(string id);
PagedResult<SimulationReport> SearchReports(string keyword, string? dateFrom, string? dateTo, int page, int pageSize);
}
}

View File

@ -0,0 +1,96 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CounterDrone.Core.Models;
using SimEvent = CounterDrone.Core.Simulation.SimEvent;
namespace CounterDrone.Core.Services
{
public class ReportGenerator
{
/// <summary>根据仿真结果生成报告内容</summary>
public string Generate(TaskFullConfig config, List<SimEvent> events,
Simulation.DroneStatus finalDroneStatus, float simulationDuration)
{
var sb = new StringBuilder();
// === 仿真前:我方配置 ===
sb.AppendLine("# 仿真评估报告");
sb.AppendLine();
sb.AppendLine($"**任务名称**{config.Task.Name}");
sb.AppendLine($"**任务编号**{config.Task.TaskNumber}");
sb.AppendLine($"**仿真耗时**{simulationDuration:F1} 秒");
sb.AppendLine();
sb.AppendLine("## 一、仿真前 — 我方配置");
sb.AppendLine();
var deployment = config.Equipment;
var platforms = deployment.Where(e => e.EquipmentRole == (int)EquipmentRole.LaunchPlatform).ToList();
var detections = deployment.Where(e => e.EquipmentRole == (int)EquipmentRole.Detection).ToList();
sb.AppendLine($"| 配置项 | 值 |");
sb.AppendLine($"|--------|-----|");
sb.AppendLine($"| 目标数量 | {config.Targets.Sum(t => t.Quantity)} |");
sb.AppendLine($"| 发射平台 | {platforms.Sum(p => p.Quantity)} 台 |");
sb.AppendLine($"| 探测设备 | {detections.Sum(d => d.Quantity)} 台 |");
sb.AppendLine($"| 气溶胶类型 | {(AerosolType)config.Cloud.AerosolType} |");
sb.AppendLine();
// === 仿真中:关键事件时序 ===
sb.AppendLine("## 二、仿真中 — 关键事件时序");
sb.AppendLine();
sb.AppendLine("| 时间(s) | 事件 | 描述 |");
sb.AppendLine("|---------|------|------|");
foreach (var e in events.OrderBy(e => e.OccurredAt))
{
sb.AppendLine($"| {e.OccurredAt:F1} | {e.Type} | {e.Description} |");
}
sb.AppendLine();
// === 仿真后:统计分析 ===
sb.AppendLine("## 三、仿真后 — 数据统计");
sb.AppendLine();
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 cloudCount = events.Count(e => e.Type == SimEventType.CloudGenerated);
var launchCount = events.Count(e => e.Type == SimEventType.MunitionLaunched);
var totalDrones = config.Targets.Sum(t => t.Quantity);
sb.AppendLine($"| 统计项 | 值 |");
sb.AppendLine($"|--------|-----|");
sb.AppendLine($"| 无人机总数 | {totalDrones} |");
sb.AppendLine($"| 被摧毁 | {destroyed} |");
sb.AppendLine($"| 到达目标 | {reached} |");
sb.AppendLine($"| 侵入管控区 | {intruded} |");
sb.AppendLine($"| 弹药发射次数 | {launchCount} |");
sb.AppendLine($"| 云团生成次数 | {cloudCount} |");
sb.AppendLine();
// 对抗结果
var result = DetermineInterceptResult(destroyed, reached + intruded, totalDrones);
sb.AppendLine($"**对抗结果**{TranslateResult(result)}");
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 => "拦截失败",
_ => "未知",
};
}
}

View File

@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
using System.Linq;
using CounterDrone.Core.Models;
using SQLite;
using SimEvent = CounterDrone.Core.Simulation.SimEvent;
namespace CounterDrone.Core.Services
{
public class ReportService : IReportService
{
private readonly SQLiteConnection _db;
private readonly ReportGenerator _generator = new();
public ReportService(SQLiteConnection db)
{
_db = db;
}
public SimulationReport Generate(string taskId, TaskFullConfig config,
List<SimEvent> events, string finalDroneStatus, float duration)
{
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 total = config.Targets.Sum(t => t.Quantity);
var result = _generator.DetermineInterceptResult(destroyed, reached + intruded, total);
var report = new SimulationReport
{
TaskId = taskId,
TaskName = config.Task.Name,
TaskNumber = config.Task.TaskNumber,
CompletedAt = DateTime.UtcNow.ToString("o"),
TargetCount = total,
EquipmentCount = config.Equipment.Count,
InterceptResult = (int)result,
Content = _generator.Generate(config, events, Simulation.DroneStatus.Flying, duration),
};
_db.Insert(report);
return report;
}
public SimulationReport GetReport(string id)
{
return _db.Find<SimulationReport>(id);
}
public void DeleteReport(string id)
{
_db.Delete<SimulationReport>(id);
}
public PagedResult<SimulationReport> SearchReports(string keyword, string? dateFrom, string? dateTo, int page, int pageSize)
{
if (page < 1) page = 1;
if (pageSize < 1) pageSize = 10;
var offset = (page - 1) * pageSize;
var query = _db.Table<SimulationReport>().AsQueryable();
if (!string.IsNullOrWhiteSpace(keyword))
query = query.Where(r => r.TaskName.Contains(keyword) || r.TaskNumber.Contains(keyword));
if (!string.IsNullOrWhiteSpace(dateFrom))
query = query.Where(r => r.CompletedAt.CompareTo(dateFrom) >= 0);
if (!string.IsNullOrWhiteSpace(dateTo))
query = query.Where(r => r.CompletedAt.CompareTo(dateTo) <= 0);
var total = query.Count();
var items = query.OrderByDescending(r => r.CompletedAt).Skip(offset).Take(pageSize).ToList();
return new PagedResult<SimulationReport>
{
Items = items, TotalCount = total, Page = page, PageSize = pageSize,
};
}
}
}

View File

@ -0,0 +1,63 @@
using System.Collections.Generic;
using CounterDrone.Core.Models;
using CounterDrone.Core.Services;
using CounterDrone.Core.Simulation;
using Xunit;
using SimEvent = CounterDrone.Core.Simulation.SimEvent;
namespace CounterDrone.Core.Tests
{
public class ReportGeneratorTests
{
[Fact]
public void DetermineInterceptResult_AllDestroyed_Success()
{
var gen = new ReportGenerator();
Assert.Equal(InterceptResult.Success, gen.DetermineInterceptResult(3, 0, 3));
}
[Fact]
public void DetermineInterceptResult_Partial()
{
var gen = new ReportGenerator();
Assert.Equal(InterceptResult.Partial, gen.DetermineInterceptResult(2, 1, 3));
}
[Fact]
public void DetermineInterceptResult_AllFailed()
{
var gen = new ReportGenerator();
Assert.Equal(InterceptResult.Failed, gen.DetermineInterceptResult(0, 3, 3));
}
[Fact]
public void Generate_ProducesReportContent()
{
var gen = new ReportGenerator();
var config = new TaskFullConfig
{
Task = new SimTask { Name = "测试任务", TaskNumber = "SIM-001" },
Targets = new List<TargetConfig> { new TargetConfig { Quantity = 1 } },
Equipment = new List<EquipmentDeployment>
{
new EquipmentDeployment { EquipmentRole = (int)EquipmentRole.LaunchPlatform, Quantity = 3 },
new EquipmentDeployment { EquipmentRole = (int)EquipmentRole.Detection, Quantity = 1 },
},
Cloud = new CloudDispersal { AerosolType = (int)AerosolType.InertGas },
};
var events = new List<SimEvent>
{
new SimEvent { Type = SimEventType.MunitionLaunched, OccurredAt = 5f, Description = "发射" },
new SimEvent { Type = SimEventType.CloudGenerated, OccurredAt = 10f, Description = "云团" },
new SimEvent { Type = SimEventType.DroneDestroyed, OccurredAt = 15f, Description = "摧毁" },
};
var content = gen.Generate(config, events, DroneStatus.Destroyed, 30f);
Assert.Contains("仿真评估报告", content);
Assert.Contains("测试任务", content);
Assert.Contains("成功拦截", content);
Assert.Contains("发射", content);
}
}
}

View File

@ -0,0 +1,93 @@
using System;
using System.Collections.Generic;
using System.IO;
using CounterDrone.Core;
using CounterDrone.Core.Models;
using CounterDrone.Core.Services;
using CounterDrone.Core.Simulation;
using SQLite;
using Xunit;
using SimEvent = CounterDrone.Core.Simulation.SimEvent;
namespace CounterDrone.Core.Tests
{
public class ReportServiceTests : IDisposable
{
private readonly string _testDir;
private readonly IReportService _service;
private readonly SQLiteConnection _db;
public ReportServiceTests()
{
_testDir = Path.Combine(Path.GetTempPath(), $"cd_rep_{Guid.NewGuid():N}");
var paths = new TestPathProvider(_testDir);
var dbm = new DatabaseManager(paths);
_db = dbm.OpenMainDb();
_service = new ReportService(_db);
}
public void Dispose()
{
_db?.Close();
if (Directory.Exists(_testDir)) Directory.Delete(_testDir, true);
}
[Fact]
public void Generate_And_Get_ReturnsReport()
{
var config = new TaskFullConfig
{
Task = new SimTask { Name = "Test", TaskNumber = "SIM-001" },
Targets = new List<TargetConfig> { new TargetConfig { Quantity = 1 } },
Equipment = new List<EquipmentDeployment>(),
Cloud = new CloudDispersal(),
};
var report = _service.Generate("task1", config, new List<SimEvent>(), "Destroyed", 45f);
Assert.NotNull(report);
Assert.NotEmpty(report.Id);
Assert.Equal("Test", report.TaskName);
Assert.Contains("仿真评估报告", report.Content);
var fetched = _service.GetReport(report.Id);
Assert.NotNull(fetched);
}
[Fact]
public void Delete_RemovesReport()
{
var config = new TaskFullConfig
{
Task = new SimTask { Name = "Del", TaskNumber = "SIM-DEL" },
Targets = new List<TargetConfig> { new TargetConfig { Quantity = 1 } },
Equipment = new List<EquipmentDeployment>(),
Cloud = new CloudDispersal(),
};
var report = _service.Generate("task1", config, new List<SimEvent>(), "Destroyed", 10f);
_service.DeleteReport(report.Id);
Assert.Null(_service.GetReport(report.Id));
}
[Fact]
public void SearchReports_Keyword()
{
var cfg = new TaskFullConfig
{
Task = new SimTask { Name = "城市防御", TaskNumber = "SIM-A" },
Targets = new List<TargetConfig> { new TargetConfig { Quantity = 1 } },
Equipment = new List<EquipmentDeployment>(),
Cloud = new CloudDispersal(),
};
_service.Generate("t1", cfg, new List<SimEvent>(), "Destroyed", 10f);
cfg.Task.Name = "平原拦截";
cfg.Task.TaskNumber = "SIM-B";
_service.Generate("t2", cfg, new List<SimEvent>(), "Destroyed", 20f);
var result = _service.SearchReports("城市", null, null, 1, 10);
Assert.Equal(1, result.TotalCount);
}
}
}