149 lines
5.9 KiB
C#
149 lines
5.9 KiB
C#
|
|
using Microsoft.EntityFrameworkCore;
|
|||
|
|
using Microsoft.Extensions.Configuration;
|
|||
|
|
using Microsoft.Extensions.Logging;
|
|||
|
|
using Gpulse.WCT.DataAnalyzer.Core.Infrastructure.LocalData;
|
|||
|
|
using Gpulse.WCT.DataAnalyzer.Core.Domain.Local;
|
|||
|
|
using Gpulse.WCT.DataAnalyzer.Core.Domain.Release;
|
|||
|
|
|
|||
|
|
namespace Gpulse.WCT.DataAnalyzer.Core.Application;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 从本地解析库生成独立正式发布库。
|
|||
|
|
/// 当前业务规则:功率列使用 Ploss 的 Field7,重复结果取平均值;缺失值保留为空。
|
|||
|
|
/// </summary>
|
|||
|
|
public class AggregationService
|
|||
|
|
{
|
|||
|
|
private static readonly int[] PowerLevels = [350, 500, 750, 1000, 1250, 1500, 1750, 2000, 2250];
|
|||
|
|
private readonly WctMinerDbContext _localContext;
|
|||
|
|
private readonly ReleaseDbContext _releaseContext;
|
|||
|
|
private readonly IConfiguration _configuration;
|
|||
|
|
private readonly ILogger<AggregationService> _logger;
|
|||
|
|
|
|||
|
|
public AggregationService(
|
|||
|
|
WctMinerDbContext localContext,
|
|||
|
|
ReleaseDbContext releaseContext,
|
|||
|
|
IConfiguration configuration,
|
|||
|
|
ILogger<AggregationService> logger)
|
|||
|
|
{
|
|||
|
|
_localContext = localContext;
|
|||
|
|
_releaseContext = releaseContext;
|
|||
|
|
_configuration = configuration;
|
|||
|
|
_logger = logger;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public async Task<AggregationReport> AggregateAsync(bool rebuild = true, CancellationToken cancellationToken = default)
|
|||
|
|
{
|
|||
|
|
var rows = await LoadRowsAsync(cancellationToken);
|
|||
|
|
var records = rows
|
|||
|
|
.GroupBy(r => BuildKey(r.Scenario))
|
|||
|
|
.Select(group => CreateRecord(group.Key, group.ToList()))
|
|||
|
|
.ToList();
|
|||
|
|
|
|||
|
|
await using var transaction = await _releaseContext.Database.BeginTransactionAsync(cancellationToken);
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
if (rebuild)
|
|||
|
|
await _releaseContext.Database.ExecuteSqlRawAsync("DELETE FROM charging_parameter", cancellationToken);
|
|||
|
|
|
|||
|
|
await _releaseContext.ChargingParameters.AddRangeAsync(records, cancellationToken);
|
|||
|
|
await _releaseContext.SaveChangesAsync(cancellationToken);
|
|||
|
|
await transaction.CommitAsync(cancellationToken);
|
|||
|
|
}
|
|||
|
|
catch
|
|||
|
|
{
|
|||
|
|
await transaction.RollbackAsync(cancellationToken);
|
|||
|
|
throw;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_logger.LogInformation("聚合完成: {Count} 条正式发布记录", records.Count);
|
|||
|
|
return new AggregationReport(records.Count, rows.Count);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private async Task<List<PlossRow>> LoadRowsAsync(CancellationToken cancellationToken)
|
|||
|
|
{
|
|||
|
|
var records = await _localContext.PlossRecords
|
|||
|
|
.AsNoTracking()
|
|||
|
|
.Where(r => !r.IsDeleted)
|
|||
|
|
.Include(r => r.Scenario)
|
|||
|
|
.ThenInclude(s => s!.TxPanel)
|
|||
|
|
.Include(r => r.Scenario)
|
|||
|
|
.ThenInclude(s => s!.TxHardware)
|
|||
|
|
.Include(r => r.Scenario)
|
|||
|
|
.ThenInclude(s => s!.RxType)
|
|||
|
|
.Include(r => r.Scenario)
|
|||
|
|
.ThenInclude(s => s!.QfodRecords)
|
|||
|
|
.ToListAsync(cancellationToken);
|
|||
|
|
|
|||
|
|
return records.Select(r => new PlossRow(
|
|||
|
|
r,
|
|||
|
|
r.Scenario,
|
|||
|
|
r.Scenario.TxPanel.Name,
|
|||
|
|
r.Scenario.TxHardware.Version,
|
|||
|
|
r.Scenario.RxType.Name)).ToList();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private ChargingParameterKey BuildKey(TestScenario scenario)
|
|||
|
|
{
|
|||
|
|
var panelName = scenario.TxPanel.Name;
|
|||
|
|
var mapping = _configuration.GetSection("Aggregation:TxPanelMappings")[panelName];
|
|||
|
|
var carFactory = mapping ?? panelName;
|
|||
|
|
var carModel = _configuration[$"Aggregation:CarModelMappings:{scenario.TxHardware.Version}"]
|
|||
|
|
?? scenario.TxHardware.Version;
|
|||
|
|
var (phoneBrand, phoneModel) = SplitPhoneName(scenario.RxType.Name);
|
|||
|
|
return new ChargingParameterKey(carFactory, carModel, phoneBrand, phoneModel);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private static (string Brand, string Model) SplitPhoneName(string value)
|
|||
|
|
{
|
|||
|
|
var separator = value.IndexOfAny(['/', '_', ':']);
|
|||
|
|
if (separator > 0 && separator < value.Length - 1)
|
|||
|
|
return (value[..separator], value[(separator + 1)..]);
|
|||
|
|
|
|||
|
|
return ("Unknown", value);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private static ChargingParameterRecord CreateRecord(ChargingParameterKey key, List<PlossRow> rows)
|
|||
|
|
{
|
|||
|
|
var values = PowerLevels.Select(power => Average(rows
|
|||
|
|
.Where(row => IsPowerMatch(row.Record, power))
|
|||
|
|
.Select(row => (double?)row.Record.Field7))).ToArray();
|
|||
|
|
|
|||
|
|
return new ChargingParameterRecord
|
|||
|
|
{
|
|||
|
|
CarFactory = key.CarFactory,
|
|||
|
|
CarModel = key.CarModel,
|
|||
|
|
PhoneBrand = key.PhoneBrand,
|
|||
|
|
PhoneModel = key.PhoneModel,
|
|||
|
|
Power350mW = values[0],
|
|||
|
|
Power500mW = values[1],
|
|||
|
|
Power750mW = values[2],
|
|||
|
|
Power1000mW = values[3],
|
|||
|
|
Power1250mW = values[4],
|
|||
|
|
Power1500mW = values[5],
|
|||
|
|
Power1750mW = values[6],
|
|||
|
|
Power2000mW = values[7],
|
|||
|
|
Power2250mW = values[8],
|
|||
|
|
QValue = Average(rows.SelectMany(row => row.Scenario.QfodRecords).Select(q => (double?)q.CurrentQ)),
|
|||
|
|
QBaseValue = Average(rows.SelectMany(row => row.Scenario.QfodRecords).Select(q => (double?)q.RawQ)),
|
|||
|
|
PqCoefficient = Average(rows.Select(row => row.Record.DeltaP.HasValue ? (double?)row.Record.DeltaP.Value : null)),
|
|||
|
|
ResonanceFrequency = null
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private static bool IsPowerMatch(PlossRecord record, int power)
|
|||
|
|
{
|
|||
|
|
return record.Field4 == power || record.Field5 == power || record.PowLoss == power;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private static double? Average(IEnumerable<double?> values)
|
|||
|
|
{
|
|||
|
|
var valid = values.Where(value => value.HasValue).Select(value => value!.Value).ToArray();
|
|||
|
|
return valid.Length == 0 ? null : valid.Average();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private record PlossRow(PlossRecord Record, TestScenario Scenario, string Panel, string Hardware, string RxType);
|
|||
|
|
private record ChargingParameterKey(string CarFactory, string CarModel, string PhoneBrand, string PhoneModel);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public record AggregationReport(int ReleaseRecordCount, int LocalRecordCount);
|