Proposal: cable-sizing module (on-demand DIN VDE 0298-4 cross-section calculator) #1

Open
Grovy311 wants to merge 10 commits from Grovy311/leistungsbilanz-ts:feature/cable-sizing-module into main
First-time contributor

Summary

Adds an on-demand cable cross-section calculator for individual circuits, matching the direction already stated in this repo's own 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").

This is a review proposal, not a request to merge as-is. Full rationale, architecture, API contract, verification status and known caveats are all written up in docs/cable-sizing-module.md - please read that first, it answers most "why did you do it this way" questions.

What it does

  • Click a calculator icon next to a circuit's cable field to open a dialog: pick laying method / insulation / conductor material / ambient temperature / grouping / max voltage drop, get a recommended cross-section, or type one in by hand.
  • Sizing is breaker-aware: uses max(operating current, existing protection device rating), not just the load current (the standard In <= Iz rule) - a bare load-based calculation is not enough once a breaker is already chosen.
  • Shows the maximum single-run length for the chosen cross-section at the requested voltage-drop limit.
  • Applies a floor of 2.5 mm² for single_phase circuits, matching the exact convention already named in docs/spec/06-future-sizing-and-calculations.md - never lowers a calculation that needs more.
  • Manual entry always works, calculated or not, per that same doc's "users must remain able to manually override suggestions".
  • Honest about gaps: 4 of the 10 DIN VDE 0298-4 reference laying methods and XLPE insulation could not be verified against public sources without contradiction, so those combinations return no numeric result rather than a guess (see the Verification status section in the docs).

Why it's isolated

Mirrors src/external-model/'s dependency direction: src/cable-sizing/domain/ has zero imports from db/, server/ or frontend/. Touches exactly two existing files with a one-line hook each (server/index.ts route mount, circuit-tree-editor.tsx modal trigger) - everything else is new files. Never writes to circuits directly; applying a result goes through the existing, unmodified circuit.update command, so expectedRevision/undo/redo work automatically. No changes to the command dispatcher, Zod command schemas, or any circuits/project_revisions migration.

Testing

Deployed and manually exercised end-to-end against seeded real project/circuit data (created through the actual command API) on an internal host, including a deliberately long 85 m circuit to hit the voltage-drop-critical path. npm test, npm run build:api, npm run build:web and npm run typecheck:scripts all pass at every commit in this branch (429 tests, all green as of the latest commit).

Two commits are marked "Local-only" (port remap, dev-origin allowlist) - infrastructure needed only for testing on that internal host, not part of this proposal.

Open questions for you

  • Whether the practical-minimum and breaker-aware conventions match how you'd want this to behave, or whether they should be configurable/optional.
  • Whether the missing laying-method/XLPE data is worth tracking down together, or better left as-is for now.
  • Whether the module boundary (own folder, own table, own routes) is the shape you'd want, or whether you'd rather see it differently.

Happy to adjust based on whatever you think, including not merging this at all if it doesn't fit.

