Align cable sizing with DIN VDE 0298-4

This commit is contained in:
Julian Appel 2026-09-27 17:01:23 +02:00
parent 598003554a
commit 5cbbd3a84a
9 changed files with 262 additions and 244 deletions

View file

@ -607,10 +607,13 @@ circuit list (see `docs/cable-sizing-module.md`). Rules:
by clicking a grid cell; cable cells keep the normal spreadsheet behavior. by clicking a grid cell; cable cells keep the normal spreadsheet behavior.
- It has no database table and no migration; `/api/cable-sizing/*` is - It has no database table and no migration; `/api/cable-sizing/*` is
stateless. stateless.
- Reference data is IEC 60364-5-52:2009 Annex B. Change table values only with - Current-carrying capacities and correction factors use
a documented source in `docs/cable-sizing-module.md`; where sources differ, DIN VDE 0298-4:2023-06 with Berichtigung 1:2023-10; the exact scope and the
use the lower value. Inputs outside the tables return no result instead of separate basis of voltage-drop and protection checks are documented in
assuming a factor of 1. `docs/cable-sizing-module.md`. Inputs outside the supported tables return no
result instead of assuming a factor of 1. Direct burial, grouped method E,
aluminium below 25 mm² and neutral-current sizing above 33 % third harmonic
remain unsupported until their required inputs or normative basis exist.
- Inputs are prefilled from the circuit (power, phase, voltage, weighted cos φ, - Inputs are prefilled from the circuit (power, phase, voltage, weighted cos φ,
length, protective device) and remain editable; manual cable entry without length, protective device) and remain editable; manual cable entry without
a calculation stays possible. a calculation stays possible.

View file

