import assert from "node:assert/strict"; import { describe, it } from "node:test"; import { buildPreliminaryBoardHierarchy, 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, ...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", 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, deviceRows, }, ], }, ], }; } 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("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.devicePowerKw, 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("nests boards by matching free-text feed and sums downstream demand", () => { const entries = buildPreliminaryBoardHierarchy([ summary("hv", { boardNumber: "HV 0.1", fedFrom: "HAK" }, 10), summary("uv-a", { boardNumber: "UV 1.1", fedFrom: " hv 0.1 " }, 4), summary("uv-b", { boardNumber: "UV 1.2", fedFrom: "UV 1.1" }, 2), summary("uv-c", { name: "UV-C", fedFrom: "unbekannt" }, 1), ]); assert.deepEqual( entries.map((entry) => [ entry.summary.board.id, entry.depth, entry.parentBoardId, entry.cumulatedDemandPowerKw, ]), [ ["hv", 0, null, 16], ["uv-a", 1, "hv", 6], ["uv-b", 2, "uv-a", 2], ["uv-c", 0, null, 1], ] ); }); it("keeps boards with cyclic feed texts at the top level", () => { const entries = buildPreliminaryBoardHierarchy([ summary("a", { boardNumber: "A", fedFrom: "B" }, 1), summary("b", { boardNumber: "B", fedFrom: "A" }, 1), ]); assert.equal(entries.length, 2); assert.ok(entries.some((entry) => entry.parentBoardId === null)); assert.equal( entries.reduce((sum, entry) => sum + (entry.depth === 0 ? entry.cumulatedDemandPowerKw : 0), 0), 2 ); }); 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); }); });