forked from jappel/leistungsbilanz-ts
Add Linear Excel transport
This commit is contained in:
parent
d4803a18d2
commit
52d3b1a270
10 changed files with 2131 additions and 83 deletions
|
|
@ -1,81 +0,0 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { createHash } from "node:crypto";
|
||||
import { createDefaultExternalCsvConfiguration } from "../src/external-model/csv/external-csv-contracts.js";
|
||||
import {
|
||||
parseExternalCsv,
|
||||
serializeExternalCsv,
|
||||
} from "../src/external-model/csv/external-csv-transport.js";
|
||||
import { createExternalCsvPreview } from "../src/external-model/application/external-csv-preview.js";
|
||||
|
||||
const filePath = resolve(
|
||||
process.argv[2] ?? "docs/spec/ELT Stromkreisnummernvergabe-Check_DIV.csv"
|
||||
);
|
||||
const source = readFileSync(filePath);
|
||||
const configuration = createDefaultExternalCsvConfiguration({
|
||||
ifcGuid: "IfcGUID",
|
||||
roomNumber: "MEP-Raum: Nummer",
|
||||
roomName: "MEP-Raum: Name",
|
||||
familyAndType: "Familie und Typ",
|
||||
selectionMarker: "CAx_Auswahlkenner",
|
||||
circuitIdentifier: "kbp_Stromkreisnummer",
|
||||
power: "kbp-E-Elektrische Leistung",
|
||||
quantity: null,
|
||||
});
|
||||
configuration.additionalSourceMappings = [
|
||||
{ sourceColumn: "CAx_Anmerkung", targetField: "sourceRemark" },
|
||||
{ sourceColumn: "kbp-E-Spannung", targetField: "sourceVoltage" },
|
||||
{ sourceColumn: "kbp-E-Stromstärke", targetField: "sourceCurrent" },
|
||||
{ sourceColumn: "kbp-E-Versorgung von ELT", targetField: "sourceElectricalSupply" },
|
||||
{ sourceColumn: "kbp-E-Versorgung von MSR/GLT", targetField: "sourceControlSupply" },
|
||||
];
|
||||
|
||||
const document = parseExternalCsv(source, configuration);
|
||||
const counts = Object.fromEntries(
|
||||
["metadata", "header", "passthrough", "object", "suspect-object"].map(
|
||||
(classification) => [
|
||||
classification,
|
||||
document.rows.filter((row) => row.classification === classification).length,
|
||||
]
|
||||
)
|
||||
);
|
||||
const nonEmptyPassthroughRows = document.rows.filter(
|
||||
(row) =>
|
||||
row.classification === "passthrough" &&
|
||||
row.cells.some((cell) => cell.value !== "")
|
||||
).length;
|
||||
const serialized = serializeExternalCsv(document);
|
||||
const preview = createExternalCsvPreview({
|
||||
fileName: filePath,
|
||||
bytes: source,
|
||||
configuration,
|
||||
});
|
||||
|
||||
assert.equal(counts.object, 897, "Expected 897 object rows.");
|
||||
assert.equal(preview.objectCount, 897, "Expected 897 preview objects.");
|
||||
assert.equal(nonEmptyPassthroughRows, 211, "Expected 211 non-empty passthrough rows.");
|
||||
assert.equal(counts["suspect-object"], 0, "Expected no suspect object rows.");
|
||||
assert.equal(
|
||||
Buffer.compare(Buffer.from(serialized), source),
|
||||
0,
|
||||
"Expected a byte-identical CSV round-trip."
|
||||
);
|
||||
|
||||
const sha256 = createHash("sha256").update(source).digest("hex");
|
||||
process.stdout.write(
|
||||
`${JSON.stringify(
|
||||
{
|
||||
file: filePath,
|
||||
bytes: source.length,
|
||||
sha256,
|
||||
dialect: document.dialect,
|
||||
rows: counts,
|
||||
nonEmptyPassthroughRows,
|
||||
roundTripByteIdentical: true,
|
||||
previewObjectCount: preview.objectCount,
|
||||
},
|
||||
null,
|
||||
2
|
||||
)}\n`
|
||||
);
|
||||
162
scripts/verify-revit-reference-xlsx.ts
Normal file
162
scripts/verify-revit-reference-xlsx.ts
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { createHash } from "node:crypto";
|
||||
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import ExcelJS from "exceljs";
|
||||
import {
|
||||
createDefaultExternalXlsxTransportConfiguration,
|
||||
type ExternalXlsxScalar,
|
||||
} from "../src/external-model/xlsx/external-xlsx-contracts.js";
|
||||
import {
|
||||
createExternalXlsxReturn,
|
||||
parseExternalXlsx,
|
||||
} from "../src/external-model/xlsx/external-xlsx-transport.js";
|
||||
|
||||
async function main() {
|
||||
const inputPath = resolve(
|
||||
process.argv[2] ?? "docs/spec/kbp-Stromkreis.xlsx"
|
||||
);
|
||||
const outputPath = resolve(
|
||||
process.argv[3] ?? "data/revit-reference/kbp-Stromkreis-R2-Rueckgabe.xlsx"
|
||||
);
|
||||
assert.notEqual(
|
||||
inputPath,
|
||||
outputPath,
|
||||
"Eingabe- und Ausgabedatei müssen verschieden sein."
|
||||
);
|
||||
|
||||
const source = readFileSync(inputPath);
|
||||
const configuration = createDefaultExternalXlsxTransportConfiguration();
|
||||
const document = await parseExternalXlsx({
|
||||
fileName: inputPath,
|
||||
bytes: source,
|
||||
configuration,
|
||||
});
|
||||
const objectRows = document.rows.filter((row) => row.classification === "object");
|
||||
const suspectRows = document.rows.filter(
|
||||
(row) => row.classification === "suspect-object"
|
||||
);
|
||||
const worksharingIdColumn = document.columns.find(
|
||||
(column) => column.header === configuration.columns.worksharingId
|
||||
)?.columnNumber;
|
||||
assert.ok(worksharingIdColumn, "Worksharing-ID-Spalte wurde nicht aufgelöst.");
|
||||
|
||||
assert.equal(objectRows.length, 487, "Es werden 487 Objektzeilen erwartet.");
|
||||
assert.equal(suspectRows.length, 0, "Es werden keine verdächtigen Zeilen erwartet.");
|
||||
assert.equal(
|
||||
document.columns.filter((column) => column.comment !== null).length,
|
||||
14,
|
||||
"Es werden 14 Kopfzeilenkommentare erwartet."
|
||||
);
|
||||
assert.equal(
|
||||
document.issues.filter((issue) => issue.code === "duplicate-ifc-guid").length,
|
||||
0,
|
||||
"Die Musterdatei darf keine exakten IfcGUID-Duplikate enthalten."
|
||||
);
|
||||
|
||||
const testValues = [
|
||||
{
|
||||
circuitCombined: "UV_R2_TEST-2F1",
|
||||
electricalPowerW: 111,
|
||||
voltageV: 230,
|
||||
currentA: 0.48,
|
||||
},
|
||||
{
|
||||
circuitCombined: "UV_R2_TEST-2F2",
|
||||
electricalPowerW: 222,
|
||||
voltageV: 230,
|
||||
currentA: 0.97,
|
||||
},
|
||||
{
|
||||
circuitCombined: "UV_R2_TEST-3F1",
|
||||
electricalPowerW: 333,
|
||||
voltageV: 400,
|
||||
currentA: 0.48,
|
||||
},
|
||||
] satisfies Record<string, ExternalXlsxScalar>[];
|
||||
|
||||
const updates = testValues.map((values, index) => {
|
||||
const value = objectRows[index]?.cells[worksharingIdColumn - 1]?.value;
|
||||
if (typeof value !== "string") {
|
||||
throw new Error("Worksharing-ID muss Text sein.");
|
||||
}
|
||||
return { worksharingId: value, values };
|
||||
});
|
||||
const result = await createExternalXlsxReturn({
|
||||
fileName: inputPath,
|
||||
bytes: source,
|
||||
configuration,
|
||||
updates,
|
||||
});
|
||||
assert.equal(result.exportedObjectCount, 487);
|
||||
mkdirSync(dirname(outputPath), { recursive: true });
|
||||
writeFileSync(outputPath, result.bytes);
|
||||
|
||||
const outputDocument = await parseExternalXlsx({
|
||||
fileName: outputPath,
|
||||
bytes: result.bytes,
|
||||
configuration,
|
||||
});
|
||||
assert.equal(
|
||||
outputDocument.rows.filter((row) => row.classification === "object").length,
|
||||
487
|
||||
);
|
||||
assert.equal(
|
||||
outputDocument.rows.some((row) => row.classification !== "object"),
|
||||
false,
|
||||
"Die Rückgabedatei darf keine Summen- oder Hinweiszeilen enthalten."
|
||||
);
|
||||
assert.equal(outputDocument.sheetName, document.sheetName);
|
||||
assert.deepEqual(
|
||||
outputDocument.columns,
|
||||
document.columns,
|
||||
"Kopfzeilen, Kommentare und Spaltenbreiten müssen erhalten bleiben."
|
||||
);
|
||||
assert.deepEqual(
|
||||
outputDocument.rows.slice(updates.length).map((row) => row.cells),
|
||||
objectRows.slice(updates.length).map((row) => row.cells),
|
||||
"Nicht geänderte Objektzellen müssen Wert, Typ und Zahlenformat behalten."
|
||||
);
|
||||
const outputWorkbook = new ExcelJS.Workbook();
|
||||
await outputWorkbook.xlsx.load(result.bytes as never);
|
||||
const commentedCells: string[] = [];
|
||||
outputWorkbook.worksheets[0].eachRow({ includeEmpty: true }, (row) => {
|
||||
row.eachCell({ includeEmpty: true }, (cell) => {
|
||||
if (cell.note !== undefined) commentedCells.push(cell.address);
|
||||
});
|
||||
});
|
||||
assert.equal(
|
||||
commentedCells.length,
|
||||
14,
|
||||
"Nur die 14 Kopfzellen dürfen Kommentare enthalten."
|
||||
);
|
||||
assert.equal(
|
||||
commentedCells.every((address) => address.endsWith("1")),
|
||||
true,
|
||||
"Datenzellen dürfen keine Kommentare enthalten."
|
||||
);
|
||||
|
||||
process.stdout.write(
|
||||
`${JSON.stringify(
|
||||
{
|
||||
input: inputPath,
|
||||
output: outputPath,
|
||||
inputBytes: source.length,
|
||||
outputBytes: result.bytes.length,
|
||||
inputSha256: createHash("sha256").update(source).digest("hex"),
|
||||
outputSha256: createHash("sha256").update(result.bytes).digest("hex"),
|
||||
sheetName: document.sheetName,
|
||||
objectCount: objectRows.length,
|
||||
headerCommentCount: document.columns.filter(
|
||||
(column) => column.comment !== null
|
||||
).length,
|
||||
exactDuplicateIfcGuidCount: 0,
|
||||
changedObjectCount: updates.length,
|
||||
},
|
||||
null,
|
||||
2
|
||||
)}\n`
|
||||
);
|
||||
}
|
||||
|
||||
void main();
|
||||
Loading…
Add table
Add a link
Reference in a new issue