feat(analyze): 新增 Ploss/Qfod 分析命令

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Scottxjw
2026-08-14 15:36:29 +08:00
parent b88c07356e
commit 0667e2e9b8
8 changed files with 686 additions and 0 deletions

View File

@@ -0,0 +1,144 @@
using System.CommandLine;
using Gpulse.WCT.DataAnalyzer.Core.Application.Analysis;
namespace Gpulse.WCT.DataAnalyzer.Commands;
/// <summary>
/// 数据分析命令 - 支持 Ploss 余量/delta_p 分析与 Qfod 标定阈值分析
/// </summary>
public class AnalyzeCommand : Command
{
public AnalyzeCommand(
PlossAnalysisService plossAnalysisService,
QfodCalibrationService qfodCalibrationService)
: base("analyze", "Analyze Ploss margin or Qfod calibration threshold from log files")
{
var typeOption = new Option<string>(
"--type",
"Analysis type: ploss or qfod"
)
{
IsRequired = true
};
var fileOption = new Option<string?>(
"--file",
"Ploss: path to a log file to analyze"
);
var pureOption = new Option<string?>(
"--pure",
"Qfod: path to the phone-only charging log file"
);
var foreignOption = new Option<string?>(
"--foreign",
"Qfod: path to the phone + foreign object charging log file"
);
AddOption(typeOption);
AddOption(fileOption);
AddOption(pureOption);
AddOption(foreignOption);
this.SetHandler(async (type, file, pure, foreign) =>
{
type = type.ToLower();
switch (type)
{
case "ploss":
await HandlePlossAsync(plossAnalysisService, file);
break;
case "qfod":
await HandleQfodAsync(qfodCalibrationService, pure, foreign);
break;
default:
Console.WriteLine($"Unknown type: {type}. Use 'ploss' or 'qfod'.");
break;
}
}, typeOption, fileOption, pureOption, foreignOption);
}
private static async Task HandlePlossAsync(PlossAnalysisService service, string? file)
{
if (string.IsNullOrEmpty(file))
{
Console.WriteLine("Please specify --file for Ploss analysis");
return;
}
var report = await service.AnalyzeFileAsync(file);
if (!report.IsSuccess)
{
Console.WriteLine($" [{report.FileName}] FAILED: {report.ErrorMessage}");
return;
}
Console.WriteLine($"\n=== Ploss Analysis: {report.FileName} ===");
Console.WriteLine($"Total: {report.TotalCount} (TwoLine: {report.TwoLineCount}, Legacy: {report.LegacyCount}), FOD: {report.FodCount}, Errors: {report.ErrorCount}");
var normalRatio = Percent(report.NormalAboveCount, report.NormalCount);
var fodRatio = Percent(report.FodAboveCount, report.FodCount);
Console.WriteLine($"Normal margin>2000: {report.NormalAboveCount}/{report.NormalCount} ({normalRatio}%)");
Console.WriteLine($"FOD margin>2000: {report.FodAboveCount}/{report.FodCount} ({fodRatio}%)");
Console.WriteLine($"Identity (delta_p = Tx - Rx - pow_loss - ploss): {report.IdentityHeldCount}/{report.IdentityCheckedCount}");
if (report.FodRows.Count == 0)
{
Console.WriteLine("No FOD rows (Field12 == 1).");
return;
}
Console.WriteLine($"\nFOD rows (delta_p back-solve):");
foreach (var row in report.FodRows)
{
var format = row.IsTwoLine ? "two-line" : "legacy";
Console.WriteLine($" Line {row.LineNumber,-6} [{format}] pow_loss={row.PowLoss?.ToString() ?? "-"} delta_p={row.DeltaP?.ToString() ?? "-"} ploss={row.Ploss} threshold={row.Threshold} margin={row.Margin}");
if (row.IsTwoLine && row.DeltaPPrime.HasValue)
{
Console.WriteLine($" -> delta_p'={row.DeltaPPrime.Value}");
Console.WriteLine($" rewrite: {row.RewrittenHeader}");
}
else
{
Console.WriteLine($" (旧单行格式无 header无法反解 delta_p)");
}
}
}
private static async Task HandleQfodAsync(QfodCalibrationService service, string? pure, string? foreign)
{
if (string.IsNullOrEmpty(pure) || string.IsNullOrEmpty(foreign))
{
Console.WriteLine("Please specify --pure and --foreign for Qfod calibration");
return;
}
var report = await service.CalibrateAsync(pure, foreign);
if (!report.IsSuccess)
{
Console.WriteLine($" FAILED: {report.ErrorMessage}");
return;
}
Console.WriteLine("\n=== Qfod Calibration ===");
PrintQfodFileSummary("Pure phone", report.Pure!);
PrintQfodFileSummary("Foreign object", report.Foreign!);
Console.WriteLine($"\nThreshold = (pureMax + foreignMin) / 2 = ({report.Pure!.Max} + {report.Foreign!.Min}) / 2 = {report.Threshold}");
Console.WriteLine($"Margin: pureMax - threshold = {report.PureMargin} (负为安全), foreignMin - threshold = {report.ForeignMargin} (正为安全)");
}
private static void PrintQfodFileSummary(string label, QfodFileSummary summary)
{
Console.WriteLine($" {label}: {summary.FileName}");
Console.WriteLine($" count={summary.Count} min={summary.Min} max={summary.Max} avg={summary.Average:F1}");
}
private static string Percent(int part, int total)
=> total == 0 ? "0.0" : (part * 100.0 / total).ToString("F1");
}