Add protection domain foundation

This commit is contained in:
Julian Appel 2026-07-30 19:03:36 +02:00
parent a72d2c76e0
commit a88c4b2086
13 changed files with 909 additions and 2 deletions

View file

@ -0,0 +1,20 @@
export const circuitGroupCategories = [
"lighting",
"single_phase",
"three_phase",
] as const;
export type CircuitGroupCategory = (typeof circuitGroupCategories)[number];
export const circuitGroupCategoryNumbers: Record<CircuitGroupCategory, 1 | 2 | 3> =
{
lighting: 1,
single_phase: 2,
three_phase: 3,
};
export const circuitGroupCategoryLabels: Record<CircuitGroupCategory, string> = {
lighting: "Beleuchtung",
single_phase: "1-phasig",
three_phase: "3-phasig",
};

View file

@ -0,0 +1,54 @@
export const distributionBoardComponentRoles = [
"main_switch",
"surge_protective_device",
"group_upstream_protection",
"group_residual_current_protection",
"auxiliary",
] as const;
export type DistributionBoardComponentRole =
(typeof distributionBoardComponentRoles)[number];
export const distributionBoardComponentPlacements = [
"header",
"group",
"footer",
] as const;
export type DistributionBoardComponentPlacement =
(typeof distributionBoardComponentPlacements)[number];
export const distributionBoardComponentRoleLabels: Record<
DistributionBoardComponentRole,
string
> = {
main_switch: "Hauptschalter",
surge_protective_device: "Überspannungsableiter",
group_upstream_protection: "Gruppenvorsicherung",
group_residual_current_protection: "Gruppen-FI",
auxiliary: "Verteilergerät",
};
export const defaultMainSwitchComponent = {
equipmentIdentifier: "-Q0",
name: "Hauptschalter",
role: "main_switch",
placement: "header",
} as const satisfies {
equipmentIdentifier: string;
name: string;
role: DistributionBoardComponentRole;
placement: DistributionBoardComponentPlacement;
};
export const defaultSurgeProtectiveDeviceComponent = {
equipmentIdentifier: "-FA",
name: "Überspannungsableiter",
role: "surge_protective_device",
placement: "header",
} as const satisfies {
equipmentIdentifier: string;
name: string;
role: DistributionBoardComponentRole;
placement: DistributionBoardComponentPlacement;
};

View file

@ -0,0 +1,108 @@
export const fuseProtectionDeviceTypes = [
"D02",
"NH000",
"NH00",
"NH0",
"NH1",
"NH2",
"NH3",
] as const;
export type FuseProtectionDeviceType =
(typeof fuseProtectionDeviceTypes)[number];
export const breakerProtectionDeviceTypes = ["LS", "FI_LS", "AFDD"] as const;
export type BreakerProtectionDeviceType =
(typeof breakerProtectionDeviceTypes)[number];
export const protectionDeviceTypes = [
...fuseProtectionDeviceTypes,
"LS",
"FI",
"FI_LS",
"AFDD",
] as const;
export type ProtectionDeviceType = (typeof protectionDeviceTypes)[number];
export const fuseUtilizationCategories = ["gG", "gR"] as const;
export type FuseUtilizationCategory =
(typeof fuseUtilizationCategories)[number];
export const breakerTripCharacteristics = ["B", "C", "D", "Z"] as const;
export type BreakerTripCharacteristic =
(typeof breakerTripCharacteristics)[number];
export const rcdTypes = ["A", "AC", "B", "B+"] as const;
export type RcdType = (typeof rcdTypes)[number];
export const ratedResidualCurrentsMa = [10, 30, 100, 300, 500] as const;
export type RatedResidualCurrentMa =
(typeof ratedResidualCurrentsMa)[number];
const d02RatedCurrentsA = [20, 25, 32, 35, 40, 50, 63] as const;
const nh000AndNh00RatedCurrentsA = [
6, 10, 16, 20, 25, 32, 35, 40, 50, 63, 80, 100, 125, 160,
] as const;
const nh0RatedCurrentsA = [
6, 10, 16, 20, 25, 32, 35, 40, 50, 63, 80, 100, 125, 160, 200, 224, 250,
] as const;
const nh1RatedCurrentsA = [
16, 20, 25, 32, 35, 40, 50, 63, 80, 100, 125, 160, 200, 224, 250,
] as const;
const nh2RatedCurrentsA = [
25, 32, 35, 40, 50, 63, 80, 100, 125, 160, 200, 224, 250, 300, 315, 355,
400,
] as const;
const nh3RatedCurrentsA = [
50, 63, 80, 100, 125, 160, 200, 224, 250, 315, 355, 400, 500, 630, 800,
] as const;
const breakerRatedCurrentsA = [
6, 10, 13, 16, 20, 25, 32, 40, 50, 63, 80, 100, 125,
] as const;
const rcdRatedCurrentsA = [16, 25, 40, 63, 80, 100, 125] as const;
export const allowedRatedCurrentsAByProtectionDeviceType = {
D02: d02RatedCurrentsA,
NH000: nh000AndNh00RatedCurrentsA,
NH00: nh000AndNh00RatedCurrentsA,
NH0: nh0RatedCurrentsA,
NH1: nh1RatedCurrentsA,
NH2: nh2RatedCurrentsA,
NH3: nh3RatedCurrentsA,
LS: breakerRatedCurrentsA,
FI: rcdRatedCurrentsA,
FI_LS: breakerRatedCurrentsA,
AFDD: breakerRatedCurrentsA,
} as const satisfies Record<ProtectionDeviceType, readonly number[]>;
export const protectionDeviceTypeLabels: Record<ProtectionDeviceType, string> = {
D02: "D02 / Neozed",
NH000: "NH000",
NH00: "NH00",
NH0: "NH0",
NH1: "NH1",
NH2: "NH2",
NH3: "NH3",
LS: "LS",
FI: "FI",
FI_LS: "FI/LS",
AFDD: "AFDD",
};
export function isAllowedRatedCurrentA(
type: ProtectionDeviceType,
ratedCurrentA: number
): boolean {
const allowedValues: readonly number[] =
allowedRatedCurrentsAByProtectionDeviceType[type];
return allowedValues.includes(ratedCurrentA);
}
export function isAllowedRatedResidualCurrentMa(
ratedResidualCurrentMa: number
): ratedResidualCurrentMa is RatedResidualCurrentMa {
const allowedValues: readonly number[] = ratedResidualCurrentsMa;
return allowedValues.includes(ratedResidualCurrentMa);
}

