refactor(ploss): 统一字段语义 Field9=ploss Field10=threshold

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Scottxjw
2026-08-14 15:35:48 +08:00
parent dea7af045b
commit b88c07356e
5 changed files with 185 additions and 241 deletions

View File

@@ -28,15 +28,15 @@ FOD-> (1) (2) (3) (4) (5) (6) (7) (8) (9) (10) (11) (12) (13) (14) (15) (16)
| (1) | Field1 | int | 待定义 | | (1) | Field1 | int | 待定义 |
| (2) | Field2 | int | 待定义 | | (2) | Field2 | int | 待定义 |
| (3) | Field3 | int | 待定义 | | (3) | Field3 | int | 待定义 |
| (4) | Field4 | int | 待定义 | | (4) | Field4 | int | RX power - 接收端功率 |
| (5) | Field5 | int | 待定义 | | (5) | Field5 | int | TX power - 发射端功率 |
| (6) | Field6 | int | 待定义 | | (6) | Field6 | int | 待定义 |
| (7) | Field7 | int | 待定义 | | (7) | Field7 | int | 待定义 |
| (8) | Field8 | int | 待定义 | | (8) | Field8 | int | 待定义 |
| (9) | Field9 | int | 待定义 | | (9) | Field9 | int | ploss - 功率损耗计算值 |
| (10) | Field10 | int | 待定义 | | (10) | Field10 | int | threshold - 阈值 |
| (11) | Field11 | int | 待定义 | | (11) | Field11 | int | 触发次数 - 当 Field9 > Field10ploss 大于阈值时计数超过5次触发FOD保护 |
| (12) | Field12 | int | 待定义 | | (12) | Field12 | int | PFOD result 标志 - 触发时置位 |
| (13) | Field13 | int | 待定义 | | (13) | Field13 | int | 待定义 |
| (14) | Field14 | int | 待定义 | | (14) | Field14 | int | 待定义 |
| (15) | Field15 | int | 待定义 | | (15) | Field15 | int | 待定义 |
@@ -99,127 +99,78 @@ private static readonly Regex FodPattern = new(
## 4. 解析器实现 ## 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 ```csharp
// Parsers/PlossParser.cs public enum MatchType { None, Header, Fod, Legacy }
using System.Text.RegularExpressions;
using Gpulse.WCT.DataAnalyzer.Models;
namespace Gpulse.WCT.DataAnalyzer.Parsers; public (MatchType Type, Match Match) TryMatch(string line)
public class PlossParser : IParser<PlossRecord>
{ {
private static readonly Regex HeaderPattern = new( var headerMatch = HeaderPattern.Match(line);
@"^pow_loss\s*=\s*(\d+),\s*delta_p\s*=\s*(\d+)$", if (headerMatch.Success) return (MatchType.Header, headerMatch);
RegexOptions.Compiled
);
private static readonly Regex FodPattern = new( var fodMatch = FodPattern.Match(line);
@"^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+)$", if (fodMatch.Success) return (MatchType.Fod, fodMatch);
RegexOptions.Compiled
);
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) return (MatchType.None, Match.Empty);
{ }
// 尝试匹配第一行
var headerMatch = HeaderPattern.Match(line);
if (headerMatch.Success)
{
return ParseHeaderLine(headerMatch, scenarioId);
}
// 尝试匹配第二行 public bool CanParse(string line) => TryMatch(line).Type != MatchType.None;
var fodMatch = FodPattern.Match(line); public bool IsMultiLineStart(string line) => TryMatch(line).Type == MatchType.Header;
if (fodMatch.Success)
{
return ParseFodLine(fodMatch, scenarioId);
}
return ParseResult<PlossRecord>.Failure( public ParseResult<PlossRecord>? ParseMultiLine(string[] lines, Guid scenarioId)
"FORMAT_MISMATCH", {
"Line does not match Ploss pattern" if (lines.Length < 2 || lines[0] == null || lines[1] == null)
); return null;
}
private ParseResult<PlossRecord> ParseHeaderLine(Match match, Guid scenarioId) var headerMatch = HeaderPattern.Match(lines[0].Trim());
{ var fodMatch = FodPattern.Match(lines[1].Trim());
try if (!headerMatch.Success || !fodMatch.Success)
{ return null;
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}"
);
}
}
private ParseResult<PlossRecord> ParseFodLine(Match match, Guid scenarioId) // 第一行 header 组1/组2 → PowLoss/DeltaP
{ // 第二行 FOD 组1~16 → Field1~Field16
try return ParseResult<PlossRecord>.Success(new PlossRecord { /* ... */ });
{
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}"
);
}
}
} }
``` ```
> **注意**:实际实现中,两行日志需要合并为一条完整记录。可以使用 `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. 核心判定逻辑 ## 5. 核心判定逻辑
> 字段语义以 `PlossRecord` 实体注释为准:`Field9` = ploss功率损耗计算值、`Field10` = threshold阈值、`Field11` = 触发次数。
### 5.1 FOD 触发条件 ### 5.1 FOD 触发条件
``` ```
if (Ploss > Threshold) → 计数累积 if (Field9 > Field10) → Field11 计数累积
if (Count > Limit) → 触发 FOD 报警,停止充电 if (Field11 > 5) → 触发 FOD 报警,停止充电
``` ```
> **说明**:触发条件统一为 `Field9 > Field10`ploss > threshold实体 `Field11` 注释已同步此口径。
### 5.2 安全余量计算 ### 5.2 安全余量计算
```csharp ```csharp
// 派生属性(不落库) // 派生属性(不落库) = Field10 - Field9
Margin = Threshold - Ploss Margin = Field10 - Field9
``` ```
| Margin 状态 | 含义 | | Margin 状态 | 含义 |

View File

@@ -8,34 +8,44 @@
**表名**`ploss_record` **表名**`ploss_record`
**描述**:存储 Ploss FOD 格式解析数据,对应日志格式 `FOD-> (1)...(13)` **描述**:存储 Ploss FOD 格式解析数据,对应两行日志格式
```
pow_loss = X, delta_p = Y
FOD-> (1) (2) ... (16)
```
--- ---
## 2. 字段定义 ## 2. 字段定义
> **类型修正说明**:需求文档中 rx_power, tx_power, vcoil, vin, isns, pwm_duty 为 `uint16`0-65535但 SQLite 不支持无符号类型。实际数据如 vcoil=35377 超出了 SMALLINT-32768~32767范围因此这些字段使用 INT 类型存储。
| 列名 | 类型 | 约束 | 说明 | | 列名 | 类型 | 约束 | 说明 |
|------|------|------|------| |------|------|------|------|
| id | UUID | PK, NOT NULL | 主键 | | id | UUID | PK, NOT NULL | 主键 |
| scenario_id | UUID | FK, NOT NULL | 关联测试场景 | | scenario_id | UUID | FK, NOT NULL | 关联测试场景 |
| rx_type | TINYINT | NOT NULL | 接收端类型标识 | | pow_loss | INT | NULL | 第一行:功率损耗值 |
| rx_power | INT | NOT NULL | 接收端功率mW原 uint16 | | delta_p | INT | NULL | 第一行:功率差值 |
| tx_power | INT | NOT NULL | 发射端功率mW原 uint16 | | field_1 | INT | NOT NULL | Field1语义待定义 |
| vcoil | INT | NOT NULL | 发射线圈电压,原 uint16 | | field_2 | INT | NOT NULL | Field2语义待定义 |
| vin | INT | NOT NULL | 输入电压,原 uint16 | | field_3 | INT | NOT NULL | Field3语义待定义 |
| isns | INT | NOT NULL | 输入电流采样值,原 uint16 | | field_4 | INT | NOT NULL | Field4: RX power - 接收端功率 |
| ploss | INT | NOT NULL | **核心值**:计算损耗(负数=安全) | | field_5 | INT | NOT NULL | Field5: TX power - 发射端功率 |
| threshold | INT | NOT NULL | **安全红线**:当前功率段阈值 | | field_6 | INT | NOT NULL | Field6语义待定义 |
| trigger_count | INT | NOT NULL, DEFAULT 0 | 触发计数器 | | field_7 | INT | NOT NULL | Field7语义待定义 |
| fod_result | INT | NOT NULL, DEFAULT 0 | FOD判定结果 | | field_8 | INT | NOT NULL | Field8语义待定义 |
| protocol_type | TINYINT | NOT NULL | 充电协议类型 | | field_9 | INT | NOT NULL, DEFAULT 0 | Field9: ploss - 功率损耗计算值 |
| pwm_duty | INT | NOT NULL | PWM占空比原 uint16 | | field_10 | INT | NOT NULL, DEFAULT 0 | Field10: threshold - 阈值 |
| coil_index | TINYINT | NOT NULL | 充电线圈索引 | | field_11 | INT | NOT NULL | Field11: 触发次数 - 当 Field9 > Field10 时计数超过5次触发FOD保护 |
| field_12 | INT | NOT NULL | Field12: PFOD result 标志 - 触发时置位 |
| field_13 | INT | NOT NULL, DEFAULT 0 | Field13语义待定义 |
| field_14 | INT | NOT NULL, DEFAULT 0 | Field14语义待定义 |
| field_15 | INT | NOT NULL, DEFAULT 0 | Field15语义待定义 |
| field_16 | INT | NOT NULL, DEFAULT 0 | Field16语义待定义 |
| is_deleted | BOOLEAN | NOT NULL, DEFAULT FALSE | 软删除标记 | | is_deleted | BOOLEAN | NOT NULL, DEFAULT FALSE | 软删除标记 |
| created_at | TIMESTAMP | NOT NULL, DEFAULT NOW() | 创建时间 | | created_at | TIMESTAMP | NOT NULL, DEFAULT NOW() | 创建时间 |
> **字段语义**以 `PlossRecord` 实体注释为准。
--- ---
## 3. 外键 ## 3. 外键
@@ -52,9 +62,9 @@
| 字段 | 计算公式 | 说明 | | 字段 | 计算公式 | 说明 |
|------|----------|------| |------|----------|------|
| margin | threshold - ploss | 安全余量(>0 安全,<0 危险) | | margin | field_10 - field_9 | 安全余量(>0 安全,<0 危险) |
> **使用限制**`Margin` 属性通过 EF Core `Ignore()` 排除映射,仅用于内存计算。不可在数据库查询中直接使用(如 `WHERE Margin > 0`),需改用 `WHERE (threshold - ploss) > 0` 或先加载到内存再计算。 > **使用限制**`Margin` 属性通过 EF Core `Ignore()` 排除映射,仅用于内存计算。不可在数据库查询中直接使用(如 `WHERE Margin > 0`),需改用 `WHERE (field_10 - field_9) > 0` 或先加载到内存再计算。
--- ---
@@ -63,10 +73,12 @@
| 索引名 | 字段 | 用途 | | 索引名 | 字段 | 用途 |
|--------|------|------| |--------|------|------|
| idx_ploss_scenario | scenario_id | 按测试场景查询 | | idx_ploss_scenario | scenario_id | 按测试场景查询 |
| idx_ploss_rx_power | rx_power | 阈值表匹配 | | idx_ploss_field_2 | field_2 | 字段查询 |
| idx_ploss_ploss | ploss | 损耗值查询 | | idx_ploss_field_7 | field_7 | 字段查询 |
| idx_ploss_threshold | threshold | 阈值查询 | | idx_ploss_field_8 | field_8 | 字段查询 |
| idx_ploss_fod_result | fod_result | FOD结果筛选 | | idx_ploss_field_10 | field_10 | 字段查询 |
| idx_ploss_pow_loss | pow_loss | 第一行损耗值查询 |
| idx_ploss_delta_p | delta_p | 第一行功率差值查询 |
--- ---
@@ -75,51 +87,73 @@
### 实体类 ### 实体类
```csharp ```csharp
// Models/PlossRecord.cs // Domain/Local/PlossRecord.cs
namespace Gpulse.WCT.DataAnalyzer.Models; namespace Gpulse.WCT.DataAnalyzer.Core.Domain.Local;
public class PlossRecord public class PlossRecord
{ {
public Guid Id { get; set; } = Guid.NewGuid(); public Guid Id { get; set; } = Guid.NewGuid();
public Guid ScenarioId { get; set; } public Guid ScenarioId { get; set; }
public byte RxType { get; set; }
public short RxPower { get; set; }
public short TxPower { get; set; }
public short Vcoil { get; set; }
public short Vin { get; set; }
public short Isns { get; set; }
/// <summary>核心判定值:负数表示安全</summary> // ===== 第一行字段nullable兼容旧数据 =====
public int Ploss { get; set; } /// <summary>功率损耗值(来自第一行)</summary>
public int? PowLoss { get; set; }
/// <summary>安全红线:当前功率段阈值</summary> /// <summary>功率差值(来自第一行)</summary>
public int Threshold { get; set; } public int? DeltaP { get; set; }
// ===== 第二行字段16个通用字段均支持负数 =====
public int Field1 { get; set; }
public int Field2 { get; set; }
public int Field3 { get; set; }
/// <summary>Field4: RX power - 接收端功率</summary>
public int Field4 { get; set; }
/// <summary>Field5: TX power - 发射端功率</summary>
public int Field5 { get; set; }
public int Field6 { get; set; }
public int Field7 { get; set; }
public int Field8 { get; set; }
/// <summary>Field9: ploss - 功率损耗计算值</summary>
public int Field9 { get; set; }
/// <summary>Field10: threshold - 阈值</summary>
public int Field10 { get; set; }
/// <summary>Field11: 触发次数 - 当 Field9 > Field10ploss 大于阈值时计数超过5次触发FOD保护</summary>
public int Field11 { get; set; }
/// <summary>Field12: PFOD result 标志 - 触发时置位</summary>
public int Field12 { get; set; }
public int Field13 { get; set; }
public int Field14 { get; set; }
public int Field15 { get; set; }
public int Field16 { get; set; }
public short TriggerCount { get; set; } = 0;
public short FodResult { get; set; } = 0;
public byte ProtocolType { get; set; }
public short PwmDuty { get; set; }
public byte CoilIndex { get; set; }
public bool IsDeleted { get; set; } = false; public bool IsDeleted { get; set; } = false;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow; public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
// Navigation Properties // Navigation Properties
public TestScenario Scenario { get; set; } = null!; public TestScenario Scenario { get; set; } = null!;
/// <summary>派生属性:安全余量(不落库)</summary> /// <summary>派生属性:安全余量(不落库) = Field10 - Field9</summary>
public int Margin => Threshold - Ploss; public int Margin => Field10 - Field9;
} }
``` ```
### 配置类 ### 配置类
```csharp ```csharp
// Data/Configurations/PlossRecordConfiguration.cs // Infrastructure/LocalData/Configurations/PlossRecordConfiguration.cs
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Gpulse.WCT.DataAnalyzer.Models; using Gpulse.WCT.DataAnalyzer.Core.Domain.Local;
namespace Gpulse.WCT.DataAnalyzer.Data.Configurations; namespace Gpulse.WCT.DataAnalyzer.Core.Infrastructure.LocalData.Configurations;
public class PlossRecordConfiguration : IEntityTypeConfiguration<PlossRecord> public class PlossRecordConfiguration : IEntityTypeConfiguration<PlossRecord>
{ {
@@ -134,59 +168,27 @@ public class PlossRecordConfiguration : IEntityTypeConfiguration<PlossRecord>
.IsRequired() .IsRequired()
.HasColumnName("scenario_id"); .HasColumnName("scenario_id");
builder.Property(e => e.RxType) // ===== 第一行字段nullable =====
.IsRequired() builder.Property(e => e.PowLoss).IsRequired(false).HasColumnName("pow_loss");
.HasColumnName("rx_type"); builder.Property(e => e.DeltaP).IsRequired(false).HasColumnName("delta_p");
builder.Property(e => e.RxPower) // ===== 第二行字段(通用命名) =====
.IsRequired() builder.Property(e => e.Field1).IsRequired().HasColumnName("field_1");
.HasColumnName("rx_power"); builder.Property(e => e.Field2).IsRequired().HasColumnName("field_2");
builder.Property(e => e.Field3).IsRequired().HasColumnName("field_3");
builder.Property(e => e.TxPower) builder.Property(e => e.Field4).IsRequired().HasColumnName("field_4");
.IsRequired() builder.Property(e => e.Field5).IsRequired().HasColumnName("field_5");
.HasColumnName("tx_power"); builder.Property(e => e.Field6).IsRequired().HasColumnName("field_6");
builder.Property(e => e.Field7).IsRequired().HasColumnName("field_7");
builder.Property(e => e.Vcoil) builder.Property(e => e.Field8).IsRequired().HasColumnName("field_8");
.IsRequired() builder.Property(e => e.Field9).IsRequired().HasDefaultValue(0).HasColumnName("field_9");
.HasColumnName("vcoil"); builder.Property(e => e.Field10).IsRequired().HasDefaultValue(0).HasColumnName("field_10");
builder.Property(e => e.Field11).IsRequired().HasColumnName("field_11");
builder.Property(e => e.Vin) builder.Property(e => e.Field12).IsRequired().HasColumnName("field_12");
.IsRequired() builder.Property(e => e.Field13).IsRequired().HasDefaultValue(0).HasColumnName("field_13");
.HasColumnName("vin"); builder.Property(e => e.Field14).IsRequired().HasDefaultValue(0).HasColumnName("field_14");
builder.Property(e => e.Field15).IsRequired().HasDefaultValue(0).HasColumnName("field_15");
builder.Property(e => e.Isns) builder.Property(e => e.Field16).IsRequired().HasDefaultValue(0).HasColumnName("field_16");
.IsRequired()
.HasColumnName("isns");
builder.Property(e => e.Ploss)
.IsRequired()
.HasColumnName("ploss");
builder.Property(e => e.Threshold)
.IsRequired()
.HasColumnName("threshold");
builder.Property(e => e.TriggerCount)
.IsRequired()
.HasDefaultValue((short)0)
.HasColumnName("trigger_count");
builder.Property(e => e.FodResult)
.IsRequired()
.HasDefaultValue((short)0)
.HasColumnName("fod_result");
builder.Property(e => e.ProtocolType)
.IsRequired()
.HasColumnName("protocol_type");
builder.Property(e => e.PwmDuty)
.IsRequired()
.HasColumnName("pwm_duty");
builder.Property(e => e.CoilIndex)
.IsRequired()
.HasColumnName("coil_index");
builder.Property(e => e.IsDeleted) builder.Property(e => e.IsDeleted)
.IsRequired() .IsRequired()
@@ -195,7 +197,7 @@ public class PlossRecordConfiguration : IEntityTypeConfiguration<PlossRecord>
builder.Property(e => e.CreatedAt) builder.Property(e => e.CreatedAt)
.IsRequired() .IsRequired()
.HasDefaultValueSql("NOW()") .HasDefaultValueSql("datetime('now')")
.HasColumnName("created_at"); .HasColumnName("created_at");
// 派生属性不落库 // 派生属性不落库
@@ -203,16 +205,21 @@ public class PlossRecordConfiguration : IEntityTypeConfiguration<PlossRecord>
// 索引 // 索引
builder.HasIndex(e => e.ScenarioId).HasDatabaseName("idx_ploss_scenario"); builder.HasIndex(e => e.ScenarioId).HasDatabaseName("idx_ploss_scenario");
builder.HasIndex(e => e.RxPower).HasDatabaseName("idx_ploss_rx_power"); builder.HasIndex(e => e.Field2).HasDatabaseName("idx_ploss_field_2");
builder.HasIndex(e => e.Ploss).HasDatabaseName("idx_ploss_ploss"); builder.HasIndex(e => e.Field7).HasDatabaseName("idx_ploss_field_7");
builder.HasIndex(e => e.Threshold).HasDatabaseName("idx_ploss_threshold"); builder.HasIndex(e => e.Field8).HasDatabaseName("idx_ploss_field_8");
builder.HasIndex(e => e.FodResult).HasDatabaseName("idx_ploss_fod_result"); builder.HasIndex(e => e.Field10).HasDatabaseName("idx_ploss_field_10");
builder.HasIndex(e => e.PowLoss).HasDatabaseName("idx_ploss_pow_loss");
builder.HasIndex(e => e.DeltaP).HasDatabaseName("idx_ploss_delta_p");
// 关系配置 // 关系配置
builder.HasOne(e => e.Scenario) builder.HasOne(e => e.Scenario)
.WithMany(e => e.PlossRecords) .WithMany(e => e.PlossRecords)
.HasForeignKey(e => e.ScenarioId) .HasForeignKey(e => e.ScenarioId)
.IsRequired(); .OnDelete(DeleteBehavior.Cascade);
// 全局查询过滤器
builder.HasQueryFilter(e => !e.IsDeleted);
} }
} }
``` ```
@@ -221,7 +228,7 @@ public class PlossRecordConfiguration : IEntityTypeConfiguration<PlossRecord>
## 7. 动态阈值表 ## 7. 动态阈值表
> 阈值根据 RxPower 动态查表(不落库) > 阈值根据 RxPower 动态查表(不落库,业务规则
| Rx Power 区间 | Threshold | 备注 | | Rx Power 区间 | Threshold | 备注 |
|---------------|-----------|------| |---------------|-----------|------|
@@ -230,15 +237,3 @@ public class PlossRecordConfiguration : IEntityTypeConfiguration<PlossRecord>
| 10000-15000 mW | 750 mW | | | 10000-15000 mW | 750 mW | |
| 15000-30000 mW | 1000 mW | | | 15000-30000 mW | 1000 mW | |
| 30000-50000 mW | 1250 mW | 高功率 | | 30000-50000 mW | 1250 mW | 高功率 |
```csharp
// Services/ThresholdCalculator.cs
public static int GetThresholdByPower(int rxPower)
{
if (rxPower <= 5000) return 350;
if (rxPower <= 10000) return 500;
if (rxPower <= 15000) return 750;
if (rxPower <= 30000) return 1000;
return 1250;
}
```

View File

@@ -144,19 +144,19 @@ public class PlossParser : IParser<PlossRecord>
PowLoss = null, PowLoss = null,
DeltaP = null, DeltaP = null,
// 映射到新字段名 // 映射到新字段名
Field1 = int.Parse(match.Groups[1].Value), // RxType Field1 = int.Parse(match.Groups[1].Value),
Field2 = int.Parse(match.Groups[2].Value), // RxPower Field2 = int.Parse(match.Groups[2].Value),
Field3 = int.Parse(match.Groups[3].Value), // TxPower Field3 = int.Parse(match.Groups[3].Value),
Field4 = int.Parse(match.Groups[4].Value), // Vcoil Field4 = int.Parse(match.Groups[4].Value), // RX power
Field5 = int.Parse(match.Groups[5].Value), // Vin Field5 = int.Parse(match.Groups[5].Value), // TX power
Field6 = int.Parse(match.Groups[6].Value), // Isns Field6 = int.Parse(match.Groups[6].Value),
Field7 = int.Parse(match.Groups[7].Value), // Ploss Field7 = int.Parse(match.Groups[7].Value),
Field8 = int.Parse(match.Groups[8].Value), // Threshold Field8 = int.Parse(match.Groups[8].Value),
Field9 = int.Parse(match.Groups[9].Value), // TriggerCount Field9 = int.Parse(match.Groups[9].Value), // ploss
Field10 = int.Parse(match.Groups[10].Value), // FodResult Field10 = int.Parse(match.Groups[10].Value), // threshold
Field11 = int.Parse(match.Groups[11].Value), // ProtocolType Field11 = int.Parse(match.Groups[11].Value), // 触发次数
Field12 = int.Parse(match.Groups[12].Value), // PwmDuty Field12 = int.Parse(match.Groups[12].Value), // PFOD result
Field13 = int.Parse(match.Groups[13].Value), // CoilIndex Field13 = int.Parse(match.Groups[13].Value),
// 旧格式没有Field14-16使用默认值 // 旧格式没有Field14-16使用默认值
Field14 = 0, Field14 = 0,
Field15 = 0, Field15 = 0,

View File

@@ -19,7 +19,6 @@ public class PlossRecord
public int? DeltaP { get; set; } public int? DeltaP { get; set; }
// ===== 第二行字段16个通用字段均支持负数 ===== // ===== 第二行字段16个通用字段均支持负数 =====
/// <summary>Field1: RX_TYPE - 接收端类型</summary>
public int Field1 { get; set; } public int Field1 { get; set; }
public int Field2 { get; set; } public int Field2 { get; set; }
@@ -33,10 +32,9 @@ public class PlossRecord
public int Field6 { get; set; } public int Field6 { get; set; }
/// <summary>核心判定值Field7 (原Ploss) - 负数表示安全</summary>
public int Field7 { get; set; } public int Field7 { get; set; }
/// <summary>安全红线Field8 (原Threshold) - 当前功率段阈值</summary>
public int Field8 { get; set; } public int Field8 { get; set; }
/// <summary>Field9: ploss - 功率损耗计算值</summary> /// <summary>Field9: ploss - 功率损耗计算值</summary>
@@ -45,7 +43,7 @@ public class PlossRecord
/// <summary>Field10: threshold - 阈值</summary> /// <summary>Field10: threshold - 阈值</summary>
public int Field10 { get; set; } public int Field10 { get; set; }
/// <summary>Field11: 触发次数 - 当 Field7 > Field8损耗大于阈值时计数超过5次后触发FOD保护</summary> /// <summary>Field11: 触发次数 - 当 Field9 > Field10ploss 大于阈值时计数超过5次后触发FOD保护</summary>
public int Field11 { get; set; } public int Field11 { get; set; }
/// <summary>Field12: PFOD result 标志 - 触发时置位</summary> /// <summary>Field12: PFOD result 标志 - 触发时置位</summary>
@@ -62,6 +60,6 @@ public class PlossRecord
// Navigation Properties // Navigation Properties
public TestScenario Scenario { get; set; } = null!; public TestScenario Scenario { get; set; } = null!;
/// <summary>派生属性:安全余量(不落库) = Field8 - Field7</summary> /// <summary>派生属性:安全余量(不落库) = Field10 - Field9Threshold - Ploss</summary>
public int Margin => Field8 - Field7; public int Margin => Field10 - Field9;
} }

View File

@@ -111,12 +111,12 @@ public class PlossRecordConfiguration : IEntityTypeConfiguration<PlossRecord>
// 索引 // 索引
builder.HasIndex(e => e.ScenarioId).HasDatabaseName("idx_ploss_scenario"); builder.HasIndex(e => e.ScenarioId).HasDatabaseName("idx_ploss_scenario");
builder.HasIndex(e => e.Field2).HasDatabaseName("idx_ploss_field_2"); // 原 rx_power builder.HasIndex(e => e.Field2).HasDatabaseName("idx_ploss_field_2");
builder.HasIndex(e => e.Field7).HasDatabaseName("idx_ploss_field_7"); // 原 ploss builder.HasIndex(e => e.Field7).HasDatabaseName("idx_ploss_field_7");
builder.HasIndex(e => e.Field8).HasDatabaseName("idx_ploss_field_8"); // 原 threshold builder.HasIndex(e => e.Field8).HasDatabaseName("idx_ploss_field_8");
builder.HasIndex(e => e.Field10).HasDatabaseName("idx_ploss_field_10"); // 原 fod_result builder.HasIndex(e => e.Field10).HasDatabaseName("idx_ploss_field_10");
builder.HasIndex(e => e.PowLoss).HasDatabaseName("idx_ploss_pow_loss"); // 新增 builder.HasIndex(e => e.PowLoss).HasDatabaseName("idx_ploss_pow_loss");
builder.HasIndex(e => e.DeltaP).HasDatabaseName("idx_ploss_delta_p"); // 新增 builder.HasIndex(e => e.DeltaP).HasDatabaseName("idx_ploss_delta_p");
// 关系配置 // 关系配置
builder.HasOne(e => e.Scenario) builder.HasOne(e => e.Scenario)