import assert from "node:assert/strict"; import { describe, it } from "node:test"; import { buildCableSizingAlerts, calculateCableSizing, isCableSizingDataVerified, type CableSizingInput, } from "../src/cable-sizing/domain/cable-sizing-calculation.js"; const BASE_INPUT: CableSizingInput = { phase: 1, mode: "power", powerKw: 5, cosPhi: 1, voltage: 230, lengthM: 30, layingMethod: "C", conductorMaterial: "copper", insulation: "pvc", ambientTemperatureC: 30, groupingCircuits: 1, maxVoltageDropPercent: 3, harmonicNeutralLoad: "none", }; describe("calculateCableSizing", () => { it("matches the verified reference case (1~, 5 kW, 230 V, 30 m, method C, copper)", () => { const result = calculateCableSizing(BASE_INPUT); assert.equal(result.dataVerified, true); assert.ok(Math.abs(result.operatingCurrentA - 21.739) < 0.01); assert.equal(result.crossSectionByCapacityMm2, 2.5); assert.equal(result.crossSectionByVoltageDropMm2, 4); assert.equal(result.recommendedCrossSectionMm2, 4); }); it("returns dataVerified: false and no numeric result for an unverified laying method", () => { const result = calculateCableSizing({ ...BASE_INPUT, layingMethod: "A2" }); assert.equal(result.dataVerified, false); assert.equal(result.recommendedCrossSectionMm2, null); assert.equal(result.rows.length, 0); // The operating current itself does not depend on the capacity table // and is still reported so the UI can show at least that much. assert.ok(Math.abs(result.operatingCurrentA - 21.739) < 0.01); }); it("returns dataVerified: false for xlpe regardless of method", () => { const result = calculateCableSizing({ ...BASE_INPUT, insulation: "xlpe" }); assert.equal(result.dataVerified, false); }); it("isCableSizingDataVerified matches the six ported, verified methods only", () => { for (const method of ["A1", "B2", "C", "E", "D1", "D2"] as const) { assert.equal(isCableSizingDataVerified(method, "pvc"), true, method); } for (const method of ["A2", "B1", "F", "G"] as const) { assert.equal(isCableSizingDataVerified(method, "pvc"), false, method); } }); it("applies the 0.86 harmonic reduction factor only for three-phase + 15to33Percent", () => { const threePhase: CableSizingInput = { ...BASE_INPUT, phase: 3, mode: "current", currentA: 10, powerKw: undefined, voltage: 400, }; const base = calculateCableSizing({ ...threePhase, harmonicNeutralLoad: "none" }); const derated = calculateCableSizing({ ...threePhase, harmonicNeutralLoad: "15to33Percent", }); assert.equal(base.harmonicReductionApplied, false); assert.equal(derated.harmonicReductionApplied, true); assert.ok( Math.abs(derated.combinedDerationFactor - base.combinedDerationFactor * 0.86) < 1e-9 ); const singlePhaseWithHarmonics = calculateCableSizing({ ...BASE_INPUT, harmonicNeutralLoad: "15to33Percent", }); assert.equal(singlePhaseWithHarmonics.harmonicReductionApplied, false); }); it("uses max(operatingCurrentA, existingProtectionRatedCurrentA) as the design current for cross-section selection", () => { // Load alone (21.7 A) would recommend 4 mm² (see the reference case // above); a 32 A breaker on the same circuit must still be covered by // the cable (In <= Iz), so the recommendation should grow accordingly. const withoutBreaker = calculateCableSizing(BASE_INPUT); const withBreaker = calculateCableSizing({ ...BASE_INPUT, existingProtectionRatedCurrentA: 32, }); assert.equal(withoutBreaker.designCurrentA, withoutBreaker.operatingCurrentA); assert.equal(withBreaker.designCurrentA, 32); assert.ok( (withBreaker.recommendedCrossSectionMm2 ?? 0) >= (withoutBreaker.recommendedCrossSectionMm2 ?? 0) ); assert.ok(withBreaker.protectionCoordination?.coordinated); }); it("reports an oversized breaker as the limiting factor when no cross-section can cover it", () => { const result = calculateCableSizing({ ...BASE_INPUT, existingProtectionRatedCurrentA: 1000, }); assert.equal(result.designCurrentA, 1000); assert.equal(result.recommendedCrossSectionMm2, null); // No cross-section satisfies the design current at all, so there is no // "recommended but under-protected" case to flag - protectionCoordination // is only meaningful once a recommendation exists. assert.equal(result.protectionCoordination, null); const alerts = buildCableSizingAlerts( { ...BASE_INPUT, existingProtectionRatedCurrentA: 1000 }, result ); assert.equal(alerts.length, 1); assert.equal(alerts[0].kind, "critical"); assert.ok(alerts[0].text.includes("Vorhandene Sicherung")); }); }); describe("buildCableSizingAlerts", () => { it("returns a single critical alert for an unverified combination, no numeric claims", () => { const input: CableSizingInput = { ...BASE_INPUT, layingMethod: "G" }; const result = calculateCableSizing(input); const alerts = buildCableSizingAlerts(input, result); assert.equal(alerts.length, 1); assert.equal(alerts[0].kind, "critical"); assert.ok(alerts[0].text.includes("keine geprüften")); }); it("returns a single ok alert for a clean, unremarkable case", () => { const input: CableSizingInput = { ...BASE_INPUT, mode: "current", currentA: 15, powerKw: undefined, lengthM: 3, }; const result = calculateCableSizing(input); const alerts = buildCableSizingAlerts(input, result); assert.equal(alerts.length, 1); assert.equal(alerts[0].kind, "ok"); }); });