@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
@@ -2,37 +2,49 @@
2
2
  import { getAppState, getController } from '../context.svelte.js';
3
3
  import { fmt } from '../format.js';
4
4
  import type { LocalCase } from '../state.svelte.js';
5
+ import { formatPowerIoDiagnostic, type PowerIoDiagnostic } from '@tellegen/engine';
5
6
 
6
7
  const app = getAppState();
7
8
  const ctrl = getController();
8
9
 
9
- // Powerio writer tokens the committed state exports to; label carries the extension.
10
+ // Grid exchange formats the committed state can export.
10
11
  const EXPORT_FORMATS = [
11
12
  { token: 'matpower', label: 'MATPOWER (.m)' },
12
13
  { token: 'psse', label: 'PSS/E (.raw)' },
14
+ { token: 'powerworld', label: 'PowerWorld (.aux)' },
15
+ { token: 'pslf', label: 'PSLF (.epc)' },
13
16
  { token: 'powermodels-json', label: 'PowerModels (.json)' },
14
- { token: 'pandapower-json', label: 'pandapower (.json)' },
15
- { token: 'powerio-json', label: 'PowerIO (.json)' }
17
+ { token: 'pandapower-json', label: 'pandapower (.json)' }
16
18
  ];
17
19
 
18
20
  let busy = $state(false);
19
21
  let exportOpen = $state(false);
20
- let exportWarnings = $state<string[]>([]);
22
+ let exportDiagnostics = $state<PowerIoDiagnostic[]>([]);
21
23
 
