Add logical distribution board feed links

This commit is contained in:
Julian Appel 2026-09-27 22:21:58 +02:00
parent b50c00c0ac
commit b893c72534
23 changed files with 3732 additions and 45 deletions

View file

@ -35,7 +35,8 @@ import {
type ExternalModelStateSnapshot,
} from "../../external-model/domain/external-model-contracts.js";
export const projectStateSnapshotSchemaVersion = 8 as const;
export const projectStateSnapshotSchemaVersion = 9 as const;
const roomAreaProjectStateSnapshotSchemaVersion = 8 as const;
const buildingProjectStateSnapshotSchemaVersion = 7 as const;
const coverProjectStateSnapshotSchemaVersion = 6 as const;
const boardInfoProjectStateSnapshotSchemaVersion = 5 as const;
@ -112,7 +113,7 @@ const distributionBoardInfoValueSchema = (field: DistributionBoardInfoField) =>
.max(distributionBoardInfoFieldMaxLengths[field])
.nullable();
const distributionBoardSchema = legacyDistributionBoardSchema
const distributionBoardWithoutFeedingSchema = legacyDistributionBoardSchema
.extend({
boardNumber: distributionBoardInfoValueSchema("boardNumber"),
location: distributionBoardInfoValueSchema("location"),
@ -121,6 +122,13 @@ const distributionBoardSchema = legacyDistributionBoardSchema
})
.strict();
const distributionBoardSchema = distributionBoardWithoutFeedingSchema
.extend({
feedingDistributionBoardId: idSchema.nullable(),
feedingCircuitId: idSchema.nullable(),
})
.strict();
function withEmptyDistributionBoardInfo(
boards: Array<z.infer<typeof legacyDistributionBoardSchema>>
) {
@ -133,6 +141,16 @@ function withEmptyDistributionBoardInfo(
}));
}
function withEmptyDistributionBoardFeeding(
boards: Array<z.infer<typeof distributionBoardWithoutFeedingSchema>>
) {
return boards.map((board) => ({
...board,
feedingDistributionBoardId: null,
feedingCircuitId: null,
}));
}
const circuitListSchema = z
.object({
id: idSchema,
@ -553,11 +571,22 @@ export const projectStateSnapshotSchema = z
})
.strict();
const roomAreaProjectStateSnapshotSchema = z
.object({
schemaVersion: z.literal(roomAreaProjectStateSnapshotSchemaVersion),
project: projectSchema,
distributionBoards: z.array(distributionBoardWithoutFeedingSchema),
externalCsvConfiguration: externalCsvConfigurationSnapshotSchema.nullable(),
externalModel: externalModelStateSchema,
...projectStateSnapshotContents,
})
.strict();
const buildingProjectStateSnapshotSchema = z
.object({
schemaVersion: z.literal(buildingProjectStateSnapshotSchemaVersion),
project: projectSchema,
distributionBoards: z.array(distributionBoardSchema),
distributionBoards: z.array(distributionBoardWithoutFeedingSchema),
externalCsvConfiguration: externalCsvConfigurationSnapshotSchema.nullable(),
externalModel: externalModelStateSchema,
...buildingProjectStateSnapshotContents,
@ -568,7 +597,7 @@ const coverProjectStateSnapshotSchema = z
.object({
schemaVersion: z.literal(coverProjectStateSnapshotSchemaVersion),
project: projectSchema,
distributionBoards: z.array(distributionBoardSchema),
distributionBoards: z.array(distributionBoardWithoutFeedingSchema),
externalCsvConfiguration: externalCsvConfigurationSnapshotSchema.nullable(),
externalModel: externalModelStateSchema,
...previousProjectStateSnapshotContents,
@ -655,6 +684,27 @@ export function parseProjectStateSnapshot(
}
function upgradePreviousProjectStateSnapshot(value: unknown): unknown {
const roomArea = upgradeToRoomAreaProjectStateSnapshot(value);
if (
roomArea !== null &&
typeof roomArea === "object" &&
!Array.isArray(roomArea) &&
(roomArea as { schemaVersion?: unknown }).schemaVersion ===
roomAreaProjectStateSnapshotSchemaVersion
) {
const previous = roomAreaProjectStateSnapshotSchema.parse(roomArea);
return {
...previous,
schemaVersion: projectStateSnapshotSchemaVersion,
distributionBoards: withEmptyDistributionBoardFeeding(
previous.distributionBoards
),
};
}
return roomArea;
}
function upgradeToRoomAreaProjectStateSnapshot(value: unknown): unknown {
const cover = upgradeToCoverProjectStateSnapshot(value);
if (
cover !== null &&
@ -666,7 +716,7 @@ function upgradePreviousProjectStateSnapshot(value: unknown): unknown {
const previous = buildingProjectStateSnapshotSchema.parse(cover);
return {
...previous,
schemaVersion: projectStateSnapshotSchemaVersion,
schemaVersion: roomAreaProjectStateSnapshotSchemaVersion,
rooms: previous.rooms.map((room) => ({ ...room, areaM2: null })),
};
}
@ -683,7 +733,7 @@ function upgradePreviousProjectStateSnapshot(value: unknown): unknown {
// building. Build version 7 first so the room-area upgrade above remains a
// single exact transition.
const previous = coverProjectStateSnapshotSchema.parse(cover);
return upgradePreviousProjectStateSnapshot({
return upgradeToRoomAreaProjectStateSnapshot({
...previous,
schemaVersion: buildingProjectStateSnapshotSchemaVersion,
buildings: [],
@ -873,6 +923,15 @@ function assertProjectStateSnapshotRelations(
const sectionById = new Map(
snapshot.circuitSections.map((entry) => [entry.id, entry])
);
const boardById = new Map(
snapshot.distributionBoards.map((entry) => [entry.id, entry])
);
const circuitListById = new Map(
snapshot.circuitLists.map((entry) => [entry.id, entry])
);
const circuitById = new Map(
snapshot.circuits.map((entry) => [entry.id, entry])
);
const componentById = new Map(
snapshot.distributionBoardComponents.map((entry) => [
entry.id,
@ -901,6 +960,47 @@ function assertProjectStateSnapshotRelations(
"Snapshot distribution board uses a disabled supply type."
);
}
if (board.feedingDistributionBoardId === board.id) {
throw new Error("Snapshot distribution board cannot feed itself.");
}
if (board.feedingDistributionBoardId !== null) {
assertReference(
boardIds,
board.feedingDistributionBoardId,
"feeding distribution board"
);
}
if (board.feedingCircuitId !== null) {
if (board.feedingDistributionBoardId === null) {
throw new Error(
"Snapshot feeding circuit requires a feeding distribution board."
);
}
assertReference(circuitIds, board.feedingCircuitId, "feeding circuit");
const feedingCircuit = circuitById.get(board.feedingCircuitId);
const feedingList = feedingCircuit
? circuitListById.get(feedingCircuit.circuitListId)
: undefined;
if (
!feedingList ||
feedingList.distributionBoardId !== board.feedingDistributionBoardId
) {
throw new Error(
"Snapshot feeding circuit belongs to another distribution board."
);
}
}
}
for (const board of snapshot.distributionBoards) {
const visited = new Set<string>([board.id]);
let currentId = board.feedingDistributionBoardId;
while (currentId !== null) {
if (visited.has(currentId)) {
throw new Error("Snapshot distribution-board feeding graph contains a cycle.");
}
visited.add(currentId);
currentId = boardById.get(currentId)?.feedingDistributionBoardId ?? null;
}
}
for (const list of snapshot.circuitLists) {
assertProjectOwnership(list.projectId, projectId, "circuit list");