docs: 完善数据模型设计

- 重命名 log_file 为 test_scenario(测试场景)
- 拆分维度表:tx_panel、tx_hardware、tx_software、rx_type
- 采用星型模型设计,消除数据冗余
- 为所有表添加软删除字段 is_deleted
- 更新关系说明,包含全局查询过滤器配置
- 更新 ER 图和查询示例

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ssss
2026-07-02 15:53:30 +08:00
commit 28cb2533a7
23 changed files with 2919 additions and 0 deletions

View File

@@ -0,0 +1,165 @@
# Ploss 解析器
> 所属模块:[解析器设计](./概览.md)
---
## 1. 日志格式
**格式:** `FOD-> (1) X X X (2) (3) (4) (5) (6) (7) (8) (9) (10) (11) (12) (13)`
**示例:** `FOD-> 4 2 1 127 10690 12013 35377 9904 1213 -2882 750 0 0 1 500 1`
> 括号数字表示有效字段编号X 表示忽略该位置数据
---
## 2. 字段定义
| 编号 | 字段名称 | 数据类型 | 说明 |
|------|----------|----------|------|
| (1) | RxType | byte | 接收端类型标识 |
| (2) | RxPower | short | 接收端功率mW |
| (3) | TxPower | short | 发射端功率mW |
| (4) | Vcoil | short | 发射线圈电压 |
| (5) | Vin | short | 输入电压 |
| (6) | Isns | short | 输入电流采样值 |
| (7) | Ploss | int | **核心值**:计算损耗(负数表示安全) |
| (8) | Threshold | int | **安全红线**:当前功率段阈值 |
| (9) | TriggerCount | short | 触发计数器 |
| (10) | FodResult | short | FOD判定结果状态 |
| (11) | ProtocolType | byte | 充电协议类型 |
| (12) | PwmDuty | short | PWM控制占空比 |
| (13) | CoilIndex | byte | 充电线圈索引 |
---
## 3. 正则表达式
```csharp
// Parsers/PlossParser.cs
private static readonly Regex PlossPattern = 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+)$",
RegexOptions.Compiled
);
```
**捕获组映射:**
| 捕获组 | 字段 | 类型转换 |
|--------|------|----------|
| Group 1 | RxType | byte.Parse() |
| Group 2 | RxPower | short.Parse() |
| Group 3 | TxPower | short.Parse() |
| Group 4 | Vcoil | short.Parse() |
| Group 5 | Vin | short.Parse() |
| Group 6 | Isns | short.Parse() |
| Group 7 | Ploss | int.Parse() |
| Group 8 | Threshold | int.Parse() |
| Group 9 | TriggerCount | short.Parse() |
| Group 10 | FodResult | short.Parse() |
| Group 11 | ProtocolType | byte.Parse() |
| Group 12 | PwmDuty | short.Parse() |
| Group 13 | CoilIndex | byte.Parse() |
---
## 4. 解析器实现
```csharp
// Parsers/PlossParser.cs
using System.Text.RegularExpressions;
using WCTDataMiner.Models;
namespace WCTDataMiner.Parsers;
public class PlossParser : IParser<PlossRecord>
{
private static readonly Regex PlossPattern = 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+)$",
RegexOptions.Compiled
);
public bool CanParse(string line) => PlossPattern.IsMatch(line);
public ParseResult<PlossRecord> Parse(string line, int lineNumber, Guid logFileId)
{
var match = PlossPattern.Match(line);
if (!match.Success)
{
return ParseResult<PlossRecord>.Failure(
"FORMAT_MISMATCH",
"Line does not match Ploss pattern"
);
}
try
{
var record = new PlossRecord
{
LogFileId = logFileId,
LineNumber = lineNumber,
RxType = byte.Parse(match.Groups[1].Value),
RxPower = short.Parse(match.Groups[2].Value),
TxPower = short.Parse(match.Groups[3].Value),
Vcoil = short.Parse(match.Groups[4].Value),
Vin = short.Parse(match.Groups[5].Value),
Isns = short.Parse(match.Groups[6].Value),
Ploss = int.Parse(match.Groups[7].Value),
Threshold = int.Parse(match.Groups[8].Value),
TriggerCount = short.Parse(match.Groups[9].Value),
FodResult = short.Parse(match.Groups[10].Value),
ProtocolType = byte.Parse(match.Groups[11].Value),
PwmDuty = short.Parse(match.Groups[12].Value),
CoilIndex = byte.Parse(match.Groups[13].Value)
};
return ParseResult<PlossRecord>.Success(record);
}
catch (Exception ex)
{
return ParseResult<PlossRecord>.Failure(
"TYPE_CONVERSION",
$"Failed to convert field: {ex.Message}"
);
}
}
}
```
---
## 5. 核心判定逻辑
### 5.1 FOD 触发条件
```
if (Ploss > Threshold) → 计数器累积
if (Count > Limit) → 触发 FOD 报警,停止充电
```
### 5.2 安全余量计算
```csharp
// 派生属性(不落库)
Margin = Threshold - Ploss
```
| Margin 状态 | 含义 |
|-------------|------|
| Margin > 0 | 安全,损耗低于阈值 |
| Margin < 0 | 危险,损耗超过阈值,可能触发 FOD |
---
## 6. 动态阈值表
阈值根据接收功率动态查表:
| 接收功率区间 (Rx Power) | 阈值 (Threshold) | 备注 |
|------------------------|------------------|------|
| 0 ~ 5W (0-5000 mW) | 350 mW | 低功率最敏感 |
| 5 ~ 10W (5000-10000 mW) | 500 mW | |
| 10 ~ 15W (10000-15000 mW) | 750 mW | |
| 15 ~ 30W (15000-30000 mW) | 1000 mW | 示例 Log 处于此区间 |
| 30 ~ 50W (30000-50000 mW) | 1250 mW | 高功率允许更多损耗 |

