@wafertools/wafermap 0.26.1 → 0.27.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 (88) hide show
  1. package/CHANGELOG.md +360 -0
  2. package/README.md +46 -3
  3. package/dist/packages/canvas-adapter/canvasTheme.d.ts +4 -0
  4. package/dist/packages/canvas-adapter/canvasTheme.js +1 -1
  5. package/dist/packages/canvas-adapter/charts/barPanel.d.ts +16 -0
  6. package/dist/packages/canvas-adapter/charts/barPanel.js +1 -1
  7. package/dist/packages/canvas-adapter/charts/binCluster.js +1 -1
  8. package/dist/packages/canvas-adapter/charts/boxplot.d.ts +13 -1
  9. package/dist/packages/canvas-adapter/charts/boxplot.js +1 -1
  10. package/dist/packages/canvas-adapter/charts/capability.d.ts +10 -10
  11. package/dist/packages/canvas-adapter/charts/capability.js +1 -1
  12. package/dist/packages/canvas-adapter/charts/chartShell.d.ts +337 -3
  13. package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
  14. package/dist/packages/canvas-adapter/charts/correlation.d.ts +8 -9
  15. package/dist/packages/canvas-adapter/charts/correlation.js +2 -1
  16. package/dist/packages/canvas-adapter/charts/groupedBarPlot.d.ts +50 -0
  17. package/dist/packages/canvas-adapter/charts/groupedBarPlot.js +1 -0
  18. package/dist/packages/canvas-adapter/charts/histogram.d.ts +12 -1
  19. package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
  20. package/dist/packages/canvas-adapter/charts/regionYieldDiagram.js +1 -1
  21. package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
  22. package/dist/packages/canvas-adapter/charts/testPassRate.d.ts +20 -0
  23. package/dist/packages/canvas-adapter/charts/testPassRate.js +1 -0
  24. package/dist/packages/canvas-adapter/charts/trend.d.ts +30 -0
  25. package/dist/packages/canvas-adapter/charts/trend.js +1 -0
  26. package/dist/packages/canvas-adapter/dieList.js +7 -7
  27. package/dist/packages/canvas-adapter/identityHeader.d.ts +46 -0
  28. package/dist/packages/canvas-adapter/identityHeader.js +1 -0
  29. package/dist/packages/canvas-adapter/index.d.ts +1 -1
  30. package/dist/packages/canvas-adapter/insightsTab.d.ts +27 -3
  31. package/dist/packages/canvas-adapter/insightsTab.js +1 -1
  32. package/dist/packages/canvas-adapter/maplessSummary.js +1 -1
  33. package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +15 -2
  34. package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -20
  35. package/dist/packages/canvas-adapter/renderWaferMap.d.ts +72 -17
  36. package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
  37. package/dist/packages/canvas-adapter/summaryPanel.d.ts +219 -33
  38. package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
  39. package/dist/packages/canvas-adapter/toCanvas.d.ts +6 -0
  40. package/dist/packages/canvas-adapter/toCanvas.js +1 -1
  41. package/dist/packages/canvas-adapter/toolbar.d.ts +310 -6
  42. package/dist/packages/canvas-adapter/toolbar.js +2 -2
  43. package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
  44. package/dist/packages/canvas-adapter/userGuideHtml.js +97 -34
  45. package/dist/packages/canvas-adapter/version.d.ts +2 -2
  46. package/dist/packages/canvas-adapter/version.js +1 -1
  47. package/dist/packages/canvas-adapter/warnings.d.ts +0 -4
  48. package/dist/packages/canvas-adapter/warnings.js +1 -1
  49. package/dist/packages/core/utils.d.ts +11 -0
  50. package/dist/packages/core/utils.js +1 -1
  51. package/dist/packages/renderer/buildView.d.ts +34 -1
  52. package/dist/packages/renderer/buildView.js +1 -1
  53. package/dist/packages/renderer/buildWaferMap.d.ts +104 -5
  54. package/dist/packages/renderer/buildWaferMap.js +1 -1
  55. package/dist/packages/renderer/colorSchemes.js +1 -1
  56. package/dist/packages/stats/analyzeWaferMap.js +1 -1
  57. package/dist/packages/stats/binPareto.d.ts +15 -0
  58. package/dist/packages/stats/binPareto.js +1 -1
  59. package/dist/packages/stats/clusterDetection.js +1 -1
  60. package/dist/packages/stats/connectedComponents.d.ts +14 -0
  61. package/dist/packages/stats/connectedComponents.js +1 -0
  62. package/dist/packages/stats/correlation.d.ts +26 -0
  63. package/dist/packages/stats/correlation.js +1 -1
  64. package/dist/packages/stats/facets.js +1 -1
  65. package/dist/packages/stats/filterFindings.d.ts +18 -0
  66. package/dist/packages/stats/filterFindings.js +1 -1
  67. package/dist/packages/stats/findingsNarrative.js +1 -1
  68. package/dist/packages/stats/histogram.d.ts +19 -1
  69. package/dist/packages/stats/histogram.js +1 -1
  70. package/dist/packages/stats/index.d.ts +6 -0
  71. package/dist/packages/stats/index.js +1 -1
  72. package/dist/packages/stats/mergeTestDefs.d.ts +44 -0
  73. package/dist/packages/stats/mergeTestDefs.js +1 -0
  74. package/dist/packages/stats/metadataColumns.d.ts +20 -4
  75. package/dist/packages/stats/metadataColumns.js +1 -1
  76. package/dist/packages/stats/patternClassification.js +1 -1
  77. package/dist/packages/stats/renderFindingsReport.js +14 -14
  78. package/dist/packages/stats/renderSummaryReport.js +32 -29
  79. package/dist/packages/stats/reportHtml.js +38 -13
  80. package/dist/packages/stats/testPassRate.d.ts +85 -0
  81. package/dist/packages/stats/testPassRate.js +1 -0
  82. package/dist/packages/stats/trend.d.ts +40 -0
  83. package/dist/packages/stats/trend.js +1 -0
  84. package/dist/packages/stats/types.d.ts +117 -6
  85. package/llms.txt +6 -0
  86. package/package.json +7 -5
  87. package/dist/packages/canvas-adapter/metadataBadge.d.ts +0 -30
  88. package/dist/packages/canvas-adapter/metadataBadge.js +0 -1
@@ -5,8 +5,11 @@ import type { StatsFinding, StatsSummary, LotStatsSummary } from '../stats/types
5
5
  import { type FindingsFilter } from '../stats/filterFindings.js';
6
6
  import { buildWarningsBanner, type WaferWarning } from './warnings.js';
7
7
  export { buildWarningsBanner };
8
+ import type { PlotMode } from '../renderer/buildView.js';
9
+ import { type CapabilityItem } from '../stats/capability.js';
8
10
  import { type SaveTextHandler } from './toolbar.js';
9
11
  import { type DieListDisplayOptions } from './dieList.js';
12
+ export { csvField } from '../core/utils.js';
10
13
  import type { WaferMetadata } from '../core/metadata.js';
11
14
  export interface SummaryPanelOptions {
12
15
  /** Which side of the content area to place the panel. Default 'right'. */
@@ -20,19 +23,6 @@ export interface SummaryPanelOptions {
20
23
  * metadata badge and `buildMetadataStripRow`/`buildFacetSummaryChips` so
21
24
  * they can't drift on what they consider "no metadata". */
22
25
  export declare function metadataEntries(meta: Record<string, unknown>): Array<[string, string]>;
23
- /** Single-line, wrap-when-needed "Label: value1, value2 [+N more]" chips —
24
- * the gallery's top-of-grid strip summary over a whole (possibly multi-lot)
25
- * population, one chip per `FacetField` from `stats/facets.ts`'s
26
- * `buildFacetTable`. Shows every distinct value a field takes
27
- * across the population — so a mixed-lot gallery still surfaces "Lot:
28
- * LOT123, LOT456" instead of silently dropping a field the moment it
29
- * varies. `field.values` is already sorted by coverage (`buildFacetTable`),
30
- * so the values shown inline are the most common ones. Labels use
31
- * `prettyKey(field.key)`, not `field.label` — `DEFAULT_FACET_CURATION`'s own
32
- * labels (e.g. "Program") differ from the `prettyKey` convention every other
33
- * metadata surface in this library uses ("Test Program"), and this strip
34
- * must read as the same field as those surfaces, not a differently-named one.
35
- * Returns `null` for an empty table. */
36
26
  export declare function buildFacetSummaryChips(table: Array<{
37
27
  key: string;
38
28
  values: Array<{
@@ -103,30 +93,104 @@ export declare function buildBinSection(dies: Die[], binDefs: BinDef[] | undefin
103
93
  * already scoped to the yield-eligible population) — used directly instead
104
94
  * of re-walking `dies` when supplied.
105
95
  */
106
- precomputedCounts?: Record<number, number>): HTMLDivElement | null;
96
+ precomputedCounts?: Record<number, number>,
97
+ /** Pass bins, pinned to the top of the pareto ordering. See `binRows`. */
98
+ passBins?: number[]): HTMLDivElement | null;
99
+ /**
100
+ * The panels' bin section: identical bars to `buildBinSection`, wrapped in a
101
+ * collapsible shell with a Hard/Soft selector.
102
+ *
103
+ * Which bin type it opens on is derived from the map's own plot mode, not from
104
+ * which bin data happens to exist. The old rule (`hasHbin ? 'hard' : 'soft'`) meant
105
+ * a soft-bin map always sat beside a hard-bin breakdown whenever both bin types
106
+ * were present — the panel silently describing a different population from the one
107
+ * on screen. `maplessSummary.ts` already derived this correctly from `plotMode`;
108
+ * this makes all three call sites agree.
109
+ *
110
+ * The selector only appears when both bin types actually have data — a one-option
111
+ * toggle is noise.
112
+ */
113
+ export declare function buildBinBreakdownSection(params: {
114
+ dies: Die[];
115
+ hbinDefs?: BinDef[];
116
+ sbinDefs?: BinDef[];
117
+ colorScheme?: string;
118
+ hardCounts?: Record<number, number>;
119
+ softCounts?: Record<number, number>;
120
+ /** The map's active plot mode. `hardBin`/`softBin` pick the matching bin type. */
121
+ plotMode?: PlotMode;
122
+ passBins?: number[];
123
+ panel?: HTMLElement;
124
+ }): HTMLDivElement | null;
107
125
  /** Aggregate bin counts across all wafers in the lot — no lot-pooled
108
126
  * precomputed bin counts exist (`hardBinCounts`/`softBinCounts` are
109
127
  * per-wafer only), so this scans the pooled `Die[]` directly, same as
110
128
  * `buildBinSection` does when it has no `precomputedCounts`. */
111
129
  export declare function buildLotBinSection(allDies: Die[], binDefs: BinDef[] | undefined, mode: 'hard' | 'soft', colorScheme?: string): HTMLDivElement | null;
112
- export declare function buildRingSection(dies: Die[], wafer: Wafer, ringCount: number, passBins: number[]): HTMLDivElement | null;
113
- export declare function buildQuadrantSection(dies: Die[], wafer: Wafer, ringCount: number, passBins: number[]): HTMLDivElement | null;
114
- /** Aggregate ring yield across all wafers in the lot. */
115
- export declare function buildLotRingSection(diesByWafer: Die[][], allWafers: Wafer[], ringCount: number, passBins: number[]): HTMLDivElement | null;
116
- /** Aggregate quadrant yield across all wafers in the lot. */
117
- export declare function buildLotQuadrantSection(diesByWafer: Die[][], allWafers: Wafer[], ringCount: number, passBins: number[]): HTMLDivElement | null;
130
+ /**
131
+ * Region yield as ONE section with a Ring/Quadrant selector, replacing the two
132
+ * always-on stacked sections (`Ring Yield` + `Quadrant Yield`) this used to render.
133
+ *
134
+ * Ring is the default and quadrant is opt-in because they are not equally
135
+ * informative. Ring yield tracks the edge roll-off that dominates real wafer maps
136
+ * (85% core → 70% edge is routine, and is what the edge-local findings are about).
137
+ * Quadrant yield averages over half the wafer twice and lands within a point or two
138
+ * of the wafer mean on almost every lot — and in the case where a quadrant really
139
+ * is asymmetric, the findings engine already reports it with a significance test
140
+ * behind it, which four bare bars do not have. Between them they were consuming
141
+ * eight rows of a 260px column to say one thing.
142
+ *
143
+ * Works uniformly for a single wafer (`diesByWafer: [dies]`, `allWafers: [wafer]`)
144
+ * or a whole lot.
145
+ */
146
+ export declare function buildRegionYieldPanelSection(params: {
147
+ diesByWafer: Die[][];
148
+ allWafers: Wafer[];
149
+ ringCount: number;
150
+ passBins: number[];
151
+ panel?: HTMLElement;
152
+ }): HTMLDivElement | null;
118
153
  /** Lot overview — wafer count and mean (unweighted arithmetic mean of each
119
154
  * wafer's own yield%, not a die-weighted lot yield — see CLAUDE.md on
120
155
  * correctly labelling aggregation methods) wafer yield. Metadata is a
121
156
  * separate section (`buildMetadataInfoSection`) built from the lot's own
122
157
  * items, not from `lotSummary.lot` — see that function's doc comment. */
123
- export declare function buildLotOverviewSection(lotSummary: LotStatsSummary): HTMLDivElement;
124
- /** Per-wafer yield bars, absolute 0–100% scale, with a median marker line and
125
- * below-median wafers muted — lets an engineer spot outlier wafers within
126
- * the lot at a glance. */
158
+ export declare function buildLotOverviewSection(lotSummary: LotStatsSummary,
159
+ /** Per-wafer summaries, for the analysed/excluded die tally below. */
160
+ perWaferSummaries?: StatsSummary[]): HTMLDivElement;
161
+ /** Per-wafer yield bars, absolute 0–100% scale, with a visible median marker and
162
+ * low outliers named in the row label — lets an engineer spot outlier wafers
163
+ * within the lot at a glance.
164
+ *
165
+ * "Outlier" is the Tukey rule (below `Q1 − 1.5 × IQR`), not "below the median".
166
+ * The median splits every lot in half by construction, so half the bars always
167
+ * got flagged — including on a lot where every wafer is within a point of every
168
+ * other. The Tukey fence flags nothing on a tight lot, which is the correct
169
+ * answer, and it is stated in text rather than encoded only in a colour.
170
+ *
171
+ * Slot order is the default and must stay so: wafer-number order is what makes a
172
+ * slot-correlated pattern (a bad cassette position, a chuck issue) visible at all,
173
+ * and sorting by yield destroys it. Yield order is opt-in via the header control. */
127
174
  export declare function buildPerWaferYieldSection(lotSummary: LotStatsSummary, items: Array<{
128
175
  label?: string;
129
- } | null>, onWaferClick?: (waferIndex: number) => void): HTMLDivElement | null;
176
+ } | null>, onWaferClick?: (waferIndex: number) => void,
177
+ /** Panel element owning the sort/collapse state. Omit for a stateless render (reports). */
178
+ panel?: HTMLElement,
179
+ /**
180
+ * Per-wafer findings tally, badged onto each row.
181
+ *
182
+ * This is what let the gallery's separate "Findings" tab go away. That tab was
183
+ * a second per-wafer list — a SUBSET (only wafers that have findings), with the
184
+ * same row idiom as this one but a different click action, and no findings text
185
+ * on it at all. Folding the count in here gives one list instead of two, and
186
+ * shows the case the subset list structurally could not: a low-yielding wafer
187
+ * with no findings, sitting next to the flagged ones.
188
+ */
189
+ findingsFor?: (waferIndex: number) => {
190
+ total: number;
191
+ unusual: number;
192
+ notable: number;
193
+ } | undefined): HTMLDivElement | null;
130
194
  /**
131
195
  * Identity context stamped onto a per-test CSV export, so a file that has
132
196
  * left the app still says which wafer(s) it describes. Die-level metadata is
@@ -147,8 +211,6 @@ export interface CsvExportContext {
147
211
  * section title and emitted as a `Population` CSV column. */
148
212
  populationLabel?: string;
149
213
  }
