cable-sizing: max length, socket minimum, manual-entry warning, SVG icon
- 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 commit is contained in:
parent
8562d6eb83
commit
cfb76a3754
6 changed files with 210 additions and 6 deletions
|
|
@ -134,7 +134,7 @@ describe("buildCableSizingAlerts", () => {
|
|||
assert.ok(alerts[0].text.includes("keine geprüften"));
|
||||
});
|
||||
|
||||
it("returns a single ok alert for a clean, unremarkable case", () => {
|
||||
it("returns an ok alert plus a max-length info alert for a clean, unremarkable case", () => {
|
||||
const input: CableSizingInput = {
|
||||
...BASE_INPUT,
|
||||
mode: "current",
|
||||
|
|
@ -144,7 +144,66 @@ describe("buildCableSizingAlerts", () => {
|
|||
};
|
||||
const result = calculateCableSizing(input);
|
||||
const alerts = buildCableSizingAlerts(input, result);
|
||||
assert.equal(alerts.length, 1);
|
||||
assert.equal(alerts.length, 2);
|
||||
assert.equal(alerts[0].kind, "ok");
|
||||
assert.equal(alerts[1].kind, "info");
|
||||
assert.ok(alerts[1].text.includes("Maximale Länge"));
|
||||
});
|
||||
});
|
||||
|
||||
describe("maxLengthForVoltageDropM", () => {
|
||||
it("is the inverse of the voltage-drop formula: recalculating at that length gives back the limit", () => {
|
||||
const result = calculateCableSizing(BASE_INPUT);
|
||||
const recommendedRow = result.rows.find((row) => row.recommended);
|
||||
assert.ok(recommendedRow?.maxLengthForVoltageDropM != null);
|
||||
|
||||
const atMaxLength = calculateCableSizing({
|
||||
...BASE_INPUT,
|
||||
lengthM: recommendedRow!.maxLengthForVoltageDropM!,
|
||||
});
|
||||
const rowAtSameCrossSection = atMaxLength.rows.find(
|
||||
(row) => row.crossSectionMm2 === recommendedRow!.crossSectionMm2
|
||||
);
|
||||
assert.ok(
|
||||
Math.abs(rowAtSameCrossSection!.voltageDropPercent! - BASE_INPUT.maxVoltageDropPercent) <
|
||||
0.01
|
||||
);
|
||||
});
|
||||
|
||||
it("is null when there is no current flowing (division by zero guard)", () => {
|
||||
const result = calculateCableSizing({ ...BASE_INPUT, mode: "current", currentA: 0, powerKw: undefined });
|
||||
assert.ok(result.rows.every((row) => row.maxLengthForVoltageDropM === null));
|
||||
});
|
||||
});
|
||||
|
||||
describe("practical minimum cross-section for single_phase circuits", () => {
|
||||
it("raises a smaller calculated recommendation to 2.5 mm² for single_phase circuits", () => {
|
||||
// 1 A load at 30 m would normally recommend 1.5 mm² by calculation alone.
|
||||
const smallLoad: CableSizingInput = { ...BASE_INPUT, mode: "current", currentA: 1, powerKw: undefined };
|
||||
const withoutCategory = calculateCableSizing(smallLoad);
|
||||
const withCategory = calculateCableSizing({ ...smallLoad, circuitCategory: "single_phase" });
|
||||
|
||||
assert.equal(withoutCategory.recommendedCrossSectionMm2, 1.5);
|
||||
assert.equal(withoutCategory.practicalMinimumApplied, false);
|
||||
assert.equal(withCategory.recommendedCrossSectionMm2, 2.5);
|
||||
assert.equal(withCategory.practicalMinimumApplied, true);
|
||||
});
|
||||
|
||||
it("never lowers a recommendation that already needs more than the practical minimum", () => {
|
||||
const result = calculateCableSizing({ ...BASE_INPUT, circuitCategory: "single_phase" });
|
||||
assert.equal(result.recommendedCrossSectionMm2, 4);
|
||||
assert.equal(result.practicalMinimumApplied, false);
|
||||
});
|
||||
|
||||
it("does not apply to lighting or three_phase categories", () => {
|
||||
const smallLoad: CableSizingInput = { ...BASE_INPUT, mode: "current", currentA: 1, powerKw: undefined };
|
||||
assert.equal(
|
||||
calculateCableSizing({ ...smallLoad, circuitCategory: "lighting" }).recommendedCrossSectionMm2,
|
||||
1.5
|
||||
);
|
||||
assert.equal(
|
||||
calculateCableSizing({ ...smallLoad, circuitCategory: "three_phase" }).recommendedCrossSectionMm2,
|
||||
1.5
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue