@tellegen/svelte 0.2.0 → 0.3.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.
Files changed (66) hide show
  1. package/README.md +36 -5
  2. package/dist/TellegenMap.svelte +325 -163
  3. package/dist/TellegenProvider.svelte +4 -0
  4. package/dist/TellegenShell.svelte +19 -13
  5. package/dist/api.d.ts +8 -1
  6. package/dist/api.js +6 -2
  7. package/dist/colors.d.ts +7 -7
  8. package/dist/colors.js +13 -13
  9. package/dist/components/AppHeader.svelte +23 -11
  10. package/dist/components/BindingLines.svelte +3 -6
  11. package/dist/components/BusPicker.svelte +8 -2
  12. package/dist/components/ControlPanel.svelte +106 -343
  13. package/dist/components/DemandSlider.svelte +21 -23
  14. package/dist/components/DiagramCanvas.svelte +486 -0
  15. package/dist/components/DiagramCanvas.svelte.d.ts +13 -0
  16. package/dist/components/DisplayControls.svelte +4 -2
  17. package/dist/components/FormulationSelector.svelte +8 -3
  18. package/dist/components/LocalCaseDetails.svelte +55 -45
  19. package/dist/components/McResults.svelte +365 -0
  20. package/dist/components/McResults.svelte.d.ts +11 -0
  21. package/dist/components/ModelDetails.svelte +230 -0
  22. package/dist/components/ModelDetails.svelte.d.ts +7 -0
  23. package/dist/components/MulticonductorDetails.svelte +227 -78
  24. package/dist/components/NetworkStats.svelte +20 -18
  25. package/dist/components/NotificationCenter.svelte +243 -0
  26. package/dist/components/NotificationCenter.svelte.d.ts +3 -0
  27. package/dist/components/PanelCard.svelte +319 -0
  28. package/dist/components/PanelCard.svelte.d.ts +9 -0
  29. package/dist/components/PanelFrame.svelte +46 -0
  30. package/dist/components/PanelFrame.svelte.d.ts +16 -0
  31. package/dist/components/PanelHost.svelte +212 -0
  32. package/dist/components/PanelHost.svelte.d.ts +3 -0
  33. package/dist/components/PowerFlowDetails.svelte +99 -0
  34. package/dist/components/PowerFlowDetails.svelte.d.ts +3 -0
  35. package/dist/components/RatingSlider.svelte +5 -9
  36. package/dist/components/SavedNetworkDetails.svelte +214 -0
  37. package/dist/components/SavedNetworkDetails.svelte.d.ts +3 -0
  38. package/dist/components/SensitivityReadout.svelte +24 -16
  39. package/dist/components/SolveCard.svelte +46 -49
  40. package/dist/components/TopMovers.svelte +8 -3
  41. package/dist/components/TopMovers.svelte.d.ts +2 -17
  42. package/dist/components.d.ts +2 -0
  43. package/dist/components.js +2 -0
  44. package/dist/controller.svelte.d.ts +54 -33
  45. package/dist/controller.svelte.js +927 -233
  46. package/dist/diagram-labels.d.ts +9 -0
  47. package/dist/diagram-labels.js +40 -0
  48. package/dist/display.d.ts +3 -7
  49. package/dist/display.js +31 -27
  50. package/dist/format.js +5 -4
  51. package/dist/index.d.ts +10 -4
  52. package/dist/index.js +7 -2
  53. package/dist/map.d.ts +1 -1
  54. package/dist/map.js +1 -1
  55. package/dist/multiconductor.d.ts +11 -2
  56. package/dist/multiconductor.js +107 -14
  57. package/dist/notices.svelte.d.ts +29 -0
  58. package/dist/notices.svelte.js +82 -0
  59. package/dist/panels.svelte.d.ts +65 -0
  60. package/dist/panels.svelte.js +215 -0
  61. package/dist/state.svelte.d.ts +90 -23
  62. package/dist/state.svelte.js +94 -20
  63. package/dist/synthetic-layout.js +1 -0
  64. package/package.json +22 -9
  65. package/dist/study-package.d.ts +0 -8
  66. package/dist/study-package.js +0 -17
@@ -1,56 +1,63 @@
1
1
  <script lang="ts">
2
- import { getController } from '../context.svelte.js';
2
+ import { getAppState, getController } from '../context.svelte.js';
3
3
  import { formulationLabel, solveMetaLabel } from '../format.js';
