@wafertools/wafermap 0.30.2 → 0.30.3

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 (82) hide show
  1. package/AGENTS.md +16 -5
  2. package/CHANGELOG.md +384 -0
  3. package/README.md +6 -3
  4. package/dist/packages/canvas-adapter/chartPopulation.d.ts +48 -0
  5. package/dist/packages/canvas-adapter/chartPopulation.js +1 -0
  6. package/dist/packages/canvas-adapter/charts/barPanel.d.ts +4 -1
  7. package/dist/packages/canvas-adapter/charts/barPanel.js +1 -1
  8. package/dist/packages/canvas-adapter/charts/boxplot.d.ts +4 -1
  9. package/dist/packages/canvas-adapter/charts/boxplot.js +1 -1
  10. package/dist/packages/canvas-adapter/charts/capability.js +1 -1
  11. package/dist/packages/canvas-adapter/charts/chartShell.d.ts +81 -7
  12. package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
  13. package/dist/packages/canvas-adapter/charts/correlation.js +2 -2
  14. package/dist/packages/canvas-adapter/charts/groupedBarPlot.js +1 -1
  15. package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
  16. package/dist/packages/canvas-adapter/charts/regionYieldDiagram.js +1 -1
  17. package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
  18. package/dist/packages/canvas-adapter/charts/sweep.d.ts +31 -0
  19. package/dist/packages/canvas-adapter/charts/sweep.js +1 -0
  20. package/dist/packages/canvas-adapter/charts/trend.d.ts +3 -1
  21. package/dist/packages/canvas-adapter/charts/trend.js +1 -1
  22. package/dist/packages/canvas-adapter/dieList.js +2 -2
  23. package/dist/packages/canvas-adapter/drilldown.d.ts +23 -0
  24. package/dist/packages/canvas-adapter/drilldown.js +1 -0
  25. package/dist/packages/canvas-adapter/icons.js +1 -1
  26. package/dist/packages/canvas-adapter/insightsTab.d.ts +55 -3
  27. package/dist/packages/canvas-adapter/insightsTab.js +1 -1
  28. package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
  29. package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
  30. package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
  31. package/dist/packages/canvas-adapter/toCanvas.js +1 -1
  32. package/dist/packages/canvas-adapter/toolbar.d.ts +30 -2
  33. package/dist/packages/canvas-adapter/toolbar.js +2 -2
  34. package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
  35. package/dist/packages/canvas-adapter/userGuideHtml.js +126 -3
  36. package/dist/packages/canvas-adapter/version.d.ts +2 -2
  37. package/dist/packages/canvas-adapter/version.js +1 -1
  38. package/dist/packages/canvas-adapter/warnings.js +1 -1
  39. package/dist/packages/renderer/axisTicks.d.ts +32 -0
  40. package/dist/packages/renderer/axisTicks.js +1 -0
  41. package/dist/packages/renderer/buildView.d.ts +11 -37
  42. package/dist/packages/renderer/buildView.js +1 -1
  43. package/dist/packages/renderer/buildWaferMap.d.ts +58 -1
  44. package/dist/packages/renderer/buildWaferMap.js +1 -1
  45. package/dist/packages/renderer/deprecate.d.ts +13 -5
  46. package/dist/packages/renderer/deprecate.js +1 -1
  47. package/dist/packages/renderer/derivedTests/apply.d.ts +85 -0
  48. package/dist/packages/renderer/derivedTests/apply.js +1 -0
  49. package/dist/packages/renderer/derivedTests/evaluate.d.ts +48 -0
  50. package/dist/packages/renderer/derivedTests/evaluate.js +1 -0
  51. package/dist/packages/renderer/derivedTests/parser.d.ts +148 -0
  52. package/dist/packages/renderer/derivedTests/parser.js +2 -0
  53. package/dist/packages/renderer/fmt.d.ts +24 -2
  54. package/dist/packages/renderer/fmt.js +1 -1
  55. package/dist/packages/renderer/index.d.ts +1 -0
  56. package/dist/packages/renderer/index.js +1 -1
  57. package/dist/packages/renderer/spec.d.ts +49 -0
  58. package/dist/packages/renderer/spec.js +1 -0
  59. package/dist/packages/renderer/testLabel.d.ts +154 -0
  60. package/dist/packages/renderer/testLabel.js +1 -0
  61. package/dist/packages/stats/analyzeWaferMap.js +1 -1
  62. package/dist/packages/stats/capability.d.ts +4 -0
  63. package/dist/packages/stats/capability.js +1 -1
  64. package/dist/packages/stats/correlation.d.ts +3 -0
  65. package/dist/packages/stats/correlation.js +1 -1
  66. package/dist/packages/stats/deprecated.d.ts +3 -0
  67. package/dist/packages/stats/deprecated.js +1 -1
  68. package/dist/packages/stats/index.d.ts +2 -2
  69. package/dist/packages/stats/index.js +1 -1
  70. package/dist/packages/stats/mergeTestDefs.js +1 -1
  71. package/dist/packages/stats/renderFindingsReport.js +8 -28
  72. package/dist/packages/stats/renderSummaryReport.js +21 -30
  73. package/dist/packages/stats/reportHtml.d.ts +27 -0
  74. package/dist/packages/stats/reportHtml.js +35 -14
  75. package/dist/packages/stats/sweep.d.ts +178 -0
  76. package/dist/packages/stats/sweep.js +1 -0
  77. package/dist/packages/stats/sweepXFromName.d.ts +27 -0
  78. package/dist/packages/stats/sweepXFromName.js +1 -0
  79. package/dist/packages/stats/testPassRate.js +1 -1
  80. package/dist/packages/stats/types.d.ts +28 -1
  81. package/llms.txt +3 -2
  82. package/package.json +1 -1
