Per-circuit cross-section recommendation as a separate module attached to the circuit list. It never writes project data itself: "Übernehmen" applies cable type, cross-section and length through the regular circuit.update command, so revisions and Undo/Redo stay intact. - Pure domain module (src/cable-sizing) with IEC 60364-5-52:2009 Annex B tables for PVC and XLPE, copper and aluminium, methods A1, A2, B1, B2, C, D1, D2 and E. Every value was cross-checked against public reproductions; where sources differ the lower value is used (docs/cable-sizing-module.md). - Temperature and grouping between table values round to the safer value; values outside the tables return no result instead of a factor of 1. Ground methods use B.52.18 (D2) and B.52.19 A (D1). - Overload protection: circuit-breakers In <= Iz; gG fuses additionally I2 <= 1.45 Iz with I2 = 1.6/1.9/2.1 In. - Inputs prefilled from the circuit: power, phase (group or device rows), project voltage, power-weighted cos phi, length and protective device. - Opened from a "Leitungsberechnung" button in the row actions, not from a grid cell, so cable cells keep the spreadsheet behavior. - Stateless API under /api/cable-sizing; no database table or migration. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
54 lines
1.9 KiB
TypeScript
54 lines
1.9 KiB
TypeScript
// Derives the cable-sizing inputs that follow from the circuit itself, so the
|
|
// planner starts from the circuit's real values instead of fixed defaults.
|
|
import type {
|
|
CircuitCategory,
|
|
ExistingProtection,
|
|
} from "../../cable-sizing/domain/cable-sizing-calculation";
|
|
import type { CircuitTreeCircuitDto, CircuitTreeProtectionDeviceDto } from "../types";
|
|
|
|
/** Three-phase if the circuit group or any device row is three-phase. */
|
|
export function deriveCircuitPhase(
|
|
circuit: CircuitTreeCircuitDto,
|
|
circuitCategory?: CircuitCategory
|
|
): 1 | 3 {
|
|
return circuitCategory === "three_phase" ||
|
|
circuit.deviceRows.some((row) => row.phaseType === "three_phase")
|
|
? 3
|
|
: 1;
|
|
}
|
|
|
|
/**
|
|
* Power-weighted cos φ of the circuit: P / S with S = Σ P_row / cos φ_row.
|
|
* Rows without cos φ count as 1; a circuit without load yields 1.
|
|
*/
|
|
export function deriveCircuitCosPhi(circuit: CircuitTreeCircuitDto): number {
|
|
let activePowerKw = 0;
|
|
let apparentPowerKva = 0;
|
|
for (const row of circuit.deviceRows) {
|
|
activePowerKw += row.rowTotalPower;
|
|
apparentPowerKva += row.rowTotalPower / (row.cosPhi || 1);
|
|
}
|
|
if (activePowerKw <= 0 || apparentPowerKva <= 0) {
|
|
return 1;
|
|
}
|
|
return Math.round((activePowerKw / apparentPowerKva) * 100) / 100;
|
|
}
|
|
|
|
/**
|
|
* Maps the circuit's protective device to the overload-protection kind of the
|
|
* calculation. Circuit-breakers (LS, FI/LS, AFDD) are "breaker", D0 and NH
|
|
* fuse links "fuse"; a residual-current device without overcurrent release
|
|
* (FI) provides no overload protection and is ignored.
|
|
*/
|
|
export function toExistingProtection(
|
|
protection: CircuitTreeProtectionDeviceDto | undefined
|
|
): ExistingProtection | undefined {
|
|
if (!protection || protection.type === "FI") {
|
|
return undefined;
|
|
}
|
|
const kind =
|
|
protection.type === "LS" || protection.type === "FI_LS" || protection.type === "AFDD"
|
|
? "breaker"
|
|
: "fuse";
|
|
return { ratedCurrentA: protection.ratedCurrentA, kind };
|
|
}
|