Plan circuit group renumbering

This commit is contained in:
2026-07-30 19:51:44 +02:00
parent e702712bd8
commit 63650a623c
5 changed files with 351 additions and 1 deletions
@@ -0,0 +1,193 @@
import type { CircuitGroupCategory } from "../../shared/constants/circuit-group.js";
import {
formatCircuitGroupPrefix,
formatGroupRcdIdentifier,
formatGroupedCircuitIdentifier,
formatGroupUpstreamProtectionIdentifier,
parseGroupedEquipmentIdentifier,
} from "./circuit-group-numbering.js";
export interface RenumberableCircuitGroup {
id: string;
category: CircuitGroupCategory;
groupNumber: number;
prefix: string;
circuits: Array<{
id: string;
equipmentIdentifier: string;
}>;
components: Array<{
id: string;
role:
| "group_upstream_protection"
| "group_residual_current_protection";
equipmentIdentifier: string;
}>;
}
export interface CircuitGroupRenumberRequest {
groupId: string;
targetGroupNumber: number;
}
export interface CircuitGroupRenumberPlan {
groups: Array<{
groupId: string;
category: CircuitGroupCategory;
expectedGroupNumber: number;
targetGroupNumber: number;
expectedPrefix: string;
targetPrefix: string;
circuits: Array<{
circuitId: string;
expectedEquipmentIdentifier: string;
targetEquipmentIdentifier: string;
}>;
components: Array<{
componentId: string;
expectedEquipmentIdentifier: string;
targetEquipmentIdentifier: string;
}>;
}>;
}
export function createCircuitGroupRenumberPlan(
groups: RenumberableCircuitGroup[],
requests: CircuitGroupRenumberRequest[]
): CircuitGroupRenumberPlan {
if (requests.length === 0) {
throw new Error("Circuit-group renumbering requires requests.");
}
const groupsById = new Map(groups.map((group) => [group.id, group]));
if (groupsById.size !== groups.length) {
throw new Error("Circuit-group renumbering contains duplicate group ids.");
}
const requestGroupIds = new Set<string>();
const targetKeys = new Set<string>();
const selected = requests.map((request) => {
const group = groupsById.get(request.groupId);
if (
!group ||
requestGroupIds.has(request.groupId) ||
!Number.isInteger(request.targetGroupNumber) ||
request.targetGroupNumber < 1
) {
throw new Error("Circuit-group renumber request is invalid.");
}
const targetKey = `${group.category}:${request.targetGroupNumber}`;
if (targetKeys.has(targetKey)) {
throw new Error(
"Circuit-group renumbering contains duplicate target numbers."
);
}
requestGroupIds.add(request.groupId);
targetKeys.add(targetKey);
return { group, targetGroupNumber: request.targetGroupNumber };
});
if (
selected.every(
({ group, targetGroupNumber }) =>
group.groupNumber === targetGroupNumber
)
) {
throw new Error(
"Circuit-group renumbering must change at least one number."
);
}
for (const group of groups) {
if (
!requestGroupIds.has(group.id) &&
targetKeys.has(`${group.category}:${group.groupNumber}`)
) {
throw new Error(
"Target group number belongs to an unchanged group."
);
}
}
return {
groups: selected.map(({ group, targetGroupNumber }) => {
assertCurrentGroupIdentity(group);
return {
groupId: group.id,
category: group.category,
expectedGroupNumber: group.groupNumber,
targetGroupNumber,
expectedPrefix: group.prefix,
targetPrefix: formatCircuitGroupPrefix(
group.category,
targetGroupNumber
),
circuits: group.circuits.map((circuit) => {
const parsed = parseGroupedEquipmentIdentifier(
circuit.equipmentIdentifier
);
if (
parsed?.kind !== "circuit" ||
parsed.category !== group.category ||
parsed.groupNumber !== group.groupNumber ||
parsed.circuitNumber === null
) {
throw new Error(
"Circuit BMK does not match its current group."
);
}
return {
circuitId: circuit.id,
expectedEquipmentIdentifier:
circuit.equipmentIdentifier,
targetEquipmentIdentifier:
formatGroupedCircuitIdentifier(
group.category,
targetGroupNumber,
parsed.circuitNumber
),
};
}),
components: group.components.map((component) => {
const expected =
component.role === "group_upstream_protection"
? formatGroupUpstreamProtectionIdentifier(
group.category,
group.groupNumber
)
: formatGroupRcdIdentifier(
group.category,
group.groupNumber
);
if (component.equipmentIdentifier !== expected) {
throw new Error(
"Group component BMK does not match its current group."
);
}
return {
componentId: component.id,
expectedEquipmentIdentifier: expected,
targetEquipmentIdentifier:
component.role === "group_upstream_protection"
? formatGroupUpstreamProtectionIdentifier(
group.category,
targetGroupNumber
)
: formatGroupRcdIdentifier(
group.category,
targetGroupNumber
),
};
}),
};
}),
};
}
function assertCurrentGroupIdentity(group: RenumberableCircuitGroup) {
if (
!Number.isInteger(group.groupNumber) ||
group.groupNumber < 1 ||
group.prefix !==
formatCircuitGroupPrefix(group.category, group.groupNumber)
) {
throw new Error("Circuit group has an invalid current identity.");
}
}