@@ -0,0 +1,154 @@
1
+ import type { TestDef } from './buildWaferMap.js';
2
+ /**
3
+ * The derived-test part of anything that describes a test: a `TestDef`, or a
4
+ * narrower record carried onward from one — a finding's `variable`, a
5
+ * capability row. Structural, so every such shape is read by the same helpers.
6
+ */
7
+ export type DerivedSource = {
8
+ derived?: true;
9
+ expression?: string;
10
+ } | undefined;
11
+ /**
12
+ * Marks a test name as derived rather than measured.
13
+ *
14
+ * A dagger rather than the obvious `ƒ`: in this domain `f` is femto, and a `ƒ`
15
+ * beside a column of `fA`/`fF` units is a genuine misread. The dagger collides
16
+ * with no unit and no operator, and already means "see the note" — which is the
17
+ * contract: wherever the glyph can appear, {@link DERIVED_KEY} appears with it.
18
+ */
19
+ export declare const DERIVED_MARK = "\u2020";
20
+ /**
21
+ * What {@link DERIVED_MARK} means, in words — the key every marked surface shows.
22
+ *
23
+ * Short on purpose: it has to fit the lower-right corner of a map beside the
24
+ * colorbar as well as a legend, and one string everywhere cannot drift. What a
25
+ * value was computed FROM is the expression, which tooltips show alongside it.
26
+ */
27
+ export declare const DERIVED_KEY = "Derived, not measured";
28
+ /**
29
+ * True when this def describes a test computed from other tests rather than
30
+ * measured by the tester. Reads the flag `buildWaferMap` stamps on the defs it
31
+ * admits from `derivedTests` — never infers it from the test number, which is
32
+ * an application-defined identifier carrying no such meaning.
33
+ */
34
+ export declare function isDerivedTest(def: DerivedSource): boolean;
35
+ /**
36
+ * The display name for a test: its `name`, or `"Test <number>"` when it has no
37
+ * def or no name. The single source of that fallback.
38
+ *
39
+ * `testNumber` is passed separately so a caller holding only a number and a
40
+ * possibly-missing def gets the same string as one holding a complete def — the
41
+ * shape every previous copy of this rule had.
42
+ *
43
+ * Typed to the structural minimum rather than `TestDef` so the chart test
44
+ * picker's own `TestSelectItem` (which carries only a number and an optional
45
+ * name) uses the same fallback as everything else instead of keeping its copy.
46
+ */
47
+ export declare function testLabel(def: {
48
+ name?: string;
49
+ } | undefined, testNumber: number): string;
50
+ /**
51
+ * {@link testLabel} with the derived-test marker in front when the def is
52
+ * derived — `"† On/Off Ratio"`. The form every surface uses to name a test: the
53
+ * map title, tooltips, findings, tables, pickers and menus.
54
+ *
55
+ * The marker always PRECEDES the name, everywhere (Paul, 2026-09-23). In a list
56
+ * the eye runs down the left edge, so leading marks form a column and a derived
57
+ * test is found at a glance, where trailing ones land at a different x on every
58
+ * row; and truncating a long name can never cut the marker off. One position on
59
+ * every surface, so a reader learns it once. A unit follows the name as usual —
60
+ * `"† Vth Margin (V)"` — so the marker qualifies the whole test.
61
+ *
62
+ * A list that should keep measured names aligned with marked ones pads them by
63
+ * the marker's width when any entry is derived — {@link DERIVED_LANE_PAD} for
64
+ * plain text, a fixed-width slot where there is DOM or canvas to measure with.
65
+ */
66
+ export declare function markedTestLabel(def: (TestDef | {
67
+ name?: string;
68
+ derived?: true;
69
+ }) | undefined, testNumber: number): string;
70
+ /**
71
+ * Plain-text stand-in for `"† "` in front of a MEASURED name, for a text-only
72
+ * list (a native `<select>`) that holds derived tests too, so every name starts
73
+ * at about the same x. A figure space plus a thin space: close to the width of
74
+ * a dagger and a space in the UI fonts, and invisible. Approximate by nature —
75
+ * a surface that can measure uses a real fixed-width slot instead.
76
+ */
77
+ export declare const DERIVED_LANE_PAD = "\u2007\u2009";
78
+ /**
79
+ * The one-line explanation of a derived test for a tooltip:
80
+ * `"† Derived, not measured: t[1010] / t[1020]"`, or without
81
+ * the expression when none was carried. `undefined` for a measured test.
82
+ *
83
+ * Plain text — the caller escapes it, like every other tooltip line.
84
+ */
85
+ export declare function derivedTestNote(def: DerivedSource): string | undefined;
86
+ /**
87
+ * What a derived test was computed from, as a phrase fit to show a user, or
88
+ * `undefined` for a measured test.
89
+ *
90
+ * Deliberately returns the expression verbatim rather than prettifying it: it
91
+ * is the exact text the application author wrote and can be matched against
92
+ * their own test-program source, which a reformatted version could not be.
93
+ */
94
+ export declare function derivedTestSource(def: DerivedSource): string | undefined;
95
+ /**
96
+ * `{ derived: true, expression }` for a derived test, `{}` for a measured one —
97
+ * spread into any row or record that carries a test onward (a capability row,
98
+ * a summary table entry, a finding's variable, a stacked card's def).
99
+ *
100
+ * The single form of that hand-off. Every place a test is re-described in a
101
+ * narrower shape is a place the flag can be dropped — it was, in
102
+ * `mergeTestDefs`, the Summary panel's table and `poolFunctionalYield`, each a
103
+ * row rebuilt field by field. A shape that spreads this cannot forget the
104
+ * expression while remembering the flag.
105
+ */
106
+ export declare function derivedFields(def: DerivedSource): {
107
+ derived?: true;
108
+ expression?: string;
109
+ };
110
+ /**
111
+ * A label with the derived-test marker taken back out — for a surface that
112
+ * states derivation another way and so must not also carry a bare glyph (a
113
+ * CSV column, the key line that opens with the marker). The inverse of
114
+ * {@link markedTestLabel}, which is the only thing that marks a label, so the
115
+ * two live side by side.
116
+ */
117
+ export declare function unmarkedLabel(label: string): string;
118
+ /** Header of the column a CSV export adds when any row is a derived test. */
119
+ export declare const DERIVED_CSV_HEADER = "Derived from";
120
+ /**
121
+ * The {@link DERIVED_CSV_HEADER} cell for one row. A CSV has nowhere to put a
122
+ * key, so a derived test is stated as data: its expression, or the key's
123
+ * words if none was carried — never blank, which would read as measured.
124
+ * Blank for a measured test.
125
+ */
126
+ export declare function derivedCsvCell(entry: DerivedSource): string;
127
+ /**
128
+ * The key line for a surface that shows several tests at once with no hover to
129
+ * explain each one — a stats table, a findings list, a printed report:
130
+ * `"† Derived, not measured — Leakage Shift: abs(t[1020] - t[1010]); …"`.
131
+ * `undefined` when none of `entries` is derived, so the line appears exactly
132
+ * when a marked name is on the surface.
133
+ *
134
+ * Entries are de-duplicated by name, and the marker is dropped from each name
135
+ * ({@link unmarkedLabel}) since the line already opens with it. Plain text; the
136
+ * caller escapes it.
137
+ */
138
+ export declare function derivedKeyText(entries: ReadonlyArray<{
139
+ label: string;
140
+ derived?: true;
141
+ expression?: string;
142
+ }>): string | undefined;
143
+ /**
144
+ * True when any test in this population is derived — the gate for reserving a
145
+ * marker gutter in a view.
146
+ *
147
+ * Reserving it per view rather than per row is what keeps the marker from
148
+ * shifting content relative to itself: every row of a view indents equally, so
149
+ * the markers align in a column instead of leaving a ragged edge, and a
150
+ * population with no derived tests — the common case — is unchanged to the
151
+ * pixel.
152
+ */
153
+ export declare function hasDerivedTests(defs: readonly TestDef[] | undefined): boolean;
154
+ //# sourceMappingURL=testLabel.d.ts.map
@@ -0,0 +1 @@
1
+ export const DERIVED_MARK="\u2020",DERIVED_KEY="Derived, not measured";export function isDerivedTest(e){return e?.derived===!0}export function testLabel(e,r){const t=e?.name;return t!==void 0&&t!==""?t:`Test ${r}`}export function markedTestLabel(e,r){const t=testLabel(e,r);return e?.derived===!0?`\u2020 ${t}`:t}export const DERIVED_LANE_PAD="\u2007\u2009";export function derivedTestNote(e){if(!isDerivedTest(e))return;const r=derivedTestSource(e);return r?`\u2020 ${DERIVED_KEY}: ${r}`:`\u2020 ${DERIVED_KEY}`}export function derivedTestSource(e){if(!(!isDerivedTest(e)||!e?.expression))return e.expression}export function derivedFields(e){return isDerivedTest(e)?e.expression!==void 0?{derived:!0,expression:e.expression}:{derived:!0}:{}}export function unmarkedLabel(e){return e.startsWith("\u2020 ")?e.slice(2):e}export const DERIVED_CSV_HEADER="Derived from";export function derivedCsvCell(e){return isDerivedTest(e)?derivedTestSource(e)??DERIVED_KEY:""}export function derivedKeyText(e){const r=new Map;for(const n of e){if(!isDerivedTest(n))continue;const i=unmarkedLabel(n.label);r.has(i)||r.set(i,n.expression)}if(r.size===0)return;const t=[...r].map(([n,i])=>i?`${n}: ${i}`:n);return`\u2020 ${DERIVED_KEY} \u2014 ${t.join("; ")}`}export function hasDerivedTests(e){return(e??[]).some(isDerivedTest)}
@@ -1 +1 @@
1
- import{normalizeInput as Ce}from"./normalizeInput.js";import{computeCapability as Ne,computeTestFlagYield as Pe,computeRegionYield as Ve}from"./summaryFigures.js";import{isYieldEligibleDie as te,getDieKey as T,isPositionedDie as ge}from"../core/dies.js";import{getTestPassStatus as me,isParametricTest as O}from"../renderer/buildWaferMap.js";import{buildQuadrantRegions as Le,buildReticlePositionRegions as Ee,buildRingRegions as Ke,buildSectorRegions as Te,buildTestSiteRegions as xe,sectorCompassNames as he,areQuadrantsAdjacent as je,parseRegionKey as q}from"./regions.js";import{buildClusterFindings as Ae}from"./clusterDetection.js";import{classifyPattern as Oe}from"./patternClassification.js";import{normalCdf as pe}from"./math.js";import{quantile as ne}from"./math.js";import{mean as J,clamp01 as be}from"../core/utils.js";const qe={significanceLevel:{min:1e-6,max:1},minimumEffectSize:{min:0,max:1},minimumRelativeEffect:{min:0,max:Number.MAX_SAFE_INTEGER}},ye=[4,8,16,32];function He(s){const a=Object.fromEntries(Object.entries(s).filter(([,t])=>t!==void 0)),i={...x,...a},o=[],e=[];for(const t of["significanceLevel","minimumEffectSize","minimumRelativeEffect"]){const{min:l,max:n}=qe[t],u=i[t];if(typeof u!="number"||!Number.isFinite(u)){e.push(`${t}=${String(u)} is not a finite number (using ${x[t]})`),i[t]=x[t];continue}const f=Math.min(n,Math.max(l,u));f!==u&&(e.push(`${t}=${u} is outside ${l}\u2013${n} (using ${f})`),i[t]=f)}if(ye.includes(i.sectorCount)||(e.push(`sectorCount=${i.sectorCount} is not one of ${ye.join(", ")} (using ${x.sectorCount})`),i.sectorCount=x.sectorCount),e.length){const t=`analyzeWaferMap: ${e.join("; ")}. The analysis ran with the corrected values.`;console.warn(`[wafermap] ${t}`),o.push({code:"analysis-option-corrected",message:t,severity:"warning"})}const c=["passBins","ringCount"].filter(t=>a[t]!==void 0);if(c.length){const t=`analyzeWaferMap: ${c.join(" and ")} ${c.length>1?"are":"is"} no longer an analysis option and was ignored. Set ${c.length>1?"them":"it"} on buildWaferMap; the analysis used the value the map was built with.`;console.warn(`[wafermap] ${t}`),o.push({code:"analysis-option-corrected",message:t,severity:"warning"})}const r=["enableYieldAnalysis","enableHardBinAnalysis","enableSoftBinAnalysis","enableReticlePositionAnalysis","enableTestSiteAnalysis","enableClusterAnalysis","enableAngularAnalysis","enablePatternClassification"].filter(t=>a[t]!==void 0);if(r.length){const t=`analyzeWaferMap: ${r.join(", ")} ${r.length>1?"are":"is"} no longer an option and was ignored: every analysis now runs. Filter the findings (filterFindings) to show fewer.`;console.warn(`[wafermap] ${t}`),o.push({code:"analysis-option-corrected",message:t,severity:"warning"})}return{resolved:i,warnings:o}}const x={ringCount:4,passBins:[1],significanceLevel:.05,minimumEffectSize:.2,minimumRelativeEffect:1,minimumSampleSize:5,enableTestValueAnalysis:!1,computePerTestStats:!1,sectorCount:8,minimumClusterSize:5};function Ie(s,a){const i={...s};return i.minimumClusterSize=Math.max(5,Math.round(a*.003)),i}function Ye(s){return te(s)?s.hbin!==void 0||s.sbin!==void 0||s.testValues!==void 0&&Object.keys(s.testValues).length>0:!1}function _e(s,a,i){if(!s||a)return;const o=(i??[]).filter(e=>O(e)&&(e.limitLow!==void 0||e.limitHigh!==void 0));if(o.length!==0)return e=>{for(const c of o){const r=c.testNumber,t=e.testValues?.[r];if(t!==void 0&&(c.limitLow!==void 0&&t<c.limitLow||c.limitHigh!==void 0&&t>c.limitHigh))return!0}return!1}}function We(s,a,i){const o=new Set,e=new Set,c=new Set,r=new Map,t=new Map;for(const l of s){if(l.testValues)for(const n of Object.keys(l.testValues))o.add(Number(n));if(l.testPass)for(const n of Object.keys(l.testPass))o.add(Number(n));l.hbin!==void 0&&e.add(l.hbin),l.sbin!==void 0&&c.add(l.sbin),te(l)&&(l.hbin!==void 0&&r.set(l.hbin,(r.get(l.hbin)??0)+1),l.sbin!==void 0&&t.set(l.sbin,(t.get(l.sbin)??0)+1))}return{totalDies:s.length,analyzedDies:a,excludedDies:s.length-a,yieldPercent:i,testsConsidered:[...o].sort((l,n)=>l-n),hardBinsConsidered:[...e].sort((l,n)=>l-n),softBinsConsidered:[...c].sort((l,n)=>l-n),...r.size?{hardBinCounts:Object.fromEntries(r)}:{},...t.size?{softBinCounts:Object.fromEntries(t)}:{}}}function Qe(s,a){if(!a?.length)return;const i=a.filter(e=>O(e)&&(e.limitLow!==void 0||e.limitHigh!==void 0));if(!i.length)return;const o=[];for(const e of i){const c=e.testNumber;let r=0,t=0,l=0,n=0;for(const u of s){if(u.partial||u.edgeExcluded)continue;const f=u.testValues?.[c];f!==void 0&&(n++,e.limitLow!==void 0&&f<e.limitLow?t++:e.limitHigh!==void 0&&f>e.limitHigh?l++:r++)}o.push({testNumber:c,label:e.name,passDies:r,failLowDies:t,failHighDies:l,totalDies:n,yieldPercent:n>0?r/n*100:null})}return o.length?o:void 0}export function computeFunctionalYield(s,a){if(!a?.length)return;const i=a.filter(e=>!O(e));if(!i.length)return;const o=[];for(const e of i){const c=e.testNumber;let r=0,t=0;for(const n of s){if(n.partial||n.edgeExcluded)continue;const u=me(n,c,e);u!==void 0&&(u?r++:t++)}const l=r+t;o.push({testNumber:c,label:e.name,passDies:r,failDies:t,totalDies:l,passRatePercent:l>0?r/l*100:null})}return o.length?o:void 0}function Ue(s,a,i,o){const e=[];for(const c of a){const r=[];for(const f of s){if(f.partial||f.edgeExcluded)continue;const h=f.testValues?.[c];h!==void 0&&Number.isFinite(h)&&r.push(h)}if(r.length<o)continue;const t=J(r),l=Math.sqrt(fe(r,t)),n=Float64Array.from(r);n.sort();const u=i?.find(f=>f.testNumber===c)?.name??String(c);e.push({testNumber:c,label:u,count:r.length,min:n[0],max:n[n.length-1],mean:t,stddev:l,median:ne(n,.5),q1:ne(n,.25),q3:ne(n,.75)})}return e.length?e:void 0}function H(s,a,i,o){const e=(s+i)/(a+o),c=e*(1-e)*(1/a+1/o);if(!Number.isFinite(c)||c<=0)return 1;const r=(s/a-i/o)/Math.sqrt(c);return be(2*(1-pe(Math.abs(r))))}function ie(s){const a=new Map;for(const i of s){const o=`${i.variable.kind}:${i.comparison.family}`,e=a.get(o)??[];e.push(i),a.set(o,e)}for(const i of a.values()){const o=[...i].filter(r=>r.stats.pValue!==void 0).sort((r,t)=>(r.stats.pValue??1)-(t.stats.pValue??1)),e=o.length;let c=1;for(let r=e-1;r>=0;r--){const t=o[r],l=t.stats.pValue??1,n=Math.min(1,Math.min(c,l*e/(r+1)));t.stats.adjustedPValue=n,c=n}}return s}function se(s,a,i){const o=Math.abs(a),e=i!==void 0?Math.abs(i):0;return s<=.01&&(o>=.3||e>=2.5)?"unusual":s<=.05&&(o>=.2||e>=1.5)?"notable":"info"}function oe(s,a){return s<=.01&&Math.abs(a)>=.5?"unusual":s<=.05&&Math.abs(a)>=.15?"notable":"info"}function Z(s){return s==="reticle-position"?"other reticle positions":s==="test-site"?"other test sites":"the rest of the map"}function I(s){return s==="reticle-position"?"Other reticle positions":s==="test-site"?"Other test sites":"Rest of map"}function Se(s,a,i){const o=(Math.abs(a)*100).toFixed(1);return`${s} yield is ${o} percentage points ${a>0?"higher":"lower"} than ${Z(i)}`}function ae(s,a){return a==="quadrant"&&!s.startsWith("Quadrant")?`quadrant ${s}`:s}function we(s,a,i,o){const e=ae(s,o),c=(Math.abs(i)*100).toFixed(1);return`${e} has ${a} occurrence ${c} percentage points ${i>0?"higher":"lower"} than ${Z(o)}`}function Ge(s,a,i,o){const e=ae(s,o),c=(Math.abs(i)*100).toFixed(1);return`${e} has ${a} pass rate ${c} percentage points ${i>0?"higher":"lower"} than ${Z(o)}`}function $e(s,a,i,o,e,c){const r=ae(s,e),t=Z(e),l=i>0?"higher":"lower";if(o!==void 0&&Number.isFinite(o)){const u=(Math.abs(o)*100).toFixed(1);return`${r} mean ${a} is ${u}% ${l} than ${t}`}const n=c?` ${c}`:"";return`${r} mean ${a} is ${Math.abs(i).toPrecision(3)}${n} ${l} than ${t}`}function ve(s,a,i){const o=a?.find(e=>e.bin===s);return o?.name?`${i} ${s} (${o.name})`:`${i} ${s}`}function Xe(s,a){const i=a?.find(o=>o.testNumber===s);return{label:i?.name??`Test ${s}`,unit:i?.unit}}function re(s,a){const i=new Map(s.map(e=>[T(e),e])),o=new Map;for(const e of a){const c=[];for(const r of e.dieKeys){const t=i.get(r);t&&c.push(t)}o.set(e.key,c)}return o}function le(s,a){return ie(s),s.filter(i=>{const o=i.stats.adjustedPValue??i.stats.pValue??1,e=Math.abs(i.effect.absoluteDelta??0),c=Math.abs(i.effect.relativeDelta??0);return o<=a.significanceLevel&&(e>=a.minimumEffectSize||c>=a.minimumRelativeEffect)}).map(i=>({...i,severity:se(i.stats.adjustedPValue??i.stats.pValue??1,i.effect.absoluteDelta??0,i.effect.relativeDelta)}))}function W(s,a,i,o){const e=new Set(i),c=re(s,a),r=new Map,t=new Map;for(const[n,u]of c){let f=0,h=0;for(const g of u)g.hbin!==void 0&&(h++,e.has(g.hbin)&&f++);r.set(n,f),t.set(n,h)}const l=[];for(const n of a){const u=t.get(n.key)??0,f=r.get(n.key)??0;let h=0,g=0;for(const[w,m]of t)w!==n.key&&(h+=m,g+=r.get(w)??0);if(u<o.minimumSampleSize||h<o.minimumSampleSize)continue;const p=f/u,b=g/h,y=p-b,z=H(f,u,g,h);l.push({id:`yield:${n.key}`,level:"wafer",severity:"info",variable:{kind:"yield",label:"Yield"},comparison:{family:n.family,left:n.label,right:I(n.family)},effect:{direction:y===0?"different":y>0?"higher":"lower",absoluteDelta:y,relativeDelta:b===0?void 0:y/b,effectSize:y},stats:{method:"two-proportion-z",pValue:z,sampleSizeLeft:u,sampleSizeRight:h},summary:Se(n.label,y,n.family),highlight:{kind:"region",regionFamily:n.family,regionKeys:[n.key],dieKeys:[...n.dieKeys]}})}return le(l,o)}function L(s,a,i,o,e,c){const r=g=>i==="soft"?g.sbin:g.hbin,t=[...new Set(s.map(r).filter(g=>g!==void 0))].sort((g,p)=>g-p),l=re(s,a),n=new Map,u=new Map;for(const[g,p]of l){const b=new Map;for(const y of p){const z=r(y);z!==void 0&&b.set(z,(b.get(z)??0)+1)}n.set(g,b),u.set(g,p.length)}const f=[],h=e==="hardBin"?"HBin":"SBin";for(const g of a){const p=u.get(g.key)??0,b=n.get(g.key);let y=0;const z=new Map;for(const[w,m]of n)if(w!==g.key){y+=u.get(w)??0;for(const[S,v]of m)z.set(S,(z.get(S)??0)+v)}if(!(p<c.minimumSampleSize||y<c.minimumSampleSize))for(const w of t){const m=b.get(w)??0,S=z.get(w)??0,v=m/p,D=S/y,$=v-D,k=H(m,p,S,y),M=ve(w,o,h);f.push({id:`${e}:${w}:${g.key}`,level:"wafer",severity:"info",variable:{kind:e,bin:w,label:M},comparison:{family:g.family,left:g.label,right:I(g.family)},effect:{direction:$===0?"different":$>0?"higher":"lower",absoluteDelta:$,relativeDelta:D===0?void 0:$/D,effectSize:$},stats:{method:"two-proportion-z",pValue:k,sampleSizeLeft:p,sampleSizeRight:y},summary:we(g.label,M,$,g.family),highlight:{kind:"bin",bin:w,regionKeys:[g.key],dieKeys:[...g.dieKeys]}})}}return le(f,c)}function Q(s,a,i,o){const e=(i??[]).filter(u=>!O(u));if(!e.length)return[];const c=e.map(u=>u.testNumber),r=re(s,a),t=new Map,l=new Map;for(const[u,f]of r){const h=new Uint32Array(e.length),g=new Uint32Array(e.length);for(const p of f)for(let b=0;b<e.length;b++){const y=me(p,c[b],e[b]);y!==void 0&&(g[b]++,y&&h[b]++)}t.set(u,h),l.set(u,g)}const n=[];for(const u of a){const f=t.get(u.key),h=l.get(u.key);for(let g=0;g<e.length;g++){let p=0,b=0;for(const[$,k]of l)$!==u.key&&(b+=k[g],p+=t.get($)[g]);const y=h[g],z=f[g];if(y<o.minimumSampleSize||b<o.minimumSampleSize)continue;const w=z/y,m=p/b,S=w-m,v=H(z,y,p,b),D=c[g];n.push({id:`functional:${D}:${u.key}`,level:"wafer",severity:"info",variable:{kind:"functionalTest",index:D,label:`${e[g].name} pass rate`},comparison:{family:u.family,left:u.label,right:I(u.family)},effect:{direction:S===0?"different":S>0?"higher":"lower",absoluteDelta:S,relativeDelta:m===0?void 0:S/m,effectSize:S},stats:{method:"two-proportion-z",pValue:v,sampleSizeLeft:y,sampleSizeRight:b},summary:Ge(u.label,e[g].name,S,u.family),highlight:{kind:"region",regionFamily:u.family,regionKeys:[u.key],dieKeys:[...u.dieKeys]}})}}return le(n,o)}function fe(s,a){return s.length<2?0:s.reduce((i,o)=>i+(o-a)**2,0)/(s.length-1)}function De(s,a,i,o,e,c){const r=Math.sqrt(i/s+c/o),t=a-e;if(!Number.isFinite(r)||r===0)return{pValue:1,effectSize:0,delta:t};const l=t/r,n=Math.sqrt(Math.max(0,(i+c)/2)),u=n===0?t:t/n;return{pValue:be(2*(1-pe(Math.abs(l)))),effectSize:u,delta:t}}function Je(s,a){const i=J(s),o=J(a);return De(s.length,i,fe(s,i),a.length,o,fe(a,o))}const ke=250;function ze(s,a,i){const o=r=>r.filter(t=>O(i?.find(l=>l.testNumber===t)));if(a)return{testNumbers:o(a.slice().sort((r,t)=>r-t))};const e=new Set;let c=!1;e:for(const r of s)for(const t of Object.keys(r.testValues??{})){const l=Number(t);if(!e.has(l)&&(e.add(l),e.size>ke)){c=!0;break e}}if(c){const r=`Test-value analysis was skipped: more than ${ke} tests were found in the die data, so no test findings were computed. Pass testNumbers: [...] to analyse specific tests.`;return console.warn(`[wafermap] analyzeWaferMap: ${r}`),{testNumbers:[],warning:{code:"test-count-capped",message:r,severity:"warning"}}}return{testNumbers:o([...e].sort((r,t)=>r-t))}}function U(s,a,i,o){const e=ze(s,o.testNumbers,i);if(e.warning)return{findings:[],warning:e.warning};const c=e.testNumbers,r=new Map;for(let p=0;p<a.length;p++)for(const b of a[p].dieKeys)r.set(b,p);const t=a.length,l=[],n=[];for(const p of s){const b=r.get(T(p));b!==void 0&&(l.push(p),n.push(b))}const u=[],f=new Float64Array(t),h=new Float64Array(t),g=new Float64Array(t);for(const p of c){f.fill(0),h.fill(0),g.fill(0);let b=0,y=0,z=0,w;for(let m=0;m<l.length;m++){const v=l[m].testValues?.[p];if(v===void 0)continue;w===void 0&&(w=v);const D=v-w,$=n[m];f[$]+=1,h[$]+=D,g[$]+=D*D,b+=1,y+=D,z+=D*D}if(w!==void 0)for(let m=0;m<t;m++){const S=f[m],v=b-S;if(S<o.minimumSampleSize||v<o.minimumSampleSize)continue;const D=h[m]/S,k=(y-h[m])/v,M=D+w,R=k+w,P=S>1?Math.max(0,g[m]-S*D*D)/(S-1):0,d=v>1?Math.max(0,z-g[m]-v*k*k)/(v-1):0,{pValue:N,effectSize:B,delta:V}=De(S,M,P,v,R,d),F=a[m],{label:A,unit:G}=Xe(p,i),_=R!==0?V/Math.abs(R):void 0;u.push({id:`test:${p}:${F.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:p,label:A,unit:G},comparison:{family:F.family,left:F.label,right:I(F.family)},effect:{direction:V===0?"different":V>0?"higher":"lower",absoluteDelta:V,relativeDelta:_,effectSize:B},stats:{method:"welch-z-approx",pValue:N,sampleSizeLeft:S,sampleSizeRight:v},summary:$e(F.label,A,V,_,F.family,G),highlight:{kind:"region",regionFamily:F.family,regionKeys:[F.key],dieKeys:[...F.dieKeys]}})}}return ie(u),{activeTestNumbers:c,findings:u.filter(p=>{const b=p.stats.adjustedPValue??p.stats.pValue??1,y=Math.abs(p.effect.effectSize??0);return b<=o.significanceLevel&&y>=o.minimumEffectSize}).map(p=>({...p,severity:oe(p.stats.adjustedPValue??p.stats.pValue??1,p.effect.effectSize??0)}))}}function Ze(s,a,i,o){if(!i?.length)return[];const e=i.filter(t=>O(t)&&(t.limitLow!==void 0||t.limitHigh!==void 0));if(!e.length)return[];const c=[],r=new Map(s.map(t=>[T(t),t]));for(const t of e){const l=t.testNumber;for(const n of a){const u=new Map;for(const h of n){const g=[];for(const p of h.dieKeys){const b=r.get(p);b&&g.push(b)}u.set(h.key,g)}const f=[];for(const h of n){const g=u.get(h.key),p=[];for(const[M,R]of u)if(M!==h.key)for(const P of R)p.push(P);const b=M=>M.testValues?.[l]!==void 0,y=M=>{const R=M.testValues?.[l];return R===void 0?!1:t.limitLow!==void 0&&R<t.limitLow||t.limitHigh!==void 0&&R>t.limitHigh},z=g.filter(b),w=p.filter(b);if(z.length<o.minimumSampleSize||w.length<o.minimumSampleSize)continue;const m=z.filter(y).length,S=w.filter(y).length,v=m/z.length,D=S/w.length,$=v-D,k=H(m,z.length,S,w.length);f.push({id:`specLimit:${l}:${h.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:l,label:t.name,unit:t.unit},comparison:{family:h.family,left:h.label,right:I(h.family)},effect:{direction:$===0?"different":$>0?"higher":"lower",absoluteDelta:$,relativeDelta:D===0?void 0:$/D,effectSize:$},stats:{method:"two-proportion-z",pValue:k,sampleSizeLeft:z.length,sampleSizeRight:w.length},summary:`${h.label} spec-fail rate for ${t.name} is ${(Math.abs($)*100).toFixed(1)} pp ${$>0?"higher":"lower"} than the rest of the wafer`,highlight:{kind:"region",regionFamily:h.family,regionKeys:[h.key],dieKeys:[...h.dieKeys]}})}ie(f),c.push(...f.filter(h=>{const g=h.stats.adjustedPValue??h.stats.pValue??1,p=Math.abs(h.effect.absoluteDelta??0),b=Math.abs(h.effect.relativeDelta??0);return g<=o.significanceLevel&&(p>=o.minimumEffectSize||b>=o.minimumRelativeEffect)}).map(h=>({...h,severity:oe(h.stats.adjustedPValue??h.stats.pValue??1,h.effect.effectSize??0)})))}}return c}function j(s){return s.highlight.regionKeys??[]}function et(s){const a=s.variable.bin??s.variable.index??"";return`${s.comparison.family}\0${s.variable.kind}\0${a}\0${s.effect.direction}`}function Y(s,a){const i=j(s)[0]??"",o=q(i);return o.family==="ring"?o.ring??0:o.family==="sector"?he(a).indexOf(o.sector??""):0}function tt(s,a,i){if(a==="quadrant"){const r=new Map;for(const u of s){const f=q(j(u)[0]??"").quadrant;f&&r.set(f,u)}const t=[...r.keys()],l=new Set,n=[];for(const u of t){if(l.has(u))continue;const f=[],h=[u];for(l.add(u);h.length;){const g=h.pop();f.push(g);for(const p of t)!l.has(p)&&je(g,p)&&(l.add(p),h.push(p))}n.push(f.map(g=>r.get(g)))}return n}const o=[...s].sort((r,t)=>Y(r,i)-Y(t,i)),e=[];let c=[];for(const r of o){if(c.length===0){c.push(r);continue}const t=Y(c[c.length-1],i);Y(r,i)-t===1?c.push(r):(e.push(c),c=[r])}if(c.length&&e.push(c),a==="sector"&&e.length>1){const t=he(i).length,l=Y(e[0][0],i),n=Y(e[e.length-1][e[e.length-1].length-1],i);if(l===0&&n===t-1){const u=e.pop();e[0]=[...u,...e[0]]}}return e}function nt(s,a){const i=s.map(r=>q(j(r)[0]??"").ring).filter(r=>r!==void 0).sort((r,t)=>r-t),o=i[0],e=i[i.length-1];let c="";return o===e&&o===1&&a>1?c=" (core)":o===e&&o===a&&a>1&&(c=" (edge)"),o===e?`Ring ${o}${c}`:`Rings ${o}\u2013${e}`}function it(s,a){const i=s.map(o=>q(j(o)[0]??"").sector??"");return i.length===1?`Sector ${i[0]}`:`Sectors ${i[0]}\u2013${i[i.length-1]}`}function st(s){const a=s.map(i=>q(j(i)[0]??"").quadrant??"");return a.length===1?a[0]:a.length===2?`Quadrants ${a[0]} & ${a[1]}`:`Quadrants ${a.slice(0,-1).join(", ")} & ${a[a.length-1]}`}function ce(s){return[...new Set(s)]}function ot(s,a){const i=s[0],o=i.comparison.family,e=i.variable.kind,c=ce(s.flatMap(m=>m.highlight.dieKeys??[])),r=ce(s.flatMap(j)),t=new Set(c),l=o==="ring"?nt(s,a.ringCount):o==="sector"?it(s,a.sectorCount):st(s),n=e==="softBin"?a.softEligibleDies:e==="test"?a.testDies:a.eligibleDies,u=n.filter(m=>t.has(T(m))),f=n.filter(m=>!t.has(T(m)));let h,g,p,b,y;if(e==="test"){const m=i.variable.index,S=d=>d.testValues?.[m],v=u.map(S).filter(d=>d!==void 0),D=f.map(S).filter(d=>d!==void 0),{pValue:$,effectSize:k,delta:M}=Je(v,D),R=J(D),P=R!==0?M/Math.abs(R):void 0;h={direction:M===0?"different":M>0?"higher":"lower",absoluteDelta:M,relativeDelta:P,effectSize:k},g={method:"welch-z-approx",pValue:$,sampleSizeLeft:v.length,sampleSizeRight:D.length},p=oe($,k),b=$e(l,i.variable.label,M,P,o,i.variable.unit),y=`test:${m}`}else if(e==="yield"){const m=new Set(a.passBins),S=u.filter(B=>B.hbin!==void 0),v=f.filter(B=>B.hbin!==void 0),D=S.length,$=v.length,k=S.filter(B=>m.has(B.hbin)).length,M=v.filter(B=>m.has(B.hbin)).length,R=D>0?k/D:0,P=$>0?M/$:0,d=R-P,N=H(k,D,M,$);h={direction:d===0?"different":d>0?"higher":"lower",absoluteDelta:d,relativeDelta:P===0?void 0:d/P,effectSize:d},g={method:"two-proportion-z",pValue:N,sampleSizeLeft:D,sampleSizeRight:$},p=se(N,d,h.relativeDelta),b=Se(l,d,o),y="yield"}else{const m=i.variable.bin,S=d=>e==="softBin"?d.sbin:d.hbin,v=u.filter(d=>S(d)===m).length,D=f.filter(d=>S(d)===m).length,$=v/u.length,k=D/f.length,M=$-k,R=H(v,u.length,D,f.length);h={direction:M===0?"different":M>0?"higher":"lower",absoluteDelta:M,relativeDelta:k===0?void 0:M/k,effectSize:M},g={method:"two-proportion-z",pValue:R,sampleSizeLeft:u.length,sampleSizeRight:f.length},p=se(R,M,h.relativeDelta);const P=e==="softBin"?a.sbinDefs:a.hbinDefs;b=we(l,ve(m,P,e==="softBin"?"SBin":"HBin"),M,o),y=`${e}:${m}`}const z=ce(r.map(m=>m.replace(`${o}:`,""))).join("-"),w=e==="hardBin"||e==="softBin"?{kind:"bin",bin:i.variable.bin,regionKeys:r,dieKeys:c}:{kind:"region",regionFamily:o,regionKeys:r,dieKeys:c};return{...i,id:`${y}:${o}:${z}`,severity:p,comparison:{family:o,left:l,right:I(o)},effect:h,stats:g,summary:b,highlight:w,relatedIds:s.map(m=>m.id)}}function at(s,a){const i=new Set(["ring","quadrant","sector"]),o=[],e=new Map;for(const r of s){if(!i.has(r.comparison.family)){o.push(r);continue}const t=et(r),l=e.get(t)??[];l.push(r),e.set(t,l)}const c=[];for(const r of e.values()){if(r.length===1){c.push(r[0]);continue}const t=r[0].comparison.family;for(const l of tt(r,t,a.sectorCount))c.push(l.length===1?l[0]:ot(l,a))}return[...o,...c]}function rt(s){const a=new Map,i=new Map;for(const e of s){const c=T(e);e.hbin!==void 0&&e.hbin!==null&&(a.get(e.hbin)??a.set(e.hbin,new Set).get(e.hbin)).add(c),e.sbin!==void 0&&e.sbin!==null&&(i.get(e.sbin)??i.set(e.sbin,new Set).get(e.sbin)).add(c)}const o=new Set;for(const[e,c]of a)for(const[r,t]of i){if(c.size!==t.size)continue;let l=!0;for(const n of c)if(!t.has(n)){l=!1;break}l&&o.add(`${e}:${r}`)}return o}function lt(s){return`${s.comparison.family}\0${s.comparison.left}`}function ft(s,a,i){const o=(t,l)=>{t.absorbedIds=[...t.absorbedIds??[],l.id]},e=new Map;for(const t of s){const l=lt(t);(e.get(l)??e.set(l,[]).get(l)).push(t)}const c=rt(a),r=new Set;for(const t of e.values()){for(const n of t)if(!(n.variable.kind!=="hardBin"||n.variable.bin===void 0))for(const u of t){if(u.variable.kind!=="softBin"||u.variable.bin===void 0||r.has(u.id)||!c.has(`${n.variable.bin}:${u.variable.bin}`)||u.effect.direction!==n.effect.direction)continue;o(n,u),r.add(u.id);const f=n.variable.label.replace(/^HBin\s+/,""),h=u.variable.label.replace(/^SBin\s+/,""),g=f===h?`HBin and SBin ${f} (same dies)`:`${n.variable.label} and ${u.variable.label} (same dies)`;n.summary=n.summary.replace(n.variable.label,g),n.variable.label=g;break}if(i.length!==1)continue;const l=t.find(n=>n.variable.kind==="yield");if(l){for(const n of t)if(!(n===l||r.has(n.id)||!((n.variable.kind==="hardBin"||n.variable.kind==="softBin")&&n.variable.bin===i[0]))){o(l,n),r.add(n.id);for(const f of n.absorbedIds??[])l.absorbedIds=[...l.absorbedIds??[],f]}}}}export function analyzeWaferMap(s,a={}){const{resolved:i,warnings:o}=He(a),e=Ce(s),c={...i,passBins:e.passBins??x.passBins,ringCount:e.ringCount??x.ringCount},r=e.isLotStack,t=e.aggrMethod,l=!r||t==="mode"||t==="countBin"||t==="percent",n=e.dies.filter(d=>Ye(d)),u=n.filter(ge),f=Ie(c,n.length),h=e.dies.filter(d=>te(d)),g=h.filter(ge),p=Ke(g,e.wafer,f.ringCount),b=Le(g,e.wafer,f.ringCount),y=Ee(g,e.reticleConfig),z=xe(h,!1),w=Te(g,e.wafer,f.sectorCount),m=[];l&&m.push(...W(n,p,f.passBins,f),...W(n,b,f.passBins,f),...W(n,y,f.passBins,f),...W(n,z,f.passBins,f),...W(n,w,f.passBins,f)),l&&m.push(...L(n,p,"hard",e.hbinDefs,"hardBin",f),...L(n,b,"hard",e.hbinDefs,"hardBin",f),...L(n,y,"hard",e.hbinDefs,"hardBin",f),...L(n,z,"hard",e.hbinDefs,"hardBin",f),...L(n,w,"hard",e.hbinDefs,"hardBin",f));{const d=n.filter(N=>N.sbin!==void 0);m.push(...L(d,p,"soft",e.sbinDefs,"softBin",f),...L(d,b,"soft",e.sbinDefs,"softBin",f),...L(d,y,"soft",e.sbinDefs,"softBin",f),...L(d,z,"soft",e.sbinDefs,"softBin",f),...L(d,w,"soft",e.sbinDefs,"softBin",f))}const S=[...o];let v;if(f.enableTestValueAnalysis){const d=U(n,p,e.testDefs,f),N=U(n,b,e.testDefs,f),B=U(n,y,e.testDefs,f),V=U(n,z,e.testDefs,f),F=U(n,w,e.testDefs,f);m.push(...d.findings,...N.findings,...B.findings,...V.findings,...F.findings),d.warning&&S.push(d.warning),v=d.activeTestNumbers,m.push(...Ze(n,[p,b,y,z,w],e.testDefs,f)),m.push(...Q(n,p,e.testDefs,f),...Q(n,b,e.testDefs,f),...Q(n,y,e.testDefs,f),...Q(n,z,e.testDefs,f),...Q(n,w,e.testDefs,f))}{const d=_e(r,l,e.testDefs);(!r||l||d!==void 0)&&m.push(...Ae(u,e.wafer,{...f,isFailingDie:d}))}const D=at(m,{eligibleDies:n,softEligibleDies:n.filter(d=>d.sbin!==void 0),testDies:n,passBins:f.passBins,ringCount:f.ringCount,sectorCount:f.sectorCount,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs});m.length=0,m.push(...D);let $;if(l){const d=Oe(u,e.wafer,{passBins:f.passBins,ringCount:f.ringCount});if($=d??void 0,d!==null&&d.pattern!=="random"&&d.pattern!=="none"){let Fe=function(C){const E=j(C).map(K=>q(K).ring).filter(K=>K!==void 0);if(E.length===0)return!0;const ue=E.some(K=>K===Be),de=E.some(K=>K===1);return X==="edge-ring"||X==="edge-local"?ue:X==="center"?de:X==="donut"?!ue&&!de:!0};const B={center:"Center cluster",donut:"Donut","edge-ring":"Edge-ring","edge-local":"Edge-local",scratch:"Scratch","near-full":"Near-full"}[d.pattern]??d.pattern,V=d.confidence==="high"?"unusual":d.confidence==="medium"?"notable":"info",F=d.features,A=C=>`${(C*100).toFixed(0)}%`,G=d.pattern==="edge-ring"||d.pattern==="edge-local"?`${A(F.edgeRdd)} of edge dies failing`:d.pattern==="center"||d.pattern==="donut"?`centroid at ${A(F.centroidDistNorm)} of wafer radius from centre`:d.pattern==="scratch"?`linear score ${F.linearScore.toFixed(2)}, eccentricity ${F.eccentricity.toFixed(2)}`:`${A(F.globalRdd)} of dies failing`,_=n.filter(C=>{const E=C.hbin??C.sbin;return E!==void 0&&!new Set(f.passBins).has(E)}),Me={"edge-ring":["ring","edge-arc"],"edge-local":["edge-arc","sector","quadrant"],center:["ring","cluster"],donut:["ring"],scratch:["cluster","sector","quadrant"],"near-full":["ring"]},Re=new Set(Me[d.pattern]??[]),X=d.pattern,Be=f.ringCount,ee=[];for(const C of m){if(!Re.has(C.comparison.family)||C.comparison.family==="ring"&&!Fe(C))continue;ee.push(C.id);const E=C.comparison.family==="ring"||C.comparison.family==="quadrant"||C.comparison.family==="sector";V!=="info"&&E&&(C.severity="info")}m.push({id:`spatial-pattern:${d.pattern}`,level:"wafer",severity:V,variable:{kind:"spatialPattern",label:B},comparison:{family:"spatial-pattern",left:B,right:"Wafer"},effect:{direction:"different",effectSize:F.globalRdd},stats:{method:"geometry",sampleSizeLeft:_.length,sampleSizeRight:n.length},summary:`Spatial pattern: ${B.toLowerCase()} (${d.confidence} confidence) \u2014 ${G}${d.note?` [${d.note}]`:""}`,highlight:{kind:"dies",dieKeys:_.map(C=>T(C))},relatedIds:ee.length>0?ee:void 0})}}m.sort((d,N)=>{const B=d.stats.adjustedPValue??d.stats.pValue??1,V=N.stats.adjustedPValue??N.stats.pValue??1;return B!==V?B-V:Math.abs(N.effect.absoluteDelta??0)-Math.abs(d.effect.absoluteDelta??0)});const k=We(e.dies,n.length,e.yield.yieldPercent);r&&(k.isLotStack=!0,t&&(k.aggregationMethod=t));const M=Qe(e.dies,e.testDefs);M&&(k.testSpecYield=M);const R=computeFunctionalYield(e.dies,e.testDefs);if(R&&(k.functionalYield=R),$&&(k.spatialPattern=$),v===void 0&&f.computePerTestStats){const d=ze(n,f.testNumbers,e.testDefs);d.warning&&S.push(d.warning),v=d.testNumbers}if(v?.length){const d=Ue(e.dies,v,e.testDefs,f.minimumSampleSize);d&&(k.perTestStats=d);const N=Ne([{dies:e.dies}],e.testDefs,v);N&&(k.capability=N)}Object.assign(k,Pe(e.dies,e.testDefs));const P=Ve([{dies:e.dies,wafer:e.wafer,passBins:f.passBins,ringCount:f.ringCount}]);return P&&(k.regionYield=P),S.length>0&&(k.warnings=S),ft(m,n,f.passBins),{level:"wafer",hasNotableFindings:m.some(d=>d.severity==="notable"||d.severity==="unusual"),findings:m,wafer:e.wafer.metadata??void 0,stats:k}}
1
+ import{normalizeInput as Le}from"./normalizeInput.js";import{computeCapability as Ee,computeTestFlagYield as Ke,computeRegionYield as Te}from"./summaryFigures.js";import{isYieldEligibleDie as ae,getDieKey as T,isPositionedDie as be}from"../core/dies.js";import{getTestPassStatus as ee,isParametricTest as q}from"../renderer/buildWaferMap.js";import{markedTestLabel as x,derivedFields as te}from"../renderer/testLabel.js";import{buildQuadrantRegions as xe,buildReticlePositionRegions as je,buildRingRegions as Ae,buildSectorRegions as Oe,buildTestSiteRegions as He,sectorCompassNames as ye,areQuadrantsAdjacent as qe,parseRegionKey as I}from"./regions.js";import{buildClusterFindings as Ie}from"./clusterDetection.js";import{classifyPattern as Ye}from"./patternClassification.js";import{normalCdf as Se}from"./math.js";import{quantile as re}from"./math.js";import{mean as ne,clamp01 as we}from"../core/utils.js";const _e={significanceLevel:{min:1e-6,max:1},minimumEffectSize:{min:0,max:1},minimumRelativeEffect:{min:0,max:Number.MAX_SAFE_INTEGER}},ve=[4,8,16,32];function We(s){const a=Object.fromEntries(Object.entries(s).filter(([,t])=>t!==void 0)),i={...j,...a},o=[],e=[];for(const t of["significanceLevel","minimumEffectSize","minimumRelativeEffect"]){const{min:f,max:n}=_e[t],u=i[t];if(typeof u!="number"||!Number.isFinite(u)){e.push(`${t}=${String(u)} is not a finite number (using ${j[t]})`),i[t]=j[t];continue}const l=Math.min(n,Math.max(f,u));l!==u&&(e.push(`${t}=${u} is outside ${f}\u2013${n} (using ${l})`),i[t]=l)}if(ve.includes(i.sectorCount)||(e.push(`sectorCount=${i.sectorCount} is not one of ${ve.join(", ")} (using ${j.sectorCount})`),i.sectorCount=j.sectorCount),e.length){const t=`analyzeWaferMap: ${e.join("; ")}. The analysis ran with the corrected values.`;console.warn(`[wafermap] ${t}`),o.push({code:"analysis-option-corrected",message:t,severity:"warning"})}const c=["passBins","ringCount"].filter(t=>a[t]!==void 0);if(c.length){const t=`analyzeWaferMap: ${c.join(" and ")} ${c.length>1?"are":"is"} no longer an analysis option and was ignored. Set ${c.length>1?"them":"it"} on buildWaferMap; the analysis used the value the map was built with.`;console.warn(`[wafermap] ${t}`),o.push({code:"analysis-option-corrected",message:t,severity:"warning"})}const r=["enableYieldAnalysis","enableHardBinAnalysis","enableSoftBinAnalysis","enableReticlePositionAnalysis","enableTestSiteAnalysis","enableClusterAnalysis","enableAngularAnalysis","enablePatternClassification"].filter(t=>a[t]!==void 0);if(r.length){const t=`analyzeWaferMap: ${r.join(", ")} ${r.length>1?"are":"is"} no longer an option and was ignored: every analysis now runs. Filter the findings (filterFindings) to show fewer.`;console.warn(`[wafermap] ${t}`),o.push({code:"analysis-option-corrected",message:t,severity:"warning"})}return{resolved:i,warnings:o}}const j={ringCount:4,passBins:[1],significanceLevel:.05,minimumEffectSize:.2,minimumRelativeEffect:1,minimumSampleSize:5,enableTestValueAnalysis:!1,computePerTestStats:!1,sectorCount:8,minimumClusterSize:5};function Qe(s,a){const i={...s};return i.minimumClusterSize=Math.max(5,Math.round(a*.003)),i}function Ue(s){return ae(s)?s.hbin!==void 0||s.sbin!==void 0||s.testValues!==void 0&&Object.keys(s.testValues).length>0:!1}function Ge(s,a,i){if(!s||a)return;const o=(i??[]).filter(e=>q(e)&&(e.limitLow!==void 0||e.limitHigh!==void 0));if(o.length!==0)return e=>{for(const c of o){const r=c.testNumber,t=e.testValues?.[r];if(t!==void 0&&(c.limitLow!==void 0&&t<c.limitLow||c.limitHigh!==void 0&&t>c.limitHigh))return!0}return!1}}function Xe(s,a,i){const o=new Set,e=new Set,c=new Set,r=new Map,t=new Map;for(const f of s){if(f.testValues)for(const n of Object.keys(f.testValues))o.add(Number(n));if(f.testPass)for(const n of Object.keys(f.testPass))o.add(Number(n));f.hbin!==void 0&&e.add(f.hbin),f.sbin!==void 0&&c.add(f.sbin),ae(f)&&(f.hbin!==void 0&&r.set(f.hbin,(r.get(f.hbin)??0)+1),f.sbin!==void 0&&t.set(f.sbin,(t.get(f.sbin)??0)+1))}return{totalDies:s.length,analyzedDies:a,excludedDies:s.length-a,yieldPercent:i,testsConsidered:[...o].sort((f,n)=>f-n),hardBinsConsidered:[...e].sort((f,n)=>f-n),softBinsConsidered:[...c].sort((f,n)=>f-n),...r.size?{hardBinCounts:Object.fromEntries(r)}:{},...t.size?{softBinCounts:Object.fromEntries(t)}:{}}}function Je(s,a){if(!a?.length)return;const i=a.filter(e=>q(e)&&(e.limitLow!==void 0||e.limitHigh!==void 0));if(!i.length)return;const o=[];for(const e of i){const c=e.testNumber;let r=0,t=0,f=0,n=0;for(const u of s){if(u.partial||u.edgeExcluded)continue;const l=u.testValues?.[c];l!==void 0&&(n++,e.limitLow!==void 0&&l<e.limitLow?t++:e.limitHigh!==void 0&&l>e.limitHigh?f++:r++)}o.push({testNumber:c,label:e.name,passDies:r,failLowDies:t,failHighDies:f,totalDies:n,yieldPercent:n>0?r/n*100:null})}return o.length?o:void 0}export function computeFunctionalYield(s,a){if(!a?.length)return;const i=a.filter(e=>!q(e));if(!i.length)return;const o=[];for(const e of i){const c=e.testNumber;let r=0,t=0;for(const n of s){if(n.partial||n.edgeExcluded)continue;const u=ee(n,c,e);u!==void 0&&(u?r++:t++)}const f=r+t;o.push({testNumber:c,label:x(e,c),...te(e),passDies:r,failDies:t,totalDies:f,passRatePercent:f>0?r/f*100:null})}return o.length?o:void 0}function Ze(s,a,i,o){const e=[];for(const c of a){const r=[];for(const l of s){if(l.partial||l.edgeExcluded)continue;const h=l.testValues?.[c];h!==void 0&&Number.isFinite(h)&&r.push(h)}if(r.length<o)continue;const t=ne(r),f=Math.sqrt(ge(r,t)),n=Float64Array.from(r);n.sort();const u=i?.find(l=>l.testNumber===c)?.name??String(c);e.push({testNumber:c,label:u,count:r.length,min:n[0],max:n[n.length-1],mean:t,stddev:f,median:re(n,.5),q1:re(n,.25),q3:re(n,.75)})}return e.length?e:void 0}function A(s,a,i,o){const e=(s+i)/(a+o),c=e*(1-e)*(1/a+1/o);if(!Number.isFinite(c)||c<=0)return 1;const r=(s/a-i/o)/Math.sqrt(c);return we(2*(1-Se(Math.abs(r))))}function fe(s){const a=new Map;for(const i of s){const o=`${i.variable.kind}:${i.comparison.family}`,e=a.get(o)??[];e.push(i),a.set(o,e)}for(const i of a.values()){const o=[...i].filter(r=>r.stats.pValue!==void 0).sort((r,t)=>(r.stats.pValue??1)-(t.stats.pValue??1)),e=o.length;let c=1;for(let r=e-1;r>=0;r--){const t=o[r],f=t.stats.pValue??1,n=Math.min(1,Math.min(c,f*e/(r+1)));t.stats.adjustedPValue=n,c=n}}return s}function ie(s,a,i){const o=Math.abs(a),e=i!==void 0?Math.abs(i):0;return s<=.01&&(o>=.3||e>=2.5)?"unusual":s<=.05&&(o>=.2||e>=1.5)?"notable":"info"}function le(s,a){return s<=.01&&Math.abs(a)>=.5?"unusual":s<=.05&&Math.abs(a)>=.15?"notable":"info"}function se(s){return s==="reticle-position"?"other reticle positions":s==="test-site"?"other test sites":"the rest of the map"}function Y(s){return s==="reticle-position"?"Other reticle positions":s==="test-site"?"Other test sites":"Rest of map"}function $e(s,a,i){const o=(Math.abs(a)*100).toFixed(1);return`${s} yield is ${o} percentage points ${a>0?"higher":"lower"} than ${se(i)}`}function ce(s,a){return a==="quadrant"&&!s.startsWith("Quadrant")?`quadrant ${s}`:s}function De(s){return/^(Rings|Sectors|Quadrants)\b/.test(s)?"have":"has"}function ke(s,a,i,o){const e=ce(s,o),c=(Math.abs(i)*100).toFixed(1);return`${e} ${De(e)} ${a} occurrence ${c} percentage points ${i>0?"higher":"lower"} than ${se(o)}`}function ze(s,a,i,o){const e=ce(s,o),c=(Math.abs(i)*100).toFixed(1);return`${e} ${De(e)} ${a} pass rate ${c} percentage points ${i>0?"higher":"lower"} than ${se(o)}`}function Me(s,a,i,o,e,c){const r=ce(s,e),t=se(e),f=i>0?"higher":"lower";if(o!==void 0&&Number.isFinite(o)){const u=(Math.abs(o)*100).toFixed(1);return`${r} mean ${a} is ${u}% ${f} than ${t}`}const n=c?` ${c}`:"";return`${r} mean ${a} is ${Math.abs(i).toPrecision(3)}${n} ${f} than ${t}`}function Re(s,a,i){const o=a?.find(e=>e.bin===s);return o?.name?`${i} ${s} (${o.name})`:`${i} ${s}`}function et(s,a){const i=a?.find(o=>o.testNumber===s);return{label:x(i,s),unit:i?.unit,...te(i)}}function ue(s,a){const i=new Map(s.map(e=>[T(e),e])),o=new Map;for(const e of a){const c=[];for(const r of e.dieKeys){const t=i.get(r);t&&c.push(t)}o.set(e.key,c)}return o}function de(s,a){return fe(s),s.filter(i=>{const o=i.stats.adjustedPValue??i.stats.pValue??1,e=Math.abs(i.effect.absoluteDelta??0),c=Math.abs(i.effect.relativeDelta??0);return o<=a.significanceLevel&&(e>=a.minimumEffectSize||c>=a.minimumRelativeEffect)}).map(i=>({...i,severity:ie(i.stats.adjustedPValue??i.stats.pValue??1,i.effect.absoluteDelta??0,i.effect.relativeDelta)}))}function W(s,a,i,o){const e=new Set(i),c=ue(s,a),r=new Map,t=new Map;for(const[n,u]of c){let l=0,h=0;for(const g of u)g.hbin!==void 0&&(h++,e.has(g.hbin)&&l++);r.set(n,l),t.set(n,h)}const f=[];for(const n of a){const u=t.get(n.key)??0,l=r.get(n.key)??0;let h=0,g=0;for(const[k,m]of t)k!==n.key&&(h+=m,g+=r.get(k)??0);if(u<o.minimumSampleSize||h<o.minimumSampleSize)continue;const p=l/u,b=g/h,y=p-b,M=A(l,u,g,h);f.push({id:`yield:${n.key}`,level:"wafer",severity:"info",variable:{kind:"yield",label:"Yield"},comparison:{family:n.family,left:n.label,right:Y(n.family)},effect:{direction:y===0?"different":y>0?"higher":"lower",absoluteDelta:y,relativeDelta:b===0?void 0:y/b,effectSize:y},stats:{method:"two-proportion-z",pValue:M,sampleSizeLeft:u,sampleSizeRight:h},summary:$e(n.label,y,n.family),highlight:{kind:"region",regionFamily:n.family,regionKeys:[n.key],dieKeys:[...n.dieKeys]}})}return de(f,o)}function L(s,a,i,o,e,c){const r=g=>i==="soft"?g.sbin:g.hbin,t=[...new Set(s.map(r).filter(g=>g!==void 0))].sort((g,p)=>g-p),f=ue(s,a),n=new Map,u=new Map;for(const[g,p]of f){const b=new Map;for(const y of p){const M=r(y);M!==void 0&&b.set(M,(b.get(M)??0)+1)}n.set(g,b),u.set(g,p.length)}const l=[],h=e==="hardBin"?"HBin":"SBin";for(const g of a){const p=u.get(g.key)??0,b=n.get(g.key);let y=0;const M=new Map;for(const[k,m]of n)if(k!==g.key){y+=u.get(k)??0;for(const[S,$]of m)M.set(S,(M.get(S)??0)+$)}if(!(p<c.minimumSampleSize||y<c.minimumSampleSize))for(const k of t){const m=b.get(k)??0,S=M.get(k)??0,$=m/p,w=S/y,v=$-w,D=A(m,p,S,y),z=Re(k,o,h);l.push({id:`${e}:${k}:${g.key}`,level:"wafer",severity:"info",variable:{kind:e,bin:k,label:z},comparison:{family:g.family,left:g.label,right:Y(g.family)},effect:{direction:v===0?"different":v>0?"higher":"lower",absoluteDelta:v,relativeDelta:w===0?void 0:v/w,effectSize:v},stats:{method:"two-proportion-z",pValue:D,sampleSizeLeft:p,sampleSizeRight:y},summary:ke(g.label,z,v,g.family),highlight:{kind:"bin",bin:k,regionKeys:[g.key],dieKeys:[...g.dieKeys]}})}}return de(l,c)}function Q(s,a,i,o){const e=(i??[]).filter(u=>!q(u));if(!e.length)return[];const c=e.map(u=>u.testNumber),r=ue(s,a),t=new Map,f=new Map;for(const[u,l]of r){const h=new Uint32Array(e.length),g=new Uint32Array(e.length);for(const p of l)for(let b=0;b<e.length;b++){const y=ee(p,c[b],e[b]);y!==void 0&&(g[b]++,y&&h[b]++)}t.set(u,h),f.set(u,g)}const n=[];for(const u of a){const l=t.get(u.key),h=f.get(u.key);for(let g=0;g<e.length;g++){let p=0,b=0;for(const[v,D]of f)v!==u.key&&(b+=D[g],p+=t.get(v)[g]);const y=h[g],M=l[g];if(y<o.minimumSampleSize||b<o.minimumSampleSize)continue;const k=M/y,m=p/b,S=k-m,$=A(M,y,p,b),w=c[g];n.push({id:`functional:${w}:${u.key}`,level:"wafer",severity:"info",variable:{kind:"functionalTest",index:w,label:`${x(e[g],w)} pass rate`,...te(e[g])},comparison:{family:u.family,left:u.label,right:Y(u.family)},effect:{direction:S===0?"different":S>0?"higher":"lower",absoluteDelta:S,relativeDelta:m===0?void 0:S/m,effectSize:S},stats:{method:"two-proportion-z",pValue:$,sampleSizeLeft:y,sampleSizeRight:b},summary:ze(u.label,x(e[g],w),S,u.family),highlight:{kind:"region",regionFamily:u.family,regionKeys:[u.key],dieKeys:[...u.dieKeys]}})}}return de(n,o)}function ge(s,a){return s.length<2?0:s.reduce((i,o)=>i+(o-a)**2,0)/(s.length-1)}function Be(s,a,i,o,e,c){const r=Math.sqrt(i/s+c/o),t=a-e;if(!Number.isFinite(r)||r===0)return{pValue:1,effectSize:0,delta:t};const f=t/r,n=Math.sqrt(Math.max(0,(i+c)/2)),u=n===0?t:t/n;return{pValue:we(2*(1-Se(Math.abs(f)))),effectSize:u,delta:t}}function tt(s,a){const i=ne(s),o=ne(a);return Be(s.length,i,ge(s,i),a.length,o,ge(a,o))}const Fe=250;function Ne(s,a,i){const o=r=>r.filter(t=>q(i?.find(f=>f.testNumber===t)));if(a)return{testNumbers:o(a.slice().sort((r,t)=>r-t))};const e=new Set;let c=!1;e:for(const r of s)for(const t of Object.keys(r.testValues??{})){const f=Number(t);if(!e.has(f)&&(e.add(f),e.size>Fe)){c=!0;break e}}if(c){const r=`Test-value analysis was skipped: more than ${Fe} tests were found in the die data, so no test findings were computed. Pass testNumbers: [...] to analyse specific tests.`;return console.warn(`[wafermap] analyzeWaferMap: ${r}`),{testNumbers:[],warning:{code:"test-count-capped",message:r,severity:"warning"}}}return{testNumbers:o([...e].sort((r,t)=>r-t))}}function U(s,a,i,o){const e=Ne(s,o.testNumbers,i);if(e.warning)return{findings:[],warning:e.warning};const c=e.testNumbers,r=new Map;for(let p=0;p<a.length;p++)for(const b of a[p].dieKeys)r.set(b,p);const t=a.length,f=[],n=[];for(const p of s){const b=r.get(T(p));b!==void 0&&(f.push(p),n.push(b))}const u=[],l=new Float64Array(t),h=new Float64Array(t),g=new Float64Array(t);for(const p of c){l.fill(0),h.fill(0),g.fill(0);let b=0,y=0,M=0,k;for(let m=0;m<f.length;m++){const $=f[m].testValues?.[p];if($===void 0)continue;k===void 0&&(k=$);const w=$-k,v=n[m];l[v]+=1,h[v]+=w,g[v]+=w*w,b+=1,y+=w,M+=w*w}if(k!==void 0)for(let m=0;m<t;m++){const S=l[m],$=b-S;if(S<o.minimumSampleSize||$<o.minimumSampleSize)continue;const w=h[m]/S,D=(y-h[m])/$,z=w+k,R=D+k,F=S>1?Math.max(0,g[m]-S*w*w)/(S-1):0,d=$>1?Math.max(0,M-g[m]-$*D*D)/($-1):0,{pValue:N,effectSize:B,delta:V}=Be(S,z,F,$,R,d),P=a[m],{label:H,unit:G,...X}=et(p,i),J=R!==0?V/Math.abs(R):void 0;u.push({id:`test:${p}:${P.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:p,label:H,unit:G,...X},comparison:{family:P.family,left:P.label,right:Y(P.family)},effect:{direction:V===0?"different":V>0?"higher":"lower",absoluteDelta:V,relativeDelta:J,effectSize:B},stats:{method:"welch-z-approx",pValue:N,sampleSizeLeft:S,sampleSizeRight:$},summary:Me(P.label,H,V,J,P.family,G),highlight:{kind:"region",regionFamily:P.family,regionKeys:[P.key],dieKeys:[...P.dieKeys]}})}}return fe(u),{activeTestNumbers:c,findings:u.filter(p=>{const b=p.stats.adjustedPValue??p.stats.pValue??1,y=Math.abs(p.effect.effectSize??0);return b<=o.significanceLevel&&y>=o.minimumEffectSize}).map(p=>({...p,severity:le(p.stats.adjustedPValue??p.stats.pValue??1,p.effect.effectSize??0)}))}}function nt(s,a,i,o){if(!i?.length)return[];const e=i.filter(t=>q(t)&&(t.limitLow!==void 0||t.limitHigh!==void 0));if(!e.length)return[];const c=[],r=new Map(s.map(t=>[T(t),t]));for(const t of e){const f=t.testNumber;for(const n of a){const u=new Map;for(const h of n){const g=[];for(const p of h.dieKeys){const b=r.get(p);b&&g.push(b)}u.set(h.key,g)}const l=[];for(const h of n){const g=u.get(h.key),p=[];for(const[z,R]of u)if(z!==h.key)for(const F of R)p.push(F);const b=z=>z.testValues?.[f]!==void 0,y=z=>{const R=z.testValues?.[f];return R===void 0?!1:t.limitLow!==void 0&&R<t.limitLow||t.limitHigh!==void 0&&R>t.limitHigh},M=g.filter(b),k=p.filter(b);if(M.length<o.minimumSampleSize||k.length<o.minimumSampleSize)continue;const m=M.filter(y).length,S=k.filter(y).length,$=m/M.length,w=S/k.length,v=$-w,D=A(m,M.length,S,k.length);l.push({id:`specLimit:${f}:${h.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:f,label:x(t,f),unit:t.unit,...te(t)},comparison:{family:h.family,left:h.label,right:Y(h.family)},effect:{direction:v===0?"different":v>0?"higher":"lower",absoluteDelta:v,relativeDelta:w===0?void 0:v/w,effectSize:v},stats:{method:"two-proportion-z",pValue:D,sampleSizeLeft:M.length,sampleSizeRight:k.length},summary:`${h.label} spec-fail rate for ${x(t,f)} is ${(Math.abs(v)*100).toFixed(1)} pp ${v>0?"higher":"lower"} than the rest of the wafer`,highlight:{kind:"region",regionFamily:h.family,regionKeys:[h.key],dieKeys:[...h.dieKeys]}})}fe(l),c.push(...l.filter(h=>{const g=h.stats.adjustedPValue??h.stats.pValue??1,p=Math.abs(h.effect.absoluteDelta??0),b=Math.abs(h.effect.relativeDelta??0);return g<=o.significanceLevel&&(p>=o.minimumEffectSize||b>=o.minimumRelativeEffect)}).map(h=>({...h,severity:le(h.stats.adjustedPValue??h.stats.pValue??1,h.effect.effectSize??0)})))}}return c}function O(s){return s.highlight.regionKeys??[]}function it(s){const a=s.variable.bin??s.variable.index??"";return`${s.comparison.family}\0${s.variable.kind}\0${a}\0${s.effect.direction}`}function _(s,a){const i=O(s)[0]??"",o=I(i);return o.family==="ring"?o.ring??0:o.family==="sector"?ye(a).indexOf(o.sector??""):0}function st(s,a,i){if(a==="quadrant"){const r=new Map;for(const u of s){const l=I(O(u)[0]??"").quadrant;l&&r.set(l,u)}const t=[...r.keys()],f=new Set,n=[];for(const u of t){if(f.has(u))continue;const l=[],h=[u];for(f.add(u);h.length;){const g=h.pop();l.push(g);for(const p of t)!f.has(p)&&qe(g,p)&&(f.add(p),h.push(p))}n.push(l.map(g=>r.get(g)))}return n}const o=[...s].sort((r,t)=>_(r,i)-_(t,i)),e=[];let c=[];for(const r of o){if(c.length===0){c.push(r);continue}const t=_(c[c.length-1],i);_(r,i)-t===1?c.push(r):(e.push(c),c=[r])}if(c.length&&e.push(c),a==="sector"&&e.length>1){const t=ye(i).length,f=_(e[0][0],i),n=_(e[e.length-1][e[e.length-1].length-1],i);if(f===0&&n===t-1){const u=e.pop();e[0]=[...u,...e[0]]}}return e}function ot(s,a){const i=s.map(r=>I(O(r)[0]??"").ring).filter(r=>r!==void 0).sort((r,t)=>r-t),o=i[0],e=i[i.length-1];let c="";return o===e&&o===1&&a>1?c=" (core)":o===e&&o===a&&a>1&&(c=" (edge)"),o===e?`Ring ${o}${c}`:`Rings ${o}\u2013${e}`}function at(s,a){const i=s.map(o=>I(O(o)[0]??"").sector??"");return i.length===1?`Sector ${i[0]}`:`Sectors ${i[0]}\u2013${i[i.length-1]}`}function rt(s){const a=s.map(i=>I(O(i)[0]??"").quadrant??"");return a.length===1?a[0]:a.length===2?`Quadrants ${a[0]} & ${a[1]}`:`Quadrants ${a.slice(0,-1).join(", ")} & ${a[a.length-1]}`}function me(s){return[...new Set(s)]}function ft(s,a){const i=s[0],o=i.comparison.family,e=i.variable.kind,c=me(s.flatMap(m=>m.highlight.dieKeys??[])),r=me(s.flatMap(O)),t=new Set(c),f=o==="ring"?ot(s,a.ringCount):o==="sector"?at(s,a.sectorCount):rt(s),n=e==="softBin"?a.softEligibleDies:e==="test"?a.testDies:a.eligibleDies,u=n.filter(m=>t.has(T(m))),l=n.filter(m=>!t.has(T(m)));let h,g,p,b,y;if(e==="test"){const m=i.variable.index,S=d=>d.testValues?.[m],$=u.map(S).filter(d=>d!==void 0),w=l.map(S).filter(d=>d!==void 0),{pValue:v,effectSize:D,delta:z}=tt($,w),R=ne(w),F=R!==0?z/Math.abs(R):void 0;h={direction:z===0?"different":z>0?"higher":"lower",absoluteDelta:z,relativeDelta:F,effectSize:D},g={method:"welch-z-approx",pValue:v,sampleSizeLeft:$.length,sampleSizeRight:w.length},p=le(v,D),b=Me(f,i.variable.label,z,F,o,i.variable.unit),y=`test:${m}`}else if(e==="yield"){const m=new Set(a.passBins),S=u.filter(B=>B.hbin!==void 0),$=l.filter(B=>B.hbin!==void 0),w=S.length,v=$.length,D=S.filter(B=>m.has(B.hbin)).length,z=$.filter(B=>m.has(B.hbin)).length,R=w>0?D/w:0,F=v>0?z/v:0,d=R-F,N=A(D,w,z,v);h={direction:d===0?"different":d>0?"higher":"lower",absoluteDelta:d,relativeDelta:F===0?void 0:d/F,effectSize:d},g={method:"two-proportion-z",pValue:N,sampleSizeLeft:w,sampleSizeRight:v},p=ie(N,d,h.relativeDelta),b=$e(f,d,o),y="yield"}else if(e==="functionalTest"){const m=i.variable.index,S=a.testDefs?.find(N=>N.testNumber===m);let $=0,w=0,v=0,D=0;for(const N of u){const B=ee(N,m,S);B!==void 0&&(w++,B&&$++)}for(const N of l){const B=ee(N,m,S);B!==void 0&&(D++,B&&v++)}const z=w>0?$/w:0,R=D>0?v/D:0,F=z-R,d=A($,w,v,D);h={direction:F===0?"different":F>0?"higher":"lower",absoluteDelta:F,relativeDelta:R===0?void 0:F/R,effectSize:F},g={method:"two-proportion-z",pValue:d,sampleSizeLeft:w,sampleSizeRight:D},p=ie(d,F,h.relativeDelta),b=ze(f,x(S,m),F,o),y=`functional:${m}`}else{const m=i.variable.bin,S=d=>e==="softBin"?d.sbin:d.hbin,$=u.filter(d=>S(d)===m).length,w=l.filter(d=>S(d)===m).length,v=$/u.length,D=w/l.length,z=v-D,R=A($,u.length,w,l.length);h={direction:z===0?"different":z>0?"higher":"lower",absoluteDelta:z,relativeDelta:D===0?void 0:z/D,effectSize:z},g={method:"two-proportion-z",pValue:R,sampleSizeLeft:u.length,sampleSizeRight:l.length},p=ie(R,z,h.relativeDelta);const F=e==="softBin"?a.sbinDefs:a.hbinDefs;b=ke(f,Re(m,F,e==="softBin"?"SBin":"HBin"),z,o),y=`${e}:${m}`}const M=me(r.map(m=>m.replace(`${o}:`,""))).join("-"),k=e==="hardBin"||e==="softBin"?{kind:"bin",bin:i.variable.bin,regionKeys:r,dieKeys:c}:{kind:"region",regionFamily:o,regionKeys:r,dieKeys:c};return{...i,id:`${y}:${o}:${M}`,severity:p,comparison:{family:o,left:f,right:Y(o)},effect:h,stats:g,summary:b,highlight:k,relatedIds:s.map(m=>m.id)}}function lt(s,a){const i=new Set(["ring","quadrant","sector"]),o=[],e=new Map;for(const r of s){if(!i.has(r.comparison.family)){o.push(r);continue}const t=it(r),f=e.get(t)??[];f.push(r),e.set(t,f)}const c=[];for(const r of e.values()){if(r.length===1){c.push(r[0]);continue}const t=r[0].comparison.family;for(const f of st(r,t,a.sectorCount))c.push(f.length===1?f[0]:ft(f,a))}return[...o,...c]}function ct(s){const a=new Map,i=new Map;for(const e of s){const c=T(e);e.hbin!==void 0&&e.hbin!==null&&(a.get(e.hbin)??a.set(e.hbin,new Set).get(e.hbin)).add(c),e.sbin!==void 0&&e.sbin!==null&&(i.get(e.sbin)??i.set(e.sbin,new Set).get(e.sbin)).add(c)}const o=new Set;for(const[e,c]of a)for(const[r,t]of i){if(c.size!==t.size)continue;let f=!0;for(const n of c)if(!t.has(n)){f=!1;break}f&&o.add(`${e}:${r}`)}return o}function ut(s){return`${s.comparison.family}\0${s.comparison.left}`}function dt(s,a,i){const o=(t,f)=>{t.absorbedIds=[...t.absorbedIds??[],f.id]},e=new Map;for(const t of s){const f=ut(t);(e.get(f)??e.set(f,[]).get(f)).push(t)}const c=ct(a),r=new Set;for(const t of e.values()){for(const n of t)if(!(n.variable.kind!=="hardBin"||n.variable.bin===void 0))for(const u of t){if(u.variable.kind!=="softBin"||u.variable.bin===void 0||r.has(u.id)||!c.has(`${n.variable.bin}:${u.variable.bin}`)||u.effect.direction!==n.effect.direction)continue;o(n,u),r.add(u.id);const l=n.variable.label.replace(/^HBin\s+/,""),h=u.variable.label.replace(/^SBin\s+/,""),g=l===h?`HBin and SBin ${l} (same dies)`:`${n.variable.label} and ${u.variable.label} (same dies)`;n.summary=n.summary.replace(n.variable.label,g),n.variable.label=g;break}if(i.length!==1)continue;const f=t.find(n=>n.variable.kind==="yield");if(f){for(const n of t)if(!(n===f||r.has(n.id)||!((n.variable.kind==="hardBin"||n.variable.kind==="softBin")&&n.variable.bin===i[0]))){o(f,n),r.add(n.id);for(const l of n.absorbedIds??[])f.absorbedIds=[...f.absorbedIds??[],l]}}}}export function analyzeWaferMap(s,a={}){const{resolved:i,warnings:o}=We(a),e=Le(s),c={...i,passBins:e.passBins??j.passBins,ringCount:e.ringCount??j.ringCount},r=e.isLotStack,t=e.aggrMethod,f=!r||t==="mode"||t==="countBin"||t==="percent",n=e.dies.filter(d=>Ue(d)),u=n.filter(be),l=Qe(c,n.length),h=e.dies.filter(d=>ae(d)),g=h.filter(be),p=Ae(g,e.wafer,l.ringCount),b=xe(g,e.wafer,l.ringCount),y=je(g,e.reticleConfig),M=He(h,!1),k=Oe(g,e.wafer,l.sectorCount),m=[];f&&m.push(...W(n,p,l.passBins,l),...W(n,b,l.passBins,l),...W(n,y,l.passBins,l),...W(n,M,l.passBins,l),...W(n,k,l.passBins,l)),f&&m.push(...L(n,p,"hard",e.hbinDefs,"hardBin",l),...L(n,b,"hard",e.hbinDefs,"hardBin",l),...L(n,y,"hard",e.hbinDefs,"hardBin",l),...L(n,M,"hard",e.hbinDefs,"hardBin",l),...L(n,k,"hard",e.hbinDefs,"hardBin",l));{const d=n.filter(N=>N.sbin!==void 0);m.push(...L(d,p,"soft",e.sbinDefs,"softBin",l),...L(d,b,"soft",e.sbinDefs,"softBin",l),...L(d,y,"soft",e.sbinDefs,"softBin",l),...L(d,M,"soft",e.sbinDefs,"softBin",l),...L(d,k,"soft",e.sbinDefs,"softBin",l))}const S=[...o];let $;if(l.enableTestValueAnalysis){const d=U(n,p,e.testDefs,l),N=U(n,b,e.testDefs,l),B=U(n,y,e.testDefs,l),V=U(n,M,e.testDefs,l),P=U(n,k,e.testDefs,l);m.push(...d.findings,...N.findings,...B.findings,...V.findings,...P.findings),d.warning&&S.push(d.warning),$=d.activeTestNumbers,m.push(...nt(n,[p,b,y,M,k],e.testDefs,l)),m.push(...Q(n,p,e.testDefs,l),...Q(n,b,e.testDefs,l),...Q(n,y,e.testDefs,l),...Q(n,M,e.testDefs,l),...Q(n,k,e.testDefs,l))}{const d=Ge(r,f,e.testDefs);(!r||f||d!==void 0)&&m.push(...Ie(u,e.wafer,{...l,isFailingDie:d}))}const w=lt(m,{eligibleDies:n,softEligibleDies:n.filter(d=>d.sbin!==void 0),testDies:n,passBins:l.passBins,ringCount:l.ringCount,sectorCount:l.sectorCount,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs});m.length=0,m.push(...w);let v;if(f){const d=Ye(u,e.wafer,{passBins:l.passBins,ringCount:l.ringCount});if(v=d??void 0,d!==null&&d.pattern!=="random"&&d.pattern!=="none"){let Ve=function(C){const E=O(C).map(K=>I(K).ring).filter(K=>K!==void 0);if(E.length===0)return!0;const he=E.some(K=>K===Ce),pe=E.some(K=>K===1);return Z==="edge-ring"||Z==="edge-local"?he:Z==="center"?pe:Z==="donut"?!he&&!pe:!0};const B={center:"Center cluster",donut:"Donut","edge-ring":"Edge-ring","edge-local":"Edge-local",scratch:"Scratch","near-full":"Near-full"}[d.pattern]??d.pattern,V=d.confidence==="high"?"unusual":d.confidence==="medium"?"notable":"info",P=d.features,H=C=>`${(C*100).toFixed(0)}%`,G=d.pattern==="edge-ring"||d.pattern==="edge-local"?`${H(P.edgeRdd)} of edge dies failing`:d.pattern==="center"||d.pattern==="donut"?`centroid at ${H(P.centroidDistNorm)} of wafer radius from centre`:d.pattern==="scratch"?`linear score ${P.linearScore.toFixed(2)}, eccentricity ${P.eccentricity.toFixed(2)}`:`${H(P.globalRdd)} of dies failing`,X=n.filter(C=>{const E=C.hbin??C.sbin;return E!==void 0&&!new Set(l.passBins).has(E)}),J={"edge-ring":["ring","edge-arc"],"edge-local":["edge-arc","sector","quadrant"],center:["ring","cluster"],donut:["ring"],scratch:["cluster","sector","quadrant"],"near-full":["ring"]},Pe=new Set(J[d.pattern]??[]),Z=d.pattern,Ce=l.ringCount,oe=[];for(const C of m){if(!Pe.has(C.comparison.family)||C.comparison.family==="ring"&&!Ve(C))continue;oe.push(C.id);const E=C.comparison.family==="ring"||C.comparison.family==="quadrant"||C.comparison.family==="sector";V!=="info"&&E&&(C.severity="info")}m.push({id:`spatial-pattern:${d.pattern}`,level:"wafer",severity:V,variable:{kind:"spatialPattern",label:B},comparison:{family:"spatial-pattern",left:B,right:"Wafer"},effect:{direction:"different",effectSize:P.globalRdd},stats:{method:"geometry",sampleSizeLeft:X.length,sampleSizeRight:n.length},summary:`Spatial pattern: ${B.toLowerCase()} (${d.confidence} confidence) \u2014 ${G}${d.note?` [${d.note}]`:""}`,highlight:{kind:"dies",dieKeys:X.map(C=>T(C))},relatedIds:oe.length>0?oe:void 0})}}m.sort((d,N)=>{const B=d.stats.adjustedPValue??d.stats.pValue??1,V=N.stats.adjustedPValue??N.stats.pValue??1;return B!==V?B-V:Math.abs(N.effect.absoluteDelta??0)-Math.abs(d.effect.absoluteDelta??0)});const D=Xe(e.dies,n.length,e.yield.yieldPercent);r&&(D.isLotStack=!0,t&&(D.aggregationMethod=t));const z=Je(e.dies,e.testDefs);z&&(D.testSpecYield=z);const R=computeFunctionalYield(e.dies,e.testDefs);if(R&&(D.functionalYield=R),v&&(D.spatialPattern=v),$===void 0&&l.computePerTestStats){const d=Ne(n,l.testNumbers,e.testDefs);d.warning&&S.push(d.warning),$=d.testNumbers}if($?.length){const d=Ze(e.dies,$,e.testDefs,l.minimumSampleSize);d&&(D.perTestStats=d);const N=Ee([{dies:e.dies}],e.testDefs,$);N&&(D.capability=N)}Object.assign(D,Ke(e.dies,e.testDefs));const F=Te([{dies:e.dies,wafer:e.wafer,passBins:l.passBins,ringCount:l.ringCount}]);return F&&(D.regionYield=F),S.length>0&&(D.warnings=S),dt(m,n,l.passBins),{level:"wafer",hasNotableFindings:m.some(d=>d.severity==="notable"||d.severity==="unusual"),findings:m,wafer:e.wafer.metadata??void 0,stats:D}}
@@ -7,6 +7,10 @@ export interface CapabilityDatum {
7
7
  testNumber: number;
8
8
  label: string;
9
9
  unit?: string;
10
+ /** Computed from other tests rather than measured — see `TestCapability.derived`. */
11
+ derived?: true;
12
+ /** The expression a `derived` test was computed from, for display. */
13
+ expression?: string;
10
14
  /**
11
15
  * Whether this test has both `limitLow` and `limitHigh` defined. When
12
16
  * false, `lsl`/`usl`/`cp`/`cpk`/`pp`/`ppk` are all absent/null — there is
@@ -1 +1 @@
1
- import{isYieldEligibleDie as O}from"../core/dies.js";import{drain as C}from"../core/utils.js";import{isParametricTest as $}from"../renderer/buildWaferMap.js";import{describeSorted as E,quantile as k}from"./math.js";const L=25e4;function x(o){const e=new Map,t=new Map;for(const l of o){const n=l.testNumber;if(n===void 0||!$(l))continue;t.set(n,l);const i=l.limitLow,r=l.limitHigh;i!==void 0&&r!==void 0&&r>i&&e.set(n,{lsl:i,usl:r})}return{defByTestNumber:t,specByTest:e}}function*v(o,e,t){const l=new Map,n=[];for(const f of e.keys())l.set(f,n.length),n.push(f);const i=n.length,r=new Float64Array(i),s=new Float64Array(i),d=new Float64Array(i),w=new Float64Array(i),F=new Float64Array(i),g=new Float64Array(i),y=new Float64Array(i),b=new Float64Array(i),h=t?new Array(i):[],S=i<=64,A=Math.max(500,Math.floor(L/Math.max(1,i)));let u=0;for(const f of o){g.fill(0),y.fill(0),b.fill(0);for(const m of f.dies??[]){if(++u>=A&&(u=0,yield),!O(m))continue;const p=m.testValues;if(p){if(S){for(let c=0;c<i;c++){const a=p[n[c]];a===void 0||!Number.isFinite(a)||(r[c]++,s[c]+=a,d[c]+=a*a,g[c]++,y[c]+=a,b[c]+=a*a,t&&(h[c]??=[]).push(a))}continue}for(const c in p){const a=l.get(+c);if(a===void 0)continue;const N=p[+c];N===void 0||!Number.isFinite(N)||(r[a]++,s[a]+=N,d[a]+=N*N,g[a]++,y[a]+=N,b[a]+=N*N,t&&(h[a]??=[]).push(N))}}}for(let m=0;m<i;m++){const p=g[m];if(p<2)continue;const c=y[m]/p,a=(b[m]-p*c*c)/(p-1);w[m]+=(p-1)*Math.max(0,a),F[m]+=p-1}}const M=new Map;for(let f=0;f<i;f++){if(r[f]===0)continue;const m=n[f];M.set(m,{n:r[f],sum:s[f],sumSq:d[f],withinNumerator:w[f],withinDenominator:F[f]}),t&&h[f]&&t.set(m,h[f])}return M}function P(o,e,t){return C(v(o,e,t))}function q(o,e,t,l){const{n,sum:i,sumSq:r,withinNumerator:s,withinDenominator:d}=l,w=i/n,F=n>=2?Math.max(0,(r-n*w*w)/(n-1)):0,g=Math.sqrt(F),y=d>0?Math.sqrt(s/d):NaN;let b=null,h=null,S=null,A=null;if(t){const{lsl:u,usl:M}=t,f=M-u;S=g>0?f/(6*g):null,A=g>0?Math.min((M-w)/(3*g),(w-u)/(3*g)):null,b=Number.isFinite(y)&&y>0?f/(6*y):null,h=Number.isFinite(y)&&y>0?Math.min((M-w)/(3*y),(w-u)/(3*y)):null}return{testNumber:o,label:e.name??`Test ${o}`,unit:e.unit,hasSpec:t!==void 0,lsl:t?.lsl,usl:t?.usl,mean:w,stdOverall:g,stdWithin:y,n,cp:b,cpk:h,pp:S,ppk:A}}function B(o){return o.sort((e,t)=>e.hasSpec!==t.hasSpec?e.hasSpec?-1:1:e.hasSpec?e.ppk===null&&t.ppk===null?0:e.ppk===null?1:t.ppk===null?-1:e.ppk-t.ppk:t.stdOverall-e.stdOverall)}export function buildCapabilityData(o,e){return C(buildCapabilityDataSteps(o,e))}export function*buildCapabilityDataSteps(o,e){return(yield*pooledTestStatsSteps(o,e)).capability.slice()}const T=new Map,H=4,D=new WeakMap;let V=1;function _(o,e){const t=[];for(const n of o){const i=n.dies;if(!i){t.push(0);continue}let r=D.get(i);r===void 0&&(r=V++,D.set(i,r)),t.push(r)}const l=e.map(n=>`${n.testNumber}${n.testType??"P"}${n.limitLow??""}${n.limitHigh??""}${n.name??""}${n.unit??""}`);return`${t.join(",")}${l.join("")}`}export function clearPooledTestStatsCache(){T.clear()}function z(o,e){let t=0,l=o.length;for(;t<l;){const n=t+l>>1;o[n]<e?t=n+1:l=n}return t}function I(o,e){let t=0,l=o.length;for(;t<l;){const n=t+l>>1;o[n]<=e?t=n+1:l=n}return t}export function*pooledTestStatsSteps(o,e){const t=_(o,e),l=T.get(t);if(l)return T.delete(t),T.set(t,l),l;const n={stats:new Map,specTally:new Map,capability:[]},{defByTestNumber:i,specByTest:r}=x(e);if(i.size===0)return n;const s=new Map,d=yield*v(o,i,s),w=new Map,F=new Map,g=[];for(const b of e){yield;const h=b.testNumber;if(h===void 0)continue;const S=d.get(h);if(!S||S.n===0||!i.has(h))continue;const A=s.get(h);s.delete(h);const u=Float64Array.from(A);if(u.sort(),w.set(h,E(u)),b.limitLow!==void 0||b.limitHigh!==void 0){const p=b.limitLow!==void 0?z(u,b.limitLow):0,c=b.limitHigh!==void 0?u.length-I(u,b.limitHigh):0;F.set(h,{n:u.length,fail:p+c})}const M=r.get(h),f=q(h,b,M,S);let m;if(M){const p=M.usl-M.lsl;m=c=>(c-M.lsl)/p}else{const p=u[0],c=u[u.length-1]-p;m=c>0?a=>(a-p)/c:()=>.5}g.push({...f,min:m(u[0]),q1:m(k(u,.25)),median:m(k(u,.5)),q3:m(k(u,.75)),max:m(u[u.length-1])})}const y={stats:w,specTally:F,capability:B(g)};return T.set(t,y),T.size>H&&T.delete(T.keys().next().value),y}export function buildCapabilityFigures(o,e){const{defByTestNumber:t,specByTest:l}=x(e);if(t.size===0)return[];const n=P(o,t,null),i=[];for(const[r,s]of n)s.n!==0&&i.push(q(r,t.get(r),l.get(r),s));return B(i)}export function poolCapabilityFigures(o,e){const{defByTestNumber:t,specByTest:l}=x(e),n=new Map;for(const r of o)for(const s of r){if(!t.has(s.testNumber)||s.n===0)continue;let d=n.get(s.testNumber);d||(d={n:0,sum:0,sumSq:0,withinNumerator:0,withinDenominator:0},n.set(s.testNumber,d));const w=s.stdOverall*s.stdOverall;d.n+=s.n,d.sum+=s.n*s.mean,d.sumSq+=(s.n-1)*w+s.n*s.mean*s.mean,s.n>=2&&(d.withinNumerator+=(s.n-1)*w,d.withinDenominator+=s.n-1)}const i=[];for(const[r,s]of n)i.push(q(r,t.get(r),l.get(r),s));return B(i)}
1
+ import{isYieldEligibleDie as O}from"../core/dies.js";import{drain as B}from"../core/utils.js";import{isParametricTest as E}from"../renderer/buildWaferMap.js";import{testLabel as L,derivedFields as $}from"../renderer/testLabel.js";import{describeSorted as P,quantile as k}from"./math.js";const H=25e4;function x(o){const e=new Map,t=new Map;for(const l of o){const n=l.testNumber;if(n===void 0||!E(l))continue;t.set(n,l);const i=l.limitLow,r=l.limitHigh;i!==void 0&&r!==void 0&&r>i&&e.set(n,{lsl:i,usl:r})}return{defByTestNumber:t,specByTest:e}}function*C(o,e,t){const l=new Map,n=[];for(const f of e.keys())l.set(f,n.length),n.push(f);const i=n.length,r=new Float64Array(i),s=new Float64Array(i),d=new Float64Array(i),w=new Float64Array(i),T=new Float64Array(i),g=new Float64Array(i),y=new Float64Array(i),b=new Float64Array(i),h=t?new Array(i):[],S=i<=64,A=Math.max(500,Math.floor(H/Math.max(1,i)));let u=0;for(const f of o){g.fill(0),y.fill(0),b.fill(0);for(const m of f.dies??[]){if(++u>=A&&(u=0,yield),!O(m))continue;const p=m.testValues;if(p){if(S){for(let c=0;c<i;c++){const a=p[n[c]];a===void 0||!Number.isFinite(a)||(r[c]++,s[c]+=a,d[c]+=a*a,g[c]++,y[c]+=a,b[c]+=a*a,t&&(h[c]??=[]).push(a))}continue}for(const c in p){const a=l.get(+c);if(a===void 0)continue;const N=p[+c];N===void 0||!Number.isFinite(N)||(r[a]++,s[a]+=N,d[a]+=N*N,g[a]++,y[a]+=N,b[a]+=N*N,t&&(h[a]??=[]).push(N))}}}for(let m=0;m<i;m++){const p=g[m];if(p<2)continue;const c=y[m]/p,a=(b[m]-p*c*c)/(p-1);w[m]+=(p-1)*Math.max(0,a),T[m]+=p-1}}const M=new Map;for(let f=0;f<i;f++){if(r[f]===0)continue;const m=n[f];M.set(m,{n:r[f],sum:s[f],sumSq:d[f],withinNumerator:w[f],withinDenominator:T[f]}),t&&h[f]&&t.set(m,h[f])}return M}function V(o,e,t){return B(C(o,e,t))}function q(o,e,t,l){const{n,sum:i,sumSq:r,withinNumerator:s,withinDenominator:d}=l,w=i/n,T=n>=2?Math.max(0,(r-n*w*w)/(n-1)):0,g=Math.sqrt(T),y=d>0?Math.sqrt(s/d):NaN;let b=null,h=null,S=null,A=null;if(t){const{lsl:u,usl:M}=t,f=M-u;S=g>0?f/(6*g):null,A=g>0?Math.min((M-w)/(3*g),(w-u)/(3*g)):null,b=Number.isFinite(y)&&y>0?f/(6*y):null,h=Number.isFinite(y)&&y>0?Math.min((M-w)/(3*y),(w-u)/(3*y)):null}return{testNumber:o,label:L(e,o),unit:e.unit,...$(e),hasSpec:t!==void 0,lsl:t?.lsl,usl:t?.usl,mean:w,stdOverall:g,stdWithin:y,n,cp:b,cpk:h,pp:S,ppk:A}}function v(o){return o.sort((e,t)=>e.hasSpec!==t.hasSpec?e.hasSpec?-1:1:e.hasSpec?e.ppk===null&&t.ppk===null?0:e.ppk===null?1:t.ppk===null?-1:e.ppk-t.ppk:t.stdOverall-e.stdOverall)}export function buildCapabilityData(o,e){return B(buildCapabilityDataSteps(o,e))}export function*buildCapabilityDataSteps(o,e){return(yield*pooledTestStatsSteps(o,e)).capability.slice()}const F=new Map,_=4,D=new WeakMap;let z=1;function I(o,e){const t=[];for(const n of o){const i=n.dies;if(!i){t.push(0);continue}let r=D.get(i);r===void 0&&(r=z++,D.set(i,r)),t.push(r)}const l=e.map(n=>`${n.testNumber}${n.testType??"P"}${n.limitLow??""}${n.limitHigh??""}${n.name??""}${n.unit??""}`);return`${t.join(",")}${l.join("")}`}export function clearPooledTestStatsCache(){F.clear()}function R(o,e){let t=0,l=o.length;for(;t<l;){const n=t+l>>1;o[n]<e?t=n+1:l=n}return t}function j(o,e){let t=0,l=o.length;for(;t<l;){const n=t+l>>1;o[n]<=e?t=n+1:l=n}return t}export function*pooledTestStatsSteps(o,e){const t=I(o,e),l=F.get(t);if(l)return F.delete(t),F.set(t,l),l;const n={stats:new Map,specTally:new Map,capability:[]},{defByTestNumber:i,specByTest:r}=x(e);if(i.size===0)return n;const s=new Map,d=yield*C(o,i,s),w=new Map,T=new Map,g=[];for(const b of e){yield;const h=b.testNumber;if(h===void 0)continue;const S=d.get(h);if(!S||S.n===0||!i.has(h))continue;const A=s.get(h);s.delete(h);const u=Float64Array.from(A);if(u.sort(),w.set(h,P(u)),b.limitLow!==void 0||b.limitHigh!==void 0){const p=b.limitLow!==void 0?R(u,b.limitLow):0,c=b.limitHigh!==void 0?u.length-j(u,b.limitHigh):0;T.set(h,{n:u.length,fail:p+c})}const M=r.get(h),f=q(h,b,M,S);let m;if(M){const p=M.usl-M.lsl;m=c=>(c-M.lsl)/p}else{const p=u[0],c=u[u.length-1]-p;m=c>0?a=>(a-p)/c:()=>.5}g.push({...f,min:m(u[0]),q1:m(k(u,.25)),median:m(k(u,.5)),q3:m(k(u,.75)),max:m(u[u.length-1])})}const y={stats:w,specTally:T,capability:v(g)};return F.set(t,y),F.size>_&&F.delete(F.keys().next().value),y}export function buildCapabilityFigures(o,e){const{defByTestNumber:t,specByTest:l}=x(e);if(t.size===0)return[];const n=V(o,t,null),i=[];for(const[r,s]of n)s.n!==0&&i.push(q(r,t.get(r),l.get(r),s));return v(i)}export function poolCapabilityFigures(o,e){const{defByTestNumber:t,specByTest:l}=x(e),n=new Map;for(const r of o)for(const s of r){if(!t.has(s.testNumber)||s.n===0)continue;let d=n.get(s.testNumber);d||(d={n:0,sum:0,sumSq:0,withinNumerator:0,withinDenominator:0},n.set(s.testNumber,d));const w=s.stdOverall*s.stdOverall;d.n+=s.n,d.sum+=s.n*s.mean,d.sumSq+=(s.n-1)*w+s.n*s.mean*s.mean,s.n>=2&&(d.withinNumerator+=(s.n-1)*w,d.withinDenominator+=s.n-1)}const i=[];for(const[r,s]of n)i.push(q(r,t.get(r),l.get(r),s));return v(i)}
@@ -3,8 +3,11 @@ import { type TestDef } from '../renderer/buildWaferMap.js';
3
3
  /** Minimal per-test identity carried on a correlation matrix's axes. */
4
4
  export interface CorrelationTestInfo {
5
5
  testNumber: number;
6
+ /** Display name; a derived test's starts with `"† "` (`markedTestLabel`). */
6
7
  label: string;
7
8
  unit?: string;
9
+ derived?: true;
10
+ expression?: string;
8
11
  }
9
12
  export interface CorrelationCell {
10
13
  xIndex: number;
@@ -1 +1 @@
1
- import{isParametricTest as X}from"../renderer/buildWaferMap.js";export function pearsonFromSums(e,i,a,r,c,d){if(e<3)return null;const m=i/e,f=a/e,s=d/e-m*f,h=r/e-m*m,w=c/e-f*f,I=Math.sqrt(h*w);return I===0?null:Math.max(-1,Math.min(1,s/I))}export function pearsonOfPairs(e){let i=0,a=0,r=0,c=0,d=0,m=0;for(let f=0;f<e.length;f++){const{x:s,y:h}=e[f];!Number.isFinite(s)||!Number.isFinite(h)||(i++,a+=s,r+=h,c+=s*s,d+=h*h,m+=s*h)}return{r:pearsonFromSums(i,a,r,c,d,m),n:i}}export const CORRELATION_DIE_BUDGET=25e3;function Y(e,i){const a=[],r=e/i;for(let c=0;c<i;c++)a.push(Math.floor(c*r));return a}export function correlationSampleNote(e){if(e)return`From a ${e.used.toLocaleString()}-die sample of ${e.of.toLocaleString()}, spread evenly across the lot`}function v(e){return e.filter(i=>i.testNumber!==void 0&&X(i)).map(i=>({testNumber:i.testNumber,label:i.name,unit:i.unit}))}export function buildCorrelationMatrix(e,i){const a=v(i);if(a.length<2)return{tests:a,cells:[]};const r=a.length,c=a.map(n=>n.testNumber),d=r*(r-1)/2,m=new Float64Array(d),f=new Float64Array(r),s=new Float64Array(d),h=new Float64Array(d),w=new Float64Array(d),I=new Float64Array(d),A=new Float64Array(d);function R(n,o){return n*r-(n*(n+1)>>1)+(o-n-1)}const g=e.filter(n=>n.testValues!==void 0),t=g.length>CORRELATION_DIE_BUDGET,l=t?Y(g.length,CORRELATION_DIE_BUDGET).map(n=>g[n]):g,u=new Float64Array(r),p=new Uint8Array(r);for(const n of l){p.fill(0);for(let o=0;o<r;o++){const b=n.testValues[c[o]];b!==void 0&&Number.isFinite(b)&&(u[o]=b,p[o]=1)}for(let o=0;o<r;o++){if(!p[o])continue;f[o]++;const b=u[o];for(let F=o+1;F<r;F++){if(!p[F])continue;const M=u[F],N=R(o,F);m[N]++,s[N]+=b,h[N]+=M,w[N]+=b*b,I[N]+=M*M,A[N]+=b*M}}}const y=n=>pearsonFromSums(m[n],s[n],h[n],w[n],I[n],A[n]),x=[];for(let n=0;n<r;n++)for(let o=0;o<r;o++){if(o===n){x.push({xIndex:o,yIndex:n,r:1,n:f[o]});continue}const b=Math.min(o,n),F=Math.max(o,n),M=R(b,F);x.push({xIndex:o,yIndex:n,r:y(M),n:m[M]})}return t?{tests:a,cells:x,sample:{of:g.length,used:l.length}}:{tests:a,cells:x}}export function filterCorrelationMatrix(e,{threshold:i=.3,minTests:a=6,maxTests:r=20}={}){const c=[];let d=0,m=0,f=null;for(const t of e.cells){if(t.xIndex>=t.yIndex||t.r===null)continue;const l=Math.abs(t.r);c.push({xi:t.xIndex,yi:t.yIndex,absR:l}),(f===null||l>Math.abs(f.r))&&(f={xLabel:e.tests[t.xIndex].label,yLabel:e.tests[t.yIndex].label,r:t.r})}c.sort((t,l)=>l.absR-t.absR);const s=new Set;for(const{xi:t,yi:l,absR:u}of c){const p=u<i;s.size>=r||p&&s.size>=a||(s.add(t),s.size<r&&s.add(l))}if(s.size<Math.min(a,e.tests.length))for(let t=0;t<e.tests.length&&s.size<Math.min(r,a,e.tests.length);t++)s.add(t);const h=(()=>{const t=Array.from(s),l=new Map,u=new Map;for(const{xi:y,yi:x,absR:n}of c)!s.has(y)||!s.has(x)||(l.set(y,(l.get(y)??0)+n),l.set(x,(l.get(x)??0)+n),u.set(y,(u.get(y)??0)+1),u.set(x,(u.get(x)??0)+1));const p=y=>(u.get(y)??0)>0?l.get(y)/u.get(y):0;return t.sort((y,x)=>p(x)-p(y))})(),w=h.map(t=>e.tests[t]),I=new Map(h.map((t,l)=>[t,l])),A=new Set(w.map(t=>t.testNumber)),R=e.cells.filter(t=>A.has(e.tests[t.xIndex].testNumber)&&A.has(e.tests[t.yIndex].testNumber)).map(t=>({xIndex:I.get(t.xIndex),yIndex:I.get(t.yIndex),r:t.r,n:t.n}));let g=0;for(const{xi:t,yi:l,absR:u}of c)s.has(t)&&s.has(l)?u>=.7?d++:u>=.4&&m++:u<i&&g++;return{matrix:{tests:w,cells:R},strongPairs:d,moderatePairs:m,hiddenWeakPairs:g,strongestPair:f}}
1
+ import{isParametricTest as X}from"../renderer/buildWaferMap.js";import{markedTestLabel as Y,derivedFields as v}from"../renderer/testLabel.js";export function pearsonFromSums(e,r,a,i,c,d){if(e<3)return null;const m=r/e,f=a/e,s=d/e-m*f,h=i/e-m*m,w=c/e-f*f,I=Math.sqrt(h*w);return I===0?null:Math.max(-1,Math.min(1,s/I))}export function pearsonOfPairs(e){let r=0,a=0,i=0,c=0,d=0,m=0;for(let f=0;f<e.length;f++){const{x:s,y:h}=e[f];!Number.isFinite(s)||!Number.isFinite(h)||(r++,a+=s,i+=h,c+=s*s,d+=h*h,m+=s*h)}return{r:pearsonFromSums(r,a,i,c,d,m),n:r}}export const CORRELATION_DIE_BUDGET=25e3;function P(e,r){const a=[],i=e/r;for(let c=0;c<r;c++)a.push(Math.floor(c*i));return a}export function correlationSampleNote(e){if(e)return`From a ${e.used.toLocaleString()}-die sample of ${e.of.toLocaleString()}, spread evenly across the lot`}function L(e){return e.filter(r=>r.testNumber!==void 0&&X(r)).map(r=>({testNumber:r.testNumber,label:Y(r,r.testNumber),unit:r.unit,...v(r)}))}export function buildCorrelationMatrix(e,r){const a=L(r);if(a.length<2)return{tests:a,cells:[]};const i=a.length,c=a.map(n=>n.testNumber),d=i*(i-1)/2,m=new Float64Array(d),f=new Float64Array(i),s=new Float64Array(d),h=new Float64Array(d),w=new Float64Array(d),I=new Float64Array(d),A=new Float64Array(d);function R(n,o){return n*i-(n*(n+1)>>1)+(o-n-1)}const g=e.filter(n=>n.testValues!==void 0),t=g.length>CORRELATION_DIE_BUDGET,l=t?P(g.length,CORRELATION_DIE_BUDGET).map(n=>g[n]):g,u=new Float64Array(i),p=new Uint8Array(i);for(const n of l){p.fill(0);for(let o=0;o<i;o++){const b=n.testValues[c[o]];b!==void 0&&Number.isFinite(b)&&(u[o]=b,p[o]=1)}for(let o=0;o<i;o++){if(!p[o])continue;f[o]++;const b=u[o];for(let F=o+1;F<i;F++){if(!p[F])continue;const M=u[F],N=R(o,F);m[N]++,s[N]+=b,h[N]+=M,w[N]+=b*b,I[N]+=M*M,A[N]+=b*M}}}const y=n=>pearsonFromSums(m[n],s[n],h[n],w[n],I[n],A[n]),x=[];for(let n=0;n<i;n++)for(let o=0;o<i;o++){if(o===n){x.push({xIndex:o,yIndex:n,r:1,n:f[o]});continue}const b=Math.min(o,n),F=Math.max(o,n),M=R(b,F);x.push({xIndex:o,yIndex:n,r:y(M),n:m[M]})}return t?{tests:a,cells:x,sample:{of:g.length,used:l.length}}:{tests:a,cells:x}}export function filterCorrelationMatrix(e,{threshold:r=.3,minTests:a=6,maxTests:i=20}={}){const c=[];let d=0,m=0,f=null;for(const t of e.cells){if(t.xIndex>=t.yIndex||t.r===null)continue;const l=Math.abs(t.r);c.push({xi:t.xIndex,yi:t.yIndex,absR:l}),(f===null||l>Math.abs(f.r))&&(f={xLabel:e.tests[t.xIndex].label,yLabel:e.tests[t.yIndex].label,r:t.r})}c.sort((t,l)=>l.absR-t.absR);const s=new Set;for(const{xi:t,yi:l,absR:u}of c){const p=u<r;s.size>=i||p&&s.size>=a||(s.add(t),s.size<i&&s.add(l))}if(s.size<Math.min(a,e.tests.length))for(let t=0;t<e.tests.length&&s.size<Math.min(i,a,e.tests.length);t++)s.add(t);const h=(()=>{const t=Array.from(s),l=new Map,u=new Map;for(const{xi:y,yi:x,absR:n}of c)!s.has(y)||!s.has(x)||(l.set(y,(l.get(y)??0)+n),l.set(x,(l.get(x)??0)+n),u.set(y,(u.get(y)??0)+1),u.set(x,(u.get(x)??0)+1));const p=y=>(u.get(y)??0)>0?l.get(y)/u.get(y):0;return t.sort((y,x)=>p(x)-p(y))})(),w=h.map(t=>e.tests[t]),I=new Map(h.map((t,l)=>[t,l])),A=new Set(w.map(t=>t.testNumber)),R=e.cells.filter(t=>A.has(e.tests[t.xIndex].testNumber)&&A.has(e.tests[t.yIndex].testNumber)).map(t=>({xIndex:I.get(t.xIndex),yIndex:I.get(t.yIndex),r:t.r,n:t.n}));let g=0;for(const{xi:t,yi:l,absR:u}of c)s.has(t)&&s.has(l)?u>=.7?d++:u>=.4&&m++:u<r&&g++;return{matrix:{tests:w,cells:R},strongPairs:d,moderatePairs:m,hiddenWeakPairs:g,strongestPair:f}}
@@ -79,4 +79,7 @@ export declare const renderSummaryReportHtml: typeof renderSummaryReportHtmlImpl
79
79
  export declare const renderLotSummaryReportHtml: typeof renderLotSummaryReportHtmlImpl;
80
80
  /** @deprecated Removed in 0.31.0. `buildFacetTable` applies it by default. */
81
81
  export declare const DEFAULT_FACET_CURATION: Record<string, import("./facets.js").FacetCuration>;
82
+ import { renderFindingsReportHtml as renderFindingsReportHtmlImpl } from './renderFindingsReport.js';
83
+ /** @deprecated Removed in 0.31.0. Use `renderWaferReportHtml(result, summary)` or `renderLotReportHtml(results)`: their Findings section is the same table, alongside the population and yield it was found in. */
84
+ export declare const renderFindingsReportHtml: typeof renderFindingsReportHtmlImpl;
82
85
  //# sourceMappingURL=deprecated.d.ts.map
@@ -1 +1 @@
1
- import{deprecated as e}from"../renderer/deprecate.js";import{buildCapabilityData as s}from"./capability.js";import{buildCorrelationMatrix as r,filterCorrelationMatrix as i}from"./correlation.js";import{buildTestBoxplotData as o}from"./boxplot.js";import{buildTestTrendData as n,trendCentre as l}from"./trend.js";import{buildTestPassRateData as d,hasJudgeableTests as p}from"./testPassRate.js";import{buildTestHistogramData as m,buildTestHistogramSeries as u}from"./histogram.js";import{buildScatterData as c,buildScatterDataGrouped as f}from"./scatter.js";import{buildYieldData as b,buildYieldDataCombined as g}from"./yield.js";import{buildBinParetoData as h,buildBinClusterData as y}from"./binPareto.js";const t="If you depend on it, say so at https://github.com/wafertools/wafermap/issues.";export const buildCapabilityData=e(s,"buildCapabilityData",`analyzeWaferMap and analyzeWaferLot now return Cp/Cpk/Pp/Ppk as stats.capability (enable computePerTestStats); a lot's uses the pooled within-wafer stddev. ${t}`),buildCorrelationMatrix=e(r,"buildCorrelationMatrix",`It prepared data for the Insights correlation matrix, which wmap draws itself (insights: { enabled: true }); there is no data replacement yet. ${t}`),filterCorrelationMatrix=e(i,"filterCorrelationMatrix",`It prepared data for the Insights correlation matrix, which wmap draws itself (insights: { enabled: true }); there is no data replacement. ${t}`),buildTestBoxplotData=e(o,"buildTestBoxplotData",`Five-number summaries are stats.perTestStats (enable computePerTestStats), and perWaferTestStats on analyzeWaferLot's result. ${t}`),buildTestTrendData=e(n,"buildTestTrendData",`Per-wafer means and stddevs in slot order are perWaferTestStats on analyzeWaferLot's result (enable computePerTestStats). ${t}`),trendCentre=e(l,"trendCentre",`Compute the die-weighted mean from perWaferTestStats (mean \xD7 count) on analyzeWaferLot's result. ${t}`),buildTestPassRateData=e(d,"buildTestPassRateData",`analyzeWaferMap and analyzeWaferLot now return per-test pass rates as stats.testSpecYield (spec limits), stats.testFlagYield (tester verdicts) and stats.functionalYield, with stats.specVerdictDisagreementDies. ${t}`),hasJudgeableTests=e(p,"hasJudgeableTests",`Read which pass rates exist from stats.testSpecYield, stats.testFlagYield and stats.functionalYield on analyzeWaferMap's or analyzeWaferLot's result. ${t}`),buildTestHistogramData=e(m,"buildTestHistogramData",`It prepared data for the Insights histogram, which wmap draws itself (insights: { enabled: true }); there is no data replacement. ${t}`),buildTestHistogramSeries=e(u,"buildTestHistogramSeries",`It prepared data for the Insights histogram, which wmap draws itself (insights: { enabled: true }); there is no data replacement. ${t}`),buildScatterData=e(c,"buildScatterData",`It prepared data for the Insights scatter plot, which wmap draws itself (insights: { enabled: true }); there is no data replacement. ${t}`),buildScatterDataGrouped=e(f,"buildScatterDataGrouped",`It prepared data for the Insights scatter plot, which wmap draws itself (insights: { enabled: true }); there is no data replacement. ${t}`),buildYieldData=e(b,"buildYieldData",`Per-wafer yield is lotYieldSeries on analyzeWaferLot's result, and stats.yieldPercent on each summary. ${t}`),buildYieldDataCombined=e(g,"buildYieldDataCombined",`Per-wafer yield is lotYieldSeries on analyzeWaferLot's result; weight it by each wafer's die count to combine groups. ${t}`),buildBinParetoData=e(h,"buildBinParetoData",`Bin counts are stats.hardBinCounts and stats.softBinCounts on analyzeWaferMap's result. ${t}`),buildBinClusterData=e(y,"buildBinClusterData",`Bin counts per wafer are stats.hardBinCounts and stats.softBinCounts on each perWafer summary of analyzeWaferLot's result. ${t}`);import{deprecatedValue as R}from"../renderer/deprecate.js";import{buildRingRegions as D,buildQuadrantRegions as T,buildSectorRegions as I,buildReticlePositionRegions as C,buildTestSiteRegions as x,buildRegionYieldData as S,areQuadrantsAdjacent as w,parseRegionKey as $,sectorCompassNames as M}from"./regions.js";import{classifyPattern as W}from"./patternClassification.js";import{computeFunctionalYield as Y}from"./analyzeWaferMap.js";import{resolveMetadataColumns as P,discoverDieMetadataKeys as z}from"./metadataColumns.js";import{openHtmlReport as L}from"./renderFindingsReport.js";import{renderSummaryReportHtml as A,renderLotSummaryReportHtml as H}from"./renderSummaryReport.js";import{DEFAULT_FACET_CURATION as B}from"./facets.js";const F="analyzeWaferMap returns the classification, with its geometry features, as stats.spatialPattern for every wafer, and a detected pattern as a finding (comparison.family 'spatial-pattern').",E="analyzeWaferMap returns the same figures as stats.functionalYield.",a="The die list resolves its metadata columns itself (the dieList.metadataColumns option).";export const buildRingRegions=e(D,"buildRingRegions",`Ring yield is stats.regionYield.ring on analyzeWaferMap's or analyzeWaferLot's result; ring findings are in findings. ${t}`),buildQuadrantRegions=e(T,"buildQuadrantRegions",`Quadrant yield is stats.regionYield.quadrant on analyzeWaferMap's or analyzeWaferLot's result; quadrant findings are in findings. ${t}`),buildSectorRegions=e(I,"buildSectorRegions",`Sector findings are in analyzeWaferMap's findings (comparison.family 'sector'). ${t}`),buildReticlePositionRegions=e(C,"buildReticlePositionRegions",`Reticle-position findings are in analyzeWaferMap's findings; a die's reticle cell is getReticleCell(die, reticleConfig). ${t}`),buildTestSiteRegions=e(x,"buildTestSiteRegions",`Test-site findings are in analyzeWaferMap's findings (comparison.family 'test-site'). ${t}`),buildRegionYieldData=e(S,"buildRegionYieldData",`analyzeWaferMap and analyzeWaferLot now return ring and quadrant yield as stats.regionYield. ${t}`),areQuadrantsAdjacent=e(w,"areQuadrantsAdjacent",`It is an internal helper of the quadrant findings, which analyzeWaferMap already reports. ${t}`),parseRegionKey=e($,"parseRegionKey",`Findings name their region in prose (comparison.left) and region yield in RegionYield.label; region keys are identities, not to be parsed. ${t}`),sectorCompassNames=e(M,"sectorCompassNames",`Sector findings name their sector in prose (comparison.left). ${t}`),classifyPattern=e(W,"classifyPattern",`${F} ${t}`),computeFunctionalYield=e(Y,"computeFunctionalYield",`${E} ${t}`),resolveMetadataColumns=e(P,"resolveMetadataColumns",`${a} ${t}`),discoverDieMetadataKeys=e(z,"discoverDieMetadataKeys",`${a} ${t}`),openHtmlReport=e(L,"openHtmlReport",`Open reports with openReportModal(html), or route them into your host with setReportOpener. ${t}`),renderSummaryReportHtml=e(A,"renderSummaryReportHtml",`Use renderWaferReportHtml(result, summary), which reads pass bins and ring count from the built map. ${t}`),renderLotSummaryReportHtml=e(H,"renderLotSummaryReportHtml",`Use renderLotReportHtml(results), which reads each wafer's pass bins and ring count from its built map. ${t}`),DEFAULT_FACET_CURATION=R(B,"DEFAULT_FACET_CURATION");
1
+ import{deprecated as e}from"../renderer/deprecate.js";import{buildCapabilityData as s}from"./capability.js";import{buildCorrelationMatrix as r,filterCorrelationMatrix as i}from"./correlation.js";import{buildTestBoxplotData as o}from"./boxplot.js";import{buildTestTrendData as n,trendCentre as l}from"./trend.js";import{buildTestPassRateData as d,hasJudgeableTests as p}from"./testPassRate.js";import{buildTestHistogramData as m,buildTestHistogramSeries as u}from"./histogram.js";import{buildScatterData as c,buildScatterDataGrouped as f}from"./scatter.js";import{buildYieldData as b,buildYieldDataCombined as g}from"./yield.js";import{buildBinParetoData as h,buildBinClusterData as y}from"./binPareto.js";const t="If you depend on it, say so at https://github.com/wafertools/wafermap/issues.";export const buildCapabilityData=e(s,"buildCapabilityData",`analyzeWaferMap and analyzeWaferLot now return Cp/Cpk/Pp/Ppk as stats.capability (enable computePerTestStats); a lot's uses the pooled within-wafer stddev. ${t}`),buildCorrelationMatrix=e(r,"buildCorrelationMatrix",`It prepared data for the Insights correlation matrix, which wmap draws itself (insights: { enabled: true }); there is no data replacement yet. ${t}`),filterCorrelationMatrix=e(i,"filterCorrelationMatrix",`It prepared data for the Insights correlation matrix, which wmap draws itself (insights: { enabled: true }); there is no data replacement. ${t}`),buildTestBoxplotData=e(o,"buildTestBoxplotData",`Five-number summaries are stats.perTestStats (enable computePerTestStats), and perWaferTestStats on analyzeWaferLot's result. ${t}`),buildTestTrendData=e(n,"buildTestTrendData",`Per-wafer means and stddevs in slot order are perWaferTestStats on analyzeWaferLot's result (enable computePerTestStats). ${t}`),trendCentre=e(l,"trendCentre",`Compute the die-weighted mean from perWaferTestStats (mean \xD7 count) on analyzeWaferLot's result. ${t}`),buildTestPassRateData=e(d,"buildTestPassRateData",`analyzeWaferMap and analyzeWaferLot now return per-test pass rates as stats.testSpecYield (spec limits), stats.testFlagYield (tester verdicts) and stats.functionalYield, with stats.specVerdictDisagreementDies. ${t}`),hasJudgeableTests=e(p,"hasJudgeableTests",`Read which pass rates exist from stats.testSpecYield, stats.testFlagYield and stats.functionalYield on analyzeWaferMap's or analyzeWaferLot's result. ${t}`),buildTestHistogramData=e(m,"buildTestHistogramData",`It prepared data for the Insights histogram, which wmap draws itself (insights: { enabled: true }); there is no data replacement. ${t}`),buildTestHistogramSeries=e(u,"buildTestHistogramSeries",`It prepared data for the Insights histogram, which wmap draws itself (insights: { enabled: true }); there is no data replacement. ${t}`),buildScatterData=e(c,"buildScatterData",`It prepared data for the Insights scatter plot, which wmap draws itself (insights: { enabled: true }); there is no data replacement. ${t}`),buildScatterDataGrouped=e(f,"buildScatterDataGrouped",`It prepared data for the Insights scatter plot, which wmap draws itself (insights: { enabled: true }); there is no data replacement. ${t}`),buildYieldData=e(b,"buildYieldData",`Per-wafer yield is lotYieldSeries on analyzeWaferLot's result, and stats.yieldPercent on each summary. ${t}`),buildYieldDataCombined=e(g,"buildYieldDataCombined",`Per-wafer yield is lotYieldSeries on analyzeWaferLot's result; weight it by each wafer's die count to combine groups. ${t}`),buildBinParetoData=e(h,"buildBinParetoData",`Bin counts are stats.hardBinCounts and stats.softBinCounts on analyzeWaferMap's result. ${t}`),buildBinClusterData=e(y,"buildBinClusterData",`Bin counts per wafer are stats.hardBinCounts and stats.softBinCounts on each perWafer summary of analyzeWaferLot's result. ${t}`);import{deprecatedValue as R}from"../renderer/deprecate.js";import{buildRingRegions as D,buildQuadrantRegions as I,buildSectorRegions as T,buildReticlePositionRegions as C,buildTestSiteRegions as x,buildRegionYieldData as S,areQuadrantsAdjacent as w,parseRegionKey as $,sectorCompassNames as M}from"./regions.js";import{classifyPattern as W}from"./patternClassification.js";import{computeFunctionalYield as Y}from"./analyzeWaferMap.js";import{resolveMetadataColumns as H,discoverDieMetadataKeys as P}from"./metadataColumns.js";import{openHtmlReport as z}from"./renderFindingsReport.js";import{renderSummaryReportHtml as L,renderLotSummaryReportHtml as A}from"./renderSummaryReport.js";import{DEFAULT_FACET_CURATION as F}from"./facets.js";const B="analyzeWaferMap returns the classification, with its geometry features, as stats.spatialPattern for every wafer, and a detected pattern as a finding (comparison.family 'spatial-pattern').",E="analyzeWaferMap returns the same figures as stats.functionalYield.",a="The die list resolves its metadata columns itself (the dieList.metadataColumns option).";export const buildRingRegions=e(D,"buildRingRegions",`Ring yield is stats.regionYield.ring on analyzeWaferMap's or analyzeWaferLot's result; ring findings are in findings. ${t}`),buildQuadrantRegions=e(I,"buildQuadrantRegions",`Quadrant yield is stats.regionYield.quadrant on analyzeWaferMap's or analyzeWaferLot's result; quadrant findings are in findings. ${t}`),buildSectorRegions=e(T,"buildSectorRegions",`Sector findings are in analyzeWaferMap's findings (comparison.family 'sector'). ${t}`),buildReticlePositionRegions=e(C,"buildReticlePositionRegions",`Reticle-position findings are in analyzeWaferMap's findings; a die's reticle cell is getReticleCell(die, reticleConfig). ${t}`),buildTestSiteRegions=e(x,"buildTestSiteRegions",`Test-site findings are in analyzeWaferMap's findings (comparison.family 'test-site'). ${t}`),buildRegionYieldData=e(S,"buildRegionYieldData",`analyzeWaferMap and analyzeWaferLot now return ring and quadrant yield as stats.regionYield. ${t}`),areQuadrantsAdjacent=e(w,"areQuadrantsAdjacent",`It is an internal helper of the quadrant findings, which analyzeWaferMap already reports. ${t}`),parseRegionKey=e($,"parseRegionKey",`Findings name their region in prose (comparison.left) and region yield in RegionYield.label; region keys are identities, not to be parsed. ${t}`),sectorCompassNames=e(M,"sectorCompassNames",`Sector findings name their sector in prose (comparison.left). ${t}`),classifyPattern=e(W,"classifyPattern",`${B} ${t}`),computeFunctionalYield=e(Y,"computeFunctionalYield",`${E} ${t}`),resolveMetadataColumns=e(H,"resolveMetadataColumns",`${a} ${t}`),discoverDieMetadataKeys=e(P,"discoverDieMetadataKeys",`${a} ${t}`),openHtmlReport=e(z,"openHtmlReport",`Open reports with openReportModal(html), or route them into your host with setReportOpener. ${t}`),renderSummaryReportHtml=e(L,"renderSummaryReportHtml",`Use renderWaferReportHtml(result, summary), which reads pass bins and ring count from the built map. ${t}`),renderLotSummaryReportHtml=e(A,"renderLotSummaryReportHtml",`Use renderLotReportHtml(results), which reads each wafer's pass bins and ring count from its built map. ${t}`),DEFAULT_FACET_CURATION=R(F,"DEFAULT_FACET_CURATION");import{renderFindingsReportHtml as v}from"./renderFindingsReport.js";export const renderFindingsReportHtml=e(v,"renderFindingsReportHtml",`Use renderWaferReportHtml(result, summary) or renderLotReportHtml(results): their Findings section is the same table, alongside the population and yield it was found in. ${t}`);
@@ -4,7 +4,7 @@ export * from './analyzeWaferMap.js';
4
4
  export * from './analyzeWaferLot.js';
5
5
  export * from './filterFindings.js';
6
6
  export type { PatternLabel, PatternClassification, PatternFeatures } from './patternClassification.js';
7
- export { setReportOpener, renderFindingsReportHtml } from './renderFindingsReport.js';
7
+ export { setReportOpener } from './renderFindingsReport.js';
8
8
  export { renderWaferReportHtml, renderLotReportHtml } from './renderSummaryReport.js';
9
9
  export type { ReportMap } from './renderSummaryReport.js';
10
10
  export type { SummaryReportParams, LotSummaryReportParams } from './renderSummaryReport.js';
@@ -22,5 +22,5 @@ export type { HistogramBucket, HistogramItem, HistogramSeries, HistogramSeriesDa
22
22
  export type { ScatterPoint, ScatterItem } from './scatter.js';
23
23
  export type { ChartDatum, YieldItem, YieldSortBy } from './yield.js';
24
24
  export type { BinType, BinItem, BinCluster, BinClusterData } from './binPareto.js';
25
- export { buildCapabilityData, buildCorrelationMatrix, filterCorrelationMatrix, buildTestBoxplotData, buildTestTrendData, trendCentre, buildTestPassRateData, hasJudgeableTests, buildTestHistogramData, buildTestHistogramSeries, buildScatterData, buildScatterDataGrouped, buildYieldData, buildYieldDataCombined, buildBinParetoData, buildBinClusterData, buildRingRegions, buildQuadrantRegions, buildSectorRegions, buildReticlePositionRegions, buildTestSiteRegions, buildRegionYieldData, areQuadrantsAdjacent, parseRegionKey, sectorCompassNames, classifyPattern, computeFunctionalYield, resolveMetadataColumns, discoverDieMetadataKeys, openHtmlReport, renderSummaryReportHtml, renderLotSummaryReportHtml, DEFAULT_FACET_CURATION, } from './deprecated.js';
25
+ export { buildCapabilityData, buildCorrelationMatrix, filterCorrelationMatrix, buildTestBoxplotData, buildTestTrendData, trendCentre, buildTestPassRateData, hasJudgeableTests, buildTestHistogramData, buildTestHistogramSeries, buildScatterData, buildScatterDataGrouped, buildYieldData, buildYieldDataCombined, buildBinParetoData, buildBinClusterData, buildRingRegions, buildQuadrantRegions, buildSectorRegions, buildReticlePositionRegions, buildTestSiteRegions, buildRegionYieldData, areQuadrantsAdjacent, parseRegionKey, sectorCompassNames, classifyPattern, computeFunctionalYield, resolveMetadataColumns, discoverDieMetadataKeys, openHtmlReport, renderSummaryReportHtml, renderLotSummaryReportHtml, DEFAULT_FACET_CURATION, renderFindingsReportHtml, } from './deprecated.js';
26
26
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- export*from"./types.js";export*from"./regions.js";export*from"./analyzeWaferMap.js";export*from"./analyzeWaferLot.js";export*from"./filterFindings.js";export{setReportOpener,renderFindingsReportHtml}from"./renderFindingsReport.js";export{renderWaferReportHtml,renderLotReportHtml}from"./renderSummaryReport.js";export{buildFacetTable,facetValueOf,FACET_NONE_VALUE}from"./facets.js";export{mergeTestDefs}from"./mergeTestDefs.js";export{buildCapabilityData,buildCorrelationMatrix,filterCorrelationMatrix,buildTestBoxplotData,buildTestTrendData,trendCentre,buildTestPassRateData,hasJudgeableTests,buildTestHistogramData,buildTestHistogramSeries,buildScatterData,buildScatterDataGrouped,buildYieldData,buildYieldDataCombined,buildBinParetoData,buildBinClusterData,buildRingRegions,buildQuadrantRegions,buildSectorRegions,buildReticlePositionRegions,buildTestSiteRegions,buildRegionYieldData,areQuadrantsAdjacent,parseRegionKey,sectorCompassNames,classifyPattern,computeFunctionalYield,resolveMetadataColumns,discoverDieMetadataKeys,openHtmlReport,renderSummaryReportHtml,renderLotSummaryReportHtml,DEFAULT_FACET_CURATION}from"./deprecated.js";
1
+ export*from"./types.js";export*from"./regions.js";export*from"./analyzeWaferMap.js";export*from"./analyzeWaferLot.js";export*from"./filterFindings.js";export{setReportOpener}from"./renderFindingsReport.js";export{renderWaferReportHtml,renderLotReportHtml}from"./renderSummaryReport.js";export{buildFacetTable,facetValueOf,FACET_NONE_VALUE}from"./facets.js";export{mergeTestDefs}from"./mergeTestDefs.js";export{buildCapabilityData,buildCorrelationMatrix,filterCorrelationMatrix,buildTestBoxplotData,buildTestTrendData,trendCentre,buildTestPassRateData,hasJudgeableTests,buildTestHistogramData,buildTestHistogramSeries,buildScatterData,buildScatterDataGrouped,buildYieldData,buildYieldDataCombined,buildBinParetoData,buildBinClusterData,buildRingRegions,buildQuadrantRegions,buildSectorRegions,buildReticlePositionRegions,buildTestSiteRegions,buildRegionYieldData,areQuadrantsAdjacent,parseRegionKey,sectorCompassNames,classifyPattern,computeFunctionalYield,resolveMetadataColumns,discoverDieMetadataKeys,openHtmlReport,renderSummaryReportHtml,renderLotSummaryReportHtml,DEFAULT_FACET_CURATION,renderFindingsReportHtml}from"./deprecated.js";
@@ -1 +1 @@
1
- const k=1e-6;function w(t,e){if(t===e)return!0;if(!Number.isFinite(t)||!Number.isFinite(e))return!1;const i=Math.max(Math.abs(t),Math.abs(e));return Math.abs(t-e)<=1e-6*i}function v(t){const e=t.name?.trim();return e||void 0}function y(t){const e=t.unit?.trim();return e||void 0}function L(t){return t.toLowerCase()}function h(t,e){const i=new Set,s=[];for(const o of t){const r=e(o);i.has(r)||(i.add(r),s.push(o))}return s}function g(t){const e=[];for(const i of t)e.some(s=>w(s,i))||e.push(i);return e}function T(t){return`${t.testNumber} (${t.values.join(" vs ")})`}function b(t,e=5){const i=t.slice(0,e).map(T).join(", "),s=t.length-Math.min(e,t.length);return s>0?`${i}, and ${s} more`:i}export function mergeTestDefs(t){const e=new Map,i=[];for(const r of t)for(const u of r?.testDefs??[]){const f=u.testNumber;let d=e.get(f);d||(d=[],e.set(f,d),i.push(f)),d.push(u)}const s=[],o=[];for(const r of i){const u=e.get(r),f=h(u.map(v).filter(n=>n!==void 0),L),d=h(u.map(y).filter(n=>n!==void 0),n=>n),l=h(u.map(n=>n.testType).filter(n=>n!==void 0),n=>n),p=f.length>1?{testNumber:r,kind:"name",excluded:!0,values:f}:d.length>1?{testNumber:r,kind:"unit",excluded:!0,values:d}:l.length>1?{testNumber:r,kind:"testType",excluded:!0,values:l}:void 0;if(p){o.push(p);continue}const a=g(u.map(n=>n.limitLow).filter(n=>n!==void 0)),c=g(u.map(n=>n.limitHigh).filter(n=>n!==void 0)),m=a.length>1||c.length>1;m&&o.push({testNumber:r,kind:"limits",excluded:!1,values:(a.length>1?a:c).map(n=>String(n))}),s.push({testNumber:r,name:f[0]??"",...d[0]!==void 0?{unit:d[0]}:{},...l[0]!==void 0?{testType:l[0]}:{},...u.find(n=>n.logScale!==void 0)?{logScale:u.find(n=>n.logScale!==void 0).logScale}:{},...!m&&a[0]!==void 0?{limitLow:a[0]}:{},...!m&&c[0]!==void 0?{limitHigh:c[0]}:{}})}return{defs:s,conflicts:o,warnings:N(o)}}function N(t){const e=[],i=t.filter(o=>o.excluded),s=t.filter(o=>o.kind==="limits");return i.length>0&&e.push({code:"test-def-collision",severity:"error",message:`${i.length} test ${i.length===1?"number describes":"numbers describe"} a different measurement in different wafers, so the same number cannot identify one test across this population. Affected: `+b(i)+". These tests are withheld from every cross-wafer chart, report and map mode \u2014 pooling them would mix unrelated measurements into one distribution. Each wafer's own view is unaffected. Load one test program at a time to see them."}),s.length>0&&e.push({code:"test-limit-conflict",severity:"warning",message:`${s.length} ${s.length===1?"test is":"tests are"} held to different spec limits in different wafers. Affected: `+b(s)+". The measured values are still comparable, so distributions include them, but capability (Cp/Cpk/Pp/Ppk), spec yield and the limit lines are withheld for these tests \u2014 there is no single spec to judge the merged population against."}),e}
1
+ const N=1e-6;function w(n,t){if(n===t)return!0;if(!Number.isFinite(n)||!Number.isFinite(t))return!1;const i=Math.max(Math.abs(n),Math.abs(t));return Math.abs(n-t)<=1e-6*i}function v(n){const t=n.name?.trim();return t||void 0}function x(n){const t=n.unit?.trim();return t||void 0}function y(n){return n.toLowerCase()}function h(n,t){const i=new Set,s=[];for(const r of n){const d=t(r);i.has(d)||(i.add(d),s.push(r))}return s}function g(n){const t=[];for(const i of n)t.some(s=>w(s,i))||t.push(i);return t}function L(n){return`${n.testNumber} (${n.values.join(" vs ")})`}function b(n,t=5){const i=n.slice(0,t).map(L).join(", "),s=n.length-Math.min(t,n.length);return s>0?`${i}, and ${s} more`:i}export function mergeTestDefs(n){const t=new Map,i=[];for(const d of n)for(const o of d?.testDefs??[]){const f=o.testNumber;let u=t.get(f);u||(u=[],t.set(f,u),i.push(f)),u.push(o)}const s=[],r=[];for(const d of i){const o=t.get(d),f=h(o.map(v).filter(e=>e!==void 0),y),u=h(o.map(x).filter(e=>e!==void 0),e=>e),l=h(o.map(e=>e.testType).filter(e=>e!==void 0),e=>e),p=f.length>1?{testNumber:d,kind:"name",excluded:!0,values:f}:u.length>1?{testNumber:d,kind:"unit",excluded:!0,values:u}:l.length>1?{testNumber:d,kind:"testType",excluded:!0,values:l}:void 0;if(p){r.push(p);continue}const a=g(o.map(e=>e.limitLow).filter(e=>e!==void 0)),c=g(o.map(e=>e.limitHigh).filter(e=>e!==void 0)),m=a.length>1||c.length>1;m&&r.push({testNumber:d,kind:"limits",excluded:!1,values:(a.length>1?a:c).map(e=>String(e))}),s.push({testNumber:d,name:f[0]??"",...u[0]!==void 0?{unit:u[0]}:{},...l[0]!==void 0?{testType:l[0]}:{},...o.find(e=>e.logScale!==void 0)?{logScale:o.find(e=>e.logScale!==void 0).logScale}:{},...!m&&a[0]!==void 0?{limitLow:a[0]}:{},...!m&&c[0]!==void 0?{limitHigh:c[0]}:{},...o.some(e=>e.derived)?{derived:!0}:{},...o.find(e=>e.expression!==void 0)?{expression:o.find(e=>e.expression!==void 0).expression}:{}})}return{defs:s,conflicts:r,warnings:T(r)}}function T(n){const t=[],i=n.filter(r=>r.excluded),s=n.filter(r=>r.kind==="limits");return i.length>0&&t.push({code:"test-def-collision",severity:"error",message:`${i.length} test ${i.length===1?"number describes":"numbers describe"} a different measurement in different wafers, so the same number cannot identify one test across this population. Affected: `+b(i)+". These tests are withheld from every cross-wafer chart, report and map mode \u2014 pooling them would mix unrelated measurements into one distribution. Each wafer's own view is unaffected. Load one test program at a time to see them."}),s.length>0&&t.push({code:"test-limit-conflict",severity:"warning",message:`${s.length} ${s.length===1?"test is":"tests are"} held to different spec limits in different wafers. Affected: `+b(s)+". The measured values are still comparable, so distributions include them, but capability (Cp/Cpk/Pp/Ppk), spec yield and the limit lines are withheld for these tests \u2014 there is no single spec to judge the merged population against."}),t}
@@ -1,41 +1,21 @@
1
- import{describeWaferPopulation as b,populationLabel as h}from"./population.js";import{formatFindingDelta as g,formatFindingCoverage as v,formatFindingTooltip as y,buildMetadataRows as d,renderDefinitionList as p,renderSection as c,renderSeverityBadge as R,reportStyles as $}from"./reportHtml.js";import{escHtml as o}from"../core/utils.js";import{buildFindingsNarrative as F}from"./findingsNarrative.js";import{plainBinTerms as f}from"../renderer/fmt.js";import{visibleFindings as S}from"./filterFindings.js";function T(e,n){if(e.level==="lot"){const a=e,l=a.perWafer.map(r=>r.summary.wafer?.wafer??r.summary.wafer?.waferId).filter(r=>r!=null).map(String).join(", ");return p([...d(a.perWafer.map(r=>({metadata:r.summary.wafer}))),...l?[{label:"Wafers",value:l}]:[],{label:"Wafer count",value:String(a.stats.waferCount)},{label:"Generated",value:n}])}const t=e,i=t.stats.yieldPercent!==null?`${t.stats.yieldPercent.toFixed(1)}%`:"N/A";return p([...d([{metadata:t.wafer}]),{label:"Total dies",value:String(t.stats.totalDies)},{label:"Analysed dies",value:String(t.stats.analyzedDies)},{label:"Yield",value:i},{label:"Generated",value:n}])}function H(e,n){return e.length?e.map(t=>`<tr title="${o(y(t))}">
2
- <td class="tight">${R(t.severity)}</td>
3
- <td class="tight">${o(t.comparison.left)}</td>
4
- <td>${o(f(t.variable.label))}</td>
5
- <td class="numeric">${o(g(t))}</td>
6
- <td class="numeric">${o(v(t,n))}</td>
7
- </tr>`).join(`
8
- `):'<tr><td colspan="5" class="no-data">No significant findings</td></tr>'}function L(e,n){return`<table class="report-table findings-table compact">
9
- <thead>
10
- <tr>
11
- <th>Severity</th>
12
- <th>Region</th>
13
- <th>Metric</th>
14
- <th class="numeric">Delta</th>
15
- <th class="numeric">${n!==void 0?"Wafers":"N (region/rest)"}</th>
16
- </tr>
17
- </thead>
18
- <tbody>
19
- ${H(e,n)}
20
- </tbody>
21
- </table>`}export function renderFindingsReportHtml(e,n={}){const t=e.level==="lot"?b(e.perWafer.map(w=>w.summary.wafer)):void 0,i=n.title??(t?t.lotId!==void 0?`Lot ${t.lotId} Findings Report`:`Findings Report \u2014 ${h(t)}`:"Wafer Findings Report"),a=new Date().toLocaleString(),l=S(e.findings),r=e.level==="lot"?e.stats.waferCount:void 0,s=f(F(l)??""),u=s?`<p class="findings-narrative">${o(s)}</p>
22
- `:"",m=[c("Summary",T(e,a)),c("Findings",u+L(l,r))].join(`
1
+ import{describeWaferPopulation as w,populationLabel as b}from"./population.js";import{findingsTableHtml as g,buildMetadataRows as d,renderDefinitionList as p,renderSection as f,reportStyles as h}from"./reportHtml.js";import{escHtml as l}from"../core/utils.js";import{buildFindingsNarrative as v}from"./findingsNarrative.js";import{plainBinTerms as R}from"../renderer/fmt.js";import{visibleFindings as y}from"./filterFindings.js";function F(e,o){if(e.level==="lot"){const r=e,a=r.perWafer.map(n=>n.summary.wafer?.wafer??n.summary.wafer?.waferId).filter(n=>n!=null).map(String).join(", ");return p([...d(r.perWafer.map(n=>({metadata:n.summary.wafer}))),...a?[{label:"Wafers",value:a}]:[],{label:"Wafer count",value:String(r.stats.waferCount)},{label:"Generated",value:o}])}const t=e,i=t.stats.yieldPercent!==null?`${t.stats.yieldPercent.toFixed(1)}%`:"N/A";return p([...d([{metadata:t.wafer}]),{label:"Total dies",value:String(t.stats.totalDies)},{label:"Analysed dies",value:String(t.stats.analyzedDies)},{label:"Yield",value:i},{label:"Generated",value:o}])}export function renderFindingsReportHtml(e,o={}){const t=e.level==="lot"?w(e.perWafer.map(m=>m.summary.wafer)):void 0,i=o.title??(t?t.lotId!==void 0?`Lot ${t.lotId} Findings Report`:`Findings Report \u2014 ${b(t)}`:"Wafer Findings Report"),r=new Date().toLocaleString(),a=y(e.findings),n=e.level==="lot"?e.stats.waferCount:void 0,s=R(v(a)??""),c=s?`<p class="findings-narrative">${l(s)}</p>
2
+ `:"",u=[f("Summary",F(e,r)),f("Findings",c+g(a,n))].join(`
23
3
  `);return`<!DOCTYPE html>
24
4
  <html lang="en">
25
5
  <head>
26
6
  <meta charset="UTF-8">
27
- <title>${o(i)}</title>
7
+ <title>${l(i)}</title>
28
8
  <style>
29
- ${$()}
9
+ ${h()}
30
10
  </style>
31
11
  </head>
32
12
  <body>
33
13
  <main class="report">
34
14
  <header class="report-header">
35
- <h1>${o(i)}</h1>
36
- <p class="report-subtitle">Generated ${o(a)}</p>
15
+ <h1>${l(i)}</h1>
16
+ <p class="report-subtitle">Generated ${l(r)}</p>
37
17
  </header>
38
- ${m}
18
+ ${u}
39
19
  </main>
40
20
  </body>
41
- </html>`}export function openHtmlReport(e){if(typeof window.__openHtmlReport=="function"){window.__openHtmlReport(e);return}const n=URL.createObjectURL(new Blob([e],{type:"text/html"})),t=window.open(n,"_blank");setTimeout(()=>URL.revokeObjectURL(n),3e4),t||console.warn("wmap: window.open() returned null, so the report could not be shown. If this host cannot use window.open (e.g. Tauri, or popups are blocked), call setReportOpener() to supply your own opener.")}export function setReportOpener(e){window.__openHtmlReport=e}
21
+ </html>`}export function openHtmlReport(e){if(typeof window.__openHtmlReport=="function"){window.__openHtmlReport(e);return}const o=URL.createObjectURL(new Blob([e],{type:"text/html"})),t=window.open(o,"_blank");setTimeout(()=>URL.revokeObjectURL(o),3e4),t||console.warn("wmap: window.open() returned null, so the report could not be shown. If this host cannot use window.open (e.g. Tauri, or popups are blocked), call setReportOpener() to supply your own opener.")}export function setReportOpener(e){window.__openHtmlReport=e}