## Summary Adds an on-demand cable cross-section calculator for individual circuits, matching the direction already stated in this repo's own `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"). **This is a review proposal, not a request to merge as-is.** Full rationale, architecture, API contract, verification status and known caveats are all written up in [`docs/cable-sizing-module.md`](../blob/feature/cable-sizing-module/docs/cable-sizing-module.md) - please read that first, it answers most "why did you do it this way" questions. ## What it does - Click a calculator icon next to a circuit's cable field to open a dialog: pick laying method / insulation / conductor material / ambient temperature / grouping / max voltage drop, get a recommended cross-section, or type one in by hand. - Sizing is **breaker-aware**: uses `max(operating current, existing protection device rating)`, not just the load current (the standard `In <= Iz` rule) - a bare load-based calculation is not enough once a breaker is already chosen. - Shows the maximum single-run length for the chosen cross-section at the requested voltage-drop limit. - Applies a floor of 2.5 mm² for `single_phase` circuits, matching the exact convention already named in `docs/spec/06-future-sizing-and-calculations.md` - never lowers a calculation that needs more. - Manual entry always works, calculated or not, per that same doc's "users must remain able to manually override suggestions". - **Honest about gaps**: 4 of the 10 DIN VDE 0298-4 reference laying methods and XLPE insulation could not be verified against public sources without contradiction, so those combinations return no numeric result rather than a guess (see the Verification status section in the docs). ## Why it's isolated Mirrors `src/external-model/`'s dependency direction: `src/cable-sizing/domain/` has zero imports from `db/`, `server/` or `frontend/`. Touches exactly two existing files with a one-line hook each (`server/index.ts` route mount, `circuit-tree-editor.tsx` modal trigger) - everything else is new files. Never writes to `circuits` directly; applying a result goes through the existing, unmodified `circuit.update` command, so `expectedRevision`/undo/redo work automatically. No changes to the command dispatcher, Zod command schemas, or any `circuits`/`project_revisions` migration. ## Testing Deployed and manually exercised end-to-end against seeded real project/circuit data (created through the actual command API) on an internal host, including a deliberately long 85 m circuit to hit the voltage-drop-critical path. `npm test`, `npm run build:api`, `npm run build:web` and `npm run typecheck:scripts` all pass at every commit in this branch (429 tests, all green as of the latest commit). Two commits are marked "Local-only" (port remap, dev-origin allowlist) - infrastructure needed only for testing on that internal host, not part of this proposal. ## Open questions for you - Whether the practical-minimum and breaker-aware conventions match how you'd want this to behave, or whether they should be configurable/optional. - Whether the missing laying-method/XLPE data is worth tracking down together, or better left as-is for now. - Whether the module boundary (own folder, own table, own routes) is the shape you'd want, or whether you'd rather see it differently. Happy to adjust based on whatever you think, including not merging this at all if it doesn't fit.
Grovy311 added 10 commits 2026-08-07 19:56:56 +02:00
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.
Not part of the cable-sizing proposal - only needed because this host
already runs ~50 other containers using the default 3000/3001 and the
default docker network address pool was exhausted. Should not be
carried over if this branch is ever proposed upstream.
context.circuitId/projectId are caller-supplied audit-log metadata, not
used in the calculation itself. A stale or unknown id (e.g. a circuit
deleted between page load and this request) violated the foreign key
and fell through to the generic 500 handler; catch it and report it as
a normal validation error instead.
Not part of the cable-sizing proposal. next.config.mjs allowedDevOrigins
was scoped to the project owners own network; without cj-ki (192.168.0.133)
listed, Next.js 16 rejects the client HMR WebSocket handshake from this
host and the client-rendered pages (e.g. the circuit tree editor) hang on
their loading state forever, even though plain HTTP/API requests work
fine. Same root cause already documented for the sibling elt-planung-suite
project. Should not be carried over if this branch is proposed upstream.
The trigger was wired to the cableCrossSection column, which is not in
the default visible column set - the actually visible "Kabel" column
is cableSummary (a computed cableType/cableCrossSection/cableLength
join). Editing cableSummary already maps to the remark field rather
than the underlying cable data (see buildCircuitEditPatch), so this
column was effectively a read-only display already; retargeting the
trigger there loses no existing manual-edit capability. Also adds
.cell-cable-sizing-trigger styling (mirrors .cell-protection-trigger)
so the trigger is visually discoverable, which the initial version was
missing entirely.
Trigger now matches cableSummary OR cableCrossSection so it still
shows up regardless of which cable-related column a user has visible
(column visibility is a per-browser preference in this app, not fixed
- see docs/cable-sizing-module.md coupling note). Icon changed to a
calculator glyph.
- calculateCableSizing now selects cross-section against
  designCurrentA = max(operatingCurrentA, existingProtectionRatedCurrentA)
  instead of the load current alone. A breaker only trips at its own rated
  current (In), so a cable sized for the actual load could overheat under
  sustained current below that threshold - the standard In <= Iz rule.
  Voltage-drop calculation still uses the real operating current, only the
  capacity check changed. Alerts and the ok-summary now name the breaker as
  the limiting factor when it is one.
- Added a manual cable type/cross-section entry in the modal itself:
  running a calculation pre-fills these fields as a suggestion, but they
  are the single field that actually gets applied, and stay editable -
  this restores the direct manual-entry capability the modal replaced when
  it took over the cableSummary/cableCrossSection cell click.
- Fixed calculator icon position from a trailing ::after (inconsistent
  position depending on cell text length, easy to miss on empty cells) to
  a fixed-position ::before, so it is always in the same place regardless
  of whether cable data is already filled in.
- No new display for the existing protection device - it already has its
  own Schutz column in this app.
- Cross-section rows now include maxLengthForVoltageDropM, the inverse of
  the voltage-drop formula: the longest single run that stays within the
  requested max voltage drop at the given load/cosPhi/phase. Shown as a KPI
  and in the ok-summary alert.
- Added PRACTICAL_MINIMUM_CROSS_SECTION_MM2 (2.5 mm² for single_phase
  circuits) - a planning convention already named in this projects own
  docs/spec/06-future-sizing-and-calculations.md, not a thermal/
  voltage-drop requirement. Only ever raises a calculated recommendation,
  never lowers one that already needs more. circuitCategory is looked up
  from the circuits section and passed through from the editor.
- The manual cross-section field now warns (not blocks - manual override
  must stay possible per the same spec doc) when the typed value is not a
  standard cross-section, is smaller than the last calculation, or is
  below the practical minimum for single_phase circuits.
- Modal title now includes the circuit displayName next to the equipment
  identifier.
- Replaced the calculator emoji trigger icon with an inline SVG - the emoji
  did not render in at least one tested environment (missing font glyph),
  SVG has no such dependency.
This pull request has changes conflicting with the target branch.
  • compose.yaml
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u feature/cable-sizing-module:Grovy311-feature/cable-sizing-module
git checkout Grovy311-feature/cable-sizing-module

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git checkout main
git merge --no-ff Grovy311-feature/cable-sizing-module
git checkout Grovy311-feature/cable-sizing-module
git rebase main
git checkout main
git merge --ff-only Grovy311-feature/cable-sizing-module
git checkout Grovy311-feature/cable-sizing-module
git rebase main
git checkout main
git merge --no-ff Grovy311-feature/cable-sizing-module
git checkout main
git merge --squash Grovy311-feature/cable-sizing-module
git checkout main
git merge --ff-only Grovy311-feature/cable-sizing-module
git checkout main
git merge Grovy311-feature/cable-sizing-module
git push origin main
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: jappel/leistungsbilanz-ts#1
No description provided.