@tellegen/svelte 0.4.3 → 0.4.5
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 +45 -0
- package/dist/TellegenMap.svelte +63 -22
- package/dist/TellegenProvider.svelte +7 -4
- package/dist/TellegenShell.svelte +12 -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/DiagnosticLogView.svelte +176 -0
- package/dist/components/DiagnosticLogView.svelte.d.ts +11 -0
- package/dist/components/FormulationSelector.svelte +1 -1
- package/dist/components/LocalCaseDetails.svelte +18 -3
- package/dist/components/LogEntryView.svelte +188 -0
- package/dist/components/LogEntryView.svelte.d.ts +11 -0
- package/dist/components/LogPanel.svelte +25 -0
- package/dist/components/LogPanel.svelte.d.ts +3 -0
- 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/NotificationCenter.svelte +37 -6
- 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 +4 -0
- package/dist/components.js +4 -0
- package/dist/context.svelte.d.ts +2 -0
- package/dist/context.svelte.js +2 -0
- package/dist/controller.svelte.d.ts +40 -5
- package/dist/controller.svelte.js +230 -25
- 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 +3 -0
- package/dist/index.js +2 -0
- package/dist/log.svelte.d.ts +77 -0
- package/dist/log.svelte.js +149 -0
- package/dist/notices.svelte.d.ts +3 -0
- package/dist/state.svelte.d.ts +42 -5
- package/dist/state.svelte.js +56 -4
- package/dist/transformer-icon.d.ts +22 -13
- package/dist/transformer-icon.js +23 -13
- package/package.json +2 -2
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { ATTACHMENT_NAMES, ATTACHMENT_ORDER, neutralEarths } from './schematic-equipment.js';
|
|
2
|
+
export function treeWalk(tree, positions, stride = 3) {
|
|
3
|
+
const n = tree.parent.length;
|
|
4
|
+
const y = (i) => positions[i * stride + 1];
|
|
5
|
+
const x = (i) => positions[i * stride];
|
|
6
|
+
const order = (a, b) => y(a) - y(b) || x(a) - x(b) || a - b;
|
|
7
|
+
const children = Array.from({ length: n }, () => []);
|
|
8
|
+
for (let i = 0; i < n; i++)
|
|
9
|
+
if (tree.parent[i] !== i)
|
|
10
|
+
children[tree.parent[i]].push(i);
|
|
11
|
+
for (const list of children)
|
|
12
|
+
list.sort(order);
|
|
13
|
+
const roots = [...tree.roots].sort(order);
|
|
14
|
+
const siblings = (i) => (tree.parent[i] === i ? roots : children[tree.parent[i]]);
|
|
15
|
+
return {
|
|
16
|
+
children,
|
|
17
|
+
step(from, key) {
|
|
18
|
+
if (from < 0 || from >= n)
|
|
19
|
+
return roots[0] ?? -1;
|
|
20
|
+
switch (key) {
|
|
21
|
+
case 'parent':
|
|
22
|
+
return tree.parent[from];
|
|
23
|
+
case 'child': {
|
|
24
|
+
// Straight ahead: the child drawn nearest this bus's row.
|
|
25
|
+
const kids = children[from];
|
|
26
|
+
if (!kids.length)
|
|
27
|
+
return from;
|
|
28
|
+
return kids.reduce((best, k) => Math.abs(y(k) - y(from)) < Math.abs(y(best) - y(from)) ? k : best);
|
|
29
|
+
}
|
|
30
|
+
case 'previous':
|
|
31
|
+
case 'next': {
|
|
32
|
+
const list = siblings(from);
|
|
33
|
+
const at = list.indexOf(from);
|
|
34
|
+
const to = at + (key === 'next' ? 1 : -1);
|
|
35
|
+
return to >= 0 && to < list.length ? list[to] : from;
|
|
36
|
+
}
|
|
37
|
+
case 'root': {
|
|
38
|
+
let i = from;
|
|
39
|
+
while (tree.parent[i] !== i)
|
|
40
|
+
i = tree.parent[i];
|
|
41
|
+
return i;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/** A spoken description of bus `i` for the keyboard cursor: where it sits in the tree, what hangs
|
|
48
|
+
* off it, and an optional result note (for example the worst voltage against its band). */
|
|
49
|
+
export function describeBus(graph, tree, walk, i, note) {
|
|
50
|
+
const bus = graph.buses[i];
|
|
51
|
+
let root = i;
|
|
52
|
+
while (tree.parent[root] !== root)
|
|
53
|
+
root = tree.parent[root];
|
|
54
|
+
const depth = tree.depth[i];
|
|
55
|
+
const where = depth === 0
|
|
56
|
+
? bus.has_source
|
|
57
|
+
? 'source bus'
|
|
58
|
+
: 'not reached from a source'
|
|
59
|
+
: `${depth} step${depth === 1 ? '' : 's'} from ${graph.buses[root].id}`;
|
|
60
|
+
const via = tree.parentEdge[i] >= 0
|
|
61
|
+
? `, fed by ${graph.edges[tree.parentEdge[i]].kind} ${graph.edges[tree.parentEdge[i]].id}`
|
|
62
|
+
: '';
|
|
63
|
+
const onward = walk.children[i].length;
|
|
64
|
+
const counts = new Map();
|
|
65
|
+
const earths = neutralEarths(bus);
|
|
66
|
+
for (const list of Object.values(bus.terminal_attachments ?? {}))
|
|
67
|
+
for (const a of list) {
|
|
68
|
+
const kind = a.kind === 'shunt' && earths.has(a.id) ? 'earth' : a.kind;
|
|
69
|
+
counts.set(kind, (counts.get(kind) ?? new Set()).add(a.id));
|
|
70
|
+
}
|
|
71
|
+
if (bus.grounded?.length)
|
|
72
|
+
counts.set('ground', new Set(bus.grounded));
|
|
73
|
+
const attached = ATTACHMENT_ORDER.filter((k) => counts.has(k)).map((k) => {
|
|
74
|
+
const c = counts.get(k).size;
|
|
75
|
+
return `${c} ${ATTACHMENT_NAMES[k]}${c === 1 ? '' : 's'}`;
|
|
76
|
+
});
|
|
77
|
+
return [
|
|
78
|
+
`${bus.id}: ${where}${via}`,
|
|
79
|
+
`${onward} branch${onward === 1 ? '' : 'es'} onward`,
|
|
80
|
+
...(attached.length ? [attached.join(', ')] : []),
|
|
81
|
+
...(note ? [note] : [])
|
|
82
|
+
].join('. ');
|
|
83
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ResultSnapshot } from './snapshot.js';
|
|
2
|
+
export declare const VALUE_LABEL_BUDGET = 60;
|
|
3
|
+
export declare const MIN_LABEL_ZOOM = -0.2;
|
|
4
|
+
export type LabelState = 'violation' | 'warning' | 'normal';
|
|
5
|
+
export interface LabelCandidate {
|
|
6
|
+
text: string;
|
|
7
|
+
state: LabelState;
|
|
8
|
+
priority: number;
|
|
9
|
+
/** Screen position in CSS pixels. */
|
|
10
|
+
screen: [number, number];
|
|
11
|
+
selected?: boolean;
|
|
12
|
+
}
|
|
13
|
+
/** Greedy placement by (selected, severity, priority) on a coarse collision grid. The selection is
|
|
14
|
+
* always placed; others must fit inside the canvas margins and the budget. */
|
|
15
|
+
export declare function placeLabels<T extends LabelCandidate>(candidates: T[], { width, height, budget, box, margin }: {
|
|
16
|
+
width: number;
|
|
17
|
+
height: number;
|
|
18
|
+
budget?: number;
|
|
19
|
+
box?: [number, number];
|
|
20
|
+
margin?: [number, number, number, number];
|
|
21
|
+
}): T[];
|
|
22
|
+
/** The pair nearest a limit, shown in volts. Buses without declared limits rank last. */
|
|
23
|
+
export declare function busLabel(result: ResultSnapshot, bus: number): Omit<LabelCandidate, 'screen'> | null;
|
|
24
|
+
export declare function branchLabel(result: ResultSnapshot, branch: number, family: 'loading' | 'power'): Omit<LabelCandidate, 'screen'> | null;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { bandPosition } from './colour-modes.js';
|
|
2
|
+
import { loadingState } from './summary.js';
|
|
3
|
+
export const VALUE_LABEL_BUDGET = 60;
|
|
4
|
+
export const MIN_LABEL_ZOOM = -0.2;
|
|
5
|
+
const SEVERITY = { violation: 2, warning: 1, normal: 0 };
|
|
6
|
+
/** Greedy placement by (selected, severity, priority) on a coarse collision grid. The selection is
|
|
7
|
+
* always placed; others must fit inside the canvas margins and the budget. */
|
|
8
|
+
export function placeLabels(candidates, { width, height, budget = VALUE_LABEL_BUDGET, box = [64, 16], margin = [0, 40, 0, 24] }) {
|
|
9
|
+
const sorted = [...candidates].sort((a, b) => Number(!!b.selected) - Number(!!a.selected) ||
|
|
10
|
+
SEVERITY[b.state] - SEVERITY[a.state] ||
|
|
11
|
+
b.priority - a.priority);
|
|
12
|
+
const [cw, ch] = box;
|
|
13
|
+
const occupied = new Set();
|
|
14
|
+
const placed = [];
|
|
15
|
+
for (const c of sorted) {
|
|
16
|
+
if (placed.length >= budget)
|
|
17
|
+
break;
|
|
18
|
+
const [sx, sy] = c.screen;
|
|
19
|
+
const outside = sx < margin[3] ||
|
|
20
|
+
sx + cw > width - margin[1] ||
|
|
21
|
+
sy - ch < margin[0] ||
|
|
22
|
+
sy > height - margin[2];
|
|
23
|
+
if (!c.selected && outside)
|
|
24
|
+
continue;
|
|
25
|
+
const keys = [];
|
|
26
|
+
for (let gy = Math.floor((sy - ch) / ch); gy <= Math.floor(sy / ch); gy++)
|
|
27
|
+
for (let gx = Math.floor(sx / cw); gx <= Math.floor((sx + cw) / cw); gx++)
|
|
28
|
+
keys.push(`${gx},${gy}`);
|
|
29
|
+
if (!c.selected && keys.some((k) => occupied.has(k)))
|
|
30
|
+
continue;
|
|
31
|
+
for (const k of keys)
|
|
32
|
+
occupied.add(k);
|
|
33
|
+
placed.push(c);
|
|
34
|
+
}
|
|
35
|
+
return placed;
|
|
36
|
+
}
|
|
37
|
+
const short = (x) => Number(x.toPrecision(Math.abs(x) >= 1000 ? 5 : 4)).toLocaleString();
|
|
38
|
+
/** The pair nearest a limit, shown in volts. Buses without declared limits rank last. */
|
|
39
|
+
export function busLabel(result, bus) {
|
|
40
|
+
const pairs = result.buses[bus].pairs;
|
|
41
|
+
if (!pairs.length)
|
|
42
|
+
return null;
|
|
43
|
+
// Distance from mid-band; −1 without declared limits, so those pairs and buses rank last.
|
|
44
|
+
const rank = (p) => {
|
|
45
|
+
const band = bandPosition(p);
|
|
46
|
+
return band === null ? -1 : Math.abs(band);
|
|
47
|
+
};
|
|
48
|
+
let pick = pairs[0];
|
|
49
|
+
let t = rank(pick);
|
|
50
|
+
for (const p of pairs) {
|
|
51
|
+
const pt = rank(p);
|
|
52
|
+
if (pt > t)
|
|
53
|
+
[pick, t] = [p, pt];
|
|
54
|
+
}
|
|
55
|
+
return { text: `${short(pick.volts)} V`, state: t > 1 ? 'violation' : 'normal', priority: t };
|
|
56
|
+
}
|
|
57
|
+
export function branchLabel(result, branch, family) {
|
|
58
|
+
const row = result.branches[branch];
|
|
59
|
+
if (family === 'loading') {
|
|
60
|
+
const state = loadingState(row.loading);
|
|
61
|
+
return state === 'missing'
|
|
62
|
+
? null
|
|
63
|
+
: { text: `${row.loading.toFixed(1)} %`, state, priority: row.loading };
|
|
64
|
+
}
|
|
65
|
+
const end = row.ends[0] ?? row.ends[1];
|
|
66
|
+
return end
|
|
67
|
+
? { text: `${short(Math.abs(end.p))} kW`, state: 'normal', priority: Math.abs(end.p) }
|
|
68
|
+
: null;
|
|
69
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Shareable view state. Selections travel as entity keys (source identities), never array offsets. */
|
|
2
|
+
export interface CameraState {
|
|
3
|
+
target: [number, number, number];
|
|
4
|
+
zoom: number;
|
|
5
|
+
}
|
|
6
|
+
export declare function encodeCamera(c: {
|
|
7
|
+
target: ArrayLike<number>;
|
|
8
|
+
zoom: number;
|
|
9
|
+
}): string;
|
|
10
|
+
export declare function decodeCamera(text: string | null | undefined): CameraState | null;
|
|
11
|
+
/** Merge state into a URL without dropping unrelated parameters; `null` or `''` removes a key. */
|
|
12
|
+
export declare function withView(href: string | URL, state: Record<string, string | number | null | undefined>): URL;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export function encodeCamera(c) {
|
|
2
|
+
return [c.target[0].toFixed(1), c.target[1].toFixed(1), c.zoom.toFixed(2)].join(',');
|
|
3
|
+
}
|
|
4
|
+
export function decodeCamera(text) {
|
|
5
|
+
const parts = String(text ?? '')
|
|
6
|
+
.split(',')
|
|
7
|
+
.map(Number);
|
|
8
|
+
return parts.length === 3 && parts.every(Number.isFinite) && parts[2] >= -24 && parts[2] <= 5
|
|
9
|
+
? { target: [parts[0], parts[1], 0], zoom: parts[2] }
|
|
10
|
+
: null;
|
|
11
|
+
}
|
|
12
|
+
/** Merge state into a URL without dropping unrelated parameters; `null` or `''` removes a key. */
|
|
13
|
+
export function withView(href, state) {
|
|
14
|
+
const url = new URL(href);
|
|
15
|
+
for (const [k, v] of Object.entries(state)) {
|
|
16
|
+
if (v === null || v === undefined || v === '')
|
|
17
|
+
url.searchParams.delete(k);
|
|
18
|
+
else
|
|
19
|
+
url.searchParams.set(k, String(v));
|
|
20
|
+
}
|
|
21
|
+
return url;
|
|
22
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { GalvanicZone } from './galvanic-zones.js';
|
|
2
|
+
/** Voltage classes for drawing, by a zone's level in volts. Wide bands, so a single-phase winding's
|
|
3
|
+
* own rating (7.2 kV of a 12.47 kV system, 19.1 kV SWER) lands where its system does. */
|
|
4
|
+
export type VoltageClass = 'hv' | 'sub' | 'mv' | 'lv';
|
|
5
|
+
export declare const VOLTAGE_CLASSES: {
|
|
6
|
+
cls: VoltageClass;
|
|
7
|
+
name: string;
|
|
8
|
+
from: number;
|
|
9
|
+
}[];
|
|
10
|
+
/** The class of a zone: by its level when known; otherwise the zone holding a source counts as MV
|
|
11
|
+
* and the others as LV, as for line weights. */
|
|
12
|
+
export declare function voltageClass(zone: GalvanicZone, volts: number | null): VoltageClass;
|
|
13
|
+
/** A level as engineers name it: "11 kV", "400 V", "19.1 kV". */
|
|
14
|
+
export declare function levelLabel(volts: number): string;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const VOLTAGE_CLASSES = [
|
|
2
|
+
{ cls: 'hv', name: 'transmission', from: 100_000 },
|
|
3
|
+
{ cls: 'sub', name: 'subtransmission', from: 25_000 },
|
|
4
|
+
{ cls: 'mv', name: 'MV', from: 1_000 },
|
|
5
|
+
{ cls: 'lv', name: 'LV', from: 0 }
|
|
6
|
+
];
|
|
7
|
+
/** The class of a zone: by its level when known; otherwise the zone holding a source counts as MV
|
|
8
|
+
* and the others as LV, as for line weights. */
|
|
9
|
+
export function voltageClass(zone, volts) {
|
|
10
|
+
if (volts === null)
|
|
11
|
+
return zone.hasSource ? 'mv' : 'lv';
|
|
12
|
+
return VOLTAGE_CLASSES.find((c) => volts >= c.from).cls;
|
|
13
|
+
}
|
|
14
|
+
/** A level as engineers name it: "11 kV", "400 V", "19.1 kV". */
|
|
15
|
+
export function levelLabel(volts) {
|
|
16
|
+
const kv = volts / 1000;
|
|
17
|
+
if (kv < 1)
|
|
18
|
+
return `${Math.round(volts)} V`;
|
|
19
|
+
return `${Number(kv.toPrecision(4))} kV`;
|
|
20
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { getNoticeCenter, errorNoticeTitle } from './notices.svelte.js';
|
|
2
|
+
export { DiagnosticLog, LOG_SEVERITIES, errorEntry, errorStage, logSeverity, powerIoEntry, solveEntry } from './log.svelte.js';
|
|
3
|
+
export type { LogDocument, LogEntry, LogEntryInput, LogSeverity } from './log.svelte.js';
|
|
2
4
|
export { default as ModelDetails } from './components/ModelDetails.svelte';
|
|
3
5
|
export { default as PanelFrame } from './components/PanelFrame.svelte';
|
|
4
6
|
export { getPanelLayout } from './panels.svelte.js';
|
|
@@ -27,3 +29,4 @@ export type { BusSizing, CameraSnapshot, DiagramCameraSnapshot, DemandRangeMode,
|
|
|
27
29
|
export type { MultiView, PlacedMultiBus, PlacedMultiEdge } from './multiconductor.js';
|
|
28
30
|
export type { CaseFileSummary, DistAttachmentKind, DistEdgeKind, DistGraph, DistGraphAttachment, DistGraphBus, DistGraphEdge, DisplayPreview, Formulation, IngestedCase, IngestedDistCase, IngestedJsonDrop, JsonDropClassification, JsonDropKind, CapacityPlanBusWeightJson, CapacityPlanIterationJson, CapacityPlanOutcomeJson, CapacityPlanRatingChangeJson, CapacityPlanResultSummaryJson, CapacityPlanSpecJson, ImplicitObjectiveJson, SensTarget, Topology, TopologyBranch, TopologyBus } from '@tellegen/engine';
|
|
29
31
|
export { default as McResults } from './components/McResults.svelte';
|
|
32
|
+
export { default as McWorkbench } from './components/McWorkbench.svelte';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { getNoticeCenter, errorNoticeTitle } from './notices.svelte.js';
|
|
2
|
+
export { DiagnosticLog, LOG_SEVERITIES, errorEntry, errorStage, logSeverity, powerIoEntry, solveEntry } from './log.svelte.js';
|
|
2
3
|
export { default as ModelDetails } from './components/ModelDetails.svelte';
|
|
3
4
|
export { default as PanelFrame } from './components/PanelFrame.svelte';
|
|
4
5
|
export { getPanelLayout } from './panels.svelte.js';
|
|
@@ -17,3 +18,4 @@ export { distExtensionFormat } from './drop-classify.js';
|
|
|
17
18
|
export { caseDeltas, caseRatings, displayMetaFor, displaySeriesFor } from './display.js';
|
|
18
19
|
export { DEFAULT_FORMULATION, FORMULATIONS, BrowserStudy, Study, capabilities, createStudy, errorText, formatOf, classifyJson, ingestJsonDrop, ingestCase, isDisplayFile, isPermanentEngineFailure, parseDisplay, solveModule } from '@tellegen/engine';
|
|
19
20
|
export { default as McResults } from './components/McResults.svelte';
|
|
21
|
+
export { default as McWorkbench } from './components/McWorkbench.svelte';
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { PowerIoDiagnostic, SolveDiagnostic } from '@tellegen/engine';
|
|
2
|
+
/** How serious a finding is. PowerIO's `remark` and `note` both read as notes. */
|
|
3
|
+
export type LogSeverity = 'error' | 'warning' | 'note';
|
|
4
|
+
export declare const LOG_SEVERITIES: readonly LogSeverity[];
|
|
5
|
+
/** One finding to record. `stage` names what was happening (`Open`, `Solve`,
|
|
6
|
+
* `Export`, or a host's own step such as `Generate`); `source` names who
|
|
7
|
+
* reported it (`PowerIO`, `tellegen`, or a host's data source). */
|
|
8
|
+
export interface LogEntryInput {
|
|
9
|
+
severity: LogSeverity;
|
|
10
|
+
stage: string;
|
|
11
|
+
source: string;
|
|
12
|
+
message: string;
|
|
13
|
+
/** Stable machine code, such as `EMIT.MATPOWER.FIELD_DROPPED`. */
|
|
14
|
+
code?: string;
|
|
15
|
+
/** The case, file or download the finding belongs to. */
|
|
16
|
+
subject?: string;
|
|
17
|
+
/** The element the finding names, in PowerIO's spelling. */
|
|
18
|
+
target?: string;
|
|
19
|
+
buses?: number[];
|
|
20
|
+
/** What to do about it, when the reporter says. */
|
|
21
|
+
hint?: string;
|
|
22
|
+
details?: Record<string, unknown>;
|
|
23
|
+
}
|
|
24
|
+
export interface LogEntry extends LogEntryInput {
|
|
25
|
+
id: number;
|
|
26
|
+
/** How many times the same finding was reported. */
|
|
27
|
+
count: number;
|
|
28
|
+
/** Epoch milliseconds of the first and the latest report. */
|
|
29
|
+
first: number;
|
|
30
|
+
last: number;
|
|
31
|
+
}
|
|
32
|
+
/** The log as a file: what `DiagnosticLog.toJSON` returns and the log view downloads. */
|
|
33
|
+
export interface LogDocument {
|
|
34
|
+
format: 'tellegen.log';
|
|
35
|
+
version: 1;
|
|
36
|
+
written_at: string;
|
|
37
|
+
counts: Record<LogSeverity, number>;
|
|
38
|
+
entries: (Omit<LogEntry, 'first' | 'last'> & {
|
|
39
|
+
first: string;
|
|
40
|
+
last: string;
|
|
41
|
+
})[];
|
|
42
|
+
}
|
|
43
|
+
/** Every finding the app reported, in the order it first appeared. A finding
|
|
44
|
+
* reported again counts up in place instead of adding a row, so a re-solve or a
|
|
45
|
+
* second export of the same case does not flood the log. */
|
|
46
|
+
export declare class DiagnosticLog {
|
|
47
|
+
#private;
|
|
48
|
+
entries: LogEntry[];
|
|
49
|
+
/** Bumped on every write, including a repeat that only moves `last`. */
|
|
50
|
+
revision: number;
|
|
51
|
+
constructor(now?: () => number);
|
|
52
|
+
/** Record findings. With `repeat: false`, a finding already in the log keeps
|
|
53
|
+
* its count and takes the new details: use it for state that every solve
|
|
54
|
+
* restates, or to add the detail behind an error just recorded. */
|
|
55
|
+
record(input: LogEntryInput | readonly LogEntryInput[], options?: {
|
|
56
|
+
repeat?: boolean;
|
|
57
|
+
}): void;
|
|
58
|
+
clear(): void;
|
|
59
|
+
get counts(): Record<LogSeverity, number>;
|
|
60
|
+
toJSON(): LogDocument;
|
|
61
|
+
}
|
|
62
|
+
export declare function logSeverity(severity: PowerIoDiagnostic['severity']): LogSeverity;
|
|
63
|
+
interface EntryContext {
|
|
64
|
+
stage: string;
|
|
65
|
+
subject?: string;
|
|
66
|
+
}
|
|
67
|
+
/** A PowerIO reader or writer finding as a log entry. */
|
|
68
|
+
export declare function powerIoEntry(d: PowerIoDiagnostic, context: EntryContext): LogEntryInput;
|
|
69
|
+
/** A solver finding (islands, HVDC transfers) as a log entry. */
|
|
70
|
+
export declare function solveEntry(d: SolveDiagnostic, context: {
|
|
71
|
+
subject?: string;
|
|
72
|
+
}): LogEntryInput;
|
|
73
|
+
/** Which step an app error message comes from, by its wording. */
|
|
74
|
+
export declare function errorStage(message: string): string;
|
|
75
|
+
/** An app error as a log entry. */
|
|
76
|
+
export declare function errorEntry(message: string, detail?: string | null): LogEntryInput;
|
|
77
|
+
export {};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
export const LOG_SEVERITIES = ['error', 'warning', 'note'];
|
|
2
|
+
const MAX_ENTRIES = 500;
|
|
3
|
+
const keyOf = (e) => JSON.stringify([e.severity, e.stage, e.source, e.code, e.subject, e.target, e.message]);
|
|
4
|
+
/** Every finding the app reported, in the order it first appeared. A finding
|
|
5
|
+
* reported again counts up in place instead of adding a row, so a re-solve or a
|
|
6
|
+
* second export of the same case does not flood the log. */
|
|
7
|
+
export class DiagnosticLog {
|
|
8
|
+
entries = $state.raw([]);
|
|
9
|
+
/** Bumped on every write, including a repeat that only moves `last`. */
|
|
10
|
+
revision = $state(0);
|
|
11
|
+
#nextId = 0;
|
|
12
|
+
#now;
|
|
13
|
+
constructor(now = Date.now) {
|
|
14
|
+
this.#now = now;
|
|
15
|
+
}
|
|
16
|
+
/** Record findings. With `repeat: false`, a finding already in the log keeps
|
|
17
|
+
* its count and takes the new details: use it for state that every solve
|
|
18
|
+
* restates, or to add the detail behind an error just recorded. */
|
|
19
|
+
record(input, options = {}) {
|
|
20
|
+
const inputs = Array.isArray(input) ? input : [input];
|
|
21
|
+
if (!inputs.length)
|
|
22
|
+
return;
|
|
23
|
+
const repeat = options.repeat ?? true;
|
|
24
|
+
const now = this.#now();
|
|
25
|
+
const next = [...this.entries];
|
|
26
|
+
const index = new Map(next.map((entry, i) => [keyOf(entry), i]));
|
|
27
|
+
for (const item of inputs) {
|
|
28
|
+
if (!item.message)
|
|
29
|
+
continue;
|
|
30
|
+
const key = keyOf(item);
|
|
31
|
+
const at = index.get(key);
|
|
32
|
+
if (at !== undefined) {
|
|
33
|
+
const previous = next[at];
|
|
34
|
+
next[at] = repeat
|
|
35
|
+
? { ...previous, count: previous.count + 1, last: now }
|
|
36
|
+
: { ...previous, ...clean(item), last: now };
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
index.set(key, next.length);
|
|
40
|
+
next.push({
|
|
41
|
+
...clean(item),
|
|
42
|
+
id: ++this.#nextId,
|
|
43
|
+
count: 1,
|
|
44
|
+
first: now,
|
|
45
|
+
last: now
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
this.entries = next.length > MAX_ENTRIES ? next.slice(next.length - MAX_ENTRIES) : next;
|
|
49
|
+
this.revision++;
|
|
50
|
+
}
|
|
51
|
+
clear() {
|
|
52
|
+
this.entries = [];
|
|
53
|
+
this.revision++;
|
|
54
|
+
}
|
|
55
|
+
get counts() {
|
|
56
|
+
const counts = { error: 0, warning: 0, note: 0 };
|
|
57
|
+
for (const entry of this.entries)
|
|
58
|
+
counts[entry.severity]++;
|
|
59
|
+
return counts;
|
|
60
|
+
}
|
|
61
|
+
toJSON() {
|
|
62
|
+
return {
|
|
63
|
+
format: 'tellegen.log',
|
|
64
|
+
version: 1,
|
|
65
|
+
written_at: new Date(this.#now()).toISOString(),
|
|
66
|
+
counts: this.counts,
|
|
67
|
+
entries: this.entries.map((entry) => ({
|
|
68
|
+
...entry,
|
|
69
|
+
first: new Date(entry.first).toISOString(),
|
|
70
|
+
last: new Date(entry.last).toISOString()
|
|
71
|
+
}))
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function clean(input) {
|
|
76
|
+
const out = {
|
|
77
|
+
severity: input.severity,
|
|
78
|
+
stage: input.stage,
|
|
79
|
+
source: input.source,
|
|
80
|
+
message: input.message
|
|
81
|
+
};
|
|
82
|
+
if (input.code)
|
|
83
|
+
out.code = input.code;
|
|
84
|
+
if (input.subject)
|
|
85
|
+
out.subject = input.subject;
|
|
86
|
+
if (input.target)
|
|
87
|
+
out.target = input.target;
|
|
88
|
+
if (input.buses?.length)
|
|
89
|
+
out.buses = [...input.buses];
|
|
90
|
+
if (input.hint)
|
|
91
|
+
out.hint = input.hint;
|
|
92
|
+
if (input.details && Object.keys(input.details).length)
|
|
93
|
+
out.details = input.details;
|
|
94
|
+
return out;
|
|
95
|
+
}
|
|
96
|
+
export function logSeverity(severity) {
|
|
97
|
+
return severity === 'error' ? 'error' : severity === 'warning' ? 'warning' : 'note';
|
|
98
|
+
}
|
|
99
|
+
/** A PowerIO reader or writer finding as a log entry. */
|
|
100
|
+
export function powerIoEntry(d, context) {
|
|
101
|
+
return {
|
|
102
|
+
severity: logSeverity(d.severity),
|
|
103
|
+
stage: context.stage,
|
|
104
|
+
source: 'PowerIO',
|
|
105
|
+
code: d.code,
|
|
106
|
+
message: d.message,
|
|
107
|
+
subject: context.subject,
|
|
108
|
+
target: d.target,
|
|
109
|
+
hint: d.suggested_action,
|
|
110
|
+
details: d.details
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
// Solve findings that change what the numbers mean; the rest say how the
|
|
114
|
+
// solver read the case.
|
|
115
|
+
const SOLVE_WARNINGS = new Set(['island_deenergized', 'BUILD.HVDC.TERMINAL_INACTIVE']);
|
|
116
|
+
/** A solver finding (islands, HVDC transfers) as a log entry. */
|
|
117
|
+
export function solveEntry(d, context) {
|
|
118
|
+
return {
|
|
119
|
+
severity: SOLVE_WARNINGS.has(d.code) ? 'warning' : 'note',
|
|
120
|
+
stage: 'Solve',
|
|
121
|
+
source: 'tellegen',
|
|
122
|
+
code: d.code,
|
|
123
|
+
message: d.message,
|
|
124
|
+
subject: context.subject,
|
|
125
|
+
buses: d.buses
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/** Which step an app error message comes from, by its wording. */
|
|
129
|
+
export function errorStage(message) {
|
|
130
|
+
if (/export|could not save|download/i.test(message))
|
|
131
|
+
return 'Export';
|
|
132
|
+
if (/coordinat|geographic|layout|placement|placed/i.test(message))
|
|
133
|
+
return 'Coordinates';
|
|
134
|
+
if (/infeasib|converg|solv|OPF|power flow|calculation|sensitivity|webassembly|study/i.test(message))
|
|
135
|
+
return 'Solve';
|
|
136
|
+
if (/pars|read|file|format|module/i.test(message))
|
|
137
|
+
return 'Open';
|
|
138
|
+
return 'App';
|
|
139
|
+
}
|
|
140
|
+
/** An app error as a log entry. */
|
|
141
|
+
export function errorEntry(message, detail) {
|
|
142
|
+
return {
|
|
143
|
+
severity: 'error',
|
|
144
|
+
stage: errorStage(message),
|
|
145
|
+
source: 'tellegen',
|
|
146
|
+
message,
|
|
147
|
+
details: detail ? { detail } : undefined
|
|
148
|
+
};
|
|
149
|
+
}
|
package/dist/notices.svelte.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import type { LogEntryInput } from './log.svelte.js';
|
|
1
2
|
export interface NoticeInput {
|
|
2
3
|
title: string;
|
|
3
4
|
details: string;
|
|
5
|
+
/** Structured findings behind `details`; the notice lists them when present. */
|
|
6
|
+
entries?: LogEntryInput[];
|
|
4
7
|
kind?: 'error' | 'warning';
|
|
5
8
|
retry?: () => void;
|
|
6
9
|
canRetry?: () => boolean;
|
package/dist/state.svelte.d.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import type { CaseSummary, DemandDeltas, Network, NetworkBranch, NetworkBus, SensitivityColumn, Solution, SolveIteration } from './api.js';
|
|
2
2
|
import { type StudyView, type BranchRatingDeltas, type BrowserMcPfSession, type CaseFileSummary, type DistGraph, type Formulation, type IngestedDistCase, type McPfCallTiming, type McPfResult, type McPfSummary, type McLoadBranchState, type McLoadPowerEdit, type McStudySnapshot, type SensTarget, type Topology } from '@tellegen/engine';
|
|
3
3
|
import type { MultiView } from './multiconductor.js';
|
|
4
|
-
|
|
4
|
+
import { DiagnosticLog } from './log.svelte.js';
|
|
5
|
+
import { type GraphInputs } from './diagram/graph-inputs.js';
|
|
6
|
+
import { type SearchIndex } from './diagram/search.js';
|
|
7
|
+
import { type ResultSnapshot } from './diagram/snapshot.js';
|
|
8
|
+
export type SolveBackend = 'clarabel-wasm' | 'clarabel-wasm-server-sensitivity' | 'pounce-wasi' | 'rust-server';
|
|
5
9
|
/** A map framing request: a case id, 'all', or one branch to center. */
|
|
6
10
|
export type FrameTarget = string | 'all' | {
|
|
7
11
|
caseId: string;
|
|
@@ -117,6 +121,8 @@ export declare class LocalCase {
|
|
|
117
121
|
solving: boolean;
|
|
118
122
|
solveMs: number | null;
|
|
119
123
|
solveBackend: SolveBackend | null;
|
|
124
|
+
/** Short solver-specific diagnostic shown beside the elapsed time. */
|
|
125
|
+
solveDetail: string | null;
|
|
120
126
|
solveFallbackReason: string | null;
|
|
121
127
|
/** Monotone token: only the latest solve may write this case. */
|
|
122
128
|
solveSeq: number;
|
|
@@ -155,6 +161,8 @@ export declare class CaseState {
|
|
|
155
161
|
solving: boolean;
|
|
156
162
|
solveMs: number | null;
|
|
157
163
|
solveBackend: SolveBackend | null;
|
|
164
|
+
/** Short solver-specific diagnostic shown beside the elapsed time. */
|
|
165
|
+
solveDetail: string | null;
|
|
158
166
|
solveFallbackReason: string | null;
|
|
159
167
|
/** Monotone token: only the latest solve may write this case. */
|
|
160
168
|
solveSeq: number;
|
|
@@ -231,6 +239,32 @@ export declare class MulticonductorCase {
|
|
|
231
239
|
/** The selected edge id, whose conductor pairing expands in the panel;
|
|
232
240
|
* mutually exclusive with selectedBusId. */
|
|
233
241
|
selectedEdgeId: string | null;
|
|
242
|
+
/** What the pointer is over in any linked view (schematic, profile, tables); highlighted in
|
|
243
|
+
* the others. Transient: never saved or put in links. */
|
|
244
|
+
hovered: {
|
|
245
|
+
kind: "bus" | "branch";
|
|
246
|
+
id: string;
|
|
247
|
+
} | null;
|
|
248
|
+
/** Bumped to ask the views to bring the current selection into view again (an issue clicked
|
|
249
|
+
* that is already selected). */
|
|
250
|
+
revealSeq: number;
|
|
251
|
+
/** The full operating point read for whole-network views (overview, schematic); null until read,
|
|
252
|
+
* and cleared when a large case's operating point changes. */
|
|
253
|
+
overviewResult: McPfResult | null;
|
|
254
|
+
overviewLoading: boolean;
|
|
255
|
+
/** The graph's buses and branches as the overview and the diagram read them: rebuilt when the
|
|
256
|
+
* graph is replaced, never after a solve. Both views read this one copy. */
|
|
257
|
+
overviewInputs: GraphInputs | null;
|
|
258
|
+
/** Search over the graph's buses and branches, rebuilt with `overviewInputs`. */
|
|
259
|
+
overviewSearch: SearchIndex | null;
|
|
260
|
+
/** `overviewResult` projected onto the graph, built once per operating point and shared by the
|
|
261
|
+
* overview and the diagram; null while there is none. */
|
|
262
|
+
overviewSnapshot: ResultSnapshot | null;
|
|
263
|
+
/** Show this case as a schematic instead of the map. */
|
|
264
|
+
schematic: boolean;
|
|
265
|
+
/** The diagram's last geographic view as a map camera, for the map to continue from when the
|
|
266
|
+
* diagram closes. Plain, not reactive: it is read once, on close. */
|
|
267
|
+
lastGeoView: CameraSnapshot | null;
|
|
234
268
|
constructor(init: {
|
|
235
269
|
id: string;
|
|
236
270
|
label: string;
|
|
@@ -264,6 +298,9 @@ export interface StudyDisplaySnapshot {
|
|
|
264
298
|
export declare class AppState {
|
|
265
299
|
#private;
|
|
266
300
|
camera: CameraSnapshot | null;
|
|
301
|
+
/** Set when a view hands its camera to the map it returns to (the diagram closing from
|
|
302
|
+
* geographic positions): the re-created map starts at `camera` instead of framing the case. */
|
|
303
|
+
cameraHandoff: boolean;
|
|
267
304
|
cameraRequest: CameraSnapshot | null;
|
|
268
305
|
cameraSeq: number;
|
|
269
306
|
diagramCamera: DiagramCameraSnapshot | null;
|
|
@@ -303,12 +340,12 @@ export declare class AppState {
|
|
|
303
340
|
/** Re-runs the operation behind the current `error`, when one applies. Every
|
|
304
341
|
* write to `error` clears it, so a retry op can never outlive its message. */
|
|
305
342
|
errorRetry: (() => void) | null;
|
|
306
|
-
/**
|
|
307
|
-
|
|
308
|
-
* `error` clears it, as it clears `errorRetry`. */
|
|
309
|
-
errorDetail: string | null;
|
|
343
|
+
/** Every error and engine finding of this session, structured; see `DiagnosticLog`. */
|
|
344
|
+
readonly log: DiagnosticLog;
|
|
310
345
|
get error(): string | null;
|
|
311
346
|
set error(message: string | null);
|
|
347
|
+
get errorDetail(): string | null;
|
|
348
|
+
set errorDetail(detail: string | null);
|
|
312
349
|
/** Case files parsed in the browser via the powerio wasm module. */
|
|
313
350
|
localCases: LocalCase[];
|
|
314
351
|
/** Local case the panel shows; clicking a bundled case or a bus clears it. */
|