150
- /** CSV field escaper — exported so `dieList.ts` shares it rather than a second copy. */
151
- export declare function csvField(value: string): string;
152
214
  export declare function buildTestSection(dies: Die[], testDefs: TestDef[] | undefined, fallbackFormat?: 'si' | 'engineering',
153
215
  /**
154
216
  * When supplied, each test's descriptive stats and spec-yield are read from
@@ -182,7 +244,27 @@ precomputedTestStats?: {
182
244
  /** Optional host hook for the "Export CSV" button — see `saveTextFile` (toolbar.ts). */
183
245
  onSaveText?: SaveTextHandler,
184
246
  /** Wafer identity to stamp on the CSV export. See {@link CsvExportContext}. */
185
- csv?: CsvExportContext): HTMLDivElement | null;
247
+ csv?: CsvExportContext,
248
+ /**
249
+ * Population for the Ppk column — one item per wafer (see `stats/capability.ts`).
250
+ * Passing `[{ dies }]` for a single wafer is correct. Omit to drop the column.
251
+ */
252
+ capabilityItems?: CapabilityItem[],
253
+ /** Panel element owning the collapsed state. Omit for a stateless render. */
254
+ panel?: HTMLElement,
255
+ /**
256
+ * On-screen column set.
257
+ *
258
+ * `'compact'` (Test / N / Mean / Ppk / Spec yield) exists for the docked Summary
259
+ * panel's 260px column, where the full set meant four visible columns and a
260
+ * horizontal scrollbar nested inside a vertical one. `'full'` adds the
261
+ * descriptive statistics back and is the DEFAULT, because every other consumer
262
+ * — the Insights Overview card above all — has the width to show them, and
263
+ * silently trimming a full-width table to a narrow surface's budget loses real
264
+ * information for no benefit. The CSV export always carries every column
265
+ * regardless.
266
+ */
267
+ columns?: 'compact' | 'full'): HTMLDivElement | null;
186
268
  /**
187
269
  * "Functional Tests" section — one row per functional (`testType: 'F'`) test
188
270
  * with pass/fail counts and pass rate. The functional counterpart of
@@ -199,14 +281,18 @@ export declare function buildFunctionalTestSection(dies: Die[], testDefs: TestDe
199
281
  /** Optional host hook for the "Export CSV" button — see `saveTextFile` (toolbar.ts). */
200
282
  onSaveText?: SaveTextHandler,
201
283
  /** Wafer identity to stamp on the CSV export. See {@link CsvExportContext}. */
202
- csv?: CsvExportContext): HTMLDivElement | null;
284
+ csv?: CsvExportContext,
285
+ /** Panel element owning the collapsed state. Omit for a stateless render. */
286
+ panel?: HTMLElement): HTMLDivElement | null;
203
287
  /**
204
288
  * Lot-pooled "Functional Tests" section: when every wafer's
205
289
  * `StatsSummary.stats.functionalYield` is available, sums pass/fail/total
206
290
  * counts across wafers (exact — counts pool losslessly, unlike quantiles);
207
291
  * otherwise recomputes from the pooled dies.
208
292
  */
209
- export declare function buildLotFunctionalSection(allDies: Die[], testDefs: TestDef[] | undefined, perWaferSummaries?: StatsSummary[], onSaveText?: SaveTextHandler): HTMLDivElement | null;
293
+ export declare function buildLotFunctionalSection(allDies: Die[], testDefs: TestDef[] | undefined, perWaferSummaries?: StatsSummary[], onSaveText?: SaveTextHandler,
294
+ /** Panel element owning the collapsed state. Omit for a stateless render. */
295
+ panel?: HTMLElement): HTMLDivElement | null;
210
296
  export declare function buildFindingsSection(findings: StatsFinding[], statsSummary: StatsSummary | LotStatsSummary, onFindingClick: (finding: StatsFinding, row: HTMLButtonElement) => void, activeFindingId: string | null,
211
297
  /**
212
298
  * When true, skips the collapsible "Findings" header/badge and renders
@@ -215,6 +301,16 @@ export declare function buildFindingsSection(findings: StatsFinding[], statsSumm
215
301
  * Default false (collapsible-section behavior, used when this is one
216
302
  * section among several, e.g. inside the Insights Overview tab's legacy
217
303
  * embedding).
304
+ *
305
+ * NOTE — the `false` path has no in-repo caller today, and it is the only
306
+ * collapsible section in this panel that does NOT persist its collapsed
307
+ * state: `collapsibleSection` needs both a `stateKey` and the owning `panel`
308
+ * to do that, and this function is not given a `panel`. Every section that
309
+ * actually renders (bins, regionYield, waferYield, testValues,
310
+ * functionalTests, findings) does persist, so the panel is consistent as
311
+ * shipped. If you revive this path — or call it from outside the library —
312
+ * thread a `panel` through and give it a `stateKey`, or it will be the one
313
+ * section that forgets whether the reader collapsed it.
218
314
  */
219
315
  standalone?: boolean): HTMLDivElement | null;
220
316
  /**
@@ -223,7 +319,35 @@ standalone?: boolean): HTMLDivElement | null;
223
319
  * true)` in its own collapsible "Findings" header + filter row). Returns
224
320
  * `null` when the source has no findings at all (nothing to filter).
225
321
  */
226
- export declare function buildFindingsSectionWithFilter(source: StatsSummary | LotStatsSummary, onFindingClick: (finding: StatsFinding, row: HTMLButtonElement) => void, activeFindingId: string | null, filter: FindingsFilter, onFilterChange: () => void): HTMLDivElement | null;
322
+ /**
323
+ * A host-supplied row at the top of the Findings section, for stating that a
324
+ * category of finding is NOT present and offering to compute it.
325
+ *
326
+ * This exists because the absence of a finding category is invisible: a reader
327
+ * looking at a Findings list has no way to tell that a whole class of analysis
328
+ * was skipped. Advertising the *control* elsewhere (a menu item) does not fix
329
+ * that — the reader has no reason to go looking. The notice belongs where the
330
+ * gap is.
331
+ *
332
+ * wmap does not decide when to show this. The host knows what it did and did
333
+ * not compute, and what recomputing would cost; it passes a notice or it does
334
+ * not. Keep `detail` honest about that cost — an unpriced "Analyse" button on a
335
+ * lot where the answer takes half a minute is worse than no button.
336
+ */
337
+ export interface FindingsNotice {
338
+ /** What is missing, stated plainly. Not a question, not an exhortation. */
339
+ message: string;
340
+ /** Supporting detail — normally the size of the job and its expected cost. */
341
+ detail?: string;
342
+ /** Action label. Omit (with `onAction`) for a message-only notice. */
343
+ actionLabel?: string;
344
+ onAction?: () => void;
345
+ }
346
+ export declare function buildFindingsSectionWithFilter(source: StatsSummary | LotStatsSummary, onFindingClick: (finding: StatsFinding, row: HTMLButtonElement) => void, activeFindingId: string | null, filter: FindingsFilter, onFilterChange: () => void,
347
+ /** Panel element owning the collapsed state. Omit for a stateless render. */
348
+ panel?: HTMLElement,
349
+ /** See {@link FindingsNotice}. Rendered above the filter row. */
350
+ notice?: FindingsNotice): HTMLDivElement | null;
227
351
  /**
228
352
  * Aggregate test value stats across all wafers in the lot. When every
229
353
  * wafer's `StatsSummary.stats.perTestStats`/`.testSpecYield` is available
@@ -236,8 +360,37 @@ export declare function buildFindingsSectionWithFilter(source: StatsSummary | Lo
236
360
  */
237
361
  export declare function buildLotTestSection(allDies: Die[], testDefs: TestDef[] | undefined, fallbackFormat?: 'si' | 'engineering', perWaferSummaries?: StatsSummary[],
238
362
  /** Optional host hook for the "Export CSV" button — see `saveTextFile` (toolbar.ts). */
239
- onSaveText?: SaveTextHandler): HTMLDivElement | null;
240
- export declare function createSummaryPanelEl(placement: 'right' | 'left' | 'top' | 'bottom', ownerDocument?: Document): HTMLDivElement;
363
+ onSaveText?: SaveTextHandler,
364
+ /**
365
+ * One item per wafer for the Ppk column — the lot is the population Ppk is
366
+ * meant to describe, so every wafer must be passed, not the pooled die array.
367
+ */
368
+ capabilityItems?: CapabilityItem[],
369
+ /** Panel element owning the collapsed state. Omit for a stateless render. */
370
+ panel?: HTMLElement,
371
+ /** See `buildTestSection`'s `columns`. Defaults to the full set. */
372
+ columns?: 'compact' | 'full'): HTMLDivElement | null;
373
+ export declare function createSummaryPanelEl(placement: 'right' | 'left' | 'top' | 'bottom',
374
+ /**
375
+ * Inset from the edge this panel docks against — stated by the caller,
376
+ * because only the caller knows what that edge IS.
377
+ *
378
+ * `renderWaferMap` docks it inside `mapBox` and passes `EDGE_GUTTER`, the
379
+ * same inset its chrome row uses, so the panel lines up with the toolbar
380
+ * above it. It used to pass a smaller inset so the panel would align with a
381
+ * toolbar FLOATING in the map's own corner; that toolbar now sits in a row
382
+ * above the map, and the small inset aligned with nothing.
383
+ * `renderWaferGallery` docks it at the edge of the gallery region itself,
384
+ * where it is a bounded surface against the window and takes `EDGE_GUTTER`
385
+ * like every other card there.
386
+ *
387
+ * Internal (not exported from the package), so this is a positional
388
+ * parameter rather than an options bag. It has NO default on purpose: a
389
+ * default is a guess about context, and guessing wrong is what put a 4px
390
+ * gutter on the gallery's panel where 12 belonged, and 12px inside a map card
391
+ * where 4 belonged.
392
+ */
393
+ edgeInset: string, ownerDocument?: Document): HTMLDivElement;
241
394
  /** Wrap the canvas + panel in a flex container according to placement. */
242
395
  export declare function wrapWithSummaryPanel(content: HTMLElement, panel: HTMLDivElement, placement: 'right' | 'left' | 'top' | 'bottom'): HTMLDivElement;
243
396
  /** Render all wafer-level sections into a panel element. Clears existing content. */
@@ -265,16 +418,38 @@ export declare function renderWaferSummaryContent(panel: HTMLDivElement, params:
265
418
  passBins?: number[];
266
419
  ringCount?: number;
267
420
  colorScheme?: string;
421
+ /**
422
+ * The map's active plot mode. Selects which bin type the bin breakdown opens
423
+ * on, so the panel describes the population actually on screen — see
424
+ * `buildBinBreakdownSection`. Omitting it falls back to whichever bin type
425
+ * has data, which is the old (wrong-for-soft-bin-maps) behaviour.
426
+ */
427
+ plotMode?: PlotMode;
268
428
  fallbackFormat?: 'si' | 'engineering';
269
429
  onFindingClick?: (finding: StatsFinding, row: HTMLButtonElement) => void;
270
430
  activeFindingId?: string | null;
271
431
  findingsFilter?: FindingsFilter;
272
432
  onFindingsFilterChange?: () => void;
433
+ /** See {@link FindingsNotice} — a host row at the top of the Findings section. */
434
+ findingsNotice?: FindingsNotice;
273
435
  onSaveText?: SaveTextHandler;
274
436
  /** Label/order hints for die metadata columns, e.g. `WaferMapResult.metadataFields`. */
275
437
  metadataFields?: MetadataFieldDef[];
276
438
  /** See `RenderOptions.dieList` — gates the "View die list" link below. */
277
439
  dieListOptions?: DieListDisplayOptions;
440
+ /**
441
+ * Suppress the "Wafer Info" metadata section because the caller already
442
+ * renders this metadata elsewhere — `renderWaferMap` passes true whenever its
443
+ * identity header is mounted (`showIdentity`, the default), since that
444
+ * header's expandable panel is built from the very same
445
+ * `metadataEntries`/`buildCompactMetadataRows` helpers.
446
+ *
447
+ * This mirrors a decision the lot panel already makes for itself: it omits
448
+ * the section unconditionally because the gallery's top strip covers it (see
449
+ * `renderLotSummaryContent`). Two copies of the same fields in a 260px column,
450
+ * authoritative in neither place, is the thing being avoided in both cases.
451
+ */
452
+ metadataShownElsewhere?: boolean;
278
453
  }): void;
279
454
  /** Render lot-level content into the panel. Clears existing content. */
