Include components in project snapshots
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
||||
legacyProjectStateSnapshotSchemaVersion,
|
||||
previousProjectStateSnapshotSchemaVersion,
|
||||
projectStateSnapshotSchemaVersion,
|
||||
simultaneityFactorProjectStateSnapshotSchemaVersion,
|
||||
supplyTypesProjectStateSnapshotSchemaVersion,
|
||||
voltageProjectStateSnapshotSchemaVersion,
|
||||
} from "../../domain/models/project-state-snapshot.model.js";
|
||||
@@ -152,6 +153,8 @@ export class ProjectSnapshotRepository implements ProjectSnapshotStore {
|
||||
supplyTypesProjectStateSnapshotSchemaVersion &&
|
||||
stored.schemaVersion !==
|
||||
voltageProjectStateSnapshotSchemaVersion &&
|
||||
stored.schemaVersion !==
|
||||
simultaneityFactorProjectStateSnapshotSchemaVersion &&
|
||||
stored.schemaVersion !== projectStateSnapshotSchemaVersion
|
||||
) {
|
||||
throw new Error("Project snapshot schema version is not supported.");
|
||||
|
||||
@@ -15,9 +15,12 @@ import type {
|
||||
import type { AppDatabase } from "../database-context.js";
|
||||
import { circuitDeviceRows } from "../schema/circuit-device-rows.js";
|
||||
import { circuitLists } from "../schema/circuit-lists.js";
|
||||
import { circuitProtectionDevices } from "../schema/circuit-protection-devices.js";
|
||||
import { circuitSections } from "../schema/circuit-sections.js";
|
||||
import { circuits } from "../schema/circuits.js";
|
||||
import { consumers } from "../schema/consumers.js";
|
||||
import { distributionBoardComponentProtectionDevices } from "../schema/distribution-board-component-protection-devices.js";
|
||||
import { distributionBoardComponents } from "../schema/distribution-board-components.js";
|
||||
import { distributionBoards } from "../schema/distribution-boards.js";
|
||||
import { floors } from "../schema/floors.js";
|
||||
import { legacyConsumerCircuitMigrations } from "../schema/legacy-consumer-circuit-migrations.js";
|
||||
@@ -141,6 +144,17 @@ function canonicalProjectState(state: ProjectStateSnapshot) {
|
||||
distributionBoards: byId(state.distributionBoards),
|
||||
circuitLists: byId(state.circuitLists),
|
||||
circuitSections: byId(state.circuitSections),
|
||||
distributionBoardComponents: byId(
|
||||
state.distributionBoardComponents
|
||||
),
|
||||
circuitProtectionDevices: byKey(
|
||||
state.circuitProtectionDevices,
|
||||
"circuitId"
|
||||
),
|
||||
distributionBoardComponentProtectionDevices: byKey(
|
||||
state.distributionBoardComponentProtectionDevices,
|
||||
"componentId"
|
||||
),
|
||||
circuits: byId(
|
||||
state.circuits.map((circuit) => ({
|
||||
...circuit,
|
||||
@@ -159,6 +173,15 @@ function byId<T extends { id: string }>(entries: readonly T[]) {
|
||||
);
|
||||
}
|
||||
|
||||
function byKey<
|
||||
TKey extends string,
|
||||
TEntry extends Record<TKey, string>,
|
||||
>(entries: readonly TEntry[], key: TKey) {
|
||||
return [...entries].sort((left, right) =>
|
||||
left[key].localeCompare(right[key])
|
||||
);
|
||||
}
|
||||
|
||||
function replaceProjectState(
|
||||
database: AppDatabase,
|
||||
state: ProjectStateSnapshot
|
||||
@@ -214,6 +237,21 @@ function replaceProjectState(
|
||||
isReserve: circuit.isReserve ? 1 : 0,
|
||||
}))
|
||||
);
|
||||
insertMany(
|
||||
database,
|
||||
distributionBoardComponents,
|
||||
state.distributionBoardComponents
|
||||
);
|
||||
insertMany(
|
||||
database,
|
||||
circuitProtectionDevices,
|
||||
state.circuitProtectionDevices
|
||||
);
|
||||
insertMany(
|
||||
database,
|
||||
distributionBoardComponentProtectionDevices,
|
||||
state.distributionBoardComponentProtectionDevices
|
||||
);
|
||||
insertMany(
|
||||
database,
|
||||
circuitDeviceRows,
|
||||
|
||||
@@ -9,8 +9,11 @@ import {
|
||||
import type { AppDatabase } from "../database-context.js";
|
||||
import { circuitDeviceRows } from "../schema/circuit-device-rows.js";
|
||||
import { circuitLists } from "../schema/circuit-lists.js";
|
||||
import { circuitProtectionDevices } from "../schema/circuit-protection-devices.js";
|
||||
import { circuitSections } from "../schema/circuit-sections.js";
|
||||
import { circuits } from "../schema/circuits.js";
|
||||
import { distributionBoardComponentProtectionDevices } from "../schema/distribution-board-component-protection-devices.js";
|
||||
import { distributionBoardComponents } from "../schema/distribution-board-components.js";
|
||||
import { distributionBoards } from "../schema/distribution-boards.js";
|
||||
import { floors } from "../schema/floors.js";
|
||||
import { projectDevices } from "../schema/project-devices.js";
|
||||
@@ -69,6 +72,8 @@ export function readProjectStateSnapshot(
|
||||
displayName: circuitSections.displayName,
|
||||
prefix: circuitSections.prefix,
|
||||
sortOrder: circuitSections.sortOrder,
|
||||
category: circuitSections.category,
|
||||
groupNumber: circuitSections.groupNumber,
|
||||
})
|
||||
.from(circuitSections)
|
||||
.innerJoin(
|
||||
@@ -82,6 +87,56 @@ export function readProjectStateSnapshot(
|
||||
asc(circuitSections.id)
|
||||
)
|
||||
.all();
|
||||
const componentRows = database
|
||||
.select({ component: distributionBoardComponents })
|
||||
.from(distributionBoardComponents)
|
||||
.innerJoin(
|
||||
circuitLists,
|
||||
eq(circuitLists.id, distributionBoardComponents.circuitListId)
|
||||
)
|
||||
.where(eq(circuitLists.projectId, projectId))
|
||||
.orderBy(
|
||||
asc(distributionBoardComponents.circuitListId),
|
||||
asc(distributionBoardComponents.placement),
|
||||
asc(distributionBoardComponents.sortOrder),
|
||||
asc(distributionBoardComponents.id)
|
||||
)
|
||||
.all()
|
||||
.map((row) => row.component);
|
||||
const circuitProtectionRows = database
|
||||
.select({ protection: circuitProtectionDevices })
|
||||
.from(circuitProtectionDevices)
|
||||
.innerJoin(
|
||||
circuits,
|
||||
eq(circuits.id, circuitProtectionDevices.circuitId)
|
||||
)
|
||||
.innerJoin(circuitLists, eq(circuitLists.id, circuits.circuitListId))
|
||||
.where(eq(circuitLists.projectId, projectId))
|
||||
.orderBy(asc(circuitProtectionDevices.circuitId))
|
||||
.all()
|
||||
.map((row) => row.protection);
|
||||
const componentProtectionRows = database
|
||||
.select({
|
||||
protection: distributionBoardComponentProtectionDevices,
|
||||
})
|
||||
.from(distributionBoardComponentProtectionDevices)
|
||||
.innerJoin(
|
||||
distributionBoardComponents,
|
||||
eq(
|
||||
distributionBoardComponents.id,
|
||||
distributionBoardComponentProtectionDevices.componentId
|
||||
)
|
||||
)
|
||||
.innerJoin(
|
||||
circuitLists,
|
||||
eq(circuitLists.id, distributionBoardComponents.circuitListId)
|
||||
)
|
||||
.where(eq(circuitLists.projectId, projectId))
|
||||
.orderBy(
|
||||
asc(distributionBoardComponentProtectionDevices.componentId)
|
||||
)
|
||||
.all()
|
||||
.map((row) => row.protection);
|
||||
const circuitRows = database
|
||||
.select({
|
||||
id: circuits.id,
|
||||
@@ -190,6 +245,10 @@ export function readProjectStateSnapshot(
|
||||
distributionBoards: boardRows,
|
||||
circuitLists: listRows,
|
||||
circuitSections: sectionRows,
|
||||
distributionBoardComponents: componentRows,
|
||||
circuitProtectionDevices: circuitProtectionRows,
|
||||
distributionBoardComponentProtectionDevices:
|
||||
componentProtectionRows,
|
||||
circuits: circuitRows.map((circuit) => ({
|
||||
...circuit,
|
||||
isReserve: Boolean(circuit.isReserve),
|
||||
|
||||
@@ -10,8 +10,11 @@ import type { ProjectTransferStore } from "../../domain/ports/project-transfer.s
|
||||
import type { AppDatabase } from "../database-context.js";
|
||||
import { circuitDeviceRows } from "../schema/circuit-device-rows.js";
|
||||
import { circuitLists } from "../schema/circuit-lists.js";
|
||||
import { circuitProtectionDevices } from "../schema/circuit-protection-devices.js";
|
||||
import { circuitSections } from "../schema/circuit-sections.js";
|
||||
import { circuits } from "../schema/circuits.js";
|
||||
import { distributionBoardComponentProtectionDevices } from "../schema/distribution-board-component-protection-devices.js";
|
||||
import { distributionBoardComponents } from "../schema/distribution-board-components.js";
|
||||
import { distributionBoards } from "../schema/distribution-boards.js";
|
||||
import { floors } from "../schema/floors.js";
|
||||
import { projectDevices } from "../schema/project-devices.js";
|
||||
@@ -127,6 +130,21 @@ function insertProjectState(
|
||||
isReserve: circuit.isReserve ? 1 : 0,
|
||||
}))
|
||||
);
|
||||
insertMany(
|
||||
database,
|
||||
distributionBoardComponents,
|
||||
state.distributionBoardComponents
|
||||
);
|
||||
insertMany(
|
||||
database,
|
||||
circuitProtectionDevices,
|
||||
state.circuitProtectionDevices
|
||||
);
|
||||
insertMany(
|
||||
database,
|
||||
distributionBoardComponentProtectionDevices,
|
||||
state.distributionBoardComponentProtectionDevices
|
||||
);
|
||||
insertMany(
|
||||
database,
|
||||
circuitDeviceRows,
|
||||
|
||||
@@ -3,6 +3,21 @@ import {
|
||||
defaultDistributionBoardSupplyTypes,
|
||||
distributionBoardSupplyTypes,
|
||||
} from "../../shared/constants/distribution-board.js";
|
||||
import {
|
||||
circuitGroupCategories,
|
||||
type CircuitGroupCategory,
|
||||
} from "../../shared/constants/circuit-group.js";
|
||||
import {
|
||||
distributionBoardComponentPlacements,
|
||||
distributionBoardComponentRoles,
|
||||
} from "../../shared/constants/distribution-board-component.js";
|
||||
import { protectionDeviceConfigurationSchema } from "../../shared/validation/protection-device.schemas.js";
|
||||
import {
|
||||
breakerTripCharacteristics,
|
||||
fuseUtilizationCategories,
|
||||
protectionDeviceTypes,
|
||||
rcdTypes,
|
||||
} from "../../shared/constants/protection-device.js";
|
||||
import {
|
||||
resolveCircuitPhaseType,
|
||||
resolveProjectVoltage,
|
||||
@@ -14,7 +29,8 @@ export const previousProjectStateSnapshotSchemaVersion = 2 as const;
|
||||
export const distributionBoardProjectStateSnapshotSchemaVersion = 3 as const;
|
||||
export const supplyTypesProjectStateSnapshotSchemaVersion = 4 as const;
|
||||
export const voltageProjectStateSnapshotSchemaVersion = 5 as const;
|
||||
export const projectStateSnapshotSchemaVersion = 6 as const;
|
||||
export const simultaneityFactorProjectStateSnapshotSchemaVersion = 6 as const;
|
||||
export const projectStateSnapshotSchemaVersion = 7 as const;
|
||||
|
||||
const idSchema = z.string().trim().min(1);
|
||||
const nullableStringSchema = z.string().nullable();
|
||||
@@ -73,7 +89,7 @@ const circuitListSchema = z
|
||||
})
|
||||
.strict();
|
||||
|
||||
const circuitSectionSchema = z
|
||||
const legacyCircuitSectionSchema = z
|
||||
.object({
|
||||
id: idSchema,
|
||||
circuitListId: idSchema,
|
||||
@@ -84,6 +100,22 @@ const circuitSectionSchema = z
|
||||
})
|
||||
.strict();
|
||||
|
||||
const circuitSectionSchema = legacyCircuitSectionSchema
|
||||
.extend({
|
||||
category: z.enum(circuitGroupCategories).nullable(),
|
||||
groupNumber: z.number().int().positive().nullable(),
|
||||
})
|
||||
.strict()
|
||||
.superRefine((section, context) => {
|
||||
if ((section.category === null) !== (section.groupNumber === null)) {
|
||||
context.addIssue({
|
||||
code: "custom",
|
||||
message:
|
||||
"Circuit-section category and group number must both be set or both be null.",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const circuitDeviceRowSchema = z
|
||||
.object({
|
||||
id: idSchema,
|
||||
@@ -173,21 +205,106 @@ const roomSchema = z
|
||||
})
|
||||
.strict();
|
||||
|
||||
const commonProjectStateSnapshotContents = {
|
||||
const distributionBoardComponentSchema = z
|
||||
.object({
|
||||
id: idSchema,
|
||||
circuitListId: idSchema,
|
||||
sectionId: idSchema.nullable(),
|
||||
equipmentIdentifier: z.string().trim().min(1),
|
||||
name: z.string().trim().min(1),
|
||||
role: z.enum(distributionBoardComponentRoles),
|
||||
placement: z.enum(distributionBoardComponentPlacements),
|
||||
sortOrder: finiteNumberSchema,
|
||||
})
|
||||
.strict();
|
||||
|
||||
const persistedProtectionDeviceFields = {
|
||||
type: z.enum(protectionDeviceTypes),
|
||||
ratedCurrentA: finiteNumberSchema.positive(),
|
||||
fuseUtilizationCategory: z.enum(fuseUtilizationCategories).nullable(),
|
||||
tripCharacteristic: z.enum(breakerTripCharacteristics).nullable(),
|
||||
rcdType: z.enum(rcdTypes).nullable(),
|
||||
ratedResidualCurrentMa: finiteNumberSchema.positive().nullable(),
|
||||
};
|
||||
|
||||
function validatePersistedProtectionDevice(
|
||||
value: {
|
||||
type: (typeof protectionDeviceTypes)[number];
|
||||
ratedCurrentA: number;
|
||||
fuseUtilizationCategory:
|
||||
| (typeof fuseUtilizationCategories)[number]
|
||||
| null;
|
||||
tripCharacteristic:
|
||||
| (typeof breakerTripCharacteristics)[number]
|
||||
| null;
|
||||
rcdType: (typeof rcdTypes)[number] | null;
|
||||
ratedResidualCurrentMa: number | null;
|
||||
},
|
||||
context: z.RefinementCtx
|
||||
) {
|
||||
const result = protectionDeviceConfigurationSchema.safeParse({
|
||||
type: value.type,
|
||||
ratedCurrentA: value.ratedCurrentA,
|
||||
...(value.fuseUtilizationCategory === null
|
||||
? {}
|
||||
: { fuseUtilizationCategory: value.fuseUtilizationCategory }),
|
||||
...(value.tripCharacteristic === null
|
||||
? {}
|
||||
: { tripCharacteristic: value.tripCharacteristic }),
|
||||
...(value.rcdType === null ? {} : { rcdType: value.rcdType }),
|
||||
...(value.ratedResidualCurrentMa === null
|
||||
? {}
|
||||
: { ratedResidualCurrentMa: value.ratedResidualCurrentMa }),
|
||||
});
|
||||
if (!result.success) {
|
||||
context.addIssue({
|
||||
code: "custom",
|
||||
message: "Snapshot protection-device configuration is invalid.",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const circuitProtectionDeviceSchema = z
|
||||
.object({
|
||||
circuitId: idSchema,
|
||||
...persistedProtectionDeviceFields,
|
||||
})
|
||||
.strict()
|
||||
.superRefine(validatePersistedProtectionDevice);
|
||||
|
||||
const componentProtectionDeviceSchema = z
|
||||
.object({
|
||||
componentId: idSchema,
|
||||
...persistedProtectionDeviceFields,
|
||||
})
|
||||
.strict()
|
||||
.superRefine(validatePersistedProtectionDevice);
|
||||
|
||||
const legacyProjectStateSnapshotContents = {
|
||||
circuitLists: z.array(circuitListSchema),
|
||||
circuitSections: z.array(circuitSectionSchema),
|
||||
circuitSections: z.array(legacyCircuitSectionSchema),
|
||||
circuits: z.array(circuitSchema),
|
||||
projectDevices: z.array(projectDeviceSchema),
|
||||
floors: z.array(floorSchema),
|
||||
rooms: z.array(roomSchema),
|
||||
};
|
||||
|
||||
const currentProjectStateSnapshotContents = {
|
||||
...legacyProjectStateSnapshotContents,
|
||||
circuitSections: z.array(circuitSectionSchema),
|
||||
distributionBoardComponents: z.array(distributionBoardComponentSchema),
|
||||
circuitProtectionDevices: z.array(circuitProtectionDeviceSchema),
|
||||
distributionBoardComponentProtectionDevices: z.array(
|
||||
componentProtectionDeviceSchema
|
||||
),
|
||||
};
|
||||
|
||||
const legacyProjectStateSnapshotSchema = z
|
||||
.object({
|
||||
schemaVersion: z.literal(legacyProjectStateSnapshotSchemaVersion),
|
||||
project: legacyProjectSchema,
|
||||
distributionBoards: z.array(legacyDistributionBoardSchema),
|
||||
...commonProjectStateSnapshotContents,
|
||||
...legacyProjectStateSnapshotContents,
|
||||
})
|
||||
.strict();
|
||||
|
||||
@@ -196,7 +313,7 @@ const previousProjectStateSnapshotSchema = z
|
||||
schemaVersion: z.literal(previousProjectStateSnapshotSchemaVersion),
|
||||
project: projectSchema,
|
||||
distributionBoards: z.array(legacyDistributionBoardSchema),
|
||||
...commonProjectStateSnapshotContents,
|
||||
...legacyProjectStateSnapshotContents,
|
||||
})
|
||||
.strict();
|
||||
|
||||
@@ -207,7 +324,7 @@ const distributionBoardProjectStateSnapshotSchema = z
|
||||
),
|
||||
project: projectSchema,
|
||||
distributionBoards: z.array(distributionBoardSchema),
|
||||
...commonProjectStateSnapshotContents,
|
||||
...legacyProjectStateSnapshotContents,
|
||||
})
|
||||
.strict();
|
||||
|
||||
@@ -216,7 +333,7 @@ const supplyTypesProjectStateSnapshotSchema = z
|
||||
schemaVersion: z.literal(supplyTypesProjectStateSnapshotSchemaVersion),
|
||||
project: currentProjectSchema,
|
||||
distributionBoards: z.array(distributionBoardSchema),
|
||||
...commonProjectStateSnapshotContents,
|
||||
...legacyProjectStateSnapshotContents,
|
||||
})
|
||||
.strict();
|
||||
|
||||
@@ -227,12 +344,20 @@ const voltageProjectStateSnapshotSchema =
|
||||
),
|
||||
});
|
||||
|
||||
export const projectStateSnapshotSchema =
|
||||
const simultaneityFactorProjectStateSnapshotSchema =
|
||||
voltageProjectStateSnapshotSchema.extend({
|
||||
schemaVersion: z.literal(projectStateSnapshotSchemaVersion),
|
||||
schemaVersion: z.literal(
|
||||
simultaneityFactorProjectStateSnapshotSchemaVersion
|
||||
),
|
||||
distributionBoards: z.array(currentDistributionBoardSchema),
|
||||
});
|
||||
|
||||
export const projectStateSnapshotSchema =
|
||||
simultaneityFactorProjectStateSnapshotSchema.extend({
|
||||
schemaVersion: z.literal(projectStateSnapshotSchemaVersion),
|
||||
...currentProjectStateSnapshotContents,
|
||||
});
|
||||
|
||||
export type ProjectStateSnapshot = z.infer<
|
||||
typeof projectStateSnapshotSchema
|
||||
>;
|
||||
@@ -243,46 +368,65 @@ export function parseProjectStateSnapshot(
|
||||
const version = isPlainObject(value) ? value.schemaVersion : undefined;
|
||||
const parsedSnapshot =
|
||||
version === legacyProjectStateSnapshotSchemaVersion
|
||||
? upgradeVoltageProjectStateSnapshot(
|
||||
upgradeSupplyTypesProjectStateSnapshot(
|
||||
upgradeDistributionBoardProjectStateSnapshot(
|
||||
upgradePreviousProjectStateSnapshot(
|
||||
upgradeLegacyProjectStateSnapshot(
|
||||
legacyProjectStateSnapshotSchema.parse(value)
|
||||
? upgradeSimultaneityFactorProjectStateSnapshot(
|
||||
upgradeVoltageProjectStateSnapshot(
|
||||
upgradeSupplyTypesProjectStateSnapshot(
|
||||
upgradeDistributionBoardProjectStateSnapshot(
|
||||
upgradePreviousProjectStateSnapshot(
|
||||
upgradeLegacyProjectStateSnapshot(
|
||||
legacyProjectStateSnapshotSchema.parse(value)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
: version === previousProjectStateSnapshotSchemaVersion
|
||||
? upgradeVoltageProjectStateSnapshot(
|
||||
upgradeSupplyTypesProjectStateSnapshot(
|
||||
upgradeDistributionBoardProjectStateSnapshot(
|
||||
upgradePreviousProjectStateSnapshot(
|
||||
previousProjectStateSnapshotSchema.parse(value)
|
||||
? upgradeSimultaneityFactorProjectStateSnapshot(
|
||||
upgradeVoltageProjectStateSnapshot(
|
||||
upgradeSupplyTypesProjectStateSnapshot(
|
||||
upgradeDistributionBoardProjectStateSnapshot(
|
||||
upgradePreviousProjectStateSnapshot(
|
||||
previousProjectStateSnapshotSchema.parse(value)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
: version === distributionBoardProjectStateSnapshotSchemaVersion
|
||||
? upgradeVoltageProjectStateSnapshot(
|
||||
upgradeSupplyTypesProjectStateSnapshot(
|
||||
upgradeDistributionBoardProjectStateSnapshot(
|
||||
distributionBoardProjectStateSnapshotSchema.parse(value)
|
||||
? upgradeSimultaneityFactorProjectStateSnapshot(
|
||||
upgradeVoltageProjectStateSnapshot(
|
||||
upgradeSupplyTypesProjectStateSnapshot(
|
||||
upgradeDistributionBoardProjectStateSnapshot(
|
||||
distributionBoardProjectStateSnapshotSchema.parse(
|
||||
value
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
: version === supplyTypesProjectStateSnapshotSchemaVersion
|
||||
? upgradeVoltageProjectStateSnapshot(
|
||||
upgradeSupplyTypesProjectStateSnapshot(
|
||||
supplyTypesProjectStateSnapshotSchema.parse(value)
|
||||
? upgradeSimultaneityFactorProjectStateSnapshot(
|
||||
upgradeVoltageProjectStateSnapshot(
|
||||
upgradeSupplyTypesProjectStateSnapshot(
|
||||
supplyTypesProjectStateSnapshotSchema.parse(value)
|
||||
)
|
||||
)
|
||||
)
|
||||
: version === voltageProjectStateSnapshotSchemaVersion
|
||||
? upgradeVoltageProjectStateSnapshot(
|
||||
voltageProjectStateSnapshotSchema.parse(value)
|
||||
? upgradeSimultaneityFactorProjectStateSnapshot(
|
||||
upgradeVoltageProjectStateSnapshot(
|
||||
voltageProjectStateSnapshotSchema.parse(value)
|
||||
)
|
||||
)
|
||||
: projectStateSnapshotSchema.parse(value);
|
||||
: version ===
|
||||
simultaneityFactorProjectStateSnapshotSchemaVersion
|
||||
? upgradeSimultaneityFactorProjectStateSnapshot(
|
||||
simultaneityFactorProjectStateSnapshotSchema.parse(
|
||||
value
|
||||
)
|
||||
)
|
||||
: projectStateSnapshotSchema.parse(value);
|
||||
const snapshot = normalizeSnapshotPhaseTypes(parsedSnapshot);
|
||||
assertProjectStateSnapshotRelations(snapshot);
|
||||
return snapshot;
|
||||
@@ -412,10 +556,10 @@ function upgradeSupplyTypesProjectStateSnapshot(
|
||||
|
||||
function upgradeVoltageProjectStateSnapshot(
|
||||
snapshot: z.infer<typeof voltageProjectStateSnapshotSchema>
|
||||
): ProjectStateSnapshot {
|
||||
): z.infer<typeof simultaneityFactorProjectStateSnapshotSchema> {
|
||||
return {
|
||||
...snapshot,
|
||||
schemaVersion: projectStateSnapshotSchemaVersion,
|
||||
schemaVersion: simultaneityFactorProjectStateSnapshotSchemaVersion,
|
||||
distributionBoards: snapshot.distributionBoards.map((board) => ({
|
||||
...board,
|
||||
simultaneityFactor: 1,
|
||||
@@ -423,6 +567,39 @@ function upgradeVoltageProjectStateSnapshot(
|
||||
};
|
||||
}
|
||||
|
||||
function upgradeSimultaneityFactorProjectStateSnapshot(
|
||||
snapshot: z.infer<typeof simultaneityFactorProjectStateSnapshotSchema>
|
||||
): ProjectStateSnapshot {
|
||||
return {
|
||||
...snapshot,
|
||||
schemaVersion: projectStateSnapshotSchemaVersion,
|
||||
circuitSections: snapshot.circuitSections.map((section) => {
|
||||
const category = initialCategoryBySectionKey(section.key);
|
||||
return {
|
||||
...section,
|
||||
category,
|
||||
groupNumber: category === null ? null : 1,
|
||||
};
|
||||
}),
|
||||
distributionBoardComponents: [],
|
||||
circuitProtectionDevices: [],
|
||||
distributionBoardComponentProtectionDevices: [],
|
||||
};
|
||||
}
|
||||
|
||||
function initialCategoryBySectionKey(
|
||||
key: string
|
||||
): CircuitGroupCategory | null {
|
||||
if (
|
||||
key === "lighting" ||
|
||||
key === "single_phase" ||
|
||||
key === "three_phase"
|
||||
) {
|
||||
return key;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
||||
return value !== null && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
@@ -461,10 +638,22 @@ function assertProjectStateSnapshotRelations(
|
||||
);
|
||||
const floorIds = uniqueIds(snapshot.floors, "floor");
|
||||
const roomIds = uniqueIds(snapshot.rooms, "room");
|
||||
uniqueIds(snapshot.circuits, "circuit");
|
||||
const circuitIds = uniqueIds(snapshot.circuits, "circuit");
|
||||
const componentIds = uniqueIds(
|
||||
snapshot.distributionBoardComponents,
|
||||
"distribution board component"
|
||||
);
|
||||
const sectionById = new Map(
|
||||
snapshot.circuitSections.map((entry) => [entry.id, entry])
|
||||
);
|
||||
const componentById = new Map(
|
||||
snapshot.distributionBoardComponents.map((entry) => [
|
||||
entry.id,
|
||||
entry,
|
||||
])
|
||||
);
|
||||
const groupKeys = new Set<string>();
|
||||
const equipmentIdentifiersByList = new Map<string, Set<string>>();
|
||||
|
||||
for (const board of snapshot.distributionBoards) {
|
||||
assertProjectOwnership(board.projectId, projectId, "distribution board");
|
||||
@@ -500,6 +689,19 @@ function assertProjectStateSnapshotRelations(
|
||||
section.circuitListId,
|
||||
"circuit section list"
|
||||
);
|
||||
if (section.category !== null && section.groupNumber !== null) {
|
||||
const groupKey = [
|
||||
section.circuitListId,
|
||||
section.category,
|
||||
section.groupNumber,
|
||||
].join(":");
|
||||
if (groupKeys.has(groupKey)) {
|
||||
throw new Error(
|
||||
"Snapshot contains a duplicate circuit group number."
|
||||
);
|
||||
}
|
||||
groupKeys.add(groupKey);
|
||||
}
|
||||
}
|
||||
for (const device of snapshot.projectDevices) {
|
||||
assertProjectOwnership(device.projectId, projectId, "project device");
|
||||
@@ -553,6 +755,11 @@ function assertProjectStateSnapshotRelations(
|
||||
"Snapshot circuit voltage must match project settings."
|
||||
);
|
||||
}
|
||||
registerEquipmentIdentifier(
|
||||
equipmentIdentifiersByList,
|
||||
circuit.circuitListId,
|
||||
circuit.equipmentIdentifier
|
||||
);
|
||||
for (const row of circuit.deviceRows) {
|
||||
if (row.circuitId !== circuit.id) {
|
||||
throw new Error(
|
||||
@@ -575,6 +782,148 @@ function assertProjectStateSnapshotRelations(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const fixedComponentRolesByList = new Set<string>();
|
||||
const groupComponentRolesBySection = new Set<string>();
|
||||
for (const component of snapshot.distributionBoardComponents) {
|
||||
assertReference(
|
||||
circuitListIds,
|
||||
component.circuitListId,
|
||||
"distribution board component circuit list"
|
||||
);
|
||||
if (component.sectionId !== null) {
|
||||
assertReference(
|
||||
sectionIds,
|
||||
component.sectionId,
|
||||
"distribution board component section"
|
||||
);
|
||||
if (
|
||||
sectionById.get(component.sectionId)?.circuitListId !==
|
||||
component.circuitListId
|
||||
) {
|
||||
throw new Error(
|
||||
"Snapshot distribution board component section belongs to a different circuit list."
|
||||
);
|
||||
}
|
||||
}
|
||||
assertComponentPlacement(component);
|
||||
registerEquipmentIdentifier(
|
||||
equipmentIdentifiersByList,
|
||||
component.circuitListId,
|
||||
component.equipmentIdentifier
|
||||
);
|
||||
if (
|
||||
component.role === "main_switch" ||
|
||||
component.role === "surge_protective_device"
|
||||
) {
|
||||
assertUniqueKey(
|
||||
fixedComponentRolesByList,
|
||||
`${component.circuitListId}:${component.role}`,
|
||||
"Snapshot contains duplicate fixed distribution board components."
|
||||
);
|
||||
}
|
||||
if (
|
||||
component.role === "group_upstream_protection" ||
|
||||
component.role === "group_residual_current_protection"
|
||||
) {
|
||||
assertUniqueKey(
|
||||
groupComponentRolesBySection,
|
||||
`${component.sectionId}:${component.role}`,
|
||||
"Snapshot contains duplicate group protection components."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const protectedCircuitIds = new Set<string>();
|
||||
for (const protection of snapshot.circuitProtectionDevices) {
|
||||
assertReference(
|
||||
circuitIds,
|
||||
protection.circuitId,
|
||||
"circuit protection device circuit"
|
||||
);
|
||||
assertUniqueKey(
|
||||
protectedCircuitIds,
|
||||
protection.circuitId,
|
||||
"Snapshot contains duplicate circuit protection devices."
|
||||
);
|
||||
}
|
||||
|
||||
const protectedComponentIds = new Set<string>();
|
||||
for (const protection of snapshot.distributionBoardComponentProtectionDevices) {
|
||||
assertReference(
|
||||
componentIds,
|
||||
protection.componentId,
|
||||
"component protection device component"
|
||||
);
|
||||
assertUniqueKey(
|
||||
protectedComponentIds,
|
||||
protection.componentId,
|
||||
"Snapshot contains duplicate component protection devices."
|
||||
);
|
||||
const component = componentById.get(protection.componentId)!;
|
||||
if (
|
||||
component.role !== "group_upstream_protection" &&
|
||||
component.role !== "group_residual_current_protection"
|
||||
) {
|
||||
throw new Error(
|
||||
"Snapshot protection data belongs to a non-protection component."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function assertComponentPlacement(
|
||||
component: ProjectStateSnapshot["distributionBoardComponents"][number]
|
||||
) {
|
||||
const isFixedHeader =
|
||||
component.role === "main_switch" ||
|
||||
component.role === "surge_protective_device";
|
||||
const isGroupProtection =
|
||||
component.role === "group_upstream_protection" ||
|
||||
component.role === "group_residual_current_protection";
|
||||
if (
|
||||
(isFixedHeader &&
|
||||
(component.placement !== "header" ||
|
||||
component.sectionId !== null)) ||
|
||||
(isGroupProtection &&
|
||||
(component.placement !== "group" ||
|
||||
component.sectionId === null)) ||
|
||||
(component.role === "auxiliary" &&
|
||||
(component.placement !== "footer" ||
|
||||
component.sectionId !== null))
|
||||
) {
|
||||
throw new Error(
|
||||
"Snapshot distribution board component placement is invalid."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function registerEquipmentIdentifier(
|
||||
identifiersByList: Map<string, Set<string>>,
|
||||
circuitListId: string,
|
||||
equipmentIdentifier: string
|
||||
) {
|
||||
const identifiers =
|
||||
identifiersByList.get(circuitListId) ?? new Set<string>();
|
||||
const normalized = equipmentIdentifier.trim().toLocaleLowerCase("de");
|
||||
if (identifiers.has(normalized)) {
|
||||
throw new Error(
|
||||
"Snapshot contains duplicate equipment identifiers in one circuit list."
|
||||
);
|
||||
}
|
||||
identifiers.add(normalized);
|
||||
identifiersByList.set(circuitListId, identifiers);
|
||||
}
|
||||
|
||||
function assertUniqueKey(
|
||||
keys: Set<string>,
|
||||
key: string,
|
||||
message: string
|
||||
) {
|
||||
if (keys.has(key)) {
|
||||
throw new Error(message);
|
||||
}
|
||||
keys.add(key);
|
||||
}
|
||||
|
||||
function uniqueIds(
|
||||
|
||||
@@ -79,6 +79,10 @@ export function remapProjectState(
|
||||
const listIds = idMap(source.circuitLists, createId);
|
||||
const sectionIds = idMap(source.circuitSections, createId);
|
||||
const circuitIds = idMap(source.circuits, createId);
|
||||
const componentIds = idMap(
|
||||
source.distributionBoardComponents,
|
||||
createId
|
||||
);
|
||||
const deviceIds = idMap(source.projectDevices, createId);
|
||||
const floorIds = idMap(source.floors, createId);
|
||||
const roomIds = idMap(source.rooms, createId);
|
||||
@@ -110,6 +114,35 @@ export function remapProjectState(
|
||||
id: requiredId(sectionIds, section.id),
|
||||
circuitListId: requiredId(listIds, section.circuitListId),
|
||||
})),
|
||||
distributionBoardComponents:
|
||||
source.distributionBoardComponents.map((component) => ({
|
||||
...component,
|
||||
id: requiredId(componentIds, component.id),
|
||||
circuitListId: requiredId(
|
||||
listIds,
|
||||
component.circuitListId
|
||||
),
|
||||
sectionId:
|
||||
component.sectionId === null
|
||||
? null
|
||||
: requiredId(sectionIds, component.sectionId),
|
||||
})),
|
||||
circuitProtectionDevices: source.circuitProtectionDevices.map(
|
||||
(protection) => ({
|
||||
...protection,
|
||||
circuitId: requiredId(circuitIds, protection.circuitId),
|
||||
})
|
||||
),
|
||||
distributionBoardComponentProtectionDevices:
|
||||
source.distributionBoardComponentProtectionDevices.map(
|
||||
(protection) => ({
|
||||
...protection,
|
||||
componentId: requiredId(
|
||||
componentIds,
|
||||
protection.componentId
|
||||
),
|
||||
})
|
||||
),
|
||||
circuits: source.circuits.map((circuit) => ({
|
||||
...circuit,
|
||||
id: requiredId(circuitIds, circuit.id),
|
||||
|
||||
Reference in New Issue
Block a user