@wafertools/wafermap 0.30.1 → 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 (94) hide show
  1. package/AGENTS.md +71 -11
  2. package/CHANGELOG.md +464 -8
  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/chunked.d.ts +45 -0
  23. package/dist/packages/canvas-adapter/chunked.js +1 -0
  24. package/dist/packages/canvas-adapter/dieList.js +2 -2
  25. package/dist/packages/canvas-adapter/drilldown.d.ts +23 -0
  26. package/dist/packages/canvas-adapter/drilldown.js +1 -0
  27. package/dist/packages/canvas-adapter/icons.js +1 -1
  28. package/dist/packages/canvas-adapter/insightsTab.d.ts +55 -3
  29. package/dist/packages/canvas-adapter/insightsTab.js +1 -1
  30. package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +55 -0
  31. package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
  32. package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
  33. package/dist/packages/canvas-adapter/summaryPanel.d.ts +55 -9
  34. package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
  35. package/dist/packages/canvas-adapter/toCanvas.js +1 -1
  36. package/dist/packages/canvas-adapter/toolbar.d.ts +30 -2
  37. package/dist/packages/canvas-adapter/toolbar.js +2 -2
  38. package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
  39. package/dist/packages/canvas-adapter/userGuideHtml.js +126 -3
  40. package/dist/packages/canvas-adapter/version.d.ts +2 -2
  41. package/dist/packages/canvas-adapter/version.js +1 -1
  42. package/dist/packages/canvas-adapter/warnings.js +1 -1
  43. package/dist/packages/core/aggregates.js +1 -1
  44. package/dist/packages/core/utils.d.ts +55 -0
  45. package/dist/packages/core/utils.js +1 -1
  46. package/dist/packages/renderer/axisTicks.d.ts +32 -0
  47. package/dist/packages/renderer/axisTicks.js +1 -0
  48. package/dist/packages/renderer/binColors.d.ts +14 -0
  49. package/dist/packages/renderer/binColors.js +1 -1
  50. package/dist/packages/renderer/buildView.d.ts +11 -37
  51. package/dist/packages/renderer/buildView.js +1 -1
  52. package/dist/packages/renderer/buildWaferMap.d.ts +58 -1
  53. package/dist/packages/renderer/buildWaferMap.js +1 -1
  54. package/dist/packages/renderer/deprecate.d.ts +13 -5
  55. package/dist/packages/renderer/deprecate.js +1 -1
  56. package/dist/packages/renderer/derivedTests/apply.d.ts +85 -0
  57. package/dist/packages/renderer/derivedTests/apply.js +1 -0
  58. package/dist/packages/renderer/derivedTests/evaluate.d.ts +48 -0
  59. package/dist/packages/renderer/derivedTests/evaluate.js +1 -0
  60. package/dist/packages/renderer/derivedTests/parser.d.ts +148 -0
  61. package/dist/packages/renderer/derivedTests/parser.js +2 -0
  62. package/dist/packages/renderer/fmt.d.ts +24 -2
  63. package/dist/packages/renderer/fmt.js +1 -1
  64. package/dist/packages/renderer/index.d.ts +1 -0
  65. package/dist/packages/renderer/index.js +1 -1
  66. package/dist/packages/renderer/spec.d.ts +49 -0
  67. package/dist/packages/renderer/spec.js +1 -0
  68. package/dist/packages/renderer/testLabel.d.ts +154 -0
  69. package/dist/packages/renderer/testLabel.js +1 -0
  70. package/dist/packages/stats/analyzeWaferMap.js +1 -1
  71. package/dist/packages/stats/capability.d.ts +60 -0
  72. package/dist/packages/stats/capability.js +1 -1
  73. package/dist/packages/stats/correlation.d.ts +33 -0
  74. package/dist/packages/stats/correlation.js +1 -1
  75. package/dist/packages/stats/deprecated.d.ts +3 -0
  76. package/dist/packages/stats/deprecated.js +1 -1
  77. package/dist/packages/stats/findingsNarrative.js +1 -1
  78. package/dist/packages/stats/index.d.ts +2 -2
  79. package/dist/packages/stats/index.js +1 -1
  80. package/dist/packages/stats/math.d.ts +27 -2
  81. package/dist/packages/stats/math.js +1 -1
  82. package/dist/packages/stats/mergeTestDefs.js +1 -1
  83. package/dist/packages/stats/renderFindingsReport.js +8 -28
  84. package/dist/packages/stats/renderSummaryReport.js +22 -31
  85. package/dist/packages/stats/reportHtml.d.ts +27 -0
  86. package/dist/packages/stats/reportHtml.js +35 -14
  87. package/dist/packages/stats/sweep.d.ts +178 -0
  88. package/dist/packages/stats/sweep.js +1 -0
  89. package/dist/packages/stats/sweepXFromName.d.ts +27 -0
  90. package/dist/packages/stats/sweepXFromName.js +1 -0
  91. package/dist/packages/stats/testPassRate.js +1 -1
  92. package/dist/packages/stats/types.d.ts +28 -1
  93. package/llms.txt +14 -8
  94. package/package.json +2 -2
@@ -1,10 +1,16 @@
1
1
  import type { Die } from '../core/dies.js';
2
+ import { type Chunked } from '../core/utils.js';
2
3
  import { type TestDef } from '../renderer/buildWaferMap.js';
4
+ import { type DescriptiveStats } from './math.js';
3
5
  import type { TestCapability } from './types.js';
4
6
  export interface CapabilityDatum {
5
7
  testNumber: number;
6
8
  label: string;
7
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;
8
14
  /**
9
15
  * Whether this test has both `limitLow` and `limitHigh` defined. When
10
16
  * false, `lsl`/`usl`/`cp`/`cpk`/`pp`/`ppk` are all absent/null — there is
@@ -53,6 +59,60 @@ export interface CapabilityItem {
53
59
  * (most-variable-first within that tier).
54
60
  */
55
61
  export declare function buildCapabilityData(items: CapabilityItem[], testDefs: TestDef[]): CapabilityDatum[];
62
+ /**
63
+ * @internal {@link buildCapabilityData} as a {@link Chunked} computation, for a
64
+ * caller rendering on the main thread — a lot-sized call walks every die of
65
+ * every wafer once per test and then sorts every value of every test, which is
66
+ * seconds of uninterruptible work at lot scale. Steps are one slice of the die
67
+ * pass, or one test's sort. Same result, same order; the synchronous entry
68
+ * above is this function drained.
69
+ */
70
+ export declare function buildCapabilityDataSteps(items: CapabilityItem[], testDefs: TestDef[]): Chunked<CapabilityDatum[]>;
71
+ /** @internal Everything {@link pooledTestStatsSteps} derives from its one pass. */
72
+ export interface PooledTestStats {
73
+ /**
74
+ * Raw (NOT normalised) descriptive statistics per test number, over the
75
+ * pooled eligible dies — the population every other figure here describes.
76
+ * Only tests that are parametric, carry a `TestDef`, and have at least one
77
+ * finite value appear.
78
+ */
79
+ stats: Map<number, DescriptiveStats>;
80
+ /**
81
+ * Per test, how many pooled values fell outside the test's own spec limits —
82
+ * the spec-yield tally, counted off the sorted values this pass already held.
83
+ * Only tests carrying at least one limit appear. `fail` counts `v < limitLow`
84
+ * or `v > limitHigh`, matching the per-die judgement everywhere else.
85
+ *
86
+ * This is here rather than the sorted arrays themselves because the result is
87
+ * memoised: a tally is a handful of numbers per test, whereas the values are
88
+ * every die-test reading in the lot (~160 MB on a 200k x 100 lot, §5's
89
+ * browser ceiling territory) and must not outlive the pass that built them.
90
+ */
91
+ specTally: Map<number, {
92
+ n: number;
93
+ fail: number;
94
+ }>;
95
+ /** Capability indices plus the chart's normalised five-number summary,
96
+ * worst-Ppk first. */
97
+ capability: CapabilityDatum[];
98
+ }
99
+ /** @internal Test seam — `tests/pooledTestStatsCache.test.mjs`. */
100
+ export declare function clearPooledTestStatsCache(): void;
101
+ /**
102
+ * @internal Per-test descriptive statistics AND capability indices for a pooled
103
+ * population, from **one** walk of the dies.
104
+ *
105
+ * The summary panel used to scan the pooled dies three times over: once per
106
+ * test for min/σ/quartiles, once per test again to count spec failures, and a
107
+ * third time inside `buildCapabilityData` for the Ppk column beside them — all
108
+ * reading the same values off the same dies and all of them O(dies × tests).
109
+ * On a 50-wafer lot of 4,000 dies × 100 tests that was 40 s of a 41 s panel
110
+ * render. One pass collects the values, one sort per test serves the quantiles
111
+ * of both the table and the chart, and the spec tally reads the sorted array.
112
+ *
113
+ * {@link Chunked}: one step per slice of the die pass, one per test thereafter.
114
+ */
115
+ export declare function pooledTestStatsSteps(items: CapabilityItem[], testDefs: TestDef[]): Chunked<PooledTestStats>;
56
116
  /**
57
117
  * @internal Capability indices without the chart's normalised quantiles — what
58
118
  * `stats.capability` carries. Same moments, formulas and order as
@@ -1 +1 @@
1
- import{isYieldEligibleDie as B}from"../core/dies.js";import{isParametricTest as v}from"../renderer/buildWaferMap.js";import{quantile as S}from"./math.js";function q(h){const r=new Map,t=new Map;for(const m of h){const s=m.testNumber;if(s===void 0||!v(m))continue;t.set(s,m);const e=m.limitLow,o=m.limitHigh;e!==void 0&&o!==void 0&&o>e&&r.set(s,{lsl:e,usl:o})}return{defByTestNumber:t,specByTest:r}}function A(h,r,t){const m=new Map,s=[];for(const c of r.keys())m.set(c,s.length),s.push(c);const e=s.length,o=new Float64Array(e),n=new Float64Array(e),u=new Float64Array(e),a=new Float64Array(e),g=new Float64Array(e),N=new Float64Array(e),i=new Float64Array(e),d=new Float64Array(e),w=t?new Array(e):[],b=e<=64;for(const c of h){N.fill(0),i.fill(0),d.fill(0);for(const f of c.dies??[]){if(!B(f))continue;const y=f.testValues;if(y){if(b){for(let p=0;p<e;p++){const l=y[s[p]];l===void 0||!Number.isFinite(l)||(o[p]++,n[p]+=l,u[p]+=l*l,N[p]++,i[p]+=l,d[p]+=l*l,t&&(w[p]??=[]).push(l))}continue}for(const p in y){const l=m.get(+p);if(l===void 0)continue;const M=y[+p];M===void 0||!Number.isFinite(M)||(o[l]++,n[l]+=M,u[l]+=M*M,N[l]++,i[l]+=M,d[l]+=M*M,t&&(w[l]??=[]).push(M))}}}for(let f=0;f<e;f++){const y=N[f];if(y<2)continue;const p=i[f]/y,l=(d[f]-y*p*p)/(y-1);a[f]+=(y-1)*Math.max(0,l),g[f]+=y-1}}const F=new Map;for(let c=0;c<e;c++){if(o[c]===0)continue;const f=s[c];F.set(f,{n:o[c],sum:n[c],sumSq:u[c],withinNumerator:a[c],withinDenominator:g[c]}),t&&w[c]&&t.set(f,w[c])}return F}function T(h,r,t,m){const{n:s,sum:e,sumSq:o,withinNumerator:n,withinDenominator:u}=m,a=e/s,g=s>=2?Math.max(0,(o-s*a*a)/(s-1)):0,N=Math.sqrt(g),i=u>0?Math.sqrt(n/u):NaN;let d=null,w=null,b=null,F=null;if(t){const{lsl:c,usl:f}=t,y=f-c;b=N>0?y/(6*N):null,F=N>0?Math.min((f-a)/(3*N),(a-c)/(3*N)):null,d=Number.isFinite(i)&&i>0?y/(6*i):null,w=Number.isFinite(i)&&i>0?Math.min((f-a)/(3*i),(a-c)/(3*i)):null}return{testNumber:h,label:r.name??`Test ${h}`,unit:r.unit,hasSpec:t!==void 0,lsl:t?.lsl,usl:t?.usl,mean:a,stdOverall:N,stdWithin:i,n:s,cp:d,cpk:w,pp:b,ppk:F}}function k(h){return h.sort((r,t)=>r.hasSpec!==t.hasSpec?r.hasSpec?-1:1:r.hasSpec?r.ppk===null&&t.ppk===null?0:r.ppk===null?1:t.ppk===null?-1:r.ppk-t.ppk:t.stdOverall-r.stdOverall)}export function buildCapabilityData(h,r){const{defByTestNumber:t,specByTest:m}=q(r);if(t.size===0)return[];const s=new Map,e=A(h,t,s),o=[];for(const n of r){const u=n.testNumber;if(u===void 0)continue;const a=e.get(u);if(!a||a.n===0||!t.has(u))continue;const g=m.get(u),N=T(u,n,g,a),i=s.get(u).sort((w,b)=>w-b);let d;if(g){const w=g.usl-g.lsl;d=b=>(b-g.lsl)/w}else{const w=i[0],b=i[i.length-1]-w;d=b>0?F=>(F-w)/b:()=>.5}o.push({...N,min:d(i[0]),q1:d(S(i,.25)),median:d(S(i,.5)),q3:d(S(i,.75)),max:d(i[i.length-1])})}return k(o)}export function buildCapabilityFigures(h,r){const{defByTestNumber:t,specByTest:m}=q(r);if(t.size===0)return[];const s=A(h,t,null),e=[];for(const[o,n]of s)n.n!==0&&e.push(T(o,t.get(o),m.get(o),n));return k(e)}export function poolCapabilityFigures(h,r){const{defByTestNumber:t,specByTest:m}=q(r),s=new Map;for(const o of h)for(const n of o){if(!t.has(n.testNumber)||n.n===0)continue;let u=s.get(n.testNumber);u||(u={n:0,sum:0,sumSq:0,withinNumerator:0,withinDenominator:0},s.set(n.testNumber,u));const a=n.stdOverall*n.stdOverall;u.n+=n.n,u.sum+=n.n*n.mean,u.sumSq+=(n.n-1)*a+n.n*n.mean*n.mean,n.n>=2&&(u.withinNumerator+=(n.n-1)*a,u.withinDenominator+=n.n-1)}const e=[];for(const[o,n]of s)e.push(T(o,t.get(o),m.get(o),n));return k(e)}
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;
@@ -40,7 +43,37 @@ export declare function pearsonOfPairs(pairs: ArrayLike<{
40
43
  export interface CorrelationMatrix {
41
44
  tests: CorrelationTestInfo[];
42
45
  cells: CorrelationCell[];
46
+ /**
47
+ * Present only when the matrix was computed from a sample of the dies rather
48
+ * than all of them — see `CORRELATION_DIE_BUDGET`. `of` is the population that
49
+ * carried test values, `used` how many were read.
50
+ *
51
+ * **Any surface showing this matrix must say so.** An `r` from a sample is a
52
+ * perfectly good estimate, but an unlabelled one is a number the reader will
53
+ * take for the whole population — the exact class of quietly-wrong figure this
54
+ * library exists to prevent. `correlationSampleNote()` is the wording.
55
+ */
56
+ sample?: {
57
+ of: number;
58
+ used: number;
59
+ };
43
60
  }
