# 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 1. `src/server/index.ts`: one import + one `app.use("/api/cable-sizing", cableSizingRouter)` line, next to the existing `app.use("/api/projects", ...)` etc. 2. `src/frontend/components/circuit-tree-editor.tsx`: one new `useState` for the open modal, one new trigger condition (mirroring the existing `isProtectionTrigger` / `protectionEditorCircuit` pattern almost exactly), and one new conditionally-rendered `` alongside the existing ``. 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: ```ts 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 `cableCrossSection` cell keeps working unchanged - the module only adds a small calculator trigger next to the cell, per `docs/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` ```jsonc { "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.