import assert from 'node:assert/strict'
|
import { test } from 'node:test'
|
import XLSX from 'xlsx'
|
import { parseConsoleImport, consoleImportColumns } from '../src/utils/consoleDataImport.ts'
|
import * as importUtils from '../src/utils/consoleDataImport.ts'
|
|
test('一键模板包含三个标准 Sheet,并逐 Sheet 预览与报告缺失表', () => {
|
const workbook = importUtils.createConsoleImportWorkbook()
|
assert.deepEqual(workbook.SheetNames, ['实时', '工步', '循环'])
|
const row = { recordNo: '17', batteryNo: 'BAT-1', recordedAt: '2023-05-08 11:42:11', cycleNo: 1, stepNo: 1, stepName: '恒流放电', sequenceId: 17 }
|
for (const [index, type] of ['REALTIME', 'STEP', 'CYCLE'].entries()) {
|
const sheet = workbook.Sheets[workbook.SheetNames[index]]
|
assert.deepEqual(XLSX.utils.sheet_to_json(sheet, { header: 1 })[0], Object.values(consoleImportColumns[type]))
|
XLSX.utils.sheet_add_aoa(sheet, [Object.keys(consoleImportColumns[type]).map(key => row[key] ?? '')], { origin: 'A2' })
|
}
|
const sheets = importUtils.parseConsoleImportWorkbook(workbook, ['BAT-1'])
|
assert.deepEqual(sheets.map(sheet => [sheet.type, sheet.rows.length, sheet.errors]), [['REALTIME', 1, []], ['STEP', 1, []], ['CYCLE', 1, []]])
|
delete workbook.Sheets['循环']
|
const missing = importUtils.parseConsoleImportWorkbook(workbook, ['BAT-1'])
|
assert.match(missing[2].errors.join(), /缺少循环工作表/)
|
assert.equal(missing[0].rows.length, 1)
|
})
|
|
function partyWorkbook() {
|
const workbook = XLSX.utils.book_new()
|
XLSX.utils.book_append_sheet(workbook, XLSX.utils.aoa_to_sheet([['设备信息']]), 'Unit')
|
XLSX.utils.book_append_sheet(workbook, XLSX.utils.aoa_to_sheet([
|
['循环序号', '工步序号', '工步名称', '记录序号', '记录时间(h:min:s.ms)', '电压(V)', '电流(mA)', '容量(mAh)', '能量(mWh)', '绝对时间'],
|
[1, 1, '恒流放电', 1, '0:00:00.000', 1.3349, -8165.1, 0, 0, '2023-05-08 11:42:11'],
|
[1, 1, '恒流放电', 2, '1:02:32.000', 0.8999, -8165.1, 8512.2, 9717.8, '2023-05-08 12:44:43'],
|
]), 'record')
|
return workbook
|
}
|
|
test('实时子页选择 record 并保留身份、香港绝对时间及系统单位', () => {
|
const rows = parseConsoleImport(partyWorkbook(), 'REALTIME', ['BAT-1'], 'BAT-1')
|
assert.equal(rows.length, 2)
|
assert.deepEqual(rows[0].errors, [])
|
assert.equal(rows[0].data.recordNo, '1')
|
assert.equal(rows[0].data.sequenceId, 1)
|
assert.equal(rows[0].data.cycleNo, 1)
|
assert.equal(rows[0].data.stepNo, 1)
|
assert.equal(rows[0].data.recordedAt, 1683517331000)
|
assert.equal(rows[0].data.deviceTimeRaw, '2023-05-08 11:42:11')
|
assert.equal(rows[0].data.current, -8.1651)
|
assert.equal(rows[1].data.capacity, 8.5122)
|
assert.equal(rows[1].data.energy, 9.7178)
|
for (const field of ['temperature', 'positivePotential', 'negativePotential']) {
|
assert.equal(rows[0].data[field], null)
|
}
|
})
|
|
test('采集器三项测量只读取 Excel 已有值,缺列或空格不生成数值', () => {
|
const workbook = partyWorkbook()
|
XLSX.utils.sheet_add_aoa(workbook.Sheets.record, [
|
['温度(℃)', '正电位(V)', '负电位(V)'],
|
[32.75, 1.8234, -0.2045],
|
['', '', ''],
|
], { origin: 'K1' })
|
const rows = parseConsoleImport(workbook, 'REALTIME', ['BAT-1'])
|
assert.deepEqual(rows[0].errors, [])
|
assert.deepEqual(['temperature', 'positivePotential', 'negativePotential'].map(field => rows[0].data[field]), [32.75, 1.8234, -0.2045])
|
assert.deepEqual(['temperature', 'positivePotential', 'negativePotential'].map(field => rows[1].data[field]), [null, null, null])
|
})
|
|
test('标准模板兼容文本、毫秒时间戳和 Excel 日期系统,并拒绝非法日期', () => {
|
const make = (time, date1904 = false) => {
|
const workbook = XLSX.utils.book_new()
|
workbook.Workbook = { WBProps: { date1904 } }
|
const data = { recordNo: '17', batteryNo: 'BAT-1', recordedAt: time, cycleNo: 1, stepNo: 1, stepName: '恒流放电', sequenceId: 17 }
|
XLSX.utils.book_append_sheet(workbook, XLSX.utils.aoa_to_sheet([
|
Object.values(consoleImportColumns.REALTIME), Object.keys(consoleImportColumns.REALTIME).map(field => data[field] ?? ''),
|
]), '实时模板')
|
return parseConsoleImport(workbook, 'REALTIME', ['BAT-1'])[0]
|
}
|
assert.equal(make('2023/5/8 11:42:11.123').data.recordedAt, 1683517331123)
|
assert.equal(make(1683517331123).data.recordedAt, 1683517331123)
|
assert.equal(make(45054).data.recordedAt, 1683475200000)
|
assert.equal(make(43592, true).data.recordedAt, 1683475200000)
|
assert.match(make('2023-02-30 11:00:00').errors.join(), /第2行.*设备时间.*无效/)
|
assert.match(make('1:02:32.000').errors.join(), /设备时间.*无效/)
|
})
|
|
test('多电池文件要求明确归属,保留空白行后的实际错误位置', () => {
|
assert.match(parseConsoleImport(partyWorkbook(), 'REALTIME', ['BAT-1', 'BAT-2'])[0].errors.join(), /电池号.*不能为空/)
|
assert.equal(parseConsoleImport(partyWorkbook(), 'REALTIME', ['BAT-1', 'BAT-2'], 'BAT-2')[0].data.batteryNo, 'BAT-2')
|
const workbook = partyWorkbook()
|
XLSX.utils.sheet_add_aoa(workbook.Sheets.record, [[1, 1, '恒流放电', 19, '0:01:00.000', '不是数值', 0, 0, 0, '2023-05-08 11:44:11']], { origin: 'A20' })
|
const last = parseConsoleImport(workbook, 'REALTIME', ['BAT-1']).at(-1)
|
assert.equal(last.sourceRow, 20)
|
assert.match(last.errors.join(), /第20行,列「电压\(V\)」/)
|
})
|
|
test('工步和循环仅转换对应页,时间取匹配 record 的末次绝对时间', () => {
|
const workbook = partyWorkbook()
|
XLSX.utils.book_append_sheet(workbook, XLSX.utils.aoa_to_sheet([
|
['循环序号', '工步序号', '工步名称', '工步时间(h:min:s.ms)', '容量(mAh)', '能量(mWh)', '起始电压(V)', '终止电压(V)', '充电中值电压(V)', '放电中值电压(V)'],
|
[1, 1, '恒流放电', '1:02:32.000', 8512.2, 9717.8, 1.3349, 0.8999, 0, 1.1709],
|
]), 'step')
|
XLSX.utils.book_append_sheet(workbook, XLSX.utils.aoa_to_sheet([
|
['循环序号', '充电容量(mAh)', '放电容量(mAh)', '充放电效率(%)', '充电能量(mWh)', '放电能量(mWh)'],
|
[1, 0, 8512.2, 100, 0, 9717.8],
|
]), 'cycle')
|
const step = parseConsoleImport(workbook, 'STEP', ['BAT-1'])[0]
|
assert.deepEqual(step.errors, [])
|
assert.equal(step.data.recordNo, 'step:1:1')
|
assert.equal(step.data.recordedAt, 1683521083000)
|
assert.equal(step.data.stepDuration, 3752)
|
assert.equal(step.data.chargePeakVoltage, null)
|
assert.equal(step.data.dischargeValleyVoltage, null)
|
const cycle = parseConsoleImport(workbook, 'CYCLE', ['BAT-1'])[0]
|
assert.deepEqual(cycle.errors, [])
|
assert.equal(cycle.data.recordNo, 'cycle:1')
|
assert.equal(cycle.data.recordedAt, 1683521083000)
|
assert.equal(cycle.data.dischargeCapacity, 8.5122)
|
delete workbook.Sheets.record
|
assert.match(parseConsoleImport(workbook, 'STEP', ['BAT-1'])[0].errors.join(), /第2行.*绝对时间.*无效/)
|
})
|