import { createHash } from "node:crypto"; import type { ExternalCsvConfiguration } from "../csv/external-csv-contracts.js"; import { parseExternalCsv } from "../csv/external-csv-transport.js"; export interface ExternalCsvPreviewObject { rowNumber: number; ifcGuid: string; roomNumber: string; roomName: string; familyAndType: string; selectionMarker: string; circuitIdentifier: string; power: string; quantity: string | null; additionalSourceValues: Record; } export interface ExternalCsvPreview { fileName: string; byteLength: number; sha256: string; dialect: ReturnType["dialect"]; headerRowNumber: number; headerColumns: string[]; rowCount: number; objectCount: number; passthroughCount: number; nonEmptyPassthroughCount: number; suspectObjectCount: number; objects: ExternalCsvPreviewObject[]; suspectRowNumbers: number[]; } export function createExternalCsvPreview(input: { fileName: string; bytes: Uint8Array; configuration: ExternalCsvConfiguration; }): ExternalCsvPreview { const document = parseExternalCsv(input.bytes, input.configuration); const header = document.rows[document.headerRowIndex]; const headerIndex = new Map( header.cells.map((cell, index) => [cell.value, index]) ); const valueAt = (row: (typeof document.rows)[number], column: string) => row.cells[headerIndex.get(column)!]?.value ?? ""; const objects = document.rows .filter((row) => row.classification === "object") .map((row): ExternalCsvPreviewObject => ({ rowNumber: row.index + 1, ifcGuid: valueAt(row, input.configuration.columns.ifcGuid), roomNumber: valueAt(row, input.configuration.columns.roomNumber), roomName: valueAt(row, input.configuration.columns.roomName), familyAndType: valueAt(row, input.configuration.columns.familyAndType), selectionMarker: valueAt(row, input.configuration.columns.selectionMarker), circuitIdentifier: valueAt(row, input.configuration.columns.circuitIdentifier), power: valueAt(row, input.configuration.columns.power), quantity: input.configuration.columns.quantity === null ? null : valueAt(row, input.configuration.columns.quantity), additionalSourceValues: Object.fromEntries( input.configuration.additionalSourceMappings.map((mapping) => [ mapping.targetField, valueAt(row, mapping.sourceColumn), ]) ), })); const passthroughRows = document.rows.filter( (row) => row.classification === "passthrough" ); const suspectRows = document.rows.filter( (row) => row.classification === "suspect-object" ); return { fileName: input.fileName, byteLength: input.bytes.byteLength, sha256: createHash("sha256").update(input.bytes).digest("hex"), dialect: document.dialect, headerRowNumber: document.headerRowIndex + 1, headerColumns: header.cells.map((cell) => cell.value), rowCount: document.rows.length, objectCount: objects.length, passthroughCount: passthroughRows.length, nonEmptyPassthroughCount: passthroughRows.filter((row) => row.cells.some((cell) => cell.value !== "") ).length, suspectObjectCount: suspectRows.length, objects, suspectRowNumbers: suspectRows.map((row) => row.index + 1), }; }