@ -1,159 +1,109 @@
# Leitungsberechnung (Cable-Sizing-Modul) # Leitungsberechnung
Optionales, abgegrenztes Rechenmodul für die Querschnittsempfehlung eines Das optionale Modul gibt für einen einzelnen Stromkreis eine prüfbare
Stromkreises. Es wurde als eigenständiges Modul vereinbart, das an die Querschnittsempfehlung aus. Es schreibt selbst keine Projektdaten. Erst die
Stromkreisliste angedockt ist. Es **schreibt keine Projektdaten selbst**: Ein ausdrückliche Aktion **Übernehmen** aktualisiert `cableCrossSection`,
Ergebnis wird erst durch ausdrückliches „Übernehmen“ über den bestehenden `cableType` und `cableLength` über den vorhandenen Befehl `circuit.update`.
Befehl `circuit.update` in den Stromkreis geschrieben. Revisionen und Revisionen sowie Undo/Redo bleiben dadurch erhalten.
Undo/Redo bleiben damit unverändert gültig.
Die vollständige, automatische Bemessung aller Stromkreise (siehe Eine automatische Bemessung aller Stromkreise ist weiterhin nicht Bestandteil
`docs/spec/06-future-sizing-and-calculations.md`) ist weiterhin **nicht** des Moduls.
Teil dieses Moduls.
## Aufbau und Andockpunkte ## Aufbau
``` ```text
src/cable-sizing/domain/cable-sizing-calculation.ts reine Berechnung + Tabellen src/cable-sizing/domain/cable-sizing-calculation.ts Berechnung und Tabellen
src/cable-sizing/domain/cable-sizing-contracts.ts Zod-Schema der API src/cable-sizing/domain/cable-sizing-contracts.ts API-Validierung
src/server/controllers/cable-sizing.controller.ts zustandslose Endpunkte src/server/controllers/cable-sizing.controller.ts zustandslose Endpunkte
src/server/routes/cable-sizing.routes.ts Router unter /api/cable-sizing src/server/routes/cable-sizing.routes.ts Router
src/frontend/components/cable-sizing-api.ts Fetch-Client src/frontend/components/cable-sizing-api.ts Fetch-Client
src/frontend/components/cable-sizing-defaults.ts Vorbelegung aus dem Stromkreis src/frontend/components/cable-sizing-defaults.ts Vorbelegung
src/frontend/components/cable-sizing-modal.tsx Dialog src/frontend/components/cable-sizing-modal.tsx Dialog
tests/cable-sizing-calculation.test.ts Tabellen, Rechnung, Vorbelegung tests/cable-sizing-calculation.test.ts Tabellen und Grenzfälle
``` ```
Das Domänenmodul importiert nichts aus `db/`, `server/` oder `frontend/`. Das Domänenmodul hängt weder von Datenbank, Server noch React ab. Die API ist
Außerhalb dieser Dateien gibt es genau zwei Andockpunkte: zustandslos. Im Stromkreis-Editor öffnet ein Rechner-Symbol den Dialog; die
Kabelzellen behalten ihr übliches Spreadsheet-Verhalten.
1. `src/server/index.ts`: `app.use("/api/cable-sizing", cableSizingRouter)`. ## Eingaben
2. `src/frontend/components/circuit-tree-editor.tsx`: Zustand für den
geöffneten Dialog, ein Button **Leitungsberechnung** (Rechner-Icon) in der
Aktionsspalte jeder Stromkreiszeile, der Dialog selbst und
`handleApplyCableSizing`, das über `updateCircuitById` den regulären
`circuit.update`-Befehl ausführt.
Der Aufruf liegt bewusst **nicht** auf einer Tabellenzelle: Klick, Doppelklick, Aus dem Stromkreis werden Leistung, Phasen, Spannung, leistungsgewichtetes
Enter, F2 und Tippen in den Kabelspalten verhalten sich wie in jeder anderen cos φ, Leitungslänge und ein vorhandenes Überstromschutzorgan vorbelegt. Der
Zelle (Spreadsheet-Regel in `AGENTS.md`). Es gibt keine eigene Datenbanktabelle Planer wählt Verlegeart, Isolierstoff, Leitermaterial, Temperatur, Häufung,
und keine Migration. zulässigen Spannungsfall und bei Drehstrom den Anteil der dritten Harmonischen.
## Eingaben und Vorbelegung Eine Leitungslänge von `0 m` ist zulässig. Der Dialog weist sichtbar darauf
hin, dass dann kein Spannungsfall angesetzt wird.
| Eingabe | Vorbelegung | ## Normative Grundlage und Grenzen
|---|---|
| Leistung | `circuitTotalPower` des Stromkreises (mit Geräte-GZF), nicht editierbar |
| Phasen | 3~, wenn die Stromkreisgruppe 3-phasig ist oder eine Gerätezeile `three_phase` hat, sonst 1~ |
| Spannung | `circuit.voltage` (aus den Projekteinstellungen abgeleitet); bei manuell abweichender Phasenzahl die jeweilige Projektspannung |
| cos φ | leistungsgewichtet aus den Gerätezeilen: P / Σ(P_Zeile / cos φ_Zeile), fehlendes cos φ = 1; änderbar |
| Länge | `cableLength` des Stromkreises |
| Schutzorgan | Schutzgerät des Stromkreises: LS, FI/LS, AFDD = Leitungsschutzschalter; D0/NH = Sicherung gG; FI ohne Überstromauslöser wird nicht berücksichtigt |
| Verlegeart, Isolierstoff, Leitermaterial, Temperatur, Häufung, max. ΔU, Oberschwingungen | Planereingaben |
Kabeltyp und Querschnitt lassen sich im Dialog auch ohne Berechnung direkt Die Strombelastbarkeiten und Korrekturfaktoren wurden gegen
eintragen. Weicht der Eintrag von der Empfehlung oder dem **DIN VDE 0298-4 (VDE 0298-4):2023-06** einschließlich
Praxis-Mindestquerschnitt ab, erscheint nur ein Hinweis. **Berichtigung 1:2023-10** geprüft:
- Strombelastbarkeit: Tabellen 3 bis 6.
- Temperaturfaktoren: Tabellen 17 und 20.
- Häufung A1 bis C: Tabelle 22 nach der korrigierten Zuordnung.
- Häufung D: Tabelle 26 für sich berührende Rohre oder Kanäle im Erdreich.
- Oberschwingungen: Anhang B, insbesondere Tabelle B.1.
Unterstützt werden A1, A2, B1, B2, C, D und E. D bezeichnet ausschließlich
mehradrige Kabel oder Mantelleitungen im Rohr beziehungsweise Kanal im
Erdreich. Direkte Erdverlegung wird nicht angeboten, weil DIN VDE 0298-4 dafür
auf die Kabel-Produktnorm DIN VDE 0276-603 verweist.
Für Verlegeart E hängen Häufungsfaktoren von Trägerart, Abstand und Anzahl der
Kabelpritschen ab. Diese Angaben gehören noch nicht zum Eingabemodell. Daher
ist E nur für einen einzelnen Stromkreis berechenbar. Für D sind höchstens
sechs gehäufte Stromkreise durch die verwendete Tabellenzeile abgedeckt.
Aluminium wird erst ab 25 mm² angeboten, weil die verwendeten Tabellen keine
kleineren Aluminiumquerschnitte enthalten. Bei mehr als 33 % dritter
Harmonischer ist der Neutralleiterstrom Bemessungsgrundlage. Solange dieser
nicht als Zahlenwert eingegeben und berechnet wird, liefert das Modul keine
Empfehlung.
Die Berechnung des Spannungsfalls ist eine vereinfachte Ergänzung mit
Leitfähigkeit bei Betriebstemperatur; Reaktanz wird vernachlässigt. Ihre
Planungsgrenzen folgen nicht allein aus DIN VDE 0298-4. Auch die Koordination
mit Leitungsschutzschaltern und gG-Sicherungen gehört zur
Überstromschutzbemessung und ist getrennt von der Strombelastbarkeit zu
beurteilen. Der Dialog bezeichnet diese Ergebnisse deshalb nicht als vollständig
normative Leitungsbemessung.
## Rechenweg ## Rechenweg
1. Betriebsstrom I_B aus Leistung, Spannung, cos φ und Phasenzahl. 1. Betriebsstrom `I_B` aus Leistung, Spannung, cos φ und Phasenzahl.
2. Erforderliche Belastbarkeit: ohne Schutzorgan I_B; mit Schutzorgan 2. Erforderliche Belastbarkeit aus `I_B` und gegebenenfalls dem vorhandenen
max(I_B, I_n, I₂/1,45) nach IEC 60364-4-43 (I_n ≤ I_z und I₂ ≤ 1,45 · I_z). Schutzorgan.
I₂/I_n: Leitungsschutzschalter 1,45; Sicherung gG 2,1 (I_n ≤ 4 A), 3. Tabellenwert `I_z` multipliziert mit Temperatur-, Häufungs- und gegebenenfalls
1,9 (4 A < I_n < 16 A), 1,6 (I_n ≥ 16 A). Oberschwingungsfaktor.
3. Tabellenwert I_z je Querschnitt × Temperaturfaktor × Häufungsfaktor 4. Vereinfachter Spannungsfall für jeden verfügbaren Querschnitt.
(× 0,86 bei 15–33 % 3. Harmonischer im Drehstromkreis). 5. Empfehlung als größerer Querschnitt aus Belastbarkeit und Spannungsfall.
4. Spannungsfall ΔU = k · L · I_B · cos φ / (κ · A · U) mit k = 2 (1~) bzw. 6. Bei einphasigen Stromkreisen Anhebung auf den vereinbarten praktischen
√3 (3~), κ = 48 (Cu) bzw. 30 (Al) m/(Ω·mm²) bei Betriebstemperatur; Mindestquerschnitt von 2,5 mm².
Reaktanz wird vernachlässigt.
5. Empfehlung = größerer Querschnitt aus Belastbarkeit und Spannungsfall;
1-phasige Stromkreise werden auf den Praxis-Mindestquerschnitt 2,5 mm²
angehoben (Planungskonvention aus `docs/spec/06-…`, keine Rechenanforderung).
6. Zusätzlich: maximale Länge für den zulässigen Spannungsfall.
Temperaturen und Häufungen **zwischen** Tabellenwerten werden auf den nächsten Temperaturen und Häufungen zwischen Tabellenwerten werden zum nächsten
ungünstigeren Tabellenwert gerundet (höhere Temperatur, mehr Stromkreise). ungünstigeren Tabellenwert gerundet. Außerhalb des belegten Tabellenbereichs
Werte **außerhalb** der Tabellen liefern kein Ergebnis und eine Meldung – es wird keine Empfehlung ausgegeben. Ein abweichender Querschnitt kann weiterhin
wird nie stillschweigend mit Faktor 1 gerechnet. manuell eingetragen und bewusst übernommen werden.
## Datenbasis und Prüfnachweis
Grundlage ist **IEC 60364-5-52:2009, Anhang B**. Alle Werte wurden am
26.09.2026 zeilenweise mit öffentlich zugänglichen Wiedergaben abgeglichen:
TiSoft ElectricalDesign-Hilfe zu IEC 60364-5-52
(`ti-soft.com/en/support/help/electricaldesign/standards/iec-60364-5-52/…`)
und ECalPro (`ecalpro.com/standards/iec-60364/…`). Wo die Quellen voneinander
abweichen, ist der kleinere, sichere Wert eingetragen.
| Tabelle | Inhalt | Abgleich | Abweichungen |
|---|---|---|---|
| B.52.2 | PVC, 2 belastete Adern, Cu/Al, A1–D2 | TiSoft; Cu A1–C zusätzlich ECalPro | Cu A1 240 mm²: 321 / 320 → 320 |
| B.52.3 | VPE, 2 belastete Adern, Cu/Al, A1–D2 | TiSoft | – |
| B.52.4 | PVC, 3 belastete Adern, Cu/Al, A1–D2 | TiSoft | – |
| B.52.5 | VPE, 3 belastete Adern, Cu/Al, A1–D2 | TiSoft | – |
| B.52.10 / .11 | PVC, Verlegeart E, mehradrig, Cu / Al | TiSoft; Cu 2 Adern zusätzlich ECalPro | Cu 3 Adern 70 / 120 mm²: 198 / 278 bzw. 196 / 276 → 196 / 276 |
| B.52.12 / .13 | VPE, Verlegeart E, mehradrig, Cu / Al | TiSoft | – |
| B.52.14 | Temperatur Luft, PVC / VPE | TiSoft, ECalPro | – |
| B.52.15 | Temperatur Erde, PVC / VPE | TiSoft | – |
| B.52.17 | Häufung Luft, Zeile 1 (gebündelt) | TiSoft, ECalPro | – |
| B.52.18 | Häufung Erde direkt, Kabel berührend | TiSoft | – |
| B.52.19 A | Häufung mehradrige Kabel in berührenden Rohren im Erdreich | TiSoft | – |
Anwendung der Tabellen:
- Verlegearten A1, A2, B1, B2, C, E in Luft (Bezug 30 °C), D1 und D2 im
Erdreich (Bezug 20 °C, Bodenwärmewiderstand 2,5 K·m/W). Nach Tabelle B.52.1
ist **D1 = Leitung im Rohr/Kabelschutzrohr im Erdreich** und **D2 = Kabel
direkt im Erdreich**.
- Häufung in Luft immer nach B.52.17 Zeile 1, auch für Verlegeart E; das ist
die ungünstigste Anordnung (einlagig auf Pritschen wären höhere Faktoren
zulässig). D1 nach B.52.19 A, D2 nach B.52.18 jeweils „berührend“.
- Aluminium nur mit den echten Aluminiumwerten (ab 2,5 mm², D2 ab 16 mm²);
Querschnitte ohne Tabellenwert werden nie empfohlen.
- Die Verlegearten F und G (Einzeladern mit Abstand) sind nicht enthalten.
**Vor produktivem Einsatz:** Die nationalen Tabellen der DIN VDE 0298-4
können in einzelnen Werten abweichen. Der Planer muss die Tabellen gegen den
Normtext prüfen. Die Prüfung ist in `tests/cable-sizing-calculation.test.ts`
über Stichwerte und eine Monotonieprüfung jeder Tabellenzeile abgesichert.
## Nicht enthalten ## Nicht enthalten
- Kurzschlussfestigkeit, Abschaltbedingungen (Schleifenimpedanz) und - direkte Erdverlegung
Selektivität - detaillierte Anordnung mehrerer Kabel bei Verlegeart E
- Bodenwärmewiderstand ≠ 2,5 K·m/W, Häufung mit Abstand, Verlegearten F/G - Neutralleiterbemessung bei mehr als 33 % dritter Harmonischer
- Bemessung des Neutralleiters bei > 33 % 3. Harmonischer (nur Hinweis) - Kurzschlussfestigkeit, Schleifenimpedanz, Abschaltbedingungen und Selektivität
- automatische Bemessung oder Prüfung aller Stromkreise - abweichender Bodenwärmewiderstand sowie Verlegearten F und G
- automatische Berechnung oder Änderung mehrerer Stromkreise
## API ## API
`POST /api/cable-sizing/calculate` – zustandslos, liest und schreibt keine `POST /api/cable-sizing/calculate` berechnet zustandslos. Die Verlegeart ist
Projektdaten. `A1 | A2 | B1 | B2 | C | D | E`; `lengthM` darf `0` sein. Das Ergebnis enthält
`supported: false` und einen der Gründe `temperature`, `grouping` oder
`harmonics`, wenn keine belastbare Empfehlung möglich ist.
```jsonc `GET /api/cable-sizing/laying-methods` und
{ `GET /api/cable-sizing/insulation-materials` liefern die Auswahllisten.
"phase": 1, // 1 | 3
"mode": "power", // "power" | "current"
"powerKw": 4.2, // bei mode "power"
"currentA": 18.3, // bei mode "current"
"cosPhi": 0.95, // 0,1 … 1
"voltage": 230,
"lengthM": 23.5,
"layingMethod": "C", // A1 | A2 | B1 | B2 | C | D1 | D2 | E
"conductorMaterial": "copper", // copper | aluminum
"insulation": "pvc", // pvc | xlpe
"ambientTemperatureC": 30, // Luft; bei D1/D2 Bodentemperatur
"groupingCircuits": 1, // 1 … 100, Tabellen bis 20
"maxVoltageDropPercent": 3,
"harmonicNeutralLoad": "none", // none | 15to33Percent | over33Percent
"existingProtection": { "ratedCurrentA": 16, "kind": "breaker" }, // optional; kind: breaker | fuse
"circuitCategory": "single_phase" // optional
}
```
Antwort: `{ "result": CableSizingResult, "alerts": CableSizingAlert[] }`.
`result.supported = false` mit `unsupportedReason` (`temperature` oder
`grouping`), wenn die Eingaben außerhalb der Tabellen liegen.
`GET /api/cable-sizing/laying-methods` und `GET /api/cable-sizing/insulation-materials`
liefern die Auswahllisten.