View File

@@ -0,0 +1,120 @@
# Qfod 解析器
> 所属模块:[解析器设计](./概览.md)
---
## 1. 日志格式
**格式:** `X#:Q A->B C D E`
**示例:** `0#:Q 1->25 45.5 20.5 2`
---
## 2. 字段定义
| 字段标识 | 字段名称 | 数据类型 | 取值范围 | 说明 |
|----------|----------|----------|----------|------|
| X | ChargerIndex | byte | 0, 1 | 充电器索引 |
| A | CoilIndex | byte | 0, 1, 2 | 线圈索引 |
| B | DeltaQ | int | - | Q值变化量ΔQ = CurrentQ - RawQ |
| C | CurrentQ | float | - | 当前实时Q值 |
| D | RawQ | float | - | 原始Q值 |
| E | FodType | byte | 见Table 2 | 异物类型编码 |
---
## 3. 正则表达式
```csharp
// Parsers/QfodParser.cs
private static readonly Regex QfodPattern = new(
@"^(\d)#:Q\s+(\d+)->(-?\d+)\s+([-\d.]+)\s+([-\d.]+)\s+(\d+)$",
RegexOptions.Compiled
);
```
**捕获组映射:**
| 捕获组 | 字段 | 类型转换 |
|--------|------|----------|
| Group 1 | ChargerIndex | byte.Parse() |
| Group 2 | CoilIndex | byte.Parse() |
| Group 3 | DeltaQ | int.Parse() |
| Group 4 | CurrentQ | float.Parse() |
| Group 5 | RawQ | float.Parse() |
| Group 6 | FodType | byte.Parse() |
---
## 4. 解析器实现
```csharp
// Parsers/QfodParser.cs
using System.Text.RegularExpressions;
using WCTDataMiner.Models;
namespace WCTDataMiner.Parsers;
public class QfodParser : IParser<QfodRecord>
{
private static readonly Regex QfodPattern = new(
@"^(\d)#:Q\s+(\d+)->(-?\d+)\s+([-\d.]+)\s+([-\d.]+)\s+(\d+)$",
RegexOptions.Compiled
);
public bool CanParse(string line) => QfodPattern.IsMatch(line);
public ParseResult<QfodRecord> Parse(string line, int lineNumber, Guid logFileId)
{
var match = QfodPattern.Match(line);
if (!match.Success)
{
return ParseResult<QfodRecord>.Failure(
"FORMAT_MISMATCH",
"Line does not match Qfod pattern"
);
}
try
{
var record = new QfodRecord
{
LogFileId = logFileId,
LineNumber = lineNumber,
ChargerIndex = byte.Parse(match.Groups[1].Value),
CoilIndex = byte.Parse(match.Groups[2].Value),
DeltaQ = int.Parse(match.Groups[3].Value),
CurrentQ = float.Parse(match.Groups[4].Value),
RawQ = float.Parse(match.Groups[5].Value),
FodType = byte.Parse(match.Groups[6].Value)
};
return ParseResult<QfodRecord>.Success(record);
}
catch (Exception ex)
{
return ParseResult<QfodRecord>.Failure(
"TYPE_CONVERSION",
$"Failed to convert field: {ex.Message}"
);
}
}
}
```
---
## 5. 检测通道组合
`ChargerIndex + CoilIndex` 组合唯一标识一个检测通道:
| ChargerIndex | CoilIndex | 检测通道 |
|--------------|-----------|----------|
| 0 | 0 | 充电器0 线圈0 |
| 0 | 1 | 充电器0 线圈1 |
| 0 | 2 | 充电器0 线圈2 |
| 1 | 0 | 充电器1 线圈0 |
| 1 | 1 | 充电器1 线圈1 |
| 1 | 2 | 充电器1 线圈2 |