280
455
  export declare function renderLotSummaryContent(panel: HTMLDivElement, params: {
@@ -292,16 +467,27 @@ export declare function renderLotSummaryContent(panel: HTMLDivElement, params: {
292
467
  passBins?: number[];
293
468
  ringCount?: number;
294
469
  colorScheme?: string;
470
+ /** The gallery's active plot mode — see the wafer panel's `plotMode`. */
471
+ plotMode?: PlotMode;
295
472
  fallbackFormat?: 'si' | 'engineering';
296
473
  onFindingClick?: (finding: StatsFinding, row: HTMLButtonElement) => void;
297
474
  activeFindingId?: string | null;
298
475
  onWaferClick?: (waferIndex: number) => void;
299
476
  findingsFilter?: FindingsFilter;
300
477
  onFindingsFilterChange?: () => void;
478
+ /** See {@link FindingsNotice} — a host row at the top of the Findings section. */
479
+ findingsNotice?: FindingsNotice;
301
480
  onSaveText?: SaveTextHandler;
302
481
  /** See the wafer panel's `warnings` — collected by the renderer so every surface agrees. */
303
482
  warnings?: WaferWarning[];
304
483
  /** See `RenderOptions.dieList` — gates the "View die list" link below. */
305
484
  dieListOptions?: DieListDisplayOptions;
485
+ /** Per-wafer findings tally badged onto the Wafer Yield rows — see
486
+ * `buildPerWaferYieldSection`. */
487
+ findingsFor?: (waferIndex: number) => {
488
+ total: number;
489
+ unusual: number;
490
+ notable: number;
491
+ } | undefined;
306
492
  }): void;
307
493
  //# sourceMappingURL=summaryPanel.d.ts.map
@@ -1,3 +1,3 @@
1
- import{isParametricTest as bt}from"../renderer/buildWaferMap.js";import{buildRingRegions as J,buildQuadrantRegions as tt,buildRegionYieldData as wt}from"../stats/regions.js";import{computeFunctionalYield as vt}from"../stats/analyzeWaferMap.js";import{renderSummaryReportHtml as yt,renderLotSummaryReportHtml as Ct}from"../stats/renderSummaryReport.js";import{buildFindingsNarrative as St}from"../stats/findingsNarrative.js";import{filterFindings as kt}from"../stats/filterFindings.js";import{buildFacetTable as Bt,prettyKey as et}from"../stats/facets.js";import{commonMetadata as nt}from"../stats/facets.js";import{resolveMetadataColumns as Lt}from"../stats/metadataColumns.js";import{getColorScheme as $t}from"../renderer/colorSchemes.js";import{buildWarningsBanner as Q,collectWarnings as it}from"./warnings.js";export{Q as buildWarningsBanner};import{fmt as ot,fmtAggregationMethod as Dt,plainBinTerms as Y}from"../renderer/fmt.js";import{getUniqueTestNumbers as Rt}from"../renderer/buildView.js";import{quantile as G}from"../stats/math.js";import{makeLabeledSelect as at}from"./charts/chartShell.js";import{CLR as b,sevColor as U,openModal as rt,openReportModal as st,saveTextFile as lt}from"./toolbar.js";import{buildDieListSection as Ft}from"./dieList.js";import{medianOfSorted as Nt}from"../core/utils.js";import{metadataDisplayValue as Tt}from"../core/metadata.js";const Et=b.panelBg,O=`1px solid ${b.menuBorder}`,Mt="12px",I=b.label,H=b.value,dt="10px";function l(t,i,n,e=document){const o=e.createElement(t);return i&&Object.assign(o.style,i),n!==void 0&&(o.textContent=n),o}function A(t){return l("div",{fontSize:dt,fontWeight:"700",letterSpacing:"0.06em",textTransform:"uppercase",color:I,marginBottom:"6px"},t)}function pt(t,i=!0,n){const e=l("div"),o=document.createElement("button");o.type="button",Object.assign(o.style,{display:"flex",alignItems:"center",gap:"4px",width:"100%",background:"none",border:"none",cursor:"pointer",padding:"0 0 6px",textAlign:"left"});const d=l("span",{fontSize:"9px",color:I,transition:"transform 0.15s",transform:i?"rotate(90deg)":"rotate(0deg)",display:"inline-block",lineHeight:"1",marginRight:"1px"},"\u25B6"),f=l("span",{fontSize:dt,fontWeight:"700",letterSpacing:"0.06em",textTransform:"uppercase",color:I,flex:"1"},t);if(o.setAttribute("aria-expanded",i?"true":"false"),o.appendChild(d),o.appendChild(f),n){const a=l("span",{fontSize:"9px",fontWeight:"700",background:b.warnBg,color:b.warnText,borderRadius:"10px",padding:"1px 5px"},n);o.appendChild(a)}const c=l("div");c.style.display=i?"block":"none";let s=i;return o.addEventListener("click",()=>{s=!s,c.style.display=s?"block":"none",d.style.transform=s?"rotate(90deg)":"rotate(0deg)",o.setAttribute("aria-expanded",s?"true":"false")}),e.appendChild(o),e.appendChild(c),{outer:e,content:c}}function ct(){return l("div",{height:"1px",background:b.separator,margin:`${Mt} 0`,flexShrink:"0"})}function X(t,i,n=b.barFill,e,o,d){const f=l("div",{marginBottom:"5px"}),c=l("div",{display:"flex",justifyContent:"space-between",fontSize:"11px",color:H,marginBottom:"2px"}),s=l("span",{},t),a=l("span",{fontWeight:"600"},`${i.toFixed(1)}%`);c.appendChild(s),c.appendChild(a);const p=e!==void 0?e:Math.min(100,Math.max(0,i)),g=d?b.barFillMuted:n,h=l("div",{position:"relative",height:"9px",background:b.bgActive,borderRadius:"4px",overflow:"hidden"}),D=l("div",{height:"100%",width:`${p}%`,background:g,borderRadius:"4px",transition:"width 0.3s ease"});if(h.appendChild(D),o!==void 0){const M=l("div",{position:"absolute",top:"0",bottom:"0",left:`${o}%`,width:"1px",background:b.infoText,opacity:"0.5"});h.appendChild(M)}return f.appendChild(c),f.appendChild(h),f}function j(t,i,n){const e=l("div",{background:b.menuBg,border:O,borderRadius:"6px",padding:"8px 10px",textAlign:"center",flex:"1"}),o=l("div",{fontSize:"20px",fontWeight:"700",color:H,lineHeight:"1.2"},t),d=l("div",{fontSize:"10px",color:I,marginTop:"2px"},i);return e.appendChild(o),e.appendChild(d),n&&e.appendChild(l("div",{fontSize:"9px",color:I,opacity:"0.85"},n)),e}function Wt(t,i){const n=l("div",{display:"flex",justifyContent:"space-between",fontSize:"11px",gap:"8px",marginBottom:"3px"}),e=l("span",{color:I,flexShrink:"0"},t),o=l("span",{color:H,textAlign:"right",fontWeight:"500",wordBreak:"break-all"},i);return n.appendChild(e),n.appendChild(o),n}function It(t,i){const n=l("div",{display:"flex",fontSize:"11px",gap:"6px",marginBottom:"3px"}),e=l("span",{color:I,flexShrink:"0"},`${t}:`),o=l("span",{color:H,fontWeight:"500",wordBreak:"break-all"},i);return n.appendChild(e),n.appendChild(o),n}export function metadataEntries(t){const i=[];for(const[n,e]of Object.entries(t)){const o=Tt(e);o!==void 0&&i.push([n,o])}return i}export function buildFacetSummaryChips(t,i=3){if(!t.length)return null;const n=l("div",{display:"flex",flexWrap:"wrap",alignItems:"baseline",gap:"4px 10px",fontSize:"11px"});for(const e of t){const o=l("span",{whiteSpace:"nowrap"}),d=l("span",{color:I},`${et(e.key)}: `);o.appendChild(d);const f=e.values.slice(0,i),c=e.values.length-f.length;let s=f.map(a=>a.value).join(", ");c>0&&(s+=` +${c} more`),o.appendChild(document.createTextNode(s)),n.appendChild(o)}return n}export function buildMetadataStripRow(t,i,n){const e=Bt(t,n),o=buildFacetSummaryChips(e);if(!o&&!i)return null;const d=l("div",{display:"flex",flexWrap:"wrap",alignItems:"baseline",gap:"4px 14px"});if(i){const f=l("span",{fontWeight:"500",whiteSpace:"nowrap"},i.aggrMethod?`${i.lotSize} wafers stacked \xB7 ${i.aggrMethod}`:`${i.lotSize} wafers stacked`);d.appendChild(f)}return o&&d.appendChild(o),d}const zt={display:"flex",flexDirection:"column",gap:"6px",background:b.menuBg,border:`1px solid ${b.menuBorder}`,borderRadius:"6px",padding:"6px 10px",boxShadow:"0 1px 4px rgba(0,0,0,0.10)",fontSize:"12px",lineHeight:"1",boxSizing:"border-box",width:"100%",minWidth:"0"};export function buildMetadataStripBox(t,i,n){const e=buildMetadataStripRow(t,i,n);if(!e)return null;const o=l("div",zt);return o.appendChild(e),o}export function buildCompactMetadataRows(t){const i=metadataEntries(t);if(!i.length)return null;const n=l("div");for(const[e,o]of i)n.appendChild(It(et(e),o));return n}export function buildMetadataInfoSection(t,i){const n=buildMetadataStripRow(t,i,{facetableOnly:t.length>1});if(!n)return null;const e=l("div");return e.appendChild(A("Wafer Info")),e.appendChild(n),e}export function buildYieldSection(t,i,n=[1]){const e=l("div");e.appendChild(A("Summary"));const o=l("div",{display:"flex",gap:"6px",marginBottom:"8px"});if(o.appendChild(j(String(t.totalDies),"Total dies")),t.partialDies>0&&o.appendChild(j(String(t.partialDies),"Partial")),t.yieldPercent!==null){const d=n.length===1?`bin ${n[0]}`:`bins ${n.join(", ")}`;o.appendChild(j(`${t.yieldPercent.toFixed(1)}%`,"Yield",`pass: ${d}`))}return e.appendChild(o),t.edgeExcludedDies>0&&e.appendChild(Wt("Edge excluded (outer zone)",String(t.edgeExcludedDies))),e}export function buildBinSection(t,i,n,e,o){const d=new Map;if(o)for(const[p,g]of Object.entries(o))d.set(Number(p),g);else for(const p of t){if(p.partial||p.edgeExcluded)continue;const g=n==="hard"?p.hbin:p.sbin;g!=null&&d.set(g,(d.get(g)??0)+1)}if(!d.size)return null;const f=[...d.values()].reduce((p,g)=>p+g,0),c=i?new Map(i.map(p=>[p.bin,p])):null,s=[...d.entries()].sort((p,g)=>p[0]-g[0]),a=l("div");a.appendChild(A(n==="hard"?"Hard Bin Breakdown":"Soft Bin Breakdown"));for(const[p,g]of s){const h=c?.get(p),D=h?.name?`Bin ${p} \xB7 ${h.name}`:`Bin ${p}`,M=g/f*100,B=$t(e),T=(e==="custom"?h?.color:void 0)??B.forBin(p);a.appendChild(X(`${D} (${g})`,M,T))}return a}export function buildLotBinSection(t,i,n,e){return buildBinSection(t,i,n,e)}function q(t,i,n,e,o,d){const f=wt(t,i,n,e,o);if(!f.length)return null;const c=l("div");c.appendChild(A(d));for(const{label:s,n:a,yieldPercent:p}of f)c.appendChild(X(`${s} (N=${a})`,p));return c}export function buildRingSection(t,i,n,e){return q([t],[i],n,e,J,"Ring Yield")}export function buildQuadrantSection(t,i,n,e){return q([t],[i],n,e,tt,"Quadrant Yield")}export function buildLotRingSection(t,i,n,e){return q(t,i,n,e,J,"Ring Yield")}export function buildLotQuadrantSection(t,i,n,e){return q(t,i,n,e,tt,"Quadrant Yield")}export function buildLotOverviewSection(t){const i=l("div");i.appendChild(A("Lot Summary"));const n=l("div",{display:"flex",gap:"6px",marginBottom:"8px"});n.appendChild(j(String(t.stats.waferCount),"Wafers"));const e=t.perWafer.map(o=>o.summary.stats.yieldPercent).filter(o=>o!==null);if(e.length){const o=e.reduce((d,f)=>d+f,0)/e.length;n.appendChild(j(`${o.toFixed(1)}%`,"Mean wafer yield"))}return i.appendChild(n),i}export function buildPerWaferYieldSection(t,i,n){const e=t.perWafer.map(a=>({waferIndex:a.waferIndex,label:(i[a.waferIndex]?.label??`W${a.waferIndex+1}`).replace(/\s*·\s*\d+(\.\d+)?%$/,""),yieldPct:a.summary.stats.yieldPercent})).filter(a=>a.yieldPct!==null);if(!e.length)return null;const o=Math.min(...e.map(a=>a.yieldPct)),d=Math.max(...e.map(a=>a.yieldPct)),f=o===d?"":` (${o.toFixed(1)}\u2013${d.toFixed(1)}%)`,c=Nt([...e.map(a=>a.yieldPct)].sort((a,p)=>a-p)),s=l("div");s.appendChild(A("Wafer Yield"+f));for(const{waferIndex:a,label:p,yieldPct:g}of e){const h=X(p,g,void 0,void 0,c,g<c);n&&(h.style.cursor="pointer",h.style.borderRadius="4px",h.style.padding="2px 3px",h.style.marginLeft="-3px",h.style.marginRight="-3px",h.setAttribute("role","button"),h.setAttribute("aria-label",`${p}, ${g.toFixed(1)}% yield \u2014 view wafer`),h.tabIndex=0,h.addEventListener("mouseenter",()=>{h.style.background=b.bgHover}),h.addEventListener("mouseleave",()=>{h.style.background=""}),h.addEventListener("focus",()=>{h.style.background=b.bgHover}),h.addEventListener("blur",()=>{h.style.background=""}),h.addEventListener("click",()=>n(a)),h.addEventListener("keydown",D=>{(D.key==="Enter"||D.key===" ")&&(D.preventDefault(),n(a))})),s.appendChild(h)}return s}function Ht(t){const i=[...t].sort((o,d)=>o-d),n=t.reduce((o,d)=>o+d,0)/t.length,e=t.reduce((o,d)=>o+(d-n)**2,0)/t.length;return{min:i[0],max:i[i.length-1],mean:n,count:t.length,stddev:Math.sqrt(e),median:G(i,.5),q1:G(i,.25),q3:G(i,.75)}}function ut(t,i){if(!t)return[];const n=t.waferMetadata??(t.perWaferMetadata?nt(t.perWaferMetadata.map(e=>({metadata:e}))):void 0);return!n||!Object.keys(n).length?[]:Lt({waferMetadata:n,waferPlacement:"csv",reservedLabels:i}).columns}const At=/^[=+\-@]/;export function csvField(t){const i=At.test(t)&&Number.isNaN(Number(t))?`'${t}`:t;return/[",\n\r]/.test(i)?`"${i.replace(/"/g,'""')}"`:i}export function buildTestSection(t,i,n,e,o,d){const f=t.filter(r=>!r.partial&&!r.edgeExcluded),c=new Map((e?.perTestStats??[]).map(r=>[r.testNumber,r])),s=new Map((e?.testSpecYield??[]).map(r=>[r.testNumber,r]));let a;if(i?.length?a=i.filter(bt).map(r=>({testNumber:r.testNumber,name:r.name,unit:r.unit,limitLow:r.limitLow,limitHigh:r.limitHigh})):a=Rt(f).map(m=>({testNumber:m,name:`Test ${m}`})),!a.length)return null;const p=a.filter(r=>f.some(m=>{const $=m.testValues?.[r.testNumber];return $!==void 0&&isFinite($)}));if(!p.length)return null;const g=p.some(r=>r.limitLow!==void 0||r.limitHigh!==void 0),h=[];for(const r of p){const m=c.get(r.testNumber),$=m&&m.stddev!==void 0&&m.median!==void 0&&m.q1!==void 0&&m.q3!==void 0;let v;if($){const w=m;v={testNumber:r.testNumber,min:w.min,max:w.max,mean:w.mean,count:w.count,stddev:w.stddev,median:w.median,q1:w.q1,q3:w.q3}}else{const w=f.map(N=>N.testValues?.[r.testNumber]).filter(N=>N!==void 0&&isFinite(N));if(!w.length)continue;v={testNumber:r.testNumber,...Ht(w)}}let x=null,S=0;if(r.limitLow!==void 0||r.limitHigh!==void 0){const w=s.get(r.testNumber);if(w)({yieldPercent:x,totalDies:S}=w);else{const N=f.map(E=>E.testValues?.[r.testNumber]).filter(E=>E!==void 0&&isFinite(E)),W=N.filter(E=>r.limitLow!==void 0&&E<r.limitLow||r.limitHigh!==void 0&&E>r.limitHigh).length;S=N.length,x=N.length>0?(N.length-W)/N.length*100:null}}h.push({entry:r,stats:v,specYieldPct:x,specN:S})}if(!h.length)return null;const D=l("div"),M=l("div",{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:"6px"}),B=A(d?.populationLabel?`Test Values (${h.length}) \u2014 ${d.populationLabel}`:`Test Values (${h.length})`);B.style.marginBottom="0",M.appendChild(B);const T=document.createElement("button");T.type="button",T.textContent="Export CSV",Object.assign(T.style,{background:"none",border:O,borderRadius:"4px",cursor:"pointer",fontSize:"10px",color:b.iconHover,padding:"2px 7px"}),T.addEventListener("click",()=>{const r=["Test","Unit","N","Min","Q1","Median","Mean","Q3","Max","StdDev"];g&&r.push("LSL","USL","Spec Yield %","Spec Yield N");const m=ut(d,r),v=[[...m.map(S=>S.label),...r].map(csvField).join(",")],x=S=>ot(S,void 0,"engineering");for(const{entry:S,stats:w,specYieldPct:N,specN:W}of h){const E=[...m.map(y=>y.constant??""),S.name,S.unit??"",String(w.count),x(w.min),x(w.q1),x(w.median),x(w.mean),x(w.q3),x(w.max),x(w.stddev)];g&&E.push(S.limitLow!==void 0?x(S.limitLow):"",S.limitHigh!==void 0?x(S.limitHigh):"",N!==null?N.toFixed(1):"",S.limitLow!==void 0||S.limitHigh!==void 0?String(W):""),v.push(E.map(csvField).join(","))}lt(v.join(`
2
- `),"test-values.csv","text/csv",o)}),M.appendChild(T),D.appendChild(M);const F=l("table",{width:"100%",borderCollapse:"collapse",fontSize:"11px"}),R=document.createElement("thead"),u=document.createElement("tr"),L=["Test","N","Min","Q1","Median","Mean","Q3","Max","StdDev"];g&&L.push("LSL","USL","Spec yield");for(const r of L){const m=l("th",{textAlign:r==="Test"?"left":"right",fontWeight:"700",fontSize:"10px",letterSpacing:"0.03em",textTransform:"uppercase",color:I,padding:"3px 8px 5px",borderBottom:`1px solid ${b.menuBorder}`,whiteSpace:"nowrap"},r);u.appendChild(m)}R.appendChild(u),F.appendChild(R);const k=document.createElement("tbody");for(const{entry:r,stats:m,specYieldPct:$,specN:v}of h){const x=N=>ot(N,r.unit,n),S=document.createElement("tr"),w=(N,W="right")=>{const E=l("td",{textAlign:W,padding:"4px 8px",borderBottom:`1px solid ${b.menuBorder}`,color:H,whiteSpace:"nowrap"},N);S.appendChild(E)};w(r.name,"left"),w(`${m.count}`),w(x(m.min)),w(x(m.q1)),w(x(m.median)),w(x(m.mean)),w(x(m.q3)),w(x(m.max)),w(x(m.stddev)),g&&(w(r.limitLow!==void 0?x(r.limitLow):"\u2014"),w(r.limitHigh!==void 0?x(r.limitHigh):"\u2014"),w($!==null?`${$.toFixed(1)}% (N=${v})`:"\u2014")),k.appendChild(S)}F.appendChild(k);const C=l("div",{overflowX:"auto"});return C.appendChild(F),D.appendChild(C),D}export function buildFunctionalTestSection(t,i,n,e,o){const d=n??vt(t,i);if(!d?.length)return null;const f=l("div"),c=l("div",{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:"6px"}),s=A(o?.populationLabel?`Functional Tests (${d.length}) \u2014 ${o.populationLabel}`:`Functional Tests (${d.length})`);s.style.marginBottom="0",c.appendChild(s);const a=document.createElement("button");a.type="button",a.textContent="Export CSV",Object.assign(a.style,{background:"none",border:O,borderRadius:"4px",cursor:"pointer",fontSize:"10px",color:b.iconHover,padding:"2px 7px"}),a.addEventListener("click",()=>{const B=["Test","N","Pass","Fail","Pass Rate %"],T=ut(o,B),F=[[...T.map(R=>R.label),...B].map(csvField).join(",")];for(const R of d)F.push([...T.map(u=>u.constant??""),R.label,String(R.totalDies),String(R.passDies),String(R.failDies),R.passRatePercent!==null?R.passRatePercent.toFixed(1):""].map(csvField).join(","));lt(F.join(`
3
- `),"functional-tests.csv","text/csv",e)}),c.appendChild(a),f.appendChild(c);const p=l("table",{width:"100%",borderCollapse:"collapse",fontSize:"11px"}),g=document.createElement("thead"),h=document.createElement("tr");for(const B of["Test","N","Pass","Fail","Pass rate"])h.appendChild(l("th",{textAlign:B==="Test"?"left":"right",fontWeight:"700",fontSize:"10px",letterSpacing:"0.03em",textTransform:"uppercase",color:I,padding:"3px 8px 5px",borderBottom:`1px solid ${b.menuBorder}`,whiteSpace:"nowrap"},B));g.appendChild(h),p.appendChild(g);const D=document.createElement("tbody");for(const B of d){const T=document.createElement("tr"),F=(R,u="right")=>{T.appendChild(l("td",{textAlign:u,padding:"4px 8px",borderBottom:`1px solid ${b.menuBorder}`,color:H,whiteSpace:"nowrap"},R))};F(B.label,"left"),F(String(B.totalDies)),F(String(B.passDies)),F(String(B.failDies)),F(B.passRatePercent!==null?`${B.passRatePercent.toFixed(1)}% (N=${B.totalDies})`:"\u2014"),D.appendChild(T)}p.appendChild(D);const M=l("div",{overflowX:"auto"});return M.appendChild(p),f.appendChild(M),f}export function buildLotFunctionalSection(t,i,n,e){const o=n?.length?{perWaferMetadata:n.map(d=>d.wafer??{}),populationLabel:`${n.length} wafer${n.length===1?"":"s"} pooled`}:void 0;if(n?.length&&n.every(d=>d.stats.functionalYield!==void 0)){const d=new Map;for(const c of n)for(const s of c.stats.functionalYield??[]){const a=d.get(s.testNumber)??{label:s.label,passDies:0,failDies:0,totalDies:0};a.passDies+=s.passDies,a.failDies+=s.failDies,a.totalDies+=s.totalDies,d.set(s.testNumber,a)}const f=[...d.entries()].map(([c,s])=>({testNumber:c,...s,passRatePercent:s.totalDies>0?s.passDies/s.totalDies*100:null}));if(f.length)return buildFunctionalTestSection(t,i,f,e,o)}return buildFunctionalTestSection(t,i,void 0,e,o)}const P={unusual:0,notable:1,info:2};function K(t){return l("span",{display:"inline-block",width:"7px",height:"7px",borderRadius:"50%",background:U(t),flexShrink:"0"})}function ft(t,i){let n=t.summary;return i&&(n.startsWith(`${i} has `)?n=n.slice(i.length+5):n.startsWith(`${i} `)&&(n=n.slice(i.length+1))),n=Y(n),n.length?n[0].toUpperCase()+n.slice(1):n}export function buildFindingsSection(t,i,n,e,o=!1){if(!t.length)return null;const d=t.some(u=>u.severity==="unusual"||u.severity==="notable"),f=d?t.filter(u=>u.severity!=="info").length.toString():void 0,{outer:c,content:s}=o?(()=>{const u=l("div");return{outer:u,content:u}})():pt(`Findings (${t.length})`,d,f);function a(u){return u.reduce((L,k)=>P[k.severity]<P[L]?k.severity:L,"info")}function p(u){const L=new Map;for(const C of u){const r=`${C.comparison.family}\0${C.comparison.left}`,m=L.get(r)??[];m.push(C),L.set(r,m)}const k=[...L.entries()].map(([C,r])=>{const m=[...r].sort(($,v)=>P[$.severity]-P[v.severity]);return{key:C,findings:m,worst:a(m)}});return k.sort((C,r)=>P[C.worst]-P[r.worst]),k}function g(u,L){const C={ring:"Ring",quadrant:"Quadrant",sector:"Sector","reticle-position":"Reticle","test-site":"Test site",cluster:"Cluster","edge-arc":"Edge arc",wafer:"Wafer"}[u]??u;return L.toLowerCase().startsWith(C.toLowerCase())?L:`${C}: ${L}`}const h=t.filter(u=>u.comparison.family==="spatial-pattern"),D=new Set([...h.flatMap(u=>u.relatedIds??[]),...t.flatMap(u=>u.absorbedIds??[])]),M=t.filter(u=>u.comparison.family!=="spatial-pattern"&&!D.has(u.id)),B=p(M);function T(u,L=!1,k){const C=e===u.id,r=document.createElement("button");return r.type="button",r.dataset.wmapFinding=u.id,r.textContent=ft(u,k),r.title=u.summary,r.setAttribute("aria-current",C?"true":"false"),Object.assign(r.style,{border:`1px solid ${b.menuBorder}`,borderLeft:`3px solid ${U(u.severity)}`,background:C?b.bgActive:b.menuBg,borderRadius:"6px",padding:L?"6px 10px":"8px 10px",textAlign:"left",fontSize:L?"10px":"11px",fontWeight:C?"600":"400",color:b.iconHover,cursor:"pointer",width:"100%",marginBottom:"4px"}),r.addEventListener("click",()=>n(u,r)),r}const F=Y(St(t)??"");if(F){const u=l("div",{background:b.bgActive,borderRadius:"6px",padding:"8px 10px",marginBottom:"8px",display:"flex",gap:"8px",alignItems:"flex-start"}),L=l("span",{flex:"1",fontSize:"12px",lineHeight:"1.5",color:b.iconHover},F);u.appendChild(L);const k=l("button",{flexShrink:"0",border:"none",background:"none",fontSize:"10px",color:b.icon,cursor:"pointer",padding:"0",lineHeight:"1.5",whiteSpace:"nowrap"},"Detail \u25B8");k.type="button",k.addEventListener("click",()=>{const C=rt({title:"Findings Summary",onClose:()=>{},anchor:k}),r=l("p",{fontSize:"16px",lineHeight:"1.6",color:b.iconHover,padding:"20px 24px 16px",margin:"0",borderBottom:`1px solid ${b.menuBorder}`,flexShrink:"0"},F);C.contentWrap.appendChild(r);const m=l("div",{overflowY:"auto",padding:"16px 24px",flex:"1"}),$=(v,x)=>{const S=l("div",{display:"flex",alignItems:"center",gap:"7px",fontSize:"13px",fontWeight:"600",color:H,marginTop:"10px",marginBottom:"4px"});return S.appendChild(K(v)),S.appendChild(l("span",{},x)),S};for(const v of h){m.appendChild($(v.severity,v.comparison.left)),m.appendChild(l("div",{fontSize:"13px",color:b.iconHover,padding:"3px 0 3px 15px",marginBottom:"2px"},Y(v.summary)));const x=t.filter(S=>v.relatedIds?.includes(S.id));for(const S of x)m.appendChild(l("div",{fontSize:"12px",color:b.icon,padding:"2px 0 2px 23px",marginBottom:"2px"},Y(S.summary)))}for(const v of B){const[x,S]=v.key.split("\0");m.appendChild($(v.worst,g(x,S)));for(const w of v.findings)m.appendChild(l("div",{fontSize:"13px",color:b.iconHover,padding:"3px 0 3px 15px",marginBottom:"2px"},ft(w,S)))}C.contentWrap.appendChild(m),Object.assign(C.contentWrap.style,{flexDirection:"column",overflow:"hidden"})}),u.appendChild(k),s.appendChild(u)}let R=!0;for(const u of h){R||s.appendChild(l("div",{height:"1px",background:b.separator,margin:"4px 0"})),R=!1;const L=t.filter($=>u.relatedIds?.includes($.id)),k=L.length>0,C=l("div",{position:"relative",marginBottom:k?"2px":"4px"}),r=e===u.id,m=document.createElement("button");if(m.type="button",m.dataset.wmapFinding=u.id,Object.assign(m.style,{border:`1px solid ${b.menuBorder}`,borderLeft:`3px solid ${U(u.severity)}`,background:r?b.bgActive:b.menuBg,borderRadius:"6px",padding:"8px 32px 8px 10px",textAlign:"left",fontSize:"11px",fontWeight:r?"600":"500",color:b.iconHover,cursor:"pointer",width:"100%"}),m.textContent=Y(u.summary),m.title=u.summary,m.setAttribute("aria-current",r?"true":"false"),m.addEventListener("click",()=>n(u,m)),C.appendChild(m),k){const $=l("div",{display:"none",paddingLeft:"12px",marginBottom:"4px"});for(const S of L)$.appendChild(T(S,!0));C.appendChild($);let v=!1;const x=l("button",{position:"absolute",top:"50%",right:"8px",transform:"translateY(-50%)",border:"none",background:"none",fontSize:"10px",color:b.icon,cursor:"pointer",padding:"2px 4px",lineHeight:"1"},"\u25B8");x.type="button",x.title="Show supporting findings",x.setAttribute("aria-label","Show supporting findings"),x.setAttribute("aria-expanded","false"),x.addEventListener("click",S=>{S.stopPropagation(),v=!v,$.style.display=v?"block":"none",x.textContent=v?"\u25BE":"\u25B8";const w=v?"Hide supporting findings":"Show supporting findings";x.title=w,x.setAttribute("aria-label",w),x.setAttribute("aria-expanded",String(v))}),C.appendChild(x)}s.appendChild(C)}for(const u of B){R||s.appendChild(l("div",{height:"1px",background:b.separator,margin:"4px 0"})),R=!1;const[L,k]=u.key.split("\0"),C=l("div",{display:"flex",alignItems:"center",gap:"6px",fontSize:"11px",fontWeight:"600",color:H,margin:"2px 0 4px"});C.appendChild(K(u.worst)),C.appendChild(l("span",{},g(L,k))),s.appendChild(C);for(const r of u.findings)s.appendChild(T(r,!1,k))}return c}const Pt=[{value:"",label:"All kinds"},{value:"yield",label:"Yield"},{value:"hardBin",label:"Hard bin"},{value:"softBin",label:"Soft bin"},{value:"test",label:"Test value"},{value:"functionalTest",label:"Functional test"},{value:"spatialPattern",label:"Spatial pattern"}],Yt=[{value:"",label:"All regions"},{value:"ring",label:"Ring"},{value:"quadrant",label:"Quadrant"},{value:"reticle-position",label:"Reticle position"},{value:"test-site",label:"Test site"},{value:"wafer",label:"Wafer"},{value:"sector",label:"Sector"},{value:"cluster",label:"Cluster"},{value:"edge-arc",label:"Edge arc"},{value:"spatial-pattern",label:"Spatial pattern"}],Ot=["unusual","notable","info"],jt={unusual:"Unusual",notable:"Notable",info:"Info"};function qt(t,i,n){const e=l("div",{display:"flex",flexWrap:"wrap",alignItems:"center",gap:"10px",marginBottom:"10px",paddingBottom:"10px",borderBottom:`1px solid ${b.separator}`}),o={unusual:0,notable:0,info:0};for(const s of t)o[s.severity]++;const d=Ot.filter(s=>o[s]>0),f=new Set(i.severity===void 0?d:Array.isArray(i.severity)?i.severity:[i.severity]),c=l("div",{display:"flex",gap:"4px",flexWrap:"wrap"});for(const s of d){const a=document.createElement("button");a.type="button";const p=()=>{const g=f.has(s);a.setAttribute("aria-pressed",g?"true":"false"),Object.assign(a.style,{display:"inline-flex",alignItems:"center",gap:"5px",border:`1px solid ${b.menuBorder}`,borderRadius:"10px",background:g?b.bgActive:"none",opacity:g?"1":"0.5",color:b.text,fontSize:"11px",padding:"2px 8px",cursor:"pointer"})};a.appendChild(K(s)),a.appendChild(l("span",{},`${jt[s]} ${o[s]}`)),a.title=f.has(s)?"Click to hide these findings":"Click to show these findings",p(),a.addEventListener("click",()=>{f.has(s)?f.delete(s):f.add(s),i.severity=f.size===d.length?void 0:[...f],n()}),c.appendChild(a)}return e.appendChild(c),e.appendChild(at("Kind:",Pt,i.kind??"",s=>{i.kind=s||void 0,n()},{maxWidth:"130px"})),e.appendChild(at("Region:",Yt,i.family??"",s=>{i.family=s||void 0,n()},{maxWidth:"150px"})),e}export function buildFindingsSectionWithFilter(t,i,n,e,o){if(!t.findings.length)return null;const d=t.findings.some(g=>g.severity==="unusual"||g.severity==="notable"),f=d?t.findings.filter(g=>g.severity!=="info").length.toString():void 0,{outer:c,content:s}=pt(`Findings (${t.findings.length})`,d,f);s.appendChild(qt(t.findings,e,o));const a=kt(t,e);if(!a.length)return s.appendChild(l("div",{color:I,fontSize:"11px",textAlign:"center",padding:"16px 8px"},"No findings match the current filter.")),c;const p=buildFindingsSection(a,t,i,n,!0);return p&&s.appendChild(p),c}export function buildLotTestSection(t,i,n,e,o){const d=e?.length?{perWaferMetadata:e.map(c=>c.wafer??{}),populationLabel:`${e.length} wafer${e.length===1?"":"s"} pooled`}:void 0;let f;if(e?.length&&e.every(c=>c.stats.perTestStats!==void 0)){const c=new Map;for(const s of e)for(const a of s.stats.perTestStats??[]){const p=c.get(a.testNumber);p?(p.n+=a.count,p.sum+=a.mean*a.count,p.min=Math.min(p.min,a.min),p.max=Math.max(p.max,a.max)):c.set(a.testNumber,{n:a.count,sum:a.mean*a.count,min:a.min,max:a.max})}f={perTestStats:[...c.entries()].map(([s,a])=>({testNumber:s,count:a.n,min:a.min,max:a.max,mean:a.sum/a.n}))}}if(e?.length&&e.every(c=>c.stats.testSpecYield!==void 0)){const c=new Map;for(const s of e)for(const a of s.stats.testSpecYield??[]){const p=c.get(a.testNumber);p?(p.passDies+=a.passDies,p.totalDies+=a.totalDies):c.set(a.testNumber,{passDies:a.passDies,totalDies:a.totalDies})}f={...f,testSpecYield:[...c.entries()].map(([s,a])=>({testNumber:s,totalDies:a.totalDies,yieldPercent:a.totalDies>0?a.passDies/a.totalDies*100:null}))}}return buildTestSection(t,i,n,f,o,d)}export function createSummaryPanelEl(t,i=document){const n=t==="top"||t==="bottom",e=l("div",{background:Et,border:O,borderRadius:"6px",padding:"12px",overflowY:n?"hidden":"auto",overflowX:n?"auto":"hidden",flexShrink:"0",boxSizing:"border-box",fontFamily:"system-ui, sans-serif",fontSize:"12px",boxShadow:"0 1px 4px rgba(0,0,0,0.08)"},void 0,i);return n?(e.style.height="180px",e.style.width="100%"):(e.style.width="260px",e.style.maxHeight="100%"),e}export function wrapWithSummaryPanel(t,i,n){const e=n==="top"||n==="bottom",o=l("div",{display:"flex",flexDirection:e?"column":"row",gap:"8px",width:"100%",height:"100%",flex:"1 1 0",minHeight:"0",boxSizing:"border-box",alignItems:e?"flex-start":"stretch"});return t.style.flex="1 1 0",t.style.minWidth="0",t.style.minHeight="0",n==="right"||n==="bottom"?(o.appendChild(t),o.appendChild(i)):(o.appendChild(i),o.appendChild(t)),o}function mt(t){return l("div",{fontSize:"13px",fontWeight:"700",color:H,marginBottom:"10px"},t)}function V(t,i){const n=l("button",{background:"none",border:O,borderRadius:"4px",cursor:"pointer",fontSize:"10px",color:b.iconHover,padding:"2px 7px"},t);return n.type="button",n.addEventListener("click",i),n}function ht(...t){const i=t.filter(e=>!!e);if(!i.length)return null;const n=l("div",{display:"flex",flexWrap:"wrap",gap:"6px",marginBottom:"10px"});for(const e of i)n.appendChild(e);return n}function gt(t,i,n,e,o,d,f,c,s,a,p){const g=t.ownerDocument,h=rt({title:"Die list",onClose:()=>{},anchor:t,ownerDocument:g});h.contentWrap.style.padding="14px 16px";const D=Ft(i,n,{...f,title:e,onSaveText:c,waferMetadata:o,metadataFields:d,extraColumn:s,getWafer:a,ringCount:p,ownerDocument:g});D&&h.contentWrap.appendChild(D)}export function renderWaferSummaryContent(t,i){const n=t.scrollTop;t.innerHTML="";const{wafer:e,dies:o,yieldSummary:d,dataCoverage:f,hbinDefs:c,sbinDefs:s,testDefs:a,statsSummary:p,passBins:g=[1],ringCount:h=4,colorScheme:D,fallbackFormat:M,onFindingClick:B,activeFindingId:T=null,findingsFilter:F,onFindingsFilterChange:R,onSaveText:u,metadataFields:L,dieListOptions:k}=i;t.appendChild(mt("Wafer Summary"));const C=i.warnings??it({statsSummary:p});C.length&&t.appendChild(Q(C,t.ownerDocument));const r=d&&f?V("Summary report",()=>{st(yt({wafer:e,dies:o,yieldSummary:d,dataCoverage:f,hbinDefs:c,sbinDefs:s,testDefs:a,statsSummary:p,passBins:g,ringCount:h}),{anchor:t})}):null,m=(k?.enabled??!0)&&o.length?V("View die list",()=>{gt(t,o,a,`Die list \u2014 ${o.length} dies`,e.metadata,L,k,u,void 0,()=>e,h)}):null,$=ht(r,m);$&&t.appendChild($);const v=[],x=p?.stats.isLotStack?p.stats:void 0,S=x?.lotSize!==void 0?{lotSize:x.lotSize,aggrMethod:Dt(x.aggregationMethod)}:void 0;v.push(buildMetadataInfoSection([{metadata:e.metadata??void 0}],S)),d&&f&&v.push(buildYieldSection(d,f,g));const w=o.some(y=>y.hbin!=null),N=o.some(y=>y.sbin!=null);w?v.push(buildBinSection(o,c,"hard",D,p?.stats.hardBinCounts)):N&&v.push(buildBinSection(o,s,"soft",D,p?.stats.softBinCounts)),v.push(buildRingSection(o,e,h,g)),v.push(buildQuadrantSection(o,e,h,g));const W=e.metadata?{waferMetadata:e.metadata}:void 0;v.push(buildTestSection(o,a,M,p?.stats,u,W)),v.push(buildFunctionalTestSection(o,a,p?.stats.functionalYield,u,W)),p&&B&&F&&R&&v.push(buildFindingsSectionWithFilter(p,B,T,F,R));let E=!0;for(const y of v)y&&(E||t.appendChild(ct()),E=!1,t.appendChild(y));t.appendChild(l("div",{height:"12px",flexShrink:"0"})),t.scrollTop=n}export function renderLotSummaryContent(t,i){const n=t.scrollTop;t.innerHTML="";const{lotSummary:e,items:o,hbinDefs:d,sbinDefs:f,testDefs:c,passBins:s=[1],ringCount:a=4,colorScheme:p,fallbackFormat:g,onFindingClick:h,activeFindingId:D=null,onWaferClick:M,findingsFilter:B,onFindingsFilterChange:T,onSaveText:F,dieListOptions:R}=i;t.appendChild(mt(`Lot Summary \u2014 ${e.stats.waferCount} wafer${e.stats.waferCount===1?"":"s"}`));const u=i.warnings??it({lotStatsSummary:e});u.length&&t.appendChild(Q(u,t.ownerDocument));const L=V("Summary report",()=>{st(Ct({items:o.map((y,z)=>({label:y?.label??`W${z+1}`,wafer:y?.wafer,dies:y?.dies,statsSummary:y?.statsSummary})),hbinDefs:d,sbinDefs:f,testDefs:c,passBins:s,ringCount:a}),{anchor:t})}),k=[],C=[],r=[],m=new WeakMap,$=new WeakMap;for(let y=0;y<o.length;y++){const z=o[y];if(!z){C.push([]);continue}z.wafer&&k.push(z.wafer);const _=z.dies??[],xt=z.label??`W${y+1}`;for(const Z of _)m.set(Z,xt),z.wafer&&$.set(Z,z.wafer);C.push(_),r.push(..._)}const v=R?.enabled??!0?V("View die list",()=>{r.length&&gt(t,r,c,`Die list \u2014 ${r.length} dies across ${o.length} wafer${o.length===1?"":"s"}`,nt(o.filter(y=>!!y).map(y=>({metadata:y.wafer?.metadata}))),o.find(y=>y?.metadataFields?.length)?.metadataFields,R,F,{label:"Wafer",get:y=>m.get(y)},y=>$.get(y),a)}):null,x=ht(L,v);x&&t.appendChild(x);const S=r.some(y=>y.hbin!=null),w=r.some(y=>y.sbin!=null),N=o.map(y=>y?.statsSummary).filter(y=>!!y),W=[buildLotOverviewSection(e),buildPerWaferYieldSection(e,o,M),S?buildLotBinSection(r,d,"hard",p):w?buildLotBinSection(r,f,"soft",p):null,buildLotRingSection(C,k,a,s),buildLotQuadrantSection(C,k,a,s),c?.length?buildLotTestSection(r,c,g,N,F):null,c?.length?buildLotFunctionalSection(r,c,N,F):null];h&&B&&T&&W.push(buildFindingsSectionWithFilter(e,h,D,B,T));let E=!0;for(const y of W)y&&(E||t.appendChild(ct()),E=!1,t.appendChild(y));t.appendChild(l("div",{height:"12px",flexShrink:"0"})),t.scrollTop=n}
1
+ import{isParametricTest as _t}from"../renderer/buildWaferMap.js";import{buildRingRegions as jt,buildQuadrantRegions as qt,buildRegionYieldData as Vt}from"../stats/regions.js";import{computeFunctionalYield as Kt}from"../stats/analyzeWaferMap.js";import{renderSummaryReportHtml as Gt,renderLotSummaryReportHtml as Ut}from"../stats/renderSummaryReport.js";import{buildFindingsNarrative as Qt}from"../stats/findingsNarrative.js";import{filterFindings as Xt}from"../stats/filterFindings.js";import{buildFacetTable as Zt,prettyKey as xt}from"../stats/facets.js";import{commonMetadata as yt}from"../stats/facets.js";import{resolveMetadataColumns as Jt}from"../stats/metadataColumns.js";import{getColorScheme as te}from"../renderer/colorSchemes.js";import{buildWarningsBanner as mt,collectWarnings as Ct}from"./warnings.js";export{mt as buildWarningsBanner};import{fmt as St,fmtAggregationMethod as ee,plainBinTerms as nt}from"../renderer/fmt.js";import{getUniqueTestNumbers as ne}from"../renderer/buildView.js";import{quantile as it}from"../stats/math.js";import{buildCapabilityData as ie}from"../stats/capability.js";import{sortBinsForDisplay as oe}from"../stats/binPareto.js";import{poolFunctionalYield as ae}from"../stats/testPassRate.js";import{makeLabeledSelect as wt,makeSegmented as ht}from"./charts/chartShell.js";import{SHADOW as vt,MOTION as $t,LEADING as Q,TRACKING as rt,wireControlHover as O,controlStyle as lt,SPACE as y,RADIUS as _,FONT as E,CLR as B,sevColor as dt,openModal as kt,openReportModal as Lt,saveTextFile as Bt,wireTooltip as ot}from"./toolbar.js";import{buildDieListSection as se}from"./dieList.js";export{csvField}from"../core/utils.js";import{medianOfSorted as re,csvField as ct}from"../core/utils.js";import{metadataDisplayValue as le}from"../core/metadata.js";const de=B.panelBg,Nt=`1px solid ${B.menuBorder}`,ce="12px",Y=B.label,U=B.value,Et="10px";function s(t,n,i,e=document){const o=e.createElement(t);return n&&Object.assign(o.style,n),i!==void 0&&(o.textContent=i),o}function J(t){return s("div",{fontSize:Et,fontWeight:"700",letterSpacing:rt,textTransform:"uppercase",color:Y,marginBottom:y.sm},t)}const Ft=new WeakMap,Rt=["regionYield","testValues","functionalTests"];function pt(t){if(!t)return{collapsed:new Set(Rt),regionMode:"ring",waferSort:"slot"};let n=Ft.get(t);return n||(n={collapsed:new Set(Rt),regionMode:"ring",waferSort:"slot"},Ft.set(t,n)),n}function X(t,n=!0,i,e){const o=s("div"),a=pt(e?.panel);e?.stateKey&&e.panel&&(n=!a.collapsed.has(e.stateKey));const r=document.createElement("button");r.type="button",Object.assign(r.style,{display:"flex",alignItems:"center",gap:y.xs,width:"100%",background:"none",border:"none",cursor:"pointer",padding:"0 0 6px",textAlign:"left"});const f=s("span",{fontSize:E.meta,color:Y,transition:`transform ${$t.base}`,transform:n?"rotate(90deg)":"rotate(0deg)",display:"inline-block",lineHeight:Q.none,marginRight:"1px"},"\u25B6"),c=s("span",{fontSize:Et,fontWeight:"700",letterSpacing:rt,textTransform:"uppercase",color:Y,flex:"1"},t);if(O(r,"bare"),r.setAttribute("aria-expanded",n?"true":"false"),r.appendChild(f),r.appendChild(c),i){const b=s("span",{fontSize:E.meta,fontWeight:"700",background:B.warnBg,color:B.warnText,borderRadius:_.pill,padding:"1px 5px"},i);r.appendChild(b)}const l=s("div");l.style.display=n?"block":"none";let d=n;if(r.addEventListener("click",()=>{d=!d,l.style.display=d?"block":"none",f.style.transform=d?"rotate(90deg)":"rotate(0deg)",r.setAttribute("aria-expanded",d?"true":"false"),e?.stateKey&&e.panel&&(d?a.collapsed.delete(e.stateKey):a.collapsed.add(e.stateKey))}),e?.control){const b=s("div",{display:"flex",alignItems:"center",gap:y.sm});r.style.width="auto",r.style.flex="1",b.appendChild(r);const p=()=>{l.innerHTML="",e.render?.(l)},v=e.control(p);v&&(v.style.marginBottom="6px",v.addEventListener("click",N=>N.stopPropagation()),b.appendChild(v)),o.appendChild(b)}else o.appendChild(r);return o.appendChild(l),e?.render?.(l),{outer:o,content:l}}function Tt(){return s("div",{height:"1px",background:B.separator,margin:`${ce} 0`,flexShrink:"0"})}function gt(t,n,i=B.barFill,e,o){const a=s("div",{marginBottom:"5px"}),r=s("div",{display:"flex",justifyContent:"space-between",fontSize:E.body,color:U,marginBottom:y.xxs}),f=s("span",{color:Y},t),c=s("span",{fontWeight:"600"},`${n.toFixed(1)}%`);r.appendChild(f),r.appendChild(c);const l=e!==void 0?e:Math.min(100,Math.max(0,n)),d=i,b=s("div",{position:"relative",height:"9px",background:B.bgActive,borderRadius:_.control,overflow:"hidden"}),p=s("div",{height:"100%",width:`${l}%`,background:d,borderRadius:_.control,transition:`width ${$t.base}`});if(b.appendChild(p),o!==void 0){const v=s("div",{position:"absolute",top:"0",bottom:"0",left:`${o}%`,width:"2px",marginLeft:"-1px",background:B.value,opacity:"0.8"});b.appendChild(v)}return a.appendChild(r),a.appendChild(b),a}function Dt(){const t=s("div",{display:"flex",gap:y.sm,marginBottom:y.md});return t.finish=()=>{const n=t.lastElementChild;n&&Object.assign(n.style,{borderRight:"none",paddingRight:"0"})},t}function at(t,n,i){const e=s("div",{padding:`${y.xxs} ${y.md} ${y.xxs} 0`,borderRight:Nt,textAlign:"center",flex:"1"}),o=s("div",{fontSize:E.heading,fontWeight:"700",color:U,lineHeight:Q.tight},t),a=s("div",{fontSize:E.body,color:Y,marginTop:y.xxs},n);return e.appendChild(o),e.appendChild(a),i&&e.appendChild(s("div",{fontSize:E.body,color:Y,opacity:"0.85"},i)),e}function Mt(t,n){const i=s("div",{display:"flex",justifyContent:"space-between",fontSize:E.body,gap:y.md,marginBottom:"3px"}),e=s("span",{color:Y,flexShrink:"0"},t),o=s("span",{color:U,textAlign:"right",fontWeight:"500",wordBreak:"break-all"},n);return i.appendChild(e),i.appendChild(o),i}function pe(t,n){const i=s("div",{display:"flex",fontSize:E.body,gap:y.sm,marginBottom:"3px"}),e=s("span",{color:Y,flexShrink:"0"},`${t}:`),o=s("span",{color:U,fontWeight:"500",wordBreak:"break-all"},n);return i.appendChild(e),i.appendChild(o),i}export function metadataEntries(t){const n=[];for(const[i,e]of Object.entries(t)){const o=le(e);o!==void 0&&n.push([i,o])}return n}const ue=["lot","product","testprogram","split"],ut=t=>{const n=ue.indexOf(t.toLowerCase().replace(/[^a-z]/g,""));return n===-1?Number.MAX_SAFE_INTEGER:n};export function buildFacetSummaryChips(t,n=3){if(!t.length)return null;const i=s("div",{display:"flex",flexWrap:"wrap",alignItems:"baseline",gap:`${y.xs} ${y.lg}`}),e=[...t].sort((f,c)=>ut(f.key)-ut(c.key)),o=e.filter(f=>ut(f.key)!==Number.MAX_SAFE_INTEGER),a=e.filter(f=>ut(f.key)===Number.MAX_SAFE_INTEGER),r=f=>{const c=s("span",{whiteSpace:"nowrap"}),l=s("span",{color:Y},`${xt(f.key)}: `);c.appendChild(l);const d=f.values.slice(0,n),b=f.values.length-d.length;let p=d.map(v=>v.value).join(", ");return b>0&&(p+=` +${b} more`),c.appendChild(document.createTextNode(p)),c};for(const f of o)i.appendChild(r(f));if(a.length){const f=s("span",{display:"none",flexWrap:"wrap",alignItems:"baseline",gap:`${y.xs} ${y.lg}`});for(const d of a)f.appendChild(r(d));const c=s("button",{background:"none",border:"none",padding:"0",cursor:"pointer",color:B.iconActive,fontSize:E.body,whiteSpace:"nowrap"});c.type="button",O(c,"bare");const l=d=>{f.style.display=d?"contents":"none",c.textContent=d?"less":`+${a.length} more`,c.setAttribute("aria-expanded",String(d))};l(!1),c.addEventListener("click",()=>l(c.getAttribute("aria-expanded")!=="true")),i.appendChild(f),i.appendChild(c)}return i}export function buildMetadataStripRow(t,n,i){const e=Zt(t,i),o=buildFacetSummaryChips(e);if(!o&&!n)return null;const a=s("div",{display:"flex",flexWrap:"wrap",alignItems:"baseline",gap:"4px 14px"});if(n){const r=s("span",{fontWeight:"500",whiteSpace:"nowrap"},n.aggrMethod?`${n.lotSize} wafers stacked \xB7 ${n.aggrMethod}`:`${n.lotSize} wafers stacked`);a.appendChild(r)}return o&&a.appendChild(o),a}const fe={display:"flex",flexDirection:"column",gap:y.sm,background:B.menuBg,border:`1px solid ${B.menuBorder}`,borderRadius:_.container,padding:`${y.sm} ${y.lg}`,boxShadow:vt.panel,fontSize:E.body,lineHeight:Q.none,boxSizing:"border-box",width:"100%",minWidth:"0"};export function buildMetadataStripBox(t,n,i){const e=buildMetadataStripRow(t,n,i);if(!e)return null;const o=s("div",fe);return o.appendChild(e),o}export function buildCompactMetadataRows(t){const n=metadataEntries(t);if(!n.length)return null;const i=s("div");for(const[e,o]of n)i.appendChild(pe(xt(e),o));return i}export function buildMetadataInfoSection(t,n){const i=buildMetadataStripRow(t,n,{facetableOnly:t.length>1});if(!i)return null;const e=s("div");return e.appendChild(J("Wafer Info")),e.appendChild(i),e}export function buildYieldSection(t,n,i=[1]){const e=s("div");e.appendChild(J("Summary"));const o=Dt();if(o.appendChild(at(String(t.totalDies),"Total dies")),t.partialDies>0&&o.appendChild(at(String(t.partialDies),"Partial")),t.yieldPercent!==null){const a=i.length===1?`bin ${i[0]}`:`bins ${i.join(", ")}`;o.appendChild(at(`${t.yieldPercent.toFixed(1)}%`,"Yield",`pass: ${a}`))}return o.finish(),e.appendChild(o),t.edgeExcludedDies>0&&e.appendChild(Mt("Edge excluded (outer zone)",String(t.edgeExcludedDies))),e}export function buildBinSection(t,n,i,e,o,a){const r=new Map;if(o)for(const[c,l]of Object.entries(o))r.set(Number(c),l);else for(const c of t){if(c.partial||c.edgeExcluded)continue;const l=i==="hard"?c.hbin:c.sbin;l!=null&&r.set(l,(r.get(l)??0)+1)}if(!r.size)return null;const f=s("div");f.appendChild(J(At(i,[...r.values()].reduce((c,l)=>c+l,0))));for(const c of It(r,n,e,a))f.appendChild(c);return f}function At(t,n){return`${t==="hard"?"Hard":"Soft"} Bin Breakdown \u2014 % of dies (N=${n})`}function It(t,n,i,e=[1]){const o=[...t.values()].reduce((c,l)=>c+l,0),a=n?new Map(n.map(c=>[c.bin,c])):null,r=oe(t.entries(),e),f=te(i);return r.map(([c,l])=>{const d=a?.get(c),b=d?.name?`Bin ${c} \xB7 ${d.name}`:`Bin ${c}`,p=l/o*100,v=(i==="custom"?d?.color:void 0)??f.forBin(c);return gt(`${b} (${l})`,p,v)})}export function buildBinBreakdownSection(t){const{dies:n,hbinDefs:i,sbinDefs:e,colorScheme:o,hardCounts:a,softCounts:r,plotMode:f,passBins:c=[1],panel:l}=t,d=m=>{const x=m==="hard"?a:r,S=new Map;if(x){for(const[$,L]of Object.entries(x))S.set(Number($),L);return S}for(const $ of n){if($.partial||$.edgeExcluded)continue;const L=m==="hard"?$.hbin:$.sbin;L!=null&&S.set(L,(S.get(L)??0)+1)}return S},b=d("hard"),p=d("soft"),v=b.size>0,N=p.size>0;if(!v&&!N)return null;const M=pt(l),W=f==="softBin"||f==="stackedSoftBins"?"soft":f==="hardBin"||f==="stackedBins"?"hard":void 0;let R=M.binMode??W??(v?"hard":"soft");R==="hard"&&!v&&(R="soft"),R==="soft"&&!N&&(R="hard");const H=R==="hard"?b:p,h=R==="hard"?i:e,u=[...H.values()].reduce((m,x)=>m+x,0),{outer:w}=X(At(R,u),!0,void 0,{stateKey:"bins",panel:l,render:m=>{for(const x of It(H,h,o,c))m.appendChild(x)},control:v&&N&&l?()=>ht([["hard","Hard"],["soft","Soft"]],R,m=>{M.binMode=m;const x=buildBinBreakdownSection(t);x&&w.replaceWith(x)},l.ownerDocument,!0):void 0});return w}export function buildLotBinSection(t,n,i,e){return buildBinSection(t,n,i,e)}export function buildRegionYieldPanelSection(t){const{diesByWafer:n,allWafers:i,ringCount:e,passBins:o,panel:a}=t,r=pt(a),f=d=>Vt(n,i,e,o,d==="ring"?jt:qt);if(!f("ring").length&&!f("quadrant").length)return null;const c=r.regionMode,{outer:l}=X(c==="ring"?"Ring Yield":"Quadrant Yield",!0,void 0,{stateKey:"regionYield",panel:a,render:d=>{const b=f(c);if(!b.length){d.appendChild(s("div",{fontSize:E.body,color:Y},"No positioned dies for this breakdown."));return}for(const{label:p,n:v,yieldPercent:N}of b)d.appendChild(gt(`${p} (N=${v})`,N))},control:a?()=>ht([["ring","Ring"],["quadrant","Quadrant"]],c,d=>{r.regionMode=d;const b=buildRegionYieldPanelSection(t);b&&l.replaceWith(b)},a.ownerDocument,!0):void 0});return l}export function buildLotOverviewSection(t,n=[]){const i=s("div");i.appendChild(J("Lot Summary"));const e=Dt();e.appendChild(at(String(t.stats.waferCount),"Wafers"));const o=t.perWafer.map(a=>a.summary.stats.yieldPercent).filter(a=>a!==null);if(o.length){const a=o.reduce((r,f)=>r+f,0)/o.length;e.appendChild(at(`${a.toFixed(1)}%`,"Mean per-wafer yield"))}if(e.finish(),i.appendChild(e),n.length){const a=n.reduce((f,c)=>f+c.stats.analyzedDies,0),r=n.reduce((f,c)=>f+c.stats.excludedDies,0);(a||r)&&i.appendChild(Mt("Dies analysed",r?`${a.toLocaleString()} (${r.toLocaleString()} excluded)`:a.toLocaleString()))}return i}export function buildPerWaferYieldSection(t,n,i,e,o){const a=t.perWafer.map(u=>({waferIndex:u.waferIndex,label:(n[u.waferIndex]?.label??`W${u.waferIndex+1}`).replace(/\s*·\s*\d+(\.\d+)?%$/,""),yieldPct:u.summary.stats.yieldPercent})).filter(u=>u.yieldPct!==null);if(!a.length)return null;const r=Math.min(...a.map(u=>u.yieldPct)),f=Math.max(...a.map(u=>u.yieldPct)),c=r===f?"":` (${r.toFixed(1)}\u2013${f.toFixed(1)}%)`,l=[...a.map(u=>u.yieldPct)].sort((u,w)=>u-w),d=re(l),b=it(l,.25),p=it(l,.75),v=a.length>=5?b-1.5*(p-b):-1/0,N=pt(e),M=u=>{const w=N.waferSort==="yield"?[...a].sort((m,x)=>m.yieldPct-x.yieldPct):a;for(const{waferIndex:m,label:x,yieldPct:S}of w){const L=S<v?`${x} \xB7 low outlier`:x,F=gt(L,S,void 0,void 0,d),T=o?.(m);T?.total&&H(F,T),h(F,m,x,S,T),u.appendChild(F)}},W=` \xB7 median ${d.toFixed(1)}%`,{outer:R}=X("Wafer Yield"+c+W,!0,void 0,{stateKey:"waferYield",panel:e,render:M,control:e?u=>ht([["slot","Slot"],["yield","Yield"]],N.waferSort,w=>{N.waferSort=w,u()},e.ownerDocument,!0):void 0});return R;function H(u,w){const m=w.unusual?"unusual":w.notable?"notable":"info",x=s("span",{marginLeft:y.sm,flexShrink:"0",background:dt(m),color:"#fff",borderRadius:_.container,padding:"0 5px",fontSize:E.body,fontWeight:"600"},`${w.total}`);x.setAttribute("aria-hidden","true"),ot(x,`${w.total} finding${w.total===1?"":"s"}`+(w.unusual?`, ${w.unusual} unusual`:"")+(w.notable?`, ${w.notable} notable`:"")),u.firstElementChild?.firstElementChild?.appendChild(x)}function h(u,w,m,x,S){i&&(u.style.cursor="pointer",u.style.borderRadius="4px",u.style.padding="2px 3px",u.style.marginLeft="-3px",u.style.marginRight="-3px",u.setAttribute("role","button"),u.setAttribute("aria-label",`${m}, ${x.toFixed(1)}% yield`+(S?.total?`, ${S.total} finding${S.total===1?"":"s"}${S.unusual?`, ${S.unusual} unusual`:""}`:"")+" \u2014 open wafer"),u.tabIndex=0,u.addEventListener("mouseenter",()=>{u.style.background=B.bgHover}),u.addEventListener("mouseleave",()=>{u.style.background=""}),u.addEventListener("focus",()=>{u.style.background=B.bgHover}),u.addEventListener("blur",()=>{u.style.background=""}),u.addEventListener("click",()=>i(w)),u.addEventListener("keydown",$=>{($.key==="Enter"||$.key===" ")&&($.preventDefault(),i(w))}))}}function me(t){const n=[...t].sort((o,a)=>o-a),i=t.reduce((o,a)=>o+a,0)/t.length,e=t.reduce((o,a)=>o+(a-i)**2,0)/t.length;return{min:n[0],max:n[n.length-1],mean:i,count:t.length,stddev:Math.sqrt(e),median:it(n,.5),q1:it(n,.25),q3:it(n,.75)}}function Wt(t,n){if(!t)return[];const i=t.waferMetadata??(t.perWaferMetadata?yt(t.perWaferMetadata.map(e=>({metadata:e}))):void 0);return!i||!Object.keys(i).length?[]:Jt({waferMetadata:i,waferKeys:"identity",waferPlacement:"csv",reservedLabels:n}).columns}function Pt(t){return t?.populationLabel?[{key:"population",label:"Population",scope:"wafer",constant:t.populationLabel,get:()=>t.populationLabel,csvOnly:!0}]:[]}export function buildTestSection(t,n,i,e,o,a,r,f,c="full"){const l=t.filter(g=>!g.partial&&!g.edgeExcluded),d=new Map((e?.perTestStats??[]).map(g=>[g.testNumber,g])),b=new Map((e?.testSpecYield??[]).map(g=>[g.testNumber,g]));let p;if(n?.length?p=n.filter(_t).map(g=>({testNumber:g.testNumber,name:g.name,unit:g.unit,limitLow:g.limitLow,limitHigh:g.limitHigh})):p=ne(l).map(C=>({testNumber:C,name:`Test ${C}`})),!p.length)return null;const v=p.filter(g=>l.some(C=>{const I=C.testValues?.[g.testNumber];return I!==void 0&&isFinite(I)}));if(!v.length)return null;const N=v.some(g=>g.limitLow!==void 0||g.limitHigh!==void 0),M=[];for(const g of v){const C=d.get(g.testNumber),I=C&&C.stddev!==void 0&&C.median!==void 0&&C.q1!==void 0&&C.q3!==void 0;let j;if(I){const k=C;j={testNumber:g.testNumber,min:k.min,max:k.max,mean:k.mean,count:k.count,stddev:k.stddev,median:k.median,q1:k.q1,q3:k.q3}}else{const k=l.map(D=>D.testValues?.[g.testNumber]).filter(D=>D!==void 0&&isFinite(D));if(!k.length)continue;j={testNumber:g.testNumber,...me(k)}}let z=null,P=0;if(g.limitLow!==void 0||g.limitHigh!==void 0){const k=b.get(g.testNumber);if(k)({yieldPercent:z,totalDies:P}=k);else{const D=l.map(q=>q.testValues?.[g.testNumber]).filter(q=>q!==void 0&&isFinite(q)),Z=D.filter(q=>g.limitLow!==void 0&&q<g.limitLow||g.limitHigh!==void 0&&q>g.limitHigh).length;P=D.length,z=D.length>0?(D.length-Z)/D.length*100:null}}M.push({entry:g,stats:j,specYieldPct:z,specN:P})}if(!M.length)return null;const W=new Map;if(r?.length&&n?.length)for(const g of ie(r,n))g.hasSpec&&W.set(g.testNumber,g.ppk);const R=W.size>0,h=new Set(M.map(g=>g.stats.count)).size===1?M[0].stats.count:null,u=s("div"),w=s("div",{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:y.sm}),m=h!==null?` \xB7 N=${h.toLocaleString()}`:"",x=a?.populationLabel?`Test Values (${M.length}) \u2014 ${a.populationLabel}${m}`:`Test Values (${M.length})${m}`,S=J(x);S.style.marginBottom="0",w.appendChild(S);const $=document.createElement("button");$.type="button",$.textContent="Test values CSV",Object.assign($.style,{...lt("outlined")}),O($),$.addEventListener("click",()=>{const g=["Test","Unit","N","Min","Q1","Median","Mean","Q3","Max","StdDev"];R&&g.push("Ppk");const C=M.some(k=>k.specYieldPct!==null&&k.specN!==k.stats.count);N&&(g.push("LSL","USL","Spec Yield %"),C&&g.push("Spec Yield N"));const I=[...Wt(a,g),...Pt(a)],z=[[...I.map(k=>k.label),...g].map(ct).join(",")],P=k=>St(k,void 0,"engineering");for(const{entry:k,stats:D,specYieldPct:Z,specN:q}of M){const st=[...I.map(et=>et.constant??""),k.name,k.unit??"",String(D.count),P(D.min),P(D.q1),P(D.median),P(D.mean),P(D.q3),P(D.max),P(D.stddev)];if(R){const et=W.get(k.testNumber);st.push(et==null?"":et.toFixed(3))}N&&(st.push(k.limitLow!==void 0?P(k.limitLow):"",k.limitHigh!==void 0?P(k.limitHigh):"",Z!==null?Z.toFixed(1):""),C&&st.push(k.limitLow!==void 0||k.limitHigh!==void 0?String(q):"")),z.push(st.map(ct).join(","))}Bt(z.join(`
2
+ `),"test-values.csv","text/csv",o)}),w.appendChild($),u.appendChild(w);const L=s("table",{width:"100%",borderCollapse:"collapse",fontSize:E.body}),F=document.createElement("thead"),T=document.createElement("tr"),A=["Test"];h===null&&A.push("N"),c==="full"&&A.push("Min","Q1","Median"),A.push("Mean"),c==="full"&&A.push("Q3","Max","StdDev"),R&&A.push("Ppk"),c==="full"&&N&&A.push("LSL","USL"),N&&A.push("Spec yield");for(const g of A){const C=s("th",{textAlign:g==="Test"?"left":"right",fontWeight:"600",fontSize:E.meta,letterSpacing:rt,textTransform:"uppercase",color:Y,padding:"3px 8px 5px",borderBottom:`1px solid ${B.menuBorder}`,whiteSpace:"nowrap"},g);T.appendChild(C)}F.appendChild(T),L.appendChild(F);const V=document.createElement("tbody");for(const{entry:g,stats:C,specYieldPct:I,specN:j}of M){const z=D=>St(D,g.unit,i),P=document.createElement("tr"),k=(D,Z="right")=>{const q=s("td",{textAlign:Z,padding:`${y.xs} ${y.md}`,borderBottom:`1px solid ${B.menuBorder}`,color:B.text,whiteSpace:"nowrap"},D);P.appendChild(q)};if(k(g.name,"left"),h===null&&k(`${C.count}`),c==="full"&&(k(z(C.min)),k(z(C.q1)),k(z(C.median))),k(z(C.mean)),c==="full"&&(k(z(C.q3)),k(z(C.max)),k(z(C.stddev))),R){const D=W.get(g.testNumber);k(D==null?"\u2014":D.toFixed(2))}c==="full"&&N&&(k(g.limitLow!==void 0?z(g.limitLow):"\u2014"),k(g.limitHigh!==void 0?z(g.limitHigh):"\u2014")),N&&k(I!==null?h!==null&&j===h?`${I.toFixed(1)}%`:`${I.toFixed(1)}% (N=${j})`:"\u2014"),V.appendChild(P)}L.appendChild(V);const K=s("div",{overflowX:"auto"});if(K.appendChild(L),!f)return u.appendChild(K),u;w.remove();const{outer:G}=X(x,!0,void 0,{stateKey:"testValues",panel:f,render:g=>g.appendChild(K),control:()=>$});return G}export function buildFunctionalTestSection(t,n,i,e,o,a){const r=i??Kt(t,n);if(!r?.length)return null;const c=new Set(r.map(m=>m.totalDies)).size===1?r[0].totalDies:null,l=c!==null?` \xB7 N=${c.toLocaleString()}`:"",d=s("div"),b=s("div",{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:y.sm}),p=o?.populationLabel?`Functional Tests (${r.length}) \u2014 ${o.populationLabel}${l}`:`Functional Tests (${r.length})${l}`,v=J(p);v.style.marginBottom="0",b.appendChild(v);const N=document.createElement("button");N.type="button",N.textContent="Functional CSV",Object.assign(N.style,{...lt("outlined")}),O(N),N.addEventListener("click",()=>{const m=["Test","N","Pass","Fail","Pass Rate %"],x=[...Wt(o,m),...Pt(o)],S=[[...x.map($=>$.label),...m].map(ct).join(",")];for(const $ of r)S.push([...x.map(L=>L.constant??""),$.label,String($.totalDies),String($.passDies),String($.failDies),$.passRatePercent!==null?$.passRatePercent.toFixed(1):""].map(ct).join(","));Bt(S.join(`
3
+ `),"functional-tests.csv","text/csv",e)}),b.appendChild(N),d.appendChild(b);const M=s("table",{width:"100%",borderCollapse:"collapse",fontSize:E.body}),W=document.createElement("thead"),R=document.createElement("tr"),H=c===null?["Test","N","Pass","Fail","Pass rate"]:["Test","Pass","Fail","Pass rate"];for(const m of H)R.appendChild(s("th",{textAlign:m==="Test"?"left":"right",fontWeight:"700",fontSize:E.body,letterSpacing:rt,textTransform:"uppercase",color:Y,padding:"3px 8px 5px",borderBottom:`1px solid ${B.menuBorder}`,whiteSpace:"nowrap"},m));W.appendChild(R),M.appendChild(W);const h=document.createElement("tbody");for(const m of r){const x=document.createElement("tr"),S=($,L="right")=>{x.appendChild(s("td",{textAlign:L,padding:`${y.xs} ${y.md}`,borderBottom:`1px solid ${B.menuBorder}`,color:U,whiteSpace:"nowrap"},$))};S(m.label,"left"),c===null&&S(String(m.totalDies)),S(String(m.passDies)),S(String(m.failDies)),S(m.passRatePercent!==null?`${m.passRatePercent.toFixed(1)}%`:"\u2014"),h.appendChild(x)}M.appendChild(h);const u=s("div",{overflowX:"auto"});if(u.appendChild(M),!a)return d.appendChild(u),d;b.remove();const{outer:w}=X(p,!0,void 0,{stateKey:"functionalTests",panel:a,render:m=>m.appendChild(u),control:()=>N});return w}export function buildLotFunctionalSection(t,n,i,e,o){const a=i?.length?{perWaferMetadata:i.map(f=>f.wafer??{}),populationLabel:`${i.length} wafer${i.length===1?"":"s"} pooled`}:void 0,r=ae(i);return r?buildFunctionalTestSection(t,n,r,e,a,o):buildFunctionalTestSection(t,n,void 0,e,a,o)}const tt={unusual:0,notable:1,info:2};function bt(t){return s("span",{display:"inline-block",width:"7px",height:"7px",borderRadius:_.pill,background:dt(t),flexShrink:"0"})}function zt(t,n){let i=t.summary;return n&&(i.startsWith(`${n} has `)?i=i.slice(n.length+5):i.startsWith(`${n} `)&&(i=i.slice(n.length+1))),i=nt(i),i.length?i[0].toUpperCase()+i.slice(1):i}export function buildFindingsSection(t,n,i,e,o=!1){if(!t.length)return null;const a=t.some(h=>h.severity==="unusual"||h.severity==="notable"),r=a?t.filter(h=>h.severity!=="info").length.toString():void 0,{outer:f,content:c}=o?(()=>{const h=s("div");return{outer:h,content:h}})():X(`Findings (${t.length})`,a,r);function l(h){return h.reduce((u,w)=>tt[w.severity]<tt[u]?w.severity:u,"info")}function d(h){const u=new Map;for(const m of h){const x=`${m.comparison.family}\0${m.comparison.left}`,S=u.get(x)??[];S.push(m),u.set(x,S)}const w=[...u.entries()].map(([m,x])=>{const S=[...x].sort(($,L)=>tt[$.severity]-tt[L.severity]);return{key:m,findings:S,worst:l(S)}});return w.sort((m,x)=>tt[m.worst]-tt[x.worst]),w}function b(h,u){const m={ring:"Ring",quadrant:"Quadrant",sector:"Sector","reticle-position":"Reticle","test-site":"Test site",cluster:"Cluster","edge-arc":"Edge arc",wafer:"Wafer"}[h]??h;return u.toLowerCase().startsWith(m.toLowerCase())?u:`${m}: ${u}`}const p=t.filter(h=>h.comparison.family==="spatial-pattern"),v=new Set([...p.flatMap(h=>h.relatedIds??[]),...t.flatMap(h=>h.absorbedIds??[])]),N=t.filter(h=>h.comparison.family!=="spatial-pattern"&&!v.has(h.id)),M=d(N);function W(h,u=!1,w){const m=e===h.id,x=document.createElement("button");return x.type="button",x.dataset.wmapFinding=h.id,x.textContent=zt(h,w),ot(x,h.summary),x.setAttribute("aria-current",m?"true":"false"),Object.assign(x.style,{border:`1px solid ${B.menuBorder}`,borderLeft:`3px solid ${dt(h.severity)}`,background:m?B.bgActive:B.menuBg,borderRadius:_.container,padding:u?"6px 10px":"8px 10px",textAlign:"left",fontSize:u?"10px":"11px",fontWeight:m?"600":"400",color:B.text,cursor:"pointer",width:"100%",marginBottom:y.xs}),O(x,"bare"),x.addEventListener("click",()=>i(h,x)),x}const R=nt(Qt(t)??"");if(R){const h=s("div",{background:B.bgActive,borderRadius:_.container,padding:`${y.md} ${y.lg}`,marginBottom:y.md,display:"flex",gap:y.md,alignItems:"flex-start"}),u=s("span",{flex:"1",fontSize:E.body,lineHeight:Q.base,color:B.text},R);h.appendChild(u);const w=s("button",{flexShrink:"0",border:"none",background:"none",fontSize:E.body,color:B.icon,cursor:"pointer",padding:"0",lineHeight:Q.base,whiteSpace:"nowrap"},"Detail \u25B8");w.type="button",O(w,"bare"),w.addEventListener("click",()=>{const m=kt({title:"Findings Summary",onClose:()=>{},anchor:w}),x=s("p",{fontSize:"16px",lineHeight:Q.base,color:B.text,padding:`${y.xxl} ${y.xxxl} ${y.xxl}`,margin:"0",borderBottom:`1px solid ${B.menuBorder}`,flexShrink:"0"},R);m.contentWrap.appendChild(x);const S=s("div",{overflowY:"auto",padding:`${y.xxl} ${y.xxxl}`,flex:"1"}),$=(L,F)=>{const T=s("div",{display:"flex",alignItems:"center",gap:"7px",fontSize:E.sub,fontWeight:"600",color:U,marginTop:y.lg,marginBottom:y.xs});return T.appendChild(bt(L)),T.appendChild(s("span",{},F)),T};for(const L of p){S.appendChild($(L.severity,L.comparison.left)),S.appendChild(s("div",{fontSize:E.body,color:B.text,padding:"4px 0 4px 15px",marginBottom:y.xxs},nt(L.summary)));const F=t.filter(T=>L.relatedIds?.includes(T.id));for(const T of F)S.appendChild(s("div",{fontSize:E.body,color:Y,padding:"2px 0 2px 23px",marginBottom:y.xxs},nt(T.summary)))}for(const L of M){const[F,T]=L.key.split("\0");S.appendChild($(L.worst,b(F,T)));for(const A of L.findings)S.appendChild(s("div",{fontSize:E.body,color:B.text,padding:"4px 0 4px 15px",marginBottom:y.xxs},zt(A,T)))}m.contentWrap.appendChild(S),Object.assign(m.contentWrap.style,{flexDirection:"column",overflow:"hidden"})}),h.appendChild(w),c.appendChild(h)}let H=!0;for(const h of p){H||c.appendChild(s("div",{height:"1px",background:B.separator,margin:"4px 0"})),H=!1;const u=t.filter($=>h.relatedIds?.includes($.id)),w=u.length>0,m=s("div",{position:"relative",marginBottom:w?"2px":"4px"}),x=e===h.id,S=document.createElement("button");if(S.type="button",S.dataset.wmapFinding=h.id,Object.assign(S.style,{border:`1px solid ${B.menuBorder}`,borderLeft:`3px solid ${dt(h.severity)}`,background:x?B.bgActive:B.menuBg,borderRadius:_.container,padding:"8px 32px 8px 10px",textAlign:"left",fontSize:E.body,fontWeight:x?"600":"500",color:B.text,cursor:"pointer",width:"100%"}),S.textContent=nt(h.summary),ot(S,h.summary),S.setAttribute("aria-current",x?"true":"false"),O(S,"bare"),S.addEventListener("click",()=>i(h,S)),m.appendChild(S),w){const $=s("div",{display:"none",paddingLeft:"12px",marginBottom:y.xs});for(const T of u)$.appendChild(W(T,!0));m.appendChild($);let L=!1;const F=s("button",{position:"absolute",top:"50%",right:"8px",transform:"translateY(-50%)",border:"none",background:"none",fontSize:E.body,color:B.icon,cursor:"pointer",padding:`${y.xxs} ${y.xs}`,lineHeight:Q.none},"\u25B8");O(F,"bare"),F.type="button",ot(F),O(F,"bare"),F.setAttribute("aria-label","Show supporting findings"),F.setAttribute("aria-expanded","false"),F.addEventListener("click",T=>{T.stopPropagation(),L=!L,$.style.display=L?"block":"none",F.textContent=L?"\u25BE":"\u25B8";const A=L?"Hide supporting findings":"Show supporting findings";F.setAttribute("aria-label",A),F.setAttribute("aria-expanded",String(L))}),m.appendChild(F)}c.appendChild(m)}for(const h of M){H||c.appendChild(s("div",{height:"1px",background:B.separator,margin:"4px 0"})),H=!1;const[u,w]=h.key.split("\0"),m=s("div",{display:"flex",alignItems:"center",gap:y.sm,fontSize:E.body,fontWeight:"600",color:U,margin:"2px 0 4px"});m.appendChild(bt(h.worst)),m.appendChild(s("span",{},b(u,w))),c.appendChild(m);for(const x of h.findings)c.appendChild(W(x,!1,w))}return f}const he=[{value:"",label:"All kinds"},{value:"yield",label:"Yield"},{value:"hardBin",label:"Hard bin"},{value:"softBin",label:"Soft bin"},{value:"test",label:"Test value"},{value:"functionalTest",label:"Functional test"},{value:"spatialPattern",label:"Spatial pattern"}],ge=[{value:"",label:"All regions"},{value:"ring",label:"Ring"},{value:"quadrant",label:"Quadrant"},{value:"reticle-position",label:"Reticle position"},{value:"test-site",label:"Test site"},{value:"wafer",label:"Wafer"},{value:"sector",label:"Sector"},{value:"cluster",label:"Cluster"},{value:"edge-arc",label:"Edge arc"},{value:"spatial-pattern",label:"Spatial pattern"}],be=["unusual","notable","info"],xe={unusual:"Unusual",notable:"Notable",info:"Info"};function ye(t,n,i){const e=s("div",{display:"flex",flexWrap:"wrap",alignItems:"center",gap:y.lg,marginBottom:y.lg,paddingBottom:"10px",borderBottom:`1px solid ${B.separator}`}),o={unusual:0,notable:0,info:0};for(const l of t)o[l.severity]++;const a=be.filter(l=>o[l]>0),r=new Set(n.severity===void 0?a:Array.isArray(n.severity)?n.severity:[n.severity]),f=s("div",{display:"flex",gap:y.xs,flexWrap:"wrap"});for(const l of a){const d=document.createElement("button");d.type="button";const b=()=>{const p=r.has(l);d.setAttribute("aria-pressed",p?"true":"false"),Object.assign(d.style,{display:"inline-flex",alignItems:"center",gap:"5px",border:`1px solid ${B.menuBorder}`,borderRadius:_.pill,background:p?B.bgActive:"none",opacity:p?"1":"0.5",color:B.text,fontSize:E.body,padding:`${y.xxs} ${y.md}`,cursor:"pointer"}),d.dataset.on=p?"true":"false"};d.appendChild(bt(l)),d.appendChild(s("span",{},`${xe[l]} ${o[l]}`)),ot(d,()=>r.has(l)?"Click to hide these findings":"Click to show these findings"),b(),O(d,"bare"),d.addEventListener("click",()=>{r.has(l)?r.delete(l):r.add(l),n.severity=r.size===a.length?void 0:[...r],i()}),f.appendChild(d)}e.appendChild(f);const c=n.kind!==void 0||n.family!==void 0;return(t.length>=Ce||c)&&(e.appendChild(wt("Kind:",he,n.kind??"",l=>{n.kind=l||void 0,i()},{maxWidth:"130px"})),e.appendChild(wt("Region:",ge,n.family??"",l=>{n.family=l||void 0,i()},{maxWidth:"150px"}))),e}const Ce=8;function Se(t,n){const i=s("div",{display:"flex",flexDirection:"column",gap:y.xs,padding:y.md,marginBottom:y.md,background:B.bgHover,borderRadius:_.control},void 0,n);if(i.appendChild(s("div",{fontSize:E.body,color:B.value},t.message,n)),t.detail&&i.appendChild(s("div",{fontSize:E.meta,color:Y},t.detail,n)),t.actionLabel&&t.onAction){const e=s("button",lt("outlined"),t.actionLabel,n);e.type="button",e.style.alignSelf="flex-start",e.style.marginTop=y.xs,O(e),e.addEventListener("click",t.onAction),i.appendChild(e)}return i}export function buildFindingsSectionWithFilter(t,n,i,e,o,a,r){if(!t.findings.length&&!r)return null;const f=t.findings.some(N=>N.severity==="unusual"||N.severity==="notable"),c=f?t.findings.filter(N=>N.severity!=="info").length.toString():void 0,l=a?.ownerDocument??document,{outer:d,content:b}=X(`Findings (${t.findings.length})`,f||!!r,c,{stateKey:"findings",panel:a});if(r&&b.appendChild(Se(r,l)),!t.findings.length)return d;b.appendChild(ye(t.findings,e,o));const p=Xt(t,e);if(!p.length)return b.appendChild(s("div",{color:Y,fontSize:E.body,textAlign:"center",padding:`${y.xxl} ${y.md}`},"No findings match the current filter.")),d;const v=buildFindingsSection(p,t,n,i,!0);return v&&b.appendChild(v),d}export function buildLotTestSection(t,n,i,e,o,a,r,f="full"){const c=e?.length?{perWaferMetadata:e.map(d=>d.wafer??{}),populationLabel:`${e.length} wafer${e.length===1?"":"s"} pooled`}:void 0;let l;if(e?.length&&e.every(d=>d.stats.perTestStats!==void 0)){const d=new Map;for(const b of e)for(const p of b.stats.perTestStats??[]){const v=d.get(p.testNumber);v?(v.n+=p.count,v.sum+=p.mean*p.count,v.min=Math.min(v.min,p.min),v.max=Math.max(v.max,p.max)):d.set(p.testNumber,{n:p.count,sum:p.mean*p.count,min:p.min,max:p.max})}l={perTestStats:[...d.entries()].map(([b,p])=>({testNumber:b,count:p.n,min:p.min,max:p.max,mean:p.sum/p.n}))}}if(e?.length&&e.every(d=>d.stats.testSpecYield!==void 0)){const d=new Map;for(const b of e)for(const p of b.stats.testSpecYield??[]){const v=d.get(p.testNumber);v?(v.passDies+=p.passDies,v.totalDies+=p.totalDies):d.set(p.testNumber,{passDies:p.passDies,totalDies:p.totalDies})}l={...l,testSpecYield:[...d.entries()].map(([b,p])=>({testNumber:b,totalDies:p.totalDies,yieldPercent:p.totalDies>0?p.passDies/p.totalDies*100:null}))}}return buildTestSection(t,n,i,l,o,c,a,r,f)}export function createSummaryPanelEl(t,n,i=document){const e=t==="top"||t==="bottom",o=s("div",{background:de,color:B.text,fontVariantNumeric:"tabular-nums",border:Nt,borderRadius:_.container,padding:y.xl,overflowY:e?"hidden":"auto",overflowX:e?"auto":"hidden",flexShrink:"0",boxSizing:"border-box",fontFamily:E.family,fontSize:E.sub,boxShadow:vt.panel},void 0,i);return t==="right"&&(o.style.marginRight=n),t==="left"&&(o.style.marginLeft=n),t==="bottom"&&(o.style.marginBottom=n),e?(o.style.height="180px",o.style.width="100%"):(o.style.width="300px",o.style.maxHeight="100%"),o}export function wrapWithSummaryPanel(t,n,i){const e=i==="top"||i==="bottom",o=s("div",{display:"flex",flexDirection:e?"column":"row",gap:y.md,width:"100%",height:"100%",flex:"1 1 0",minHeight:"0",boxSizing:"border-box",alignItems:e?"flex-start":"stretch"});return t.style.flex="1 1 0",t.style.minWidth="0",t.style.minHeight="0",i==="right"||i==="bottom"?(o.appendChild(t),o.appendChild(n)):(o.appendChild(n),o.appendChild(t)),o}function Ht(t){return s("div",{fontSize:E.heading,fontWeight:"600",color:U,marginBottom:y.lg},t)}function ft(t,n){const i=s("button",lt("outlined"),t);return i.type="button",O(i),i.addEventListener("click",n),i}function Yt(...t){const n=t.filter(e=>!!e);if(!n.length)return null;const i=s("div",{display:"flex",flexWrap:"wrap",gap:y.sm,marginBottom:y.lg});for(const e of n)i.appendChild(e);return i}function Ot(t,n,i,e,o,a,r,f,c,l,d){const b=t.ownerDocument,p=kt({title:"Die list",onClose:()=>{},anchor:t,ownerDocument:b});p.contentWrap.style.padding="14px 16px";const v=se(n,i,{...r,title:e,onSaveText:f,waferMetadata:o,metadataFields:a,extraColumn:c,getWafer:l,ringCount:d,ownerDocument:b});v&&p.contentWrap.appendChild(v)}export function renderWaferSummaryContent(t,n){const i=t.scrollTop;t.innerHTML="";const{wafer:e,dies:o,yieldSummary:a,dataCoverage:r,hbinDefs:f,sbinDefs:c,testDefs:l,statsSummary:d,passBins:b=[1],ringCount:p=4,colorScheme:v,plotMode:N,fallbackFormat:M,onFindingClick:W,activeFindingId:R=null,findingsFilter:H,onFindingsFilterChange:h,findingsNotice:u,onSaveText:w,metadataFields:m,dieListOptions:x,metadataShownElsewhere:S}=n;t.appendChild(Ht("Wafer Summary"));const $=n.warnings??Ct({statsSummary:d});$.length&&t.appendChild(mt($,t.ownerDocument));const L=a&&r?ft("Summary report",()=>{Lt(Gt({wafer:e,dies:o,yieldSummary:a,dataCoverage:r,hbinDefs:f,sbinDefs:c,testDefs:l,statsSummary:d,passBins:b,ringCount:p}),{anchor:t})}):null,F=(x?.enabled??!0)&&o.length?ft("View die list",()=>{Ot(t,o,l,`Die list \u2014 ${o.length} dies`,e.metadata,m,x,w,void 0,()=>e,p)}):null,T=Yt(L,F);T&&t.appendChild(T);const A=[],V=d?.stats.isLotStack?d.stats:void 0,K=V?.lotSize!==void 0?{lotSize:V.lotSize,aggrMethod:ee(V.aggregationMethod)}:void 0;S||A.push(buildMetadataInfoSection([{metadata:e.metadata??void 0}],K)),a&&r&&A.push(buildYieldSection(a,r,b)),d&&W&&H&&h&&A.push(buildFindingsSectionWithFilter(d,W,R,H,h,t,u)),A.push(buildBinBreakdownSection({dies:o,hbinDefs:f,sbinDefs:c,colorScheme:v,hardCounts:d?.stats.hardBinCounts,softCounts:d?.stats.softBinCounts,plotMode:N,passBins:b,panel:t})),A.push(buildRegionYieldPanelSection({diesByWafer:[o],allWafers:[e],ringCount:p,passBins:b,panel:t}));const G=e.metadata?{waferMetadata:e.metadata}:void 0;A.push(buildTestSection(o,l,M,d?.stats,w,G,[{dies:o}],t,"compact")),A.push(buildFunctionalTestSection(o,l,d?.stats.functionalYield,w,G,t));let g=!0;for(const C of A)C&&(g||t.appendChild(Tt()),g=!1,t.appendChild(C));t.appendChild(s("div",{height:"12px",flexShrink:"0"})),t.scrollTop=i}export function renderLotSummaryContent(t,n){const i=t.scrollTop;t.innerHTML="";const{lotSummary:e,items:o,hbinDefs:a,sbinDefs:r,testDefs:f,passBins:c=[1],ringCount:l=4,colorScheme:d,plotMode:b,fallbackFormat:p,onFindingClick:v,activeFindingId:N=null,onWaferClick:M,findingsFilter:W,onFindingsFilterChange:R,findingsNotice:H,onSaveText:h,dieListOptions:u,findingsFor:w}=n;t.appendChild(Ht(`Lot Summary \u2014 ${e.stats.waferCount} wafer${e.stats.waferCount===1?"":"s"}`));const m=n.warnings??Ct({lotStatsSummary:e});m.length&&t.appendChild(mt(m,t.ownerDocument));const x=ft("Summary report",()=>{Lt(Ut({items:o.map((C,I)=>({label:C?.label??`W${I+1}`,wafer:C?.wafer,dies:C?.dies,statsSummary:C?.statsSummary})),hbinDefs:a,sbinDefs:r,testDefs:f,passBins:c,ringCount:l}),{anchor:t})}),S=[],$=[],L=[],F=new WeakMap,T=new WeakMap;for(let C=0;C<o.length;C++){const I=o[C];if(!I){$.push([]);continue}I.wafer&&S.push(I.wafer);const j=I.dies??[],z=I.label??`W${C+1}`;for(const P of j)F.set(P,z),I.wafer&&T.set(P,I.wafer);$.push(j),L.push(...j)}const A=u?.enabled??!0?ft("View die list",()=>{L.length&&Ot(t,L,f,`Die list \u2014 ${L.length} dies across ${o.length} wafer${o.length===1?"":"s"}`,yt(o.filter(C=>!!C).map(C=>({metadata:C.wafer?.metadata}))),o.find(C=>C?.metadataFields?.length)?.metadataFields,u,h,{label:"Wafer",get:C=>F.get(C)},C=>T.get(C),l)}):null,V=Yt(x,A);V&&t.appendChild(V);const K=o.map(C=>C?.statsSummary).filter(C=>!!C),G=[buildLotOverviewSection(e,K)];v&&W&&R&&G.push(buildFindingsSectionWithFilter(e,v,N,W,R,t,H)),G.push(buildPerWaferYieldSection(e,o,M,t,w),buildBinBreakdownSection({dies:L,hbinDefs:a,sbinDefs:r,colorScheme:d,plotMode:b,passBins:c,panel:t}),buildRegionYieldPanelSection({diesByWafer:$,allWafers:S,ringCount:l,passBins:c,panel:t}),f?.length?buildLotTestSection(L,f,p,K,h,$.map(C=>({dies:C})),t,"compact"):null,f?.length?buildLotFunctionalSection(L,f,K,h,t):null);let g=!0;for(const C of G)C&&(g||t.appendChild(Tt()),g=!1,t.appendChild(C));t.appendChild(s("div",{height:"12px",flexShrink:"0"})),t.scrollTop=i}
@@ -44,6 +44,12 @@ export interface ToCanvasOptions {
44
44
  viewport?: ViewportTransform;
45
45
  /** Currently highlighted bin (or metadata value) — drawn with an active indicator in the bin legend. */
46
46
  activeBin?: number | string;
47
+ /** Bin legend row under the pointer — drawn with a background fill, which is
48
+ * deliberately a different channel from `activeBin`'s accent border and bold
49
+ * label so "selected" and "pointed at" never look the same. The legend is
50
+ * canvas-drawn, so this is the only way it can answer the pointer at all;
51
+ * a DOM `:hover` cannot reach it. */
52
+ hoverBin?: number | string;
47
53
  /**
48
54
  * Format to use for unitless values outside the normal display range [0.1, 9999].
49
55
  * `'engineering'` (default): multiples-of-3 exponent notation (e.g. `12E-6`).