22
- async function saveStudy(lc: LocalCase) {
24
+ async function saveCase(lc: LocalCase) {
23
25
  busy = true;
24
- exportWarnings = [];
26
+ exportDiagnostics = [];
25
27
  try {
26
- await ctrl.saveStudyPackage(lc);
28
+ await ctrl.saveCaseModule(lc);
27
29
  } finally {
28
30
  busy = false;
29
31
  }
30
32
  }
31
33
 
32
- async function exportStudy(lc: LocalCase, token: string) {
34
+ async function saveSolution(lc: LocalCase) {
33
35
  busy = true;
36
+ exportDiagnostics = [];
34
37
  try {
35
- exportWarnings = await ctrl.exportStudyAs(lc, token);
38
+ await ctrl.saveSolutionModule(lc);
39
+ } finally {
40
+ busy = false;
41
+ }
42
+ }
43
+
44
+ async function exportCase(lc: LocalCase, token: string) {
45
+ busy = true;
46
+ try {
47
+ exportDiagnostics = await ctrl.exportCaseAs(lc, token);
36
48
  exportOpen = false;
37
49
  } finally {
38
50
  busy = false;
@@ -91,17 +103,17 @@
91
103
  <dd>{fmt.format(lc.summary.base_mva)}</dd>
92
104
  </div>
93
105
  </dl>
94
- {#if lc.summary.warnings.length > 0}
95
- <ul class="warnings mono">
96
- {#each lc.summary.warnings.slice(0, 4) as w, i (i)}
97
- <li>{w}</li>
98
- {/each}
99
- {#if lc.summary.warnings.length > 4}
100
- <li>+{lc.summary.warnings.length - 4} more</li>
101
- {/if}
102
- </ul>
103
- {/if}
104
- {#if !lc.view}
106
+ {#if lc.displayMode === 'diagram' && lc.diagram}
107
+ <p class="footnote mono">Drawing: {lc.diagram.name}</p>
108
+ {#if lc.diagram.warnings.length}
109
+ <details>
110
+ <summary>Drawing details</summary>
111
+ <ul class="warnings mono">
112
+ {#each lc.diagram.warnings as warning, i (i)}<li>{warning}</li>{/each}
113
+ </ul>
114
+ </details>
115
+ {/if}
116
+ {:else if !lc.view}
105
117
  <p class="footnote mono">
106
118
  no coordinates in this file: click the map or drop a geographic file
107
119
  </p>
@@ -114,22 +126,17 @@
114
126
  coordinates: geographic file data from {lc.geoSource}
115
127
  </p>
116
128
  {/if}
117
- {#if lc.geoWarnings && lc.geoWarnings.length > 0}
118
- <ul class="warnings mono">
119
- {#each lc.geoWarnings.slice(0, 4) as w, i (i)}
120
- <li>{w}</li>
121
- {/each}
122
- {#if lc.geoWarnings.length > 4}
123
- <li>+{lc.geoWarnings.length - 4} more</li>
124
- {/if}
125
- </ul>
126
- {/if}
127
129
  <p class="footnote mono">parsed in your browser by powerio (wasm); never uploaded</p>
128
- {#if lc.networkJson}
129
- <div class="study">
130
- <button class="reset mono" disabled={busy} onclick={() => saveStudy(lc)}>
131
- save study (.pio.json)
130
+ {#if lc.studyInputJson}
131
+ <div class="case-actions">
132
+ <button class="reset mono" disabled={busy} onclick={() => saveCase(lc)}>
133
+ save PowerIO module (.json)
132
134
  </button>
135
+ {#if lc.formulation === 'dcopf' && lc.solution}
136
+ <button class="reset mono" disabled={busy} onclick={() => saveSolution(lc)}>
137
+ save exact solution (.json)
138
+ </button>
139
+ {/if}
133
140
  <div class="export">
134
141
  <button
135
142
  class="reset mono"
@@ -143,7 +150,11 @@
143
150
  <ul class="export-menu mono">
144
151
  {#each EXPORT_FORMATS as f (f.token)}
145
152
  <li>
146
- <button class="reset mono" disabled={busy} onclick={() => exportStudy(lc, f.token)}>
153
+ <button
154
+ class="reset mono"
155
+ disabled={busy}
156
+ onclick={() => exportCase(lc, f.token)}
157
+ >
147
158
  {f.label}
148
159
  </button>
149
160
  </li>
@@ -151,25 +162,27 @@
151
162
  </ul>
152
163
  {/if}
153
164
  </div>
154
- {#if lc.view}
165
+ {#if lc.view || lc.diagram}
155
166
  <button class="reset mono" disabled={busy} onclick={() => downloadLayout(lc)}>
156
- download layout (.geo.json)
167
+ {lc.displayMode === 'diagram'
168
+ ? 'Download drawing (.geo.json)'
169
+ : 'Download geography (.geo.json)'}
157
170
  </button>
158
171
  {/if}
159
172
  </div>
160
- {#if exportWarnings.length > 0}
173
+ {#if exportDiagnostics.length > 0}
161
174
  <ul class="warnings mono">
162
- {#each exportWarnings.slice(0, 4) as w, i (i)}
163
- <li>{w}</li>
175
+ {#each exportDiagnostics.slice(0, 4) as diagnostic, i (i)}
176
+ <li>{formatPowerIoDiagnostic(diagnostic)}</li>
164
177
  {/each}
165
- {#if exportWarnings.length > 4}
166
- <li>+{exportWarnings.length - 4} more</li>
178
+ {#if exportDiagnostics.length > 4}
179
+ <li>+{exportDiagnostics.length - 4} more</li>
167
180
  {/if}
168
181
  </ul>
169
182
  {/if}
170
183
  {/if}
171
184
  {/if}
172
- {#if lc.topology && lc.coordsKind !== 'file'}
185
+ {#if lc.displayMode !== 'diagram' && lc.topology && lc.coordsKind !== 'file'}
173
186
  <button class="reset mono" onclick={() => ctrl.moveLocalCase(lc)}>
174
187
  {lc.coordsKind === 'synthetic_pending'
175
188
  ? 'place on map'
@@ -223,7 +236,7 @@
223
236
  color: var(--text-accent);
224
237
  }
225
238
 
226
- .study {
239
+ .case-actions {
227
240
  display: flex;
228
241
  flex-wrap: wrap;
229
242
  gap: 8px;
@@ -1,4 +1,3 @@
1
1
  declare const LocalCaseDetails: import("svelte").Component<Record<string, never>, {}, "">;
2
2
  type LocalCaseDetails = ReturnType<typeof LocalCaseDetails>;
3
3
  export default LocalCaseDetails;
4
- //# sourceMappingURL=LocalCaseDetails.svelte.d.ts.map
@@ -0,0 +1,365 @@
1
+ <script lang="ts">
2
+ import type { McPfResult, McComplex, DistGraph } from '@tellegen/engine';
3
+ import { neutralTerminal } from '../multiconductor.js';
4
+ let {
5
+ result,
6
+ elapsedMs,
7
+ selectedBus,
8
+ selectedEdge,
9
+ graph
10
+ }: {
11
+ result: McPfResult;
12
+ elapsedMs: number | null;
13
+ selectedBus: string | null;
14
+ selectedEdge: string | null;
15
+ graph: DistGraph | null;
16
+ } = $props();
17
+ let busChoice = $state('');
18
+ let edgeChoice = $state('');
19
+ const busId = $derived(
20
+ selectedBus ??
21
+ (graph?.buses.some((b) => b.id === busChoice) ? busChoice : graph?.buses[0]?.id) ??
22
+ ''
23
+ );
24
+ const bus = $derived(graph?.buses.find((b) => b.id === busId));
25
+ const neutralName = $derived(bus ? neutralTerminal(bus.terminals, bus.neutral_terminal) : null);
26
+ const neutral = $derived(
27
+ result.terminals.find((t) => t.bus === busId && t.terminal === neutralName)?.voltage
28
+ );
29
+ const edgeId = $derived(selectedEdge ?? edgeChoice);
30
+ const sourcePower = $derived(
31
+ result.source_reactions.reduce(
32
+ (sum, port) => ({
33
+ re: sum.re + port.power_into_network.re,
34
+ im: sum.im + port.power_into_network.im
35
+ }),
36
+ { re: 0, im: 0 }
37
+ )
38
+ );
39
+ const passiveLoss = $derived(
40
+ result.element_ports
41
+ .filter((port) => !['load', 'generator', 'ibr'].includes(port.kind))
42
+ .reduce((sum, port) => sum + port.power_into_element.re, 0)
43
+ );
44
+ let search = $state('');
45
+ let page = $state(0);
46
+ const size = 20;
47
+ const terminals = $derived(
48
+ result.terminals.filter(
49
+ (row) =>
50
+ (!busId || row.bus === busId) &&
51
+ `${row.bus} ${row.terminal}`.toLowerCase().includes(search.toLowerCase())
52
+ )
53
+ );
54
+ const pageCount = $derived(Math.max(1, Math.ceil(terminals.length / size)));
55
+ const currentPage = $derived(Math.min(page, pageCount - 1));
56
+ const visible = $derived(terminals.slice(currentPage * size, (currentPage + 1) * size));
57
+ const ports = $derived(result.element_ports.filter((row) => !edgeId || row.element === edgeId));
58
+ let portPage = $state(0);
59
+ const portPages = $derived(Math.max(1, Math.ceil(ports.length / size)));
60
+ const currentPortPage = $derived(Math.min(portPage, portPages - 1));
61
+ const visiblePorts = $derived(ports.slice(currentPortPage * size, (currentPortPage + 1) * size));
62
+ const magnitude = (value: McComplex) => Math.hypot(value.re, value.im);
63
+ const fixed = (value: number) => (Number.isFinite(value) ? value.toFixed(3) : '-');
64
+ const angle = (value: McComplex) =>
65
+ magnitude(value) > 1e-12 ? ((Math.atan2(value.im, value.re) * 180) / Math.PI).toFixed(2) : '-';
66
+ </script>
67
+
68
+ <section aria-label="AC power flow results" class="mc-results">
69
+ <div class="result-heading">
70
+ <strong>AC power flow</strong><span
71
+ >{result.iterations} iterations{elapsedMs === null
72
+ ? ''
73
+ : `, ${Math.round(elapsedMs)} ms`}</span
74
+ >
75
+ </div>
76
+ <dl class="totals">
77
+ <div>
78
+ <dt>Status</dt>
79
+ <dd>{result.converged ? 'Converged' : 'Not converged'}</dd>
80
+ </div>
81
+ <div>
82
+ <dt>Voltage band</dt>
83
+ <dd>{result.voltage_valid ? 'Valid' : `${result.voltage_violations.length} violations`}</dd>
84
+ </div>
85
+ <div>
86
+ <dt>Load voltage range</dt>
87
+ <dd>{result.min_voltage_pu == null || result.max_voltage_pu == null ? 'n/a' : `${fixed(result.min_voltage_pu)}–${fixed(result.max_voltage_pu)} pu`}</dd>
88
+ </div>
89
+ <div>
90
+ <dt>Source P / Q</dt>
91
+ <dd>{fixed(sourcePower.re / 1000)} / {fixed(sourcePower.im / 1000)} kW / kvar</dd>
92
+ </div>
93
+ <div>
94
+ <dt>Passive loss</dt>
95
+ <dd>{fixed(passiveLoss / 1000)} kW</dd>
96
+ </div>
97
+ </dl>
98
+ <label class="search"
99
+ >Bus result<select
100
+ value={busId}
101
+ onchange={(event) => {
102
+ busChoice = event.currentTarget.value;
103
+ selectedBus = null;
104
+ page = 0;
105
+ }}
106
+ >{#each graph?.buses ?? [] as bus (bus.id)}<option value={bus.id}>{bus.id}</option
107
+ >{/each}</select
108
+ ></label
109
+ >
110
+ <label class="search"
111
+ >Terminals<input
112
+ aria-label="Find terminal"
113
+ type="search"
114
+ placeholder="Bus or terminal"
115
+ bind:value={search}
116
+ oninput={() => (page = 0)}
117
+ /></label
118
+ >
119
+ <!-- Scrollable result tables accept keyboard navigation. -->
120
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
121
+ <div class="scroll" role="region" aria-label="Terminal result columns" tabindex="0">
122
+ <table aria-label="Terminal results">
123
+ <thead
124
+ ><tr
125
+ ><th>Terminal</th><th>To ground<br />V</th><th>Angle<br />deg</th><th
126
+ >Net current<br />A</th
127
+ ></tr
128
+ ></thead
129
+ >
130
+ <tbody
131
+ >{#each visible as row (JSON.stringify([row.bus, row.terminal]))}
132
+ <tr
133
+ ><td>{row.terminal}</td><td>{fixed(magnitude(row.voltage))}</td><td
134
+ >{angle(row.voltage)}</td
135
+ ><td>{fixed(magnitude(row.current_into_network))}</td></tr
136
+ >
137
+ {/each}</tbody
138
+ >
139
+ </table>
140
+ </div>
141
+ <div class="pagination">
142
+ <span>{terminals.length} terminals{busId ? ` at bus ${busId}` : ''}</span
143
+ >{#if pageCount > 1}<button
144
+ disabled={currentPage === 0}
145
+ onclick={() => (page = currentPage - 1)}
146
+ aria-label="Previous terminals">Previous</button
147
+ ><span>{currentPage + 1} / {pageCount}</span><button
148
+ disabled={currentPage + 1 >= pageCount}
149
+ onclick={() => (page = currentPage + 1)}
150
+ aria-label="Next terminals">Next</button
151
+ >{/if}
152
+ </div>
153
+ {#if neutral}
154
+ <details>
155
+ <summary>Voltages to neutral</summary>
156
+ <!-- Scrollable result tables accept keyboard navigation. -->
157
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
158
+ <div class="scroll" role="region" aria-label="Neutral voltage columns" tabindex="0">
159
+ <table aria-label="Neutral voltage results">
160
+ <thead><tr><th>Terminal</th><th>To neutral<br />V</th><th>Angle<br />deg</th></tr></thead>
161
+ <tbody
162
+ >{#each visible as row (JSON.stringify([row.bus, row.terminal]))}
163
+ {@const relative = {
164
+ re: row.voltage.re - neutral.re,
165
+ im: row.voltage.im - neutral.im
166
+ }}
167
+ <tr
168
+ ><td>{row.terminal}</td><td>{fixed(magnitude(relative))}</td><td
169
+ >{angle(relative)}</td
170
+ ></tr
171
+ >
172
+ {/each}</tbody
173
+ >
174
+ </table>
175
+ </div>
176
+ </details>
177
+ {/if}
178
+ <details>
179
+ <summary>Equipment currents and power</summary>
180
+ <label class="search"
181
+ >Branch result<select
182
+ value={edgeId}
183
+ onchange={(event) => {
184
+ edgeChoice = event.currentTarget.value;
185
+ selectedEdge = null;
186
+ portPage = 0;
187
+ }}
188
+ ><option value="">All equipment</option>{#each graph?.edges ?? [] as edge (edge.id)}<option
189
+ value={edge.id}>{edge.from} - {edge.to}, {edge.id}</option
190
+ >{/each}</select
191
+ ></label
192
+ >
193
+ <p>Current enters the named equipment at each bus terminal.</p>
194
+ <!-- Scrollable result tables accept keyboard navigation. -->
195
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
196
+ <div class="scroll" role="region" aria-label="Equipment current columns" tabindex="0">
197
+ <table aria-label="Equipment currents">
198
+ <thead
199
+ ><tr
200
+ ><th>Equipment</th><th>Bus</th><th>Terminal</th><th>Current<br />A</th><th>P<br />kW</th
201
+ ><th>Q<br />kvar</th></tr
202
+ ></thead
203
+ ><tbody
204
+ >{#each visiblePorts as row (JSON.stringify( [row.kind, row.element, row.branch, row.bus, row.terminal] ))}<tr
205
+ ><td>{row.kind} {row.element}</td><td>{row.bus}</td><td>{row.terminal}</td><td
206
+ >{fixed(magnitude(row.current_into_element))}</td
207
+ ><td>{fixed(row.power_into_element.re / 1000)}</td><td
208
+ >{fixed(row.power_into_element.im / 1000)}</td
209
+ ></tr
210
+ >{/each}</tbody
211
+ >
212
+ </table>
213
+ </div>
214
+ <div class="pagination">
215
+ <span>{ports.length} connections</span>{#if portPages > 1}<button
216
+ disabled={currentPortPage === 0}
217
+ onclick={() => (portPage = currentPortPage - 1)}
218
+ aria-label="Previous currents">Previous</button
219
+ ><span>{currentPortPage + 1} / {portPages}</span><button
220
+ disabled={currentPortPage + 1 >= portPages}
221
+ onclick={() => (portPage = currentPortPage + 1)}
222
+ aria-label="Next currents">Next</button
223
+ >{/if}
224
+ </div>
225
+ </details>
226
+ <details>
227
+ <summary>Calculation details</summary>
228
+ <dl>
229
+ <div>
230
+ <dt>Maximum KCL residual</dt>
231
+ <dd>{result.physical_kcl_residual.toExponential(3)} A</dd>
232
+ </div>
233
+ <div>
234
+ <dt>Scaled KCL residual</dt>
235
+ <dd>{result.scaled_kcl_residual.toExponential(3)}</dd>
236
+ </div>
237
+ <div>
238
+ <dt>Voltage change</dt>
239
+ <dd>{result.voltage_change.toExponential(3)} V</dd>
240
+ </div>
241
+ <div>
242
+ <dt>Matrix size</dt>
243
+ <dd>{result.matrix_dimension}</dd>
244
+ </div>
245
+ <div>
246
+ <dt>Nonzero entries</dt>
247
+ <dd>{result.matrix_nonzeros}</dd>
248
+ </div>
249
+ <div>
250
+ <dt>Factorizations</dt>
251
+ <dd>{result.factorization_count}</dd>
252
+ </div>
253
+ </dl>
254
+ <p>
255
+ Voltage is measured to ground. Net current is the injection into the network. The calculation
256
+ checks current balance at unconstrained terminals.
257
+ </p>
258
+ </details>
259
+ </section>
260
+
261
+ <style>
262
+ .mc-results {
263
+ margin: 14px 0;
264
+ padding-top: 12px;
265
+ border-top: 1px solid var(--line);
266
+ font-size: 11px;
267
+ }
268
+ .result-heading,
269
+ .pagination,
270
+ dl div {
271
+ display: flex;
272
+ align-items: center;
273
+ justify-content: space-between;
274
+ gap: 8px;
275
+ }
276
+ .result-heading span,
277
+ .pagination,
278
+ dt,
279
+ p {
280
+ color: var(--text-secondary);
281
+ }
282
+ .search {
283
+ display: flex;
284
+ flex-direction: column;
285
+ gap: 5px;
286
+ margin: 12px 0 8px;
287
+ }
288
+ input,
289
+ select,
290
+ button {
291
+ font: inherit;
292
+ color: var(--ink);
293
+ background: var(--paper);
294
+ border: 1px solid var(--line);
295
+ border-radius: 3px;
296
+ padding: 5px 7px;
297
+ }
298
+ button {
299
+ cursor: pointer;
300
+ }
301
+ button:disabled {
302
+ opacity: 0.4;
303
+ cursor: default;
304
+ }
305
+ input,
306
+ select {
307
+ width: 100%;
308
+ box-sizing: border-box;
309
+ }
310
+ .scroll {
311
+ overflow: auto;
312
+ max-width: 100%;
313
+ }
314
+ .scroll:focus-visible {
315
+ outline: 2px solid var(--accent);
316
+ outline-offset: 2px;
317
+ }
318
+ table {
319
+ width: 100%;
320
+ border-collapse: collapse;
321
+ font: 10px var(--font-mono);
322
+ }
323
+ th,
324
+ td {
325
+ padding: 6px 5px;
326
+ text-align: right;
327
+ white-space: nowrap;
328
+ border-bottom: 1px solid var(--line);
329
+ }
330
+ th:first-child,
331
+ td:first-child {
332
+ text-align: left;
333
+ }
334
+ th {
335
+ color: var(--text-secondary);
336
+ font-weight: 500;
337
+ }
338
+ .pagination {
339
+ margin: 8px 0;
340
+ justify-content: flex-end;
341
+ }
342
+ .pagination > span:first-child {
343
+ margin-right: auto;
344
+ }
345
+ details {
346
+ margin-top: 12px;
347
+ }
348
+ summary {
349
+ cursor: pointer;
350
+ font-weight: 500;
351
+ }
352
+ p {
353
+ line-height: 1.5;
354
+ margin: 8px 0;
355
+ }
356
+ dl {
357
+ font: 10px var(--font-mono);
358
+ }
359
+ dl div {
360
+ padding: 4px 0;
361
+ }
362
+ dd {
363
+ margin: 0;
364
+ }
365
+ </style>
@@ -0,0 +1,11 @@
1
+ import type { McPfResult, DistGraph } from '@tellegen/engine';
2
+ type $$ComponentProps = {
3
+ result: McPfResult;
4
+ elapsedMs: number | null;
5
+ selectedBus: string | null;
6
+ selectedEdge: string | null;
7
+ graph: DistGraph | null;
8
+ };
9
+ declare const McResults: import("svelte").Component<$$ComponentProps, {}, "">;
10
+ type McResults = ReturnType<typeof McResults>;
11
+ export default McResults;