Define circuit group subtrees

This commit is contained in:
2026-07-30 20:06:09 +02:00
parent 3399fcd88b
commit db4c757018
6 changed files with 363 additions and 3 deletions
@@ -0,0 +1,169 @@
import type {
BreakerTripCharacteristic,
FuseUtilizationCategory,
ProtectionDeviceType,
RcdType,
} from "../../shared/constants/protection-device.js";
import { protectionDeviceConfigurationSchema } from "../../shared/validation/protection-device.schemas.js";
import {
assertCircuitGroupSnapshot,
type CircuitGroupSnapshot,
} from "./circuit-group-structure-project-command.model.js";
import {
assertCircuitInsertProjectCommand,
circuitInsertCommandType,
circuitStructureCommandSchemaVersion,
type CircuitSnapshot,
} from "./circuit-structure-project-command.model.js";
import {
assertDistributionBoardComponentSnapshot,
type DistributionBoardComponentSnapshot,
} from "./distribution-board-component-structure-project-command.model.js";
export interface CircuitProtectionDeviceSnapshot {
circuitId: string;
type: ProtectionDeviceType;
ratedCurrentA: number;
fuseUtilizationCategory: FuseUtilizationCategory | null;
tripCharacteristic: BreakerTripCharacteristic | null;
rcdType: RcdType | null;
ratedResidualCurrentMa: number | null;
}
export interface CircuitGroupSubtreeSnapshot {
group: CircuitGroupSnapshot;
components: DistributionBoardComponentSnapshot[];
circuits: Array<{
circuit: CircuitSnapshot;
protectionDevice: CircuitProtectionDeviceSnapshot | null;
}>;
}
export interface CircuitGroupDeletionSummary {
hasUpstreamProtection: boolean;
hasResidualCurrentProtection: boolean;
circuitCount: number;
deviceRowCount: number;
}
export function assertCircuitGroupSubtreeSnapshot(
value: unknown
): asserts value is CircuitGroupSubtreeSnapshot {
if (
!isPlainObject(value) ||
Object.keys(value).length !== 3 ||
!Array.isArray(value.components) ||
!Array.isArray(value.circuits)
) {
throw new Error("Circuit-group subtree snapshot is invalid.");
}
assertCircuitGroupSnapshot(value.group);
const group = value.group;
const entityIds = new Set<string>();
const equipmentIdentifiers = new Set<string>();
for (const component of value.components) {
assertDistributionBoardComponentSnapshot(component);
if (
component.component.circuitListId !== group.circuitListId ||
component.component.sectionId !== group.id ||
entityIds.has(component.component.id) ||
equipmentIdentifiers.has(component.component.equipmentIdentifier)
) {
throw new Error(
"Circuit-group component snapshot has invalid ownership or duplicates."
);
}
entityIds.add(component.component.id);
equipmentIdentifiers.add(component.component.equipmentIdentifier);
}
for (const entry of value.circuits) {
if (
!isPlainObject(entry) ||
Object.keys(entry).length !== 2 ||
!isPlainObject(entry.circuit)
) {
throw new Error("Circuit-group circuit snapshot is invalid.");
}
assertCircuitInsertProjectCommand({
schemaVersion: circuitStructureCommandSchemaVersion,
type: circuitInsertCommandType,
payload: { circuit: entry.circuit },
});
const circuit = entry.circuit as unknown as CircuitSnapshot;
if (
circuit.circuitListId !== group.circuitListId ||
circuit.sectionId !== group.id ||
entityIds.has(circuit.id) ||
equipmentIdentifiers.has(circuit.equipmentIdentifier)
) {
throw new Error(
"Circuit-group circuit snapshot has invalid ownership or duplicates."
);
}
entityIds.add(circuit.id);
equipmentIdentifiers.add(circuit.equipmentIdentifier);
if (entry.protectionDevice !== null) {
assertCircuitProtectionDeviceSnapshot(
entry.protectionDevice,
circuit.id
);
}
}
}
export function summarizeCircuitGroupDeletion(
snapshot: CircuitGroupSubtreeSnapshot
): CircuitGroupDeletionSummary {
assertCircuitGroupSubtreeSnapshot(snapshot);
return {
hasUpstreamProtection: snapshot.components.some(
({ component }) =>
component.role === "group_upstream_protection"
),
hasResidualCurrentProtection: snapshot.components.some(
({ component }) =>
component.role === "group_residual_current_protection"
),
circuitCount: snapshot.circuits.length,
deviceRowCount: snapshot.circuits.reduce(
(count, { circuit }) => count + circuit.deviceRows.length,
0
),
};
}
function assertCircuitProtectionDeviceSnapshot(
value: unknown,
circuitId: string
): asserts value is CircuitProtectionDeviceSnapshot {
if (
!isPlainObject(value) ||
Object.keys(value).length !== 7 ||
value.circuitId !== circuitId
) {
throw new Error("Circuit protection-device snapshot is invalid.");
}
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) {
throw new Error(
"Circuit protection-device configuration is invalid."
);
}
}
function isPlainObject(value: unknown): value is Record<string, unknown> {
return value !== null && typeof value === "object" && !Array.isArray(value);
}