View File

@@ -0,0 +1,78 @@
# 解析器设计
> 所属模块:[架构概览](../概览.md)
---
## 文档索引
| 文档 | 说明 |
|------|------|
| [Qfod 解析器](./Qfod解析器.md) | Qfod 格式日志解析设计 |
| [Ploss 解析器](./Ploss解析器.md) | Ploss FOD 格式日志解析设计 |
---
## 通用接口定义
```csharp
// Parsers/IParser.cs
namespace WCTDataMiner.Parsers;
public interface IParser<TRecord>
{
/// <summary>
/// 判断是否可以解析该行
/// </summary>
bool CanParse(string line);
/// <summary>
/// 解析日志行
/// </summary>
ParseResult<TRecord> Parse(string line, int lineNumber, Guid logFileId);
}
public record ParseResult<TRecord>
{
public bool IsSuccess { get; init; }
public TRecord? Record { get; init; }
public string? ErrorType { get; init; }
public string? ErrorMessage { get; init; }
public static ParseResult<TRecord> Success(TRecord record) =>
new() { IsSuccess = true, Record = record };
public static ParseResult<TRecord> Failure(string errorType, string errorMessage) =>
new() { IsSuccess = false, ErrorType = errorType, ErrorMessage = errorMessage };
}
```
---
## 解析流程
```mermaid
flowchart TD
A[读取日志文件] --> B[逐行遍历]
B --> C{判断行类型}
C -->|Qfod| D[QfodParser.Parse]
C -->|Ploss| E[PlossParser.Parse]
C -->|其他| F[跳过]
D --> G{解析成功?}
E --> G
G -->|成功| H[添加到记录列表]
G -->|失败| I[记录到 ParseError]
H --> J[批量保存到数据库]
I --> J
J --> K[输出解析报告]
```
---
## 错误类型定义
| ErrorType | 说明 | 处理方式 |
|-----------|------|----------|
| FORMAT_MISMATCH | 行格式不匹配正则表达式 | 跳过该行,记录原始内容 |
| TYPE_CONVERSION | 字段类型转换失败 | 跳过该行,记录错误字段 |
| VALUE_OUT_OF_RANGE | 字段值超出有效范围 | 使用边界值或跳过 |