View file

@ -433,14 +433,15 @@ read nor write project data; applying a result uses `circuit.update` through
- `POST /cable-sizing/calculate` - `POST /cable-sizing/calculate`
- request: phase, mode (`power` | `current`), powerKw or currentA, cosPhi, - request: phase, mode (`power` | `current`), powerKw or currentA, cosPhi,
voltage, lengthM, layingMethod (`A1` | `A2` | `B1` | `B2` | `C` | `D1` | voltage, lengthM, layingMethod (`A1` | `A2` | `B1` | `B2` | `C` | `D` |
`D2` | `E`), conductorMaterial, insulation (`pvc` | `xlpe`), `E`), conductorMaterial, insulation (`pvc` | `xlpe`),
ambientTemperatureC, groupingCircuits, maxVoltageDropPercent, ambientTemperatureC, groupingCircuits, maxVoltageDropPercent,
harmonicNeutralLoad, optional `existingProtection` harmonicNeutralLoad, optional `existingProtection`
(`{ ratedCurrentA, kind: "breaker" | "fuse" }`) and circuitCategory (`{ ratedCurrentA, kind: "breaker" | "fuse" }`) and circuitCategory
- returns `{ "result": { ... }, "alerts": [ ... ] }`; `result.supported` - returns `{ "result": { ... }, "alerts": [ ... ] }`; `result.supported`
is `false` with `unsupportedReason` when temperature or grouping lie is `false` with `unsupportedReason` when temperature or grouping lie
outside the tables outside the tables or the neutral current needed above 33 % third
harmonic is unavailable
- unknown fields are rejected (`400`) - unknown fields are rejected (`400`)
- `GET /cable-sizing/laying-methods` - `GET /cable-sizing/laying-methods`
- `GET /cable-sizing/insulation-materials` - `GET /cable-sizing/insulation-materials`

View file

@ -20,11 +20,11 @@
- The global device library supports basic CRUD and copy operations, but has no - The global device library supports basic CRUD and copy operations, but has no
versioning, permissions or controlled synchronization model. versioning, permissions or controlled synchronization model.
- There is no automatic sizing of all circuits. The optional cable sizing - There is no automatic sizing of all circuits. The optional cable sizing
module suggests a cross-section for one circuit on demand (IEC 60364-5-52 module suggests a cross-section for one circuit on demand using
Annex B tables; no short-circuit, disconnection-time or selectivity checks, DIN VDE 0298-4:2023-06 with Berichtigung 1:2023-10. It excludes direct
no methods F/G, soil resistivity fixed at 2.5 K·m/W) and does not validate burial, grouped method E and neutral-current sizing above 33 % third
manually entered cable values. National tables (DIN VDE 0298-4) must be harmonic, and it does not check short-circuit withstand, disconnection time
checked by the planner; see `docs/cable-sizing-module.md`. or selectivity. See `docs/cable-sizing-module.md`.
- Sorting is view-only until users explicitly apply sorted order. - Sorting is view-only until users explicitly apply sorted order.
- Complete-circuit drag supports one circuit at a time between groups of the - Complete-circuit drag supports one circuit at a time between groups of the
same category. Cross-category moves and multi-circuit moves across group same category. Cross-category moves and multi-circuit moves across group

View file

@ -507,8 +507,10 @@ Kopieren in ein Projekt erzeugt ein eigenständiges Projektgerät.
## Leitungsberechnung ## Leitungsberechnung
Das optionale Modul `src/cable-sizing` berechnet auf Anforderung eine Das optionale Modul `src/cable-sizing` berechnet auf Anforderung eine
Querschnittsempfehlung für einen Stromkreis (IEC 60364-5-52 Anhang B: Querschnittsempfehlung für einen Stromkreis. Strombelastbarkeit, Temperatur
Belastbarkeit, Temperatur, Häufung, Spannungsfall, Schutzorgan). Die und Häufung beruhen auf DIN VDE 0298-4:2023-06 einschließlich
Berichtigung 1:2023-10; Spannungsfall und Schutzorgan werden ergänzend und mit
den in der Moduldokumentation beschriebenen Grenzen geprüft. Die
Domänenlogik ist rein, `POST /api/cable-sizing/calculate` ist zustandslos und Domänenlogik ist rein, `POST /api/cable-sizing/calculate` ist zustandslos und
es gibt keine eigene Tabelle. Der Editor öffnet den Dialog über den Button es gibt keine eigene Tabelle. Der Editor öffnet den Dialog über den Button
„Leitungsberechnung“ in der Aktionsspalte; „Übernehmen“ schreibt Kabeltyp, „Leitungsberechnung“ in der Aktionsspalte; „Übernehmen“ schreibt Kabeltyp,

View file

@ -47,8 +47,9 @@ requirements and intended sequencing, not proof of implementation.
- [x] Phase F: documentation and full GUI verification. - [x] Phase F: documentation and full GUI verification.
- [ ] Keep full electrical sizing and cable-dimensioning rules separate until - [ ] Keep full electrical sizing and cable-dimensioning rules separate until
the planner supplies the calculation requirements. The optional per-circuit the planner supplies the calculation requirements. The optional per-circuit
cable sizing module (`docs/cable-sizing-module.md`) exists; its tables must cable sizing module (`docs/cable-sizing-module.md`) exists; its supported
be confirmed by the planner against DIN VDE 0298-4. tables were confirmed against DIN VDE 0298-4:2023-06 with
Berichtigung 1:2023-10. Unsupported arrangements stay explicitly blocked.
- Detailed specification: - Detailed specification:
`docs/spec/09-distribution-board-components-and-protection-groups.md` `docs/spec/09-distribution-board-components-and-protection-groups.md`

