@tellegen/svelte 0.1.2 → 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 (140) hide show
  1. package/README.md +46 -0
  2. package/dist/Sparkline.svelte.d.ts +0 -1
  3. package/dist/TellegenMap.svelte +328 -200
  4. package/dist/TellegenMap.svelte.d.ts +0 -1
  5. package/dist/TellegenProvider.svelte +4 -0
  6. package/dist/TellegenProvider.svelte.d.ts +0 -1
  7. package/dist/TellegenShell.svelte +19 -13
  8. package/dist/TellegenShell.svelte.d.ts +0 -1
  9. package/dist/TellegenViewer.svelte.d.ts +0 -1
  10. package/dist/api.d.ts +8 -2
  11. package/dist/api.js +6 -2
  12. package/dist/colors.d.ts +7 -8
  13. package/dist/colors.js +13 -13
  14. package/dist/components/AppFooter.svelte.d.ts +0 -1
  15. package/dist/components/AppHeader.svelte +27 -13
  16. package/dist/components/AppHeader.svelte.d.ts +0 -1
  17. package/dist/components/BindingLines.svelte +3 -6
  18. package/dist/components/BindingLines.svelte.d.ts +0 -1
  19. package/dist/components/BusPicker.svelte +8 -2
  20. package/dist/components/BusPicker.svelte.d.ts +0 -1
  21. package/dist/components/ControlPanel.svelte +106 -343
  22. package/dist/components/ControlPanel.svelte.d.ts +0 -1
  23. package/dist/components/DemandSlider.svelte +26 -8
  24. package/dist/components/DemandSlider.svelte.d.ts +0 -1
  25. package/dist/components/DiagramCanvas.svelte +486 -0
  26. package/dist/components/DiagramCanvas.svelte.d.ts +13 -0
  27. package/dist/components/DisplayControls.svelte +4 -2
  28. package/dist/components/DisplayControls.svelte.d.ts +0 -1
  29. package/dist/components/DropZone.svelte.d.ts +0 -1
  30. package/dist/components/FormulationSelector.svelte +8 -3
  31. package/dist/components/FormulationSelector.svelte.d.ts +0 -1
  32. package/dist/components/LocalCaseDetails.svelte +57 -44
  33. package/dist/components/LocalCaseDetails.svelte.d.ts +0 -1
  34. package/dist/components/McResults.svelte +365 -0
  35. package/dist/components/McResults.svelte.d.ts +11 -0
  36. package/dist/components/ModelDetails.svelte +230 -0
  37. package/dist/components/ModelDetails.svelte.d.ts +7 -0
  38. package/dist/components/MulticonductorDetails.svelte +227 -78
  39. package/dist/components/MulticonductorDetails.svelte.d.ts +0 -1
  40. package/dist/components/NetworkStats.svelte +20 -18
  41. package/dist/components/NetworkStats.svelte.d.ts +0 -1
  42. package/dist/components/NotificationCenter.svelte +243 -0
  43. package/dist/components/NotificationCenter.svelte.d.ts +3 -0
  44. package/dist/components/PanelCard.svelte +319 -0
  45. package/dist/components/PanelCard.svelte.d.ts +9 -0
  46. package/dist/components/PanelFrame.svelte +46 -0
  47. package/dist/components/PanelFrame.svelte.d.ts +16 -0
  48. package/dist/components/PanelHost.svelte +212 -0
  49. package/dist/components/PanelHost.svelte.d.ts +3 -0
  50. package/dist/components/PlacementCue.svelte.d.ts +0 -1
  51. package/dist/components/PowerFlowDetails.svelte +99 -0
  52. package/dist/components/PowerFlowDetails.svelte.d.ts +3 -0
  53. package/dist/components/RatingSlider.svelte +5 -9
  54. package/dist/components/RatingSlider.svelte.d.ts +0 -1
  55. package/dist/components/RestoreDefaultsButton.svelte.d.ts +0 -1
  56. package/dist/components/SavedNetworkDetails.svelte +214 -0
  57. package/dist/components/SavedNetworkDetails.svelte.d.ts +3 -0
  58. package/dist/components/SensitivityReadout.svelte +24 -16
  59. package/dist/components/SensitivityReadout.svelte.d.ts +0 -1
  60. package/dist/components/SizeLegend.svelte.d.ts +0 -1
  61. package/dist/components/SolveCard.svelte +46 -49
  62. package/dist/components/SolveCard.svelte.d.ts +0 -1
  63. package/dist/components/TopMovers.svelte +8 -3
  64. package/dist/components/TopMovers.svelte.d.ts +2 -18
  65. package/dist/components.d.ts +2 -1
  66. package/dist/components.js +2 -0
  67. package/dist/context.svelte.d.ts +0 -1
  68. package/dist/controller.svelte.d.ts +74 -41
  69. package/dist/controller.svelte.js +1095 -284
  70. package/dist/diagram-labels.d.ts +9 -0
  71. package/dist/diagram-labels.js +40 -0
  72. package/dist/display.d.ts +3 -8
  73. package/dist/display.js +31 -27
  74. package/dist/drop-classify.d.ts +5 -42
  75. package/dist/drop-classify.js +5 -78
  76. package/dist/drop-limits.d.ts +27 -0
  77. package/dist/drop-limits.js +83 -0
  78. package/dist/format.d.ts +0 -1
  79. package/dist/format.js +5 -4
  80. package/dist/index.d.ts +11 -7
  81. package/dist/index.js +8 -3
  82. package/dist/map-bounds.d.ts +5 -0
  83. package/dist/map-bounds.js +41 -0
  84. package/dist/map.d.ts +1 -2
  85. package/dist/map.js +1 -1
  86. package/dist/multiconductor.d.ts +11 -3
  87. package/dist/multiconductor.js +107 -14
  88. package/dist/notices.svelte.d.ts +29 -0
  89. package/dist/notices.svelte.js +82 -0
  90. package/dist/panels.svelte.d.ts +65 -0
  91. package/dist/panels.svelte.js +215 -0
  92. package/dist/state.svelte.d.ts +90 -24
  93. package/dist/state.svelte.js +94 -20
  94. package/dist/synthetic-layout.d.ts +0 -1
  95. package/dist/synthetic-layout.js +17 -2
  96. package/dist/transformer-icon.d.ts +0 -1
  97. package/dist/types.d.ts +0 -1
  98. package/package.json +28 -12
  99. package/dist/Sparkline.svelte.d.ts.map +0 -1
  100. package/dist/TellegenMap.svelte.d.ts.map +0 -1
  101. package/dist/TellegenProvider.svelte.d.ts.map +0 -1
  102. package/dist/TellegenShell.svelte.d.ts.map +0 -1
  103. package/dist/TellegenViewer.svelte.d.ts.map +0 -1
  104. package/dist/api.d.ts.map +0 -1
  105. package/dist/colors.d.ts.map +0 -1
  106. package/dist/components/AppFooter.svelte.d.ts.map +0 -1
  107. package/dist/components/AppHeader.svelte.d.ts.map +0 -1
  108. package/dist/components/BindingLines.svelte.d.ts.map +0 -1
  109. package/dist/components/BusPicker.svelte.d.ts.map +0 -1
  110. package/dist/components/ControlPanel.svelte.d.ts.map +0 -1
  111. package/dist/components/DemandSlider.svelte.d.ts.map +0 -1
  112. package/dist/components/DisplayControls.svelte.d.ts.map +0 -1
  113. package/dist/components/DropZone.svelte.d.ts.map +0 -1
  114. package/dist/components/FormulationSelector.svelte.d.ts.map +0 -1
  115. package/dist/components/LocalCaseDetails.svelte.d.ts.map +0 -1
  116. package/dist/components/MulticonductorDetails.svelte.d.ts.map +0 -1
  117. package/dist/components/NetworkStats.svelte.d.ts.map +0 -1
  118. package/dist/components/PlacementCue.svelte.d.ts.map +0 -1
  119. package/dist/components/RatingSlider.svelte.d.ts.map +0 -1
  120. package/dist/components/RestoreDefaultsButton.svelte.d.ts.map +0 -1
  121. package/dist/components/SensitivityReadout.svelte.d.ts.map +0 -1
  122. package/dist/components/SizeLegend.svelte.d.ts.map +0 -1
  123. package/dist/components/SolveCard.svelte.d.ts.map +0 -1
  124. package/dist/components/TopMovers.svelte.d.ts.map +0 -1
  125. package/dist/components.d.ts.map +0 -1
  126. package/dist/context.svelte.d.ts.map +0 -1
  127. package/dist/controller.svelte.d.ts.map +0 -1
  128. package/dist/display.d.ts.map +0 -1
  129. package/dist/drop-classify.d.ts.map +0 -1
  130. package/dist/format.d.ts.map +0 -1
  131. package/dist/index.d.ts.map +0 -1
  132. package/dist/map.d.ts.map +0 -1
  133. package/dist/multiconductor.d.ts.map +0 -1
  134. package/dist/state.svelte.d.ts.map +0 -1
  135. package/dist/study-package.d.ts +0 -9
  136. package/dist/study-package.d.ts.map +0 -1
  137. package/dist/study-package.js +0 -17
  138. package/dist/synthetic-layout.d.ts.map +0 -1
  139. package/dist/transformer-icon.d.ts.map +0 -1
  140. package/dist/types.d.ts.map +0 -1
@@ -0,0 +1,9 @@
1
+ type PositionedLabel = {
2
+ id: number;
3
+ lon: number;
4
+ lat: number;
5
+ name?: string | null;
6
+ };
7
+ /** Place labels in screen pixels so zooming reveals equipment names as space opens. */
8
+ export declare function diagramLabels(buses: PositionedLabel[], scale: number, x: number, y: number, width: number, height: number, selected: number | null): Set<number>;
9
+ export {};
@@ -0,0 +1,40 @@
1
+ /** Place labels in screen pixels so zooming reveals equipment names as space opens. */
2
+ export function diagramLabels(buses, scale, x, y, width, height, selected) {
3
+ const visible = new Set();
4
+ const cells = new Map();
5
+ const order = selected === null
6
+ ? buses
7
+ : [
8
+ ...buses.filter((bus) => bus.id === selected),
9
+ ...buses.filter((bus) => bus.id !== selected)
10
+ ];
11
+ for (const bus of order) {
12
+ const label = bus.name || String(bus.id);
13
+ const left = bus.lon * scale + x + 8;
14
+ const bottom = bus.lat * scale + y - 7;
15
+ const box = { left, bottom, right: left + label.length * 7 + 6, top: bottom - 16 };
16
+ if (box.right < 0 || box.left > width || box.bottom < 0 || box.top > height)
17
+ continue;
18
+ const keys = [];
19
+ let overlaps = false;
20
+ for (let col = Math.floor(Math.max(0, box.left) / 64); col <= Math.floor(Math.min(width, box.right) / 64); col++) {
21
+ for (let row = Math.floor(Math.max(0, box.top) / 64); row <= Math.floor(Math.min(height, box.bottom) / 64); row++) {
22
+ const key = `${col},${row}`;
23
+ keys.push(key);
24
+ if (cells
25
+ .get(key)
26
+ ?.some((other) => box.left < other.right &&
27
+ box.right > other.left &&
28
+ box.top < other.bottom &&
29
+ box.bottom > other.top))
30
+ overlaps = true;
31
+ }
32
+ }
33
+ if (overlaps)
34
+ continue;
35
+ visible.add(bus.id);
36
+ for (const key of keys)
37
+ cells.set(key, [...(cells.get(key) ?? []), box]);
38
+ }
39
+ return visible;
40
+ }
package/dist/display.d.ts CHANGED
@@ -10,14 +10,10 @@ export type DisplayOption = {
10
10
  copy: string;
11
11
  gradient: string;
12
12
  };
13
- /** The display variables available for a case's current solution and formulation:
14
- * LMP always, the DC phase angle under DC OPF, the relaxed |V| under SOCWR. Metadata
15
- * only; call `displaySeriesFor` for one mode's per-bus values. Empty when unsolved. */
13
+ /** Variables present in the current result. Power flow has no LMP values. */
16
14
  export declare function displayMetaFor(c: SolvableCase | null): DisplayOption[];
17
- /** Per-bus values for one display mode: LMP in $/MWh, the DC phase angle in rad, or
18
- * the SOCWR voltage magnitude (sqrt of the squared-voltage variable w, clamped at 0).
19
- * Empty when the case is unsolved. Single source for both the panel legend stats and
20
- * the map node coloring, so the |V| transform stays in one place. */
15
+ /** Bus LMPs, voltage angles in radians, or voltage magnitudes in per unit.
16
+ * SOCWR supplies squared voltages, which are converted to magnitudes here. */
21
17
  export declare function displaySeriesFor(c: SolvableCase | null, mode: DisplayMode): {
22
18
  bus: number;
23
19
  value: number;
@@ -28,4 +24,3 @@ export declare function caseDeltas(c: SolvableCase): DemandDeltas;
28
24
  /** The committed branch rating deltas for a case (MW from base, keyed by branch).
29
25
  * The rating counterpart of `caseDeltas`. */
30
26
  export declare function caseRatings(c: SolvableCase): BranchRatingDeltas;
31
- //# sourceMappingURL=display.d.ts.map
package/dist/display.js CHANGED
@@ -1,45 +1,44 @@
1
- import { lmpGradient } from './colors.js';
1
+ import { priceGradient } from './colors.js';
2
2
  import { priceCopy } from './format.js';
3
3
  import { CaseState } from './state.svelte.js';
4
- /** The display variables available for a case's current solution and formulation:
5
- * LMP always, the DC phase angle under DC OPF, the relaxed |V| under SOCWR. Metadata
6
- * only; call `displaySeriesFor` for one mode's per-bus values. Empty when unsolved. */
4
+ /** Variables present in the current result. Power flow has no LMP values. */
7
5
  export function displayMetaFor(c) {
8
6
  if (!c?.solution)
9
7
  return [];
10
- const options = [
11
- {
12
- mode: 'lmp',
8
+ const options = [];
9
+ if (c.formulation !== 'acpf' && c.solution.prices.length > 0) {
10
+ options.push({
11
+ mode: 'price',
13
12
  label: 'LMP',
14
- unit: '$/MWh',
13
+ unit: 'objective units/MW',
15
14
  copy: priceCopy(c.formulation),
16
- gradient: lmpGradient
17
- }
18
- ];
19
- if (c.formulation === 'dcopf' && c.solution.va.length > 0) {
20
- options.push({
21
- mode: 'angle',
22
- label: 'angle',
23
- unit: 'rad',
24
- copy: 'DC bus voltage phase angle from the current OPF solution.',
25
- gradient: lmpGradient
15
+ gradient: priceGradient
26
16
  });
27
17
  }
28
- if (c.formulation === 'socwr' && c.solution.w.length > 0) {
18
+ if (c.solution.vm?.length || c.solution.w.length > 0) {
29
19
  options.push({
30
20
  mode: 'voltage',
31
21
  label: '|V|',
32
22
  unit: 'pu',
33
- copy: 'SOCWR voltage magnitude from the current relaxed solution.',
34
- gradient: lmpGradient
23
+ copy: c.formulation === 'socwr'
24
+ ? 'SOCWR voltage magnitude from the current relaxed solution.'
25
+ : 'Bus voltage magnitude from the current power-flow result.',
26
+ gradient: priceGradient
27
+ });
28
+ }
29
+ if (c.solution.va.length > 0) {
30
+ options.push({
31
+ mode: 'angle',
32
+ label: 'angle',
33
+ unit: 'rad',
34
+ copy: 'Bus voltage angle from the current result.',
35
+ gradient: priceGradient
35
36
  });
36
37
  }
37
38
  return options;
38
39
  }
39
- /** Per-bus values for one display mode: LMP in $/MWh, the DC phase angle in rad, or
40
- * the SOCWR voltage magnitude (sqrt of the squared-voltage variable w, clamped at 0).
41
- * Empty when the case is unsolved. Single source for both the panel legend stats and
42
- * the map node coloring, so the |V| transform stays in one place. */
40
+ /** Bus LMPs, voltage angles in radians, or voltage magnitudes in per unit.
41
+ * SOCWR supplies squared voltages, which are converted to magnitudes here. */
43
42
  export function displaySeriesFor(c, mode) {
44
43
  const sol = c?.solution;
45
44
  if (!sol)
@@ -47,9 +46,14 @@ export function displaySeriesFor(c, mode) {
47
46
  if (mode === 'angle')
48
47
  return sol.va;
49
48
  if (mode === 'voltage') {
50
- return sol.w.map((s) => ({ bus: s.bus, value: Math.sqrt(Math.max(0, s.value)) }));
49
+ if (sol.vm?.length)
50
+ return sol.vm;
51
+ return sol.w.map((s) => ({
52
+ bus: s.bus,
53
+ value: Math.sqrt(Math.max(0, s.value))
54
+ }));
51
55
  }
52
- return sol.lmp.map((s) => ({ bus: s.bus, value: s.usd_per_mwh }));
56
+ return sol.prices.map((s) => ({ bus: s.bus, value: s.value }));
53
57
  }
54
58
  /** The committed demand deltas for a case (MW from base, keyed by bus). A backend
55
59
  * case always carries a deltas map; a local case defaults to an empty one. */
@@ -1,48 +1,11 @@
1
- /** The single owner of drop-file classification: given a file name or its JSON
2
- * content, decide which ingest path handles it. Extension routing (`.dss`, and
3
- * the balanced `.m`/`.raw`/`.aux` via `formatOf` in the engine) is name-based;
4
- * everything under `.json` is content-sniffed here so there is one place that
5
- * decides package-vs-case and balanced-vs-multiconductor.
6
- *
7
- * The rules mirror the readers they feed:
8
- * - a `.pio.json` package is recognized by its `schema` envelope field, then
9
- * split by the authoritative `model_kind` (balanced restores a study;
10
- * multiconductor is viewed);
11
- * - a non-package document is distribution JSON, PMD when it carries the
12
- * `data_model` marker and BMOPF otherwise (the same split
13
- * `powerio_dist` uses for `.json`), except a GeoJSON FeatureCollection,
14
- * which stays unrouted so the geo sidecar path keeps accepting it.
15
- *
16
- * Every function is total: malformed, truncated, or non-JSON input classifies
17
- * as `not-json` rather than throwing. */
18
- /** The schema URL prefix every `.pio.json` package envelope carries. */
19
- export declare const PIO_PACKAGE_SCHEMA_PREFIX = "https://powerio.dev/schema/pio-package";
20
- /** How a dropped JSON file should be ingested. */
21
- export type JsonDropKind =
22
- /** A saved balanced study package: restore the case, edits, and formulation. */
23
- 'balanced-package'
24
- /** A package carrying a multiconductor payload: view it (no solve). */
25
- | 'multiconductor-package'
26
- /** A BMOPF JSON distribution case. */
27
- | 'bmopf'
28
- /** A PowerModelsDistribution ENGINEERING JSON case. */
29
- | 'pmd'
30
- /** Not JSON, or JSON we do not route (left to the generic error path). */
31
- | 'not-json';
1
+ /** File name helpers for drop routing. JSON content classification lives in
2
+ * powerio's Rust/Wasm `classify_json_bytes` path, not in this TypeScript layer. */
32
3
  /** The distribution format for a file recognized by extension, or null. Only
33
- * `.dss` is name-routed; PMD and BMOPF share the `.json` extension and are
34
- * content-sniffed by {@link classifyJson}. */
4
+ * `.dss` is name routed; PMD and BMOPF share the `.json` extension and are
5
+ * classified by the engine. */
35
6
  export declare function distExtensionFormat(name: string): 'dss' | null;
36
7
  /** True for geographic sidecar files by extension (`.csv`, `.json`,
37
8
  * `.geojson`). Routable `.json` content (packages, distribution documents) is
38
- * consumed by {@link classifyJson} before this applies; what remains parses
9
+ * consumed by the engine classifier before this applies; what remains parses
39
10
  * through the engine's tolerant geo reader. */
40
11
  export declare function isGeoFileName(name: string): boolean;
41
- /** Whether `text` is a `.pio.json` package envelope, by its `schema` field. */
42
- export declare function isPackageEnvelope(obj: Record<string, unknown> | null): boolean;
43
- /** Classify a dropped JSON document into its ingest path. */
44
- export declare function classifyJson(text: string): JsonDropKind;
45
- /** Whether `text` is a saved study package (a balanced `.pio.json`). Kept for
46
- * the study-restore path; multiconductor packages are viewed, not restored. */
47
- export declare function isStudyPackageText(text: string): boolean;
48
- //# sourceMappingURL=drop-classify.d.ts.map
@@ -1,31 +1,14 @@
1
- /** The single owner of drop-file classification: given a file name or its JSON
2
- * content, decide which ingest path handles it. Extension routing (`.dss`, and
3
- * the balanced `.m`/`.raw`/`.aux` via `formatOf` in the engine) is name-based;
4
- * everything under `.json` is content-sniffed here so there is one place that
5
- * decides package-vs-case and balanced-vs-multiconductor.
6
- *
7
- * The rules mirror the readers they feed:
8
- * - a `.pio.json` package is recognized by its `schema` envelope field, then
9
- * split by the authoritative `model_kind` (balanced restores a study;
10
- * multiconductor is viewed);
11
- * - a non-package document is distribution JSON, PMD when it carries the
12
- * `data_model` marker and BMOPF otherwise (the same split
13
- * `powerio_dist` uses for `.json`), except a GeoJSON FeatureCollection,
14
- * which stays unrouted so the geo sidecar path keeps accepting it.
15
- *
16
- * Every function is total: malformed, truncated, or non-JSON input classifies
17
- * as `not-json` rather than throwing. */
18
- /** The schema URL prefix every `.pio.json` package envelope carries. */
19
- export const PIO_PACKAGE_SCHEMA_PREFIX = 'https://powerio.dev/schema/pio-package';
1
+ /** File name helpers for drop routing. JSON content classification lives in
2
+ * powerio's Rust/Wasm `classify_json_bytes` path, not in this TypeScript layer. */
20
3
  /** The distribution format for a file recognized by extension, or null. Only
21
- * `.dss` is name-routed; PMD and BMOPF share the `.json` extension and are
22
- * content-sniffed by {@link classifyJson}. */
4
+ * `.dss` is name routed; PMD and BMOPF share the `.json` extension and are
5
+ * classified by the engine. */
23
6
  export function distExtensionFormat(name) {
24
7
  return name.split('.').pop()?.toLowerCase() === 'dss' ? 'dss' : null;
25
8
  }
26
9
  /** True for geographic sidecar files by extension (`.csv`, `.json`,
27
10
  * `.geojson`). Routable `.json` content (packages, distribution documents) is
28
- * consumed by {@link classifyJson} before this applies; what remains parses
11
+ * consumed by the engine classifier before this applies; what remains parses
29
12
  * through the engine's tolerant geo reader. */
30
13
  export function isGeoFileName(name) {
31
14
  const dot = name.lastIndexOf('.');
@@ -34,59 +17,3 @@ export function isGeoFileName(name) {
34
17
  const ext = name.slice(dot + 1).toLowerCase();
35
18
  return ext === 'csv' || ext === 'json' || ext === 'geojson';
36
19
  }
37
- /** Parse `text` as JSON, returning the top-level object or null. */
38
- function topLevelObject(text) {
39
- let value;
40
- try {
41
- value = JSON.parse(text);
42
- }
43
- catch {
44
- return null;
45
- }
46
- return typeof value === 'object' && value !== null && !Array.isArray(value)
47
- ? value
48
- : null;
49
- }
50
- /** Whether `text` is a `.pio.json` package envelope, by its `schema` field. */
51
- export function isPackageEnvelope(obj) {
52
- return typeof obj?.schema === 'string' && obj.schema.startsWith(PIO_PACKAGE_SCHEMA_PREFIX);
53
- }
54
- /** The package's model family. `model_kind` is authoritative and stored
55
- * explicitly; fall back to the payload's own `model.kind` tag for a package
56
- * written before the field existed. */
57
- function packageModelKind(obj) {
58
- const explicit = obj.model_kind;
59
- if (explicit === 'balanced' || explicit === 'multiconductor')
60
- return explicit;
61
- const payloadKind = obj.model?.kind;
62
- if (payloadKind === 'balanced' || payloadKind === 'multiconductor')
63
- return payloadKind;
64
- return null;
65
- }
66
- /** Classify a dropped JSON document into its ingest path. */
67
- export function classifyJson(text) {
68
- const obj = topLevelObject(text);
69
- if (!obj)
70
- return 'not-json';
71
- if (isPackageEnvelope(obj)) {
72
- const kind = packageModelKind(obj);
73
- // A package with no readable kind is treated as balanced: that is the
74
- // historical payload and the study-restore path fails closed on a
75
- // non-balanced document anyway.
76
- return kind === 'multiconductor' ? 'multiconductor-package' : 'balanced-package';
77
- }
78
- // A GeoJSON FeatureCollection saved as `.json` is coordinate data for the
79
- // geo sidecar path, which accepted it before this classifier existed; leave
80
- // it unrouted so it falls through.
81
- if (Array.isArray(obj.features))
82
- return 'not-json';
83
- // Non-package JSON is a distribution document: PMD declares `data_model`,
84
- // everything else is routed to BMOPF (which gives the precise parse error
85
- // when it is neither).
86
- return 'data_model' in obj ? 'pmd' : 'bmopf';
87
- }
88
- /** Whether `text` is a saved study package (a balanced `.pio.json`). Kept for
89
- * the study-restore path; multiconductor packages are viewed, not restored. */
90
- export function isStudyPackageText(text) {
91
- return classifyJson(text) === 'balanced-package';
92
- }
@@ -0,0 +1,27 @@
1
+ /** Browser drop limits. Each accepted file is copied into JavaScript and worker/wasm
2
+ * memory, so the whole batch is checked before any file contents are read. */
3
+ export declare const MAX_DROP_FILES = 32;
4
+ export declare const MAX_DROP_FILE_BYTES: number;
5
+ export declare const MAX_DROP_TOTAL_BYTES: number;
6
+ type DropFileDescriptor = {
7
+ readonly name: string;
8
+ readonly size: number;
9
+ };
10
+ /** One-at-a-time admission for browser ingestion. A second drop is refused
11
+ * while the first still owns its release callback, so independently valid
12
+ * batches cannot queue enough worker copies to bypass the aggregate limit. */
13
+ export declare class DropBatchGate {
14
+ #private;
15
+ enter(): (() => void) | null;
16
+ }
17
+ /** Return bytes retained by an earlier routing pass, or read the file once when
18
+ * no retained buffer exists. Keeping this decision here makes the no-reread
19
+ * guarantee independently testable. */
20
+ export declare function readDropFileBytes(file: {
21
+ arrayBuffer(): Promise<ArrayBuffer>;
22
+ }, retained?: Uint8Array): Promise<Uint8Array>;
23
+ /** Validate and materialize an array-like browser file batch without calling
24
+ * `Array.from` first. The count and every declared byte size must be safe integers,
25
+ * and aggregate addition is guarded before it is performed. */
26
+ export declare function validateDropBatch<T extends DropFileDescriptor>(input: ArrayLike<T>): T[];
27
+ export {};
@@ -0,0 +1,83 @@
1
+ /** Browser drop limits. Each accepted file is copied into JavaScript and worker/wasm
2
+ * memory, so the whole batch is checked before any file contents are read. */
3
+ export const MAX_DROP_FILES = 32;
4
+ export const MAX_DROP_FILE_BYTES = 128 * 1024 * 1024;
5
+ export const MAX_DROP_TOTAL_BYTES = 128 * 1024 * 1024;
6
+ /** One-at-a-time admission for browser ingestion. A second drop is refused
7
+ * while the first still owns its release callback, so independently valid
8
+ * batches cannot queue enough worker copies to bypass the aggregate limit. */
9
+ export class DropBatchGate {
10
+ #active = false;
11
+ enter() {
12
+ if (this.#active)
13
+ return null;
14
+ this.#active = true;
15
+ let released = false;
16
+ return () => {
17
+ if (released)
18
+ return;
19
+ released = true;
20
+ this.#active = false;
21
+ };
22
+ }
23
+ }
24
+ /** Return bytes retained by an earlier routing pass, or read the file once when
25
+ * no retained buffer exists. Keeping this decision here makes the no-reread
26
+ * guarantee independently testable. */
27
+ export async function readDropFileBytes(file, retained) {
28
+ return retained ?? new Uint8Array(await file.arrayBuffer());
29
+ }
30
+ /** Validate and materialize an array-like browser file batch without calling
31
+ * `Array.from` first. The count and every declared byte size must be safe integers,
32
+ * and aggregate addition is guarded before it is performed. */
33
+ export function validateDropBatch(input) {
34
+ let length;
35
+ try {
36
+ length = input.length;
37
+ }
38
+ catch {
39
+ throw new Error('the dropped file count is invalid');
40
+ }
41
+ if (typeof length !== 'number' || !Number.isSafeInteger(length) || length < 0) {
42
+ throw new Error('the dropped file count is invalid');
43
+ }
44
+ if (length > MAX_DROP_FILES) {
45
+ throw new Error(`drop at most ${MAX_DROP_FILES} files at a time`);
46
+ }
47
+ const files = [];
48
+ let totalBytes = 0;
49
+ for (let index = 0; index < length; index++) {
50
+ let file;
51
+ try {
52
+ file = input[index];
53
+ }
54
+ catch {
55
+ throw new Error(`dropped file ${index + 1} is invalid`);
56
+ }
57
+ if (!file || typeof file !== 'object') {
58
+ throw new Error(`dropped file ${index + 1} is invalid`);
59
+ }
60
+ let size;
61
+ let name;
62
+ try {
63
+ size = file.size;
64
+ name = file.name;
65
+ }
66
+ catch {
67
+ throw new Error(`dropped file ${index + 1} is invalid`);
68
+ }
69
+ const label = typeof name === 'string' && name.length > 0 ? name : `dropped file ${index + 1}`;
70
+ if (typeof size !== 'number' || !Number.isSafeInteger(size) || size < 0) {
71
+ throw new Error(`${label} has an invalid file size`);
72
+ }
73
+ if (size > MAX_DROP_FILE_BYTES) {
74
+ throw new Error(`${label} exceeds the ${MAX_DROP_FILE_BYTES}-byte per-file limit`);
75
+ }
76
+ if (size > MAX_DROP_TOTAL_BYTES - totalBytes) {
77
+ throw new Error(`the dropped files exceed the ${MAX_DROP_TOTAL_BYTES}-byte total limit`);
78
+ }
79
+ totalBytes += size;
80
+ files.push(file);
81
+ }
82
+ return files;
83
+ }
package/dist/format.d.ts CHANGED
@@ -27,4 +27,3 @@ export declare const fmt: Intl.NumberFormat;
27
27
  export declare const signed: (v: number) => string;
28
28
  export declare const signedExp: (v: number) => string;
29
29
  export declare const displayFmt: (mode: DisplayMode, value: number) => string;
30
- //# sourceMappingURL=format.d.ts.map
package/dist/format.js CHANGED
@@ -10,9 +10,10 @@ export function formulationHint(id) {
10
10
  return FORMULATIONS.find((f) => f.id === id)?.hint ?? id;
11
11
  }
12
12
  export function priceCopy(id) {
13
+ const definition = 'Locational marginal price (LMP): the change in the OPF objective for an additional MW at a bus, in objective units/MW.';
13
14
  return id === 'socwr'
14
- ? 'Select a bus to see how relaxed prices change.'
15
- : 'Select a bus to see how prices change.';
15
+ ? `${definition} Prices come from the SOCWR relaxation. Select a bus to see their sensitivity.`
16
+ : `${definition} Select a bus to see its demand sensitivity.`;
16
17
  }
17
18
  export function splitName(name) {
18
19
  const m = name.match(/^(.*?)\s*\((.*)\)$/);
@@ -22,7 +23,7 @@ export function solveMetaLabel(c) {
22
23
  if ((c.iterations ?? []).length > 1)
23
24
  return `${c.iterations?.length} iterations`;
24
25
  if (c.solveBackend === 'clarabel-wasm-server-sensitivity')
25
- return 'server dLMP/dd';
26
+ return 'server price sensitivity';
26
27
  return c.solveBackend === 'rust-server' ? 'server solve' : 'browser solve';
27
28
  }
28
29
  export function rgbaCss([r, g, b, a]) {
@@ -46,4 +47,4 @@ export function extent(values) {
46
47
  export const fmt = new Intl.NumberFormat('en-US', { maximumFractionDigits: 1 });
47
48
  export const signed = (v) => `${v < 0 ? '−' : '+'}${fmt.format(Math.abs(v))}`;
48
49
  export const signedExp = (v) => `${v < 0 ? '−' : '+'}${Math.abs(v).toExponential(2)}`;
49
- export const displayFmt = (mode, value) => mode === 'lmp' ? fmt.format(value) : value.toFixed(3);
50
+ export const displayFmt = (mode, value) => mode === 'price' ? fmt.format(value) : value.toFixed(3);
package/dist/index.d.ts CHANGED
@@ -1,3 +1,8 @@
1
+ export { getNoticeCenter, errorNoticeTitle } from './notices.svelte.js';
2
+ export { default as ModelDetails } from './components/ModelDetails.svelte';
3
+ export { default as PanelFrame } from './components/PanelFrame.svelte';
4
+ export { getPanelLayout } from './panels.svelte.js';
5
+ export type { PanelSide, PanelPosition, PanelBounds } from './panels.svelte.js';
1
6
  export { default as TellegenProvider } from './TellegenProvider.svelte';
2
7
  export { default as TellegenShell } from './TellegenShell.svelte';
3
8
  export { default as TellegenViewer } from './TellegenViewer.svelte';
@@ -6,16 +11,15 @@ export { DEFAULT_TELLEGEN_UI_CONFIG, getAppState, getController, getUiConfig, re
6
11
  export { ApiError, createApiClient } from './api.js';
7
12
  export { Controller, createController } from './controller.svelte.js';
8
13
  export { AppState, CaseState, LocalCase, MulticonductorCase, createAppState } from './state.svelte.js';
9
- export { attachmentColor, attachmentGlyph, edgeColor, edgeWidth, phaseColor, placeMultiView, buildGeographicView } from './multiconductor.js';
10
- export { classifyJson, distExtensionFormat, isStudyPackageText } from './drop-classify.js';
14
+ export { attachmentColor, attachmentGlyph, edgeColor, edgeWidth, phaseColor, neutralTerminal, placeMultiView, buildGeographicView } from './multiconductor.js';
15
+ export { distExtensionFormat } from './drop-classify.js';
11
16
  export { caseDeltas, caseRatings, displayMetaFor, displaySeriesFor } from './display.js';
12
- export { DEFAULT_FORMULATION, FORMULATIONS, BrowserStudy, Study, capabilities, createStudy, errorText, formatOf, ingestCase, isDisplayFile, isPermanentEngineFailure, parseDisplay, solveJson } from '@tellegen/engine';
17
+ export { DEFAULT_FORMULATION, FORMULATIONS, BrowserStudy, Study, capabilities, createStudy, errorText, formatOf, classifyJson, ingestJsonDrop, ingestCase, isDisplayFile, isPermanentEngineFailure, parseDisplay, solveModule } from '@tellegen/engine';
13
18
  export type { BranchRatingDeltas, CaseSummary, DemandDeltas, Network, NetworkBranch, NetworkBus, SensitivityColumn, Solution, SolveIteration, SolveStreamHandlers, TellegenApiClient, TellegenApiClientOptions } from './api.js';
14
19
  export type { TellegenUiConfig, TellegenUiOptions } from './context.svelte.js';
15
20
  export type { ControllerOptions } from './controller.svelte.js';
16
21
  export type { DisplayOption } from './display.js';
17
- export type { DemandRangeMode, DisplayMode, FallbackTarget, LocalCaseInit, LocalSubstations, MultiCaseSummary, MultiCoordsKind, SolvableCase, SolveBackend } from './state.svelte.js';
18
- export type { JsonDropKind } from './drop-classify.js';
22
+ export type { CameraSnapshot, DiagramCameraSnapshot, DemandRangeMode, DisplayMode, FallbackTarget, LocalCaseInit, LocalSubstations, MultiCaseSummary, MultiCoordsKind, SolvableCase, StudyDisplaySnapshot, SolveBackend } from './state.svelte.js';
19
23
  export type { MultiView, PlacedMultiBus, PlacedMultiEdge } from './multiconductor.js';
20
- export type { CaseFileSummary, DistAttachmentKind, DistEdgeKind, DistGraph, DistGraphAttachment, DistGraphBus, DistGraphEdge, DisplayPreview, Formulation, IngestedCase, IngestedDistCase, SensTarget, Topology, TopologyBranch, TopologyBus } from '@tellegen/engine';
21
- //# sourceMappingURL=index.d.ts.map
24
+ 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';
25
+ export { default as McResults } from './components/McResults.svelte';
package/dist/index.js CHANGED
@@ -1,3 +1,7 @@
1
+ export { getNoticeCenter, errorNoticeTitle } from './notices.svelte.js';
2
+ export { default as ModelDetails } from './components/ModelDetails.svelte';
3
+ export { default as PanelFrame } from './components/PanelFrame.svelte';
4
+ export { getPanelLayout } from './panels.svelte.js';
1
5
  export { default as TellegenProvider } from './TellegenProvider.svelte';
2
6
  export { default as TellegenShell } from './TellegenShell.svelte';
3
7
  export { default as TellegenViewer } from './TellegenViewer.svelte';
@@ -6,7 +10,8 @@ export { DEFAULT_TELLEGEN_UI_CONFIG, getAppState, getController, getUiConfig, re
6
10
  export { ApiError, createApiClient } from './api.js';
7
11
  export { Controller, createController } from './controller.svelte.js';
8
12
  export { AppState, CaseState, LocalCase, MulticonductorCase, createAppState } from './state.svelte.js';
9
- export { attachmentColor, attachmentGlyph, edgeColor, edgeWidth, phaseColor, placeMultiView, buildGeographicView } from './multiconductor.js';
10
- export { classifyJson, distExtensionFormat, isStudyPackageText } from './drop-classify.js';
13
+ export { attachmentColor, attachmentGlyph, edgeColor, edgeWidth, phaseColor, neutralTerminal, placeMultiView, buildGeographicView } from './multiconductor.js';
14
+ export { distExtensionFormat } from './drop-classify.js';
11
15
  export { caseDeltas, caseRatings, displayMetaFor, displaySeriesFor } from './display.js';
12
- export { DEFAULT_FORMULATION, FORMULATIONS, BrowserStudy, Study, capabilities, createStudy, errorText, formatOf, ingestCase, isDisplayFile, isPermanentEngineFailure, parseDisplay, solveJson } from '@tellegen/engine';
16
+ export { DEFAULT_FORMULATION, FORMULATIONS, BrowserStudy, Study, capabilities, createStudy, errorText, formatOf, classifyJson, ingestJsonDrop, ingestCase, isDisplayFile, isPermanentEngineFailure, parseDisplay, solveModule } from '@tellegen/engine';
17
+ export { default as McResults } from './components/McResults.svelte';
@@ -0,0 +1,5 @@
1
+ export type MapBounds = [[number, number], [number, number]];
2
+ /** Fold finite Web Mercator coordinates into map bounds. Invalid points are
3
+ * ignored so one malformed row cannot hide an otherwise usable network, and a
4
+ * single surviving point still yields a box `fitBounds` can frame. */
5
+ export declare function foldMapBounds(points: Iterable<readonly [number, number]>): MapBounds | null;
@@ -0,0 +1,41 @@
1
+ /** Half-width given to an axis whose extent collapsed to a single value. A
2
+ * zero-span box makes `fitBounds` divide by zero, clamp to the map's maximum
3
+ * zoom, and slam the camera onto one point instead of framing it. */
4
+ const DEGENERATE_PAD_DEGREES = 0.005;
5
+ /** Fold finite Web Mercator coordinates into map bounds. Invalid points are
6
+ * ignored so one malformed row cannot hide an otherwise usable network, and a
7
+ * single surviving point still yields a box `fitBounds` can frame. */
8
+ export function foldMapBounds(points) {
9
+ let minLon = Infinity;
10
+ let minLat = Infinity;
11
+ let maxLon = -Infinity;
12
+ let maxLat = -Infinity;
13
+ for (const [lon, lat] of points) {
14
+ if (!Number.isFinite(lon) ||
15
+ !Number.isFinite(lat) ||
16
+ lon < -180 ||
17
+ lon > 180 ||
18
+ lat < -85.05 ||
19
+ lat > 85.05) {
20
+ continue;
21
+ }
22
+ minLon = Math.min(minLon, lon);
23
+ minLat = Math.min(minLat, lat);
24
+ maxLon = Math.max(maxLon, lon);
25
+ maxLat = Math.max(maxLat, lat);
26
+ }
27
+ if (!Number.isFinite(minLon) || !Number.isFinite(minLat))
28
+ return null;
29
+ if (minLon === maxLon) {
30
+ minLon -= DEGENERATE_PAD_DEGREES;
31
+ maxLon += DEGENERATE_PAD_DEGREES;
32
+ }
33
+ if (minLat === maxLat) {
34
+ minLat -= DEGENERATE_PAD_DEGREES;
35
+ maxLat += DEGENERATE_PAD_DEGREES;
36
+ }
37
+ return [
38
+ [minLon, minLat],
39
+ [maxLon, maxLat]
40
+ ];
41
+ }
package/dist/map.d.ts CHANGED
@@ -1,9 +1,8 @@
1
1
  import type { ComponentProps } from 'svelte';
2
2
  import TellegenMap from './TellegenMap.svelte';
3
3
  export { default as TellegenMap } from './TellegenMap.svelte';
4
- export { branchColor, branchWidth, busNeutral, busRadius, lmpColor, lmpDomain, lmpGradient, scalarDomain, sensColor, sensFlatColor, sensGradient, sensNeutral, sensitivityDomain } from './colors.js';
4
+ export { branchColor, branchWidth, busNeutral, busRadius, priceColor, priceDomain, priceGradient, scalarDomain, sensColor, sensFlatColor, sensGradient, sensNeutral, sensitivityDomain } from './colors.js';
5
5
  export { caseDeltas, displayMetaFor, displaySeriesFor } from './display.js';
6
6
  export type { RGBA, SensitivityDomain } from './colors.js';
7
7
  export type { DisplayOption } from './display.js';
8
8
  export type TellegenMapProps = ComponentProps<typeof TellegenMap>;
9
- //# sourceMappingURL=map.d.ts.map
package/dist/map.js CHANGED
@@ -1,4 +1,4 @@
1
1
  import TellegenMap from './TellegenMap.svelte';
2
2
  export { default as TellegenMap } from './TellegenMap.svelte';
3
- export { branchColor, branchWidth, busNeutral, busRadius, lmpColor, lmpDomain, lmpGradient, scalarDomain, sensColor, sensFlatColor, sensGradient, sensNeutral, sensitivityDomain } from './colors.js';
3
+ export { branchColor, branchWidth, busNeutral, busRadius, priceColor, priceDomain, priceGradient, scalarDomain, sensColor, sensFlatColor, sensGradient, sensNeutral, sensitivityDomain } from './colors.js';
4
4
  export { caseDeltas, displayMetaFor, displaySeriesFor } from './display.js';
@@ -12,6 +12,7 @@ export interface PlacedMultiBus {
12
12
  lat: number;
13
13
  terminals: string[];
14
14
  grounded: string[];
15
+ neutral_terminal?: string | null;
15
16
  load_kw: number;
16
17
  gen_kw: number;
17
18
  has_source: boolean;
@@ -28,7 +29,7 @@ export interface PlacedMultiEdge {
28
29
  conductors: [string, string][];
29
30
  closed: boolean;
30
31
  n_phases: number;
31
- path: [[number, number], [number, number]];
32
+ path: [number, number][];
32
33
  }
33
34
  /** The placed graph the map renders. */
34
35
  export interface MultiView {
@@ -37,7 +38,11 @@ export interface MultiView {
37
38
  }
38
39
  /** Place a geographic graph: each bus's `xy` is `[longitude, latitude]` and
39
40
  * drops straight onto the map. */
40
- export declare function buildGeographicView(graph: DistGraph): MultiView;
41
+ export declare function buildGeographicView(graph: DistGraph, layer?: string): MultiView;
42
+ /** Retain native drawing positions; unpositioned buses receive a separate generated layout. */
43
+ export declare function buildDiagramView(graph: DistGraph, layer?: string): MultiView;
44
+ /** Numeric indices address drawing objects only; source identities remain on each object. */
45
+ export declare function multiDiagramNetwork(id: string, name: string, view: MultiView): import('./api.js').Network;
41
46
  /** Place a non-geographic graph at a map `center`: fit the provided planar
42
47
  * coordinates when the case carries them, otherwise fall back to the shared
43
48
  * synthetic force layout. */
@@ -48,6 +53,10 @@ export declare function placeMultiView(graph: DistGraph, center: PlacementCenter
48
53
  export declare function phaseColor(terminal: string): RGBA;
49
54
  /** True when a terminal is a phase conductor (not a neutral/ground return). */
50
55
  export declare function isPhaseTerminal(terminal: string): boolean;
56
+ /** Return the explicit neutral conductor in a terminal map. Other non-phase
57
+ * labels are left alone: an arbitrary terminal name is not evidence that the
58
+ * network has a usable neutral reference. */
59
+ export declare function neutralTerminal(terminals: string[], declared?: string | null): string | null;
51
60
  /** Base color for an edge by kind and state. Lines are the warm gray of the
52
61
  * single-phase branches; transformers are steel; a switch is amber when closed
53
62
  * and a faded red when open. */
@@ -73,4 +82,3 @@ export declare function transformerMarks(edges: PlacedMultiEdge[]): TransformerM
73
82
  export declare function attachmentColor(kind: DistAttachmentKind): RGBA;
74
83
  /** A short glyph for an attachment kind, for the panel legend and badges. */
75
84
  export declare function attachmentGlyph(kind: DistAttachmentKind): string;
76
- //# sourceMappingURL=multiconductor.d.ts.map