Add distribution power summaries
This commit is contained in:
@@ -5,11 +5,14 @@ import {
|
||||
buildCircuitEditPatch,
|
||||
buildDeviceRowEditPatch,
|
||||
formatValue,
|
||||
getCircuitSectionLabel,
|
||||
getProjectColumnLayoutStorageKey,
|
||||
isGridEditorControlTarget,
|
||||
getBlockSortValue,
|
||||
getCellKind,
|
||||
getCircuitValue,
|
||||
getDeviceValue,
|
||||
parseStoredColumnLayout,
|
||||
parseNumeric,
|
||||
} from "../src/frontend/utils/circuit-grid-model.js";
|
||||
import type { CircuitTreeCircuitDto, CircuitTreeDeviceRowDto } from "../src/frontend/types.js";
|
||||
@@ -73,6 +76,47 @@ describe("circuit grid model", () => {
|
||||
assert.equal(allColumns[0].locked, true);
|
||||
});
|
||||
|
||||
it("stores valid column layouts under a project-specific key", () => {
|
||||
assert.equal(
|
||||
getProjectColumnLayoutStorageKey("project-1"),
|
||||
"circuitTreeEditor.columnLayout.v2.project-1"
|
||||
);
|
||||
const layout = parseStoredColumnLayout(
|
||||
JSON.stringify({
|
||||
order: ["displayName", "equipmentIdentifier"],
|
||||
visible: [
|
||||
"displayName",
|
||||
"equipmentIdentifier",
|
||||
"unknown",
|
||||
],
|
||||
})
|
||||
);
|
||||
assert.ok(layout);
|
||||
assert.equal(layout.order[0], "equipmentIdentifier");
|
||||
assert.deepEqual(layout.visible, [
|
||||
"displayName",
|
||||
"equipmentIdentifier",
|
||||
]);
|
||||
assert.equal(parseStoredColumnLayout("{invalid"), null);
|
||||
});
|
||||
|
||||
it("shows stable circuit sections with German labels", () => {
|
||||
assert.equal(
|
||||
getCircuitSectionLabel({
|
||||
key: "lighting",
|
||||
displayName: "Lighting",
|
||||
}),
|
||||
"Licht"
|
||||
);
|
||||
assert.equal(
|
||||
getCircuitSectionLabel({
|
||||
key: "custom",
|
||||
displayName: "Sonderbereich",
|
||||
}),
|
||||
"Sonderbereich"
|
||||
);
|
||||
});
|
||||
|
||||
it("maps shared fields to the correct level for every row shape", () => {
|
||||
assert.equal(getCellKind("circuitCompact", "displayName"), "deviceField");
|
||||
assert.equal(getCellKind("circuitCompact", "equipmentIdentifier"), "circuitField");
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { describe, it } from "node:test";
|
||||
import {
|
||||
applyDistributionBoardSimultaneityFactor,
|
||||
calculateCircuitTotalPower,
|
||||
calculateDistributionBoardTotalPower,
|
||||
calculateRowTotalPower,
|
||||
calculateSectionTotalPower,
|
||||
} from "../src/domain/calculations/circuit-power-calculation.js";
|
||||
import {
|
||||
resolveCircuitPhaseType,
|
||||
@@ -19,6 +22,32 @@ describe("circuit power calculation", () => {
|
||||
]);
|
||||
assert.equal(total, 4.5);
|
||||
});
|
||||
|
||||
it("calculates section and distribution-board totals", () => {
|
||||
const lighting = calculateSectionTotalPower([
|
||||
{ circuitTotalPower: 1.5 },
|
||||
{ circuitTotalPower: 2.25 },
|
||||
]);
|
||||
const singlePhase = calculateSectionTotalPower([
|
||||
{ circuitTotalPower: 3 },
|
||||
]);
|
||||
const total = calculateDistributionBoardTotalPower([
|
||||
{ sectionTotalPower: lighting },
|
||||
{ sectionTotalPower: singlePhase },
|
||||
{ sectionTotalPower: 0 },
|
||||
]);
|
||||
|
||||
assert.equal(lighting, 3.75);
|
||||
assert.equal(total, 6.75);
|
||||
assert.equal(
|
||||
applyDistributionBoardSimultaneityFactor(total, 0.8),
|
||||
5.4
|
||||
);
|
||||
assert.throws(
|
||||
() => applyDistributionBoardSimultaneityFactor(total, 1.1),
|
||||
/between zero and one/
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("project voltage derivation", () => {
|
||||
|
||||
@@ -22,7 +22,10 @@ import {
|
||||
createDistributionBoardStructureSnapshot,
|
||||
type DistributionBoardStructureProjectCommand,
|
||||
} from "../src/domain/models/distribution-board-structure-project-command.model.js";
|
||||
import { createDistributionBoardUpdateProjectCommand } from "../src/domain/models/distribution-board-project-command.model.js";
|
||||
import {
|
||||
createDistributionBoardUpdateProjectCommand,
|
||||
type DistributionBoardUpdateProjectCommand,
|
||||
} from "../src/domain/models/distribution-board-project-command.model.js";
|
||||
import {
|
||||
createDistributionBoard,
|
||||
updateDistributionBoard,
|
||||
@@ -293,7 +296,7 @@ describe("distribution-board structure project command", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("updates floor and supply type with persisted undo and validates project ownership", () => {
|
||||
it("updates floor, supply type and simultaneity factor with persisted undo", () => {
|
||||
const context = createTestDatabase();
|
||||
try {
|
||||
context.db
|
||||
@@ -335,7 +338,11 @@ describe("distribution-board structure project command", () => {
|
||||
source: "user",
|
||||
command: createDistributionBoardUpdateProjectCommand(
|
||||
structure.distributionBoard.id,
|
||||
{ floorId: "floor-1", supplyType: "SV" }
|
||||
{
|
||||
floorId: "floor-1",
|
||||
supplyType: "SV",
|
||||
simultaneityFactor: 0.75,
|
||||
}
|
||||
),
|
||||
});
|
||||
assert.deepEqual(
|
||||
@@ -343,10 +350,16 @@ describe("distribution-board structure project command", () => {
|
||||
.select({
|
||||
floorId: distributionBoards.floorId,
|
||||
supplyType: distributionBoards.supplyType,
|
||||
simultaneityFactor:
|
||||
distributionBoards.simultaneityFactor,
|
||||
})
|
||||
.from(distributionBoards)
|
||||
.get(),
|
||||
{ floorId: "floor-1", supplyType: "SV" }
|
||||
{
|
||||
floorId: "floor-1",
|
||||
supplyType: "SV",
|
||||
simultaneityFactor: 0.75,
|
||||
}
|
||||
);
|
||||
|
||||
const undoStep = new ProjectHistoryRepository(
|
||||
@@ -365,10 +378,16 @@ describe("distribution-board structure project command", () => {
|
||||
.select({
|
||||
floorId: distributionBoards.floorId,
|
||||
supplyType: distributionBoards.supplyType,
|
||||
simultaneityFactor:
|
||||
distributionBoards.simultaneityFactor,
|
||||
})
|
||||
.from(distributionBoards)
|
||||
.get(),
|
||||
{ floorId: null, supplyType: "AV" }
|
||||
{
|
||||
floorId: null,
|
||||
supplyType: "AV",
|
||||
simultaneityFactor: 1,
|
||||
}
|
||||
);
|
||||
assert.throws(
|
||||
() =>
|
||||
@@ -395,6 +414,52 @@ describe("distribution-board structure project command", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps stored version-one board updates executable", () => {
|
||||
const context = createTestDatabase();
|
||||
try {
|
||||
const repository =
|
||||
new DistributionBoardStructureProjectCommandRepository(
|
||||
context.db
|
||||
);
|
||||
const structure = createDistributionBoardStructureSnapshot(
|
||||
"project-1",
|
||||
"UV Alt",
|
||||
{ supplyType: "AV" }
|
||||
);
|
||||
repository.execute({
|
||||
projectId: "project-1",
|
||||
expectedRevision: 0,
|
||||
source: "user",
|
||||
command:
|
||||
createDistributionBoardInsertProjectCommand(structure),
|
||||
});
|
||||
const legacyUpdate = {
|
||||
schemaVersion: 1,
|
||||
type: "distribution-board.update",
|
||||
payload: {
|
||||
distributionBoardId: structure.distributionBoard.id,
|
||||
changes: [{ field: "supplyType", value: "SV" }],
|
||||
},
|
||||
} as DistributionBoardUpdateProjectCommand;
|
||||
|
||||
repository.executeUpdate({
|
||||
projectId: "project-1",
|
||||
expectedRevision: 1,
|
||||
source: "user",
|
||||
command: legacyUpdate,
|
||||
});
|
||||
|
||||
const board = context.db
|
||||
.select()
|
||||
.from(distributionBoards)
|
||||
.get();
|
||||
assert.equal(board?.supplyType, "SV");
|
||||
assert.equal(board?.simultaneityFactor, 1);
|
||||
} finally {
|
||||
context.close();
|
||||
}
|
||||
});
|
||||
|
||||
it("sends distribution-board updates to the revision-safe API", async () => {
|
||||
const requests: Array<{ url: string; method: string; body: unknown }> = [];
|
||||
const originalFetch = globalThis.fetch;
|
||||
@@ -413,7 +478,11 @@ describe("distribution-board structure project command", () => {
|
||||
await updateDistributionBoard(
|
||||
"project-1",
|
||||
"board-1",
|
||||
{ floorId: "floor-1", supplyType: "USV" },
|
||||
{
|
||||
floorId: "floor-1",
|
||||
supplyType: "USV",
|
||||
simultaneityFactor: 0.8,
|
||||
},
|
||||
9
|
||||
);
|
||||
} finally {
|
||||
@@ -426,6 +495,7 @@ describe("distribution-board structure project command", () => {
|
||||
body: {
|
||||
floorId: "floor-1",
|
||||
supplyType: "USV",
|
||||
simultaneityFactor: 0.8,
|
||||
expectedRevision: 9,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -67,7 +67,11 @@ function createTestDatabase(): DatabaseContext {
|
||||
.run();
|
||||
context.db
|
||||
.update(distributionBoards)
|
||||
.set({ floorId: "floor-1", supplyType: "SV" })
|
||||
.set({
|
||||
floorId: "floor-1",
|
||||
supplyType: "SV",
|
||||
simultaneityFactor: 0.65,
|
||||
})
|
||||
.where(eq(distributionBoards.id, board.id))
|
||||
.run();
|
||||
context.db
|
||||
@@ -176,6 +180,10 @@ describe("project snapshot repository", () => {
|
||||
assert.equal(state.distributionBoards.length, 1);
|
||||
assert.equal(state.distributionBoards[0].floorId, "floor-1");
|
||||
assert.equal(state.distributionBoards[0].supplyType, "SV");
|
||||
assert.equal(
|
||||
state.distributionBoards[0].simultaneityFactor,
|
||||
0.65
|
||||
);
|
||||
assert.equal(state.circuitLists.length, 1);
|
||||
assert.equal(
|
||||
state.circuitSections.length,
|
||||
@@ -293,6 +301,7 @@ describe("project snapshot repository", () => {
|
||||
>) {
|
||||
delete board.floorId;
|
||||
delete board.supplyType;
|
||||
delete board.simultaneityFactor;
|
||||
}
|
||||
const legacyPayloadJson = JSON.stringify(currentPayload);
|
||||
context.db
|
||||
@@ -375,6 +384,10 @@ describe("portable project transfer", () => {
|
||||
copied.state.distributionBoards[0].supplyType,
|
||||
"SV"
|
||||
);
|
||||
assert.equal(
|
||||
copied.state.distributionBoards[0].simultaneityFactor,
|
||||
0.65
|
||||
);
|
||||
assert.equal(
|
||||
copied.state.circuits[0].deviceRows[0].legacyConsumerId,
|
||||
null
|
||||
@@ -431,6 +444,7 @@ describe("portable project transfer", () => {
|
||||
for (const board of legacyState.distributionBoards) {
|
||||
delete board.floorId;
|
||||
delete board.supplyType;
|
||||
delete board.simultaneityFactor;
|
||||
}
|
||||
const legacyPayloadJson = JSON.stringify(legacyState);
|
||||
const duplicate = repository.importDuplicate({
|
||||
@@ -448,6 +462,10 @@ describe("portable project transfer", () => {
|
||||
assert.ok(copied);
|
||||
assert.equal(copied.state.distributionBoards[0].floorId, null);
|
||||
assert.equal(copied.state.distributionBoards[0].supplyType, null);
|
||||
assert.equal(
|
||||
copied.state.distributionBoards[0].simultaneityFactor,
|
||||
1
|
||||
);
|
||||
} finally {
|
||||
context.close();
|
||||
}
|
||||
|
||||
@@ -103,6 +103,7 @@ describe("project state snapshot model", () => {
|
||||
...previous.distributionBoards[0],
|
||||
floorId: null,
|
||||
supplyType: null,
|
||||
simultaneityFactor: 1,
|
||||
},
|
||||
]);
|
||||
});
|
||||
@@ -158,6 +159,30 @@ describe("project state snapshot model", () => {
|
||||
assert.equal(snapshot.projectDevices[0]?.voltageV, 400);
|
||||
});
|
||||
|
||||
it("upgrades version-five distribution boards with a neutral factor", () => {
|
||||
const previous = {
|
||||
...minimalSnapshot(),
|
||||
schemaVersion: 5 as const,
|
||||
distributionBoards: [
|
||||
{
|
||||
id: "board-1",
|
||||
projectId: "project-1",
|
||||
name: "UV 1",
|
||||
floorId: null,
|
||||
supplyType: "AV" as const,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const snapshot = parseProjectStateSnapshot(previous);
|
||||
|
||||
assert.equal(snapshot.schemaVersion, projectStateSnapshotSchemaVersion);
|
||||
assert.equal(
|
||||
snapshot.distributionBoards[0]?.simultaneityFactor,
|
||||
1
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects duplicate ids and cross-project ownership", () => {
|
||||
assert.throws(
|
||||
() =>
|
||||
@@ -170,6 +195,7 @@ describe("project state snapshot model", () => {
|
||||
name: "UV 1",
|
||||
floorId: null,
|
||||
supplyType: null,
|
||||
simultaneityFactor: 1,
|
||||
},
|
||||
{
|
||||
id: "board-1",
|
||||
@@ -177,6 +203,7 @@ describe("project state snapshot model", () => {
|
||||
name: "UV 2",
|
||||
floorId: null,
|
||||
supplyType: null,
|
||||
simultaneityFactor: 1,
|
||||
},
|
||||
],
|
||||
}),
|
||||
@@ -211,6 +238,7 @@ describe("project state snapshot model", () => {
|
||||
name: "UV 1",
|
||||
floorId: "missing",
|
||||
supplyType: "AV",
|
||||
simultaneityFactor: 1,
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user