forked from jappel/leistungsbilanz-ts
Circuits can now record that they deliberately have no protection device (type NONE, shown as "–"), separate from circuits whose protection is still open. Migration 0017 makes rated_current_a nullable. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
117 lines
4.1 KiB
TypeScript
117 lines
4.1 KiB
TypeScript
import { isValidCircuitProtectionRecord } from "../../shared/validation/protection-device.schemas.js";
|
|
import type {
|
|
BreakerTripCharacteristic,
|
|
CircuitProtectionDeviceType,
|
|
FuseUtilizationCategory,
|
|
RcdType,
|
|
} from "../../shared/constants/protection-device.js";
|
|
import type { SerializedProjectCommand } from "./project-command.model.js";
|
|
|
|
export const circuitProtectionUpdateCommandType =
|
|
"circuit-protection.update" as const;
|
|
export const circuitProtectionUpdateCommandSchemaVersion = 1 as const;
|
|
|
|
// type "NONE" records an explicit "no protection device" decision; all
|
|
// values are then null. A missing snapshot (null) means "not yet planned".
|
|
export interface CircuitProtectionSnapshot {
|
|
circuitId: string;
|
|
type: CircuitProtectionDeviceType;
|
|
ratedCurrentA: number | null;
|
|
fuseUtilizationCategory: FuseUtilizationCategory | null;
|
|
tripCharacteristic: BreakerTripCharacteristic | null;
|
|
rcdType: RcdType | null;
|
|
ratedResidualCurrentMa: number | null;
|
|
}
|
|
|
|
interface CircuitProtectionUpdatePayload {
|
|
circuitId: string;
|
|
expected: CircuitProtectionSnapshot | null;
|
|
target: CircuitProtectionSnapshot | null;
|
|
}
|
|
|
|
export interface CircuitProtectionUpdateProjectCommand
|
|
extends SerializedProjectCommand<CircuitProtectionUpdatePayload> {
|
|
schemaVersion: typeof circuitProtectionUpdateCommandSchemaVersion;
|
|
type: typeof circuitProtectionUpdateCommandType;
|
|
}
|
|
|
|
export function createCircuitProtectionUpdateProjectCommand(
|
|
circuitId: string,
|
|
expected: CircuitProtectionSnapshot | null,
|
|
target: CircuitProtectionSnapshot | null
|
|
): CircuitProtectionUpdateProjectCommand {
|
|
const command: CircuitProtectionUpdateProjectCommand = {
|
|
schemaVersion: circuitProtectionUpdateCommandSchemaVersion,
|
|
type: circuitProtectionUpdateCommandType,
|
|
payload: { circuitId, expected, target },
|
|
};
|
|
assertCircuitProtectionUpdateProjectCommand(command);
|
|
return command;
|
|
}
|
|
|
|
export function assertCircuitProtectionUpdateProjectCommand(
|
|
command: SerializedProjectCommand<unknown>
|
|
): asserts command is CircuitProtectionUpdateProjectCommand {
|
|
if (
|
|
command.schemaVersion !== circuitProtectionUpdateCommandSchemaVersion ||
|
|
command.type !== circuitProtectionUpdateCommandType ||
|
|
!isPlainObject(command.payload) ||
|
|
Object.keys(command.payload).length !== 3
|
|
) {
|
|
throw new Error("Unsupported circuit-protection update command.");
|
|
}
|
|
const { circuitId, expected, target } = command.payload;
|
|
if (typeof circuitId !== "string" || !circuitId.trim()) {
|
|
throw new Error("Circuit protection requires a circuit id.");
|
|
}
|
|
if (expected === null && target === null) {
|
|
throw new Error("Circuit protection update must change state.");
|
|
}
|
|
if (expected !== null) {
|
|
assertCircuitProtectionSnapshot(expected, circuitId);
|
|
}
|
|
if (target !== null) {
|
|
assertCircuitProtectionSnapshot(target, circuitId);
|
|
}
|
|
if (circuitProtectionSnapshotsEqual(expected, target)) {
|
|
throw new Error("Circuit protection update must change state.");
|
|
}
|
|
}
|
|
|
|
function circuitProtectionSnapshotsEqual(
|
|
left: CircuitProtectionSnapshot | null,
|
|
right: CircuitProtectionSnapshot | null
|
|
): boolean {
|
|
if (left === null || right === null) {
|
|
return left === right;
|
|
}
|
|
return (
|
|
left.circuitId === right.circuitId &&
|
|
left.type === right.type &&
|
|
left.ratedCurrentA === right.ratedCurrentA &&
|
|
left.fuseUtilizationCategory === right.fuseUtilizationCategory &&
|
|
left.tripCharacteristic === right.tripCharacteristic &&
|
|
left.rcdType === right.rcdType &&
|
|
left.ratedResidualCurrentMa === right.ratedResidualCurrentMa
|
|
);
|
|
}
|
|
|
|
export function assertCircuitProtectionSnapshot(
|
|
value: unknown,
|
|
circuitId: string
|
|
): asserts value is CircuitProtectionSnapshot {
|
|
if (
|
|
!isPlainObject(value) ||
|
|
Object.keys(value).length !== 7 ||
|
|
value.circuitId !== circuitId
|
|
) {
|
|
throw new Error("Circuit-protection snapshot is invalid.");
|
|
}
|
|
if (!isValidCircuitProtectionRecord(value)) {
|
|
throw new Error("Circuit-protection configuration is invalid.");
|
|
}
|
|
}
|
|
|
|
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
}
|