forked from jappel/leistungsbilanz-ts
Update print view and documentation for logical feeds
This commit is contained in:
parent
042a050876
commit
b74b495fab
10 changed files with 278 additions and 158 deletions
|
|
@ -19,14 +19,14 @@ export interface PrintBoardSummary {
|
|||
circuitCount: number;
|
||||
/** Σ quantity × power per unit, without any simultaneity factor. */
|
||||
installedPowerKw: number;
|
||||
/** Σ row totals, i.e. with the device-row simultaneity factors. */
|
||||
devicePowerKw: number;
|
||||
/** Device-row demand plus downstream-board demand before this board's GZF. */
|
||||
powerBeforeBoardSimultaneityKw: number;
|
||||
simultaneityFactor: number;
|
||||
/** Device power with the board simultaneity factor applied. */
|
||||
/** Total active power with this board's simultaneity factor applied. */
|
||||
demandPowerKw: number;
|
||||
/**
|
||||
* Symmetrical three-phase design current from the apparent power
|
||||
* S = Σ(row total / cos φ) × board GZF. A missing cos φ counts as 1.
|
||||
* Design current from the apparent power including downstream boards.
|
||||
* The selected feeder circuit determines whether one or three phases apply.
|
||||
*/
|
||||
designCurrentA: number;
|
||||
}
|
||||
|
|
@ -46,8 +46,13 @@ export function summarizePrintBoard(input: {
|
|||
circuitCount += 1;
|
||||
for (const row of circuit.deviceRows) {
|
||||
installedPowerKw += row.quantity * row.powerPerUnit;
|
||||
apparentPowerKva += row.rowTotalPower / (row.cosPhi || 1);
|
||||
}
|
||||
apparentPowerKva +=
|
||||
circuit.circuitApparentPower ??
|
||||
circuit.deviceRows.reduce(
|
||||
(sum, row) => sum + row.rowTotalPower / (row.cosPhi || 1),
|
||||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
const simultaneityFactor = tree.distributionBoardSimultaneityFactor;
|
||||
|
|
@ -55,12 +60,13 @@ export function summarizePrintBoard(input: {
|
|||
...input,
|
||||
circuitCount,
|
||||
installedPowerKw,
|
||||
devicePowerKw: tree.distributionBoardTotalPower,
|
||||
powerBeforeBoardSimultaneityKw: tree.distributionBoardTotalPower,
|
||||
simultaneityFactor,
|
||||
demandPowerKw: tree.distributionBoardTotalPowerWithSimultaneityFactor,
|
||||
designCurrentA:
|
||||
(apparentPowerKva * simultaneityFactor * 1000) /
|
||||
(Math.sqrt(3) * tree.threePhaseVoltageV),
|
||||
designCurrentA: calculateDesignCurrent(
|
||||
apparentPowerKva * simultaneityFactor,
|
||||
tree
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -68,8 +74,6 @@ export interface PrintBoardHierarchyEntry {
|
|||
summary: PrintBoardSummary;
|
||||
depth: number;
|
||||
parentBoardId: string | null;
|
||||
/** Own demand plus the plain sum of all resolved downstream boards. */
|
||||
cumulatedDemandPowerKw: number;
|
||||
}
|
||||
|
||||
export function assertPrintRevisionConsistency(
|
||||
|
|
@ -82,31 +86,21 @@ export function assertPrintRevisionConsistency(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PRELIMINARY: `fedFrom` is free text until the logical feed link is
|
||||
* decided. A board is shown below another board only when its `fedFrom`
|
||||
* text equals that board's number or name (case-insensitive). The
|
||||
* cumulated value is a plain sum without an upstream simultaneity factor.
|
||||
* Unresolved texts and cycles keep a board at the top level.
|
||||
*/
|
||||
export function buildPreliminaryBoardHierarchy(
|
||||
export function buildBoardHierarchy(
|
||||
summaries: PrintBoardSummary[]
|
||||
): PrintBoardHierarchyEntry[] {
|
||||
const normalize = (value: string | null) =>
|
||||
value?.trim().toLocaleLowerCase("de") || null;
|
||||
const summaryById = new Map(
|
||||
summaries.map((summary) => [summary.board.id, summary])
|
||||
);
|
||||
const parentById = new Map<string, string | null>();
|
||||
for (const summary of summaries) {
|
||||
const fedFrom = normalize(summary.board.fedFrom);
|
||||
const candidates = fedFrom
|
||||
? summaries.filter(
|
||||
(candidate) =>
|
||||
candidate !== summary &&
|
||||
(normalize(candidate.board.boardNumber) === fedFrom ||
|
||||
normalize(candidate.board.name) === fedFrom)
|
||||
)
|
||||
: [];
|
||||
const parent = candidates.length === 1 ? candidates[0] : undefined;
|
||||
parentById.set(summary.board.id, parent?.board.id ?? null);
|
||||
const parentId = summary.board.feedingDistributionBoardId;
|
||||
parentById.set(
|
||||
summary.board.id,
|
||||
parentId && parentId !== summary.board.id && summaryById.has(parentId)
|
||||
? parentId
|
||||
: null
|
||||
);
|
||||
}
|
||||
for (const summary of summaries) {
|
||||
const visited = new Set<string>([summary.board.id]);
|
||||
|
|
@ -125,20 +119,12 @@ export function buildPreliminaryBoardHierarchy(
|
|||
summaries.filter(
|
||||
(summary) => parentById.get(summary.board.id) === boardId
|
||||
);
|
||||
const cumulated = (summary: PrintBoardSummary): number =>
|
||||
summary.demandPowerKw +
|
||||
childrenOf(summary.board.id).reduce(
|
||||
(sum, child) => sum + cumulated(child),
|
||||
0
|
||||
);
|
||||
|
||||
const entries: PrintBoardHierarchyEntry[] = [];
|
||||
const walk = (summary: PrintBoardSummary, depth: number) => {
|
||||
entries.push({
|
||||
summary,
|
||||
depth,
|
||||
parentBoardId: parentById.get(summary.board.id) ?? null,
|
||||
cumulatedDemandPowerKw: cumulated(summary),
|
||||
});
|
||||
childrenOf(summary.board.id).forEach((child) => walk(child, depth + 1));
|
||||
};
|
||||
|
|
@ -146,6 +132,25 @@ export function buildPreliminaryBoardHierarchy(
|
|||
return entries;
|
||||
}
|
||||
|
||||
export function formatFeedingReference(summary: PrintBoardSummary): string {
|
||||
const reference = summary.tree.feedingReference;
|
||||
if (!reference) return summary.board.fedFrom ?? "–";
|
||||
return `${
|
||||
reference.distributionBoardNumber ?? reference.distributionBoardName
|
||||
}${reference.circuitEquipmentIdentifier ?? ""}`;
|
||||
}
|
||||
|
||||
function calculateDesignCurrent(
|
||||
apparentPowerKva: number,
|
||||
tree: CircuitTreeResponseDto
|
||||
) {
|
||||
const category = tree.feedingReference?.circuitCategory;
|
||||
return category === "lighting" || category === "single_phase"
|
||||
? (apparentPowerKva * 1000) / tree.singlePhaseVoltageV
|
||||
: (apparentPowerKva * 1000) /
|
||||
(Math.sqrt(3) * tree.threePhaseVoltageV);
|
||||
}
|
||||
|
||||
const kilowattFormat = new Intl.NumberFormat("de-DE", {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import {
|
|||
formatCircuitCable,
|
||||
formatCurrent,
|
||||
formatFactor,
|
||||
formatFeedingReference,
|
||||
formatKilowatt,
|
||||
formatProtectionDevice,
|
||||
formatUnitPower,
|
||||
|
|
@ -269,8 +270,8 @@ function OverviewSheet({
|
|||
stand: string;
|
||||
}) {
|
||||
const roots = entries.filter((entry) => entry.parentBoardId === null);
|
||||
// Top-level boards whose "fed from" text names no other board are grouped
|
||||
// below that text as an external feed (e.g. "HAK / Zähleranlage").
|
||||
// Root boards may still carry an external feed description such as
|
||||
// "HAK / Zähleranlage"; logical board links determine the actual tree.
|
||||
const feedGroups = new Map<string, PrintBoardHierarchyEntry[]>();
|
||||
for (const root of roots) {
|
||||
const feed = root.summary.board.fedFrom ?? "";
|
||||
|
|
@ -320,11 +321,10 @@ function OverviewSheet({
|
|||
<th>Nr.</th><th>Bezeichnung</th><th>Örtlichkeit</th><th>Etage</th><th>Netz</th><th>Gespeist von</th>
|
||||
<th className="pd-r">SK</th>
|
||||
<th className="pd-r">P<sub>inst</sub><br />[kW]</th>
|
||||
<th className="pd-r">P<sub>Geräte</sub><br />[kW]</th>
|
||||
<th className="pd-r">P vor Vert.-GZF<br />[kW]</th>
|
||||
<th className="pd-r">GZF<br />Vert.</th>
|
||||
<th className="pd-r">P<sub>gl</sub><br />[kW]</th>
|
||||
<th className="pd-r">P<sub>gl</sub> inkl. UV<br />[kW]</th>
|
||||
<th className="pd-r">I<sub>B</sub><br />[A]</th>
|
||||
<th className="pd-r">Σ inkl.<br />unterlagert [kW]</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -340,41 +340,45 @@ function OverviewSheet({
|
|||
<td>{board.location ?? "–"}</td>
|
||||
<td>{entry.summary.floorName ?? "–"}</td>
|
||||
<td>{board.supplyType ?? "–"}</td>
|
||||
<td>{board.fedFrom ?? "–"}</td>
|
||||
<td>{formatFeedingReference(entry.summary)}</td>
|
||||
<td className="pd-r">{entry.summary.circuitCount}</td>
|
||||
<td className="pd-r">{formatKilowatt(entry.summary.installedPowerKw)}</td>
|
||||
<td className="pd-r">{formatKilowatt(entry.summary.devicePowerKw)}</td>
|
||||
<td className="pd-r">{formatKilowatt(entry.summary.powerBeforeBoardSimultaneityKw)}</td>
|
||||
<td className="pd-r">{formatFactor(entry.summary.simultaneityFactor)}</td>
|
||||
<td className="pd-r"><strong>{formatKilowatt(entry.summary.demandPowerKw)}</strong></td>
|
||||
<td className="pd-r">{formatCurrent(entry.summary.designCurrentA)}</td>
|
||||
<td className="pd-r">{formatKilowatt(entry.cumulatedDemandPowerKw)}</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colSpan={7}>Summe aller Verteiler (ohne übergeordneten GZF)</td>
|
||||
<td colSpan={7}>Summe der Einspeisungen</td>
|
||||
<td className="pd-r">{formatKilowatt(sum((entry) => entry.summary.installedPowerKw))}</td>
|
||||
<td className="pd-r">{formatKilowatt(sum((entry) => entry.summary.devicePowerKw))}</td>
|
||||
<td />
|
||||
<td className="pd-r">{formatKilowatt(sum((entry) => entry.summary.demandPowerKw))}</td>
|
||||
<td className="pd-r">{formatKilowatt(sum((entry) =>
|
||||
entry.parentBoardId === null
|
||||
? entry.summary.powerBeforeBoardSimultaneityKw
|
||||
: 0
|
||||
))}</td>
|
||||
<td />
|
||||
<td className="pd-r">{formatKilowatt(sum((entry) =>
|
||||
entry.parentBoardId === null ? entry.summary.demandPowerKw : 0
|
||||
))}</td>
|
||||
<td />
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
<p className="pd-note">
|
||||
<strong>Vorläufig:</strong> „Gespeist von“ ist noch ein Freitextfeld. Struktur und
|
||||
„Σ inkl. unterlagert“ werden nur über eindeutig passende Verteilernummern bzw.
|
||||
-bezeichnungen zugeordnet und einfach addiert (kein übergeordneter GZF).
|
||||
Mehrdeutige Angaben bleiben ohne Zuordnung. Die fachliche
|
||||
Verknüpfung und Summierung folgt.
|
||||
Die Struktur folgt den logischen Verknüpfungen. Eine Unterverteilung
|
||||
wird im gewählten Abgang mit ihrer Leistung nach eigenem
|
||||
Verteiler-GZF berücksichtigt. Ohne gewählten Abgang erscheint sie
|
||||
nur in der Hierarchie.
|
||||
</p>
|
||||
<p className="pd-legend">
|
||||
P<sub>inst</sub> = Anzahl × Leistung je Stück · P<sub>Geräte</sub> = mit Geräte-GZF ·
|
||||
P<sub>gl</sub> = mit Verteiler-GZF · I<sub>B</sub> = S / (√3 · {project.threePhaseVoltageV} V),
|
||||
S aus cos φ je Gerätezeile
|
||||
P<sub>inst</sub> = lokale Geräte ohne GZF · P vor Vert.-GZF =
|
||||
Geräte-GZF einschließlich gespeister Unterverteilungen · P<sub>gl</sub> =
|
||||
zusätzlich mit Verteiler-GZF · I<sub>B</sub> aus S und der Phasenart
|
||||
des speisenden Abgangs
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -404,6 +408,7 @@ function ComponentRow({ component }: { component: CircuitTreeComponentDto }) {
|
|||
|
||||
function CircuitRows({ circuit }: { circuit: CircuitTreeCircuitDto }) {
|
||||
const rows = circuit.deviceRows;
|
||||
const downstreamBoards = circuit.downstreamBoardLoads ?? [];
|
||||
const circuitCells = (
|
||||
<>
|
||||
<td>{formatProtectionDevice(circuit.protectionDevice) ?? <Open />}</td>
|
||||
|
|
@ -414,7 +419,7 @@ function CircuitRows({ circuit }: { circuit: CircuitTreeCircuitDto }) {
|
|||
<td>{circuit.remark ?? ""}</td>
|
||||
</>
|
||||
);
|
||||
if (rows.length === 0) {
|
||||
if (rows.length === 0 && downstreamBoards.length === 0) {
|
||||
return (
|
||||
<tr className="pd-circuit pd-reserve">
|
||||
<td className="pd-bmk">{circuit.equipmentIdentifier}</td>
|
||||
|
|
@ -426,7 +431,7 @@ function CircuitRows({ circuit }: { circuit: CircuitTreeCircuitDto }) {
|
|||
</tr>
|
||||
);
|
||||
}
|
||||
if (rows.length === 1) {
|
||||
if (rows.length === 1 && downstreamBoards.length === 0) {
|
||||
const row = rows[0];
|
||||
const title = circuit.displayName ?? row.displayName;
|
||||
return (
|
||||
|
|
@ -468,6 +473,16 @@ function CircuitRows({ circuit }: { circuit: CircuitTreeCircuitDto }) {
|
|||
<td colSpan={4} />
|
||||
</tr>
|
||||
))}
|
||||
{downstreamBoards.map((load) => (
|
||||
<tr className="pd-device" key={`downstream:${load.distributionBoardId}`}>
|
||||
<td />
|
||||
<td>Unterverteilung {load.boardNumber ?? load.name}</td>
|
||||
<td>–</td>
|
||||
<td /><td /><td />
|
||||
<td className="pd-r">{formatKilowatt(load.activePowerKw)}</td>
|
||||
<td colSpan={4}>Automatisch aus der verknüpften Verteilung</td>
|
||||
</tr>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -501,7 +516,7 @@ function BoardPlanSheet({
|
|||
rows={[
|
||||
["Örtlichkeit", [board.location, summary.floorName].filter(Boolean).join(" · ") || "–"],
|
||||
["Netzart", board.supplyType ?? "–"],
|
||||
["Gespeist von", board.fedFrom ?? "–"],
|
||||
["Gespeist von", formatFeedingReference(summary)],
|
||||
[
|
||||
<>P<sub>gl</sub></>,
|
||||
<>
|
||||
|
|
@ -542,7 +557,7 @@ function BoardPlanSheet({
|
|||
<td colSpan={5}>
|
||||
Summe Verteiler (P<sub>inst</sub> {formatKilowatt(summary.installedPowerKw)} kW)
|
||||
</td>
|
||||
<td className="pd-r">{formatKilowatt(summary.devicePowerKw)}</td>
|
||||
<td className="pd-r">{formatKilowatt(summary.powerBeforeBoardSimultaneityKw)}</td>
|
||||
<td colSpan={4} />
|
||||
</tr>
|
||||
<tr>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue