@wafertools/wafermap 0.31.0 → 0.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/AGENTS.md +13 -13
  2. package/CHANGELOG.md +334 -1
  3. package/README.md +3 -3
  4. package/dist/packages/canvas-adapter/charts/chartShell.d.ts +2 -12
  5. package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
  6. package/dist/packages/canvas-adapter/dieList.js +7 -7
  7. package/dist/packages/canvas-adapter/drilldown.js +1 -1
  8. package/dist/packages/canvas-adapter/insightsTab.js +1 -1
  9. package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
  10. package/dist/packages/canvas-adapter/renderWaferMap.d.ts +47 -4
  11. package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
  12. package/dist/packages/canvas-adapter/summaryPanel.d.ts +22 -3
  13. package/dist/packages/canvas-adapter/summaryPanel.js +4 -3
  14. package/dist/packages/canvas-adapter/toCanvas.d.ts +23 -2
  15. package/dist/packages/canvas-adapter/toCanvas.js +1 -1
  16. package/dist/packages/canvas-adapter/toolbar.d.ts +54 -1
  17. package/dist/packages/canvas-adapter/toolbar.js +2 -2
  18. package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
  19. package/dist/packages/canvas-adapter/userGuideHtml.js +100 -27
  20. package/dist/packages/canvas-adapter/version.d.ts +2 -2
  21. package/dist/packages/canvas-adapter/version.js +1 -1
  22. package/dist/packages/core/aggregates.d.ts +6 -0
  23. package/dist/packages/core/aggregates.js +1 -1
  24. package/dist/packages/core/compact.d.ts +205 -0
  25. package/dist/packages/core/compact.js +3 -0
  26. package/dist/packages/core/dieTable.d.ts +137 -0
  27. package/dist/packages/core/dieTable.js +1 -0
  28. package/dist/packages/core/dies.d.ts +22 -0
  29. package/dist/packages/core/dies.js +1 -1
  30. package/dist/packages/core/passBins.d.ts +6 -0
  31. package/dist/packages/core/passBins.js +1 -1
  32. package/dist/packages/core/utils.d.ts +14 -0
  33. package/dist/packages/core/utils.js +1 -1
  34. package/dist/packages/renderer/axisTicks.d.ts +14 -0
  35. package/dist/packages/renderer/axisTicks.js +1 -1
  36. package/dist/packages/renderer/buildView.d.ts +29 -4
  37. package/dist/packages/renderer/buildView.js +1 -1
  38. package/dist/packages/renderer/buildWaferMap.d.ts +29 -11
  39. package/dist/packages/renderer/buildWaferMap.js +1 -1
  40. package/dist/packages/renderer/columnarInput.d.ts +87 -0
  41. package/dist/packages/renderer/columnarInput.js +1 -0
  42. package/dist/packages/renderer/compactView.d.ts +22 -0
  43. package/dist/packages/renderer/compactView.js +1 -0
  44. package/dist/packages/renderer/derivedTests/apply.d.ts +8 -6
  45. package/dist/packages/renderer/derivedTests/apply.js +1 -1
  46. package/dist/packages/renderer/index.d.ts +1 -0
  47. package/dist/packages/renderer/spec.d.ts +3 -7
  48. package/dist/packages/renderer/spec.js +1 -1
  49. package/dist/packages/renderer/xyIndicator.d.ts +27 -0
  50. package/dist/packages/renderer/xyIndicator.js +1 -0
  51. package/dist/packages/stats/analyzeWaferLot.d.ts +28 -0
  52. package/dist/packages/stats/analyzeWaferLot.js +1 -1
  53. package/dist/packages/stats/analyzeWaferMap.d.ts +169 -2
  54. package/dist/packages/stats/analyzeWaferMap.js +1 -1
  55. package/dist/packages/stats/binRows.d.ts +38 -0
  56. package/dist/packages/stats/binRows.js +1 -0
  57. package/dist/packages/stats/boxplot.js +1 -1
  58. package/dist/packages/stats/capability.d.ts +7 -7
  59. package/dist/packages/stats/capability.js +1 -1
  60. package/dist/packages/stats/clusterDetection.js +1 -1
  61. package/dist/packages/stats/connectedComponents.js +1 -1
  62. package/dist/packages/stats/correlation.js +1 -1
  63. package/dist/packages/stats/filterFindings.d.ts +30 -0
  64. package/dist/packages/stats/filterFindings.js +1 -1
  65. package/dist/packages/stats/findingText.d.ts +9 -0
  66. package/dist/packages/stats/findingText.js +2 -0
  67. package/dist/packages/stats/histogram.js +1 -1
  68. package/dist/packages/stats/lotDrift.d.ts +16 -0
  69. package/dist/packages/stats/lotDrift.js +1 -0
  70. package/dist/packages/stats/lotPattern.d.ts +33 -0
  71. package/dist/packages/stats/lotPattern.js +1 -0
  72. package/dist/packages/stats/math.d.ts +73 -6
  73. package/dist/packages/stats/math.js +1 -1
  74. package/dist/packages/stats/normalizeInput.js +1 -1
  75. package/dist/packages/stats/patternClassification.d.ts +34 -3
  76. package/dist/packages/stats/patternClassification.js +1 -1
  77. package/dist/packages/stats/presentation.d.ts +42 -0
  78. package/dist/packages/stats/presentation.js +1 -0
  79. package/dist/packages/stats/regions.d.ts +16 -0
  80. package/dist/packages/stats/regions.js +1 -1
  81. package/dist/packages/stats/renderSummaryReport.d.ts +15 -2
  82. package/dist/packages/stats/renderSummaryReport.js +32 -27
  83. package/dist/packages/stats/reportHtml.d.ts +54 -9
  84. package/dist/packages/stats/reportHtml.js +130 -47
  85. package/dist/packages/stats/scatter.js +1 -1
  86. package/dist/packages/stats/sweep.js +1 -1
  87. package/dist/packages/stats/synthesis.d.ts +66 -0
  88. package/dist/packages/stats/synthesis.js +2 -0
  89. package/dist/packages/stats/testPassRate.js +1 -1
  90. package/dist/packages/stats/trend.js +1 -1
  91. package/dist/packages/stats/types.d.ts +6 -1
  92. package/dist/packages/stats/yieldRows.d.ts +31 -0
  93. package/dist/packages/stats/yieldRows.js +1 -0
  94. package/dist/packages/worker/index.js +1 -1
  95. package/dist/packages/worker/wafermap.worker.d.ts +3 -0
  96. package/dist/packages/worker/wafermap.worker.js +1 -1
  97. package/llms.txt +1 -1
  98. package/package.json +1 -1
  99. package/dist/packages/stats/findingsNarrative.d.ts +0 -3
  100. package/dist/packages/stats/findingsNarrative.js +0 -1
@@ -0,0 +1,16 @@
1
+ import type { StatsFinding, StatsSummary } from './types.js';
2
+ /** A trend needs this many wafers: with fewer, even a perfect run of steps cannot clear the significance level. */
3
+ export declare const DRIFT_MIN_WAFERS = 5;
4
+ type PerWafer = ReadonlyArray<{
5
+ waferIndex: number;
6
+ summary: StatsSummary;
7
+ }>;
8
+ /**
9
+ * Trend findings for a lot: yield across the wafers, and each test's mean across them. The p-values go
10
+ * through one Benjamini–Hochberg correction together (the tests are many), then a finding must also move
11
+ * far enough to matter (a point or two of yield, half a σ of a test): a long run of tiny steps is
12
+ * significant and not worth saying.
13
+ */
14
+ export declare function buildDriftFindings(perWafer: PerWafer, significanceLevel: number): StatsFinding[];
15
+ export {};
16
+ //# sourceMappingURL=lotDrift.d.ts.map
@@ -0,0 +1 @@
1
+ import{benjaminiHochberg as v,mannKendall as b}from"./math.js";import{fmt as y}from"../renderer/fmt.js";export const DRIFT_MIN_WAFERS=5;const N=2,M=.5,S=5,x=1;function _(u){const d=[],a=u.map(n=>({waferIndex:n.waferIndex,y:n.summary.stats.yieldPercent})).filter(n=>n.y!==null&&n.y!==void 0);if(a.length>=DRIFT_MIN_WAFERS){const n=b(a.map(e=>e.y));n&&d.push({kind:"yield",label:"Yield",values:a.map(e=>e.y),waferIndices:a.map(e=>e.waferIndex),mk:n})}const r=new Map;for(const{waferIndex:n,summary:e}of u)for(const t of e.stats.perTestStats??[]){if(!Number.isFinite(t.mean)||!(t.count>0))continue;const l=r.get(t.testNumber)??{label:t.label,rows:[]};l.rows.push({waferIndex:n,mean:t.mean,n:t.count,sd:t.stddev}),r.set(t.testNumber,l)}for(const[n,{label:e,rows:t}]of r){if(t.length<DRIFT_MIN_WAFERS)continue;const l=b(t.map(s=>s.mean));if(!l)continue;const m=t.reduce((s,f)=>s+f.n,0),o=Math.sqrt(t.reduce((s,f)=>s+f.n*f.sd**2,0)/m);d.push({kind:"test",testNumber:n,label:e,values:t.map(s=>s.mean),waferIndices:t.map(s=>s.waferIndex),sigma:o,mk:l})}return d}export function buildDriftFindings(u,d){const a=_(u);if(!a.length)return[];const r=v(a.map(e=>e.mk.pValue)),n=[];return a.forEach((e,t)=>{if(r[t]>d)return;const{slope:l,intercept:m,n:o}=e.mk,s=m,i=m+l*(o-1)-s,p=i>0,h=e.kind==="test"&&e.sigma&&e.sigma>0?Math.abs(i)/e.sigma:void 0;if(e.kind==="yield"?Math.abs(i)<N:h===void 0||h<M)return;const g=(e.kind==="yield"?Math.abs(i)>=S:(h??0)>=x)&&r[t]<=.01?"unusual":"notable",k=p?"rises":"falls",c={first:e.values[0],last:e.values[e.values.length-1]},I=e.kind==="yield"?`${c.first.toFixed(1)}% \u2192 ${c.last.toFixed(1)}%`:`${y(c.first)} \u2192 ${y(c.last)}`,w=e.kind==="yield"?"on successive wafers":"across the wafers";n.push({id:e.kind==="yield"?"drift:yield":`drift:test:${e.testNumber}`,level:"inter-wafer",severity:g,variable:e.kind==="yield"?{kind:"yield",label:"Yield"}:{kind:"test",index:e.testNumber,label:e.label},comparison:{family:"wafer",left:"Wafers in input order",right:"a constant level"},effect:{direction:p?"higher":"lower",absoluteDelta:e.kind==="yield"?i/100:i,...e.kind==="yield"?{}:{relativeDelta:s!==0?i/Math.abs(s):void 0},effectSize:e.kind==="yield"?i/100:i/(e.sigma||1)},stats:{method:"mann-kendall",pValue:e.mk.pValue,adjustedPValue:r[t],sampleSizeLeft:o,sampleSizeRight:0},summary:`${e.label} ${k} ${w}: ${I} over ${o} wafers (input order)`,highlight:{kind:"wafer",waferIndices:e.waferIndices}})}),n}
@@ -0,0 +1,33 @@
1
+ import type { WaferMapResult } from '../renderer/buildWaferMap.js';
2
+ import { type PatternClassification } from './patternClassification.js';
3
+ import { type ResolvedOptions } from './analyzeWaferMap.js';
4
+ /** A lot's spatial pattern: where its failures recur, what shape that is, and which wafers show it. */
5
+ export interface LotPattern {
6
+ /** The classifier's reading of the recurring positions — never 'random' or 'none'. */
7
+ classification: PatternClassification;
8
+ /** Each wafer that shows the pattern → its failing dies inside the recurring area. */
9
+ carriers: Map<number, string[]>;
10
+ }
11
+ /**
12
+ * The lot's spatial pattern, decided from the lot rather than by counting each
13
+ * wafer's single best label — so one pattern that the classifier reads two ways
14
+ * on different wafers (an elongated centre cluster called a scratch, a faint
15
+ * edge ring called random) is still one pattern.
16
+ *
17
+ * 1. Stack the wafers by die position. A position is *recurring* when more wafers
18
+ * fail in its 3×3 neighbourhood than the lot's overall fail rate explains
19
+ * (an exact binomial test, Benjamini–Hochberg across positions at the
20
+ * analysis's significance level) and it fails above that rate itself. A systematic cause fails the same dies wafer after
21
+ * wafer; random defects and patterns that move (scratches, particles) do not.
22
+ * 2. Classify the recurring positions as a map of their own — the same
23
+ * classifier and thresholds as a single wafer.
24
+ * 3. A wafer shows the pattern when its own failures are concentrated in that
25
+ * area: its fail rate inside against outside, by the same two-proportion test
26
+ * and effect gate as any regional finding, Benjamini–Hochberg across wafers.
27
+ *
28
+ * Wafers stack only with wafers on the same grid as the first (a lot stack, or a
29
+ * wafer of another geometry, is left out and keeps its own classification).
30
+ * Returns null when nothing recurs, or what recurs has no shape.
31
+ */
32
+ export declare function findLotPattern(results: readonly WaferMapResult[], options: ResolvedOptions): LotPattern | null;
33
+ //# sourceMappingURL=lotPattern.d.ts.map
@@ -0,0 +1 @@
1
+ import{getDieKey as R,isPositionedDie as v,isYieldEligibleDie as L,positionKey as h}from"../core/dies.js";import{itemPassBins as j}from"../core/passBins.js";import{classifyFailingDies as C,patternFailVerdict as K}from"./patternClassification.js";import{benjaminiHochberg as D,binomialUpperTail as F}from"./math.js";import{passesRateGate as G,rateRelativeEffect as T,twoProportionZ as V,zPValue as W}from"./analyzeWaferMap.js";const q=.01;export function findLotPattern(H,c){const l=[];let f,k=0,w=0;if(H.forEach((e,n)=>{if(e.isLotStack)return;const s=new Set(j(e)),i=[],t=[];for(const a of e.dies){if(!L(a)||!v(a))continue;const o=K(a,s);o!==void 0&&(i.push(a),t.push(o))}if(!i.length)return;const r=(a,o)=>Math.abs(a-o)<=q*Math.max(Math.abs(a),Math.abs(o));if(!f)f=e,k=i[0].width,w=i[0].height;else if(!r(e.wafer.radius,f.wafer.radius)||!r(i[0].width,k)||!r(i[0].height,w))return;l.push({index:n,dies:i,fails:t})}),!f||l.length<2)return null;const d=new Map;let z=0,m=0;for(const e of l)e.dies.forEach((n,s)=>{const i=h(n);let t=d.get(i);t||d.set(i,t={die:n,n:0,k:0}),t.n++,e.fails[s]&&(t.k++,m++),z++});if(m===0)return null;const E=m/z,p=[...d.values()],M=p.map(e=>{let n=0,s=0;for(let i=-1;i<=1;i++)for(let t=-1;t<=1;t++){const r=i===0&&t===0?e:d.get(h({x:e.die.x+i,y:e.die.y+t}));r&&(n+=r.n,s+=r.k)}return{n,k:s}}),N=D(M.map(e=>F(e.k,e.n,E))),x=new Set,S=[];p.forEach((e,n)=>{N[n]>c.significanceLevel||e.k/e.n<=E||(x.add(h(e.die)),S.push(e.die))});const u=C(S,p.map(e=>e.die),f.wafer,f.ringCount??c.ringCount);if(!u||u.pattern==="random"||u.pattern==="none")return null;const b=l.map(e=>{let n=0,s=0,i=0,t=0;const r=[];e.dies.forEach((o,y)=>{x.has(h(o))?(s++,e.fails[y]&&(n++,r.push(R(o)))):(t++,e.fails[y]&&i++)});const a=s>=c.minimumSampleSize&&t>=c.minimumSampleSize?V(n,s,i,t):0;return{index:e.index,z:a,hits:n,n:s,restHits:i,restN:t,inside:r}}),P=D(b.map(e=>e.z>0?W(e.z):1)),g=new Map;return b.forEach((e,n)=>{if(e.z<=0)return;const s=e.hits/e.n-e.restHits/e.restN,i=T({hits:e.hits,n:e.n,restHits:e.restHits,restN:e.restN,passRate:!1});G(P[n],s,i,c)&&g.set(e.index,e.inside)}),g.size?{classification:u,carriers:g}:null}
@@ -5,31 +5,98 @@
5
5
  export declare function errorFunction(value: number): number;
6
6
  /** Standard normal cumulative distribution function Φ(value). */
7
7
  export declare function normalCdf(value: number): number;
8
+ /**
9
+ * Exact one-sided binomial tail P(X ≥ k) for X ~ Binomial(n, p). Exact rather
10
+ * than a normal approximation because it is asked about small n — how many of a
11
+ * lot's few wafers fail at one die position — where the approximation is poor.
12
+ */
13
+ export declare function binomialUpperTail(k: number, n: number, p: number): number;
14
+ /**
15
+ * Benjamini–Hochberg adjusted p-values, in the order given. The one
16
+ * false-discovery-rate rule in the library: findings (`adjustPValues`) and the
17
+ * die positions a lot's failures recur at (`findLotPattern`) both go through it.
18
+ */
19
+ export declare function benjaminiHochberg(pValues: readonly number[]): number[];
8
20
  /** Linear-interpolation quantile of a pre-sorted array (`q` in [0, 1]).
9
21
  *
10
22
  * `ArrayLike<number>`, not `number[]`, so a caller holding its values in a
11
23
  * `Float64Array` does not have to copy them back into a plain array to be
12
- * read — see `pooledTestStatsSteps`, which sorts in one. */
24
+ * read. */
13
25
  export declare function quantile(sorted: ArrayLike<number>, q: number): number;
14
- /** Descriptive statistics of one population of values — see {@link describeSorted}. */
26
+ /** Robust outlier fence over a value list: Tukey's `Q1 − k·IQR … Q3 + k·IQR`.
27
+ *
28
+ * Deliberately NOT mean ± 3σ. σ is computed FROM the data including the
29
+ * outlier, so a single reading of 1e30 inflates σ far enough that the fence no
30
+ * longer excludes it — the classic masking failure, and it fails hardest exactly
31
+ * when the outlier is worst. Quartiles are unmoved by the extreme tail.
32
+ *
33
+ * The one outlier rule in the library: the histogram's outlier clip (per-die
34
+ * values) and outlier wafers (per-wafer yields — `waferYieldFences` in
35
+ * analyzeWaferLot.ts, read by the lot findings and the Summary panel's yield
36
+ * bars) both go through it.
37
+ *
38
+ * Returns null when there are fewer than `minCount` values — too few for
39
+ * quartiles to mean anything — or when the IQR is zero. */
40
+ export declare function robustFence(values: ArrayLike<number>, k?: number, minCount?: number): {
41
+ lo: number;
42
+ hi: number;
43
+ } | null;
44
+ /** Descriptive statistics of one population of values — see {@link describeValues}. */
15
45
  export interface DescriptiveStats {
16
46
  min: number;
17
47
  max: number;
18
48
  mean: number;
19
49
  count: number;
20
- /** Population standard deviation (divide by n) — see {@link describeSorted}. */
50
+ /** Population standard deviation (divide by n) — see {@link describeValues}. */
21
51
  stddev: number;
22
52
  median: number;
23
53
  q1: number;
24
54
  q3: number;
25
55
  }
26
- /** Min/max/mean/count/σ and the quartiles of a pre-sorted array — the one copy
27
- * of the descriptive-statistics formulas behind the Test Values table.
56
+ /** Min/max/mean/count/σ and the quartiles of `values` — the one copy of the
57
+ * descriptive-statistics formulas behind the Test Values table.
58
+ *
59
+ * **Reorders `values`** (a partial sort, in place): the quartiles are found by
60
+ * selection, not by sorting every value. They are the same order statistics a
61
+ * sort would give, interpolated as {@link quantile} does, so the figures are
62
+ * identical; a full sort was ~24 ms per test at 266k values in Chrome and made
63
+ * up almost all of a large lot panel's time. The mean and σ sum in whatever
64
+ * order `values` arrives in.
28
65
  *
29
66
  * σ is the POPULATION standard deviation (divide by n), which is what this
30
67
  * table has always shown; the capability indices next to it deliberately use
31
68
  * the sample form (n−1) and say so. Two-pass, not `Σx² − n·x̄²`: the moments
32
69
  * form loses most of its significant digits when the variance is small beside
33
70
  * the mean, which is the normal shape of a passing parametric test. */
34
- export declare function describeSorted(sorted: ArrayLike<number>): DescriptiveStats;
71
+ export declare function describeValues(values: Float64Array): DescriptiveStats;
72
+ /** Min, quartiles and max of `values` — what sorting them and reading the
73
+ * ends and {@link quantile}s would give — by selection. **Reorders `values`.** */
74
+ export declare function fiveNumberSummary(values: Float64Array): {
75
+ min: number;
76
+ q1: number;
77
+ median: number;
78
+ q3: number;
79
+ max: number;
80
+ };
81
+ export interface MannKendall {
82
+ /** Points in the series. */
83
+ n: number;
84
+ /** The Mann–Kendall statistic: concordant minus discordant pairs. */
85
+ s: number;
86
+ /** S with continuity correction over its tie-corrected standard deviation. */
87
+ z: number;
88
+ /** Two-sided p-value of `z` against no monotonic trend (normal approximation). */
89
+ pValue: number;
90
+ /** The Theil–Sen slope: the median of every pair's slope, per step. Robust to a few wild points. */
91
+ slope: number;
92
+ /** The Theil–Sen line's value at the first point, so `intercept + slope × i` is the fitted series. */
93
+ intercept: number;
94
+ }
95
+ /**
96
+ * The Mann–Kendall test for a monotonic trend, with the Theil–Sen slope that sizes it. Rank-based, so
97
+ * a trend need not be linear and one wild point cannot make or break it. The normal approximation is
98
+ * conservative at small n (n = 5, a perfect trend, p ≈ 0.028 where the exact value is ≈ 0.017), which is
99
+ * the right side to err on. `null` below three points or when every value is the same.
100
+ */
101
+ export declare function mannKendall(values: readonly number[]): MannKendall | null;
35
102
  //# sourceMappingURL=math.d.ts.map
@@ -1 +1 @@
1
- export function errorFunction(n){const t=n<0?-1:1,c=Math.abs(n),a=.254829592,i=-.284496736,o=1.421413741,r=-1.453152027,s=1.061405429,e=1/(1+.3275911*c),u=1-((((s*e+r)*e+o)*e+i)*e+a)*e*Math.exp(-c*c);return t*u}export function normalCdf(n){return .5*(1+errorFunction(n/Math.sqrt(2)))}export function quantile(n,t){if(n.length===0)return NaN;const c=t*(n.length-1),a=Math.floor(c),i=Math.ceil(c);return n[a]+(n[i]-n[a])*(c-a)}export function describeSorted(n){const t=n.length;let c=0;for(let o=0;o<t;o++)c+=n[o];const a=c/t;let i=0;for(let o=0;o<t;o++)i+=(n[o]-a)**2;return{min:n[0],max:n[t-1],mean:a,count:t,stddev:Math.sqrt(i/t),median:quantile(n,.5),q1:quantile(n,.25),q3:quantile(n,.75)}}
1
+ export function errorFunction(t){const o=t<0?-1:1,n=Math.abs(t),e=.254829592,r=-.284496736,c=1.421413741,i=-1.453152027,s=1.061405429,l=1/(1+.3275911*n),u=1-((((s*l+i)*l+c)*l+r)*l+e)*l*Math.exp(-n*n);return o*u}export function normalCdf(t){return .5*(1+errorFunction(t/Math.sqrt(2)))}export function binomialUpperTail(t,o,n){if(t<=0)return 1;if(t>o||n<=0)return 0;if(n>=1)return 1;let e=t*Math.log(n)+(o-t)*Math.log1p(-n);for(let s=0;s<t;s++)e+=Math.log(o-s)-Math.log(s+1);let r=Math.exp(e),c=0;const i=n/(1-n);for(let s=t;s<=o;s++)c+=r,r*=(o-s)/(s+1)*i;return Math.min(1,c)}export function benjaminiHochberg(t){const o=t.length,n=t.map((c,i)=>i).sort((c,i)=>t[c]-t[i]),e=new Array(o);let r=1;for(let c=o-1;c>=0;c--){const i=n[c];r=Math.min(r,t[i]*o/(c+1),1),e[i]=r}return e}export function quantile(t,o){if(t.length===0)return NaN;const n=o*(t.length-1),e=Math.floor(n),r=Math.ceil(n);return t[e]+(t[r]-t[e])*(n-e)}export function robustFence(t,o=1.5,n=8){const e=Float64Array.from(Array.from(t).filter(s=>Number.isFinite(s)));if(e.length<n)return null;const{q1:r,q3:c}=fiveNumberSummary(e),i=c-r;return i===0?null:{lo:r-o*i,hi:c+o*i}}export function describeValues(t){const o=t.length;let n=0;for(let u=0;u<o;u++)n+=t[u];const e=n/o;let r=0;for(let u=0;u<o;u++)r+=(t[u]-e)**2;const{min:c,q1:i,median:s,q3:h,max:l}=fiveNumberSummary(t);return{min:c,max:l,mean:e,count:o,stddev:Math.sqrt(r/o),median:s,q1:i,q3:h}}export function fiveNumberSummary(t){let o=1/0,n=-1/0;for(let i=0;i<t.length;i++){const s=t[i];s<o&&(o=s),s>n&&(n=s)}t.length===0&&(o=NaN,n=NaN);const{q1:e,median:r,q3:c}=b(t);return{min:o,q1:e,median:r,q3:c,max:n}}function b(t){const o=t.length;if(o===0)return{q1:NaN,median:NaN,q3:NaN};const n=o-1,e=(p,g,f)=>{const a=p*n,m=Math.floor(a),q=N(t,m,g,f);if(a===m)return q;let M=1/0;for(let x=m+1;x<=f;x++)t[x]<M&&(M=t[x]);return q+(M-q)*(a-m)},r=Math.floor(.5*n),c=e(.5,0,n),i=Math.floor(.25*n),s=i===r?e(.25,r,n):u(.25,i,r),l=Math.floor(.75*n)===r?e(.75,r,n):e(.75,r+1,n);return{q1:s,median:c,q3:l};function u(p,g,f){const a=p*n,m=N(t,g,0,f-1);if(a===g)return m;let q=t[f];for(let M=g+1;M<f;M++)t[M]<q&&(q=t[M]);return m+(q-m)*(a-g)}}function N(t,o,n,e){for(;n<e;){const r=t[n],c=t[n+e>>>1],i=t[e],s=r<c?c<i?c:r<i?i:r:r<i?r:c<i?i:c;let h=n,l=e;for(;h<=l;){for(;t[h]<s;)h++;for(;t[l]>s;)l--;if(h<=l){const u=t[h];t[h]=t[l],t[l]=u,h++,l--}}if(o<=l)e=l;else if(o>=h)n=h;else break}return t[o]}export function mannKendall(t){const o=t.length;if(o<3)return null;let n=0;const e=[];for(let f=0;f<o-1;f++)for(let a=f+1;a<o;a++){const m=t[a]-t[f];n+=m>0?1:m<0?-1:0,e.push(m/(a-f))}const r=new Map;for(const f of t)r.set(f,(r.get(f)??0)+1);let c=o*(o-1)*(2*o+5)/18;for(const f of r.values())f>1&&(c-=f*(f-1)*(2*f+5)/18);if(c<=0)return null;const i=n===0?0:(n-Math.sign(n))/Math.sqrt(c),s=Math.min(1,2*(1-normalCdf(Math.abs(i))));e.sort((f,a)=>f-a);const h=e.length>>1,l=e.length%2?e[h]:(e[h-1]+e[h])/2,u=t.map((f,a)=>f-l*a).sort((f,a)=>f-a),p=u.length>>1,g=u.length%2?u[p]:(u[p-1]+u[p])/2;return{n:o,s:n,z:i,pValue:s,slope:l,intercept:g}}
@@ -1 +1 @@
1
- import{buildWaferMap as e}from"../renderer/buildWaferMap.js";export function normalizeInput(r){return"wafer"in r&&"dies"in r&&"view"in r?r:e(r)}
1
+ import{buildWaferMap as o,isBuiltMap as a}from"../renderer/buildWaferMap.js";export function normalizeInput(r){return a(r)?r:o(r)}
@@ -70,6 +70,10 @@ export interface PatternThresholds {
70
70
  scratchLinearScore: number;
71
71
  scratchLinearScoreHigh: number;
72
72
  scratchEccentricity: number;
73
+ /** A scratch's salient region must reach at least this far out (maxDistNorm): a
74
+ * scratch is long, so an elongated blob wholly inside the central zone is a
75
+ * centre cluster, not a scratch. */
76
+ scratchMinReach: number;
73
77
  minimumFailingDies: number;
74
78
  }
75
79
  /**
@@ -81,16 +85,38 @@ export interface PatternThresholds {
81
85
  * | Pattern | Recall | Notes |
82
86
  * |------------|--------|--------------------------------------------|
83
87
  * | Near-full | 100% | |
84
- * | Edge-ring | 74% | |
88
+ * | Edge-ring | 75% | |
85
89
  * | Edge-local | 65% | |
86
90
  * | Center | 60% | |
87
91
  * | Random | 59% | |
88
- * | Scratch | 26% | Fragmented patterns harder to detect |
92
+ * | Scratch | 24% | Fragmented patterns harder to detect |
89
93
  * | Donut | 15% | Geometrically similar to center with noise |
90
94
  *
91
- * Overall accuracy: 64% exact match, 86% detection rate (any pattern flagged).
95
+ * Overall accuracy: 64% exact match, 86.4% detection rate (any pattern flagged).
92
96
  */
93
97
  export declare const DEFAULT_PATTERN_THRESHOLDS: PatternThresholds;
98
+ /** How each pattern is named to an engineer — the wafer's finding and the lot's. */
99
+ export declare const PATTERN_LABELS: Record<PatternLabel, string>;
100
+ /**
101
+ * Whether a die counts as failing for pattern classification: its hard bin, or
102
+ * failing that its soft bin, is not a pass bin. `undefined` for a die with no
103
+ * bin — no data, neither passing nor failing.
104
+ */
105
+ export declare function patternFailVerdict(die: {
106
+ hbin?: number;
107
+ sbin?: number;
108
+ }, passSet: ReadonlySet<number>): boolean | undefined;
109
+ /**
110
+ * The finding families a spatial pattern explains, and so lists under itself:
111
+ * the ring an edge or centre pattern lies in, the edge arcs of an edge pattern,
112
+ * the clusters of a centre cluster or a scratch. One rule for the wafer's
113
+ * pattern and the lot's.
114
+ *
115
+ * `rings` are the ring numbers a ring finding covers (none for other families);
116
+ * edge patterns explain the outer ring, a centre cluster the core ring, a donut
117
+ * the rings between.
118
+ */
119
+ export declare function patternExplains(pattern: PatternLabel, family: string, rings: number[], ringCount: number): boolean;
94
120
  /**
95
121
  * Classify the spatial failure pattern of a wafer from its die data.
96
122
  *
@@ -105,4 +131,9 @@ export declare function classifyPattern(dies: PositionedDie[], wafer: Wafer, opt
105
131
  passBins: number[];
106
132
  ringCount?: number;
107
133
  }): PatternClassification | null;
134
+ /**
135
+ * {@link classifyPattern} over a failing set already chosen — a wafer's failing
136
+ * dies, or the positions at which a lot's failures recur (`findLotPattern`).
137
+ */
138
+ export declare function classifyFailingDies(failing: PositionedDie[], dies: PositionedDie[], wafer: Wafer, ringCount: number): PatternClassification | null;
108
139
  //# sourceMappingURL=patternClassification.d.ts.map
@@ -1 +1 @@
1
- import{findConnectedComponents as J}from"./connectedComponents.js";import{clamp01 as K}from"../core/utils.js";export const DEFAULT_PATTERN_THRESHOLDS={nearFullGlobalRdd:.6,edgeRingEdgeRdd:.18,edgeRingEdgeRddHigh:.3,edgeRingRadialSpread:.45,edgeRingAngularSpread:.6,edgeLocalEdgeRdd:.1,edgeLocalCentroidDist:.76,donutP25Dist:.4,centerCentroidDist:.22,centerCentroidDistHigh:.1,centerEdgeRdd:.35,donutEdgeRdd:.22,scratchLinearScore:.25,scratchLinearScoreHigh:.45,scratchEccentricity:.75,minimumFailingDies:5};function F(t,o,s){const i=t.physX-o.center.x,r=t.physY-o.center.y,d=Math.sqrt(i*i+r*r)/o.radius;return Math.min(s,Math.max(1,Math.floor(d*s)+1))===s}function Q(t,o,s){if(t.length<3)return 0;let i=0,r=0,d=0;for(const g of t){const l=g.physX-o,D=g.physY-s;i+=l*l,r+=D*D,d+=l*D}i/=t.length,r/=t.length,d/=t.length;const c=i+r,n=i*r-d*d,a=Math.max(0,(c/2)**2-n),m=c/2+Math.sqrt(a),R=c/2-Math.sqrt(a);if(m<=0)return 0;const f=K(R/m);return Math.sqrt(1-f)}function V(t,o){if(o===0)return 0;const s=new Map,i=new Map,r=new Map,d=new Map;for(const n of t)s.set(n.y,(s.get(n.y)??0)+1),i.set(n.x,(i.get(n.x)??0)+1),r.set(n.x-n.y,(r.get(n.x-n.y)??0)+1),d.set(n.x+n.y,(d.get(n.x+n.y)??0)+1);let c=0;for(const n of s.values())n>c&&(c=n);for(const n of i.values())n>c&&(c=n);for(const n of r.values())n>c&&(c=n);for(const n of d.values())n>c&&(c=n);return c/o}function W(t,o,s,i){const r=o.length,d=t.length,c=r>0?d/r:0,n=o.filter(e=>F(e,s,i)),a=t.filter(e=>F(e,s,i)),m=n.length>0?a.length/n.length:0,R=J(t),f=R.length>0?[...R].sort((e,h)=>h.length-e.length):[t],g=f[0],l=f.slice(0,5).flat(),D=g.length,O=d>0?D/d:0,u=s.center.x,p=s.center.y,y=s.radius;let E=0,N=0,M=1/0,x=0;for(const e of g){E+=e.physX,N+=e.physY;const h=Math.sqrt((e.physX-u)**2+(e.physY-p)**2);h<M&&(M=h),h>x&&(x=h)}const H=E/g.length,P=N/g.length,T=Math.sqrt((H-u)**2+(P-p)**2)/y,I=M===1/0?0:M/y,v=Math.min(x/y,1),C=t.map(e=>Math.sqrt((e.physX-u)**2+(e.physY-p)**2)/y).sort((e,h)=>e-h),w=C[Math.floor(C.length*.25)]??0,b=o.filter(e=>Math.sqrt((e.physX-u)**2+(e.physY-p)**2)/y<.5).length,X=o.filter(e=>Math.sqrt((e.physX-u)**2+(e.physY-p)**2)/y>=.5).length,z=t.filter(e=>Math.sqrt((e.physX-u)**2+(e.physY-p)**2)/y<.5).length,A=t.filter(e=>Math.sqrt((e.physX-u)**2+(e.physY-p)**2)/y>=.5).length,Y=b>0?z/b:0,L=X>0?A/X:0,_=L>0?Y/L:Y>0?2:1,G=l.reduce((e,h)=>e+h.physX,0)/l.length,k=l.reduce((e,h)=>e+h.physY,0)/l.length,U=Q(l,G,k),B=V(l,l.length),S=16,q=new Set;for(const e of a){const h=(Math.atan2(e.physY-p,e.physX-u)+2*Math.PI)%(2*Math.PI);q.add(Math.floor(h/(2*Math.PI)*S)%S)}const j=a.length>0?q.size/S:0;return{globalRdd:c,edgeRdd:m,centroidDistNorm:T,minDistNorm:I,maxDistNorm:v,p25DistNorm:w,eccentricity:U,linearScore:B,salienceSize:D,salienceFraction:O,edgeAngularSpread:j,innerOuterRatio:_}}function Z(t,o,s){const i=Math.max(5,Math.round(s*.003)),r=t.salienceSize>=i;if(t.globalRdd<o.nearFullGlobalRdd&&t.salienceFraction<.1)return{pattern:"random",confidence:"low"};if(t.globalRdd>=o.nearFullGlobalRdd)return{pattern:"near-full",confidence:t.globalRdd>=.8?"high":"medium"};if(r&&t.linearScore>=o.scratchLinearScore&&t.eccentricity>=o.scratchEccentricity){const a=t.linearScore>=o.scratchLinearScoreHigh?"high":"medium";return{pattern:"scratch",confidence:a,note:a==="medium"?"Scratch detection is less reliable for fragmented or diagonal patterns":void 0}}const d=r&&t.edgeRdd>=o.edgeLocalEdgeRdd&&t.maxDistNorm>=.7;if(d&&t.p25DistNorm>=o.edgeLocalCentroidDist&&t.maxDistNorm-t.minDistNorm<=o.edgeRingRadialSpread)return{pattern:"edge-ring",confidence:t.edgeRdd>=o.edgeRingEdgeRddHigh?"high":"medium"};if(d&&t.p25DistNorm>=.55&&t.p25DistNorm<o.edgeLocalCentroidDist)return{pattern:"edge-local",confidence:t.edgeRdd>=o.edgeRingEdgeRdd?"medium":"low"};const c=r&&t.centroidDistNorm<=o.centerCentroidDist&&t.edgeRdd<=o.centerEdgeRdd,n="Center and donut patterns have similar geometry; classification may be imprecise";if(c&&t.p25DistNorm<o.donutP25Dist){const a=t.centroidDistNorm<=o.centerCentroidDistHigh?"high":"medium";return{pattern:"center",confidence:a,note:a!=="high"?n:void 0}}return c&&t.p25DistNorm>=o.donutP25Dist&&t.p25DistNorm<.55?{pattern:"donut",confidence:"medium",note:n}:{pattern:"random",confidence:"low"}}export function classifyPattern(t,o,s){const i={...DEFAULT_PATTERN_THRESHOLDS},r=s.ringCount??4,d=new Set(s.passBins),c=t.filter(g=>{const l=g.hbin??g.sbin;return l!==void 0&&!d.has(l)}),n=Math.max(i.minimumFailingDies,Math.round(t.length*.003));if(c.length<n)return null;const a=W(c,t,o,r),{pattern:m,confidence:R,note:f}=Z(a,i,t.length);return{pattern:m,confidence:R,features:a,...f?{note:f}:{}}}
1
+ import{findConnectedComponents as j}from"./connectedComponents.js";import{clamp01 as J}from"../core/utils.js";export const DEFAULT_PATTERN_THRESHOLDS={nearFullGlobalRdd:.6,edgeRingEdgeRdd:.18,edgeRingEdgeRddHigh:.3,edgeRingRadialSpread:.45,edgeRingAngularSpread:.6,edgeLocalEdgeRdd:.1,edgeLocalCentroidDist:.76,donutP25Dist:.4,centerCentroidDist:.22,centerCentroidDistHigh:.1,centerEdgeRdd:.35,donutEdgeRdd:.22,scratchLinearScore:.25,scratchLinearScoreHigh:.45,scratchEccentricity:.75,scratchMinReach:.35,minimumFailingDies:5};function q(e,t,r){const d=e.physX-t.center.x,i=e.physY-t.center.y,s=Math.sqrt(d*d+i*i)/t.radius;return Math.min(r,Math.max(1,Math.floor(s*r)+1))===r}function K(e,t,r){if(e.length<3)return 0;let d=0,i=0,s=0;for(const f of e){const g=f.physX-t,R=f.physY-r;d+=g*g,i+=R*R,s+=g*R}d/=e.length,i/=e.length,s/=e.length;const c=d+i,n=d*i-s*s,a=Math.max(0,(c/2)**2-n),h=c/2+Math.sqrt(a),y=c/2-Math.sqrt(a);if(h<=0)return 0;const D=J(y/h);return Math.sqrt(1-D)}function Q(e,t){if(t===0)return 0;const r=new Map,d=new Map,i=new Map,s=new Map;for(const n of e)r.set(n.y,(r.get(n.y)??0)+1),d.set(n.x,(d.get(n.x)??0)+1),i.set(n.x-n.y,(i.get(n.x-n.y)??0)+1),s.set(n.x+n.y,(s.get(n.x+n.y)??0)+1);let c=0;for(const n of r.values())n>c&&(c=n);for(const n of d.values())n>c&&(c=n);for(const n of i.values())n>c&&(c=n);for(const n of s.values())n>c&&(c=n);return c/t}function W(e,t,r,d){const i=t.length,s=e.length,c=i>0?s/i:0,n=t.filter(o=>q(o,r,d)),a=e.filter(o=>q(o,r,d)),h=n.length>0?a.length/n.length:0,y=j(e),D=y.length>0?[...y].sort((o,l)=>l.length-o.length):[e],f=D[0],g=D.slice(0,5).flat(),R=f.length,T=s>0?R/s:0,u=r.center.x,m=r.center.y,p=r.radius;let S=0,N=0,M=1/0,x=0;for(const o of f){S+=o.physX,N+=o.physY;const l=Math.sqrt((o.physX-u)**2+(o.physY-m)**2);l<M&&(M=l),l>x&&(x=l)}const H=S/f.length,O=N/f.length,P=Math.sqrt((H-u)**2+(O-m)**2)/p,A=M===1/0?0:M/p,I=Math.min(x/p,1),C=e.map(o=>Math.sqrt((o.physX-u)**2+(o.physY-m)**2)/p).sort((o,l)=>o-l),v=C[Math.floor(C.length*.25)]??0,b=t.filter(o=>Math.sqrt((o.physX-u)**2+(o.physY-m)**2)/p<.5).length,L=t.filter(o=>Math.sqrt((o.physX-u)**2+(o.physY-m)**2)/p>=.5).length,_=e.filter(o=>Math.sqrt((o.physX-u)**2+(o.physY-m)**2)/p<.5).length,w=e.filter(o=>Math.sqrt((o.physX-u)**2+(o.physY-m)**2)/p>=.5).length,F=b>0?_/b:0,X=L>0?w/L:0,z=X>0?F/X:F>0?2:1,G=g.reduce((o,l)=>o+l.physX,0)/g.length,k=g.reduce((o,l)=>o+l.physY,0)/g.length,B=K(g,G,k),U=Q(g,g.length),E=16,Y=new Set;for(const o of a){const l=(Math.atan2(o.physY-m,o.physX-u)+2*Math.PI)%(2*Math.PI);Y.add(Math.floor(l/(2*Math.PI)*E)%E)}const V=a.length>0?Y.size/E:0;return{globalRdd:c,edgeRdd:h,centroidDistNorm:P,minDistNorm:A,maxDistNorm:I,p25DistNorm:v,eccentricity:B,linearScore:U,salienceSize:R,salienceFraction:T,edgeAngularSpread:V,innerOuterRatio:z}}function Z(e,t,r){const d=Math.max(5,Math.round(r*.003)),i=e.salienceSize>=d;if(e.globalRdd<t.nearFullGlobalRdd&&e.p25DistNorm>=t.edgeLocalCentroidDist&&e.edgeAngularSpread>=t.edgeRingAngularSpread&&e.edgeRdd>=t.edgeLocalEdgeRdd)return{pattern:"edge-ring",confidence:e.edgeRdd>=t.edgeRingEdgeRddHigh?"high":"medium"};if(e.globalRdd<t.nearFullGlobalRdd&&e.salienceFraction<.1)return{pattern:"random",confidence:"low"};if(e.globalRdd>=t.nearFullGlobalRdd)return{pattern:"near-full",confidence:e.globalRdd>=.8?"high":"medium"};if(i&&e.linearScore>=t.scratchLinearScore&&e.eccentricity>=t.scratchEccentricity&&e.maxDistNorm>=t.scratchMinReach){const a=e.linearScore>=t.scratchLinearScoreHigh?"high":"medium";return{pattern:"scratch",confidence:a,note:a==="medium"?"Scratch detection is less reliable for fragmented or diagonal patterns":void 0}}const s=i&&e.edgeRdd>=t.edgeLocalEdgeRdd&&e.maxDistNorm>=.7;if(s&&e.p25DistNorm>=t.edgeLocalCentroidDist&&e.maxDistNorm-e.minDistNorm<=t.edgeRingRadialSpread)return{pattern:"edge-ring",confidence:e.edgeRdd>=t.edgeRingEdgeRddHigh?"high":"medium"};if(s&&e.p25DistNorm>=.55&&e.p25DistNorm<t.edgeLocalCentroidDist)return{pattern:"edge-local",confidence:e.edgeRdd>=t.edgeRingEdgeRdd?"medium":"low"};const c=i&&e.centroidDistNorm<=t.centerCentroidDist&&e.edgeRdd<=t.centerEdgeRdd,n="Center and donut patterns have similar geometry; classification may be imprecise";if(c&&e.p25DistNorm<t.donutP25Dist){const a=e.centroidDistNorm<=t.centerCentroidDistHigh?"high":"medium";return{pattern:"center",confidence:a,note:a!=="high"?n:void 0}}return c&&e.p25DistNorm>=t.donutP25Dist&&e.p25DistNorm<.55?{pattern:"donut",confidence:"medium",note:n}:{pattern:"random",confidence:"low"}}export const PATTERN_LABELS={center:"Center cluster",donut:"Donut","edge-ring":"Edge-ring","edge-local":"Edge-local",scratch:"Scratch","near-full":"Near-full",random:"Random",none:"None"};export function patternFailVerdict(e,t){const r=e.hbin??e.sbin;return r===void 0?void 0:!t.has(r)}export function patternExplains(e,t,r,d){if(!{"edge-ring":["ring","edge-arc"],"edge-local":["edge-arc","sector","quadrant"],center:["ring","cluster"],donut:["ring"],scratch:["cluster","sector","quadrant"],"near-full":["ring"]}[e]?.includes(t))return!1;if(t!=="ring"||r.length===0)return!0;const s=r.includes(d),c=r.includes(1);return e==="edge-ring"||e==="edge-local"?s:e==="center"?c:e==="donut"?!s&&!c:!0}export function classifyPattern(e,t,r){const d=new Set(r.passBins),i=e.filter(s=>patternFailVerdict(s,d)===!0);return classifyFailingDies(i,e,t,r.ringCount??4)}export function classifyFailingDies(e,t,r,d){const i={...DEFAULT_PATTERN_THRESHOLDS},s=Math.max(i.minimumFailingDies,Math.round(t.length*.003));if(e.length<s)return null;const c=W(e,t,r,d),{pattern:n,confidence:a,note:h}=Z(c,i,t.length);return{pattern:n,confidence:a,features:c,...h?{note:h}:{}}}
@@ -0,0 +1,42 @@
1
+ import type { StatsSeverity } from './types.js';
2
+ import type { ImpactTier } from './synthesis.js';
3
+ /** The three steps severity and impact are drawn in. */
4
+ export type MeterTier = ImpactTier;
5
+ export declare const METER_DOTS = 3;
6
+ /** Dots filled for a tier: high 3, medium 2, low 1. Never zero, so the lowest tier is still marked. */
7
+ export declare const filledDots: (tier: MeterTier) => number;
8
+ /** A finding's severity as a tier and the word it is shown by. `'info'` reads "Minor". */
9
+ export declare const SEVERITY_MARK: Readonly<Record<StatsSeverity, {
10
+ tier: MeterTier;
11
+ word: string;
12
+ }>>;
13
+ /** An item's impact (share of dies lost) as the word it is shown by. */
14
+ export declare const impactWord: (tier: MeterTier) => string;
15
+ /**
16
+ * How strongly a yield difference is tinted: 0 for none, 1–3 light to strong. Only a shortfall of a
17
+ * point or more is tinted — a lot with nothing wrong has no tint at all, which is how a good lot is
18
+ * recognised. `points` is signed: yield minus its reference.
19
+ */
20
+ export declare function shortfallStep(points: number): 0 | 1 | 2 | 3;
21
+ /**
22
+ * The tint of a shortfall, one colour at three strengths, as an alpha over whatever the surface is
23
+ * (a translucent wash reads on the report's white and on any panel theme alike). Full ink stays on
24
+ * every step: the strongest is still under a third opaque.
25
+ */
26
+ export declare const SHORTFALL_RGB = "190, 60, 60";
27
+ export declare const SHORTFALL_ALPHA: Readonly<Record<1 | 2 | 3, number>>;
28
+ /** The CSS colour for a shortfall step. */
29
+ export declare const shortfallColor: (step: 1 | 2 | 3) => string;
30
+ /** A signed difference in yield points: `+1.2`, `−1.2` (a true minus sign), `0.0`. */
31
+ export declare function formatPoints(points: number): string;
32
+ /** A bar's fill as a percentage of a 0–100 scale, kept in range. */
33
+ export declare const barPercent: (percent: number) => number;
34
+ /** The legend sentence for a column of shortfall tints, naming its reference ("the lot", "the wafer"). */
35
+ export declare const shortfallLegend: (reference: string) => string;
36
+ /**
37
+ * The label for the unweighted mean of each wafer's own yield. It is not the die-weighted yield of the
38
+ * lot (which the bin breakdown's shares are), and the name says which statistic it is: the panel, the
39
+ * Insights population line and the reports all use this one.
40
+ */
41
+ export declare const MEAN_WAFER_YIELD_LABEL = "Mean per-wafer yield";
42
+ //# sourceMappingURL=presentation.d.ts.map
@@ -0,0 +1 @@
1
+ export const METER_DOTS=3,filledDots=t=>t==="high"?3:t==="medium"?2:1,SEVERITY_MARK={unusual:{tier:"high",word:"Unusual"},notable:{tier:"medium",word:"Notable"},info:{tier:"low",word:"Minor"}},impactWord=t=>`${t==="medium"?"Moderate":t==="high"?"High":"Low"} impact`;const e=[{points:4,step:3},{points:2,step:2},{points:1,step:1}];export function shortfallStep(t){return e.find(o=>-t>=o.points)?.step??0}export const SHORTFALL_RGB="190, 60, 60",SHORTFALL_ALPHA={1:.1,2:.2,3:.32},shortfallColor=t=>`rgba(${SHORTFALL_RGB}, ${SHORTFALL_ALPHA[t]})`;export function formatPoints(t){return`${t>0?"+":t<0?"\u2212":""}${Math.abs(t).toFixed(1)}`}export const barPercent=t=>Math.max(0,Math.min(100,t)),shortfallLegend=t=>`Bars run 0\u2013100%. A figure is tinted when it is 1, 2 or 4 or more points below ${t}; nothing above it is.`,MEAN_WAFER_YIELD_LABEL="Mean per-wafer yield";
@@ -5,7 +5,14 @@ export interface StatsRegion {
5
5
  family: 'ring' | 'quadrant' | 'reticle-position' | 'test-site' | 'sector';
6
6
  key: string;
7
7
  label: string;
8
+ /** `getDieKey` of each member — what findings carry, for highlighting. */
8
9
  dieKeys: string[];
10
+ /**
11
+ * The member dies themselves, parallel to `dieKeys`. Analysis reads these:
12
+ * finding a region's dies again by key string cost more than a quarter of a
13
+ * large wafer's analysis.
14
+ */
15
+ dies: Die[];
9
16
  }
10
17
  export interface RegionYieldDatum {
11
18
  /** Region identity, e.g. `ring:2` or `quadrant:NE` — parse with `parseRegionKey`. */
@@ -33,8 +40,17 @@ export interface RegionYieldDatum {
33
40
  export declare function buildRegionYieldData(diesByWafer: Die[][], allWafers: Wafer[], ringCount: number, passBins: readonly number[] | ((waferIndex: number) => readonly number[]), regionBuilder: (dies: PositionedDie[], wafer: Wafer, ringCount: number) => StatsRegion[]): RegionYieldDatum[];
34
41
  /** Compass bearing names for a given sector count, ordered CCW from East. */
35
42
  export declare function sectorCompassNames(sectorCount: number): string[];
43
+ /** Quadrants going round the wafer, counter-clockwise from the north-east. */
44
+ export declare const QUADRANT_CYCLE: readonly ["NE", "NW", "SW", "SE"];
36
45
  /** True when two quadrants share an edge (NE–NW, NE–SE, NW–SW, SE–SW); diagonals are not adjacent. */
37
46
  export declare function areQuadrantsAdjacent(a: string, b: string): boolean;
47
+ /**
48
+ * The angular extent of a sector or quadrant region as the set of bins it covers
49
+ * (counter-clockwise from East), for asking how much two regions overlap. Both
50
+ * are cuts of the same wafer by angle alone, so their dies overlap as their
51
+ * angles do. `undefined` for any other region, or a name outside the compass.
52
+ */
53
+ export declare function regionAngleBins(key: string, sectorCount: number): Set<number> | undefined;
38
54
  export interface ParsedRegionKey {
39
55
  family: StatsRegion['family'] | 'unknown';
40
56
  ring?: number;
@@ -1 +1 @@
1
- import{classifyDie as E,getRingLabel as W}from"../core/classify.js";import{getReticleCell as b}from"../core/reticle.js";import{isPositionedDie as M,diePassStatus as k,getDieKey as x}from"../core/dies.js";function y(t){return x(t)}export function buildRegionYieldData(t,o,r,e,n){const i=new Map,a=[];for(let s=0;s<o.length;s++){const c=t[s];if(!c?.length)continue;const u=new Set(typeof e=="function"?e(s):e),f=n(c.filter(M),o[s],r),S=new Map(c.map(l=>[y(l),l]));for(const l of f){a.includes(l.key)||a.push(l.key);const g=i.get(l.key)??{label:l.label,pass:0,total:0};for(const m of l.dieKeys){const d=S.get(m);if(!d||d.partial||d.edgeExcluded)continue;const p=k(d,u);p!==void 0&&(g.total++,p&&g.pass++)}i.set(l.key,g)}}return a.map(s=>{const c=i.get(s);return c.total===0?null:{key:s,label:c.label,n:c.total,passDies:c.pass,yieldPercent:c.pass/c.total*100}}).filter(s=>s!==null)}const R=["E","ENE","NE","NNE","N","NNW","NW","WNW","W","WSW","SW","SSW","S","SSE","SE","ESE"],K=["E","NE","N","NW","W","SW","S","SE"],q=["E","N","W","S"];export function sectorCompassNames(t){const o=[4,8,16,32].includes(t)?t:16;return o===4?q:o===8?K:R}const P={NE:["NW","SE"],NW:["NE","SW"],SE:["NE","SW"],SW:["NW","SE"]};export function areQuadrantsAdjacent(t,o){return P[t]?.includes(o)??!1}export function parseRegionKey(t){return t.startsWith("ring:")?{family:"ring",ring:Number(t.slice(5))}:t.startsWith("quadrant:")?{family:"quadrant",quadrant:t.slice(9)}:t.startsWith("sector:")?{family:"sector",sector:t.slice(7)}:{family:"unknown"}}export function buildRingRegions(t,o,r){const e=new Map;for(const n of t){const{ring:i}=E(n,o,{ringCount:r}),a=`ring:${i}`,s=e.get(a)??{family:"ring",key:a,label:W(i,r),dieKeys:[]};s.dieKeys.push(y(n)),e.set(a,s)}return[...e.values()].sort((n,i)=>n.key.localeCompare(i.key))}export function buildQuadrantRegions(t,o,r){const e=new Map;for(const i of t){const{quadrant:a}=E(i,o,{ringCount:r}),s=`quadrant:${a}`,c=e.get(s)??{family:"quadrant",key:s,label:a,dieKeys:[]};c.dieKeys.push(y(i)),e.set(s,c)}const n=new Map([["quadrant:NE",0],["quadrant:NW",1],["quadrant:SE",2],["quadrant:SW",3]]);return[...e.values()].sort((i,a)=>(n.get(i.key)??4)-(n.get(a.key)??4))}export function buildReticlePositionRegions(t,o){if(!o)return[];const r=new Map;for(const e of t){const{column:n,row:i}=b(e,o),a=`reticle-position:cell:${n},${i}`,s=r.get(a)??{family:"reticle-position",key:a,label:`Reticle cell (${n}, ${i})`,dieKeys:[]};s.dieKeys.push(y(e)),r.set(a,s)}return[...r.values()].sort((e,n)=>e.key.localeCompare(n.key))}const w=3;export function buildTestSiteRegions(t,o=!1){const r=new Map;for(const e of t){if(e.siteNum===void 0)continue;const n=r.get(e.siteNum)??[];n.push(y(e)),r.set(e.siteNum,n)}if(!o){let e=0;for(const n of r.values())n.length>=w&&e++;if(e<2)return[]}return[...r.entries()].sort(([e],[n])=>e-n).map(([e,n])=>({family:"test-site",key:`test-site:${e}`,label:`Site ${e}`,dieKeys:n}))}export function buildSectorRegions(t,o,r){const e=[4,8,16,32].includes(r)?r:16,n=sectorCompassNames(e),i=new Map,a=o.center.x,s=o.center.y,c=o.radius;for(const u of t){const f=u.physX-a,S=u.physY-s;if(Math.hypot(f,S)/c<.2)continue;const g=Math.PI/e,m=(Math.atan2(S,f)+2*Math.PI+g)%(2*Math.PI),d=Math.floor(m/(2*Math.PI)*e)%e,p=n[d],N=`sector:${p}`,h=i.get(N)??{family:"sector",key:N,label:`Sector ${p}`,dieKeys:[]};h.dieKeys.push(y(u)),i.set(N,h)}return[...i.values()].sort((u,f)=>u.key.localeCompare(f.key))}
1
+ import{classifyDie as E,getRingLabel as b}from"../core/classify.js";import{getReticleCell as x}from"../core/reticle.js";import{isPositionedDie as M,diePassStatus as R,getDieKey as k}from"../core/dies.js";function g(s){return k(s)}export function buildRegionYieldData(s,r,a,e,t){const o=new Map,n=[];for(let i=0;i<r.length;i++){const c=s[i];if(!c?.length)continue;const u=new Set(typeof e=="function"?e(i):e),d=t(c.filter(M),r[i],a);for(const l of d){n.includes(l.key)||n.push(l.key);const y=o.get(l.key)??{label:l.label,pass:0,total:0};for(const p of l.dies){if(p.partial||p.edgeExcluded)continue;const S=R(p,u);S!==void 0&&(y.total++,S&&y.pass++)}o.set(l.key,y)}}return n.map(i=>{const c=o.get(i);return c.total===0?null:{key:i,label:c.label,n:c.total,passDies:c.pass,yieldPercent:c.pass/c.total*100}}).filter(i=>i!==null)}const q=["E","ENE","NE","NNE","N","NNW","NW","WNW","W","WSW","SW","SSW","S","SSE","SE","ESE"],w=["E","NE","N","NW","W","SW","S","SE"],A=["E","N","W","S"];export function sectorCompassNames(s){const r=[4,8,16].includes(s)?s:16;return r===4?A:r===8?w:q}export const QUADRANT_CYCLE=["NE","NW","SW","SE"];const K={NE:["NW","SE"],NW:["NE","SW"],SE:["NE","SW"],SW:["NW","SE"]};export function areQuadrantsAdjacent(s,r){return K[s]?.includes(r)??!1}const f=1440;export function regionAngleBins(s,r){const a=parseRegionKey(s);let e,t;if(a.family==="quadrant"){const n=QUADRANT_CYCLE.indexOf(a.quadrant);if(n<0)return;e=n*f/4,t=f/4}else if(a.family==="sector"){const n=[4,8,16].includes(r)?r:void 0,i=n?sectorCompassNames(n).indexOf(a.sector??""):-1;if(!n||i<0)return;t=f/n,e=i*t-t/2}else return;const o=new Set;for(let n=0;n<t;n++)o.add(((e+n)%f+f)%f);return o}export function parseRegionKey(s){return s.startsWith("ring:")?{family:"ring",ring:Number(s.slice(5))}:s.startsWith("quadrant:")?{family:"quadrant",quadrant:s.slice(9)}:s.startsWith("sector:")?{family:"sector",sector:s.slice(7)}:{family:"unknown"}}export function buildRingRegions(s,r,a){const e=new Map;for(const t of s){const{ring:o}=E(t,r,{ringCount:a}),n=`ring:${o}`,i=e.get(n)??{family:"ring",key:n,label:b(o,a),dieKeys:[],dies:[]};i.dieKeys.push(g(t)),i.dies.push(t),e.set(n,i)}return[...e.values()].sort((t,o)=>t.key.localeCompare(o.key))}export function buildQuadrantRegions(s,r,a){const e=new Map;for(const o of s){const{quadrant:n}=E(o,r,{ringCount:a}),i=`quadrant:${n}`,c=e.get(i)??{family:"quadrant",key:i,label:n,dieKeys:[],dies:[]};c.dieKeys.push(g(o)),c.dies.push(o),e.set(i,c)}const t=new Map([["quadrant:NE",0],["quadrant:NW",1],["quadrant:SE",2],["quadrant:SW",3]]);return[...e.values()].sort((o,n)=>(t.get(o.key)??4)-(t.get(n.key)??4))}export function buildReticlePositionRegions(s,r){if(!r)return[];const a=new Map;for(const e of s){const{column:t,row:o}=x(e,r),n=`reticle-position:cell:${t},${o}`,i=a.get(n)??{family:"reticle-position",key:n,label:`Reticle cell (${t}, ${o})`,dieKeys:[],dies:[]};i.dieKeys.push(g(e)),i.dies.push(e),a.set(n,i)}return[...a.values()].sort((e,t)=>e.key.localeCompare(t.key))}const P=3;export function buildTestSiteRegions(s,r=!1){const a=new Map;for(const e of s){if(e.siteNum===void 0)continue;const t=a.get(e.siteNum)??[];t.push(e),a.set(e.siteNum,t)}if(!r){let e=0;for(const t of a.values())t.length>=P&&e++;if(e<2)return[]}return[...a.entries()].sort(([e],[t])=>e-t).map(([e,t])=>({family:"test-site",key:`test-site:${e}`,label:`Site ${e}`,dieKeys:t.map(g),dies:t}))}export function buildSectorRegions(s,r,a){const e=[4,8,16].includes(a)?a:16,t=sectorCompassNames(e),o=new Map,n=r.center.x,i=r.center.y,c=r.radius;for(const u of s){const d=u.physX-n,l=u.physY-i;if(Math.hypot(d,l)/c<.2)continue;const p=Math.PI/e,S=(Math.atan2(l,d)+2*Math.PI+p)%(2*Math.PI),W=Math.floor(S/(2*Math.PI)*e)%e,h=t[W],m=`sector:${h}`,N=o.get(m)??{family:"sector",key:m,label:`Sector ${h}`,dieKeys:[],dies:[]};N.dieKeys.push(g(u)),N.dies.push(u),o.set(m,N)}return[...o.values()].sort((u,d)=>u.key.localeCompare(d.key))}
@@ -1,5 +1,6 @@
1
1
  import type { Die } from '../core/dies.js';
2
2
  import type { Wafer } from '../core/wafer.js';
3
+ import { type BinColors } from '../renderer/binColors.js';
3
4
  import { type BinDef, type TestDef, type YieldSummary, type WaferMapResult } from '../renderer/buildWaferMap.js';
4
5
  import type { StatsSummary, AnalyzeWaferMapOptions } from './types.js';
5
6
  import { openHtmlReport } from './renderFindingsReport.js';
@@ -19,6 +20,10 @@ export interface SummaryReportParams {
19
20
  statsSummary?: StatsSummary;
20
21
  passBins?: number[];
21
22
  ringCount?: number;
23
+ /** @internal The map's own bin colours, so a bar here is the colour of that bin on the map. Resolved with the default palette when omitted. */
24
+ binColors?: BinColors;
25
+ /** @internal Shown inside the app: a click on a finding's row selects it on the map (see `LIVE_FINDINGS_SCRIPT`). */
26
+ live?: boolean;
22
27
  }
23
28
  /** Generate a full wafer summary as a standalone HTML string suitable for `openHtmlReport()`. */
24
29
  export declare function renderSummaryReportHtml(params: SummaryReportParams, options?: {
@@ -54,6 +59,10 @@ export interface LotSummaryReportParams {
54
59
  ringCount?: number;
55
60
  /** Passthrough to the internal per-group `analyzeWaferLot` call, e.g. `{ enableTestValueAnalysis: true }`. */
56
61
  analyzeOptions?: AnalyzeWaferMapOptions;
62
+ /** @internal The gallery's bin colours, so a bar here is the colour of that bin on the cards. Resolved per group with the default palette when omitted. */
63
+ binColors?: BinColors;
64
+ /** @internal Shown inside the app: a click on a finding's row selects it on the map. */
65
+ live?: boolean;
57
66
  }
58
67
  /**
59
68
  * Generate a full lot summary as a standalone HTML string suitable for
@@ -86,7 +95,9 @@ export declare function renderWaferReportHtml(map: ReportMap & {
86
95
  yield: YieldSummary;
87
96
  dataCoverage: SummaryReportParams['dataCoverage'];
88
97
  }, summary?: StatsSummary, options?: {
89
- title?: string;
98
+ title?: string; /** @internal */
99
+ binColors?: BinColors; /** @internal */
100
+ live?: boolean;
90
101
  }): string;
91
102
  /**
92
103
  * The lot summary report for several built maps, as a standalone HTML document.
@@ -96,6 +107,8 @@ export declare function renderWaferReportHtml(map: ReportMap & {
96
107
  */
97
108
  export declare function renderLotReportHtml(maps: readonly ReportMap[], options?: {
98
109
  title?: string;
99
- analyzeOptions?: AnalyzeWaferMapOptions;
110
+ analyzeOptions?: AnalyzeWaferMapOptions; /** @internal */
111
+ binColors?: BinColors; /** @internal */
112
+ live?: boolean;
100
113
  }): string;
101
114
  //# sourceMappingURL=renderSummaryReport.d.ts.map