feat(analyze): 新增 Ploss/Qfod 分析命令
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
144
src/Gpulse.WCT.DataAnalyzer/Commands/AnalyzeCommand.cs
Normal file
144
src/Gpulse.WCT.DataAnalyzer/Commands/AnalyzeCommand.cs
Normal 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");
|
||||
}
|
||||
@@ -7,6 +7,7 @@ using Gpulse.WCT.DataAnalyzer.Commands;
|
||||
using Gpulse.WCT.DataAnalyzer.Core.Infrastructure.LocalData;
|
||||
using Gpulse.WCT.DataAnalyzer.Core.Infrastructure.Extensions;
|
||||
using Gpulse.WCT.DataAnalyzer.Core.Application;
|
||||
using Gpulse.WCT.DataAnalyzer.Core.Application.Analysis;
|
||||
|
||||
namespace Gpulse.WCT.DataAnalyzer;
|
||||
|
||||
@@ -70,6 +71,9 @@ public class Program
|
||||
rootCommand.AddCommand(new AggregateCommand(provider.GetRequiredService<AggregationService>()));
|
||||
rootCommand.AddCommand(new ExportCommand(provider.GetRequiredService<ExportService>()));
|
||||
rootCommand.AddCommand(new CleanCommand(provider.GetRequiredService<CleanService>()));
|
||||
rootCommand.AddCommand(new AnalyzeCommand(
|
||||
provider.GetRequiredService<PlossAnalysisService>(),
|
||||
provider.GetRequiredService<QfodCalibrationService>()));
|
||||
|
||||
return await rootCommand.InvokeAsync(args);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user