import fs from "node:fs/promises"; import fsSync from "node:fs"; import path from "node:path"; import { once } from "node:events"; import { FileBlob, SpreadsheetFile, Workbook, } from "@oai/artifact-tool"; const root = "/Users/lingniu/project/ai-coding/lingniu-vehicle-ingest"; const workDir = path.join(root, "outputs/g7-mileage-history-20260724"); const dailyDir = path.join(workDir, "raw/daily"); const statisticDir = path.join(workDir, "raw/statistic"); const mergedDir = path.join(workDir, "merged"); const outputXlsx = path.join( mergedDir, "G7车辆每日里程及里程统计_20220101-20260720.xlsx", ); const importCsv = path.join( mergedDir, "G7车辆每日里程_20220101-20260720.csv", ); const manifestPath = path.join(mergedDir, "manifest.json"); const priorMappingCsv = path.join( root, "outputs/g7-mileage-import-20260716/import_rows.csv", ); await fs.mkdir(mergedDir, { recursive: true }); const months = buildMonths(); const dailyFilesByTaskId = await sortedXlsxFiles(dailyDir); // 2022-02 was the one-file validation task. The remaining formal queue then // started with 2022-01 and continued from 2022-03, so swap the first two task IDs // back into natural month order. const dailyFiles = [ dailyFilesByTaskId[1], dailyFilesByTaskId[0], ...dailyFilesByTaskId.slice(2), ]; const statisticFiles = await sortedXlsxFiles(statisticDir); assert(dailyFiles.length === months.length, `日报文件应为 ${months.length},实际 ${dailyFiles.length}`); assert( statisticFiles.length === months.length, `里程统计文件应为 ${months.length},实际 ${statisticFiles.length}`, ); const vehicles = new Map(); const dateOrder = []; const dateSource = new Map(); const monthlyDailyTotals = new Map(); const dailyBatchManifest = []; let dailyPositiveCells = 0; let dailyZeroCells = 0; let dailyTotalKm = 0; let dailyTotalMismatchRows = 0; let dailyCompletenessNoteRows = 0; for (let monthIndex = 0; monthIndex < months.length; monthIndex += 1) { const month = months[monthIndex]; const file = dailyFiles[monthIndex]; const values = await readFirstSheetValues(file); const headers = values[0] ?? []; const runtimeIndex = headers.indexOf("运行时长"); assert(runtimeIndex > 3, `${path.basename(file)} 缺少运行时长列`); const dateColumnCount = runtimeIndex - 3; assert( dateColumnCount === month.dayCount, `${path.basename(file)} 日期列应为 ${month.dayCount},实际 ${dateColumnCount}`, ); const monthDates = []; for (let day = 1; day <= month.dayCount; day += 1) { const date = `${month.year}-${pad(month.month)}-${pad(day)}`; monthDates.push(date); dateOrder.push(date); dateSource.set(date, path.basename(file)); } let batchTotal = 0; let batchPositive = 0; let batchZero = 0; let batchMismatch = 0; let batchNotes = 0; const seenPlates = new Set(); for (let rowIndex = 1; rowIndex < values.length; rowIndex += 1) { const row = values[rowIndex] ?? []; const plate = cleanText(row[0]); if (!plate) continue; assert(!seenPlates.has(plate), `${path.basename(file)} 存在重复车牌 ${plate}`); seenPlates.add(plate); const organization = cleanText(row[1]); const statedTotal = numberValue(row[2]); const dailyValues = row.slice(3, runtimeIndex).map(numberValue); const calculatedTotal = dailyValues.reduce((sum, value) => sum + value, 0); if (Math.abs(statedTotal - calculatedTotal) > 0.011) { dailyTotalMismatchRows += 1; batchMismatch += 1; } const note = cleanText(row[runtimeIndex + 1]); if (note) { dailyCompletenessNoteRows += 1; batchNotes += 1; } let vehicle = vehicles.get(plate); if (!vehicle) { vehicle = { plate, organization, values: new Map(), }; vehicles.set(plate, vehicle); } else if (!vehicle.organization && organization) { vehicle.organization = organization; } for (let dayIndex = 0; dayIndex < dailyValues.length; dayIndex += 1) { const value = dailyValues[dayIndex]; const date = monthDates[dayIndex]; vehicle.values.set(date, value); batchTotal += value; dailyTotalKm += value; if (value > 0) { batchPositive += 1; dailyPositiveCells += 1; } else { batchZero += 1; dailyZeroCells += 1; } } } monthlyDailyTotals.set(month.key, round(batchTotal)); dailyBatchManifest.push({ type: "车辆里程日报", month: month.key, date_from: month.dateFrom, date_to: month.dateTo, file: path.basename(file), vehicle_rows: seenPlates.size, date_columns: month.dayCount, positive_cells: batchPositive, zero_cells: batchZero, total_km: round(batchTotal), total_mismatch_rows: batchMismatch, completeness_note_rows: batchNotes, }); } assert(dateOrder.length === 1662, `日期列应为 1662,实际 ${dateOrder.length}`); assert(new Set(dateOrder).size === dateOrder.length, "日报日期列存在重复"); assert(dateOrder[0] === "2022-01-01", `起始日期异常:${dateOrder[0]}`); assert(dateOrder.at(-1) === "2026-07-20", `结束日期异常:${dateOrder.at(-1)}`); const statisticRows = []; const monthlyStatisticTotals = new Map(); const statisticBatchManifest = []; for (let monthIndex = 0; monthIndex < months.length; monthIndex += 1) { const month = months[monthIndex]; const file = statisticFiles[monthIndex]; const values = await readFirstSheetValues(file); const headers = (values[0] ?? []).map(cleanText); const plateIndex = findHeader(headers, ["车牌号"]); const orgIndex = findHeader(headers, ["所属机构", "机构"]); const sourceOrgIndex = findHeader(headers, ["来源机构"]); const lengthIndex = findHeader(headers, ["车长(米)", "车长"]); const boxIndex = findHeader(headers, ["厢型"]); const runtimeIndex = findHeader(headers, ["运行时长"]); const mileageIndex = findHeader(headers, ["行驶里程(KM)", "行驶里程"]); assert(plateIndex >= 0, `${path.basename(file)} 缺少车牌号`); assert(mileageIndex >= 0, `${path.basename(file)} 缺少行驶里程`); let batchTotal = 0; let vehicleRows = 0; for (let rowIndex = 1; rowIndex < values.length; rowIndex += 1) { const row = values[rowIndex] ?? []; const plate = cleanText(row[plateIndex]); if (!plate) continue; const mileage = numberValue(row[mileageIndex]); batchTotal += mileage; vehicleRows += 1; statisticRows.push([ month.key, plate, orgIndex >= 0 ? cleanText(row[orgIndex]) : "", sourceOrgIndex >= 0 ? cleanText(row[sourceOrgIndex]) : "", lengthIndex >= 0 ? row[lengthIndex] ?? "" : "", boxIndex >= 0 ? cleanText(row[boxIndex]) : "", runtimeIndex >= 0 ? cleanText(row[runtimeIndex]) : "", mileage, path.basename(file), ]); } monthlyStatisticTotals.set(month.key, round(batchTotal)); statisticBatchManifest.push({ type: "车辆里程统计", month: month.key, date_from: month.dateFrom, date_to: month.dateTo, file: path.basename(file), vehicle_rows: vehicleRows, total_km: round(batchTotal), }); } const mapping = await readPriorMapping(priorMappingCsv); const sortedVehicles = [...vehicles.values()].sort((a, b) => a.plate.localeCompare(b.plate, "zh-CN"), ); const mappedVehicles = sortedVehicles.filter((vehicle) => mapping.has(vehicle.plate)); const unmappedVehicles = sortedVehicles.filter((vehicle) => !mapping.has(vehicle.plate)); await writeImportCsv(importCsv, sortedVehicles, dateOrder); const workbook = Workbook.create(); const summarySheet = workbook.worksheets.add("汇总"); const positiveDailySheet = workbook.worksheets.add("每日里程非零"); const statisticSheet = workbook.worksheets.add("G7里程统计"); const mappingSheet = workbook.worksheets.add("车辆映射"); const batchSheet = workbook.worksheets.add("导出批次"); buildSummarySheet(summarySheet); await buildPositiveDailySheet(positiveDailySheet); await buildStatisticSheet(statisticSheet); await buildMappingSheet(mappingSheet); await buildBatchSheet(batchSheet); const summaryInspect = await workbook.inspect({ kind: "table", sheetId: "汇总", range: "A1:F25", include: "values,formulas", maxChars: 12000, tableMaxRows: 25, tableMaxCols: 6, }); const dailyInspect = await workbook.inspect({ kind: "table", sheetId: "每日里程非零", range: "A1:D12", include: "values,formulas", maxChars: 12000, tableMaxRows: 12, tableMaxCols: 4, }); const formulaErrors = await workbook.inspect({ kind: "match", searchTerm: "#REF!|#DIV/0!|#VALUE!|#NAME\\?|#N/A", options: { useRegex: true, maxResults: 300 }, summary: "final formula error scan", }); assert(!formulaErrors.ndjson.includes('"match"'), "工作簿存在公式错误"); const summaryPreview = await workbook.render({ sheetName: "汇总", range: "A1:F25", scale: 1.5, format: "png", }); await fs.writeFile( path.join(mergedDir, "summary-preview.png"), new Uint8Array(await summaryPreview.arrayBuffer()), ); const dailyPreview = await workbook.render({ sheetName: "每日里程非零", range: "A1:D12", scale: 1.2, format: "png", }); await fs.writeFile( path.join(mergedDir, "daily-preview.png"), new Uint8Array(await dailyPreview.arrayBuffer()), ); try { const output = await SpreadsheetFile.exportXlsx(workbook); await output.save(outputXlsx); } catch (error) { console.error( JSON.stringify( { stage: "export_xlsx", name: error?.name, message: error?.message, stack: String(error?.stack ?? "") .split("\n") .filter((line) => !line.includes("artifact_tool.mjs:3121")) .slice(0, 12), }, null, 2, ), ); process.exit(1); } const monthlyReconciliation = months.map((month) => { const dailyKm = monthlyDailyTotals.get(month.key) ?? 0; const statisticKm = monthlyStatisticTotals.get(month.key) ?? 0; return { month: month.key, daily_km: dailyKm, statistic_km: statisticKm, difference_km: round(dailyKm - statisticKm), }; }); const manifest = { generated_at: new Date().toISOString(), range: { date_from: dateOrder[0], date_to: dateOrder.at(-1), days: dateOrder.length, months: months.length, }, files: { daily_raw: dailyFiles.map((file) => path.basename(file)), statistic_raw: statisticFiles.map((file) => path.basename(file)), workbook: path.basename(outputXlsx), import_csv: path.basename(importCsv), }, vehicle_count: sortedVehicles.length, mapped_vehicle_count: mappedVehicles.length, unmapped_vehicle_count: unmappedVehicles.length, vehicle_day_rows: sortedVehicles.length * dateOrder.length, mapped_vehicle_day_rows: mappedVehicles.length * dateOrder.length, positive_cells: dailyPositiveCells, zero_cells: dailyZeroCells, daily_total_km: round(dailyTotalKm), daily_total_mismatch_rows: dailyTotalMismatchRows, completeness_note_rows: dailyCompletenessNoteRows, monthly_reconciliation: monthlyReconciliation, daily_batches: dailyBatchManifest, statistic_batches: statisticBatchManifest, verification: { summary_inspect: summaryInspect.ndjson, daily_inspect: dailyInspect.ndjson, formula_errors: formulaErrors.ndjson, }, }; await fs.writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, "utf8"); console.log( JSON.stringify( { outputXlsx, importCsv, manifestPath, vehicleCount: sortedVehicles.length, mappedVehicleCount: mappedVehicles.length, unmappedVehicleCount: unmappedVehicles.length, dateCount: dateOrder.length, vehicleDayRows: sortedVehicles.length * dateOrder.length, mappedVehicleDayRows: mappedVehicles.length * dateOrder.length, positiveCells: dailyPositiveCells, zeroCells: dailyZeroCells, totalKm: round(dailyTotalKm), monthlyDifferenceCount: monthlyReconciliation.filter( (row) => Math.abs(row.difference_km) > 0.02, ).length, }, null, 2, ), ); function buildSummarySheet(sheet) { sheet.showGridLines = false; sheet.getRange("A1:F1").merge(); sheet.getRange("A1").values = [["G7 车辆里程全量导出汇总"]]; sheet.getRange("A1:F1").format = { fill: "#0F766E", font: { bold: true, color: "#FFFFFF", size: 16 }, horizontalAlignment: "center", verticalAlignment: "center", }; sheet.getRange("A1:F1").format.rowHeight = 30; const metrics = [ ["指标", "值", "说明"], ["统计起始日期", new Date(`${dateOrder[0]}T00:00:00+08:00`), "G7 日报"], ["统计结束日期", new Date(`${dateOrder.at(-1)}T00:00:00+08:00`), "含当日"], ["自然日数", dateOrder.length, "连续日期"], ["日报导出批次", dailyFiles.length, "每月 1 份"], ["里程统计导出批次", statisticFiles.length, "每月 1 份"], ["G7 车辆数", sortedVehicles.length, "当前导出车辆列表"], ["已映射 VIN 车辆数", mappedVehicles.length, "沿用现有车辆映射"], ["未映射车辆数", unmappedVehicles.length, "不进入正式导入"], ["映射覆盖率", null, "已映射 / G7 车辆数"], ["车辆-日期单元格", sortedVehicles.length * dateOrder.length, "包含 0 km"], ["可导入车辆-日期行", mappedVehicles.length * dateOrder.length, "包含 0 km"], ["正里程单元格", dailyPositiveCells, "> 0 km"], ["0 km 单元格", dailyZeroCells, "按用户确认保留"], ["日报累计里程(km)", round(dailyTotalKm), "55 份日报求和"], ["日报合计不一致车辆月", dailyTotalMismatchRows, "日报“行驶里程”与逐日求和"], ["完整度说明非空车辆月", dailyCompletenessNoteRows, "来自 G7 日报"], ]; sheet.getRangeByIndexes(2, 0, metrics.length, 3).values = metrics; sheet.getRange("A3:C3").format = headerFormat(); sheet.getRange("B4:B5").format.numberFormat = "yyyy-mm-dd"; sheet.getRange("B6:B19").format.numberFormat = "#,##0.00"; sheet.getRange("B12").formulas = [["=IFERROR(B10/B9,0)"]]; sheet.getRange("B12").format.numberFormat = "0.0%"; sheet.getRange("A4:A19").format.font = { bold: true, color: "#134E4A" }; sheet.getRange("A3:C19").format.borders = { preset: "inside", style: "thin", color: "#D1D5DB", }; sheet.getRange("E3:H3").values = [["月份", "日报合计(km)", "里程统计合计(km)", "差异(km)"]]; sheet.getRange("E3:H3").format = headerFormat(); const monthlyRows = months.map((month) => [ month.key, monthlyDailyTotals.get(month.key) ?? 0, monthlyStatisticTotals.get(month.key) ?? 0, null, ]); sheet.getRangeByIndexes(3, 4, monthlyRows.length, 4).values = monthlyRows; sheet.getRange("H4").formulas = [["=F4-G4"]]; sheet.getRange(`H4:H${3 + monthlyRows.length}`).fillDown(); sheet.getRange(`F4:H${3 + monthlyRows.length}`).format.numberFormat = "#,##0.00"; sheet.getRange(`E3:H${3 + monthlyRows.length}`).format.borders = { preset: "inside", style: "thin", color: "#E5E7EB", }; sheet.freezePanes.freezeRows(3); sheet.getRange("A21:C23").merge(); sheet.getRange("A21").values = [[ "Excel 的“每日里程非零”工作表列出全部正里程记录;未出现的车辆×日期组合按用户确认均为 0 km。含全部 0 km 的逐行明细保存在同目录 CSV:G7车辆每日里程_20220101-20260720.csv。", ]]; sheet.getRange("A21:C23").format = { fill: "#ECFDF5", font: { color: "#065F46", italic: true }, wrapText: true, verticalAlignment: "center", }; sheet.getRange(`A1:H${3 + monthlyRows.length}`).format.font = { name: "Aptos", size: 10 }; sheet.getRange(`A1:A${3 + monthlyRows.length}`).format.columnWidth = 24; sheet.getRange(`B1:B${3 + monthlyRows.length}`).format.columnWidth = 18; sheet.getRange(`C1:C${3 + monthlyRows.length}`).format.columnWidth = 30; sheet.getRange(`D1:D${3 + monthlyRows.length}`).format.columnWidth = 3; sheet.getRange(`E1:E${3 + monthlyRows.length}`).format.columnWidth = 12; sheet.getRange(`F1:H${3 + monthlyRows.length}`).format.columnWidth = 18; } async function buildPositiveDailySheet(sheet) { sheet.showGridLines = false; const headers = ["日期", "车牌号", "机构", "每日里程(km)"]; sheet.getRange("A1:D1").values = [headers]; sheet.getRange("A1:D1").format = headerFormat(); const rows = []; for (const vehicle of sortedVehicles) { for (const date of dateOrder) { const mileage = vehicle.values.get(date) ?? 0; if (mileage <= 0) continue; rows.push([ new Date(`${date}T00:00:00+08:00`), vehicle.plate, vehicle.organization, mileage, ]); } } const chunkSize = 1000; for (let start = 0; start < rows.length; start += chunkSize) { const chunk = rows.slice(start, start + chunkSize); sheet.getRangeByIndexes(start + 1, 0, chunk.length, headers.length).values = chunk; } sheet.getRange(`A2:A${rows.length + 1}`).format.numberFormat = "yyyy-mm-dd"; sheet.getRange(`D2:D${rows.length + 1}`).format.numberFormat = "#,##0.00"; sheet.freezePanes.freezeRows(1); [14, 16, 30, 18].forEach((width, index) => { sheet.getRangeByIndexes(0, index, rows.length + 1, 1).format.columnWidth = width; }); } async function buildStatisticSheet(sheet) { sheet.showGridLines = false; const headers = [ "月份", "车牌号", "所属机构", "来源机构", "车长(米)", "厢型", "运行时长", "行驶里程(KM)", "来源文件", ]; sheet.getRange("A1:I1").values = [headers]; sheet.getRange("A1:I1").format = headerFormat(); const chunkSize = 1000; for (let start = 0; start < statisticRows.length; start += chunkSize) { const chunk = statisticRows.slice(start, start + chunkSize); sheet.getRangeByIndexes(start + 1, 0, chunk.length, headers.length).values = chunk; } sheet.getRange(`E2:E${statisticRows.length + 1}`).format.numberFormat = "0.0"; sheet.getRange(`H2:H${statisticRows.length + 1}`).format.numberFormat = "#,##0.00"; sheet.freezePanes.freezeRows(1); const widths = [12, 16, 28, 28, 12, 16, 18, 18, 34]; widths.forEach((width, index) => { sheet.getRangeByIndexes(0, index, statisticRows.length + 1, 1).format.columnWidth = width; }); } async function buildMappingSheet(sheet) { sheet.showGridLines = false; const headers = ["车牌号", "机构", "VIN", "终端号", "映射状态", "期间里程(km)", "正里程天数", "0 km天数"]; sheet.getRange("A1:H1").values = [headers]; sheet.getRange("A1:H1").format = headerFormat(); const rows = sortedVehicles.map((vehicle) => { const mapped = mapping.get(vehicle.plate); const allValues = dateOrder.map((date) => vehicle.values.get(date) ?? 0); const positiveDays = allValues.filter((value) => value > 0).length; return [ vehicle.plate, vehicle.organization, mapped?.vin ?? "", mapped?.phone ?? "", mapped ? "已映射" : "未映射", round(allValues.reduce((sum, value) => sum + value, 0)), positiveDays, dateOrder.length - positiveDays, ]; }); sheet.getRangeByIndexes(1, 0, rows.length, headers.length).values = rows; sheet.getRange(`F2:F${rows.length + 1}`).format.numberFormat = "#,##0.00"; sheet.getRange(`E2:E${rows.length + 1}`).conditionalFormats.add("containsText", { text: "未映射", format: { fill: "#FEE2E2", font: { color: "#991B1B", bold: true } }, }); sheet.freezePanes.freezeRows(1); [16, 30, 22, 18, 14, 18, 14, 14].forEach((width, index) => { sheet.getRangeByIndexes(0, index, rows.length + 1, 1).format.columnWidth = width; }); } async function buildBatchSheet(sheet) { sheet.showGridLines = false; const headers = [ "类型", "月份", "开始日期", "结束日期", "来源文件", "车辆数", "日期列数", "正里程单元格", "0 km 单元格", "合计里程(km)", ]; sheet.getRange("A1:J1").values = [headers]; sheet.getRange("A1:J1").format = headerFormat(); const rows = [ ...dailyBatchManifest.map((batch) => [ batch.type, batch.month, new Date(`${batch.date_from}T00:00:00+08:00`), new Date(`${batch.date_to}T00:00:00+08:00`), batch.file, batch.vehicle_rows, batch.date_columns, batch.positive_cells, batch.zero_cells, batch.total_km, ]), ...statisticBatchManifest.map((batch) => [ batch.type, batch.month, new Date(`${batch.date_from}T00:00:00+08:00`), new Date(`${batch.date_to}T00:00:00+08:00`), batch.file, batch.vehicle_rows, "", "", "", batch.total_km, ]), ]; sheet.getRangeByIndexes(1, 0, rows.length, headers.length).values = rows; sheet.getRange(`C2:D${rows.length + 1}`).format.numberFormat = "yyyy-mm-dd"; sheet.getRange(`F2:J${rows.length + 1}`).format.numberFormat = "#,##0.00"; sheet.freezePanes.freezeRows(1); [18, 12, 14, 14, 36, 12, 12, 18, 18, 18].forEach((width, index) => { sheet.getRangeByIndexes(0, index, rows.length + 1, 1).format.columnWidth = width; }); } async function readFirstSheetValues(file) { const input = await FileBlob.load(file); const workbook = await SpreadsheetFile.importXlsx(input); const sheet = workbook.worksheets.getItemAt(0); const usedRange = sheet.getUsedRange(true); return usedRange?.values ?? []; } async function sortedXlsxFiles(directory) { const names = (await fs.readdir(directory)) .filter((name) => name.endsWith(".xlsx")) .sort((a, b) => extractTaskId(a) - extractTaskId(b)); return names.map((name) => path.join(directory, name)); } function extractTaskId(file) { const match = path.basename(file).match(/-(\d+)\.xlsx$/); assert(match, `无法从文件名解析任务号:${file}`); return Number(match[1]); } function buildMonths() { const result = []; for (let year = 2022, month = 1; year < 2026 || (year === 2026 && month <= 7); ) { const naturalLastDay = new Date(Date.UTC(year, month, 0)).getUTCDate(); const lastDay = year === 2026 && month === 7 ? 20 : naturalLastDay; result.push({ key: `${year}-${pad(month)}`, year, month, dayCount: lastDay, dateFrom: `${year}-${pad(month)}-01`, dateTo: `${year}-${pad(month)}-${pad(lastDay)}`, }); month += 1; if (month === 13) { year += 1; month = 1; } } return result; } async function readPriorMapping(csvPath) { const text = await fs.readFile(csvPath, "utf8"); const lines = text.replace(/^\uFEFF/, "").split(/\r?\n/); const result = new Map(); for (const line of lines.slice(1)) { if (!line) continue; const columns = parseCsvLine(line); const [vin, plate, phone] = columns; if (!vin || !plate) continue; const current = result.get(plate); if (current && current.vin !== vin) { result.delete(plate); continue; } result.set(plate, { vin, phone: phone ?? "" }); } return result; } async function writeImportCsv(file, vehicleRows, dates) { const stream = fsSync.createWriteStream(file, { encoding: "utf8" }); stream.write("\uFEFFplate,date,daily_mileage_km\n"); for (const vehicle of vehicleRows) { for (const date of dates) { const value = vehicle.values.get(date) ?? 0; if (!stream.write(`${csvCell(vehicle.plate)},${date},${numberText(value)}\n`)) { await once(stream, "drain"); } } } stream.end(); await once(stream, "finish"); } function headerFormat() { return { fill: "#0F766E", font: { bold: true, color: "#FFFFFF" }, horizontalAlignment: "center", verticalAlignment: "center", wrapText: true, borders: { preset: "outside", style: "thin", color: "#115E59" }, }; } function findHeader(headers, names) { return headers.findIndex((header) => names.includes(header)); } function parseCsvLine(line) { const result = []; let value = ""; let quoted = false; for (let index = 0; index < line.length; index += 1) { const char = line[index]; if (quoted) { if (char === '"' && line[index + 1] === '"') { value += '"'; index += 1; } else if (char === '"') { quoted = false; } else { value += char; } } else if (char === '"') { quoted = true; } else if (char === ",") { result.push(value); value = ""; } else { value += char; } } result.push(value); return result; } function csvCell(value) { const text = cleanText(value); return /[",\r\n]/.test(text) ? `"${text.replaceAll('"', '""')}"` : text; } function cleanText(value) { return value === null || value === undefined ? "" : String(value).trim(); } function numberValue(value) { const number = Number(value); return Number.isFinite(number) ? number : 0; } function numberText(value) { return String(round(value)); } function round(value) { return Math.round((value + Number.EPSILON) * 100) / 100; } function pad(value) { return String(value).padStart(2, "0"); } function assert(condition, message) { if (!condition) throw new Error(message); }