Isolated module (mirrors src/external-model/ dependency direction) that adds an on-demand, DIN VDE 0298-4 referenced cable cross-section calculator: a click-on-a-circuit input mask for laying method, insulation, ambient temperature, grouping and voltage-drop limit that suggests a cross-section for the circuit cableCrossSection/cableLength fields. Applying a suggestion goes through the existing circuit.update command (expectedRevision, undo/redo) unchanged - nothing here writes to circuits directly or touches the revision/command system. See docs/cable-sizing-module.md for the full rationale, API contract, verification status and maintenance plan. Proposal, not yet reviewed by the project owner - see the docs file. 423 existing + 11 new tests pass, build:api/build:web/typecheck:scripts clean.
8 KiB
Cable Sizing Module (Proposal)
Status: proposal, not yet reviewed or merged by the project owner. This
document and the accompanying src/cable-sizing/, src/db/schema/cable-sizing-calculations.ts,
src/server/{controllers,routes}/cable-sizing.* and src/frontend/components/cable-sizing-*
files were written by a third party (see git history/authorship) against the
project's own stated direction in docs/spec/06-future-sizing-and-calculations.md
("The app should later support rule-based protection and cable sizing") and
AGENTS.md ("... and later electrical sizing logic"). Nothing here has been
pushed to the project's own repository; it exists as a local branch for
review, testing and discussion.
Why a separate module instead of extending core domain code
AGENTS.md is explicit that critical multi-write commands, the revision/undo
system and the Circuit-First domain model are the supported architecture, and
that changes should be small and reviewable. A cable-sizing calculation is
not a project mutation - it doesn't need expectedRevision, doesn't
belong in the undo/redo stack, and shouldn't grow the circuit.update
command's switch statement or the DTOs it doesn't already have.
So this module is built exactly like the existing src/external-model/
adapter (the Revit/CSV import foundation): a one-way dependency boundary.
src/cable-sizing/domain/ <- pure functions and types, zero imports from
db/, server/ or frontend/
src/db/schema/ <- one new, fully additive table
src/db/repositories/ <- one plain repository (no revision semantics)
src/server/{controllers,routes}/cable-sizing.* <- one new, isolated route
group, mounted with a single app.use() line
src/frontend/components/cable-sizing-* <- one new modal + one small
API client, following the existing
FormModal / CircuitProtectionModal pattern
Nothing outside these files imports from them except the two required
one-line hooks described below. If this module is rejected or needs to move
out again, removing it is a matter of deleting these files, the two
one-line hooks, and running a DROP TABLE migration - it never touches
circuits, project_revisions or any command/history table.
The two required hooks into existing code
src/server/index.ts: one import + oneapp.use("/api/cable-sizing", cableSizingRouter)line, next to the existingapp.use("/api/projects", ...)etc.src/frontend/components/circuit-tree-editor.tsx: one newuseStatefor the open modal, one new trigger condition (mirroring the existingisProtectionTrigger/protectionEditorCircuitpattern almost exactly), and one new conditionally-rendered<CableSizingModal />alongside the existing<CircuitProtectionModal />. See the diff for the exact lines.
No changes to circuit-project-command.model.ts, project-command.service.ts,
any Zod command schema, any migration for circuits/project_revisions, or
any existing test.
How a suggestion is applied
The module never writes to circuits directly. The modal's "Empfehlung
übernehmen" button calls the existing, unmodified frontend helper:
updateCircuitById(projectId, expectedRevision, circuitId, {
cableCrossSection: "4 mm²",
cableLength: 30,
});
which is the same circuit.update command the grid already uses for manual
cell edits. This means:
- optimistic concurrency (
expectedRevision) is respected automatically - the change appears in the project's revision history and is undoable/ redoable exactly like a manual edit
- manually typing a value into the
cableCrossSectioncell keeps working unchanged - the module only adds a small calculator trigger next to the cell, perdocs/spec/06-future-sizing-and-calculations.md's explicit requirement that "users must remain able to manually override suggestions"
API contract
POST /api/cable-sizing/calculate
{
"phase": 1, // 1 | 3
"mode": "power", // "power" | "current"
"powerKw": 4.2, // circuit.circuitTotalPower, when mode="power"
"cosPhi": 1,
"voltage": 230, // circuit.voltage (already project-derived, read-only)
"lengthM": 23.5, // circuit.cableLength
"layingMethod": "C", // DIN VDE 0298-4 reference method, A1|A2|B1|B2|C|D1|D2|E|F|G
"conductorMaterial": "copper", // "copper" | "aluminum"
"insulation": "pvc", // "pvc" | "xlpe"
"ambientTemperatureC": 30,
"groupingCircuits": 1,
"maxVoltageDropPercent": 3,
"harmonicNeutralLoad": "none", // "none" | "15to33Percent" | "over33Percent", three-phase only
"existingProtectionRatedCurrentA": 16, // optional, from circuit.protectionDevice
"context": { "projectId": "...", "circuitId": "...", "equipmentIdentifier": "-1F1.1" }
}
Response: { calculationId, result: CableSizingResult, alerts: CableSizingAlert[] }
(see src/cable-sizing/domain/cable-sizing-calculation.ts for the exact
shape). GET /api/cable-sizing/laying-methods and .../insulation-materials
expose the pick-list metadata (including which combinations are verified) so
a future non-modal UI could build its own form without hard-coding the enum.
Verification status - important
The reference current-carrying-capacity tables are ported from a sibling
project's Kabelliste module, which was itself only verified against ~8
public sources for six of the ten DIN VDE 0298-4 reference laying
methods (A1, B2, C, E, D1, D2) and PVC insulation only. The other four
methods (A2, B1, F, G) and XLPE/VPE insulation could not be verified without
contradiction across sources during that earlier work, so
calculateCableSizing deliberately returns dataVerified: false and no
numeric recommendation for those combinations, rather than a guessed value.
The UI surfaces this as a plain critical alert. Anyone with access to the
actual norm text can extend LAYING_METHOD_VERIFIED and
CURRENT_CAPACITY_A in src/cable-sizing/domain/cable-sizing-calculation.ts
once the missing tables are confirmed.
The protection-coordination check (existingProtectionRatedCurrentA vs. the
recommended cross-section's corrected capacity) is a simplified In <= Iz check only - it is not a full IEC 60364-4-43 overload (I2 <= 1.45 x Iz) or short-circuit withstand check.
Relationship to the project's own future-sizing spec
docs/spec/06-future-sizing-and-calculations.md separately describes simple
category-based defaults (e.g. lighting circuits -> 10 A / 1.5 mm²) as
"common planning defaults, not a replacement for full norm-compliant
calculation". This module is the latter: an on-demand, norm-referenced
calculation for one circuit at a time, not a bulk default-filling tool. The
two are complementary and could later be wired together (e.g. the category
defaults pre-fill this module's laying-method/insulation fields), but that
integration is out of scope here.
The spec's isPublicBuilding halogen-free rule and the Control Requirement
(DALI/KNX/core-count) field are not implemented by this module - both
concern cable type selection, not cross-section sizing, and the latter
isn't in the current schema yet. They're natural follow-ups once this module
is reviewed.
Maintenance plan
The src/cable-sizing/domain/ folder has zero imports from the rest of the
app by design (see the dependency-direction note above), so it can be
extracted into an independently versioned/published package later (e.g. a
private npm package or a git subtree) without touching anything outside the
five files/folders listed above - "separately maintained but part of the
app for now", per the intent of this proposal.
Local testing
Deployed and manually exercised on an internal test host at
http://192.168.0.133:3220 (a fork of this repository's main branch, not
pushed anywhere). npm test, npm run build:api, npm run build:web and
npm run typecheck:scripts all pass with this module included; see the
accompanying diff/branch for the exact commit.