@tellegen/svelte 0.4.6 → 0.5.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.
@@ -110,6 +110,43 @@ function withRoutes(view, layer) {
110
110
  return view;
111
111
  const routes = new Map();
112
112
  const same = (a, b) => typeof a === 'string' && a.toLowerCase() === b.toLowerCase();
113
+ // Index the edges once by lowercased id and by lowercased directed endpoint
114
+ // pair. A city case carries tens of thousands of routes and edges, so a scan
115
+ // of every edge per feature is quadratic.
116
+ const byId = new Map();
117
+ const byEnds = new Map();
118
+ for (const edge of view.edges) {
119
+ const id = edge.id.toLowerCase();
120
+ const from = edge.from.toLowerCase();
121
+ const to = edge.to.toLowerCase();
122
+ const withId = byId.get(id);
123
+ if (withId)
124
+ withId.push(edge);
125
+ else
126
+ byId.set(id, [edge]);
127
+ let fromEnds = byEnds.get(from);
128
+ if (!fromEnds)
129
+ byEnds.set(from, (fromEnds = new Map()));
130
+ const between = fromEnds.get(to);
131
+ if (between)
132
+ between.push(edge);
133
+ else
134
+ fromEnds.set(to, [edge]);
135
+ }
136
+ const directed = (from, to) => byEnds.get(from)?.get(to) ?? [];
137
+ // The edges `same` would match: by id when the feature names one, otherwise
138
+ // by its endpoints in either direction. A self loop's two directions are one
139
+ // set, so it is not counted twice.
140
+ const matching = (id, from, to) => {
141
+ if (id !== undefined)
142
+ return typeof id === 'string' ? (byId.get(id.toLowerCase()) ?? []) : [];
143
+ if (typeof from !== 'string' || typeof to !== 'string')
144
+ return [];
145
+ const f = from.toLowerCase();
146
+ const t = to.toLowerCase();
147
+ const forward = directed(f, t);
148
+ return f === t ? forward : [...forward, ...directed(t, f)];
149
+ };
113
150
  for (const feature of features) {
114
151
  const props = feature?.properties;
115
152
  const geometry = feature?.geometry;
@@ -126,10 +163,7 @@ function withRoutes(view, layer) {
126
163
  const from = props.from ?? props.bus_from;
127
164
  const to = props.to ?? props.bus_to;
128
165
  const id = props.id ?? props.uid;
129
- const matches = view.edges.filter((edge) => id !== undefined
130
- ? same(id, edge.id)
131
- : (same(from, edge.from) && same(to, edge.to)) ||
132
- (same(from, edge.to) && same(to, edge.from)));
166
+ const matches = matching(id, from, to);
133
167
  if (matches.length !== 1)
134
168
  continue;
135
169
  const edge = matches[0];
@@ -1,5 +1,5 @@
1
1
  import type { CaseSummary, DemandDeltas, Network, NetworkBranch, NetworkBus, SensitivityColumn, Solution, SolveIteration } from './api.js';
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';
2
+ import { type StudyView, type BranchRatingDeltas, type BrowserMcPfSession, type CaseFileSummary, type DistGraph, type Formulation, type IngestedDistCase, type McPfCallTiming, type McPfOptions, type McPfResult, type McPfResultTables, type McPfSummary, type McLoadBranchState, type McLoadPowerEdit, type McModuleInput, 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
5
  import { type GraphInputs } from './diagram/graph-inputs.js';
@@ -181,9 +181,10 @@ export declare class CaseState {
181
181
  }
182
182
  /** A case the solver can run: a server-backed case or a browser-parsed local case. */
183
183
  export type SolvableCase = CaseState | LocalCase;
184
- /** The multiconductor ingest payload without the graph: the summary counts,
185
- * connected load/generation, coordinate provenance, and diagnostics. */
186
- export type MultiCaseSummary = Omit<IngestedDistCase, 'graph'>;
184
+ /** The multiconductor ingest payload without the graph or the retained
185
+ * input: the summary counts, connected load/generation, coordinate
186
+ * provenance, and diagnostics. */
187
+ export type MultiCaseSummary = Omit<IngestedDistCase, 'graph' | 'input'>;
187
188
  /** Geographic coordinates use the map; other positions use the diagram canvas. */
188
189
  export type MultiCoordsKind = 'geographic' | 'planar' | 'synthetic';
189
190
  /** Where one interactive load edit spent its time, in milliseconds: engine
@@ -197,7 +198,10 @@ export declare class MulticonductorCase {
197
198
  readonly id: string;
198
199
  readonly label: string;
199
200
  readonly fileName: string;
200
- moduleJson: string | null;
201
+ /** The electrical input calculations start from: the module the engine
202
+ * keeps from the import, or PowerIO IR text (a saved or re-placed module).
203
+ * A kept module is freed when it is replaced or the case is removed. */
204
+ mcInput: McModuleInput | null;
201
205
  geoLayer: string | null;
202
206
  geoWarnings: string[];
203
207
  /** Summary of the displayed operating point: from the live session after
@@ -208,6 +212,15 @@ export declare class MulticonductorCase {
208
212
  mcFullResult: McPfResult | null;
209
213
  /** Latency breakdown of the latest load-edit solve. */
210
214
  mcTiming: McEditTiming | null;
215
+ /** Explicit selection for the next solve. The graph and module retain every bus. */
216
+ mcEnergizedOnly: boolean;
217
+ /** Successful explicit solve to reveal in Studies; load edits do not advance it. */
218
+ mcCompletedSolveSeq: number;
219
+ /** Detected on import, revalidated when preparing a solve; never removed from the graph. */
220
+ mcExcludedIslands: import('@tellegen/engine').McExcludedIsland[];
221
+ mcPreparedScope: import('@tellegen/engine').McNetworkScope | null;
222
+ /** Selected settings for the next explicit solve; results carry their own effective settings. */
223
+ mcOptions: McPfOptions;
211
224
  mcSnapshot: McStudySnapshot | null;
212
225
  mcSession: BrowserMcPfSession | null;
213
226
  mcLoadBranches: McLoadBranchState[];
@@ -248,9 +261,10 @@ export declare class MulticonductorCase {
248
261
  /** Bumped to ask the views to bring the current selection into view again (an issue clicked
249
262
  * that is already selected). */
250
263
  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;
264
+ /** The full operating point read for whole-network views (overview, schematic): a stored
265
+ * result, or a live session's as columns. Null until read, and cleared when a large case's
266
+ * operating point changes. */
267
+ overviewResult: McPfResult | McPfResultTables | null;
254
268
  overviewLoading: boolean;
255
269
  /** The graph's buses and branches as the overview and the diagram read them: rebuilt when the
256
270
  * graph is replaced, never after a solve. Both views read this one copy. */
@@ -270,11 +284,17 @@ export declare class MulticonductorCase {
270
284
  label: string;
271
285
  fileName: string;
272
286
  summary: MultiCaseSummary;
287
+ /** The module the engine kept from the import, in place of the
288
+ * summary's `module_json`. */
289
+ input?: McModuleInput | null;
273
290
  graph: DistGraph;
274
291
  coordsKind: MultiCoordsKind;
275
292
  view?: MultiView | null;
276
293
  });
277
294
  get mcPfReason(): string | null;
295
+ /** Import detects scope; only a solve action opts into the selection. */
296
+ get mcRequiresEnergizedSolve(): boolean;
297
+ get mcSolveLabel(): string;
278
298
  get mcPfSupported(): boolean;
279
299
  /** Whether the case is placed and ready to render. */
280
300
  get placed(): boolean;
@@ -1,3 +1,4 @@
1
+ import { DEFAULT_MC_PF_OPTIONS, summarizeMcExcludedIslands } from '@tellegen/engine';
1
2
  import { DEFAULT_FORMULATION } from '@tellegen/engine';
2
3
  import { DiagnosticLog, errorEntry } from './log.svelte.js';
3
4
  import { graphInputs } from './diagram/graph-inputs.js';
@@ -127,7 +128,10 @@ export class MulticonductorCase {
127
128
  id;
128
129
  label;
129
130
  fileName;
130
- moduleJson = $state.raw(null);
131
+ /** The electrical input calculations start from: the module the engine
132
+ * keeps from the import, or PowerIO IR text (a saved or re-placed module).
133
+ * A kept module is freed when it is replaced or the case is removed. */
134
+ mcInput = $state.raw(null);
131
135
  geoLayer = $state.raw(null);
132
136
  geoWarnings = $state.raw([]);
133
137
  /** Summary of the displayed operating point: from the live session after
@@ -138,6 +142,15 @@ export class MulticonductorCase {
138
142
  mcFullResult = $state.raw(null);
139
143
  /** Latency breakdown of the latest load-edit solve. */
140
144
  mcTiming = $state.raw(null);
145
+ /** Explicit selection for the next solve. The graph and module retain every bus. */
146
+ mcEnergizedOnly = $state(false);
147
+ /** Successful explicit solve to reveal in Studies; load edits do not advance it. */
148
+ mcCompletedSolveSeq = $state(0);
149
+ /** Detected on import, revalidated when preparing a solve; never removed from the graph. */
150
+ mcExcludedIslands = $state.raw([]);
151
+ mcPreparedScope = $state.raw(null);
152
+ /** Selected settings for the next explicit solve; results carry their own effective settings. */
153
+ mcOptions = $state({ ...DEFAULT_MC_PF_OPTIONS });
141
154
  mcSnapshot = $state.raw(null);
142
155
  mcSession = $state.raw(null);
143
156
  mcLoadBranches = $state.raw([]);
@@ -172,8 +185,9 @@ export class MulticonductorCase {
172
185
  /** Bumped to ask the views to bring the current selection into view again (an issue clicked
173
186
  * that is already selected). */
174
187
  revealSeq = $state(0);
175
- /** The full operating point read for whole-network views (overview, schematic); null until read,
176
- * and cleared when a large case's operating point changes. */
188
+ /** The full operating point read for whole-network views (overview, schematic): a stored
189
+ * result, or a live session's as columns. Null until read, and cleared when a large case's
190
+ * operating point changes. */
177
191
  overviewResult = $state.raw(null);
178
192
  overviewLoading = $state(false);
179
193
  /** The graph's buses and branches as the overview and the diagram read them: rebuilt when the
@@ -196,14 +210,17 @@ export class MulticonductorCase {
196
210
  this.label = init.label;
197
211
  this.fileName = init.fileName;
198
212
  this.summary = init.summary;
199
- this.moduleJson = init.summary.module_json ?? null;
213
+ this.mcInput = init.input ?? init.summary.module_json ?? null;
214
+ this.mcEnergizedOnly = init.summary.mc_energized_only ?? false;
215
+ this.mcExcludedIslands = init.summary.mc_excluded_islands ?? [];
216
+ this.mcPreparedScope = summarizeMcExcludedIslands(this.mcExcludedIslands);
200
217
  this.geoLayer = init.summary.geo_layer ?? null;
201
218
  this.graph = init.graph;
202
219
  this.coordsKind = init.coordsKind;
203
220
  this.view = init.view ?? null;
204
221
  }
205
222
  get mcPfReason() {
206
- if (!this.moduleJson)
223
+ if (!this.mcInput)
207
224
  return 'This case has no retained electrical input';
208
225
  if (this.summary?.mc_pf_enabled !== true)
209
226
  return 'AC power flow is unavailable in this build';
@@ -211,6 +228,13 @@ export class MulticonductorCase {
211
228
  this.summary.mc_pf_unavailable_reason ??
212
229
  (this.summary.mc_pf_supported !== true ? 'AC power flow is unavailable for this case' : null));
213
230
  }
231
+ /** Import detects scope; only a solve action opts into the selection. */
232
+ get mcRequiresEnergizedSolve() {
233
+ return this.mcExcludedIslands.length > 0;
234
+ }
235
+ get mcSolveLabel() {
236
+ return this.mcRequiresEnergizedSolve ? 'Solve energized network' : 'Run power flow';
237
+ }
214
238
  get mcPfSupported() {
215
239
  return this.mcPfReason === null;
216
240
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tellegen/svelte",
3
- "version": "0.4.6",
3
+ "version": "0.5.0",
4
4
  "description": "Svelte components for tellegen power system maps, local case files, and browser solves.",
5
5
  "keywords": [
6
6
  "svelte",
@@ -67,7 +67,7 @@
67
67
  "pack:dry-run": "npm pack --dry-run --cache ../../node_modules/.cache/npm-pack"
68
68
  },
69
69
  "dependencies": {
70
- "@tellegen/engine": "^0.4.6"
70
+ "@tellegen/engine": "^0.5.0"
71
71
  },
72
72
  "peerDependencies": {
73
73
  "@deck.gl/core": "^9.4.0",