View file

@ -0,0 +1,95 @@
import { z } from "zod";
import {
breakerTripCharacteristics,
fuseProtectionDeviceTypes,
fuseUtilizationCategories,
isAllowedRatedCurrentA,
isAllowedRatedResidualCurrentMa,
protectionDeviceTypes,
rcdTypes,
} from "../constants/protection-device.js";
function hasValue(value: unknown): boolean {
return value !== undefined;
}
export const protectionDeviceConfigurationSchema = z
.object({
type: z.enum(protectionDeviceTypes),
ratedCurrentA: z.number().finite().positive(),
fuseUtilizationCategory: z.enum(fuseUtilizationCategories).optional(),
tripCharacteristic: z.enum(breakerTripCharacteristics).optional(),
rcdType: z.enum(rcdTypes).optional(),
ratedResidualCurrentMa: z
.number()
.int()
.refine(isAllowedRatedResidualCurrentMa, {
message: "Der Bemessungsdifferenzstrom ist nicht zulässig.",
})
.optional(),
})
.strict()
.superRefine((value, context) => {
if (!isAllowedRatedCurrentA(value.type, value.ratedCurrentA)) {
context.addIssue({
code: "custom",
path: ["ratedCurrentA"],
message: "Der Bemessungsstrom ist für diesen Schutzgerätetyp nicht zulässig.",
});
}
const isFuse = (fuseProtectionDeviceTypes as readonly string[]).includes(
value.type
);
const hasBreakerFunction =
value.type === "LS" || value.type === "FI_LS" || value.type === "AFDD";
const hasResidualCurrentFunction =
value.type === "FI" || value.type === "FI_LS";
if (isFuse !== hasValue(value.fuseUtilizationCategory)) {
context.addIssue({
code: "custom",
path: ["fuseUtilizationCategory"],
message: isFuse
? "Für diesen Sicherungstyp ist eine Sicherungscharakteristik erforderlich."
: "Dieser Schutzgerätetyp darf keine Sicherungscharakteristik enthalten.",
});
}
if (hasBreakerFunction !== hasValue(value.tripCharacteristic)) {
context.addIssue({
code: "custom",
path: ["tripCharacteristic"],
message: hasBreakerFunction
? "Für diesen Schutzgerätetyp ist eine Auslösecharakteristik erforderlich."
: "Dieser Schutzgerätetyp darf keine Auslösecharakteristik enthalten.",
});
}
if (hasResidualCurrentFunction !== hasValue(value.rcdType)) {
context.addIssue({
code: "custom",
path: ["rcdType"],
message: hasResidualCurrentFunction
? "Für diesen Schutzgerätetyp ist ein FI-Typ erforderlich."
: "Dieser Schutzgerätetyp darf keinen FI-Typ enthalten.",
});
}
if (
hasResidualCurrentFunction !==
hasValue(value.ratedResidualCurrentMa)
) {
context.addIssue({
code: "custom",
path: ["ratedResidualCurrentMa"],
message: hasResidualCurrentFunction
? "Für diesen Schutzgerätetyp ist ein Bemessungsdifferenzstrom erforderlich."
: "Dieser Schutzgerätetyp darf keinen Bemessungsdifferenzstrom enthalten.",
});
}
});
export type ProtectionDeviceConfigurationInput = z.infer<
typeof protectionDeviceConfigurationSchema
>;