@tellegen/svelte 0.2.0 → 0.4.0
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 +269 -5
- package/dist/Sparkline.svelte +2 -2
- package/dist/TellegenMap.svelte +518 -202
- package/dist/TellegenMap.svelte.d.ts +45 -0
- package/dist/TellegenProvider.svelte +40 -4
- package/dist/TellegenShell.svelte +24 -46
- package/dist/TellegenViewer.svelte +12 -2
- package/dist/api.d.ts +18 -3
- package/dist/api.js +8 -2
- package/dist/basemap.d.ts +192 -0
- package/dist/basemap.js +259 -0
- package/dist/colors.d.ts +25 -7
- package/dist/colors.js +39 -13
- package/dist/components/AppFooter.svelte +3 -2
- package/dist/components/AppHeader.svelte +44 -19
- package/dist/components/BindingLines.svelte +4 -7
- package/dist/components/BusPicker.svelte +11 -5
- package/dist/components/ControlPanel.svelte +106 -343
- package/dist/components/DemandSlider.svelte +36 -29
- package/dist/components/DiagramCanvas.svelte +486 -0
- package/dist/components/DiagramCanvas.svelte.d.ts +13 -0
- package/dist/components/DisplayControls.svelte +8 -2
- package/dist/components/DropZone.svelte +1 -1
- package/dist/components/FormulationSelector.svelte +9 -4
- package/dist/components/LocalCaseDetails.svelte +61 -48
- package/dist/components/McResults.svelte +428 -0
- package/dist/components/McResults.svelte.d.ts +17 -0
- package/dist/components/ModelDetails.svelte +230 -0
- package/dist/components/ModelDetails.svelte.d.ts +7 -0
- package/dist/components/MulticonductorDetails.svelte +232 -83
- package/dist/components/NetworkStats.svelte +20 -18
- package/dist/components/NotificationCenter.svelte +245 -0
- package/dist/components/NotificationCenter.svelte.d.ts +3 -0
- package/dist/components/PanelCard.svelte +384 -0
- package/dist/components/PanelCard.svelte.d.ts +9 -0
- package/dist/components/PanelFrame.svelte +46 -0
- package/dist/components/PanelFrame.svelte.d.ts +16 -0
- package/dist/components/PanelHost.svelte +237 -0
- package/dist/components/PanelHost.svelte.d.ts +6 -0
- package/dist/components/PlacementCue.svelte +1 -1
- package/dist/components/PowerFlowDetails.svelte +99 -0
- package/dist/components/PowerFlowDetails.svelte.d.ts +3 -0
- package/dist/components/RatingSlider.svelte +19 -14
- package/dist/components/RestoreDefaultsButton.svelte +1 -1
- package/dist/components/SavedNetworkDetails.svelte +214 -0
- package/dist/components/SavedNetworkDetails.svelte.d.ts +3 -0
- package/dist/components/SensitivityReadout.svelte +26 -18
- package/dist/components/ShellEnvironment.svelte +51 -0
- package/dist/components/ShellEnvironment.svelte.d.ts +18 -0
- package/dist/components/SizeLegend.svelte +2 -2
- package/dist/components/SolveCard.svelte +48 -51
- package/dist/components/TopMovers.svelte +9 -4
- package/dist/components/TopMovers.svelte.d.ts +2 -17
- package/dist/components.d.ts +5 -0
- package/dist/components.js +5 -0
- package/dist/context.svelte.d.ts +8 -1
- package/dist/context.svelte.js +8 -3
- package/dist/controller.svelte.d.ts +117 -36
- package/dist/controller.svelte.js +1133 -253
- package/dist/diagram-labels.d.ts +9 -0
- package/dist/diagram-labels.js +40 -0
- package/dist/display.d.ts +3 -7
- package/dist/display.js +31 -27
- package/dist/format.d.ts +9 -0
- package/dist/format.js +17 -4
- package/dist/index.d.ts +15 -5
- package/dist/index.js +9 -2
- package/dist/map.d.ts +4 -2
- package/dist/map.js +2 -1
- package/dist/maplibre-worker.d.ts +5 -0
- package/dist/maplibre-worker.dev.d.ts +11 -0
- package/dist/maplibre-worker.dev.js +15 -0
- package/dist/maplibre-worker.js +8 -0
- package/dist/multiconductor.d.ts +11 -2
- package/dist/multiconductor.js +107 -14
- package/dist/notices.svelte.d.ts +29 -0
- package/dist/notices.svelte.js +86 -0
- package/dist/panels.svelte.d.ts +71 -0
- package/dist/panels.svelte.js +245 -0
- package/dist/slider.d.ts +12 -0
- package/dist/slider.js +31 -0
- package/dist/state.svelte.d.ts +107 -23
- package/dist/state.svelte.js +106 -20
- package/dist/styles.css +59 -6
- package/dist/synthetic-layout.js +1 -0
- package/package.json +29 -11
- package/dist/study-package.d.ts +0 -8
- package/dist/study-package.js +0 -17
package/dist/state.svelte.d.ts
CHANGED
|
@@ -1,14 +1,28 @@
|
|
|
1
1
|
import type { CaseSummary, DemandDeltas, Network, NetworkBranch, NetworkBus, SensitivityColumn, Solution, SolveIteration } from './api.js';
|
|
2
|
-
import { type BranchRatingDeltas, type CaseFileSummary, type DistGraph, type IngestedDistCase, type SensTarget, type Topology } from '@tellegen/engine';
|
|
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
|
export type SolveBackend = 'clarabel-wasm' | 'clarabel-wasm-server-sensitivity' | 'rust-server';
|
|
5
5
|
/** A map framing request: a case id, 'all', or one branch to center. */
|
|
6
6
|
export type FrameTarget = string | 'all' | {
|
|
7
7
|
caseId: string;
|
|
8
8
|
branchId: number;
|
|
9
|
+
} | {
|
|
10
|
+
caseId: string;
|
|
11
|
+
busId: number;
|
|
9
12
|
};
|
|
13
|
+
export interface CameraSnapshot {
|
|
14
|
+
center: [number, number];
|
|
15
|
+
zoom: number;
|
|
16
|
+
bearing: number;
|
|
17
|
+
pitch: number;
|
|
18
|
+
}
|
|
19
|
+
export interface DiagramCameraSnapshot {
|
|
20
|
+
caseId: string;
|
|
21
|
+
center: [number, number];
|
|
22
|
+
scale: number;
|
|
23
|
+
}
|
|
10
24
|
export type DemandRangeMode = 'local' | 'full';
|
|
11
|
-
export type DisplayMode = '
|
|
25
|
+
export type DisplayMode = 'price' | 'angle' | 'voltage';
|
|
12
26
|
/** The case a removal promoted to active, so the caller can hydrate it: a backend
|
|
13
27
|
* case needs its network/solution loaded, a local needs a browser solve, and
|
|
14
28
|
* `none` means nothing remains (or the removal left the active case untouched). */
|
|
@@ -34,6 +48,7 @@ export interface LocalSubstations {
|
|
|
34
48
|
}
|
|
35
49
|
type CoordsKind = 'file' | 'synthetic_pending' | 'synthetic' | 'manual' | 'geofile';
|
|
36
50
|
type LocalView = {
|
|
51
|
+
coordinate_space?: 'geographic' | 'diagram';
|
|
37
52
|
buses: NetworkBus[];
|
|
38
53
|
branches: NetworkBranch[];
|
|
39
54
|
};
|
|
@@ -42,8 +57,11 @@ export interface LocalCaseInit {
|
|
|
42
57
|
id: string;
|
|
43
58
|
label: string;
|
|
44
59
|
fileName: string;
|
|
60
|
+
formulation?: Formulation;
|
|
61
|
+
declaredFormulation?: Formulation;
|
|
45
62
|
summary?: CaseFileSummary | null;
|
|
46
|
-
|
|
63
|
+
/** Retained PowerIO module used to construct solver studies. */
|
|
64
|
+
studyInputJson?: string;
|
|
47
65
|
topology?: Topology;
|
|
48
66
|
coordsKind?: CoordsKind;
|
|
49
67
|
view?: LocalView | null;
|
|
@@ -59,10 +77,11 @@ export declare class LocalCase {
|
|
|
59
77
|
readonly id: string;
|
|
60
78
|
readonly label: string;
|
|
61
79
|
readonly fileName: string;
|
|
80
|
+
readonly declaredFormulation?: Formulation;
|
|
62
81
|
/** Case stats; null for a .pwd display only entry. */
|
|
63
82
|
summary: CaseFileSummary | null;
|
|
64
|
-
/**
|
|
65
|
-
|
|
83
|
+
/** Generation 2 PowerIO IR used for display edits and solver studies. */
|
|
84
|
+
studyInputJson: string | undefined;
|
|
66
85
|
/** Topology for synthetic placement when the file has no coordinates. */
|
|
67
86
|
topology: Topology | undefined;
|
|
68
87
|
coordsKind: CoordsKind | undefined;
|
|
@@ -72,6 +91,13 @@ export declare class LocalCase {
|
|
|
72
91
|
lon: number;
|
|
73
92
|
lat: number;
|
|
74
93
|
} | undefined;
|
|
94
|
+
diagram: {
|
|
95
|
+
view: LocalView;
|
|
96
|
+
layer: string;
|
|
97
|
+
name: string;
|
|
98
|
+
warnings: string[];
|
|
99
|
+
} | null;
|
|
100
|
+
displayMode: 'geographic' | 'diagram';
|
|
75
101
|
geoSource: string | undefined;
|
|
76
102
|
geoWarnings: string[] | undefined;
|
|
77
103
|
network: Network | null;
|
|
@@ -81,8 +107,7 @@ export declare class LocalCase {
|
|
|
81
107
|
deltas: DemandDeltas;
|
|
82
108
|
/** Committed branch rating deltas (MW from base, keyed by branch). */
|
|
83
109
|
ratings: BranchRatingDeltas;
|
|
84
|
-
/** The
|
|
85
|
-
* full AC OPF, or the SOCWR relaxation. Changing it rebuilds the Study. */
|
|
110
|
+
/** The selected calculation. Changing it rebuilds the numerical Study. */
|
|
86
111
|
formulation: import("@tellegen/engine").BrowserFormulation;
|
|
87
112
|
iterations: SolveIteration[];
|
|
88
113
|
solving: boolean;
|
|
@@ -91,6 +116,8 @@ export declare class LocalCase {
|
|
|
91
116
|
solveFallbackReason: string | null;
|
|
92
117
|
/** Monotone token: only the latest solve may write this case. */
|
|
93
118
|
solveSeq: number;
|
|
119
|
+
/** Monotone generation for external optimistic concurrency. */
|
|
120
|
+
revisionGeneration: number;
|
|
94
121
|
/** Monotone token: only the latest sensitivity request may write this case. */
|
|
95
122
|
sensitivitySeq: number;
|
|
96
123
|
predictedObjective: number | null;
|
|
@@ -104,9 +131,10 @@ export declare class LocalCase {
|
|
|
104
131
|
export declare class CaseState {
|
|
105
132
|
readonly id: string;
|
|
106
133
|
readonly name: string;
|
|
134
|
+
readonly unavailableReason: string | null;
|
|
107
135
|
network: Network | null;
|
|
108
|
-
/**
|
|
109
|
-
|
|
136
|
+
/** Retained PowerIO module for the browser solver; fetched lazily. */
|
|
137
|
+
studyInputJson: string | null;
|
|
110
138
|
/** Boot solution at base demand; never changes. */
|
|
111
139
|
baseSolution: Solution | null;
|
|
112
140
|
/** Exact solution at the current committed perturbation. */
|
|
@@ -126,6 +154,8 @@ export declare class CaseState {
|
|
|
126
154
|
solveFallbackReason: string | null;
|
|
127
155
|
/** Monotone token: only the latest solve may write this case. */
|
|
128
156
|
solveSeq: number;
|
|
157
|
+
/** Monotone generation for external optimistic concurrency. */
|
|
158
|
+
revisionGeneration: number;
|
|
129
159
|
/** Monotone token: only the latest sensitivity request may write this case. */
|
|
130
160
|
sensitivitySeq: number;
|
|
131
161
|
/** Closer for this case's in-flight server solve stream, if any. Owned per
|
|
@@ -140,27 +170,52 @@ export declare class CaseState {
|
|
|
140
170
|
/** A case the solver can run: a server-backed case or a browser-parsed local case. */
|
|
141
171
|
export type SolvableCase = CaseState | LocalCase;
|
|
142
172
|
/** The multiconductor ingest payload without the graph: the summary counts,
|
|
143
|
-
* connected load/generation, coordinate provenance, and
|
|
173
|
+
* connected load/generation, coordinate provenance, and diagnostics. */
|
|
144
174
|
export type MultiCaseSummary = Omit<IngestedDistCase, 'graph'>;
|
|
145
|
-
/**
|
|
146
|
-
* straight on; `planar`/`synthetic` need a map center, so they sit `pending`
|
|
147
|
-
* until the user places them. */
|
|
175
|
+
/** Geographic coordinates use the map; other positions use the diagram canvas. */
|
|
148
176
|
export type MultiCoordsKind = 'geographic' | 'planar' | 'synthetic';
|
|
149
|
-
/**
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
|
|
177
|
+
/** Where one interactive load edit spent its time, in milliseconds: engine
|
|
178
|
+
* (solve and summary), transfer, and parsing as recorded by the session, and
|
|
179
|
+
* the total from the start of the solve until the result state is updated. */
|
|
180
|
+
export interface McEditTiming extends McPfCallTiming {
|
|
181
|
+
total_ms: number;
|
|
182
|
+
}
|
|
183
|
+
/** A conductor-resolved case with retained electrical inputs and terminal results. */
|
|
153
184
|
export declare class MulticonductorCase {
|
|
154
185
|
readonly id: string;
|
|
155
186
|
readonly label: string;
|
|
156
187
|
readonly fileName: string;
|
|
188
|
+
moduleJson: string | null;
|
|
189
|
+
geoLayer: string | null;
|
|
190
|
+
geoWarnings: string[];
|
|
191
|
+
/** Summary of the displayed operating point: from the live session after
|
|
192
|
+
* each solve or load edit, or derived from a stored full result. */
|
|
193
|
+
result: McPfSummary | null;
|
|
194
|
+
/** The complete result when no live session holds the operating point: a
|
|
195
|
+
* saved Study, a solve without sessions, or after coordinates are attached. */
|
|
196
|
+
mcFullResult: McPfResult | null;
|
|
197
|
+
/** Latency breakdown of the latest load-edit solve. */
|
|
198
|
+
mcTiming: McEditTiming | null;
|
|
199
|
+
mcSnapshot: McStudySnapshot | null;
|
|
200
|
+
mcSession: BrowserMcPfSession | null;
|
|
201
|
+
mcLoadBranches: McLoadBranchState[];
|
|
202
|
+
mcLoadEdits: McLoadPowerEdit[];
|
|
203
|
+
mcEditRevision: number;
|
|
204
|
+
mcEditRunning: boolean;
|
|
205
|
+
mcEditTimer: ReturnType<typeof setTimeout> | null;
|
|
206
|
+
mcSavedAt: string | null;
|
|
207
|
+
solving: boolean;
|
|
208
|
+
solveMs: number | null;
|
|
209
|
+
solveSeq: number;
|
|
210
|
+
revisionGeneration: number;
|
|
211
|
+
solveAbort: AbortController | null;
|
|
157
212
|
/** Summary counts and coordinate provenance from the parse. */
|
|
158
213
|
summary: MultiCaseSummary | null;
|
|
159
214
|
/** The render-ready bus/terminal graph. */
|
|
160
215
|
graph: DistGraph | null;
|
|
161
216
|
/** Placement kind resolved at ingest from the case's coordinate space. */
|
|
162
217
|
coordsKind: MultiCoordsKind;
|
|
163
|
-
/** The
|
|
218
|
+
/** The map or diagram positions. */
|
|
164
219
|
view: MultiView | null;
|
|
165
220
|
syntheticCenter: {
|
|
166
221
|
lon: number;
|
|
@@ -181,6 +236,8 @@ export declare class MulticonductorCase {
|
|
|
181
236
|
coordsKind: MultiCoordsKind;
|
|
182
237
|
view?: MultiView | null;
|
|
183
238
|
});
|
|
239
|
+
get mcPfReason(): string | null;
|
|
240
|
+
get mcPfSupported(): boolean;
|
|
184
241
|
/** Whether the case is placed and ready to render. */
|
|
185
242
|
get placed(): boolean;
|
|
186
243
|
/** The selected bus's placed detail, or null. */
|
|
@@ -188,8 +245,30 @@ export declare class MulticonductorCase {
|
|
|
188
245
|
/** The selected edge's placed detail, or null. */
|
|
189
246
|
get selectedEdge(): import("./multiconductor.js").PlacedMultiEdge | null;
|
|
190
247
|
}
|
|
248
|
+
export interface StudyDisplaySnapshot {
|
|
249
|
+
id: string;
|
|
250
|
+
studyId: string;
|
|
251
|
+
caseId: string;
|
|
252
|
+
revision: number;
|
|
253
|
+
label: string;
|
|
254
|
+
formulation: Formulation;
|
|
255
|
+
network: Network;
|
|
256
|
+
solution: StudyView | null;
|
|
257
|
+
inputJson?: string;
|
|
258
|
+
baseDemandMw?: Record<string, number>;
|
|
259
|
+
}
|
|
191
260
|
export declare class AppState {
|
|
192
261
|
#private;
|
|
262
|
+
camera: CameraSnapshot | null;
|
|
263
|
+
cameraRequest: CameraSnapshot | null;
|
|
264
|
+
cameraSeq: number;
|
|
265
|
+
diagramCamera: DiagramCameraSnapshot | null;
|
|
266
|
+
diagramCameraRequest: DiagramCameraSnapshot | null;
|
|
267
|
+
diagramCameraSeq: number;
|
|
268
|
+
requestDiagramCamera(caseId: string, snapshot: Omit<DiagramCameraSnapshot, 'caseId'>): void;
|
|
269
|
+
requestCamera(snapshot: CameraSnapshot): void;
|
|
270
|
+
/** Saved Study inspection is independent of the editable case and its solution. */
|
|
271
|
+
studyView: StudyDisplaySnapshot | null;
|
|
193
272
|
cases: CaseState[];
|
|
194
273
|
activeCaseId: string | null;
|
|
195
274
|
/** Selected bus in the active case. */
|
|
@@ -200,24 +279,29 @@ export declare class AppState {
|
|
|
200
279
|
previewDeltaMw: number | null;
|
|
201
280
|
/** Live rating slider value (MW from base) before commit; null when idle. */
|
|
202
281
|
previewRatingMw: number | null;
|
|
203
|
-
/** True while the demand control should keep the map in
|
|
282
|
+
/** True while the demand control should keep the map in nodal value preview mode. */
|
|
204
283
|
previewActive: boolean;
|
|
205
|
-
/** Engine first
|
|
206
|
-
* ($/MWh) per bus at the previewed edit, scoped to the case and selection
|
|
284
|
+
/** Engine first order nodal value preview for the live drag, scoped to the case and selection
|
|
207
285
|
* target (bus or branch) it was computed for. Set by the Study path; null when
|
|
208
286
|
* no Study preview applies (the map then falls back to the JS
|
|
209
287
|
* sensitivity-times-step preview). Reassigned wholesale, so $state.raw. */
|
|
210
|
-
|
|
288
|
+
previewPrices: {
|
|
211
289
|
caseId: string;
|
|
212
290
|
target: SensTarget;
|
|
213
291
|
delta: Map<number, number>;
|
|
292
|
+
units: string;
|
|
214
293
|
} | null;
|
|
215
294
|
demandRangeMode: DemandRangeMode;
|
|
216
295
|
displayMode: DisplayMode;
|
|
217
296
|
sensitivityLoading: boolean;
|
|
297
|
+
errorRevision: number;
|
|
218
298
|
/** Re-runs the operation behind the current `error`, when one applies. Every
|
|
219
299
|
* write to `error` clears it, so a retry op can never outlive its message. */
|
|
220
300
|
errorRetry: (() => void) | null;
|
|
301
|
+
/** Technical detail behind the current `error`, when the message itself is
|
|
302
|
+
* written for people; notices keep it in their disclosure. Every write to
|
|
303
|
+
* `error` clears it, as it clears `errorRetry`. */
|
|
304
|
+
errorDetail: string | null;
|
|
221
305
|
get error(): string | null;
|
|
222
306
|
set error(message: string | null);
|
|
223
307
|
/** Case files parsed in the browser via the powerio wasm module. */
|
|
@@ -225,7 +309,7 @@ export declare class AppState {
|
|
|
225
309
|
/** Local case the panel shows; clicking a bundled case or a bus clears it. */
|
|
226
310
|
activeLocalId: string | null;
|
|
227
311
|
placingLocalId: string | null;
|
|
228
|
-
/** Multiconductor
|
|
312
|
+
/** Multiconductor cases with retained inputs and AC power flow results. */
|
|
229
313
|
multiCases: MulticonductorCase[];
|
|
230
314
|
/** Multiconductor case the panel shows; mutually exclusive with the solvable
|
|
231
315
|
* active ids. */
|
package/dist/state.svelte.js
CHANGED
|
@@ -12,16 +12,19 @@ export class LocalCase {
|
|
|
12
12
|
id;
|
|
13
13
|
label;
|
|
14
14
|
fileName;
|
|
15
|
+
declaredFormulation;
|
|
15
16
|
/** Case stats; null for a .pwd display only entry. */
|
|
16
17
|
summary = $state.raw(null);
|
|
17
|
-
/**
|
|
18
|
-
|
|
18
|
+
/** Generation 2 PowerIO IR used for display edits and solver studies. */
|
|
19
|
+
studyInputJson = $state.raw(undefined);
|
|
19
20
|
/** Topology for synthetic placement when the file has no coordinates. */
|
|
20
21
|
topology = $state.raw(undefined);
|
|
21
22
|
coordsKind = $state.raw(undefined);
|
|
22
23
|
/** Map geometry when the file carried or received coordinates. */
|
|
23
24
|
view = $state.raw(null);
|
|
24
25
|
syntheticCenter = $state.raw(undefined);
|
|
26
|
+
diagram = $state.raw(null);
|
|
27
|
+
displayMode = $state('geographic');
|
|
25
28
|
geoSource = $state.raw(undefined);
|
|
26
29
|
geoWarnings = $state.raw(undefined);
|
|
27
30
|
network = $state.raw(null);
|
|
@@ -31,8 +34,7 @@ export class LocalCase {
|
|
|
31
34
|
deltas = $state.raw({});
|
|
32
35
|
/** Committed branch rating deltas (MW from base, keyed by branch). */
|
|
33
36
|
ratings = $state.raw({});
|
|
34
|
-
/** The
|
|
35
|
-
* full AC OPF, or the SOCWR relaxation. Changing it rebuilds the Study. */
|
|
37
|
+
/** The selected calculation. Changing it rebuilds the numerical Study. */
|
|
36
38
|
formulation = $state(DEFAULT_FORMULATION);
|
|
37
39
|
iterations = $state.raw([]);
|
|
38
40
|
solving = $state(false);
|
|
@@ -41,6 +43,8 @@ export class LocalCase {
|
|
|
41
43
|
solveFallbackReason = $state(null);
|
|
42
44
|
/** Monotone token: only the latest solve may write this case. */
|
|
43
45
|
solveSeq = 0;
|
|
46
|
+
/** Monotone generation for external optimistic concurrency. */
|
|
47
|
+
revisionGeneration = $state(0);
|
|
44
48
|
/** Monotone token: only the latest sensitivity request may write this case. */
|
|
45
49
|
sensitivitySeq = 0;
|
|
46
50
|
predictedObjective = $state(null);
|
|
@@ -50,8 +54,10 @@ export class LocalCase {
|
|
|
50
54
|
this.id = init.id;
|
|
51
55
|
this.label = init.label;
|
|
52
56
|
this.fileName = init.fileName;
|
|
57
|
+
this.formulation = init.formulation ?? DEFAULT_FORMULATION;
|
|
58
|
+
this.declaredFormulation = init.declaredFormulation;
|
|
53
59
|
this.summary = init.summary ?? null;
|
|
54
|
-
this.
|
|
60
|
+
this.studyInputJson = init.studyInputJson;
|
|
55
61
|
this.topology = init.topology;
|
|
56
62
|
this.coordsKind = init.coordsKind;
|
|
57
63
|
this.view = init.view ?? null;
|
|
@@ -66,9 +72,10 @@ export class LocalCase {
|
|
|
66
72
|
export class CaseState {
|
|
67
73
|
id;
|
|
68
74
|
name;
|
|
75
|
+
unavailableReason;
|
|
69
76
|
network = $state.raw(null);
|
|
70
|
-
/**
|
|
71
|
-
|
|
77
|
+
/** Retained PowerIO module for the browser solver; fetched lazily. */
|
|
78
|
+
studyInputJson = $state.raw(null);
|
|
72
79
|
/** Boot solution at base demand; never changes. */
|
|
73
80
|
baseSolution = $state.raw(null);
|
|
74
81
|
/** Exact solution at the current committed perturbation. */
|
|
@@ -88,6 +95,8 @@ export class CaseState {
|
|
|
88
95
|
solveFallbackReason = $state(null);
|
|
89
96
|
/** Monotone token: only the latest solve may write this case. */
|
|
90
97
|
solveSeq = 0;
|
|
98
|
+
/** Monotone generation for external optimistic concurrency. */
|
|
99
|
+
revisionGeneration = $state(0);
|
|
91
100
|
/** Monotone token: only the latest sensitivity request may write this case. */
|
|
92
101
|
sensitivitySeq = 0;
|
|
93
102
|
/** Closer for this case's in-flight server solve stream, if any. Owned per
|
|
@@ -99,26 +108,48 @@ export class CaseState {
|
|
|
99
108
|
constructor(summary) {
|
|
100
109
|
this.id = summary.id;
|
|
101
110
|
this.name = summary.name;
|
|
111
|
+
this.unavailableReason = summary.unavailable_reason ?? null;
|
|
102
112
|
}
|
|
103
113
|
get perturbed() {
|
|
104
114
|
return hasPerturbation(this.deltas) || hasPerturbation(this.ratings);
|
|
105
115
|
}
|
|
106
116
|
}
|
|
107
|
-
/** A
|
|
108
|
-
* formulation, no sliders, no Study. It carries the bus/terminal graph, its
|
|
109
|
-
* placed map view, and the selected bus whose terminal detail expands. Fields
|
|
110
|
-
* are reactive like the solvable cases so the panel and map track them. */
|
|
117
|
+
/** A conductor-resolved case with retained electrical inputs and terminal results. */
|
|
111
118
|
export class MulticonductorCase {
|
|
112
119
|
id;
|
|
113
120
|
label;
|
|
114
121
|
fileName;
|
|
122
|
+
moduleJson = $state.raw(null);
|
|
123
|
+
geoLayer = $state.raw(null);
|
|
124
|
+
geoWarnings = $state.raw([]);
|
|
125
|
+
/** Summary of the displayed operating point: from the live session after
|
|
126
|
+
* each solve or load edit, or derived from a stored full result. */
|
|
127
|
+
result = $state.raw(null);
|
|
128
|
+
/** The complete result when no live session holds the operating point: a
|
|
129
|
+
* saved Study, a solve without sessions, or after coordinates are attached. */
|
|
130
|
+
mcFullResult = $state.raw(null);
|
|
131
|
+
/** Latency breakdown of the latest load-edit solve. */
|
|
132
|
+
mcTiming = $state.raw(null);
|
|
133
|
+
mcSnapshot = $state.raw(null);
|
|
134
|
+
mcSession = $state.raw(null);
|
|
135
|
+
mcLoadBranches = $state.raw([]);
|
|
136
|
+
mcLoadEdits = $state.raw([]);
|
|
137
|
+
mcEditRevision = 0;
|
|
138
|
+
mcEditRunning = false;
|
|
139
|
+
mcEditTimer = null;
|
|
140
|
+
mcSavedAt = $state(null);
|
|
141
|
+
solving = $state(false);
|
|
142
|
+
solveMs = $state(null);
|
|
143
|
+
solveSeq = 0;
|
|
144
|
+
revisionGeneration = $state(0);
|
|
145
|
+
solveAbort = null;
|
|
115
146
|
/** Summary counts and coordinate provenance from the parse. */
|
|
116
147
|
summary = $state.raw(null);
|
|
117
148
|
/** The render-ready bus/terminal graph. */
|
|
118
149
|
graph = $state.raw(null);
|
|
119
150
|
/** Placement kind resolved at ingest from the case's coordinate space. */
|
|
120
151
|
coordsKind = $state.raw('synthetic');
|
|
121
|
-
/** The
|
|
152
|
+
/** The map or diagram positions. */
|
|
122
153
|
view = $state.raw(null);
|
|
123
154
|
syntheticCenter = $state.raw(undefined);
|
|
124
155
|
/** The selected bus id, whose terminal stack and incident conductors expand;
|
|
@@ -132,10 +163,24 @@ export class MulticonductorCase {
|
|
|
132
163
|
this.label = init.label;
|
|
133
164
|
this.fileName = init.fileName;
|
|
134
165
|
this.summary = init.summary;
|
|
166
|
+
this.moduleJson = init.summary.module_json ?? null;
|
|
167
|
+
this.geoLayer = init.summary.geo_layer ?? null;
|
|
135
168
|
this.graph = init.graph;
|
|
136
169
|
this.coordsKind = init.coordsKind;
|
|
137
170
|
this.view = init.view ?? null;
|
|
138
171
|
}
|
|
172
|
+
get mcPfReason() {
|
|
173
|
+
if (!this.moduleJson)
|
|
174
|
+
return 'This case has no retained electrical input';
|
|
175
|
+
if (this.summary?.mc_pf_enabled !== true)
|
|
176
|
+
return 'AC power flow is unavailable in this build';
|
|
177
|
+
return (this.summary.mc_pf_reason ??
|
|
178
|
+
this.summary.mc_pf_unavailable_reason ??
|
|
179
|
+
(this.summary.mc_pf_supported !== true ? 'AC power flow is unavailable for this case' : null));
|
|
180
|
+
}
|
|
181
|
+
get mcPfSupported() {
|
|
182
|
+
return this.mcPfReason === null;
|
|
183
|
+
}
|
|
139
184
|
/** Whether the case is placed and ready to render. */
|
|
140
185
|
get placed() {
|
|
141
186
|
return this.view !== null;
|
|
@@ -154,6 +199,40 @@ export class MulticonductorCase {
|
|
|
154
199
|
}
|
|
155
200
|
}
|
|
156
201
|
export class AppState {
|
|
202
|
+
camera = $state.raw(null);
|
|
203
|
+
cameraRequest = $state.raw(null);
|
|
204
|
+
cameraSeq = $state(0);
|
|
205
|
+
diagramCamera = $state.raw(null);
|
|
206
|
+
diagramCameraRequest = $state.raw(null);
|
|
207
|
+
diagramCameraSeq = $state(0);
|
|
208
|
+
requestDiagramCamera(caseId, snapshot) {
|
|
209
|
+
if (!Array.isArray(snapshot.center) ||
|
|
210
|
+
snapshot.center.length !== 2 ||
|
|
211
|
+
![...snapshot.center, snapshot.scale].every(Number.isFinite) ||
|
|
212
|
+
snapshot.scale <= 0) {
|
|
213
|
+
throw new Error('Invalid saved drawing view');
|
|
214
|
+
}
|
|
215
|
+
this.settleFrame();
|
|
216
|
+
this.diagramCameraRequest = { caseId, center: [...snapshot.center], scale: snapshot.scale };
|
|
217
|
+
this.diagramCameraSeq++;
|
|
218
|
+
}
|
|
219
|
+
requestCamera(snapshot) {
|
|
220
|
+
if (!Array.isArray(snapshot.center) ||
|
|
221
|
+
snapshot.center.length !== 2 ||
|
|
222
|
+
![...snapshot.center, snapshot.zoom, snapshot.bearing, snapshot.pitch].every(Number.isFinite) ||
|
|
223
|
+
Math.abs(snapshot.center[1]) > 90 ||
|
|
224
|
+
snapshot.zoom < 0 ||
|
|
225
|
+
snapshot.zoom > 24 ||
|
|
226
|
+
snapshot.pitch < 0 ||
|
|
227
|
+
snapshot.pitch > 85) {
|
|
228
|
+
throw new Error('Invalid saved camera position');
|
|
229
|
+
}
|
|
230
|
+
this.settleFrame();
|
|
231
|
+
this.cameraRequest = { ...snapshot, center: [...snapshot.center] };
|
|
232
|
+
this.cameraSeq++;
|
|
233
|
+
}
|
|
234
|
+
/** Saved Study inspection is independent of the editable case and its solution. */
|
|
235
|
+
studyView = $state.raw(null);
|
|
157
236
|
cases = $state.raw([]);
|
|
158
237
|
activeCaseId = $state(null);
|
|
159
238
|
/** Selected bus in the active case. */
|
|
@@ -164,34 +243,41 @@ export class AppState {
|
|
|
164
243
|
previewDeltaMw = $state(null);
|
|
165
244
|
/** Live rating slider value (MW from base) before commit; null when idle. */
|
|
166
245
|
previewRatingMw = $state(null);
|
|
167
|
-
/** True while the demand control should keep the map in
|
|
246
|
+
/** True while the demand control should keep the map in nodal value preview mode. */
|
|
168
247
|
previewActive = $state(false);
|
|
169
|
-
/** Engine first
|
|
170
|
-
* ($/MWh) per bus at the previewed edit, scoped to the case and selection
|
|
248
|
+
/** Engine first order nodal value preview for the live drag, scoped to the case and selection
|
|
171
249
|
* target (bus or branch) it was computed for. Set by the Study path; null when
|
|
172
250
|
* no Study preview applies (the map then falls back to the JS
|
|
173
251
|
* sensitivity-times-step preview). Reassigned wholesale, so $state.raw. */
|
|
174
|
-
|
|
252
|
+
previewPrices = $state.raw(null);
|
|
175
253
|
demandRangeMode = $state('local');
|
|
176
|
-
displayMode = $state('
|
|
254
|
+
displayMode = $state('price');
|
|
177
255
|
sensitivityLoading = $state(false);
|
|
178
256
|
#error = $state(null);
|
|
257
|
+
errorRevision = $state(0);
|
|
179
258
|
/** Re-runs the operation behind the current `error`, when one applies. Every
|
|
180
259
|
* write to `error` clears it, so a retry op can never outlive its message. */
|
|
181
260
|
errorRetry = $state.raw(null);
|
|
261
|
+
/** Technical detail behind the current `error`, when the message itself is
|
|
262
|
+
* written for people; notices keep it in their disclosure. Every write to
|
|
263
|
+
* `error` clears it, as it clears `errorRetry`. */
|
|
264
|
+
errorDetail = $state(null);
|
|
182
265
|
get error() {
|
|
183
266
|
return this.#error;
|
|
184
267
|
}
|
|
185
268
|
set error(message) {
|
|
186
269
|
this.#error = message;
|
|
270
|
+
if (message)
|
|
271
|
+
this.errorRevision++;
|
|
187
272
|
this.errorRetry = null;
|
|
273
|
+
this.errorDetail = null;
|
|
188
274
|
}
|
|
189
275
|
/** Case files parsed in the browser via the powerio wasm module. */
|
|
190
276
|
localCases = $state.raw([]);
|
|
191
277
|
/** Local case the panel shows; clicking a bundled case or a bus clears it. */
|
|
192
278
|
activeLocalId = $state(null);
|
|
193
279
|
placingLocalId = $state(null);
|
|
194
|
-
/** Multiconductor
|
|
280
|
+
/** Multiconductor cases with retained inputs and AC power flow results. */
|
|
195
281
|
multiCases = $state.raw([]);
|
|
196
282
|
/** Multiconductor case the panel shows; mutually exclusive with the solvable
|
|
197
283
|
* active ids. */
|
|
@@ -273,7 +359,7 @@ export class AppState {
|
|
|
273
359
|
this.previewDeltaMw = null;
|
|
274
360
|
this.previewRatingMw = null;
|
|
275
361
|
this.previewActive = false;
|
|
276
|
-
this.
|
|
362
|
+
this.previewPrices = null;
|
|
277
363
|
this.demandRangeMode = 'local';
|
|
278
364
|
this.sensitivityLoading = false;
|
|
279
365
|
return this.activateFallback();
|
|
@@ -295,7 +381,7 @@ export class AppState {
|
|
|
295
381
|
// else a remaining local case that can render (a view or substations) or is
|
|
296
382
|
// awaiting placement, else the first remaining local. Frames whatever it picks.
|
|
297
383
|
activateFallback() {
|
|
298
|
-
this.activeCaseId = this.cases
|
|
384
|
+
this.activeCaseId = this.cases.find((c) => !c.unavailableReason)?.id ?? null;
|
|
299
385
|
if (this.activeCaseId) {
|
|
300
386
|
this.requestFrame(this.activeCaseId);
|
|
301
387
|
return { kind: 'backend', id: this.activeCaseId };
|
package/dist/styles.css
CHANGED
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
--surface-panel: var(--panel);
|
|
33
33
|
--surface-chip: rgb(var(--paper-rgb) / 0.65);
|
|
34
34
|
--surface-control: rgb(var(--paper-rgb) / 0.55);
|
|
35
|
+
--surface-menu: #fff;
|
|
35
36
|
--border-default: var(--line);
|
|
36
37
|
--border-strong: var(--accent);
|
|
37
38
|
--text-primary: var(--ink);
|
|
@@ -47,6 +48,10 @@
|
|
|
47
48
|
--data-pos: var(--pos);
|
|
48
49
|
--data-neg: var(--neg);
|
|
49
50
|
--focus-ring: var(--accent);
|
|
51
|
+
/* unselected bus outline in the plain diagram view */
|
|
52
|
+
--diagram-bus-stroke: #756a5b;
|
|
53
|
+
/* ground symbol in the multiconductor bus detail */
|
|
54
|
+
--mc-ground-stroke: #3a3a3a;
|
|
50
55
|
|
|
51
56
|
/* type scale, anchored at the 15px body */
|
|
52
57
|
--fs-display-lg: 33px;
|
|
@@ -81,6 +86,11 @@
|
|
|
81
86
|
--radius-full: 50%;
|
|
82
87
|
--elev-1: 0 2px 10px rgb(var(--ink-rgb) / 0.1);
|
|
83
88
|
--elev-2: 0 4px 24px rgb(var(--ink-rgb) / 0.08);
|
|
89
|
+
/* floating controls that keep their own light shadows; the dark theme maps
|
|
90
|
+
them onto the two steps above */
|
|
91
|
+
--elev-cue: 0 4px 18px rgb(var(--ink-rgb) / 0.1);
|
|
92
|
+
--elev-menu: 0 4px 18px rgb(var(--ink-rgb) / 0.14);
|
|
93
|
+
--elev-tooltip: 0 2px 10px rgb(var(--ink-rgb) / 0.12);
|
|
84
94
|
--shadow-thumb: 0 1px 4px rgb(var(--ink-rgb) / 0.3);
|
|
85
95
|
--inset-active: inset 0 -2px 0 var(--accent);
|
|
86
96
|
|
|
@@ -103,6 +113,49 @@
|
|
|
103
113
|
--z-grain: 9999;
|
|
104
114
|
}
|
|
105
115
|
|
|
116
|
+
/* Dark theme: `TellegenProvider theme="dark"` sets the attribute (a host may
|
|
117
|
+
also put it in its template). Contrast ratios are measured against the panel
|
|
118
|
+
composited over #4a4a4a, the darkest grey a dark basemap paints under it:
|
|
119
|
+
every text token clears 7:1. --border-strong marks a panel's edge over the
|
|
120
|
+
map, so it clears the 3:1 non-text minimum against that panel and against
|
|
121
|
+
bare #4a4a4a. The same markup passes WCAG AA on either ground. */
|
|
122
|
+
:root[data-theme='dark'] {
|
|
123
|
+
--bg: #0b1420;
|
|
124
|
+
--paper: #0f1a27;
|
|
125
|
+
--panel: rgba(10, 20, 31, 0.92);
|
|
126
|
+
--line: #34465a; /* decorative separators only */
|
|
127
|
+
--border-strong: #859cb4; /* 6.30:1 against the panel, 3.13:1 against bare #4a4a4a */
|
|
128
|
+
--ink: #e8eef4; /* 15.3:1 */
|
|
129
|
+
--ink-dim: #b4c1cd; /* 9.7:1 */
|
|
130
|
+
--ink-faint: #95a6b6; /* 7.14:1, and 7.02:1 on the opaque --paper of a panel card */
|
|
131
|
+
--accent: #ffb24d;
|
|
132
|
+
--accent-bright: #ffc46e; /* 11.4:1 */
|
|
133
|
+
--accent-soft: rgba(255, 178, 77, 0.14);
|
|
134
|
+
--red: #ff9f8b; /* 9.0:1 */
|
|
135
|
+
--pos: #c9a7e0;
|
|
136
|
+
--neg: #8fd0a4;
|
|
137
|
+
--bg-rgb: 11 20 32;
|
|
138
|
+
--ink-rgb: 232 238 244;
|
|
139
|
+
--accent-rgb: 255 178 77;
|
|
140
|
+
--paper-rgb: 15 26 39;
|
|
141
|
+
--text-primary: var(--ink);
|
|
142
|
+
--text-secondary: var(--ink-dim);
|
|
143
|
+
--text-tertiary: var(--ink-faint);
|
|
144
|
+
--text-accent: var(--accent-bright);
|
|
145
|
+
--text-danger: var(--red);
|
|
146
|
+
--focus-ring: #8de7f7; /* 12.7:1 */
|
|
147
|
+
--surface-menu: var(--paper);
|
|
148
|
+
--diagram-bus-stroke: var(--ink-faint);
|
|
149
|
+
--mc-ground-stroke: var(--ink-dim);
|
|
150
|
+
--elev-1: 0 2px 10px rgb(0 0 0 / 0.45);
|
|
151
|
+
--elev-2: 0 4px 24px rgb(0 0 0 / 0.4);
|
|
152
|
+
--elev-cue: var(--elev-2);
|
|
153
|
+
--elev-menu: var(--elev-2);
|
|
154
|
+
--elev-tooltip: var(--elev-1);
|
|
155
|
+
/* the light form derives from --ink-rgb, which would glow here */
|
|
156
|
+
--shadow-thumb: 0 1px 4px rgb(0 0 0 / 0.5);
|
|
157
|
+
}
|
|
158
|
+
|
|
106
159
|
* {
|
|
107
160
|
box-sizing: border-box;
|
|
108
161
|
}
|
|
@@ -225,7 +278,7 @@ a {
|
|
|
225
278
|
ControlPanel.svelte; the .mode / .display-mode / @media source order is
|
|
226
279
|
preserved so the cascade resolves exactly as before. */
|
|
227
280
|
.region {
|
|
228
|
-
font-size:
|
|
281
|
+
font-size: 12px;
|
|
229
282
|
font-weight: 400;
|
|
230
283
|
color: var(--text-secondary);
|
|
231
284
|
text-transform: uppercase;
|
|
@@ -245,7 +298,7 @@ a {
|
|
|
245
298
|
before the component split. Scoping this would raise its specificity past that rule. */
|
|
246
299
|
.mode > button {
|
|
247
300
|
margin-left: auto;
|
|
248
|
-
font-size:
|
|
301
|
+
font-size: 12.5px;
|
|
249
302
|
padding: 2px 7px;
|
|
250
303
|
background: none;
|
|
251
304
|
border: 1px solid var(--line);
|
|
@@ -264,7 +317,7 @@ a {
|
|
|
264
317
|
}
|
|
265
318
|
|
|
266
319
|
.display-mode .segment button {
|
|
267
|
-
font-size:
|
|
320
|
+
font-size: 13px;
|
|
268
321
|
padding: 2px 9px;
|
|
269
322
|
}
|
|
270
323
|
|
|
@@ -282,7 +335,7 @@ a {
|
|
|
282
335
|
.legend-labels {
|
|
283
336
|
display: flex;
|
|
284
337
|
justify-content: space-between;
|
|
285
|
-
font-size:
|
|
338
|
+
font-size: 12.5px;
|
|
286
339
|
color: var(--text-tertiary);
|
|
287
340
|
margin-top: 4px;
|
|
288
341
|
}
|
|
@@ -301,7 +354,7 @@ a {
|
|
|
301
354
|
border-radius: 999px;
|
|
302
355
|
color: var(--text-secondary);
|
|
303
356
|
background: var(--surface-control);
|
|
304
|
-
font-size:
|
|
357
|
+
font-size: 12px;
|
|
305
358
|
line-height: 1;
|
|
306
359
|
cursor: help;
|
|
307
360
|
}
|
|
@@ -334,7 +387,7 @@ a {
|
|
|
334
387
|
|
|
335
388
|
.reset {
|
|
336
389
|
margin-top: 10px;
|
|
337
|
-
font-size:
|
|
390
|
+
font-size: 12.5px;
|
|
338
391
|
padding: 3px 10px;
|
|
339
392
|
background: none;
|
|
340
393
|
border: 1px solid var(--line);
|