import assert from "node:assert/strict"; import { describe, it } from "node:test"; import { assertPrintRevisionConsistency, buildBoardHierarchy, formatFeedingReference, formatCircuitCable, formatProtectionDevice, summarizePrintBoard, } from "../src/frontend/print/print-document-model.js"; import type { CircuitTreeResponseDto, DistributionBoardDto, } from "../src/frontend/types.js"; function board( id: string, info: Partial = {} ): DistributionBoardDto { return { id, projectId: "project-1", name: id, floorId: null, supplyType: "AV", simultaneityFactor: 1, boardNumber: null, location: null, fedFrom: null, remark: null, feedingDistributionBoardId: null, feedingCircuitId: null, ...info, }; } function tree( rows: Array<{ quantity: number; powerPerUnit: number; simultaneityFactor: number; cosPhi?: number; }>, boardSimultaneityFactor = 1 ): CircuitTreeResponseDto { const deviceRows = rows.map((row, index) => ({ id: `row-${index}`, sortOrder: index, name: "Gerät", displayName: "Gerät", manualQuantity: row.quantity, rowTotalPower: row.quantity * row.powerPerUnit * row.simultaneityFactor, ...row, })); const total = deviceRows.reduce((sum, row) => sum + row.rowTotalPower, 0); return { circuitListId: "list", distributionBoardId: "board", distributionBoardName: "Board", currentRevision: 1, singlePhaseVoltageV: 230, threePhaseVoltageV: 400, distributionBoardSimultaneityFactor: boardSimultaneityFactor, distributionBoardTotalPower: total, distributionBoardTotalPowerWithSimultaneityFactor: total * boardSimultaneityFactor, headerComponents: [], footerComponents: [], sections: [ { id: "section", key: "single_phase", displayName: "1-phasig 1", prefix: "-2F1.", sortOrder: 1, sectionTotalPower: total, components: [], circuits: [ { id: "circuit", circuitListId: "list", sectionId: "section", equipmentIdentifier: "-2F1.1", sortOrder: 1, isReserve: false, circuitTotalPower: total, circuitApparentPower: deviceRows.reduce( (sum, row) => sum + row.rowTotalPower / (row.cosPhi || 1), 0 ), circuitCosPhi: 1, deviceRows, downstreamBoardLoads: [], }, ], }, ], }; } function summary( id: string, info: Partial, demandKw: number ) { return summarizePrintBoard({ board: board(id, info), circuitListId: id, floorName: null, tree: tree([{ quantity: 1, powerPerUnit: demandKw, simultaneityFactor: 1 }]), }); } describe("print document model", () => { it("rejects mixed project revisions instead of printing mislabeled data", () => { const summaries = [summary("uv", {}, 1)]; assert.doesNotThrow(() => assertPrintRevisionConsistency(1, summaries, 1)); assert.throws(() => assertPrintRevisionConsistency(0, summaries, 0), /neu laden/); assert.throws(() => assertPrintRevisionConsistency(1, summaries, 2), /neu laden/); assert.throws(() => assertPrintRevisionConsistency(1, [], 2), /neu laden/); }); it("prints both cable type and cross-section without hiding either value", () => { const circuit = tree([]).sections[0].circuits[0]; assert.equal(formatCircuitCable({ ...circuit, cableType: "NYM-J", cableCrossSection: "3x2,5" }), "NYM-J · 3x2,5"); assert.equal(formatCircuitCable({ ...circuit, cableType: "NYM-J" }), "NYM-J"); assert.equal(formatCircuitCable({ ...circuit, cableCrossSection: "3x2,5" }), "3x2,5"); assert.equal(formatCircuitCable(circuit), null); }); it("does not derive hierarchy from the external free text", () => { const summaries = [ summary("a", { boardNumber: "HV" }, 2), summary("b", { name: "HV" }, 3), summary("uv", { fedFrom: " hv " }, 1), ]; for (const ordered of [summaries, [...summaries].reverse()]) { const entries = buildBoardHierarchy(ordered); assert.ok(entries.every((entry) => entry.parentBoardId === null)); } }); it("derives installed, device and demand power and the design current", () => { const result = summarizePrintBoard({ board: board("uv"), circuitListId: "uv", floorName: "EG", tree: tree( [ { quantity: 4, powerPerUnit: 2, simultaneityFactor: 0.5, cosPhi: 0.8 }, { quantity: 1, powerPerUnit: 6, simultaneityFactor: 1 }, ], 0.5 ), }); assert.equal(result.circuitCount, 1); assert.equal(result.installedPowerKw, 14); assert.equal(result.powerBeforeBoardSimultaneityKw, 10); assert.equal(result.demandPowerKw, 5); // S = (4 / 0.8 + 6 / 1) × 0.5 = 5.5 kVA → I = 5500 / (√3 · 400) assert.ok(Math.abs(result.designCurrentA - 5500 / (Math.sqrt(3) * 400)) < 1e-9); }); it("uses the selected one-phase feeder circuit for the design current", () => { const inputTree = tree([ { quantity: 1, powerPerUnit: 2.3, simultaneityFactor: 1, cosPhi: 1 }, ]); inputTree.feedingReference = { distributionBoardId: "hv", circuitListId: "hv-list", distributionBoardName: "Hauptverteilung", distributionBoardNumber: "NSHV", circuitId: "out", circuitEquipmentIdentifier: "-2F1.1", circuitCategory: "single_phase", }; const result = summarizePrintBoard({ board: board("uv"), circuitListId: "uv", floorName: null, tree: inputTree, }); assert.ok(Math.abs(result.designCurrentA - 10) < 1e-9); }); it("nests boards by their logical feeding references", () => { const entries = buildBoardHierarchy([ summary("hv", { boardNumber: "HV 0.1", fedFrom: "HAK" }, 10), summary("uv-a", { boardNumber: "UV 1.1", feedingDistributionBoardId: "hv" }, 4), summary("uv-b", { boardNumber: "UV 1.2", feedingDistributionBoardId: "uv-a" }, 2), summary("uv-c", { name: "UV-C", fedFrom: "unbekannt" }, 1), ]); assert.deepEqual( entries.map((entry) => [ entry.summary.board.id, entry.depth, entry.parentBoardId, ]), [ ["hv", 0, null], ["uv-a", 1, "hv"], ["uv-b", 2, "uv-a"], ["uv-c", 0, null], ] ); }); it("keeps malformed cyclic logical links visible at the top level", () => { const entries = buildBoardHierarchy([ summary("a", { boardNumber: "A", feedingDistributionBoardId: "b" }, 1), summary("b", { boardNumber: "B", feedingDistributionBoardId: "a" }, 1), ]); assert.equal(entries.length, 2); assert.ok(entries.some((entry) => entry.parentBoardId === null)); }); it("formats the resolved feeding board and circuit instead of free text", () => { const value = summary("uv", { fedFrom: "Alttext" }, 1); value.tree.feedingReference = { distributionBoardId: "hv", circuitListId: "hv-list", distributionBoardName: "Hauptverteilung", distributionBoardNumber: "NSHV", circuitId: "out", circuitEquipmentIdentifier: "-3F1.1", circuitCategory: "three_phase", }; assert.equal(formatFeedingReference(value), "NSHV-3F1.1"); }); it("formats protection devices for the plan table", () => { assert.equal( formatProtectionDevice({ type: "LS", ratedCurrentA: 16, tripCharacteristic: "B" }), "LS B 16 A" ); assert.equal( formatProtectionDevice({ type: "FI_LS", ratedCurrentA: 16, tripCharacteristic: "B", rcdType: "A", ratedResidualCurrentMa: 30, }), "FI/LS B 16 A, 30 mA Typ A" ); assert.equal(formatProtectionDevice(undefined), null); }); });