leistungsbilanz-ts/tests/print-document-model.test.ts

248 lines
7.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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> = {}
): 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<DistributionBoardDto>,
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);
});
});