4
4
  import Sparkline from '../Sparkline.svelte';
5
+ import PanelFrame from './PanelFrame.svelte';
6
+ let open = $state(true);
5
7
 
6
8
  const ctrl = getController();
9
+ const app = getAppState();
10
+ const saved = $derived(app.studyView);
7
11
  const active = $derived(ctrl.activeSolvable);
8
- const iterations = $derived(active?.iterations ?? []);
12
+ const iterations = $derived(
13
+ saved
14
+ ? Array.isArray(saved.solution?.iterations)
15
+ ? saved.solution.iterations
16
+ : []
17
+ : (active?.iterations ?? [])
18
+ );
9
19
  </script>
10
20
 
11
- {#if active && (active.solving || active.solveMs != null)}
12
- <div class="solvecard">
13
- <div class="solvecard-head mono">
14
- <span><b>OPF solve</b></span>
21
+ {#if saved || (active && (active.solving || active.solveMs != null))}
22
+ <PanelFrame id="solver" title="Solver" side="right" order={10} width={320} bind:open>
23
+ <div class="solvecard">
24
+ <div class="solve-label mono">
25
+ {saved ? 'Saved result' : active?.formulation === 'acpf' ? 'Power flow' : 'OPF solve'}
26
+ </div>
27
+ {#if iterations.length > 1}
28
+ {#key iterations}
29
+ <Sparkline {iterations} />
30
+ {/key}
31
+ {/if}
32
+ {#if saved}
33
+ <div class="solve-meta mono dim">
34
+ <span class="solve-formulation">{formulationLabel(saved.formulation)}</span>
35
+ <span>{saved.solution?.status ?? 'Not solved'}</span>
36
+ <span>Saved state: {saved.label}</span>
37
+ </div>
38
+ {:else if active}
39
+ <div class="solve-meta mono dim">
40
+ <span class="solve-formulation">{formulationLabel(active.formulation)}</span>
41
+ <span>{solveMetaLabel(active)}</span>
42
+ {#if active.solveMs != null}<span>{active.solveMs} ms</span>{/if}
43
+ </div>
44
+ {#if active.solveBackend === 'rust-server' && active.solveFallbackReason}
45
+ <p class="fallback-reason mono dim" title={active.solveFallbackReason}>
46
+ fallback: {active.solveFallbackReason}
47
+ </p>
48
+ {/if}
49
+ {/if}
15
50
  </div>
16
- {#if iterations.length > 1}
17
- {#key iterations}
18
- <Sparkline {iterations} />
19
- {/key}
20
- {/if}
21
- <div class="solve-meta mono dim">
22
- <span class="solve-formulation">{formulationLabel(active.formulation)}</span>
23
- <span>{solveMetaLabel(active)}</span>
24
- {#if active.solveMs != null}<span>{active.solveMs} ms</span>{/if}
25
- </div>
26
- {#if active.solveBackend === 'rust-server' && active.solveFallbackReason}
27
- <p class="fallback-reason mono dim" title={active.solveFallbackReason}>
28
- fallback: {active.solveFallbackReason}
29
- </p>
30
- {/if}
31
- </div>
51
+ </PanelFrame>
32
52
  {/if}
33
53
 
34
54
  <style>
55
+ .solve-label {
56
+ font-size: 11px;
57
+ margin-bottom: 8px;
58
+ }
35
59
  .solvecard {
36
- position: absolute;
37
- top: 76px;
38
- right: 20px;
39
- z-index: 10;
40
- width: 300px;
41
60
  padding: 13px 15px 11px;
42
- background: var(--panel);
43
- border: 1px solid var(--line);
44
- border-radius: 3px;
45
- backdrop-filter: blur(6px);
46
- box-shadow: 0 4px 24px rgba(32, 36, 43, 0.08);
47
- animation: rise 0.3s ease-out both;
48
- }
49
-
50
- .solvecard-head {
51
- font-size: 10.5px;
52
- margin-bottom: 6px;
53
- white-space: nowrap;
54
61
  }
55
62
 
56
63
  .solve-meta {
@@ -72,14 +79,4 @@
72
79
  line-height: 1.35;
73
80
  overflow-wrap: anywhere;
74
81
  }
75
-
76
- @media (max-width: 760px) {
77
- .solvecard {
78
- /* just clear of the header's case tabs */
79
- top: 108px;
80
- left: auto;
81
- right: 10px;
82
- width: min(300px, calc(100% - 20px));
83
- }
84
- }
85
82
  </style>
@@ -3,15 +3,20 @@
3
3
  import { signedExp } from '../format.js';
4
4
 
5
5
  const ctrl = getController();
6
- const unitTitle =
7
- 'LMP is measured in $/MWh and demand is perturbed in MW, so dLMP/dd has units ($/MWh)/MW.';
6
+ const units = $derived(
7
+ (ctrl.selectedSensitivity?.units ?? '(objective units/MW)/MW').replaceAll(
8
+ 'objective_unit',
9
+ 'objective units'
10
+ )
11
+ );
12
+ const unitTitle = $derived(`LMP response per MW of demand, in ${units}.`);
8
13
  </script>
9
14
 
10
15
  <div class="movers-block">
11
16
  {#if !ctrl.previewing && ctrl.topMovers.length > 0}
12
17
  <table class="mono">
13
18
  <caption class="mono dim" title={unitTitle}>
14
- largest &Delta;LMP per MW demand <span class="unit">($/MWh)/MW</span>
19
+ largest LMP response <span class="unit">{units}</span>
15
20
  </caption>
16
21
  <tbody>
17
22
  {#each ctrl.topMovers as mover (mover.bus)}
@@ -1,18 +1,3 @@
1
- interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
2
- new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
3
- $$bindings?: Bindings;
4
- } & Exports;
5
- (internal: unknown, props: {
6
- $$events?: Events;
7
- $$slots?: Slots;
8
- }): Exports & {
9
- $set?: any;
10
- $on?: any;
11
- };
12
- z_$$bindings?: Bindings;
13
- }
14
- declare const TopMovers: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
- [evt: string]: CustomEvent<any>;
16
- }, {}, {}, string>;
17
- type TopMovers = InstanceType<typeof TopMovers>;
1
+ declare const TopMovers: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type TopMovers = ReturnType<typeof TopMovers>;
18
3
  export default TopMovers;
@@ -1,3 +1,4 @@
1
+ export { default as ModelDetails } from './components/ModelDetails.svelte';
1
2
  export { default as AppFooter } from './components/AppFooter.svelte';
2
3
  export { default as AppHeader } from './components/AppHeader.svelte';
3
4
  export { default as BusPicker } from './components/BusPicker.svelte';
@@ -16,3 +17,4 @@ export { default as SizeLegend } from './components/SizeLegend.svelte';
16
17
  export { default as SolveCard } from './components/SolveCard.svelte';
17
18
  export { default as TopMovers } from './components/TopMovers.svelte';
18
19
  export { default as Sparkline } from './Sparkline.svelte';
20
+ export { default as McResults } from './components/McResults.svelte';
@@ -1,3 +1,4 @@
1
+ export { default as ModelDetails } from './components/ModelDetails.svelte';
1
2
  export { default as AppFooter } from './components/AppFooter.svelte';
2
3
  export { default as AppHeader } from './components/AppHeader.svelte';
3
4
  export { default as BusPicker } from './components/BusPicker.svelte';
@@ -16,3 +17,4 @@ export { default as SizeLegend } from './components/SizeLegend.svelte';
16
17
  export { default as SolveCard } from './components/SolveCard.svelte';
17
18
  export { default as TopMovers } from './components/TopMovers.svelte';
18
19
  export { default as Sparkline } from './Sparkline.svelte';
20
+ export { default as McResults } from './components/McResults.svelte';
@@ -1,14 +1,19 @@
1
1
  import { type TellegenApiClient } from './api.js';
2
2
  import type { BranchRatingDeltas, Network, NetworkBus, SensitivityColumn, Solution } from './api.js';
3
3
  import { CaseState, LocalCase, MulticonductorCase, type AppState, type DemandRangeMode, type SolvableCase } from './state.svelte.js';
4
- import { type BrowserStudy, type Formulation, type SensTarget } from '@tellegen/engine';
4
+ import { type EngineTransport, type McPfOptions, type McPfResult, type McStudySnapshot, type BrowserStudy, type Formulation, type PowerIoDiagnostic, type SensTarget } from '@tellegen/engine';
5
5
  /** A parsed geographic sidecar awaiting application: the canonical `.geo.json`
6
6
  * layer document from the engine's tolerant reader, its source file name, and
7
7
  * the reader's notes on records it could not use. */
8
8
  type GeoLayerFile = {
9
9
  name: string;
10
10
  layer: string;
11
- warnings: string[];
11
+ diagnostics: PowerIoDiagnostic[];
12
+ };
13
+ type SavedCaseCapture = {
14
+ input: string;
15
+ solution?: string;
16
+ view?: import('@tellegen/engine').StudyView;
12
17
  };
13
18
  /** A lowered analysis row is view-only only when a current producer says so;
14
19
  * absence keeps payloads from older compatible engine/server versions editable. */
@@ -24,22 +29,25 @@ type DemandRangeAnchor = {
24
29
  export interface ControllerOptions {
25
30
  api?: TellegenApiClient;
26
31
  apiBase?: string;
32
+ mcTransport?: Pick<EngineTransport, 'solveMcModule' | 'applyMcGeo'> & Partial<Pick<EngineTransport, 'solveMcStudy' | 'applyMcStudyGeo' | 'createMcPfSession'>>;
27
33
  }
28
34
  export declare class Controller {
29
35
  app: AppState;
30
36
  api: TellegenApiClient;
37
+ mcTransport: Pick<EngineTransport, 'solveMcModule' | 'applyMcGeo'> & Partial<Pick<EngineTransport, 'solveMcStudy' | 'applyMcStudyGeo' | 'createMcPfSession'>>;
31
38
  abort: AbortController | null;
32
39
  sensitivity429Until: number;
33
40
  serverComputeOff: boolean;
34
41
  caseStudies: WeakMap<SolvableCase, {
35
42
  study: BrowserStudy;
36
- networkJson: string;
43
+ studyInputJson: string;
37
44
  formulation: Formulation;
38
45
  baseSolution: Solution;
39
46
  }>;
47
+ private detachedCaseCaptures;
40
48
  studyUnavailable: WeakMap<SolvableCase, string>;
41
49
  studyBuilds: WeakMap<SolvableCase, {
42
- networkJson: string;
50
+ studyInputJson: string;
43
51
  formulation: Formulation;
44
52
  token: object;
45
53
  promise: Promise<BrowserStudy | null>;
@@ -74,6 +82,7 @@ export declare class Controller {
74
82
  /** True when two selection targets name the same bus or branch. */
75
83
  sameTarget(a: SensTarget, b: SensTarget): boolean;
76
84
  isPerturbed(c: SolvableCase | null): boolean;
85
+ bumpRevision(c: SolvableCase): void;
77
86
  setNearbyRangeAnchor(c: SolvableCase, bus: number, delta?: number): void;
78
87
  beginBusSelection(c: SolvableCase, busId: number): {
79
88
  ac: AbortController;
@@ -90,12 +99,12 @@ export declare class Controller {
90
99
  rememberHiddenDefaultCase(id: string): void;
91
100
  localNetwork(c: LocalCase): Network | null;
92
101
  maybeStartLocalSolve(id: string): void;
93
- getStudy(c: SolvableCase, networkJson: string): Promise<BrowserStudy | null>;
94
- browserSensitivity(c: SolvableCase, networkJson: string, target: SensTarget): Promise<SensitivityColumn | null>;
102
+ getStudy(c: SolvableCase, studyInputJson: string): Promise<BrowserStudy | null>;
103
+ browserSensitivity(c: SolvableCase, studyInputJson: string, target: SensTarget): Promise<SensitivityColumn | null>;
95
104
  disposeStudy(c: SolvableCase): void;
96
105
  acceptSensitivity(c: SolvableCase, col: SensitivityColumn | null, target: SensTarget | null, sensitivitySeq?: number): void;
97
106
  finishSolve(c: SolvableCase, seq: number, target: SensTarget | null): void;
98
- ensureNetworkJson(c: SolvableCase): Promise<string | null>;
107
+ ensureStudyInputJson(c: SolvableCase): Promise<string | null>;
99
108
  previewDeltas(c: SolvableCase, bus: number, value: number): {
100
109
  [x: string]: number;
101
110
  [x: number]: number;
@@ -191,7 +200,7 @@ export declare class Controller {
191
200
  placeLocalCase: (lon: number, lat: number) => Promise<void>;
192
201
  /** Write the case's on-screen layout into its network payload
193
202
  * (`Bus.location` with `synthetic` provenance — the engine also accepts
194
- * `manual`, which round trips through restored packages), so saves,
203
+ * `manual`, which round trips through saved PowerIO modules), so saves,
195
204
  * exports, and the `.geo.json` download carry it. Best effort: on an
196
205
  * engine failure the view still renders, only the layout's persistence is
197
206
  * lost. */
@@ -205,7 +214,7 @@ export declare class Controller {
205
214
  * wins on shared elements). The engine owns matching and the network becomes
206
215
  * the single owner of the coordinates: matched points land in
207
216
  * `Bus.location`, routes in `Branch.route`, and the refreshed
208
- * `network_json` replaces the case payload — so a saved package or export
217
+ * `module_json` replaces the case IR — so a saved module or export
209
218
  * carries exactly what is on screen. A live Study is synced the same way.
210
219
  * Throws when a layer matches nothing. */
211
220
  applyGeoLayers: (c: LocalCase, layers: GeoLayerFile[]) => Promise<void>;
@@ -213,10 +222,12 @@ export declare class Controller {
213
222
  * the case's network and view, provenance flips to the geographic source,
214
223
  * and the warnings panel gets the placement summary. */
215
224
  private adoptGeoPayload;
216
- /** Keep a live Study's base network in step with a geo change (locations
217
- * are metadata the model never reads, so nothing re-solves), and re-key its
218
- * cache entry so the next solve reuses it instead of rebuilding. */
219
- private syncCachedStudy;
225
+ /** Update an existing numerical Study after the base input receives geographic data. */
226
+ private syncStudyGeo;
227
+ private caseEditKey;
228
+ private localSidecarCopy;
229
+ private stageLocalSidecars;
230
+ private attachSidecarsToSelected;
220
231
  applyGeoLayersToExisting: (layers: GeoLayerFile[]) => Promise<void>;
221
232
  /** Ask the map to frame the selected branch and wait for the camera to land,
222
233
  * capped by FOCUS_SETTLE_CAP_MS and cut short by the selection's abort. */
@@ -232,7 +243,7 @@ export declare class Controller {
232
243
  * transient (a wasm chunk or case JSON fetch), so say what is known and keep
233
244
  * the retry meaningful. */
234
245
  private computeOffNotice;
235
- /** The one server ∂LMP/∂parameter request a selection may make: the
246
+ /** The one server nodal-value/parameter request a selection may make: the
236
247
  * reconciliation path when the browser Study produced no column for a DC OPF
237
248
  * case. Never retries on its own: a second request inside the same rate limit
238
249
  * window is a guaranteed rejection, and the retry button covers the user
@@ -267,6 +278,7 @@ export declare class Controller {
267
278
  commitDelta: (value: number) => void;
268
279
  finishDemandInput: (value: number) => void;
269
280
  resetCase: (c: SolvableCase) => void;
281
+ /** Rebuild the numerical Study for a selected calculation at the current demand. */
270
282
  changeFormulation: (c: SolvableCase, next: Formulation) => void;
271
283
  /** Parse dropped files in the browser via the powerio wasm module. Case
272
284
  * files (.m, .raw, .aux, .epc, .pwb) become local networks; geographic files can
@@ -279,12 +291,9 @@ export declare class Controller {
279
291
  * the first matching PowerWorld display. A failed geographic join keeps the
280
292
  * parsed case and returns the user-facing placement error. */
281
293
  private prepareDroppedLocal;
282
- /** Fill a coordinate-less case's positions from a co-dropped PowerWorld
283
- * `.pwd`: the display's substation points project to approximate
284
- * longitude/latitude and join onto buses through the `SubNum` extras key.
285
- * The first display that joins wins; one that matches nothing stays
286
- * available as its own preview entry, and the case stays placeable. */
294
+ /** Retain diagram geometry separately from the electrical case's geographic data. */
287
295
  private fillFromDisplaySibling;
296
+ setCaseDisplayMode: (c: LocalCase, mode: "geographic" | "diagram") => void;
288
297
  /** Route a dropped `.json` by its content. Recognized payloads return without
289
298
  * mutating case state so co-dropped sidecars can be applied first; unrouted
290
299
  * content returns the bytes already read for the geographic fallback. */
@@ -292,10 +301,19 @@ export declare class Controller {
292
301
  /** Parse a dropped OpenDSS `.dss` file into a multiconductor viewing case.
293
302
  * Returns true when a case landed; a parse failure sets the panel error. */
294
303
  private ingestDistFile;
295
- /** Build a `MulticonductorCase` from an ingest payload and make it active.
296
- * Geographic cases place immediately; planar/synthetic cases enter placement
297
- * (a map click or "place on map") so the user picks their center. */
304
+ /** Retain parsed electrical inputs and draw each declared coordinate space directly. */
298
305
  private addMultiCase;
306
+ /** Compute terminal quantities only for the retained input and current case revision. */
307
+ solveMultiCase: (c: MulticonductorCase, options?: McPfOptions, signal?: AbortSignal) => Promise<McPfResult>;
308
+ /** Materialize the current retained operating point only when a portable
309
+ * snapshot is actually needed (save/export/geography). */
310
+ snapshotMultiCase: (c: MulticonductorCase) => Promise<McStudySnapshot>;
311
+ /** Apply coordinates atomically to the explicitly selected conductor-resolved case. */
312
+ applyMultiGeoLayers: (c: MulticonductorCase, layers: GeoLayerFile[]) => Promise<void>;
313
+ /** Stage an absolute load-branch P/Q edit and coalesce rapid UI changes into
314
+ * one retained-factor warm solve. */
315
+ queueMultiLoadPower: (c: MulticonductorCase, load: string, branch: number, p_w: number, q_var: number) => void;
316
+ private flushMultiLoadPowers;
299
317
  /** Make a multiconductor case active, framing it when it is placed. */
300
318
  activateMulti: (c: MulticonductorCase) => void;
301
319
  /** Select a bus in a multiconductor case: its terminal stack and incident
@@ -313,21 +331,24 @@ export declare class Controller {
313
331
  /** The unified map click-to-place: a pending multiconductor case takes the
314
332
  * click first, otherwise a pending local case does. */
315
333
  placeCase: (lon: number, lat: number) => void;
316
- /** Build a local case from a bare balanced ingest payload without activating
334
+ projectStudyInput(input: string, layer?: string): Promise<Network>;
335
+ caseGeographyLayers(c: LocalCase): Promise<string[]>;
336
+ /** Capture a matching cached result without constructing or solving a model. */
337
+ captureSavedCase(c: SolvableCase): Promise<SavedCaseCapture>;
338
+ /** Build a local case from a balanced PowerIO ingest payload without activating
317
339
  * it yet; co-dropped placement data is applied before the first solve. */
318
340
  private localFromBalancedPayload;
319
- /** Build a local case from a saved package, including its edit state and
320
- * formulation, without activating it until sidecars have been applied. */
321
- private localFromPackage;
322
341
  /** Build (or reuse) the case's Study and commit it at the current edit state, so a
323
342
  * save or export captures exactly what is on screen. Null (with an error set) when no
324
- * network or Study is available. */
325
- private syncedStudy;
326
- /** Save the active study as a `.pio.json` package and download it. */
327
- saveStudyPackage: (c: SolvableCase) => Promise<void>;
328
- /** Export the active study's committed state to `format` and download it. Returns the
329
- * writer's fidelity warnings so the caller can surface them. */
330
- exportStudyAs: (c: SolvableCase, format: string) => Promise<string[]>;
343
+ * PowerIO module or Study is available. */
344
+ syncedStudy: (c: SolvableCase) => Promise<BrowserStudy | null>;
345
+ /** Save the materialized case as a retained PowerIO module. */
346
+ saveCaseModule: (c: SolvableCase) => Promise<void>;
347
+ /** Save the exact DC OPF result with the materialized problem instance it solved. */
348
+ saveSolutionModule: (c: SolvableCase) => Promise<void>;
349
+ /** Export the active study's committed state to `format` and download it.
350
+ * Returns this output operation's structured diagnostics. */
351
+ exportCaseAs: (c: SolvableCase, format: string) => Promise<PowerIoDiagnostic[]>;
331
352
  /** A filesystem-safe base name for a downloaded case file. */
332
353
  private caseFileStem;
333
354
  setDemandRangeMode: (mode: DemandRangeMode) => void;