61
+ /**
62
+ * Dies read before `buildCorrelationMatrix` starts sampling.
63
+ *
64
+ * Correlation is the only computation here that is quadratic in tests *and*
65
+ * linear in dies: a 400k-die, 50-test lot is 1,225 pairs per die — 490 million
66
+ * pair updates, each touching six accumulators — which in a browser presents as
67
+ * a panel that never renders (tsmap WMAP_ISSUES #61).
68
+ *
69
+ * 25,000 is far past the point where more dies change a Pearson coefficient: the
70
+ * standard error of r is about `(1 - r²)/sqrt(n)`, so at n = 25,000 it is under
71
+ * 0.007 even for r = 0. Sixteen times more dies would halve an error that is
72
+ * already invisible at two decimal places.
73
+ */
74
+ export declare const CORRELATION_DIE_BUDGET = 25000;
75
+ /** The sentence a panel puts next to a sampled matrix. */
76
+ export declare function correlationSampleNote(sample: CorrelationMatrix['sample']): string | undefined;
44
77
  /**
45
78
  * Pearson correlation matrix for every test in `testDefs` across every die
46
79
  * in `dies`.
@@ -1 +1 @@
1
- import{isParametricTest as A}from"../renderer/buildWaferMap.js";export function pearsonFromSums(o,l,u,i,y,f){if(o<3)return null;const d=l/o,c=u/o,r=f/o-d*c,m=i/o-d*d,p=y/o-c*c,h=Math.sqrt(m*p);return h===0?null:Math.max(-1,Math.min(1,r/h))}export function pearsonOfPairs(o){let l=0,u=0,i=0,y=0,f=0,d=0;for(let c=0;c<o.length;c++){const{x:r,y:m}=o[c];!Number.isFinite(r)||!Number.isFinite(m)||(l++,u+=r,i+=m,y+=r*r,f+=m*m,d+=r*m)}return{r:pearsonFromSums(l,u,i,y,f,d),n:l}}function N(o){return o.filter(l=>l.testNumber!==void 0&&A(l)).map(l=>({testNumber:l.testNumber,label:l.name,unit:l.unit}))}export function buildCorrelationMatrix(o,l){const u=N(l);if(u.length<2)return{tests:u,cells:[]};const i=u.length,y=u.map(t=>t.testNumber),f=i*(i-1)/2,d=new Float64Array(f),c=new Float64Array(i),r=new Float64Array(f),m=new Float64Array(f),p=new Float64Array(f),h=new Float64Array(f),g=new Float64Array(f);function w(t,n){return t*i-(t*(t+1)>>1)+(n-t-1)}for(const t of o){if(!t.testValues)continue;const n=new Float64Array(i),x=new Uint8Array(i);for(let s=0;s<i;s++){const a=t.testValues[y[s]];a!==void 0&&Number.isFinite(a)&&(n[s]=a,x[s]=1)}for(let s=0;s<i;s++){if(!x[s])continue;c[s]++;const a=n[s];for(let b=s+1;b<i;b++){if(!x[b])continue;const M=n[b],I=w(s,b);d[I]++,r[I]+=a,m[I]+=M,p[I]+=a*a,h[I]+=M*M,g[I]+=a*M}}}const F=t=>pearsonFromSums(d[t],r[t],m[t],p[t],h[t],g[t]),e=[];for(let t=0;t<i;t++)for(let n=0;n<i;n++){if(n===t){e.push({xIndex:n,yIndex:t,r:1,n:c[n]});continue}const x=Math.min(n,t),s=Math.max(n,t),a=w(x,s);e.push({xIndex:n,yIndex:t,r:F(a),n:d[a]})}return{tests:u,cells:e}}export function filterCorrelationMatrix(o,{threshold:l=.3,minTests:u=6,maxTests:i=20}={}){const y=[];let f=0,d=0,c=null;for(const e of o.cells){if(e.xIndex>=e.yIndex||e.r===null)continue;const t=Math.abs(e.r);y.push({xi:e.xIndex,yi:e.yIndex,absR:t}),(c===null||t>Math.abs(c.r))&&(c={xLabel:o.tests[e.xIndex].label,yLabel:o.tests[e.yIndex].label,r:e.r})}y.sort((e,t)=>t.absR-e.absR);const r=new Set;for(const{xi:e,yi:t,absR:n}of y){const x=n<l;r.size>=i||x&&r.size>=u||(r.add(e),r.size<i&&r.add(t))}if(r.size<Math.min(u,o.tests.length))for(let e=0;e<o.tests.length&&r.size<Math.min(i,u,o.tests.length);e++)r.add(e);const m=(()=>{const e=Array.from(r),t=new Map,n=new Map;for(const{xi:s,yi:a,absR:b}of y)!r.has(s)||!r.has(a)||(t.set(s,(t.get(s)??0)+b),t.set(a,(t.get(a)??0)+b),n.set(s,(n.get(s)??0)+1),n.set(a,(n.get(a)??0)+1));const x=s=>(n.get(s)??0)>0?t.get(s)/n.get(s):0;return e.sort((s,a)=>x(a)-x(s))})(),p=m.map(e=>o.tests[e]),h=new Map(m.map((e,t)=>[e,t])),g=new Set(p.map(e=>e.testNumber)),w=o.cells.filter(e=>g.has(o.tests[e.xIndex].testNumber)&&g.has(o.tests[e.yIndex].testNumber)).map(e=>({xIndex:h.get(e.xIndex),yIndex:h.get(e.yIndex),r:e.r,n:e.n}));let F=0;for(const{xi:e,yi:t,absR:n}of y)r.has(e)&&r.has(t)?n>=.7?f++:n>=.4&&d++:n<l&&F++;return{matrix:{tests:p,cells:w},strongPairs:f,moderatePairs:d,hiddenWeakPairs:F,strongestPair:c}}
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}`);
@@ -1 +1 @@
1
- const y={unusual:0,notable:1,info:2},S={E:0,ENE:22.5,NE:45,NNE:67.5,N:90,NNW:112.5,NW:135,WNW:157.5,W:180,WSW:202.5,SW:225,SSW:247.5,S:270,SSE:292.5,SE:315,ESE:337.5};function m(e){return e.stats.adjustedPValue??e.stats.pValue??1}function b(e){return[...e].sort((n,t)=>{const o=y[n.severity]-y[t.severity];return o!==0?o:m(n)-m(t)})[0]}function w(e){return e.some(t=>t.variable.kind==="yield")?e.filter(t=>t.variable.kind!=="hardBin"&&t.variable.kind!=="softBin"):e}function $(e){return e.replace(/^(Rings?|Quadrants?|Sectors?)\s+/i,"").split(/[,&]|–|\band\b/).map(n=>n.trim()).filter(Boolean)}function A(e,n){const t=new Set($(e));return $(n).some(o=>t.has(o))}function v(e){return e.variable.kind==="yield"?"yield":e.variable.kind==="hardBin"?`HBin ${e.variable.bin??e.variable.index??"?"}`:e.variable.kind==="softBin"?`SBin ${e.variable.bin??e.variable.index??"?"}`:e.variable.label}function h(e){let n=0,t=0;for(const o of e)o.effect.direction==="higher"?n++:o.effect.direction==="lower"&&t++;return n>t?"higher":t>n?"lower":"mixed"}function d(e){return e==="higher"?"elevated":e==="lower"?"reduced":"shifted"}function p(e){const n=new Set,t=[];for(const o of e){const r=v(o);n.has(r)||(n.add(r),t.push(r))}return t.length===1?t[0]:t.length===2?`${t[0]} and ${t[1]}`:t.length===3?`${t[0]}, ${t[1]}, and ${t[2]}`:`${t[0]}, ${t[1]}, and ${t.length-2} more`}function E(e){const n=new Set,t=[];for(const o of e){const r=o.comparison.left;n.has(r)||(n.add(r),t.push(r))}return t.length===1?t[0]:t.length===2?`${t[0]} and ${t[1]}`:`${t.slice(0,-1).join(", ")}, and ${t[t.length-1]}`}function R(e){return e.length!==1?!1:!/^(Rings|Quadrants|Sectors)\b/.test(e[0].comparison.left)}function B(e){const n=h(e),t=e.every(c=>c.comparison.left.includes("(edge)")),o=e.every(c=>c.comparison.left.includes("(core)"));let r,a;return o?(r="The core ring",a=!0):t&&e.length>1?(r="The edge rings",a=!1):(r=E(e),a=R(e)),`${r} show${a?"s":""} ${d(n)} ${p(e)}.`}function I(e){const n=h(e),t=s=>s.highlight.kind==="region"&&s.highlight.regionKeys.length>1||s.comparison.left.startsWith("Quadrants"),o=e.filter(s=>t(s)),r=e.filter(s=>!t(s)),a=[];r.length===1?a.push(`the ${r[0].comparison.left} quadrant`):r.length>1&&a.push(`the ${E(r)} quadrants`);for(const s of o)a.push(s.comparison.left.replace(/^Quadrants/,"quadrants"));const c=a.join(" and "),u=r.length===1&&o.length===0;return`${c.charAt(0).toUpperCase()+c.slice(1)} show${u?"s":""} ${d(n)} ${p(e)}.`}function P(e){const n=h(e),t=[],o=new Set;for(const u of e){const i=u.comparison.left.replace(/^Sectors? /,"");o.has(i)||(o.add(i),t.push(i))}let r,a;return t.length===1?(r=`the ${t[0]} ${e[0].comparison.left.startsWith("Sectors")?"sectors":"region"}`,a=!e[0].comparison.left.startsWith("Sectors")):(r=`the ${t.length===2?t.join(" and "):`${t.slice(0,-1).join(", ")}, and ${t[t.length-1]}`} sectors`,a=!1),`${r.charAt(0).toUpperCase()+r.slice(1)} show${a?"s":""} ${d(n)} ${p(e)}.`}function x(e){if(e.length===1)return`A failure cluster at ${e[0].comparison.left.replace(/^Cluster at /,"")}.`;const t=e.reduce((o,r)=>(o.stats.sampleSizeLeft??0)>=(r.stats.sampleSizeLeft??0)?o:r).comparison.left.replace(/^Cluster at /,"");return`${e.length} failure clusters identified; the largest at ${t}.`}function C(e){const n=e[0].comparison.left.replace(/^Edge arc ~/,"");return e.length===1?`An edge arc near ${n} shows localised failures.`:`${e.length} edge arcs detected, including near ${n}.`}function q(e){const n=h(e);return e.length===1?`Reticle-position variation: ${e[0].comparison.left} shows ${d(n)} ${p(e)}.`:`Reticle-position variation across ${e.length} cells (${d(n)} ${p(e)}).`}function F(e,n){const t=e.stats.method==="geometry"?` (${e.severity==="unusual"?"high":e.severity==="notable"?"medium":"low"} confidence)`:"",o=new Set(e.relatedIds??[]),r=n.find(c=>o.has(c.id)&&(c.comparison.family==="edge-arc"||c.comparison.family==="cluster"));let a="";return r&&(r.comparison.family==="edge-arc"?a=`; failures concentrated at the wafer edge near ${r.comparison.left.replace(/^Edge arc ~/,"")}`:a=`; failures concentrated near ${r.comparison.left.replace(/^Cluster at /,"")}`),`${e.comparison.left} failure pattern detected${t}${a}.`}function Y(e){const n=e.replace(/\.$/,"");return n.charAt(0).toLowerCase()+n.slice(1)}function M(e){const n=e.filter(t=>t.variable.kind==="yield");return n.length>0?h(n):h(e)}function k(e,n){const t=new Map;for(const i of n){const s=t.get(i.comparison.left)??[];s.push(i),t.set(i.comparison.left,s)}const o=[],r=[];for(const i of t.values()){const s=M(i);s==="higher"?o.push(...i.filter(l=>l.effect.direction==="higher")):s==="lower"?r.push(...i.filter(l=>l.effect.direction==="lower")):o.push(...i)}const a=o.map(i=>i.comparison.left),c=r.filter(i=>!a.some(s=>A(s,i.comparison.left)));if(o.length>0&&c.length>0){const i=n.some(f=>f.variable.kind==="yield"),[s,l]=i?[c,o]:[o,c];return`${e(s).replace(/\.$/,"")} while ${Y(e(l))}.`}if(o.length>0&&r.length>0&&c.length===0)return e(o);const u=M(n);if(u!=="mixed"&&n.some(i=>i.variable.kind==="yield")){const i=n.filter(s=>s.effect.direction===u);if(i.length>0)return e(i)}return e(n)}function N(e){let n=0,t=0,o=0;for(const r of e)for(const a of $(r.comparison.left)){const c=S[a];if(c===void 0)continue;const u=c*Math.PI/180;n+=Math.cos(u),t+=Math.sin(u),o++}return o===0||n===0&&t===0?null:(Math.atan2(t,n)*180/Math.PI+360)%360}function W(e){let n="E",t=360;for(const[o,r]of Object.entries(S)){const a=Math.abs((e-r+540)%360-180);a<t&&(t=a,n=o)}return n}function D(e){const n=e.filter(u=>u.effect.direction==="higher"),t=e.filter(u=>u.effect.direction==="lower");if(n.length===0||t.length===0)return null;const o=new Set(e.map(v));if(o.size!==1)return null;const r=N(n),a=N(t);return r===null||a===null||Math.abs((r-a+540)%360-180)<120?null:`${[...o][0]} increases from ${W(a)} toward ${W(r)} across the wafer.`}function O(e){const n=w(e);if(n.length===0)return"";const t=["sector","quadrant","ring"],o=new Map;for(const i of n){const s=o.get(i.comparison.family)??[];s.push(i),o.set(i.comparison.family,s)}const r=[...o.keys()].sort((i,s)=>{const l=Math.min(...o.get(i).map(m)),f=Math.min(...o.get(s).map(m));return l!==f?l-f:t.indexOf(i)-t.indexOf(s)})[0],a=o.get(r),c=D(a);return c||k(r==="ring"?B:r==="quadrant"?I:P,a)}function j(e,n){switch(e){case"cluster":return x(n);case"edge-arc":return C(n);case"reticle-position":return k(q,n);default:return""}}const L=new Set(["ring","quadrant","sector"]);export function buildFindingsNarrative(e){if(e.length===0)return"";const n=e.filter(s=>s.severity!=="info");if(n.length===0)return"Minor spatial variation detected; no strongly significant patterns.";const t=[],o=n.filter(s=>s.comparison.family==="spatial-pattern"),r=o.length?b(o):null,a=new Set(r?.relatedIds??[]);r&&t.push(F(r,n));const c=n.filter(s=>s!==r&&s.comparison.family!=="spatial-pattern"&&!a.has(s.id));let u=c.filter(s=>L.has(s.comparison.family));if(r&&u.length){const s=w(u);s.length>0&&s.every(f=>f.variable.kind==="yield"&&f.effect.direction==="higher")&&(u=[])}const i=new Set;if(u.length){const s=O(u);if(s){t.push(s);for(const l of L)i.add(l)}}else if(!r){const s=b(c);if(s){const l=s.comparison.family,f=j(l,c.filter(g=>g.comparison.family===l));f&&(t.push(f),i.add(l))}}if(t.length<3){const s=c.filter(l=>(l.comparison.family==="cluster"||l.comparison.family==="edge-arc")&&!i.has(l.comparison.family));if(s.length){const l=s[0].comparison.family,f=j(l,s.filter(g=>g.comparison.family===l));f&&t.push(f)}}return t.slice(0,3).join(" ")}
1
+ import{minOf as y}from"../core/utils.js";const S={unusual:0,notable:1,info:2},b={E:0,ENE:22.5,NE:45,NNE:67.5,N:90,NNW:112.5,NW:135,WNW:157.5,W:180,WSW:202.5,SW:225,SSW:247.5,S:270,SSE:292.5,SE:315,ESE:337.5};function m(e){return e.stats.adjustedPValue??e.stats.pValue??1}function w(e){return[...e].sort((n,t)=>{const o=S[n.severity]-S[t.severity];return o!==0?o:m(n)-m(t)})[0]}function v(e){return e.some(t=>t.variable.kind==="yield")?e.filter(t=>t.variable.kind!=="hardBin"&&t.variable.kind!=="softBin"):e}function $(e){return e.replace(/^(Rings?|Quadrants?|Sectors?)\s+/i,"").split(/[,&]|–|\band\b/).map(n=>n.trim()).filter(Boolean)}function R(e,n){const t=new Set($(e));return $(n).some(o=>t.has(o))}function E(e){return e.variable.kind==="yield"?"yield":e.variable.kind==="hardBin"?`HBin ${e.variable.bin??e.variable.index??"?"}`:e.variable.kind==="softBin"?`SBin ${e.variable.bin??e.variable.index??"?"}`:e.variable.label}function h(e){let n=0,t=0;for(const o of e)o.effect.direction==="higher"?n++:o.effect.direction==="lower"&&t++;return n>t?"higher":t>n?"lower":"mixed"}function d(e){return e==="higher"?"elevated":e==="lower"?"reduced":"shifted"}function p(e){const n=new Set,t=[];for(const o of e){const r=E(o);n.has(r)||(n.add(r),t.push(r))}return t.length===1?t[0]:t.length===2?`${t[0]} and ${t[1]}`:t.length===3?`${t[0]}, ${t[1]}, and ${t[2]}`:`${t[0]}, ${t[1]}, and ${t.length-2} more`}function k(e){const n=new Set,t=[];for(const o of e){const r=o.comparison.left;n.has(r)||(n.add(r),t.push(r))}return t.length===1?t[0]:t.length===2?`${t[0]} and ${t[1]}`:`${t.slice(0,-1).join(", ")}, and ${t[t.length-1]}`}function B(e){return e.length!==1?!1:!/^(Rings|Quadrants|Sectors)\b/.test(e[0].comparison.left)}function I(e){const n=h(e),t=e.every(c=>c.comparison.left.includes("(edge)")),o=e.every(c=>c.comparison.left.includes("(core)"));let r,a;return o?(r="The core ring",a=!0):t&&e.length>1?(r="The edge rings",a=!1):(r=k(e),a=B(e)),`${r} show${a?"s":""} ${d(n)} ${p(e)}.`}function P(e){const n=h(e),t=s=>s.highlight.kind==="region"&&s.highlight.regionKeys.length>1||s.comparison.left.startsWith("Quadrants"),o=e.filter(s=>t(s)),r=e.filter(s=>!t(s)),a=[];r.length===1?a.push(`the ${r[0].comparison.left} quadrant`):r.length>1&&a.push(`the ${k(r)} quadrants`);for(const s of o)a.push(s.comparison.left.replace(/^Quadrants/,"quadrants"));const c=a.join(" and "),u=r.length===1&&o.length===0;return`${c.charAt(0).toUpperCase()+c.slice(1)} show${u?"s":""} ${d(n)} ${p(e)}.`}function x(e){const n=h(e),t=[],o=new Set;for(const u of e){const i=u.comparison.left.replace(/^Sectors? /,"");o.has(i)||(o.add(i),t.push(i))}let r,a;return t.length===1?(r=`the ${t[0]} ${e[0].comparison.left.startsWith("Sectors")?"sectors":"region"}`,a=!e[0].comparison.left.startsWith("Sectors")):(r=`the ${t.length===2?t.join(" and "):`${t.slice(0,-1).join(", ")}, and ${t[t.length-1]}`} sectors`,a=!1),`${r.charAt(0).toUpperCase()+r.slice(1)} show${a?"s":""} ${d(n)} ${p(e)}.`}function C(e){if(e.length===1)return`A failure cluster at ${e[0].comparison.left.replace(/^Cluster at /,"")}.`;const t=e.reduce((o,r)=>(o.stats.sampleSizeLeft??0)>=(r.stats.sampleSizeLeft??0)?o:r).comparison.left.replace(/^Cluster at /,"");return`${e.length} failure clusters identified; the largest at ${t}.`}function q(e){const n=e[0].comparison.left.replace(/^Edge arc ~/,"");return e.length===1?`An edge arc near ${n} shows localised failures.`:`${e.length} edge arcs detected, including near ${n}.`}function F(e){const n=h(e);return e.length===1?`Reticle-position variation: ${e[0].comparison.left} shows ${d(n)} ${p(e)}.`:`Reticle-position variation across ${e.length} cells (${d(n)} ${p(e)}).`}function O(e,n){const t=e.stats.method==="geometry"?` (${e.severity==="unusual"?"high":e.severity==="notable"?"medium":"low"} confidence)`:"",o=new Set(e.relatedIds??[]),r=n.find(c=>o.has(c.id)&&(c.comparison.family==="edge-arc"||c.comparison.family==="cluster"));let a="";return r&&(r.comparison.family==="edge-arc"?a=`; failures concentrated at the wafer edge near ${r.comparison.left.replace(/^Edge arc ~/,"")}`:a=`; failures concentrated near ${r.comparison.left.replace(/^Cluster at /,"")}`),`${e.comparison.left} failure pattern detected${t}${a}.`}function Y(e){const n=e.replace(/\.$/,"");return n.charAt(0).toLowerCase()+n.slice(1)}function N(e){const n=e.filter(t=>t.variable.kind==="yield");return n.length>0?h(n):h(e)}function M(e,n){const t=new Map;for(const i of n){const s=t.get(i.comparison.left)??[];s.push(i),t.set(i.comparison.left,s)}const o=[],r=[];for(const i of t.values()){const s=N(i);s==="higher"?o.push(...i.filter(l=>l.effect.direction==="higher")):s==="lower"?r.push(...i.filter(l=>l.effect.direction==="lower")):o.push(...i)}const a=o.map(i=>i.comparison.left),c=r.filter(i=>!a.some(s=>R(s,i.comparison.left)));if(o.length>0&&c.length>0){const i=n.some(f=>f.variable.kind==="yield"),[s,l]=i?[c,o]:[o,c];return`${e(s).replace(/\.$/,"")} while ${Y(e(l))}.`}if(o.length>0&&r.length>0&&c.length===0)return e(o);const u=N(n);if(u!=="mixed"&&n.some(i=>i.variable.kind==="yield")){const i=n.filter(s=>s.effect.direction===u);if(i.length>0)return e(i)}return e(n)}function W(e){let n=0,t=0,o=0;for(const r of e)for(const a of $(r.comparison.left)){const c=b[a];if(c===void 0)continue;const u=c*Math.PI/180;n+=Math.cos(u),t+=Math.sin(u),o++}return o===0||n===0&&t===0?null:(Math.atan2(t,n)*180/Math.PI+360)%360}function j(e){let n="E",t=360;for(const[o,r]of Object.entries(b)){const a=Math.abs((e-r+540)%360-180);a<t&&(t=a,n=o)}return n}function D(e){const n=e.filter(u=>u.effect.direction==="higher"),t=e.filter(u=>u.effect.direction==="lower");if(n.length===0||t.length===0)return null;const o=new Set(e.map(E));if(o.size!==1)return null;const r=W(n),a=W(t);return r===null||a===null||Math.abs((r-a+540)%360-180)<120?null:`${[...o][0]} increases from ${j(a)} toward ${j(r)} across the wafer.`}function T(e){const n=v(e);if(n.length===0)return"";const t=["sector","quadrant","ring"],o=new Map;for(const i of n){const s=o.get(i.comparison.family)??[];s.push(i),o.set(i.comparison.family,s)}const r=[...o.keys()].sort((i,s)=>{const l=y(o.get(i).map(m)),f=y(o.get(s).map(m));return l!==f?l-f:t.indexOf(i)-t.indexOf(s)})[0],a=o.get(r),c=D(a);return c||M(r==="ring"?I:r==="quadrant"?P:x,a)}function L(e,n){switch(e){case"cluster":return C(n);case"edge-arc":return q(n);case"reticle-position":return M(F,n);default:return""}}const A=new Set(["ring","quadrant","sector"]);export function buildFindingsNarrative(e){if(e.length===0)return"";const n=e.filter(s=>s.severity!=="info");if(n.length===0)return"Minor spatial variation detected; no strongly significant patterns.";const t=[],o=n.filter(s=>s.comparison.family==="spatial-pattern"),r=o.length?w(o):null,a=new Set(r?.relatedIds??[]);r&&t.push(O(r,n));const c=n.filter(s=>s!==r&&s.comparison.family!=="spatial-pattern"&&!a.has(s.id));let u=c.filter(s=>A.has(s.comparison.family));if(r&&u.length){const s=v(u);s.length>0&&s.every(f=>f.variable.kind==="yield"&&f.effect.direction==="higher")&&(u=[])}const i=new Set;if(u.length){const s=T(u);if(s){t.push(s);for(const l of A)i.add(l)}}else if(!r){const s=w(c);if(s){const l=s.comparison.family,f=L(l,c.filter(g=>g.comparison.family===l));f&&(t.push(f),i.add(l))}}if(t.length<3){const s=c.filter(l=>(l.comparison.family==="cluster"||l.comparison.family==="edge-arc")&&!i.has(l.comparison.family));if(s.length){const l=s[0].comparison.family,f=L(l,s.filter(g=>g.comparison.family===l));f&&t.push(f)}}return t.slice(0,3).join(" ")}
@@ -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";
@@ -5,6 +5,31 @@
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
- /** Linear-interpolation quantile of a pre-sorted array (`q` in [0, 1]). */
9
- export declare function quantile(sorted: number[], q: number): number;
8
+ /** Linear-interpolation quantile of a pre-sorted array (`q` in [0, 1]).
9
+ *
10
+ * `ArrayLike<number>`, not `number[]`, so a caller holding its values in a
11
+ * `Float64Array` does not have to copy them back into a plain array to be
12
+ * read — see `pooledTestStatsSteps`, which sorts in one. */
13
+ export declare function quantile(sorted: ArrayLike<number>, q: number): number;
14
+ /** Descriptive statistics of one population of values — see {@link describeSorted}. */
15
+ export interface DescriptiveStats {
16
+ min: number;
17
+ max: number;
18
+ mean: number;
19
+ count: number;
20
+ /** Population standard deviation (divide by n) — see {@link describeSorted}. */
21
+ stddev: number;
22
+ median: number;
23
+ q1: number;
24
+ q3: number;
25
+ }
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.
28
+ *
29
+ * σ is the POPULATION standard deviation (divide by n), which is what this
30
+ * table has always shown; the capability indices next to it deliberately use
31
+ * the sample form (n−1) and say so. Two-pass, not `Σx² − n·x̄²`: the moments
32
+ * form loses most of its significant digits when the variance is small beside
33
+ * the mean, which is the normal shape of a passing parametric test. */
34
+ export declare function describeSorted(sorted: ArrayLike<number>): DescriptiveStats;
10
35
  //# sourceMappingURL=math.d.ts.map
@@ -1 +1 @@
1
- export function errorFunction(n){const a=n<0?-1:1,t=Math.abs(n),o=.254829592,r=-.284496736,s=1.421413741,i=-1.453152027,e=1.061405429,c=1/(1+.3275911*t),h=1-((((e*c+i)*c+s)*c+r)*c+o)*c*Math.exp(-t*t);return a*h}export function normalCdf(n){return .5*(1+errorFunction(n/Math.sqrt(2)))}export function quantile(n,a){if(n.length===0)return NaN;const t=a*(n.length-1),o=Math.floor(t),r=Math.ceil(t);return n[o]+(n[r]-n[o])*(t-o)}
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 +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}