refactor(ploss): 统一字段语义 Field9=ploss Field10=threshold
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -28,15 +28,15 @@ FOD-> (1) (2) (3) (4) (5) (6) (7) (8) (9) (10) (11) (12) (13) (14) (15) (16)
|
||||
| (1) | Field1 | int | 待定义 |
|
||||
| (2) | Field2 | int | 待定义 |
|
||||
| (3) | Field3 | int | 待定义 |
|
||||
| (4) | Field4 | int | 待定义 |
|
||||
| (5) | Field5 | int | 待定义 |
|
||||
| (4) | Field4 | int | RX power - 接收端功率 |
|
||||
| (5) | Field5 | int | TX power - 发射端功率 |
|
||||
| (6) | Field6 | int | 待定义 |
|
||||
| (7) | Field7 | int | 待定义 |
|
||||
| (8) | Field8 | int | 待定义 |
|
||||
| (9) | Field9 | int | 待定义 |
|
||||
| (10) | Field10 | int | 待定义 |
|
||||
| (11) | Field11 | int | 待定义 |
|
||||
| (12) | Field12 | int | 待定义 |
|
||||
| (9) | Field9 | int | ploss - 功率损耗计算值 |
|
||||
| (10) | Field10 | int | threshold - 阈值 |
|
||||
| (11) | Field11 | int | 触发次数 - 当 Field9 > Field10(ploss 大于阈值)时计数,超过5次触发FOD保护 |
|
||||
| (12) | Field12 | int | PFOD result 标志 - 触发时置位 |
|
||||
| (13) | Field13 | int | 待定义 |
|
||||
| (14) | Field14 | int | 待定义 |
|
||||
| (15) | Field15 | int | 待定义 |
|
||||
@@ -99,127 +99,78 @@ private static readonly Regex FodPattern = new(
|
||||
|
||||
## 4. 解析器实现
|
||||
|
||||
> 当前实现位于 `src/Gpulse.WCT.DataAnalyzer.Core/Application/Parsing/PlossParser.cs`,实现 `IParser<PlossRecord>` 接口。
|
||||
|
||||
解析分两步:
|
||||
|
||||
1. **单行识别**:`TryMatch(line)` 依次尝试 `HeaderPattern` → `FodPattern` → `LegacyPattern`,返回匹配类型(`None/Header/Fod/Legacy`),避免重复正则匹配。
|
||||
2. **多行合并**:`ParseService` 检测到 header 行后暂存,下一行为 Ploss 行时调用 `ParseMultiLine([header, fod], scenarioId)` 合并为一条完整记录;文件末尾残留的 header 行记为解析错误。
|
||||
|
||||
```csharp
|
||||
// Parsers/PlossParser.cs
|
||||
using System.Text.RegularExpressions;
|
||||
using Gpulse.WCT.DataAnalyzer.Models;
|
||||
public enum MatchType { None, Header, Fod, Legacy }
|
||||
|
||||
namespace Gpulse.WCT.DataAnalyzer.Parsers;
|
||||
|
||||
public class PlossParser : IParser<PlossRecord>
|
||||
public (MatchType Type, Match Match) TryMatch(string line)
|
||||
{
|
||||
private static readonly Regex HeaderPattern = new(
|
||||
@"^pow_loss\s*=\s*(\d+),\s*delta_p\s*=\s*(\d+)$",
|
||||
RegexOptions.Compiled
|
||||
);
|
||||
var headerMatch = HeaderPattern.Match(line);
|
||||
if (headerMatch.Success) return (MatchType.Header, headerMatch);
|
||||
|
||||
private static readonly Regex FodPattern = new(
|
||||
@"^FOD->\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)$",
|
||||
RegexOptions.Compiled
|
||||
);
|
||||
var fodMatch = FodPattern.Match(line);
|
||||
if (fodMatch.Success) return (MatchType.Fod, fodMatch);
|
||||
|
||||
public bool CanParse(string line) => HeaderPattern.IsMatch(line) || FodPattern.IsMatch(line);
|
||||
var legacyMatch = LegacyPattern.Match(line);
|
||||
if (legacyMatch.Success) return (MatchType.Legacy, legacyMatch);
|
||||
|
||||
public ParseResult<PlossRecord> Parse(string line, Guid scenarioId)
|
||||
{
|
||||
// 尝试匹配第一行
|
||||
var headerMatch = HeaderPattern.Match(line);
|
||||
if (headerMatch.Success)
|
||||
{
|
||||
return ParseHeaderLine(headerMatch, scenarioId);
|
||||
}
|
||||
return (MatchType.None, Match.Empty);
|
||||
}
|
||||
|
||||
// 尝试匹配第二行
|
||||
var fodMatch = FodPattern.Match(line);
|
||||
if (fodMatch.Success)
|
||||
{
|
||||
return ParseFodLine(fodMatch, scenarioId);
|
||||
}
|
||||
public bool CanParse(string line) => TryMatch(line).Type != MatchType.None;
|
||||
public bool IsMultiLineStart(string line) => TryMatch(line).Type == MatchType.Header;
|
||||
|
||||
return ParseResult<PlossRecord>.Failure(
|
||||
"FORMAT_MISMATCH",
|
||||
"Line does not match Ploss pattern"
|
||||
);
|
||||
}
|
||||
public ParseResult<PlossRecord>? ParseMultiLine(string[] lines, Guid scenarioId)
|
||||
{
|
||||
if (lines.Length < 2 || lines[0] == null || lines[1] == null)
|
||||
return null;
|
||||
|
||||
private ParseResult<PlossRecord> ParseHeaderLine(Match match, Guid scenarioId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var record = new PlossRecord
|
||||
{
|
||||
ScenarioId = scenarioId,
|
||||
PowLoss = int.Parse(match.Groups[1].Value),
|
||||
DeltaP = int.Parse(match.Groups[2].Value),
|
||||
IsHeaderLine = true
|
||||
};
|
||||
return ParseResult<PlossRecord>.Success(record);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return ParseResult<PlossRecord>.Failure(
|
||||
"TYPE_CONVERSION",
|
||||
$"Failed to convert header field: {ex.Message}"
|
||||
);
|
||||
}
|
||||
}
|
||||
var headerMatch = HeaderPattern.Match(lines[0].Trim());
|
||||
var fodMatch = FodPattern.Match(lines[1].Trim());
|
||||
if (!headerMatch.Success || !fodMatch.Success)
|
||||
return null;
|
||||
|
||||
private ParseResult<PlossRecord> ParseFodLine(Match match, Guid scenarioId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var record = new PlossRecord
|
||||
{
|
||||
ScenarioId = scenarioId,
|
||||
Field1 = int.Parse(match.Groups[1].Value),
|
||||
Field2 = int.Parse(match.Groups[2].Value),
|
||||
Field3 = int.Parse(match.Groups[3].Value),
|
||||
Field4 = int.Parse(match.Groups[4].Value),
|
||||
Field5 = int.Parse(match.Groups[5].Value),
|
||||
Field6 = int.Parse(match.Groups[6].Value),
|
||||
Field7 = int.Parse(match.Groups[7].Value),
|
||||
Field8 = int.Parse(match.Groups[8].Value),
|
||||
Field9 = int.Parse(match.Groups[9].Value),
|
||||
Field10 = int.Parse(match.Groups[10].Value),
|
||||
Field11 = int.Parse(match.Groups[11].Value),
|
||||
Field12 = int.Parse(match.Groups[12].Value),
|
||||
Field13 = int.Parse(match.Groups[13].Value),
|
||||
Field14 = int.Parse(match.Groups[14].Value),
|
||||
Field15 = int.Parse(match.Groups[15].Value),
|
||||
Field16 = int.Parse(match.Groups[16].Value),
|
||||
IsHeaderLine = false
|
||||
};
|
||||
return ParseResult<PlossRecord>.Success(record);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return ParseResult<PlossRecord>.Failure(
|
||||
"TYPE_CONVERSION",
|
||||
$"Failed to convert FOD field: {ex.Message}"
|
||||
);
|
||||
}
|
||||
}
|
||||
// 第一行 header 组1/组2 → PowLoss/DeltaP
|
||||
// 第二行 FOD 组1~16 → Field1~Field16
|
||||
return ParseResult<PlossRecord>.Success(new PlossRecord { /* ... */ });
|
||||
}
|
||||
```
|
||||
|
||||
> **注意**:实际实现中,两行日志需要合并为一条完整记录。可以使用 `PlossRecordMerger` 或在业务逻辑层处理合并。
|
||||
**多行合并(ParseMultiLine)捕获组映射:**
|
||||
|
||||
| 来源 | 捕获组 | 字段 |
|
||||
|------|--------|------|
|
||||
| 第一行 header | Group 1 / Group 2 | PowLoss / DeltaP |
|
||||
| 第二行 FOD | Group 1 ~ Group 16 | Field1 ~ Field16 |
|
||||
|
||||
> **旧单行格式**:无 header 行,`Field1~13` 直接来自 `FOD->` 后 13 个数字,`Field14~16` 补默认值 0,`PowLoss/DeltaP` 为 null。字段语义以 `PlossRecord` 实体注释为准。
|
||||
|
||||
---
|
||||
|
||||
## 5. 核心判定逻辑
|
||||
|
||||
> 字段语义以 `PlossRecord` 实体注释为准:`Field9` = ploss(功率损耗计算值)、`Field10` = threshold(阈值)、`Field11` = 触发次数。
|
||||
|
||||
### 5.1 FOD 触发条件
|
||||
|
||||
```
|
||||
if (Ploss > Threshold) → 计数器累积
|
||||
if (Count > Limit) → 触发 FOD 报警,停止充电
|
||||
if (Field9 > Field10) → Field11 计数累积
|
||||
if (Field11 > 5) → 触发 FOD 报警,停止充电
|
||||
```
|
||||
|
||||
> **说明**:触发条件统一为 `Field9 > Field10`(ploss > threshold),实体 `Field11` 注释已同步此口径。
|
||||
|
||||
### 5.2 安全余量计算
|
||||
|
||||
```csharp
|
||||
// 派生属性(不落库)
|
||||
Margin = Threshold - Ploss
|
||||
// 派生属性(不落库) = Field10 - Field9
|
||||
Margin = Field10 - Field9
|
||||
```
|
||||
|
||||
| Margin 状态 | 含义 |
|
||||
|
||||
Reference in New Issue
Block a user