@tellegen/svelte 0.4.2 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -0
- package/dist/TellegenMap.svelte +63 -22
- package/dist/TellegenProvider.svelte +7 -4
- package/dist/TellegenShell.svelte +10 -1
- package/dist/TellegenViewer.svelte +2 -0
- package/dist/basemap.d.ts +14 -0
- package/dist/basemap.js +27 -0
- package/dist/colors.d.ts +6 -1
- package/dist/colors.js +11 -4
- package/dist/components/DemandSlider.svelte +0 -2
- package/dist/components/FormulationSelector.svelte +1 -1
- package/dist/components/LocalCaseDetails.svelte +18 -3
- package/dist/components/McResults.svelte +18 -3
- package/dist/components/McWorkbench.svelte +688 -0
- package/dist/components/McWorkbench.svelte.d.ts +38 -0
- package/dist/components/MultiWire.svelte +136 -0
- package/dist/components/MultiWire.svelte.d.ts +7 -0
- package/dist/components/MulticonductorDetails.svelte +7 -0
- package/dist/components/OmniSearch.svelte +163 -0
- package/dist/components/OmniSearch.svelte.d.ts +14 -0
- package/dist/components/ResultsTable.svelte +259 -0
- package/dist/components/ResultsTable.svelte.d.ts +13 -0
- package/dist/components/SchematicCanvas.svelte +1896 -0
- package/dist/components/SchematicCanvas.svelte.d.ts +13 -0
- package/dist/components/SolveCard.svelte +6 -0
- package/dist/components/schematic/PickCard.svelte +126 -0
- package/dist/components/schematic/PickCard.svelte.d.ts +56 -0
- package/dist/components/schematic/export.d.ts +21 -0
- package/dist/components/schematic/export.js +110 -0
- package/dist/components/schematic/layers.d.ts +161 -0
- package/dist/components/schematic/layers.js +756 -0
- package/dist/components/schematic/paint.d.ts +28 -0
- package/dist/components/schematic/paint.js +69 -0
- package/dist/components/schematic/palette.d.ts +34 -0
- package/dist/components/schematic/palette.js +54 -0
- package/dist/components/schematic/text.d.ts +83 -0
- package/dist/components/schematic/text.js +190 -0
- package/dist/components/schematic/view-prefs.svelte.d.ts +30 -0
- package/dist/components/schematic/view-prefs.svelte.js +77 -0
- package/dist/components.d.ts +1 -0
- package/dist/components.js +1 -0
- package/dist/context.svelte.d.ts +2 -0
- package/dist/context.svelte.js +2 -0
- package/dist/controller.svelte.d.ts +33 -5
- package/dist/controller.svelte.js +173 -20
- package/dist/diagram/basemap.d.ts +28 -0
- package/dist/diagram/basemap.js +39 -0
- package/dist/diagram/bus-stacks.d.ts +19 -0
- package/dist/diagram/bus-stacks.js +99 -0
- package/dist/diagram/colour-modes.d.ts +18 -0
- package/dist/diagram/colour-modes.js +33 -0
- package/dist/diagram/connections.d.ts +30 -0
- package/dist/diagram/connections.js +129 -0
- package/dist/diagram/display-tree.d.ts +21 -0
- package/dist/diagram/display-tree.js +65 -0
- package/dist/diagram/energisation.d.ts +10 -0
- package/dist/diagram/energisation.js +31 -0
- package/dist/diagram/feeders.d.ts +30 -0
- package/dist/diagram/feeders.js +57 -0
- package/dist/diagram/galvanic-zones.d.ts +60 -0
- package/dist/diagram/galvanic-zones.js +210 -0
- package/dist/diagram/graph-inputs.d.ts +18 -0
- package/dist/diagram/graph-inputs.js +103 -0
- package/dist/diagram/index.d.ts +28 -0
- package/dist/diagram/index.js +28 -0
- package/dist/diagram/landmarks.d.ts +50 -0
- package/dist/diagram/landmarks.js +153 -0
- package/dist/diagram/layout-morph.d.ts +15 -0
- package/dist/diagram/layout-morph.js +32 -0
- package/dist/diagram/multi-wire.d.ts +44 -0
- package/dist/diagram/multi-wire.js +65 -0
- package/dist/diagram/phase-bars.d.ts +35 -0
- package/dist/diagram/phase-bars.js +44 -0
- package/dist/diagram/results-table.d.ts +47 -0
- package/dist/diagram/results-table.js +173 -0
- package/dist/diagram/schematic-equipment.d.ts +133 -0
- package/dist/diagram/schematic-equipment.js +645 -0
- package/dist/diagram/schematic-layout.d.ts +51 -0
- package/dist/diagram/schematic-layout.js +240 -0
- package/dist/diagram/search.d.ts +36 -0
- package/dist/diagram/search.js +48 -0
- package/dist/diagram/shared-routes.d.ts +21 -0
- package/dist/diagram/shared-routes.js +40 -0
- package/dist/diagram/short-names.d.ts +11 -0
- package/dist/diagram/short-names.js +54 -0
- package/dist/diagram/snapshot.d.ts +115 -0
- package/dist/diagram/snapshot.js +242 -0
- package/dist/diagram/summary.d.ts +65 -0
- package/dist/diagram/summary.js +144 -0
- package/dist/diagram/svg-export.d.ts +80 -0
- package/dist/diagram/svg-export.js +108 -0
- package/dist/diagram/tree-walk.d.ts +13 -0
- package/dist/diagram/tree-walk.js +83 -0
- package/dist/diagram/value-labels.d.ts +24 -0
- package/dist/diagram/value-labels.js +69 -0
- package/dist/diagram/view-state.d.ts +12 -0
- package/dist/diagram/view-state.js +22 -0
- package/dist/diagram/voltage-levels.d.ts +14 -0
- package/dist/diagram/voltage-levels.js +20 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/state.svelte.d.ts +37 -1
- package/dist/state.svelte.js +35 -0
- package/dist/transformer-icon.d.ts +22 -13
- package/dist/transformer-icon.js +23 -13
- package/package.json +2 -2
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { DistGraph } from '@tellegen/engine';
|
|
2
|
+
import type { ResultSnapshot } from './snapshot.js';
|
|
3
|
+
/** One bus drawn as a stack of terminal rails. */
|
|
4
|
+
export interface WireBus {
|
|
5
|
+
id: string;
|
|
6
|
+
x: number;
|
|
7
|
+
terminals: {
|
|
8
|
+
name: string;
|
|
9
|
+
y: number;
|
|
10
|
+
grounded: boolean;
|
|
11
|
+
attachments: string[];
|
|
12
|
+
}[];
|
|
13
|
+
}
|
|
14
|
+
/** One conductor segment between two points; `terminal` names the end it belongs to. */
|
|
15
|
+
export interface Wire {
|
|
16
|
+
x1: number;
|
|
17
|
+
y1: number;
|
|
18
|
+
x2: number;
|
|
19
|
+
y2: number;
|
|
20
|
+
/** Terminal at the bus end of the segment, used for phase colour. */
|
|
21
|
+
terminal: string;
|
|
22
|
+
amps: number | null;
|
|
23
|
+
}
|
|
24
|
+
/** A bounded multi-wire drawing of one branch and its two buses, in drawing units. */
|
|
25
|
+
export interface MultiWireView {
|
|
26
|
+
width: number;
|
|
27
|
+
height: number;
|
|
28
|
+
buses: [WireBus, WireBus];
|
|
29
|
+
wires: Wire[];
|
|
30
|
+
/** Winding box for transformers: conductors meet it from each side and do not pass through. */
|
|
31
|
+
winding: {
|
|
32
|
+
x: number;
|
|
33
|
+
y: number;
|
|
34
|
+
width: number;
|
|
35
|
+
height: number;
|
|
36
|
+
} | null;
|
|
37
|
+
kind: string;
|
|
38
|
+
id: string;
|
|
39
|
+
closed: boolean;
|
|
40
|
+
}
|
|
41
|
+
/** Conductor-level view of branch `k`: every conductor pair from the from-terminal rail to the
|
|
42
|
+
* to-terminal rail, so crossed terminal maps show as crossing wires. A transformer is drawn as a
|
|
43
|
+
* winding box with no conductor continuity across it. Currents come from the snapshot's ends. */
|
|
44
|
+
export declare function multiWireBranch(graph: DistGraph, k: number, snapshot: ResultSnapshot | null, width?: number): MultiWireView | null;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const ROW = 22;
|
|
2
|
+
const TOP = 26;
|
|
3
|
+
/** Conductor-level view of branch `k`: every conductor pair from the from-terminal rail to the
|
|
4
|
+
* to-terminal rail, so crossed terminal maps show as crossing wires. A transformer is drawn as a
|
|
5
|
+
* winding box with no conductor continuity across it. Currents come from the snapshot's ends. */
|
|
6
|
+
export function multiWireBranch(graph, k, snapshot, width = 320) {
|
|
7
|
+
const edge = graph.edges[k];
|
|
8
|
+
const index = new Map(graph.buses.map((b, i) => [b.id.toLowerCase(), i]));
|
|
9
|
+
const from = graph.buses[index.get(edge?.from.toLowerCase() ?? '') ?? -1];
|
|
10
|
+
const to = graph.buses[index.get(edge?.to.toLowerCase() ?? '') ?? -1];
|
|
11
|
+
if (!edge || !from || !to)
|
|
12
|
+
return null;
|
|
13
|
+
const rails = (bus, x) => ({
|
|
14
|
+
id: bus.id,
|
|
15
|
+
x,
|
|
16
|
+
terminals: bus.terminals.map((name, i) => ({
|
|
17
|
+
name,
|
|
18
|
+
y: TOP + i * ROW,
|
|
19
|
+
grounded: bus.grounded.includes(name),
|
|
20
|
+
attachments: (bus.terminal_attachments?.[name] ?? []).map((a) => `${a.kind} ${a.id}`)
|
|
21
|
+
}))
|
|
22
|
+
});
|
|
23
|
+
const left = rails(from, 56);
|
|
24
|
+
const right = rails(to, width - 56);
|
|
25
|
+
const rowOf = (bus, name) => bus.terminals.find((t) => t.name === name)?.y;
|
|
26
|
+
const ends = snapshot?.branches[k]?.ends ?? [null, null];
|
|
27
|
+
const amps = (side, terminal) => ends[side]?.currents.find((c) => c.terminal === terminal)?.amps ?? null;
|
|
28
|
+
const rowsUsed = Math.max(left.terminals.length, right.terminals.length, 1);
|
|
29
|
+
const height = TOP + rowsUsed * ROW + 10;
|
|
30
|
+
const transformer = edge.kind === 'transformer';
|
|
31
|
+
const winding = transformer
|
|
32
|
+
? { x: width / 2 - 22, y: TOP - 10, width: 44, height: (rowsUsed - 1) * ROW + 20 }
|
|
33
|
+
: null;
|
|
34
|
+
const wires = [];
|
|
35
|
+
for (const [a, b] of edge.conductors) {
|
|
36
|
+
const y1 = rowOf(left, a);
|
|
37
|
+
const y2 = rowOf(right, b);
|
|
38
|
+
if (y1 === undefined || y2 === undefined)
|
|
39
|
+
continue;
|
|
40
|
+
if (winding) {
|
|
41
|
+
wires.push({ x1: left.x, y1, x2: winding.x, y2: y1, terminal: a, amps: amps(0, a) });
|
|
42
|
+
wires.push({
|
|
43
|
+
x1: winding.x + winding.width,
|
|
44
|
+
y1: y2,
|
|
45
|
+
x2: right.x,
|
|
46
|
+
y2,
|
|
47
|
+
terminal: b,
|
|
48
|
+
amps: amps(1, b)
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
wires.push({ x1: left.x, y1, x2: right.x, y2, terminal: a, amps: amps(0, a) });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
width,
|
|
57
|
+
height,
|
|
58
|
+
buses: [left, right],
|
|
59
|
+
wires,
|
|
60
|
+
winding,
|
|
61
|
+
kind: edge.kind,
|
|
62
|
+
id: edge.id,
|
|
63
|
+
closed: edge.closed
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { BranchEnd, PairVoltage } from './snapshot.js';
|
|
2
|
+
import { type VoltageState } from './summary.js';
|
|
3
|
+
export interface PairBar {
|
|
4
|
+
pair: [string, string];
|
|
5
|
+
volts: number;
|
|
6
|
+
state: VoltageState;
|
|
7
|
+
/** Band and marker in percent of the track; the track is the declared band ±25 %. `null`
|
|
8
|
+
* band when limits are undeclared, then the marker sits mid-track. */
|
|
9
|
+
band: {
|
|
10
|
+
left: number;
|
|
11
|
+
width: number;
|
|
12
|
+
} | null;
|
|
13
|
+
marker: number;
|
|
14
|
+
}
|
|
15
|
+
/** Each pair on its own band, so position reads as margin to the limits rather than volts. */
|
|
16
|
+
export declare function pairBar(p: PairVoltage): PairBar;
|
|
17
|
+
export interface ConductorBar {
|
|
18
|
+
terminal: string;
|
|
19
|
+
amps: number;
|
|
20
|
+
/** Percent of the largest conductor current across both ends. */
|
|
21
|
+
fill: number;
|
|
22
|
+
neutral: boolean;
|
|
23
|
+
}
|
|
24
|
+
export interface ConductorBars {
|
|
25
|
+
scaleAmps: number;
|
|
26
|
+
ends: ({
|
|
27
|
+
p: number;
|
|
28
|
+
q: number;
|
|
29
|
+
bars: ConductorBar[];
|
|
30
|
+
} | null)[];
|
|
31
|
+
/** Neutral current as a percentage of the largest phase current at the first live end. */
|
|
32
|
+
neutralShare: number | null;
|
|
33
|
+
}
|
|
34
|
+
/** Conductor |I| at both ends on one scale; neutral current is called out as an unbalance signal. */
|
|
35
|
+
export declare function conductorBars(ends: [BranchEnd | null, BranchEnd | null], neutral?: string): ConductorBars | null;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { pairState } from './summary.js';
|
|
2
|
+
const clampPercent = (x) => Math.max(0, Math.min(100, x));
|
|
3
|
+
/** Each pair on its own band, so position reads as margin to the limits rather than volts. */
|
|
4
|
+
export function pairBar(p) {
|
|
5
|
+
const state = pairState(p);
|
|
6
|
+
if (p.min === null || p.max === null || !(p.max > p.min))
|
|
7
|
+
return { pair: p.pair, volts: p.volts, state, band: null, marker: 50 };
|
|
8
|
+
const span = p.max - p.min;
|
|
9
|
+
const lo = p.min - 0.25 * span;
|
|
10
|
+
const x = (v) => (100 * (v - lo)) / (1.5 * span);
|
|
11
|
+
return {
|
|
12
|
+
pair: p.pair,
|
|
13
|
+
volts: p.volts,
|
|
14
|
+
state,
|
|
15
|
+
band: { left: x(p.min), width: x(p.max) - x(p.min) },
|
|
16
|
+
marker: clampPercent(x(p.volts))
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/** Conductor |I| at both ends on one scale; neutral current is called out as an unbalance signal. */
|
|
20
|
+
export function conductorBars(ends, neutral = 'n') {
|
|
21
|
+
const live = ends.filter((e) => !!e?.currents.length);
|
|
22
|
+
if (!live.length)
|
|
23
|
+
return null;
|
|
24
|
+
const scaleAmps = Math.max(...live.flatMap((e) => e.currents.map((c) => c.amps)).filter(Number.isFinite), 1e-9);
|
|
25
|
+
const first = live[0];
|
|
26
|
+
const n = first.currents.find((c) => c.terminal === neutral);
|
|
27
|
+
const phaseMax = Math.max(0, ...first.currents.filter((c) => c.terminal !== neutral).map((c) => c.amps));
|
|
28
|
+
return {
|
|
29
|
+
scaleAmps,
|
|
30
|
+
ends: ends.map((e) => e?.currents.length
|
|
31
|
+
? {
|
|
32
|
+
p: e.p,
|
|
33
|
+
q: e.q,
|
|
34
|
+
bars: e.currents.map((c) => ({
|
|
35
|
+
terminal: c.terminal,
|
|
36
|
+
amps: c.amps,
|
|
37
|
+
fill: clampPercent((100 * c.amps) / scaleAmps),
|
|
38
|
+
neutral: c.terminal === neutral
|
|
39
|
+
}))
|
|
40
|
+
}
|
|
41
|
+
: null),
|
|
42
|
+
neutralShare: n && phaseMax > 0 ? (100 * n.amps) / phaseMax : null
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { ResultSnapshot } from './snapshot.js';
|
|
2
|
+
export type TableKind = 'bus' | 'branch';
|
|
3
|
+
type Cell = string | number | null;
|
|
4
|
+
export type TableRow = Record<string, Cell> & {
|
|
5
|
+
state: string;
|
|
6
|
+
};
|
|
7
|
+
export interface TableColumn {
|
|
8
|
+
key: string;
|
|
9
|
+
label: string;
|
|
10
|
+
unit?: string;
|
|
11
|
+
numeric?: boolean;
|
|
12
|
+
digits?: number;
|
|
13
|
+
title?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface TableNames {
|
|
16
|
+
buses: {
|
|
17
|
+
id: string;
|
|
18
|
+
source?: string;
|
|
19
|
+
}[];
|
|
20
|
+
branches: {
|
|
21
|
+
id: string;
|
|
22
|
+
family: string;
|
|
23
|
+
from: number;
|
|
24
|
+
to: number;
|
|
25
|
+
inService: boolean;
|
|
26
|
+
}[];
|
|
27
|
+
}
|
|
28
|
+
export interface TableModel {
|
|
29
|
+
kind: TableKind;
|
|
30
|
+
count: number;
|
|
31
|
+
columns: TableColumn[];
|
|
32
|
+
row(index: number): TableRow;
|
|
33
|
+
}
|
|
34
|
+
/** Rows are built on demand and cached in a bounded window, so 100k-row snapshots stay cheap. */
|
|
35
|
+
export declare function tableModel(result: ResultSnapshot, names: TableNames, kind: TableKind): TableModel;
|
|
36
|
+
export interface TableQuery {
|
|
37
|
+
text?: string;
|
|
38
|
+
issuesOnly?: boolean;
|
|
39
|
+
sortKey?: string | null;
|
|
40
|
+
descending?: boolean;
|
|
41
|
+
}
|
|
42
|
+
/** Filter by text over identities and optionally issues only, then a stable sort with missing
|
|
43
|
+
* values last in both directions. */
|
|
44
|
+
export declare function queryTable(model: TableModel, q?: TableQuery): Int32Array;
|
|
45
|
+
/** Full-precision CSV of the given rows, units in the header, provenance as `#` comment lines. */
|
|
46
|
+
export declare function tableCsv(model: TableModel, indices: Iterable<number>, provenance?: string[]): string;
|
|
47
|
+
export {};
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { worstBand } from './colour-modes.js';
|
|
2
|
+
import { busState, busUnbalance, loadingState } from './summary.js';
|
|
3
|
+
const BUS_COLUMNS = [
|
|
4
|
+
{ key: 'id', label: 'Bus' },
|
|
5
|
+
{ key: 'source', label: 'Source' },
|
|
6
|
+
{ key: 'reference', label: 'Reference' },
|
|
7
|
+
{ key: 'pairs', label: 'Pairs' },
|
|
8
|
+
{ key: 'v0', label: 'V1', unit: 'V', numeric: true, digits: 2 },
|
|
9
|
+
{ key: 'v1', label: 'V2', unit: 'V', numeric: true, digits: 2 },
|
|
10
|
+
{ key: 'v2', label: 'V3', unit: 'V', numeric: true, digits: 2 },
|
|
11
|
+
{
|
|
12
|
+
key: 'band',
|
|
13
|
+
label: 'Band position',
|
|
14
|
+
unit: '±1 = limit',
|
|
15
|
+
numeric: true,
|
|
16
|
+
digits: 3,
|
|
17
|
+
title: 'Worst pair: −1 at the declared minimum, +1 at the maximum; sorts by distance from mid-band'
|
|
18
|
+
},
|
|
19
|
+
{ key: 'unbalance', label: 'Unbalance', unit: '%', numeric: true, digits: 2 },
|
|
20
|
+
{ key: 'unbalanceBasis', label: 'Basis' },
|
|
21
|
+
{ key: 'state', label: 'Status' }
|
|
22
|
+
];
|
|
23
|
+
const BRANCH_COLUMNS = [
|
|
24
|
+
{ key: 'id', label: 'Branch' },
|
|
25
|
+
{ key: 'family', label: 'Type' },
|
|
26
|
+
{ key: 'from', label: 'From bus' },
|
|
27
|
+
{ key: 'to', label: 'To bus' },
|
|
28
|
+
{
|
|
29
|
+
key: 'pFrom',
|
|
30
|
+
label: 'P from',
|
|
31
|
+
unit: 'kW',
|
|
32
|
+
numeric: true,
|
|
33
|
+
digits: 2,
|
|
34
|
+
title: 'Positive into the equipment'
|
|
35
|
+
},
|
|
36
|
+
{ key: 'qFrom', label: 'Q from', unit: 'kvar', numeric: true, digits: 2 },
|
|
37
|
+
{
|
|
38
|
+
key: 'pTo',
|
|
39
|
+
label: 'P to',
|
|
40
|
+
unit: 'kW',
|
|
41
|
+
numeric: true,
|
|
42
|
+
digits: 2,
|
|
43
|
+
title: 'Positive into the equipment'
|
|
44
|
+
},
|
|
45
|
+
{ key: 'qTo', label: 'Q to', unit: 'kvar', numeric: true, digits: 2 },
|
|
46
|
+
{
|
|
47
|
+
key: 'loss',
|
|
48
|
+
label: 'Losses',
|
|
49
|
+
unit: 'kW',
|
|
50
|
+
numeric: true,
|
|
51
|
+
digits: 3,
|
|
52
|
+
title: 'Sum of both end P'
|
|
53
|
+
},
|
|
54
|
+
{ key: 'loading', label: 'Loading', unit: '%', numeric: true, digits: 1 },
|
|
55
|
+
{ key: 'state', label: 'Status' }
|
|
56
|
+
];
|
|
57
|
+
/** Rows are built on demand and cached in a bounded window, so 100k-row snapshots stay cheap. */
|
|
58
|
+
export function tableModel(result, names, kind) {
|
|
59
|
+
const cache = new Map();
|
|
60
|
+
const build = (i) => {
|
|
61
|
+
if (kind === 'bus') {
|
|
62
|
+
const bus = result.buses[i];
|
|
63
|
+
const u = busUnbalance(bus);
|
|
64
|
+
return {
|
|
65
|
+
id: names.buses[i].id,
|
|
66
|
+
source: names.buses[i].source ?? null,
|
|
67
|
+
reference: bus.pairs.length ? [...new Set(bus.pairs.map((p) => p.zone))].join(', ') : null,
|
|
68
|
+
pairs: bus.pairs.map((p) => p.pair.join('–')).join(' ') || null,
|
|
69
|
+
v0: bus.pairs[0]?.volts ?? null,
|
|
70
|
+
v1: bus.pairs[1]?.volts ?? null,
|
|
71
|
+
v2: bus.pairs[2]?.volts ?? null,
|
|
72
|
+
band: worstBand(bus.pairs),
|
|
73
|
+
unbalance: u?.percent ?? null,
|
|
74
|
+
unbalanceBasis: u?.definition ?? null,
|
|
75
|
+
state: busState(bus)
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const name = names.branches[i];
|
|
79
|
+
const { ends, loading, loss } = result.branches[i];
|
|
80
|
+
const [a, b] = ends;
|
|
81
|
+
return {
|
|
82
|
+
id: name.id,
|
|
83
|
+
family: name.family,
|
|
84
|
+
from: names.buses[name.from]?.id ?? null,
|
|
85
|
+
to: names.buses[name.to]?.id ?? null,
|
|
86
|
+
pFrom: a?.p ?? null,
|
|
87
|
+
qFrom: a?.q ?? null,
|
|
88
|
+
pTo: b?.p ?? null,
|
|
89
|
+
qTo: b?.q ?? null,
|
|
90
|
+
// The equipment's loss, on its first edge only, so the column sums to the total.
|
|
91
|
+
loss: loss?.p ?? null,
|
|
92
|
+
loading,
|
|
93
|
+
state: name.inService ? loadingState(loading) : 'inactive'
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
return {
|
|
97
|
+
kind,
|
|
98
|
+
count: kind === 'bus' ? result.buses.length : result.branches.length,
|
|
99
|
+
columns: kind === 'bus' ? BUS_COLUMNS : BRANCH_COLUMNS,
|
|
100
|
+
row(i) {
|
|
101
|
+
let row = cache.get(i);
|
|
102
|
+
if (!row) {
|
|
103
|
+
if (cache.size > 4096)
|
|
104
|
+
cache.clear();
|
|
105
|
+
row = build(i);
|
|
106
|
+
cache.set(i, row);
|
|
107
|
+
}
|
|
108
|
+
return row;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
const ISSUE_STATES = {
|
|
113
|
+
bus: new Set(['violation']),
|
|
114
|
+
branch: new Set(['violation', 'warning'])
|
|
115
|
+
};
|
|
116
|
+
const isMissing = (v) => v === null || (typeof v === 'number' && Number.isNaN(v));
|
|
117
|
+
/** Filter by text over identities and optionally issues only, then a stable sort with missing
|
|
118
|
+
* values last in both directions. */
|
|
119
|
+
export function queryTable(model, q = {}) {
|
|
120
|
+
const text = (q.text ?? '').trim().toLowerCase();
|
|
121
|
+
const out = [];
|
|
122
|
+
for (let i = 0; i < model.count; i++) {
|
|
123
|
+
const row = model.row(i);
|
|
124
|
+
if (q.issuesOnly && !ISSUE_STATES[model.kind].has(row.state))
|
|
125
|
+
continue;
|
|
126
|
+
if (text &&
|
|
127
|
+
!['id', 'source', 'from', 'to', 'family'].some((k) => String(row[k] ?? '')
|
|
128
|
+
.toLowerCase()
|
|
129
|
+
.includes(text)))
|
|
130
|
+
continue;
|
|
131
|
+
out.push(i);
|
|
132
|
+
}
|
|
133
|
+
const column = model.columns.find((c) => c.key === q.sortKey);
|
|
134
|
+
if (column) {
|
|
135
|
+
const key = column.key;
|
|
136
|
+
const sign = q.descending ? -1 : 1;
|
|
137
|
+
const values = new Map(out.map((i) => [i, model.row(i)[key]]));
|
|
138
|
+
out.sort((a, b) => {
|
|
139
|
+
const x = values.get(a) ?? null;
|
|
140
|
+
const y = values.get(b) ?? null;
|
|
141
|
+
if (isMissing(x) || isMissing(y))
|
|
142
|
+
return isMissing(x) === isMissing(y) ? a - b : isMissing(x) ? 1 : -1;
|
|
143
|
+
const order = column.numeric
|
|
144
|
+
? key === 'band'
|
|
145
|
+
? Math.abs(x) - Math.abs(y)
|
|
146
|
+
: x - y
|
|
147
|
+
: String(x).localeCompare(String(y));
|
|
148
|
+
return order * sign || a - b;
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
return Int32Array.from(out);
|
|
152
|
+
}
|
|
153
|
+
/** Text a spreadsheet would evaluate as a formula gets a leading apostrophe; numbers stay numeric. */
|
|
154
|
+
function csvCell(v) {
|
|
155
|
+
if (v === null || v === undefined)
|
|
156
|
+
return '';
|
|
157
|
+
let s = String(v);
|
|
158
|
+
if (typeof v !== 'number' && /^[=+\-@\t\r]/.test(s))
|
|
159
|
+
s = `'${s}`;
|
|
160
|
+
return /[",\n\r']/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
|
|
161
|
+
}
|
|
162
|
+
/** Full-precision CSV of the given rows, units in the header, provenance as `#` comment lines. */
|
|
163
|
+
export function tableCsv(model, indices, provenance = []) {
|
|
164
|
+
const lines = [
|
|
165
|
+
...provenance.map((line) => `# ${line}`),
|
|
166
|
+
model.columns.map((c) => csvCell(c.unit ? `${c.label} (${c.unit})` : c.label)).join(',')
|
|
167
|
+
];
|
|
168
|
+
for (const i of indices) {
|
|
169
|
+
const row = model.row(i);
|
|
170
|
+
lines.push(model.columns.map((c) => csvCell(row[c.key])).join(','));
|
|
171
|
+
}
|
|
172
|
+
return lines.join('\r\n') + '\r\n';
|
|
173
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import type { DistAttachmentKind, DistConnection, DistGraph } from '@tellegen/engine';
|
|
2
|
+
import type { SchematicGeometry } from './schematic-layout.js';
|
|
3
|
+
/** A stroked glyph: polylines in symbol units, x along the connection, y down. */
|
|
4
|
+
export type Glyph = [number, number][][];
|
|
5
|
+
/** Branch glyphs, centred on the branch and spanning `±INLINE_HALF` along it, where the drawn line
|
|
6
|
+
* is interrupted. Shapes follow the #177 specimens: a two-winding transformer as two overlapping
|
|
7
|
+
* circles (no conductor continuity), a generic switch as a blade between two contacts, open when
|
|
8
|
+
* the pole is open. A line has no glyph. */
|
|
9
|
+
export declare const INLINE_HALF = 1.4;
|
|
10
|
+
export declare function branchGlyph(kind: string, closed: boolean): Glyph | null;
|
|
11
|
+
/** Attachment glyphs, connected at the origin and extending along +x. A load is a demand arrow, a
|
|
12
|
+
* generator a machine circle with a G, an external source an AC supply circle with a sinusoid, an
|
|
13
|
+
* inverter-based resource a converter box (DC bar, diagonal, AC wave), and a shunt a general
|
|
14
|
+
* admittance block: BMOPF shunts can be conductive, so no capacitor is asserted. A shunt on a bus's
|
|
15
|
+
* neutral and nothing else is a neutral earth through an impedance (an MEN electrode, say): drawn
|
|
16
|
+
* as an impedance box in series with the IEC 60617 earth (three shrinking bars), not as a solid
|
|
17
|
+
* earth, since a shunt always has finite admittance. A perfectly grounded terminal is a solid earth:
|
|
18
|
+
* the earth symbol alone. */
|
|
19
|
+
export declare function attachmentGlyph(kind: SymbolKind): Glyph;
|
|
20
|
+
/** Conductor marks across a line, centred on it: one slash per phase conductor and a slash ending
|
|
21
|
+
* in a small circle for the neutral (the IEC 60617 convention). */
|
|
22
|
+
export declare function conductorTicks(phases: number, neutral: boolean): Glyph;
|
|
23
|
+
/** Phase conductors and whether a neutral runs, from a branch's conductor pairs: a conductor whose
|
|
24
|
+
* from terminal is the from bus's neutral is the neutral. */
|
|
25
|
+
export declare function conductorSet(graph: DistGraph, k: number): {
|
|
26
|
+
phases: number;
|
|
27
|
+
neutral: boolean;
|
|
28
|
+
};
|
|
29
|
+
/** Attachment symbols: the PowerIO kinds, plus `earth` for a shunt on a bus's neutral alone and
|
|
30
|
+
* `ground` for a bus's perfectly grounded terminals. */
|
|
31
|
+
export type SymbolKind = DistAttachmentKind | 'earth' | 'ground';
|
|
32
|
+
/** Drawing order of attachment kinds below a bus. A neutral earth shares the shunt slot (the
|
|
33
|
+
* engine layout reserves one slot per PowerIO kind). */
|
|
34
|
+
export declare const ATTACHMENT_ORDER: SymbolKind[];
|
|
35
|
+
export declare const ATTACHMENT_NAMES: Record<SymbolKind, string>;
|
|
36
|
+
/** Shunts of `bus` connected to its neutral terminal and to nothing else: neutral earths. */
|
|
37
|
+
export declare function neutralEarths(bus: DistGraph['buses'][number]): Set<string>;
|
|
38
|
+
/** One symbol: what it stands for and where to pick it. */
|
|
39
|
+
export interface EquipmentItem {
|
|
40
|
+
kind: SymbolKind | 'transformer' | 'switch';
|
|
41
|
+
/** Branch index for inline symbols, bus index for attachments. */
|
|
42
|
+
edge: number;
|
|
43
|
+
bus: number;
|
|
44
|
+
ids: string[];
|
|
45
|
+
x: number;
|
|
46
|
+
y: number;
|
|
47
|
+
/** An attachment's terminals in its own order and its connection, where the graph has them. */
|
|
48
|
+
terminals?: string[];
|
|
49
|
+
connection?: DistConnection;
|
|
50
|
+
/** A transformer's vector group and rating, such as "Dyn · 500 kVA". */
|
|
51
|
+
label?: string;
|
|
52
|
+
}
|
|
53
|
+
export interface EquipmentSymbols {
|
|
54
|
+
/** Glyph strokes: `x, y, 0` per segment end. */
|
|
55
|
+
sources: Float32Array;
|
|
56
|
+
targets: Float32Array;
|
|
57
|
+
items: EquipmentItem[];
|
|
58
|
+
/** The geometry with each branch line interrupted where its symbol sits. */
|
|
59
|
+
cut: SchematicGeometry;
|
|
60
|
+
/** Symbol unit in drawing units: glyphs are about two units across. */
|
|
61
|
+
unit: number;
|
|
62
|
+
/** Kinds drawn, for the legend. */
|
|
63
|
+
kinds: Set<EquipmentItem['kind']>;
|
|
64
|
+
/** Owner of each glyph stroke: an edge index, or `-(bus + 1)` for attachment strokes. */
|
|
65
|
+
owner: Int32Array;
|
|
66
|
+
/** Conductor marks on lines (drawn only when zoomed further in), with their edge per stroke. */
|
|
67
|
+
ticks: {
|
|
68
|
+
sources: Float32Array;
|
|
69
|
+
targets: Float32Array;
|
|
70
|
+
edge: Int32Array;
|
|
71
|
+
};
|
|
72
|
+
/** Per edge: `x, y` where its symbol or conductor marks sit (NaN when not drawn). */
|
|
73
|
+
anchors: Float32Array;
|
|
74
|
+
/** Per edge: unit direction of the segment carrying the anchor, pointing from the edge's
|
|
75
|
+
* `from` bus towards its `to` bus, and the free length upstream of the anchor on it. */
|
|
76
|
+
flowDir: Float32Array;
|
|
77
|
+
room: Float32Array;
|
|
78
|
+
/** Per edge: how many branches are drawn along exactly the same segment (1 when alone), as
|
|
79
|
+
* when an MV and an LV line hang between buses at the same positions. */
|
|
80
|
+
sharedRoute: Uint8Array;
|
|
81
|
+
/** Routes carrying more than one branch: their member edges, in drawing order. */
|
|
82
|
+
routeGroups: number[][];
|
|
83
|
+
/** Per edge: its index in `routeGroups`, or −1 when its route is its own. */
|
|
84
|
+
routeOf: Int32Array;
|
|
85
|
+
/** Per edge: the segment its symbol or marks sit on (−1 when not drawn). */
|
|
86
|
+
host: Int32Array;
|
|
87
|
+
}
|
|
88
|
+
/** Symbols for every branch and attachment of `graph` drawn on `geometry`.
|
|
89
|
+
*
|
|
90
|
+
* Inline symbols sit on the child-end segment of a tree route, a fixed distance from the child bus
|
|
91
|
+
* so they line up in columns; other branches carry theirs at the middle of their middle segment.
|
|
92
|
+
* Attachments hang from a riser below the bus, one slot per kind (the engine layout reserves that
|
|
93
|
+
* room), each on a short tick with its count when there are several. */
|
|
94
|
+
export declare function schematicEquipment(graph: DistGraph, geometry: SchematicGeometry): EquipmentSymbols;
|
|
95
|
+
/** An SVG path for a glyph in symbol units, for legends. */
|
|
96
|
+
export declare function glyphPath(glyph: Glyph, scale: number, dx?: number, dy?: number): string;
|
|
97
|
+
/** Power-flow chevrons: one per closed branch with a result, just upstream of its symbol or marks
|
|
98
|
+
* on the same segment, pointing the way active power flows (from the `from` end when the power
|
|
99
|
+
* into the branch there is positive). Branches carrying under `minPower` (in the units of `p`)
|
|
100
|
+
* get none. */
|
|
101
|
+
export declare function flowArrows(symbols: EquipmentSymbols, ends: ({
|
|
102
|
+
p: number;
|
|
103
|
+
} | null)[][], closed: boolean[], minPower?: number): {
|
|
104
|
+
sources: Float32Array;
|
|
105
|
+
targets: Float32Array;
|
|
106
|
+
edge: Int32Array;
|
|
107
|
+
};
|
|
108
|
+
/** Complex power into each element (loads, generators, IBRs, shunts) by element id, and for
|
|
109
|
+
* sources the power they draw (the negative of what they inject), so every attachment reads with
|
|
110
|
+
* the same convention: positive is consumption. Branch ports are left out: names are unique only
|
|
111
|
+
* within a family, and a line sharing a load's name must not add to it. */
|
|
112
|
+
export declare function elementPowers(result: {
|
|
113
|
+
element_ports: {
|
|
114
|
+
element: string;
|
|
115
|
+
kind?: string;
|
|
116
|
+
power_into_element: {
|
|
117
|
+
re: number;
|
|
118
|
+
im: number;
|
|
119
|
+
};
|
|
120
|
+
}[];
|
|
121
|
+
source_reactions: {
|
|
122
|
+
source: string;
|
|
123
|
+
power_into_network: {
|
|
124
|
+
re: number;
|
|
125
|
+
im: number;
|
|
126
|
+
};
|
|
127
|
+
}[];
|
|
128
|
+
}): Map<string, {
|
|
129
|
+
p: number;
|
|
130
|
+
q: number;
|
|
131
|
+
}>;
|
|
132
|
+
/** `12.3 kW 4.1 kvar` for a power in W and var. */
|
|
133
|
+
export declare function formatPower(p: number, q: number): string;
|