View file

@ -2,16 +2,14 @@
// the module is an optional calculator attached to the circuit list and never // the module is an optional calculator attached to the circuit list and never
// writes project data itself (see docs/cable-sizing-module.md). // writes project data itself (see docs/cable-sizing-module.md).
// //
// Reference data: IEC 60364-5-52:2009, Annex B (tables B.52.2 to B.52.5, // Reference data: DIN VDE 0298-4 (VDE 0298-4):2023-06 together with
// B.52.10 to B.52.19). Every value was cross-checked against publicly // Berichtigung 1:2023-10. The table-by-table scope and the separate sources
// available reproductions of these tables; where sources disagreed the lower, // required for voltage drop and protection coordination are documented in
// i.e. safer, value is used. The table-by-table verification record is in // docs/cable-sizing-module.md.
// docs/cable-sizing-module.md. National tables (DIN VDE 0298-4) may differ in
// individual values and must be checked by the planner against the norm text.
// -- Reference installation methods (IEC 60364-5-52, Table B.52.1) ---------- // -- Reference installation methods (DIN VDE 0298-4, Table 2) ---------------
export const LAYING_METHODS = ["A1", "A2", "B1", "B2", "C", "D1", "D2", "E"] as const; export const LAYING_METHODS = ["A1", "A2", "B1", "B2", "C", "D", "E"] as const;
export type LayingMethod = (typeof LAYING_METHODS)[number]; export type LayingMethod = (typeof LAYING_METHODS)[number];
export const LAYING_METHOD_GROUP: Record<LayingMethod, "air" | "ground"> = { export const LAYING_METHOD_GROUP: Record<LayingMethod, "air" | "ground"> = {
@ -21,8 +19,7 @@ export const LAYING_METHOD_GROUP: Record<LayingMethod, "air" | "ground"> = {
B2: "air", B2: "air",
C: "air", C: "air",
E: "air", E: "air",
D1: "ground", D: "ground",
D2: "ground",
}; };
export const LAYING_METHOD_LABELS: Record<LayingMethod, string> = { export const LAYING_METHOD_LABELS: Record<LayingMethod, string> = {
@ -31,8 +28,7 @@ export const LAYING_METHOD_LABELS: Record<LayingMethod, string> = {
B1: "B1 – Einzeladern im Rohr auf oder in der Wand", B1: "B1 – Einzeladern im Rohr auf oder in der Wand",
B2: "B2 – Mehradrige Leitung im Rohr auf oder in der Wand", B2: "B2 – Mehradrige Leitung im Rohr auf oder in der Wand",
C: "C – Mehradrige Leitung direkt auf oder in der Wand", C: "C – Mehradrige Leitung direkt auf oder in der Wand",
D1: "D1 – Leitung im Rohr oder Kabelschutzrohr im Erdreich", D: "D – Mehradriges Kabel im Rohr oder Kanal im Erdreich",
D2: "D2 – Kabel direkt im Erdreich",
E: "E – Mehradrige Leitung frei in Luft", E: "E – Mehradrige Leitung frei in Luft",
}; };
@ -49,7 +45,7 @@ export const INSULATION_MATERIAL_LABELS: Record<InsulationMaterial, string> = {
export const CONDUCTOR_MATERIALS = ["copper", "aluminum"] as const; export const CONDUCTOR_MATERIALS = ["copper", "aluminum"] as const;
export type ConductorMaterial = (typeof CONDUCTOR_MATERIALS)[number]; export type ConductorMaterial = (typeof CONDUCTOR_MATERIALS)[number];
// -- Harmonic neutral loading (IEC 60364-5-52, Annex E, three-phase only) --- // -- Harmonic neutral loading (DIN VDE 0298-4, Annex B, three-phase only) ---
export const HARMONIC_NEUTRAL_LOAD_OPTIONS = [ export const HARMONIC_NEUTRAL_LOAD_OPTIONS = [
"none", "none",
@ -61,7 +57,7 @@ export type HarmonicNeutralLoad = (typeof HARMONIC_NEUTRAL_LOAD_OPTIONS)[number]
export const HARMONIC_NEUTRAL_LOAD_LABELS: Record<HarmonicNeutralLoad, string> = { export const HARMONIC_NEUTRAL_LOAD_LABELS: Record<HarmonicNeutralLoad, string> = {
none: "Keine nennenswerte 3. Harmonische (< 15 %) – Standardfall", none: "Keine nennenswerte 3. Harmonische (< 15 %) – Standardfall",
"15to33Percent": "3. Harmonische 15–33 % – Reduktionsfaktor 0,86", "15to33Percent": "3. Harmonische 15–33 % – Reduktionsfaktor 0,86",
over33Percent: "3. Harmonische > 33 % – Neutralleiter wie Außenleiter dimensionieren", over33Percent: "3. Harmonische > 33 % – ohne Neutralleiterstrom keine Empfehlung",
}; };
export const HARMONIC_REDUCTION_FACTOR = 0.86; export const HARMONIC_REDUCTION_FACTOR = 0.86;
@ -85,14 +81,13 @@ export const CURRENT_CAPACITY_A: Record<
> = { > = {
pvc: { pvc: {
copper: { copper: {
// B.52.2 / B.52.4; E: B.52.10 multi-core. A1 2L 240 mm²: sources 320/321 → 320. // DIN VDE 0298-4, tables 3 and 4.
// E 3L 70/120 mm²: sources 196/198 and 276/278 → 196 and 276.
A1: { A1: {
2: [14.5, 19.5, 26, 34, 46, 61, 80, 99, 119, 151, 182, 210, 240, 273, 320], 2: [15.5, 19.5, 26, 34, 46, 61, 80, 99, 119, 151, 182, 210, 240, 273, 321],
3: [13.5, 18, 24, 31, 42, 56, 73, 89, 108, 136, 164, 188, 216, 245, 286], 3: [13.5, 18, 24, 31, 42, 56, 73, 89, 108, 136, 164, 188, 216, 245, 286],
}, },
A2: { A2: {
2: [14, 18.5, 25, 32, 43, 57, 75, 92, 110, 139, 167, 192, 219, 248, 291], 2: [15.5, 18.5, 25, 32, 43, 57, 75, 92, 110, 139, 167, 192, 219, 248, 291],
3: [13, 17.5, 23, 29, 39, 52, 68, 83, 99, 125, 150, 172, 196, 223, 261], 3: [13, 17.5, 23, 29, 39, 52, 68, 83, 99, 125, 150, 172, 196, 223, 261],
}, },
B1: { B1: {
@ -107,22 +102,18 @@ export const CURRENT_CAPACITY_A: Record<
2: [19.5, 27, 36, 46, 63, 85, 112, 138, 168, 213, 258, 299, 344, 392, 461], 2: [19.5, 27, 36, 46, 63, 85, 112, 138, 168, 213, 258, 299, 344, 392, 461],
3: [17.5, 24, 32, 41, 57, 76, 96, 119, 144, 184, 223, 259, 299, 341, 403], 3: [17.5, 24, 32, 41, 57, 76, 96, 119, 144, 184, 223, 259, 299, 341, 403],
}, },
D1: { D: {
2: [22, 29, 37, 46, 60, 78, 99, 119, 140, 173, 204, 231, 261, 292, 336], 2: [22, 29, 37, 46, 60, 78, 99, 119, 140, 173, 204, 231, 261, 292, 336],
3: [18, 24, 30, 38, 50, 64, 82, 98, 116, 143, 169, 192, 217, 243, 280], 3: [18, 24, 30, 38, 50, 64, 82, 98, 116, 143, 169, 192, 217, 243, 280],
}, },
D2: {
2: [22, 28, 38, 48, 64, 83, 110, 132, 156, 192, 230, 261, 293, 331, 382],
3: [19, 24, 33, 41, 54, 70, 92, 110, 130, 162, 193, 220, 246, 278, 320],
},
E: { E: {
2: [22, 30, 40, 51, 70, 94, 119, 148, 180, 232, 282, 328, 379, 434, 514], 2: [22, 30, 40, 51, 70, 94, 119, 148, 180, 232, 282, 328, 379, 434, 514],
3: [18.5, 25, 34, 43, 60, 80, 101, 126, 153, 196, 238, 276, 319, 364, 430], 3: [18.5, 25, 34, 43, 60, 80, 101, 126, 153, 196, 238, 276, 319, 364, 430],
}, },
}, },
aluminum: { aluminum: {
// B.52.2 / B.52.4 aluminium; E: B.52.11. No values below 2.5 mm² // DIN VDE 0298-4 aluminium values; the normalization below removes all
// (D2 below 16 mm²). // entries below the first tabulated aluminium size of 25 mm².
A1: { A1: {
2: [n, 15, 20, 26, 36, 48, 63, 77, 93, 118, 142, 164, 189, 215, 252], 2: [n, 15, 20, 26, 36, 48, 63, 77, 93, 118, 142, 164, 189, 215, 252],
3: [n, 14, 18.5, 24, 32, 43, 57, 70, 84, 107, 129, 149, 170, 194, 227], 3: [n, 14, 18.5, 24, 32, 43, 57, 70, 84, 107, 129, 149, 170, 194, 227],
@ -143,14 +134,10 @@ export const CURRENT_CAPACITY_A: Record<
2: [n, 21, 28, 36, 49, 66, 83, 103, 125, 160, 195, 226, 261, 298, 352], 2: [n, 21, 28, 36, 49, 66, 83, 103, 125, 160, 195, 226, 261, 298, 352],
3: [n, 18.5, 25, 32, 44, 59, 73, 90, 110, 140, 170, 197, 227, 259, 305], 3: [n, 18.5, 25, 32, 44, 59, 73, 90, 110, 140, 170, 197, 227, 259, 305],
}, },
D1: { D: {
2: [n, 22, 29, 36, 47, 61, 77, 93, 109, 135, 159, 180, 204, 228, 262], 2: [n, 22, 29, 36, 47, 61, 77, 93, 109, 135, 159, 180, 204, 228, 262],
3: [n, 18.5, 24, 30, 39, 50, 64, 77, 91, 112, 132, 150, 169, 190, 218], 3: [n, 18.5, 24, 30, 39, 50, 64, 77, 91, 112, 132, 150, 169, 190, 218],
}, },
D2: {
2: [n, n, n, n, n, 63, 82, 98, 117, 145, 173, 200, 224, 255, 298],
3: [n, n, n, n, n, 53, 69, 83, 99, 122, 148, 169, 189, 214, 250],
},
E: { E: {
2: [n, 23, 31, 39, 54, 73, 89, 111, 135, 173, 210, 244, 282, 322, 380], 2: [n, 23, 31, 39, 54, 73, 89, 111, 135, 173, 210, 244, 282, 322, 380],
3: [n, 19.5, 26, 33, 46, 61, 78, 96, 117, 150, 183, 212, 245, 280, 330], 3: [n, 19.5, 26, 33, 46, 61, 78, 96, 117, 150, 183, 212, 245, 280, 330],
@ -180,14 +167,10 @@ export const CURRENT_CAPACITY_A: Record<
2: [24, 33, 45, 58, 80, 107, 138, 171, 209, 269, 328, 382, 441, 506, 599], 2: [24, 33, 45, 58, 80, 107, 138, 171, 209, 269, 328, 382, 441, 506, 599],
3: [22, 30, 40, 52, 71, 96, 119, 147, 179, 229, 278, 322, 371, 424, 500], 3: [22, 30, 40, 52, 71, 96, 119, 147, 179, 229, 278, 322, 371, 424, 500],
}, },
D1: { D: {
2: [25, 33, 43, 53, 71, 91, 116, 139, 164, 203, 239, 271, 306, 343, 395], 2: [25, 33, 43, 53, 71, 91, 116, 139, 164, 203, 239, 271, 306, 343, 395],
3: [21, 28, 36, 44, 58, 75, 96, 115, 135, 167, 197, 223, 251, 281, 324], 3: [21, 28, 36, 44, 58, 75, 96, 115, 135, 167, 197, 223, 251, 281, 324],
}, },
D2: {
2: [27, 35, 46, 58, 77, 100, 129, 155, 183, 225, 270, 306, 343, 387, 448],
3: [23, 30, 39, 49, 65, 84, 107, 129, 153, 188, 226, 257, 287, 324, 375],
},
E: { E: {
2: [26, 36, 49, 63, 86, 115, 149, 185, 225, 289, 352, 410, 473, 542, 641], 2: [26, 36, 49, 63, 86, 115, 149, 185, 225, 289, 352, 410, 473, 542, 641],
3: [23, 32, 42, 54, 75, 100, 127, 158, 192, 246, 298, 346, 399, 456, 538], 3: [23, 32, 42, 54, 75, 100, 127, 158, 192, 246, 298, 346, 399, 456, 538],
@ -215,14 +198,10 @@ export const CURRENT_CAPACITY_A: Record<
2: [n, 26, 35, 45, 62, 84, 101, 126, 154, 198, 241, 280, 324, 371, 439], 2: [n, 26, 35, 45, 62, 84, 101, 126, 154, 198, 241, 280, 324, 371, 439],
3: [n, 24, 32, 41, 57, 76, 90, 112, 136, 174, 211, 245, 283, 323, 382], 3: [n, 24, 32, 41, 57, 76, 90, 112, 136, 174, 211, 245, 283, 323, 382],
}, },
D1: { D: {
2: [n, 26, 33, 42, 55, 71, 90, 108, 128, 158, 186, 211, 238, 267, 307], 2: [n, 26, 33, 42, 55, 71, 90, 108, 128, 158, 186, 211, 238, 267, 307],
3: [n, 22, 28, 35, 46, 59, 75, 90, 106, 130, 154, 174, 197, 220, 253], 3: [n, 22, 28, 35, 46, 59, 75, 90, 106, 130, 154, 174, 197, 220, 253],
}, },
D2: {
2: [n, n, n, n, n, 76, 98, 117, 139, 170, 204, 233, 261, 296, 343],
3: [n, n, n, n, n, 64, 82, 98, 117, 144, 172, 197, 220, 250, 290],
},
E: { E: {
2: [n, 28, 38, 49, 67, 91, 108, 135, 164, 211, 257, 300, 346, 397, 470], 2: [n, 28, 38, 49, 67, 91, 108, 135, 164, 211, 257, 300, 346, 397, 470],
3: [n, 24, 32, 42, 58, 77, 97, 120, 146, 187, 227, 263, 304, 347, 409], 3: [n, 24, 32, 42, 58, 77, 97, 120, 146, 187, 227, 263, 304, 347, 409],
@ -231,9 +210,25 @@ export const CURRENT_CAPACITY_A: Record<
}, },
}; };
// DIN VDE 0298-4 only supplies aluminium current-carrying capacities from
// 25 mm² onward. Smaller values from the former IEC data set must therefore
// never participate in a recommendation.
for (const insulation of INSULATION_MATERIALS) {
for (const method of LAYING_METHODS) {
for (const coreCount of [2, 3] as const) {
const row = CURRENT_CAPACITY_A[insulation].aluminum[method][coreCount] as (number | null)[];
CROSS_SECTIONS_MM2.forEach((crossSection, index) => {
if (crossSection < 25) {
row[index] = null;
}
});
}
}
}
// -- Correction factors ------------------------------------------------------ // -- Correction factors ------------------------------------------------------
/** B.52.14 (air, reference 30 °C) and B.52.15 (ground, reference 20 °C). */ /** DIN VDE 0298-4 tables 17 (air) and 20 (ground). */
export const TEMPERATURE_FACTORS: Record< export const TEMPERATURE_FACTORS: Record<
"air" | "ground", "air" | "ground",
Record<InsulationMaterial, Record<number, number>> Record<InsulationMaterial, Record<number, number>>
@ -260,26 +255,32 @@ export const REFERENCE_TEMPERATURE_C: Record<"air" | "ground", number> = {
}; };
/** /**
* Grouping reduction factors for touching circuits/cables: * Grouping reduction factors according to the corrected references in
* air (A1–C, E): B.52.17 item 1 "bunched in air, on a surface, embedded or * DIN VDE 0298-4 Berichtigung 1:2023-10:
* enclosed" – the most restrictive arrangement, also used for E (single * A1–C use table 22, row 1 (bundled/touching), D uses table 26 for touching
* layers on trays would allow higher factors); * conduits/ducts in earth. Method E needs details about support, spacing and
* D1: B.52.19 A (multi-core cables in touching ducts); * number of trays from table 23, which the current input model does not carry;
* D2: B.52.18 (cables direct in ground, touching). * it is therefore supported only for one circuit.
*/ */
export const GROUPING_FACTORS: Record<"air" | "D1" | "D2", Record<number, number>> = { export const GROUPING_FACTORS: Record<"A1_TO_C" | "D" | "E", Record<number, number>> = {
air: { 1: 1.0, 2: 0.8, 3: 0.7, 4: 0.65, 5: 0.6, 6: 0.57, 7: 0.54, 8: 0.52, 9: 0.5, 12: 0.45, 16: 0.41, 20: 0.38 }, A1_TO_C: {
D1: { 1: 1.0, 2: 0.8, 3: 0.7, 4: 0.65, 5: 0.6, 6: 0.57, 7: 0.54, 8: 0.52,
1: 1.0, 2: 0.85, 3: 0.75, 4: 0.7, 5: 0.65, 6: 0.6, 7: 0.57, 8: 0.54, 9: 0.52, 10: 0.49, 9: 0.5, 10: 0.48, 12: 0.45, 14: 0.43, 16: 0.41, 18: 0.39, 20: 0.38,
11: 0.47, 12: 0.45, 13: 0.44, 14: 0.42, 15: 0.41, 16: 0.39, 17: 0.38, 18: 0.37, 19: 0.35, 20: 0.34,
}, },
D2: { 1: 1.0, 2: 0.75, 3: 0.65, 4: 0.6, 5: 0.55, 6: 0.5, 7: 0.45, 8: 0.43, 9: 0.41, 12: 0.36, 16: 0.32, 20: 0.29 }, D: { 1: 1.0, 2: 0.85, 3: 0.75, 4: 0.7, 5: 0.65, 6: 0.6 },
E: { 1: 1.0 },
}; };
export const MAX_GROUPING_CIRCUITS = 20;
function groupingTableFor(method: LayingMethod) { function groupingTableFor(method: LayingMethod) {
return method === "D1" ? GROUPING_FACTORS.D1 : method === "D2" ? GROUPING_FACTORS.D2 : GROUPING_FACTORS.air; return method === "D"
? GROUPING_FACTORS.D
: method === "E"
? GROUPING_FACTORS.E
: GROUPING_FACTORS.A1_TO_C;
}
export function maxGroupingCircuits(method: LayingMethod): number {
return Math.max(...Object.keys(groupingTableFor(method)).map(Number));
} }
/** /**
@ -373,7 +374,7 @@ export interface CableSizingInput {
layingMethod: LayingMethod; layingMethod: LayingMethod;
conductorMaterial: ConductorMaterial; conductorMaterial: ConductorMaterial;
insulation: InsulationMaterial; insulation: InsulationMaterial;
/** Ambient air temperature, or ground temperature for D1/D2. */ /** Ambient air temperature, or ground temperature for D. */
ambientTemperatureC: number; ambientTemperatureC: number;
/** Number of grouped circuits or multi-core cables, including this one. */ /** Number of grouped circuits or multi-core cables, including this one. */
groupingCircuits: number; groupingCircuits: number;
@ -405,7 +406,7 @@ export interface ProtectionCoordinationResult {
coordinated: boolean; coordinated: boolean;
} }
export type UnsupportedReason = "temperature" | "grouping"; export type UnsupportedReason = "temperature" | "grouping" | "harmonics";
export interface CableSizingResult { export interface CableSizingResult {
/** false: the inputs are outside the tabulated range – no numeric result. */ /** false: the inputs are outside the tabulated range – no numeric result. */
@ -454,13 +455,23 @@ export function calculateCableSizing(input: CableSizingInput): CableSizingResult
input.ambientTemperatureC input.ambientTemperatureC
); );
const grouping = const grouping =
input.groupingCircuits <= MAX_GROUPING_CIRCUITS input.groupingCircuits <= maxGroupingCircuits(input.layingMethod)
? lookupConservative(groupingTableFor(input.layingMethod), Math.max(1, input.groupingCircuits)) ? lookupConservative(groupingTableFor(input.layingMethod), Math.max(1, input.groupingCircuits))
: null; : null;
const harmonicsSupported = !(
input.phase === 3 && input.harmonicNeutralLoad === "over33Percent"
);
const unsupportedReason: UnsupportedReason | null = !temperature
? "temperature"
: !grouping
? "grouping"
: !harmonicsSupported
? "harmonics"
: null;
const empty: CableSizingResult = { const empty: CableSizingResult = {
supported: false, supported: false,
unsupportedReason: !temperature ? "temperature" : "grouping", unsupportedReason,
operatingCurrentA: ib, operatingCurrentA: ib,
designCurrentA, designCurrentA,
temperatureUsedC: temperature?.key ?? null, temperatureUsedC: temperature?.key ?? null,
@ -477,7 +488,7 @@ export function calculateCableSizing(input: CableSizingInput): CableSizingResult
rows: [], rows: [],
protectionCoordination: null, protectionCoordination: null,
}; };
if (!temperature || !grouping) { if (unsupportedReason || !temperature || !grouping) {
return empty; return empty;
} }
@ -586,12 +597,17 @@ export function buildCableSizingAlerts(input: CableSizingInput, result: CableSiz
const group = LAYING_METHOD_GROUP[input.layingMethod]; const group = LAYING_METHOD_GROUP[input.layingMethod];
if (!result.supported) { if (!result.supported) {
const groupingMaximum = maxGroupingCircuits(input.layingMethod);
alerts.push({ alerts.push({
kind: "critical", kind: "critical",
text: text:
result.unsupportedReason === "temperature" result.unsupportedReason === "temperature"
? `${group === "air" ? "Umgebungstemperatur" : "Bodentemperatur"} ${input.ambientTemperatureC} °C liegt außerhalb der Tabelle für ${input.insulation === "pvc" ? "PVC" : "VPE"} – keine Berechnung möglich.` ? `${group === "air" ? "Umgebungstemperatur" : "Bodentemperatur"} ${input.ambientTemperatureC} °C liegt außerhalb der Tabelle für ${input.insulation === "pvc" ? "PVC" : "VPE"} – keine Berechnung möglich.`
: `Häufung mit ${input.groupingCircuits} Stromkreisen liegt außerhalb der Tabelle (max. ${MAX_GROUPING_CIRCUITS}) – keine Berechnung möglich.`, : result.unsupportedReason === "harmonics"
? "Bei mehr als 33 % dritter Harmonischer muss nach dem Neutralleiterstrom bemessen werden. Ohne diesen Wert wird keine Querschnittsempfehlung ausgegeben."
: input.layingMethod === "E"
? "Für Verlegeart E benötigt die Norm bei Häufung Angaben zu Trägerart, Abstand und Anzahl der Kabelpritschen. Ohne diese Angaben ist nur ein einzelner Stromkreis berechenbar."
: `Häufung mit ${input.groupingCircuits} Stromkreisen liegt für Verlegeart ${input.layingMethod} außerhalb der verwendeten Tabelle (max. ${groupingMaximum}) – keine Berechnung möglich.`,
}); });
return alerts; return alerts;
} }
@ -637,10 +653,10 @@ export function buildCableSizingAlerts(input: CableSizingInput, result: CableSiz
text: "Reduktionsfaktor 0,86 wegen 15–33 % 3. Harmonischer im Neutralleiter angewendet.", text: "Reduktionsfaktor 0,86 wegen 15–33 % 3. Harmonischer im Neutralleiter angewendet.",
}); });
} }
if (input.phase === 3 && input.harmonicNeutralLoad === "over33Percent") { if (input.lengthM === 0) {
alerts.push({ alerts.push({
kind: "warn", kind: "info",
text: "3. Harmonische > 33 %: Der Neutralleiter ist nach dem Neutralleiterstrom zu bemessen – hier nicht berücksichtigt.", text: "Leitungslänge 0 m: Für diese Berechnung wird kein Spannungsfall angesetzt.",
}); });
} }
if (group === "ground") { if (group === "ground") {

View file

@ -11,9 +11,9 @@ import {
LAYING_METHODS, LAYING_METHODS,
LAYING_METHOD_GROUP, LAYING_METHOD_GROUP,
LAYING_METHOD_LABELS, LAYING_METHOD_LABELS,
MAX_GROUPING_CIRCUITS,
PRACTICAL_MINIMUM_CROSS_SECTION_MM2, PRACTICAL_MINIMUM_CROSS_SECTION_MM2,
REFERENCE_TEMPERATURE_C, REFERENCE_TEMPERATURE_C,
maxGroupingCircuits,
temperatureOptions, temperatureOptions,
type CableSizingAlert, type CableSizingAlert,
type CableSizingInput, type CableSizingInput,
@ -100,6 +100,7 @@ export function CableSizingModal({
? threePhaseVoltageV ? threePhaseVoltageV
: singlePhaseVoltageV; : singlePhaseVoltageV;
const group = LAYING_METHOD_GROUP[layingMethod]; const group = LAYING_METHOD_GROUP[layingMethod];
const groupingMaximum = maxGroupingCircuits(layingMethod);
const availableTemperatures = temperatureOptions(layingMethod, insulation); const availableTemperatures = temperatureOptions(layingMethod, insulation);
const input: CableSizingInput = useMemo( const input: CableSizingInput = useMemo(
@ -208,7 +209,7 @@ export function CableSizingModal({
return ( return (
<FormModal <FormModal
description="Querschnittsempfehlung nach IEC 60364-5-52 Anhang B (Belastbarkeit, Temperatur, Häufung, Spannungsfall, Schutzorgan) oder direkte Eingabe. Die Empfehlung vor der Übernahme fachlich prüfen; nationale Tabellen (DIN VDE 0298-4) können abweichen." description="Querschnittsempfehlung mit Strombelastbarkeit, Temperatur- und Häufungsfaktoren nach DIN VDE 0298-4:2023-06 einschließlich Berichtigung 1:2023-10. Spannungsfall und Schutzorgan werden ergänzend geprüft. Die Empfehlung vor der Übernahme fachlich prüfen."
isSaving={isSaving} isSaving={isSaving}
onClose={onClose} onClose={onClose}
onSubmit={handleSubmit} onSubmit={handleSubmit}
@ -234,6 +235,11 @@ export function CableSizingModal({
type="number" type="number"
value={lengthM} value={lengthM}
/> />
{lengthM === 0 ? (
<div className="form-text text-warning">
Bei 0 m wird kein Spannungsfall angesetzt.
</div>
) : null}
</div> </div>
<div className="col-12 col-md-4"> <div className="col-12 col-md-4">
<label className="form-label" htmlFor="cable-sizing-phase"> <label className="form-label" htmlFor="cable-sizing-phase">
@ -279,6 +285,7 @@ export function CableSizingModal({
onChange={(event) => { onChange={(event) => {
const method = event.target.value as LayingMethod; const method = event.target.value as LayingMethod;
setLayingMethod(method); setLayingMethod(method);
setGroupingCircuits((current) => Math.min(current, maxGroupingCircuits(method)));
applyReferenceTemperature(method, insulation); applyReferenceTemperature(method, insulation);
resetResult(); resetResult();
}} }}
@ -362,7 +369,7 @@ export function CableSizingModal({
<input <input
className="form-control" className="form-control"
id="cable-sizing-grouping" id="cable-sizing-grouping"
max={MAX_GROUPING_CIRCUITS} max={groupingMaximum}
min={1} min={1}
onChange={(event) => { onChange={(event) => {
setGroupingCircuits(Math.max(1, Math.round(Number(event.target.value) || 1))); setGroupingCircuits(Math.max(1, Math.round(Number(event.target.value) || 1)));
@ -373,7 +380,10 @@ export function CableSizingModal({
value={groupingCircuits} value={groupingCircuits}
/> />
<div className="form-text"> <div className="form-text">
Anzahl gemeinsam verlegter Stromkreise, einschließlich dieses. Anzahl gemeinsam verlegter Stromkreise, einschließlich dieses
{layingMethod === "E"
? "; ohne Angaben zur Kabelpritsche ist nur 1 zulässig."
: `; für diese Verlegeart sind bis ${groupingMaximum} tabelliert.`}
</div> </div>
</div> </div>
{phase === 3 ? ( {phase === 3 ? (

View file

@ -45,28 +45,34 @@ const capacity = (
CROSS_SECTIONS_MM2.indexOf(crossSection as (typeof CROSS_SECTIONS_MM2)[number]) CROSS_SECTIONS_MM2.indexOf(crossSection as (typeof CROSS_SECTIONS_MM2)[number])
]; ];
describe("cable sizing reference tables (IEC 60364-5-52 Annex B)", () => { describe("cable sizing reference tables (DIN VDE 0298-4:2023-06)", () => {
it("contains the checked PVC copper values", () => { it("contains the checked PVC copper values", () => {
// B.52.2: A1 two loaded conductors (the previously wrong row) assert.equal(capacity("pvc", "copper", "A1", 2, 1.5), 15.5);
assert.equal(capacity("pvc", "copper", "A2", 2, 1.5), 15.5);
assert.equal(capacity("pvc", "copper", "A1", 2, 2.5), 19.5); assert.equal(capacity("pvc", "copper", "A1", 2, 2.5), 19.5);
assert.equal(capacity("pvc", "copper", "A1", 2, 16), 61); assert.equal(capacity("pvc", "copper", "A1", 2, 16), 61);
assert.equal(capacity("pvc", "copper", "A1", 2, 120), 210); assert.equal(capacity("pvc", "copper", "A1", 2, 120), 210);
// B.52.2: B2 from 50 mm² (previously copied from B1) // DIN table 3: B2 from 50 mm² (previously copied from B1)
assert.equal(capacity("pvc", "copper", "B2", 2, 50), 133); assert.equal(capacity("pvc", "copper", "B2", 2, 50), 133);
assert.equal(capacity("pvc", "copper", "B2", 2, 120), 232); assert.equal(capacity("pvc", "copper", "B2", 2, 120), 232);
// B.52.4 and D1/D2 // Tables 3 and 4, including reference method D in conduits/ducts.
assert.equal(capacity("pvc", "copper", "C", 3, 6), 41); assert.equal(capacity("pvc", "copper", "C", 3, 6), 41);
assert.equal(capacity("pvc", "copper", "D1", 2, 2.5), 29); assert.equal(capacity("pvc", "copper", "D", 2, 2.5), 29);
assert.equal(capacity("pvc", "copper", "D2", 3, 16), 70); assert.equal(capacity("pvc", "copper", "A1", 2, 240), 321);
// Conflicting sources resolved to the lower value
assert.equal(capacity("pvc", "copper", "A1", 2, 240), 320);
assert.equal(capacity("pvc", "copper", "E", 3, 70), 196); assert.equal(capacity("pvc", "copper", "E", 3, 70), 196);
}); });
it("uses the aluminium and XLPE tables instead of flat factors", () => { it("uses aluminium only from the first DIN-tabulated size of 25 mm²", () => {
assert.equal(capacity("pvc", "aluminum", "A1", 2, 1.5), null); for (const insulation of ["pvc", "xlpe"] as const) {
assert.equal(capacity("pvc", "aluminum", "C", 3, 16), 59); for (const method of ["A1", "A2", "B1", "B2", "C", "D", "E"] as const) {
assert.equal(capacity("pvc", "aluminum", "D2", 2, 10), null); for (const cores of [2, 3] as const) {
for (const crossSection of CROSS_SECTIONS_MM2.filter((value) => value < 25)) {
assert.equal(capacity(insulation, "aluminum", method, cores, crossSection), null);
}
}
}
}
assert.equal(capacity("pvc", "aluminum", "C", 3, 25), 73);
assert.equal(capacity("xlpe", "copper", "C", 2, 2.5), 33); assert.equal(capacity("xlpe", "copper", "C", 2, 2.5), 33);
assert.equal(capacity("xlpe", "aluminum", "E", 3, 25), 97); assert.equal(capacity("xlpe", "aluminum", "E", 3, 25), 97);
}); });
@ -113,10 +119,15 @@ describe("calculateCableSizing", () => {
const cold = calculateCableSizing({ ...BASE_INPUT, ambientTemperatureC: 5 }); const cold = calculateCableSizing({ ...BASE_INPUT, ambientTemperatureC: 5 });
assert.equal(cold.temperatureUsedC, 10); assert.equal(cold.temperatureUsedC, 10);
assert.equal(cold.temperatureFactor, 1.22); assert.equal(cold.temperatureFactor, 1.22);
const grouped = calculateCableSizing({ ...BASE_INPUT, groupingCircuits: 10 }); const exact = calculateCableSizing({ ...BASE_INPUT, groupingCircuits: 10 });
assert.equal(exact.groupingUsed, 10);
assert.equal(exact.groupingFactor, 0.48);
assert.equal(calculateCableSizing({ ...BASE_INPUT, groupingCircuits: 14 }).groupingFactor, 0.43);
assert.equal(calculateCableSizing({ ...BASE_INPUT, groupingCircuits: 18 }).groupingFactor, 0.39);
const grouped = calculateCableSizing({ ...BASE_INPUT, groupingCircuits: 11 });
assert.equal(grouped.groupingUsed, 12); assert.equal(grouped.groupingUsed, 12);
assert.equal(grouped.groupingFactor, 0.45); assert.equal(grouped.groupingFactor, 0.45);
const alerts = buildCableSizingAlerts({ ...BASE_INPUT, groupingCircuits: 10 }, grouped); const alerts = buildCableSizingAlerts({ ...BASE_INPUT, groupingCircuits: 11 }, grouped);
assert.ok(alerts.some((alert) => alert.text.includes("12 Stromkreise gerundet"))); assert.ok(alerts.some((alert) => alert.text.includes("12 Stromkreise gerundet")));
}); });
@ -129,13 +140,15 @@ describe("calculateCableSizing", () => {
); );
}); });
it("uses the ground tables for D1 (ducts) and D2 (direct)", () => { it("limits grouping to the corrected DIN tables for D and E", () => {
const d1 = calculateCableSizing({ ...BASE_INPUT, layingMethod: "D1", ambientTemperatureC: 20, groupingCircuits: 10 }); const d = calculateCableSizing({ ...BASE_INPUT, layingMethod: "D", ambientTemperatureC: 20, groupingCircuits: 6 });
assert.equal(d1.groupingFactor, 0.49); assert.equal(d.groupingFactor, 0.6);
const d2 = calculateCableSizing({ ...BASE_INPUT, layingMethod: "D2", ambientTemperatureC: 20, groupingCircuits: 10 }); assert.equal(calculateCableSizing({ ...BASE_INPUT, layingMethod: "D", ambientTemperatureC: 20, groupingCircuits: 7 }).supported, false);
assert.equal(d2.groupingUsed, 12); const e = calculateCableSizing({ ...BASE_INPUT, layingMethod: "E", groupingCircuits: 2 });
assert.equal(d2.groupingFactor, 0.36); assert.equal(e.supported, false);
assert.equal(calculateCableSizing({ ...BASE_INPUT, layingMethod: "D2", ambientTemperatureC: 30 }).temperatureFactor, 0.89); assert.equal(e.unsupportedReason, "grouping");
assert.ok(buildCableSizingAlerts({ ...BASE_INPUT, layingMethod: "E", groupingCircuits: 2 }, e)[0]?.text.includes("Kabelpritsche"));
assert.equal(calculateCableSizing({ ...BASE_INPUT, layingMethod: "D", ambientTemperatureC: 30 }).temperatureFactor, 0.89);
}); });
it("refuses inputs outside the tables instead of assuming a factor of 1", () => { it("refuses inputs outside the tables instead of assuming a factor of 1", () => {
@ -156,9 +169,11 @@ describe("calculateCableSizing", () => {
lengthM: 5, lengthM: 5,
conductorMaterial: "aluminum", conductorMaterial: "aluminum",
}); });
assert.equal(result.recommendedCrossSectionMm2, 2.5); assert.equal(result.recommendedCrossSectionMm2, 25);
assert.equal(result.rows[0].ratedCurrentA, null); for (const row of result.rows.filter((row) => row.crossSectionMm2 < 25)) {
assert.equal(result.rows[0].recommended, false); assert.equal(row.ratedCurrentA, null);
assert.equal(row.recommended, false);
}
}); });
it("checks gG fuses with I2 = 1.6 In and circuit-breakers with In ≤ Iz", () => { it("checks gG fuses with I2 = 1.6 In and circuit-breakers with In ≤ Iz", () => {
@ -186,6 +201,23 @@ describe("calculateCableSizing", () => {
assert.equal(single.harmonicReductionApplied, false); assert.equal(single.harmonicReductionApplied, false);
}); });
it("does not recommend a cross-section above 33 % third harmonic without neutral current", () => {
const input = { ...BASE_INPUT, phase: 3 as const, voltage: 400, harmonicNeutralLoad: "over33Percent" as const };
const result = calculateCableSizing(input);
assert.equal(result.supported, false);
assert.equal(result.unsupportedReason, "harmonics");
assert.equal(result.recommendedCrossSectionMm2, null);
assert.equal(buildCableSizingAlerts(input, result)[0]?.kind, "critical");
});
it("allows zero length and states that no voltage drop is applied", () => {
const input = { ...BASE_INPUT, lengthM: 0 };
const result = calculateCableSizing(input);
assert.equal(result.supported, true);
assert.equal(result.voltageDropAtRecommendedPercent, 0);
assert.ok(buildCableSizingAlerts(input, result).some((alert) => alert.text.includes("kein Spannungsfall")));
});
it("raises single-phase circuits to the 2.5 mm² practical minimum", () => { it("raises single-phase circuits to the 2.5 mm² practical minimum", () => {
const result = calculateCableSizing({ const result = calculateCableSizing({
...BASE_INPUT, ...BASE_INPUT,
@ -203,7 +235,7 @@ describe("calculateCableSizing", () => {
it("offers only tabulated temperatures", () => { it("offers only tabulated temperatures", () => {
assert.deepEqual(temperatureOptions("C", "pvc").at(-1), 60); assert.deepEqual(temperatureOptions("C", "pvc").at(-1), 60);
assert.deepEqual(temperatureOptions("C", "xlpe").at(-1), 80); assert.deepEqual(temperatureOptions("C", "xlpe").at(-1), 80);
assert.deepEqual(temperatureOptions("D1", "pvc")[2], 20); assert.deepEqual(temperatureOptions("D", "pvc")[2], 20);
}); });
}); });
@ -219,6 +251,9 @@ describe("cable sizing request schema", () => {
false false
); );
assert.equal(cableSizingRequestSchema.safeParse({ ...BASE_INPUT, layingMethod: "F" }).success, false); assert.equal(cableSizingRequestSchema.safeParse({ ...BASE_INPUT, layingMethod: "F" }).success, false);
assert.equal(cableSizingRequestSchema.safeParse({ ...BASE_INPUT, layingMethod: "D1" }).success, false);
assert.equal(cableSizingRequestSchema.safeParse({ ...BASE_INPUT, layingMethod: "D2" }).success, false);
assert.equal(cableSizingRequestSchema.safeParse({ ...BASE_INPUT, layingMethod: "D" }).success, true);
assert.equal(cableSizingRequestSchema.safeParse({ ...BASE_INPUT, mode: "current" }).success, false); assert.equal(cableSizingRequestSchema.safeParse({ ...BASE_INPUT, mode: "current" }).success, false);
}); });
}); });