feat: 完善每日氢耗明细导出与质量标记
This commit is contained in:
@@ -912,8 +912,16 @@ export interface DailyMileageRow {
|
||||
hydrogenConsumptionKgPer100Km?: number | null;
|
||||
hydrogenSocBalancedKg?: number | null;
|
||||
hydrogenSocBalancedKgPer100Km?: number | null;
|
||||
pureElectricMileageKm?: number | null;
|
||||
mixedMileageKm?: number | null;
|
||||
batterySocDeltaPct?: number | null;
|
||||
chargeCount?: number | null;
|
||||
chargeEnergyKWh?: number | null;
|
||||
refuelCount?: number | null;
|
||||
refuelAmountKg?: number | null;
|
||||
hydrogenEvidenceAvailable?: boolean;
|
||||
hydrogenQualityStatus?: string;
|
||||
hydrogenQualityReason?: string;
|
||||
hydrogenAlgorithmVersion?: string;
|
||||
source: string;
|
||||
anomalySeverity?: string;
|
||||
@@ -934,7 +942,7 @@ export interface HydrogenDailyEvidence {
|
||||
batterySocDeltaPct?: number; batteryDischargeKWh?: number; batteryEquivalentKg?: number;
|
||||
socBalancedConsumptionKg?: number; mixedMileageKm: number; pureElectricMileageKm: number;
|
||||
consumptionKgPer100Km?: number; socBalancedKgPer100Km?: number; sampleCount: number;
|
||||
refuelCount: number; chargeCount: number; validSegmentCount: number; invalidSegmentCount: number;
|
||||
refuelCount: number; refuelAmountKg?: number; chargeCount: number; chargeEnergyKWh?: number; validSegmentCount: number; invalidSegmentCount: number;
|
||||
qualityStatus: string; qualityReason: string; algorithmVersion: string; calculatedAt: string;
|
||||
parameters: {
|
||||
batteryCapacityKWh: number; hydrogenEnergyKWhPerKg: number; powerOnDelaySeconds: number;
|
||||
|
||||
@@ -14,7 +14,7 @@ test('creates a styled numeric mileage workbook with formulas and frozen panes',
|
||||
{ vin: 'LTEST000000000002', plate: '粤A54321', brandName: '飞驰', modelName: 'FSQ' }
|
||||
],
|
||||
mileageRows: [
|
||||
{ vin: 'LTEST000000000001', plate: '粤A12345', date: '2026-07-13', startMileageKm: 100, endMileageKm: 188.7, dailyMileageKm: 88.7, pureHydrogenMileageKm: 56.2, hydrogenConsumptionKg: 3.1, hydrogenConsumptionKgPer100Km: 5.5, source: 'GB32960' },
|
||||
{ vin: 'LTEST000000000001', plate: '粤A12345', date: '2026-07-13', startMileageKm: 100, endMileageKm: 188.7, dailyMileageKm: 88.7, pureHydrogenMileageKm: 56.2, pureElectricMileageKm: 32.5, mixedMileageKm: 56.2, hydrogenConsumptionKg: 3.1, hydrogenConsumptionKgPer100Km: 5.5, hydrogenSocBalancedKg: 3.126, batterySocDeltaPct: -2, chargeCount: 1, chargeEnergyKWh: 12.5, refuelCount: 1, refuelAmountKg: 8.976, hydrogenQualityStatus: 'SUSPECT', hydrogenQualityReason: '疑似管路泄压', source: 'GB32960' },
|
||||
{ vin: 'LTEST000000000001', plate: '粤A12345', date: '2026-07-14', startMileageKm: 188.7, endMileageKm: 293.3, dailyMileageKm: 104.6, pureHydrogenMileageKm: 72.2, hydrogenConsumptionKg: 4.2, hydrogenConsumptionKgPer100Km: 5.8, source: 'GB32960' }
|
||||
],
|
||||
sources: [
|
||||
@@ -43,12 +43,12 @@ test('creates a styled numeric mileage workbook with formulas and frozen panes',
|
||||
expect((sheet as unknown as { conditionalFormattings: unknown[] }).conditionalFormattings).toHaveLength(1);
|
||||
const hydrogenSheet = workbook.getWorksheet('氢能明细')!;
|
||||
expect(hydrogenSheet.getRow(1).values).toEqual([
|
||||
undefined, '日期', '车牌', 'VIN', '品牌', '车型', '总里程 (km)', '纯氢里程 (km)', '耗氢 (kg)', '百公里纯氢耗 (kg/100km)', '来源'
|
||||
undefined, '日期', '车牌', 'VIN', '品牌', '车型', '每日氢耗 (kg/100km)', '结果状态', '总里程 (km)', '纯电里程 (km)', '混动里程 (km)', '电SOC差值 (百分点)', '充电次数', '充电量 (kWh)', '加氢次数', '加氢量 (kg)', '修正用氢量 (kg)', '原因', '物理用氢量 (kg)', '来源'
|
||||
]);
|
||||
expect(hydrogenSheet.getRow(2).values).toEqual([
|
||||
undefined, '2026-07-13', '粤A12345', 'LTEST000000000001', '现代', 'XCIENT', 88.7, 56.2, 3.1, 5.5, 'GB32960'
|
||||
undefined, '2026-07-13', '粤A12345', 'LTEST000000000001', '现代', 'XCIENT', 5.5, 'SUSPECT', 88.7, 32.5, 56.2, -2, 1, 12.5, 1, 8.976, 3.126, '疑似管路泄压', 3.1, 'GB32960'
|
||||
]);
|
||||
expect(hydrogenSheet.autoFilter).toEqual({ from: { row: 1, column: 1 }, to: { row: 3, column: 10 } });
|
||||
expect(hydrogenSheet.autoFilter).toEqual({ from: { row: 1, column: 1 }, to: { row: 3, column: 19 } });
|
||||
expect((await workbook.xlsx.writeBuffer()).byteLength).toBeGreaterThan(5_000);
|
||||
});
|
||||
|
||||
@@ -106,6 +106,16 @@ test('terminates the workbook worker when an active export is cancelled', async
|
||||
pureHydrogenMileageKm: undefined,
|
||||
hydrogenConsumptionKg: undefined,
|
||||
hydrogenConsumptionKgPer100Km: undefined,
|
||||
hydrogenSocBalancedKg: undefined,
|
||||
pureElectricMileageKm: undefined,
|
||||
mixedMileageKm: undefined,
|
||||
batterySocDeltaPct: undefined,
|
||||
chargeCount: undefined,
|
||||
chargeEnergyKWh: undefined,
|
||||
refuelCount: undefined,
|
||||
refuelAmountKg: undefined,
|
||||
hydrogenQualityStatus: undefined,
|
||||
hydrogenQualityReason: undefined,
|
||||
source: 'GB32960'
|
||||
}]);
|
||||
controller.abort();
|
||||
|
||||
@@ -90,6 +90,16 @@ export function createMileageExportStream(input: Omit<MileageExportInput, 'vehic
|
||||
pureHydrogenMileageKm: row.pureHydrogenMileageKm,
|
||||
hydrogenConsumptionKg: row.hydrogenConsumptionKg,
|
||||
hydrogenConsumptionKgPer100Km: row.hydrogenConsumptionKgPer100Km,
|
||||
hydrogenSocBalancedKg: row.hydrogenSocBalancedKg,
|
||||
pureElectricMileageKm: row.pureElectricMileageKm,
|
||||
mixedMileageKm: row.mixedMileageKm,
|
||||
batterySocDeltaPct: row.batterySocDeltaPct,
|
||||
chargeCount: row.chargeCount,
|
||||
chargeEnergyKWh: row.chargeEnergyKWh,
|
||||
refuelCount: row.refuelCount,
|
||||
refuelAmountKg: row.refuelAmountKg,
|
||||
hydrogenQualityStatus: row.hydrogenQualityStatus,
|
||||
hydrogenQualityReason: row.hydrogenQualityReason,
|
||||
source: row.source
|
||||
}))
|
||||
});
|
||||
|
||||
@@ -137,7 +137,12 @@ export async function createMileageWorkbook(input: MileageExportInput) {
|
||||
views: [{ state: 'frozen', ySplit: 1, activeCell: 'A2', showGridLines: false }],
|
||||
properties: { defaultRowHeight: 21 }
|
||||
});
|
||||
hydrogenSheet.addRow(['日期', '车牌', 'VIN', '品牌', '车型', '总里程 (km)', '纯氢里程 (km)', '耗氢 (kg)', '百公里纯氢耗 (kg/100km)', '来源']);
|
||||
const hydrogenHeaders = [
|
||||
'日期', '车牌', 'VIN', '品牌', '车型', '每日氢耗 (kg/100km)', '结果状态', '总里程 (km)',
|
||||
'纯电里程 (km)', '混动里程 (km)', '电SOC差值 (百分点)', '充电次数', '充电量 (kWh)', '加氢次数', '加氢量 (kg)',
|
||||
'修正用氢量 (kg)', '原因', '物理用氢量 (kg)', '来源'
|
||||
];
|
||||
hydrogenSheet.addRow(hydrogenHeaders);
|
||||
const hydrogenHeader = hydrogenSheet.getRow(1);
|
||||
hydrogenHeader.height = 30;
|
||||
hydrogenHeader.eachCell((cell) => {
|
||||
@@ -156,26 +161,36 @@ export async function createMileageWorkbook(input: MileageExportInput) {
|
||||
value.vin,
|
||||
vehicle?.brandName || '待维护',
|
||||
vehicle?.modelName || '待维护',
|
||||
value.dailyMileageKm,
|
||||
value.pureHydrogenMileageKm ?? null,
|
||||
value.hydrogenConsumptionKg ?? null,
|
||||
value.hydrogenConsumptionKgPer100Km ?? null,
|
||||
value.hydrogenQualityStatus || 'NO_DATA',
|
||||
value.dailyMileageKm,
|
||||
value.pureElectricMileageKm ?? null,
|
||||
value.mixedMileageKm ?? null,
|
||||
value.batterySocDeltaPct ?? null,
|
||||
value.chargeCount ?? null,
|
||||
value.chargeEnergyKWh ?? null,
|
||||
value.refuelCount ?? null,
|
||||
value.refuelAmountKg ?? null,
|
||||
value.hydrogenSocBalancedKg ?? null,
|
||||
value.hydrogenQualityReason || '',
|
||||
value.hydrogenConsumptionKg ?? null,
|
||||
value.source ?? ''
|
||||
]);
|
||||
row.height = 24;
|
||||
row.eachCell({ includeEmpty: true }, (cell, columnNumber) => {
|
||||
cell.font = { name: columnNumber === 3 ? 'Consolas' : 'Microsoft YaHei', size: columnNumber === 3 ? 9 : 10, color: { argb: 'FF34445A' } };
|
||||
cell.alignment = { vertical: 'middle', horizontal: columnNumber >= 6 && columnNumber <= 9 ? 'right' : 'left' };
|
||||
cell.alignment = { vertical: 'middle', horizontal: columnNumber >= 6 && columnNumber <= 16 ? 'right' : 'left' };
|
||||
cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: index % 2 ? 'FFF9FBFD' : 'FFFFFFFF' } };
|
||||
cell.border = { bottom: { style: 'thin', color: { argb: 'FFE6ECF3' } } };
|
||||
if (columnNumber >= 6 && columnNumber <= 9) cell.numFmt = '#,##0.0';
|
||||
if ([6, 8, 9, 10, 11, 13, 15, 16, 18].includes(columnNumber)) cell.numFmt = '#,##0.000';
|
||||
if ([12, 14].includes(columnNumber)) cell.numFmt = '0';
|
||||
});
|
||||
});
|
||||
[13, 15, 24, 18, 24, 15, 17, 14, 27, 16].forEach((width, index) => {
|
||||
[13, 15, 24, 18, 24, 22, 14, 15, 17, 17, 22, 12, 17, 12, 16, 19, 52, 19, 16].forEach((width, index) => {
|
||||
hydrogenSheet.getColumn(index + 1).width = width;
|
||||
});
|
||||
if (energyRows.length) {
|
||||
hydrogenSheet.autoFilter = { from: { row: 1, column: 1 }, to: { row: energyRows.length + 1, column: 10 } };
|
||||
hydrogenSheet.autoFilter = { from: { row: 1, column: 1 }, to: { row: energyRows.length + 1, column: hydrogenHeaders.length } };
|
||||
}
|
||||
hydrogenSheet.headerFooter.oddFooter = sheet.headerFooter.oddFooter;
|
||||
return workbook;
|
||||
|
||||
@@ -899,6 +899,8 @@ function HydrogenEvidenceSheet({ target, onClose }: { target?: HydrogenEvidenceT
|
||||
<div><dt>纯电异常</dt><dd>-{data.parameters.pureElectricDropMpa}MPa / {data.parameters.pureElectricWindowSeconds}s</dd></div>
|
||||
<div><dt>样本</dt><dd>{data.sampleCount}</dd></div>
|
||||
<div><dt>加氢 / 充电</dt><dd>{data.refuelCount} / {data.chargeCount}</dd></div>
|
||||
<div><dt>加氢量</dt><dd>{data.refuelAmountKg == null ? '—' : `${evidenceNumber(data.refuelAmountKg)} kg`}</dd></div>
|
||||
<div><dt>充电量</dt><dd>{data.chargeEnergyKWh == null ? '—' : `${evidenceNumber(data.chargeEnergyKWh)} kWh`}</dd></div>
|
||||
<div><dt>无效区间</dt><dd>{data.invalidSegmentCount}</dd></div>
|
||||
<div><dt>计算时间</dt><dd>{data.calculatedAt}</dd></div>
|
||||
</dl>
|
||||
|
||||
Reference in New Issue
Block a user