@wafertools/wafermap 0.30.4 → 0.32.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 (103) hide show
  1. package/AGENTS.md +21 -31
  2. package/CHANGELOG.md +265 -1
  3. package/README.md +3 -3
  4. package/dist/packages/canvas-adapter/charts/boxplot.js +1 -1
  5. package/dist/packages/canvas-adapter/charts/chartShell.d.ts +89 -6
  6. package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
  7. package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
  8. package/dist/packages/canvas-adapter/charts/scatter.d.ts +11 -1
  9. package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
  10. package/dist/packages/canvas-adapter/charts/trend.js +1 -1
  11. package/dist/packages/canvas-adapter/dieList.d.ts +1 -1
  12. package/dist/packages/canvas-adapter/dieList.js +7 -7
  13. package/dist/packages/canvas-adapter/drilldown.js +1 -1
  14. package/dist/packages/canvas-adapter/exportName.d.ts +1 -12
  15. package/dist/packages/canvas-adapter/exportName.js +1 -1
  16. package/dist/packages/canvas-adapter/index.d.ts +2 -3
  17. package/dist/packages/canvas-adapter/index.js +1 -1
  18. package/dist/packages/canvas-adapter/insightsTab.js +1 -1
  19. package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +4 -7
  20. package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
  21. package/dist/packages/canvas-adapter/renderWaferMap.d.ts +32 -34
  22. package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
  23. package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
  24. package/dist/packages/canvas-adapter/toCanvas.d.ts +23 -2
  25. package/dist/packages/canvas-adapter/toCanvas.js +1 -1
  26. package/dist/packages/canvas-adapter/toolbar.js +2 -2
  27. package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
  28. package/dist/packages/canvas-adapter/userGuideHtml.js +38 -24
  29. package/dist/packages/canvas-adapter/version.d.ts +2 -2
  30. package/dist/packages/canvas-adapter/version.js +1 -1
  31. package/dist/packages/core/aggregates.d.ts +0 -6
  32. package/dist/packages/core/aggregates.js +1 -1
  33. package/dist/packages/core/dieTable.d.ts +137 -0
  34. package/dist/packages/core/dieTable.js +1 -0
  35. package/dist/packages/core/dies.d.ts +22 -2
  36. package/dist/packages/core/dies.js +1 -1
  37. package/dist/packages/core/index.d.ts +8 -10
  38. package/dist/packages/core/index.js +1 -1
  39. package/dist/packages/core/stdf.d.ts +9 -0
  40. package/dist/packages/core/stdf.js +1 -0
  41. package/dist/packages/core/transforms.d.ts +0 -23
  42. package/dist/packages/core/transforms.js +1 -1
  43. package/dist/packages/core/utils.d.ts +14 -0
  44. package/dist/packages/core/utils.js +1 -1
  45. package/dist/packages/renderer/buildView.d.ts +4 -12
  46. package/dist/packages/renderer/buildView.js +1 -1
  47. package/dist/packages/renderer/buildWaferMap.d.ts +42 -32
  48. package/dist/packages/renderer/buildWaferMap.js +1 -1
  49. package/dist/packages/renderer/colorMap.d.ts +0 -2
  50. package/dist/packages/renderer/colorMap.js +1 -1
  51. package/dist/packages/renderer/columnarInput.d.ts +87 -0
  52. package/dist/packages/renderer/columnarInput.js +1 -0
  53. package/dist/packages/renderer/deprecate.d.ts +1 -1
  54. package/dist/packages/renderer/deprecate.js +1 -1
  55. package/dist/packages/renderer/derivedTests/apply.d.ts +8 -6
  56. package/dist/packages/renderer/derivedTests/apply.js +1 -1
  57. package/dist/packages/renderer/index.d.ts +7 -5
  58. package/dist/packages/renderer/index.js +1 -1
  59. package/dist/packages/renderer/spec.d.ts +3 -7
  60. package/dist/packages/renderer/spec.js +1 -1
  61. package/dist/packages/stats/analyzeWaferLot.js +1 -1
  62. package/dist/packages/stats/analyzeWaferMap.d.ts +134 -2
  63. package/dist/packages/stats/analyzeWaferMap.js +1 -1
  64. package/dist/packages/stats/boxplot.js +1 -1
  65. package/dist/packages/stats/capability.d.ts +7 -7
  66. package/dist/packages/stats/capability.js +1 -1
  67. package/dist/packages/stats/clusterDetection.d.ts +2 -1
  68. package/dist/packages/stats/clusterDetection.js +1 -1
  69. package/dist/packages/stats/connectedComponents.js +1 -1
  70. package/dist/packages/stats/correlation.js +1 -1
  71. package/dist/packages/stats/facets.d.ts +1 -1
  72. package/dist/packages/stats/filterFindings.d.ts +5 -0
  73. package/dist/packages/stats/filterFindings.js +1 -1
  74. package/dist/packages/stats/histogram.js +1 -1
  75. package/dist/packages/stats/index.d.ts +6 -16
  76. package/dist/packages/stats/index.js +1 -1
  77. package/dist/packages/stats/math.d.ts +22 -6
  78. package/dist/packages/stats/math.js +1 -1
  79. package/dist/packages/stats/normalizeInput.js +1 -1
  80. package/dist/packages/stats/patternClassification.d.ts +5 -4
  81. package/dist/packages/stats/patternClassification.js +1 -1
  82. package/dist/packages/stats/regions.d.ts +9 -1
  83. package/dist/packages/stats/regions.js +1 -1
  84. package/dist/packages/stats/renderFindingsReport.d.ts +0 -4
  85. package/dist/packages/stats/renderFindingsReport.js +1 -21
  86. package/dist/packages/stats/renderSummaryReport.js +18 -18
  87. package/dist/packages/stats/scatter.js +1 -1
  88. package/dist/packages/stats/sweep.js +1 -1
  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 +5 -0
  92. package/dist/packages/worker/index.js +1 -1
  93. package/dist/packages/worker/wafermap.worker.d.ts +3 -0
  94. package/dist/packages/worker/wafermap.worker.js +1 -1
  95. package/package.json +2 -2
  96. package/dist/packages/canvas-adapter/deprecated.d.ts +0 -7
  97. package/dist/packages/canvas-adapter/deprecated.js +0 -1
  98. package/dist/packages/core/deprecated.d.ts +0 -60
  99. package/dist/packages/core/deprecated.js +0 -1
  100. package/dist/packages/renderer/deprecated.d.ts +0 -45
  101. package/dist/packages/renderer/deprecated.js +0 -1
  102. package/dist/packages/stats/deprecated.d.ts +0 -85
  103. package/dist/packages/stats/deprecated.js +0 -1
@@ -1 +1 @@
1
- import{diePassStatus as P}from"../../core/dies.js";import{getTestPassStatus as D,isParametricTest as S}from"../buildWaferMap.js";import{classifySpec as V}from"../spec.js";import{parseExpression as k}from"./parser.js";import{evaluate as O}from"./evaluate.js";function p(m){return{code:"derived-test-invalid",message:m,severity:"warning"}}function M(m,l){const w=[],o=n=>{switch(n.k){case"acc":{if(n.kind!=="t")return;const i=l.get(n.test)?.unit;return i?new Set([i]):void 0}case"unary":return n.op==="-"?o(n.arg):void 0;case"binary":{const i=o(n.left),a=o(n.right);if(n.op==="+"||n.op==="-"){if(i&&a){const d=new Set([...i,...a]);return d.size>1&&w.push(`${[...d].join(" and ")} are combined by "${n.op}"`),d}return i??a}return}case"call":{if(n.name==="min"||n.name==="max"||n.name==="mean"||n.name==="sum"){const i=n.args[0];if(i?.k==="vec"&&i.kind==="t"){const a=new Set(i.tests.map(d=>l.get(d)?.unit).filter(d=>!!d));return a.size>1&&w.push(`${[...a].join(" and ")} are combined by "${n.name}"`),a}}return}default:return}};return o(m),w}function T(m,l,w){const o=[],n=new Map((l??[]).map(e=>[e.testNumber,e])),i=e=>`${JSON.stringify(e.name??"")} (test ${e.testNumber})`,a=[],d=new Set;for(const e of m){const t=i(e);if(!Number.isInteger(e.testNumber)){o.push(p(`Derived test ${t} was dropped: \`testNumber\` must be an integer.`));continue}if(w.has(e.testNumber)||n.has(e.testNumber)){o.push(p(`Derived test ${t} was dropped: test number ${e.testNumber} already exists in the data. Measured values are never replaced by a derived test \u2014 give it an unused number.`));continue}if(d.has(e.testNumber)){o.push(p(`Derived test ${t} was dropped: test number ${e.testNumber} is used by an earlier derived test.`));continue}if(typeof e.expression!="string"||e.expression.trim()===""){o.push(p(`Derived test ${t} was dropped: \`expression\` is empty.`));continue}d.add(e.testNumber),a.push(e)}const y=new Map(n);for(const e of a){const{expression:t,constants:u,...c}=e;y.set(e.testNumber,{...c,testType:e.testType??"P"})}const s=new Map,r=new Set;for(const e of a){const t=i(e),u={testDefs:y,constants:e.constants??{}},c=k(e.expression,u);if(!c.ok){o.push(p(`Derived test ${t} was dropped: ${c.message}`)),r.add(e.testNumber);continue}const f=c.type==="boolean",g=!S(e);if(f!==g){o.push(p(f?`Derived test ${t} was dropped: its expression produces a true/false verdict, so it must declare \`testType: 'F'\`.`:`Derived test ${t} was dropped: it declares \`testType: 'F'\` but its expression produces a number. Functional tests carry a verdict, not a measurement.`)),r.add(e.testNumber);continue}if(f&&(e.limitLow!==void 0||e.limitHigh!==void 0)&&o.push(p(`Derived test ${t}: test limits are ignored on a true/false derived test.`)),e.testNumber===void 0)continue;const N=e.testNumber,x=[...new Set(c.reads.map(h=>h.test))].filter(h=>h!==N&&d.has(h));if(c.reads.some(h=>h.test===N)){o.push(p(`Derived test ${t} was dropped: its expression reads itself.`)),r.add(N);continue}for(const h of M(c.ast,y))o.push(p(`Derived test ${t} combines different units \u2014 ${h}. The result is computed as written; set \`unit\` on the derived test to say what it is.`));s.set(N,{raw:e,compiled:{def:{...e,testType:f?"F":"P",derived:!0},ast:c.ast,boolean:f},deps:x})}const b=[],v=new Map,$=(e,t)=>{if(v.get(e)==="done")return!r.has(e);if(v.get(e)==="visiting"){const f=[...t.slice(t.indexOf(e)),e].join(" \u2192 ");for(const g of t.slice(t.indexOf(e)))r.has(g)||(r.add(g),o.push(p(`Derived test ${i(s.get(g).raw)} was dropped: its expression depends on itself through ${f}.`)));return!1}const u=s.get(e);if(u===void 0)return!1;v.set(e,"visiting");let c=!0;for(const f of u.deps)if(!$(f,[...t,e])){if(!r.has(e)){r.add(e);const g=s.get(f)?.raw.name??`test ${f}`;o.push(p(`Derived test ${i(u.raw)} was dropped: it reads ${JSON.stringify(g)} (test ${f}), which was itself dropped.`))}c=!1;break}return v.set(e,"done"),!c||r.has(e)?!1:(b.push(u.compiled),!0)};for(const e of a)$(e.testNumber,[]);return{compiled:b,warnings:o}}export function applyDerivedTests(m,l,w,o){if(w===void 0||w.length===0)return{results:m,testDefs:l,warnings:[]};const n=new Set;for(const s of m){for(const r of Object.keys(s.testValues??{}))n.add(Number(r));for(const r of Object.keys(s.testPass??{}))n.add(Number(r))}const{compiled:i,warnings:a}=T(w,l,n);if(i.length===0)return{results:m,testDefs:l,warnings:a};const d=new Map((l??[]).map(s=>[s.testNumber,s]));for(const s of i)d.set(s.def.testNumber,s.def);return{results:m.map(s=>{let r,b;const v=t=>r?.[t]??s.testValues?.[t],e={value:v,testPass:t=>b?.[t]??D({testValues:r??s.testValues,testPass:b??s.testPass},t,d.get(t)),specPass:t=>{const u=V(v(t),d.get(t));return u===null?void 0:u==="pass"},diePass:()=>P(s,o)};for(const t of i){const u=O(t.ast,e);u!==void 0&&(t.boolean?(b??={...s.testPass},b[t.def.testNumber]=u):(r??={...s.testValues},r[t.def.testNumber]=u))}return r===void 0&&b===void 0?s:{...s,...r!==void 0?{testValues:r}:{},...b!==void 0?{testPass:b}:{}}}),testDefs:[...l??[],...i.map(s=>s.def)],warnings:a}}
1
+ import{isStdfTestNumber as D,STDF_TEST_NUM_MAX as T}from"../../core/stdf.js";import{diePassStatus as M}from"../../core/dies.js";import{getTestPassStatus as V,isParametricTest as _}from"../buildWaferMap.js";import{classifySpec as F}from"../spec.js";import{parseExpression as P}from"./parser.js";import{evaluate as A}from"./evaluate.js";import{extendTable as E,rowView as O,testValue as I,HAS_VALUES as k,HAS_VERDICTS as C,VERDICT_FAIL as L,VERDICT_NONE as S,VERDICT_PASS as R}from"../../core/dieTable.js";function l(N){return{code:"derived-test-invalid",message:N,severity:"warning"}}function j(N,p){const m=[],r=n=>{switch(n.k){case"acc":{if(n.kind!=="t")return;const o=p.get(n.test)?.unit;return o?new Set([o]):void 0}case"unary":return n.op==="-"?r(n.arg):void 0;case"binary":{const o=r(n.left),i=r(n.right);if(n.op==="+"||n.op==="-"){if(o&&i){const a=new Set([...o,...i]);return a.size>1&&m.push(`${[...a].join(" and ")} are combined by "${n.op}"`),a}return o??i}return}case"call":{if(n.name==="min"||n.name==="max"||n.name==="mean"||n.name==="sum"){const o=n.args[0];if(o?.k==="vec"&&o.kind==="t"){const i=new Set(o.tests.map(a=>p.get(a)?.unit).filter(a=>!!a));return i.size>1&&m.push(`${[...i].join(" and ")} are combined by "${n.name}"`),i}}return}default:return}};return r(N),m}function B(N,p,m){const r=[],n=new Map((p??[]).map(e=>[e.testNumber,e])),o=e=>`${JSON.stringify(e.name??"")} (test ${e.testNumber})`,i=[],a=new Set;for(const e of N){const t=o(e);if(!D(e.testNumber)){r.push(l(`Derived test ${t} was dropped: \`testNumber\` must be a whole number from 0 to ${T}, as STDF stores it.`));continue}if(m.has(e.testNumber)||n.has(e.testNumber)){r.push(l(`Derived test ${t} was dropped: test number ${e.testNumber} already exists in the data. Measured values are never replaced by a derived test \u2014 give it an unused number.`));continue}if(a.has(e.testNumber)){r.push(l(`Derived test ${t} was dropped: test number ${e.testNumber} is used by an earlier derived test.`));continue}if(typeof e.expression!="string"||e.expression.trim()===""){r.push(l(`Derived test ${t} was dropped: \`expression\` is empty.`));continue}a.add(e.testNumber),i.push(e)}const v=new Map(n);for(const e of i){const{expression:t,constants:d,...u}=e;v.set(e.testNumber,{...u,testType:e.testType??"P"})}const b=new Map,f=new Set;for(const e of i){const t=o(e),d={testDefs:v,constants:e.constants??{}},u=P(e.expression,d);if(!u.ok){r.push(l(`Derived test ${t} was dropped: ${u.message}`)),f.add(e.testNumber);continue}const s=u.type==="boolean",c=!_(e);if(s!==c){r.push(l(s?`Derived test ${t} was dropped: its expression produces a true/false verdict, so it must declare \`testType: 'F'\`.`:`Derived test ${t} was dropped: it declares \`testType: 'F'\` but its expression produces a number. Functional tests carry a verdict, not a measurement.`)),f.add(e.testNumber);continue}if(s&&(e.limitLow!==void 0||e.limitHigh!==void 0)&&r.push(l(`Derived test ${t}: test limits are ignored on a true/false derived test.`)),e.testNumber===void 0)continue;const $=e.testNumber,x=[...new Set(u.reads.map(h=>h.test))].filter(h=>h!==$&&a.has(h));if(u.reads.some(h=>h.test===$)){r.push(l(`Derived test ${t} was dropped: its expression reads itself.`)),f.add($);continue}for(const h of j(u.ast,v))r.push(l(`Derived test ${t} combines different units \u2014 ${h}. The result is computed as written; set \`unit\` on the derived test to say what it is.`));b.set($,{raw:e,compiled:{def:{...e,testType:s?"F":"P",derived:!0},ast:u.ast,boolean:s},deps:x})}const g=[],w=new Map,y=(e,t)=>{if(w.get(e)==="done")return!f.has(e);if(w.get(e)==="visiting"){const s=[...t.slice(t.indexOf(e)),e].join(" \u2192 ");for(const c of t.slice(t.indexOf(e)))f.has(c)||(f.add(c),r.push(l(`Derived test ${o(b.get(c).raw)} was dropped: its expression depends on itself through ${s}.`)));return!1}const d=b.get(e);if(d===void 0)return!1;w.set(e,"visiting");let u=!0;for(const s of d.deps)if(!y(s,[...t,e])){if(!f.has(e)){f.add(e);const c=b.get(s)?.raw.name??`test ${s}`;r.push(l(`Derived test ${o(d.raw)} was dropped: it reads ${JSON.stringify(c)} (test ${s}), which was itself dropped.`))}u=!1;break}return w.set(e,"done"),!u||f.has(e)?!1:(g.push(d.compiled),!0)};for(const e of i)y(e.testNumber,[]);return{compiled:g,warnings:r}}export function applyDerivedTests(N,p,m,r,n){if(r===void 0||r.length===0)return{table:p,testDefs:m,warnings:[]};const o=new Set;for(const[e,t]of p.values)t.some(d=>!Number.isNaN(d))&&o.add(e);for(const[e,t]of p.verdicts)t.some(d=>d!==S)&&o.add(e);const{compiled:i,warnings:a}=B(r,m,o);if(i.length===0)return{table:p,testDefs:m,warnings:a};const v=new Map((m??[]).map(e=>[e.testNumber,e]));for(const e of i)v.set(e.def.testNumber,e.def);const b=p.rows,f=new Map,g=new Map;for(const e of i)e.boolean?g.set(e.def.testNumber,new Int8Array(b).fill(S)):f.set(e.def.testNumber,new Float64Array(b).fill(NaN));const w=E(p,f,g),y=O(w);for(let e=0;e<b;e++){const t=y(e),d=s=>I(t,s),u={value:d,testPass:s=>V(t,s,v.get(s)),specPass:s=>{const c=F(d(s),v.get(s));return c===null?void 0:c==="pass"},diePass:()=>M(N[e],n)};for(const s of i){const c=A(s.ast,u);c!==void 0&&(s.boolean?(g.get(s.def.testNumber)[e]=c?R:L,w.fields[e]|=C):(f.get(s.def.testNumber)[e]=c,w.fields[e]|=k))}}return{table:w,testDefs:[...m??[],...i.map(e=>e.def)],warnings:a}}
@@ -1,10 +1,12 @@
1
1
  export { getDieKey } from './buildView.js';
2
- export type { View, MapTitleParts, PlotMode, ViewRect, ViewText, ViewHoverPoint, ViewOverlay, ViewOptions, } from './buildView.js';
3
- export * from './buildWaferMap.js';
2
+ export type { PlotMode } from './buildView.js';
3
+ export { buildWaferMap, getTestPassStatus } from './buildWaferMap.js';
4
+ export type { DerivedTestDef, DieResult, WaferConfig, DieConfig, ReticleConfig, LotStackConfig, TestDef, BinDef, MetadataFieldDef, WaferMapInputBase, WaferMapInputSingle, WaferMapInputLotStack, WaferMapInputLayout, WaferMapInput, YieldSummary, WaferWarning, WaferMapResult, } from './buildWaferMap.js';
5
+ export type { DieColumns } from './columnarInput.js';
4
6
  export type { WaferMetadata, DieMetadata } from '../core/metadata.js';
5
- export * from './colorSchemes.js';
6
- export type { BinColors, BinColorOptions, BinColorSource, MapBinColorOptions } from './binColors.js';
7
+ export { registerValueColorScheme, listValueColorSchemes, resolveValueColorFn, registerBinColorScheme, listBinColorSchemes } from './colorSchemes.js';
8
+ export type { ValueColorScheme, BinColorScheme } from './colorSchemes.js';
9
+ export type { BinColors, BinColorSource, MapBinColorOptions } from './binColors.js';
7
10
  export { binColorsForMaps } from './binColors.js';
8
11
  export { DERIVED_MARK, DERIVED_KEY } from './testLabel.js';
9
- export { valueToViridis, valueToGreyscale, getValueColorScheme, buildView, buildHoverText, buildMapTitle, resolveBinColors, getBinColorScheme, contrastTextColor, dieHasTestData, isParametricTest, getDieTestValue, STANDARD_WAFER_DIAMETERS_MM, } from './deprecated.js';
10
12
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- export{getDieKey}from"./buildView.js";export*from"./buildWaferMap.js";export*from"./colorSchemes.js";export{binColorsForMaps}from"./binColors.js";export{DERIVED_MARK,DERIVED_KEY}from"./testLabel.js";export{valueToViridis,valueToGreyscale,getValueColorScheme,buildView,buildHoverText,buildMapTitle,resolveBinColors,getBinColorScheme,contrastTextColor,dieHasTestData,isParametricTest,getDieTestValue,STANDARD_WAFER_DIAMETERS_MM}from"./deprecated.js";
1
+ export{getDieKey}from"./buildView.js";export{buildWaferMap,getTestPassStatus}from"./buildWaferMap.js";export{registerValueColorScheme,listValueColorSchemes,resolveValueColorFn,registerBinColorScheme,listBinColorSchemes}from"./colorSchemes.js";export{binColorsForMaps}from"./binColors.js";export{DERIVED_MARK,DERIVED_KEY}from"./testLabel.js";
@@ -52,13 +52,9 @@ export interface SpecLimits {
52
52
  export declare function classifySpec(value: number | undefined, limits: SpecLimits | undefined): SpecCategory | null;
53
53
  /** True for a verdict that is outside the limits. */
54
54
  export declare function isOutOfSpec(category: SpecCategory | null): boolean;
55
- /**
56
- * How many values in an ascending, finite array `classifySpec` would judge out of
57
- * spec — the same rule, counted by binary search instead of one call per value,
58
- * for callers that already hold the values sorted. `tests/specLimits.test.mjs`
59
- * holds the two in agreement.
60
- */
61
- export declare function countOutOfSpecSorted(sorted: ArrayLike<number>, limits: SpecLimits): number;
55
+ /** How many of `values` are out of spec: exactly the per-value
56
+ * {@link classifySpec} judgement, counted. Any order. */
57
+ export declare function countOutOfSpec(values: ArrayLike<number>, limits: SpecLimits): number;
62
58
  /** True when `limits` can produce a verdict at all — i.e. at least one limit is
63
59
  * defined. Used to reject `specPass[n]` at parse time rather than silently
64
60
  * yielding no-data for every die. */
@@ -1 +1 @@
1
- export function classifySpec(i,n){return i===void 0||!Number.isFinite(i)||n===void 0||n.limitLow===void 0&&n.limitHigh===void 0?null:n.limitLow!==void 0&&(n.limitLowInclusive===!1?i<=n.limitLow:i<n.limitLow)?"failLow":n.limitHigh!==void 0&&(n.limitHighInclusive===!1?i>=n.limitHigh:i>n.limitHigh)?"failHigh":"pass"}export function isOutOfSpec(i){return i==="failLow"||i==="failHigh"}export function countOutOfSpecSorted(i,n){let u=0,h=0;if(n.limitLow!==void 0){const l=n.limitLow,o=n.limitLowInclusive===!1?e=>e<=l:e=>e<l;let f=0,t=i.length;for(;f<t;){const e=f+t>>1;o(i[e])?f=e+1:t=e}u=f}if(n.limitHigh!==void 0){const l=n.limitHigh,o=n.limitHighInclusive===!1?e=>e<l:e=>e<=l;let f=0,t=i.length;for(;f<t;){const e=f+t>>1;o(i[e])?f=e+1:t=e}h=i.length-Math.max(f,u)}return u+h}export function hasSpecLimits(i){return i!==void 0&&(i.limitLow!==void 0||i.limitHigh!==void 0)}
1
+ export function classifySpec(n,i){return n===void 0||!Number.isFinite(n)||i===void 0||i.limitLow===void 0&&i.limitHigh===void 0?null:i.limitLow!==void 0&&(i.limitLowInclusive===!1?n<=i.limitLow:n<i.limitLow)?"failLow":i.limitHigh!==void 0&&(i.limitHighInclusive===!1?n>=i.limitHigh:n>i.limitHigh)?"failHigh":"pass"}export function isOutOfSpec(n){return n==="failLow"||n==="failHigh"}export function countOutOfSpec(n,i){let f=0;for(let e=0;e<n.length;e++)isOutOfSpec(classifySpec(n[e],i))&&f++;return f}export function hasSpecLimits(n){return n!==void 0&&(n.limitLow!==void 0||n.limitHigh!==void 0)}
@@ -1 +1 @@
1
- import{analyzeWaferMap as g}from"./analyzeWaferMap.js";import{normalizeInput as h}from"./normalizeInput.js";import{poolCapability as b,poolRegionYield as w,poolPassRates as v}from"./summaryFigures.js";import{mergeTestDefs as I}from"./mergeTestDefs.js";import{median as p}from"../core/utils.js";import{describeWaferPopulation as S,populationStat as k}from"./population.js";const P=1.3,R=2;function x(f,c){const o={info:0,notable:1,unusual:2};return o[f]>=o[c]?f:c}function z(f){const c=new Map;for(const t of f){const i=new Set,l=new Set(t.summary.findings.flatMap(n=>n.absorbedIds??[]));for(const n of t.summary.findings){if(l.has(n.id))continue;const s=[n.variable.kind,n.variable.index??"",n.variable.bin??"",n.comparison.family,n.comparison.left,n.effect.direction].join("|");if(i.has(s))continue;i.add(s);const a=c.get(s)??{finding:n,waferIndices:[],severity:n.severity};a.waferIndices.push(t.waferIndex),a.severity=x(a.severity,n.severity),c.set(s,a)}}const o=[];for(const[t,i]of c){if(i.waferIndices.length<R)continue;const l=i.waferIndices.length/Math.max(1,f.length),n=l>=.6||i.severity==="unusual"?"unusual":"notable",s=i.finding;o.push({id:`lot-repeat:${t}`,level:"lot",severity:n,variable:{...s.variable},comparison:{...s.comparison},effect:{direction:s.effect.direction,absoluteDelta:s.effect.absoluteDelta,relativeDelta:s.effect.relativeDelta,effectSize:s.effect.effectSize},stats:{method:"wafer-finding-frequency",sampleSizeLeft:i.waferIndices.length,sampleSizeRight:f.length-i.waferIndices.length},summary:`${s.summary} \u2014 seen on ${i.waferIndices.length}/${f.length} wafers (${Math.round(l*100)}%)`,highlight:{kind:"wafer",waferIndices:i.waferIndices}})}return o}function $(f,c){const o=k(c,"median"),t=f.map(a=>({waferIndex:a.waferIndex,yieldPercent:a.summary.stats.yieldPercent})).filter(a=>a.yieldPercent!==null);if(t.length<3)return[];const i=t.map(a=>a.yieldPercent),l=p(i),n=p(i.map(a=>Math.abs(a-l)));if(!Number.isFinite(n)||n===0)return[];const s=[];for(const a of t){const u=.6745*(a.yieldPercent-l)/n;if(Math.abs(u)<P)continue;const e=a.yieldPercent-l;s.push({id:`inter-wafer:yield:${a.waferIndex}`,level:"inter-wafer",severity:Math.abs(u)>=2?"unusual":"notable",variable:{kind:"yield",label:"Yield"},comparison:{family:"wafer",left:`Wafer ${a.waferIndex+1}`,right:o.charAt(0).toUpperCase()+o.slice(1)},effect:{direction:e>0?"higher":"lower",absoluteDelta:e,relativeDelta:l===0?void 0:e/l,effectSize:u},stats:{method:"mad-z-score",sampleSizeLeft:1,sampleSizeRight:t.length-1},summary:`Wafer ${a.waferIndex+1} yield is ${Math.abs(e).toFixed(1)} percentage points ${e>0?"higher":"lower"} than the ${o}`,highlight:{kind:"wafer",waferIndices:[a.waferIndex]}})}return s}export function analyzeWaferLot(f,c={}){const o=f.map(h),t=o.map((e,r)=>({waferIndex:r,summary:c.perWaferSummaries?.[r]??g(e,c)})),i=[...z(t),...$(t,S(t.map(e=>e.summary.wafer)))].sort((e,r)=>{const d=e.severity==="unusual"?2:e.severity==="notable"?1:0,y=r.severity==="unusual"?2:r.severity==="notable"?1:0;return d!==y?y-d:e.summary.localeCompare(r.summary,"en")}),l=t.map(e=>e.summary.wafer).filter(e=>!!e),n={},s=[];if(l.length>0){const e=new Set;for(const r of l)for(const d of Object.keys(r))e.add(d);e.delete("wafer"),e.delete("waferId");for(const r of e){const d=l.filter(m=>r in m).map(m=>m[r]);d.every(m=>m===d[0])?n[r]=d[0]:s.push(r)}}const a=t.map(({waferIndex:e,summary:r})=>({waferIndex:e,yieldPercent:r.stats.yieldPercent})),u=t.map(({waferIndex:e,summary:r})=>r.stats.perTestStats?.length?{waferIndex:e,tests:r.stats.perTestStats}:null).filter(e=>e!==null);return{level:"lot",hasNotableFindings:i.some(e=>e.severity!=="info")||t.some(e=>e.summary.hasNotableFindings),findings:i,lot:Object.keys(n).length>0?n:void 0,...s.length>0?{mixedIdentityFields:s}:{},stats:{waferCount:f.length,...M(o,t.map(e=>e.summary))},lotYieldSeries:a,perWafer:t,...u.length>0?{perWaferTestStats:u}:{}}}function M(f,c){const o={...v(c)},t=b(c,I(f).defs);t&&(o.capability=t);const i=w(c,f.map(l=>l.ringCount));return i&&(o.regionYield=i),o}
1
+ import{analyzeWaferMap as M,candidatesOf as $,describeRegional as A,resolveOptions as q,adjustPValues as C,twoProportionZ as O,zPValue as x,welchOfSums as j,rateRelativeEffect as V,passesRateGate as _,severityForFinding as Y,severityForScore as G,collapseRedundantFindings as H}from"./analyzeWaferMap.js";import{normalizeInput as U}from"./normalizeInput.js";import{poolCapability as Z,poolRegionYield as J,poolPassRates as Q}from"./summaryFigures.js";import{mergeTestDefs as X}from"./mergeTestDefs.js";import{findingPatternKey as ee}from"./filterFindings.js";import{median as B}from"../core/utils.js";import{describeWaferPopulation as te,populationStat as ne}from"./population.js";const ie=1.3,D=2;function F(c,o){const r={info:0,notable:1,unusual:2};return r[c]>=r[o]?c:o}const se=new Set(["ring","quadrant","sector","reticle-position","test-site"]),ae=new Set(["yield","hardBin","softBin","functionalTest","test"]);function re(c){return se.has(c.comparison.family)&&ae.has(c.variable.kind)}function oe(c){const o=c.map(s=>s.facts);if(!o.length||o.some(s=>!s))return{coincident:new Set,yieldSoftBin:void 0,passBins:[]};const[r,...n]=o,l=n.every(s=>s.passBins.length===r.passBins.length&&s.passBins.every(i=>r.passBins.includes(i)));return{coincident:new Set([...r.coincident].filter(s=>n.every(i=>i.coincident.has(s)))),yieldSoftBin:n.every(s=>s.yieldSoftBin===r.yieldSoftBin)?r.yieldSoftBin:void 0,passBins:l?r.passBins:[]}}function E(c,o){let r=0,n=0,l=0,s=0,i=0,m=0,f=0,p=0,t=0,d=!0,e=0;for(const{waferIndex:h,c:u}of c){const b=o(h,u),k=u.rate??u.mean,v=Math.sqrt(k.n+b.n);if(u.rate)r+=v*O(u.rate.hits,u.rate.n,b.hits,b.n),l+=u.rate.hits,s+=u.rate.n,i+=b.hits,m+=b.n;else{const S=j(u.mean,b.n,b.sum,b.sq);r+=v*S.z,f+=u.mean.n*S.effectSize,p+=u.mean.n*S.delta,e+=u.mean.n,u.relativeDelta===void 0?d=!1:t+=u.mean.n*u.relativeDelta}n+=v*v}const a=n>0?r/Math.sqrt(n):0,y=c[0].c;if(y.rate){const h=i/m,u=l/s-h;return{z:a,delta:u,relativeDelta:h===0?void 0:u/h,size:V({hits:l,n:s,restHits:i,restN:m,passRate:y.rate.passRate})}}return{z:a,delta:p/e,relativeDelta:d?t/e:void 0,size:f/e}}function K(c,o,r,n){return c.rate?_(o,r.delta,r.size,n):o<=n.significanceLevel&&Math.abs(r.size)>=n.minimumEffectSize}function le(c,o,r){const n=new Map;for(const{waferIndex:t,byKey:d}of c)for(const e of d.values())(n.get(e.key)??n.set(e.key,[]).get(e.key)).push({waferIndex:t,c:e});const l=new Map(c.map(t=>[t.waferIndex,t.byKey])),s=(t,d)=>d.rate?{hits:d.rate.restHits,n:d.rate.restN,sum:0,sq:0}:{hits:0,n:d.mean.restN,sum:d.mean.restSum,sq:d.mean.restSq},i=[];for(const[t,d]of n){if(d.length<D)continue;const e=E(d,s);if(e.z===0)continue;const a=d[0].c;i.push({key:t,entries:d,combined:e,finding:{id:`lot-region:${t}`,level:"lot",severity:"info",variable:{...a.variable,label:a.label},comparison:{...a.comparison},effect:{direction:e.z>0?"higher":"lower",absoluteDelta:e.delta,relativeDelta:e.relativeDelta,effectSize:a.rate?e.delta:e.size},stats:{method:"stouffer-z",pValue:x(e.z),sampleSizeLeft:0,sampleSizeRight:0},summary:"",highlight:{kind:"wafer",waferIndices:[]}}})}C(i.map(t=>t.finding));const m=i.filter(t=>K(t.entries[0].c,t.finding.stats.adjustedPValue??1,t.combined,o)),f=(t,d)=>{const e=t.entries[0].c,a=d.entries[0].c;return e.source===a.source&&e.variable.kind===a.variable.kind&&e.variable.bin===a.variable.bin&&e.variable.index===a.variable.index&&e.region.family===a.region.family},p=[];for(const t of m){const d=t.finding.stats.pValue??1,e=m.filter(g=>g!==t&&f(t,g)&&g.finding.effect.direction!==t.finding.effect.direction&&(g.finding.stats.pValue??1)<d);if(e.length){const g=(I,T)=>{const z=s(I,T);for(const L of e){const w=l.get(I)?.get(L.key);w&&(w.rate?(z.hits-=w.rate.hits,z.n-=w.rate.n):(z.n-=w.mean.n,z.sum-=w.mean.sum,z.sq-=w.mean.sq))}return z},R=t.entries.filter(I=>g(I.waferIndex,I.c).n>=o.minimumSampleSize);if(!R.length)continue;const P=E(R,g),N=d>0?(t.finding.stats.adjustedPValue??d)/d:1;if(Math.sign(P.z)!==Math.sign(t.combined.z)||!K(t.entries[0].c,Math.min(1,x(P.z)*N),P,o))continue}const a=Math.sign(t.combined.z),y=t.entries.filter(g=>Math.sign(g.c.delta)===a),h=c.length,u=t.entries[0].c,b=t.finding.stats.adjustedPValue??d,k=u.rate?Y(b,t.combined.delta,t.combined.size):G(b,t.combined.size),v=F(k,y.length/h>=.6?"unusual":"info"),S={};for(const g of y)S[g.waferIndex]=[...g.c.region.dieKeys];p.push({...t.finding,severity:v,stats:{...t.finding.stats,sampleSizeLeft:y.length,sampleSizeRight:h-y.length},summary:`${A(u,t.combined.delta,t.combined.relativeDelta)} \u2014 ${t.finding.effect.direction} on ${y.length}/${h} wafers, all wafers' data combined`,highlight:{kind:"wafer",waferIndices:y.map(g=>g.waferIndex),dieKeysByWafer:S}})}return H(p,r),p}const W={"Edge-ring":"edge","Edge-local":"edge","Center cluster":"centre or donut",Donut:"centre or donut"};function ce(c){const o=new Map;for(const n of c){const l=new Set,s=new Set(n.summary.findings.flatMap(i=>i.absorbedIds??[]));for(const i of n.summary.findings){if(s.has(i.id)||re(i))continue;const m=i.variable.kind==="spatialPattern"?W[i.comparison.left]:void 0,f=m?`spatialPattern|${m}`:ee(i);if(l.has(f))continue;l.add(f);const p=o.get(f)??{finding:i,waferIndices:[],severity:i.severity,labels:new Map,dieKeysByWafer:{}};if(p.waferIndices.push(n.waferIndex),i.variable.kind==="spatialPattern"){p.labels.set(i.comparison.left,(p.labels.get(i.comparison.left)??0)+1);const t=i.highlight.dieKeys;t&&(p.dieKeysByWafer[n.waferIndex]=t)}p.severity=F(p.severity,i.severity),o.set(f,p)}}const r=[];for(const[n,l]of o){if(l.waferIndices.length<D)continue;const s=l.waferIndices.length/Math.max(1,c.length),i=s>=.6||l.severity==="unusual"?"unusual":"notable",m=l.finding;r.push({id:`lot-repeat:${n}`,level:"lot",severity:i,variable:{...m.variable},comparison:{...m.comparison},effect:{direction:m.effect.direction,absoluteDelta:m.effect.absoluteDelta,relativeDelta:m.effect.relativeDelta,effectSize:m.effect.effectSize},stats:{method:"wafer-finding-frequency",sampleSizeLeft:l.waferIndices.length,sampleSizeRight:c.length-l.waferIndices.length},summary:`${fe(m,l.labels)} \u2014 seen on ${l.waferIndices.length}/${c.length} wafers (${Math.round(s*100)}%)`,highlight:{kind:"wafer",waferIndices:l.waferIndices,...l.labels.size?{dieKeysByWafer:l.dieKeysByWafer}:{}}})}return r}function fe(c,o){if(!o.size)return c.summary;const r=s=>s.toLowerCase();if(o.size===1)return`Spatial pattern: ${r([...o.keys()][0])}`;const n=W[c.comparison.left],l=[...o].sort((s,i)=>i[1]-s[1]).map(([s,i])=>`${r(s)} on ${i}`);return`Spatial pattern: ${n} (${l.join(", ")})`}function de(c,o){const r=ne(o,"median"),n=c.map(f=>({waferIndex:f.waferIndex,yieldPercent:f.summary.stats.yieldPercent})).filter(f=>f.yieldPercent!==null);if(n.length<3)return[];const l=n.map(f=>f.yieldPercent),s=B(l),i=B(l.map(f=>Math.abs(f-s)));if(!Number.isFinite(i)||i===0)return[];const m=[];for(const f of n){const p=.6745*(f.yieldPercent-s)/i;if(Math.abs(p)<ie)continue;const t=f.yieldPercent-s;m.push({id:`inter-wafer:yield:${f.waferIndex}`,level:"inter-wafer",severity:Math.abs(p)>=2?"unusual":"notable",variable:{kind:"yield",label:"Yield"},comparison:{family:"wafer",left:`Wafer ${f.waferIndex+1}`,right:r.charAt(0).toUpperCase()+r.slice(1)},effect:{direction:t>0?"higher":"lower",absoluteDelta:t,relativeDelta:s===0?void 0:t/s,effectSize:p},stats:{method:"mad-z-score",sampleSizeLeft:1,sampleSizeRight:n.length-1},summary:`Wafer ${f.waferIndex+1} yield is ${Math.abs(t).toFixed(1)} percentage points ${t>0?"higher":"lower"} than the ${r}`,highlight:{kind:"wafer",waferIndices:[f.waferIndex]}})}return m}export function analyzeWaferLot(c,o={}){const r=c.map(U),n=r.map((e,a)=>({waferIndex:a,summary:o.perWaferSummaries?.[a]??M(e,o)})),l=n.map(({waferIndex:e,summary:a})=>$(a)??$(M(r[e],o))),s=l.map((e,a)=>({waferIndex:a,byKey:new Map(e.candidates.map(y=>[y.key,y]))})),i=[...le(s,q(o).resolved,oe(l)),...ce(n),...de(n,te(n.map(e=>e.summary.wafer)))].sort((e,a)=>{const y=e.severity==="unusual"?2:e.severity==="notable"?1:0,h=a.severity==="unusual"?2:a.severity==="notable"?1:0;return y!==h?h-y:e.summary.localeCompare(a.summary,"en")}),m=n.map(e=>e.summary.wafer).filter(e=>!!e),f={},p=[];if(m.length>0){const e=new Set;for(const a of m)for(const y of Object.keys(a))e.add(y);e.delete("wafer"),e.delete("waferId");for(const a of e){const y=m.filter(u=>a in u).map(u=>u[a]);y.every(u=>u===y[0])?f[a]=y[0]:p.push(a)}}const t=n.map(({waferIndex:e,summary:a})=>({waferIndex:e,yieldPercent:a.stats.yieldPercent})),d=n.map(({waferIndex:e,summary:a})=>a.stats.perTestStats?.length?{waferIndex:e,tests:a.stats.perTestStats}:null).filter(e=>e!==null);return{level:"lot",hasNotableFindings:i.some(e=>e.severity!=="info")||n.some(e=>e.summary.hasNotableFindings),findings:i,lot:Object.keys(f).length>0?f:void 0,...p.length>0?{mixedIdentityFields:p}:{},stats:{waferCount:c.length,...ue(r,n.map(e=>e.summary))},lotYieldSeries:t,perWafer:n,...d.length>0?{perWaferTestStats:d}:{}}}function ue(c,o){const r={...Q(o)},n=Z(o,X(c).defs);n&&(r.capability=n);const l=J(o,c.map(s=>s.ringCount));return l&&(r.regionYield=l),r}
@@ -1,6 +1,37 @@
1
1
  import type { Die } from '../core/dies.js';
2
- import type { TestDef } from '../renderer/buildWaferMap.js';
3
- import type { AnalyzeWaferMapInput, AnalyzeWaferMapOptions, StatsSummary } from './types.js';
2
+ import type { TestDef, WaferWarning } from '../renderer/buildWaferMap.js';
3
+ import type { AnalyzeWaferMapInput, AnalyzeWaferMapOptions, StatsFinding, StatsSeverity, StatsSummary } from './types.js';
4
+ import { type StatsRegion } from './regions.js';
5
+ interface EligibleDie extends Die {
6
+ hbin?: number;
7
+ }
8
+ interface RawFinding extends StatsFinding {
9
+ comparison: StatsFinding['comparison'];
10
+ stats: StatsFinding['stats'];
11
+ effect: StatsFinding['effect'];
12
+ }
13
+ export type ResolvedOptions = Required<Omit<AnalyzeWaferMapOptions, 'testNumbers'>> & {
14
+ testNumbers?: number[];
15
+ passBins: number[];
16
+ ringCount: number;
17
+ significanceLevel: number;
18
+ minimumEffectSize: number;
19
+ minimumRelativeEffect: number;
20
+ minimumSampleSize: number;
21
+ minimumClusterSize: number;
22
+ /** Soft bins every die carrying which passes — computed once per analysis. */
23
+ softPassBins?: ReadonlySet<number>;
24
+ };
25
+ /**
26
+ * Clamp the numeric options into ranges that can produce a meaningful analysis,
27
+ * reporting anything corrected. A caller who passes nonsense previously got a
28
+ * confident, wrong answer with no indication anything was amiss; they now get
29
+ * the nearest sane analysis plus a warning saying what was changed.
30
+ */
31
+ export declare function resolveOptions(options: AnalyzeWaferMapOptions): {
32
+ resolved: ResolvedOptions;
33
+ warnings: WaferWarning[];
34
+ };
4
35
  /**
5
36
  * Per-test pass rate for functional (`testType: 'F'`) tests — the functional
6
37
  * counterpart of `computeTestSpecYield`. Same population convention: partial and
@@ -9,5 +40,106 @@ import type { AnalyzeWaferMapInput, AnalyzeWaferMapOptions, StatsSummary } from
9
40
  * functional data is counted identically); dies never tested count as neither.
10
41
  */
11
42
  export declare function computeFunctionalYield(dies: Die[], testDefs: TestDef[] | undefined): StatsSummary['stats']['functionalYield'];
43
+ /** The two-proportion z statistic, signed as left − right; 0 when untestable. */
44
+ export declare function twoProportionZ(leftPass: number, leftTotal: number, rightPass: number, rightTotal: number): number;
45
+ /** Two-sided p-value of a z statistic. */
46
+ export declare function zPValue(z: number): number;
47
+ export declare function adjustPValues(findings: RawFinding[]): RawFinding[];
48
+ /**
49
+ * A rate finding's counts: hits and dies in its region and in the rest of the
50
+ * wafer. `passRate` marks a rate of passes (yield, a functional pass rate)
51
+ * rather than of an outcome such as a bin or a limit fail.
52
+ */
53
+ export interface RateCounts {
54
+ hits: number;
55
+ n: number;
56
+ restHits: number;
57
+ restN: number;
58
+ passRate: boolean;
59
+ }
60
+ /**
61
+ * The size of a rate's relative change, for the effect gate and severity —
62
+ * always measured on the adverse outcome. A pass rate is judged by its failure
63
+ * rate: yield 98% → 94% is failures 2% → 6%, a tripling, and must reach the same
64
+ * verdict as the bin rate made of the same dies; as a relative change in yield
65
+ * (4%) it never could. A rate that is zero in the rest of the wafer and not in
66
+ * the region — a bin found only at the edge — is the largest relative change
67
+ * there is, not none (its published `relativeDelta` has no finite value).
68
+ */
69
+ export declare function rateRelativeEffect(c: RateCounts): number;
70
+ export declare function severityForFinding(pValue: number, delta: number, relEffect: number): StatsSeverity;
71
+ export declare function severityForScore(pValue: number, score: number): StatsSeverity;
72
+ export type RegionFamily = 'ring' | 'quadrant' | 'reticle-position' | 'test-site' | 'sector';
73
+ /**
74
+ * One regional comparison a wafer's analysis made, reported or not — what
75
+ * `analyzeWaferLot` combines across wafers. Compact on purpose: counts or sums
76
+ * and the region, never the finding's own copy of its die keys.
77
+ */
78
+ export interface RegionCandidate {
79
+ /** `source|variable|family|region key` — equal for the same comparison on every wafer. */
80
+ key: string;
81
+ source: 'yield' | 'hardBin' | 'softBin' | 'functional' | 'limitFail' | 'test';
82
+ /** The finding's own objects, shared; the wafer's redundancy collapse later
83
+ * relabels a surviving hard/soft twin, so the label is kept as it was here. */
84
+ variable: StatsFinding['variable'];
85
+ label: string;
86
+ comparison: StatsFinding['comparison'];
87
+ region: StatsRegion;
88
+ /** Signed z of the wafer's own test (region − rest). */
89
+ z: number;
90
+ /** Region − rest, in the finding's units; and its standardised size for test values. */
91
+ delta: number;
92
+ effectSize: number;
93
+ relativeDelta: number | undefined;
94
+ rate?: RateCounts;
95
+ mean?: MeanStats;
96
+ }
97
+ /** A wafer analysis's comparisons and the facts its redundancy collapse used. */
98
+ export interface WaferComparisons {
99
+ candidates: RegionCandidate[];
100
+ facts: RedundancyFacts | undefined;
101
+ }
102
+ /** The comparisons behind a summary `analyzeWaferMap` returned in this realm (not
103
+ * after a structured clone, which drops them). */
104
+ export declare function candidatesOf(summary: StatsSummary): WaferComparisons | undefined;
105
+ /**
106
+ * A combined finding's sentence, in the words the wafer finding of the same
107
+ * comparison uses — one wording per kind, whichever level reports it.
108
+ */
109
+ export declare function describeRegional(c: RegionCandidate, delta: number, relativeDelta: number | undefined): string;
110
+ export declare function passesRateGate(adjustedP: number, delta: number, relEffect: number, options: ResolvedOptions): boolean;
111
+ /** A test-value finding's per-region sums, in the builder's shifted space. */
112
+ export interface MeanStats {
113
+ n: number;
114
+ sum: number;
115
+ sq: number;
116
+ restN: number;
117
+ restSum: number;
118
+ restSq: number;
119
+ }
120
+ /** Welch's test of a region's sums against a rest given as sums (one wafer's shifted space). */
121
+ export declare function welchOfSums(own: MeanStats, restN: number, restSum: number, restSq: number): ReturnType<typeof welchFromStats>;
122
+ /**
123
+ * Welch two-sample comparison from summary statistics (count, mean, variance).
124
+ * The findings pass accumulates these per region in a single columnar scan, so
125
+ * the comparison never materialises the value arrays — see buildTestValueFindings.
126
+ */
127
+ export declare function welchFromStats(leftN: number, leftMean: number, leftVar: number, rightN: number, rightMean: number, rightVar: number): {
128
+ pValue: number;
129
+ effectSize: number;
130
+ delta: number;
131
+ z: number;
132
+ };
133
+ /** What {@link collapseRedundantFindings} needs to know about a wafer's dies. */
134
+ export interface RedundancyFacts {
135
+ /** `"hard:soft"` bin pairs carried by exactly the same dies. */
136
+ coincident: Set<string>;
137
+ /** The soft bin whose rate is the yield, if one is. */
138
+ yieldSoftBin: number | undefined;
139
+ passBins: number[];
140
+ }
141
+ export declare function redundancyFacts(eligibleDies: EligibleDie[], passBins: number[]): RedundancyFacts;
142
+ export declare function collapseRedundantFindings(findings: RawFinding[], facts: RedundancyFacts): void;
12
143
  export declare function analyzeWaferMap(input: AnalyzeWaferMapInput, options?: AnalyzeWaferMapOptions): StatsSummary;
144
+ export {};
13
145
  //# sourceMappingURL=analyzeWaferMap.d.ts.map
@@ -1 +1 @@
1
- import{normalizeInput as Ke}from"./normalizeInput.js";import{computeCapability as Te,computeTestFlagYield as xe,computeRegionYield as je}from"./summaryFigures.js";import{isYieldEligibleDie as ae,getDieKey as T,isPositionedDie as ye}from"../core/dies.js";import{getTestPassStatus as ee,isParametricTest as H}from"../renderer/buildWaferMap.js";import{markedTestLabel as x,derivedFields as te}from"../renderer/testLabel.js";import{buildQuadrantRegions as Ae,buildReticlePositionRegions as Oe,buildRingRegions as qe,buildSectorRegions as He,buildTestSiteRegions as Ie,sectorCompassNames as Se,areQuadrantsAdjacent as Ye,parseRegionKey as I}from"./regions.js";import{buildClusterFindings as _e}from"./clusterDetection.js";import{classifyPattern as We}from"./patternClassification.js";import{normalCdf as we}from"./math.js";import{quantile as re}from"./math.js";import{mean as ne,clamp01 as $e}from"../core/utils.js";import{classifySpec as le,isOutOfSpec as ve}from"../renderer/spec.js";const Qe={significanceLevel:{min:1e-6,max:1},minimumEffectSize:{min:0,max:1},minimumRelativeEffect:{min:0,max:Number.MAX_SAFE_INTEGER}},De=[4,8,16,32];function Ue(s){const a=Object.fromEntries(Object.entries(s).filter(([,n])=>n!==void 0)),i={...j,...a},o=[],e=[];for(const n of["significanceLevel","minimumEffectSize","minimumRelativeEffect"]){const{min:l,max:t}=Qe[n],u=i[n];if(typeof u!="number"||!Number.isFinite(u)){e.push(`${n}=${String(u)} is not a finite number (using ${j[n]})`),i[n]=j[n];continue}const f=Math.min(t,Math.max(l,u));f!==u&&(e.push(`${n}=${u} is outside ${l}\u2013${t} (using ${f})`),i[n]=f)}if(De.includes(i.sectorCount)||(e.push(`sectorCount=${i.sectorCount} is not one of ${De.join(", ")} (using ${j.sectorCount})`),i.sectorCount=j.sectorCount),e.length){const n=`analyzeWaferMap: ${e.join("; ")}. The analysis ran with the corrected values.`;console.warn(`[wafermap] ${n}`),o.push({code:"analysis-option-corrected",message:n,severity:"warning"})}const c=["passBins","ringCount"].filter(n=>a[n]!==void 0);if(c.length){const n=`analyzeWaferMap: ${c.join(" and ")} ${c.length>1?"are":"is"} no longer an analysis option and was ignored. Set ${c.length>1?"them":"it"} on buildWaferMap; the analysis used the value the map was built with.`;console.warn(`[wafermap] ${n}`),o.push({code:"analysis-option-corrected",message:n,severity:"warning"})}const r=["enableYieldAnalysis","enableHardBinAnalysis","enableSoftBinAnalysis","enableReticlePositionAnalysis","enableTestSiteAnalysis","enableClusterAnalysis","enableAngularAnalysis","enablePatternClassification"].filter(n=>a[n]!==void 0);if(r.length){const n=`analyzeWaferMap: ${r.join(", ")} ${r.length>1?"are":"is"} no longer an option and was ignored: every analysis now runs. Filter the findings (filterFindings) to show fewer.`;console.warn(`[wafermap] ${n}`),o.push({code:"analysis-option-corrected",message:n,severity:"warning"})}return{resolved:i,warnings:o}}const j={ringCount:4,passBins:[1],significanceLevel:.05,minimumEffectSize:.2,minimumRelativeEffect:1,minimumSampleSize:5,enableTestValueAnalysis:!1,computePerTestStats:!1,sectorCount:8,minimumClusterSize:5};function Ge(s,a){const i={...s};return i.minimumClusterSize=Math.max(5,Math.round(a*.003)),i}function Xe(s){return ae(s)?s.hbin!==void 0||s.sbin!==void 0||s.testValues!==void 0&&Object.keys(s.testValues).length>0:!1}function Je(s,a,i){if(!s||a)return;const o=(i??[]).filter(e=>H(e)&&(e.limitLow!==void 0||e.limitHigh!==void 0));if(o.length!==0)return e=>{for(const c of o)if(ve(le(e.testValues?.[c.testNumber],c)))return!0;return!1}}function Ze(s,a,i){const o=new Set,e=new Set,c=new Set,r=new Map,n=new Map;for(const l of s){if(l.testValues)for(const t of Object.keys(l.testValues))o.add(Number(t));if(l.testPass)for(const t of Object.keys(l.testPass))o.add(Number(t));l.hbin!==void 0&&e.add(l.hbin),l.sbin!==void 0&&c.add(l.sbin),ae(l)&&(l.hbin!==void 0&&r.set(l.hbin,(r.get(l.hbin)??0)+1),l.sbin!==void 0&&n.set(l.sbin,(n.get(l.sbin)??0)+1))}return{totalDies:s.length,analyzedDies:a,excludedDies:s.length-a,yieldPercent:i,testsConsidered:[...o].sort((l,t)=>l-t),hardBinsConsidered:[...e].sort((l,t)=>l-t),softBinsConsidered:[...c].sort((l,t)=>l-t),...r.size?{hardBinCounts:Object.fromEntries(r)}:{},...n.size?{softBinCounts:Object.fromEntries(n)}:{}}}function et(s,a){if(!a?.length)return;const i=a.filter(e=>H(e)&&(e.limitLow!==void 0||e.limitHigh!==void 0));if(!i.length)return;const o=[];for(const e of i){const c=e.testNumber;let r=0,n=0,l=0,t=0;for(const u of s){if(u.partial||u.edgeExcluded)continue;const f=le(u.testValues?.[c],e);f!==null&&(t++,f==="failLow"?n++:f==="failHigh"?l++:r++)}o.push({testNumber:c,label:e.name,passDies:r,failLowDies:n,failHighDies:l,totalDies:t,yieldPercent:t>0?r/t*100:null})}return o.length?o:void 0}export function computeFunctionalYield(s,a){if(!a?.length)return;const i=a.filter(e=>!H(e));if(!i.length)return;const o=[];for(const e of i){const c=e.testNumber;let r=0,n=0;for(const t of s){if(t.partial||t.edgeExcluded)continue;const u=ee(t,c,e);u!==void 0&&(u?r++:n++)}const l=r+n;o.push({testNumber:c,label:x(e,c),...te(e),passDies:r,failDies:n,totalDies:l,passRatePercent:l>0?r/l*100:null})}return o.length?o:void 0}function tt(s,a,i,o){const e=[];for(const c of a){const r=[];for(const f of s){if(f.partial||f.edgeExcluded)continue;const m=f.testValues?.[c];m!==void 0&&Number.isFinite(m)&&r.push(m)}if(r.length<o)continue;const n=ne(r),l=Math.sqrt(he(r,n)),t=Float64Array.from(r);t.sort();const u=i?.find(f=>f.testNumber===c)?.name??String(c);e.push({testNumber:c,label:u,count:r.length,min:t[0],max:t[t.length-1],mean:n,stddev:l,median:re(t,.5),q1:re(t,.25),q3:re(t,.75)})}return e.length?e:void 0}function A(s,a,i,o){const e=(s+i)/(a+o),c=e*(1-e)*(1/a+1/o);if(!Number.isFinite(c)||c<=0)return 1;const r=(s/a-i/o)/Math.sqrt(c);return $e(2*(1-we(Math.abs(r))))}function fe(s){const a=new Map;for(const i of s){const o=`${i.variable.kind}:${i.comparison.family}`,e=a.get(o)??[];e.push(i),a.set(o,e)}for(const i of a.values()){const o=[...i].filter(r=>r.stats.pValue!==void 0).sort((r,n)=>(r.stats.pValue??1)-(n.stats.pValue??1)),e=o.length;let c=1;for(let r=e-1;r>=0;r--){const n=o[r],l=n.stats.pValue??1,t=Math.min(1,Math.min(c,l*e/(r+1)));n.stats.adjustedPValue=t,c=t}}return s}function ie(s,a,i){const o=Math.abs(a),e=i!==void 0?Math.abs(i):0;return s<=.01&&(o>=.3||e>=2.5)?"unusual":s<=.05&&(o>=.2||e>=1.5)?"notable":"info"}function ce(s,a){return s<=.01&&Math.abs(a)>=.5?"unusual":s<=.05&&Math.abs(a)>=.15?"notable":"info"}function se(s){return s==="reticle-position"?"other reticle positions":s==="test-site"?"other test sites":"the rest of the map"}function Y(s){return s==="reticle-position"?"Other reticle positions":s==="test-site"?"Other test sites":"Rest of map"}function ke(s,a,i){const o=(Math.abs(a)*100).toFixed(1);return`${s} yield is ${o} percentage points ${a>0?"higher":"lower"} than ${se(i)}`}function ue(s,a){return a==="quadrant"&&!s.startsWith("Quadrant")?`quadrant ${s}`:s}function ze(s){return/^(Rings|Sectors|Quadrants)\b/.test(s)?"have":"has"}function Me(s,a,i,o){const e=ue(s,o),c=(Math.abs(i)*100).toFixed(1);return`${e} ${ze(e)} ${a} occurrence ${c} percentage points ${i>0?"higher":"lower"} than ${se(o)}`}function Re(s,a,i,o){const e=ue(s,o),c=(Math.abs(i)*100).toFixed(1);return`${e} ${ze(e)} ${a} pass rate ${c} percentage points ${i>0?"higher":"lower"} than ${se(o)}`}function Be(s,a,i,o,e,c){const r=ue(s,e),n=se(e),l=i>0?"higher":"lower";if(o!==void 0&&Number.isFinite(o)){const u=(Math.abs(o)*100).toFixed(1);return`${r} mean ${a} is ${u}% ${l} than ${n}`}const t=c?` ${c}`:"";return`${r} mean ${a} is ${Math.abs(i).toPrecision(3)}${t} ${l} than ${n}`}function Fe(s,a,i){const o=a?.find(e=>e.bin===s);return o?.name?`${i} ${s} (${o.name})`:`${i} ${s}`}function nt(s,a){const i=a?.find(o=>o.testNumber===s);return{label:x(i,s),unit:i?.unit,...te(i)}}function de(s,a){const i=new Map(s.map(e=>[T(e),e])),o=new Map;for(const e of a){const c=[];for(const r of e.dieKeys){const n=i.get(r);n&&c.push(n)}o.set(e.key,c)}return o}function ge(s,a){return fe(s),s.filter(i=>{const o=i.stats.adjustedPValue??i.stats.pValue??1,e=Math.abs(i.effect.absoluteDelta??0),c=Math.abs(i.effect.relativeDelta??0);return o<=a.significanceLevel&&(e>=a.minimumEffectSize||c>=a.minimumRelativeEffect)}).map(i=>({...i,severity:ie(i.stats.adjustedPValue??i.stats.pValue??1,i.effect.absoluteDelta??0,i.effect.relativeDelta)}))}function W(s,a,i,o){const e=new Set(i),c=de(s,a),r=new Map,n=new Map;for(const[t,u]of c){let f=0,m=0;for(const g of u)g.hbin!==void 0&&(m++,e.has(g.hbin)&&f++);r.set(t,f),n.set(t,m)}const l=[];for(const t of a){const u=n.get(t.key)??0,f=r.get(t.key)??0;let m=0,g=0;for(const[k,h]of n)k!==t.key&&(m+=h,g+=r.get(k)??0);if(u<o.minimumSampleSize||m<o.minimumSampleSize)continue;const p=f/u,b=g/m,y=p-b,M=A(f,u,g,m);l.push({id:`yield:${t.key}`,level:"wafer",severity:"info",variable:{kind:"yield",label:"Yield"},comparison:{family:t.family,left:t.label,right:Y(t.family)},effect:{direction:y===0?"different":y>0?"higher":"lower",absoluteDelta:y,relativeDelta:b===0?void 0:y/b,effectSize:y},stats:{method:"two-proportion-z",pValue:M,sampleSizeLeft:u,sampleSizeRight:m},summary:ke(t.label,y,t.family),highlight:{kind:"region",regionFamily:t.family,regionKeys:[t.key],dieKeys:[...t.dieKeys]}})}return ge(l,o)}function L(s,a,i,o,e,c){const r=g=>i==="soft"?g.sbin:g.hbin,n=[...new Set(s.map(r).filter(g=>g!==void 0))].sort((g,p)=>g-p),l=de(s,a),t=new Map,u=new Map;for(const[g,p]of l){const b=new Map;for(const y of p){const M=r(y);M!==void 0&&b.set(M,(b.get(M)??0)+1)}t.set(g,b),u.set(g,p.length)}const f=[],m=e==="hardBin"?"HBin":"SBin";for(const g of a){const p=u.get(g.key)??0,b=t.get(g.key);let y=0;const M=new Map;for(const[k,h]of t)if(k!==g.key){y+=u.get(k)??0;for(const[S,v]of h)M.set(S,(M.get(S)??0)+v)}if(!(p<c.minimumSampleSize||y<c.minimumSampleSize))for(const k of n){const h=b.get(k)??0,S=M.get(k)??0,v=h/p,w=S/y,$=v-w,D=A(h,p,S,y),z=Fe(k,o,m);f.push({id:`${e}:${k}:${g.key}`,level:"wafer",severity:"info",variable:{kind:e,bin:k,label:z},comparison:{family:g.family,left:g.label,right:Y(g.family)},effect:{direction:$===0?"different":$>0?"higher":"lower",absoluteDelta:$,relativeDelta:w===0?void 0:$/w,effectSize:$},stats:{method:"two-proportion-z",pValue:D,sampleSizeLeft:p,sampleSizeRight:y},summary:Me(g.label,z,$,g.family),highlight:{kind:"bin",bin:k,regionKeys:[g.key],dieKeys:[...g.dieKeys]}})}}return ge(f,c)}function Q(s,a,i,o){const e=(i??[]).filter(u=>!H(u));if(!e.length)return[];const c=e.map(u=>u.testNumber),r=de(s,a),n=new Map,l=new Map;for(const[u,f]of r){const m=new Uint32Array(e.length),g=new Uint32Array(e.length);for(const p of f)for(let b=0;b<e.length;b++){const y=ee(p,c[b],e[b]);y!==void 0&&(g[b]++,y&&m[b]++)}n.set(u,m),l.set(u,g)}const t=[];for(const u of a){const f=n.get(u.key),m=l.get(u.key);for(let g=0;g<e.length;g++){let p=0,b=0;for(const[$,D]of l)$!==u.key&&(b+=D[g],p+=n.get($)[g]);const y=m[g],M=f[g];if(y<o.minimumSampleSize||b<o.minimumSampleSize)continue;const k=M/y,h=p/b,S=k-h,v=A(M,y,p,b),w=c[g];t.push({id:`functional:${w}:${u.key}`,level:"wafer",severity:"info",variable:{kind:"functionalTest",index:w,label:`${x(e[g],w)} pass rate`,...te(e[g])},comparison:{family:u.family,left:u.label,right:Y(u.family)},effect:{direction:S===0?"different":S>0?"higher":"lower",absoluteDelta:S,relativeDelta:h===0?void 0:S/h,effectSize:S},stats:{method:"two-proportion-z",pValue:v,sampleSizeLeft:y,sampleSizeRight:b},summary:Re(u.label,x(e[g],w),S,u.family),highlight:{kind:"region",regionFamily:u.family,regionKeys:[u.key],dieKeys:[...u.dieKeys]}})}}return ge(t,o)}function he(s,a){return s.length<2?0:s.reduce((i,o)=>i+(o-a)**2,0)/(s.length-1)}function Ne(s,a,i,o,e,c){const r=Math.sqrt(i/s+c/o),n=a-e;if(!Number.isFinite(r)||r===0)return{pValue:1,effectSize:0,delta:n};const l=n/r,t=Math.sqrt(Math.max(0,(i+c)/2)),u=t===0?n:n/t;return{pValue:$e(2*(1-we(Math.abs(l)))),effectSize:u,delta:n}}function it(s,a){const i=ne(s),o=ne(a);return Ne(s.length,i,he(s,i),a.length,o,he(a,o))}const Pe=250;function Ce(s,a,i){const o=r=>r.filter(n=>H(i?.find(l=>l.testNumber===n)));if(a)return{testNumbers:o(a.slice().sort((r,n)=>r-n))};const e=new Set;let c=!1;e:for(const r of s)for(const n of Object.keys(r.testValues??{})){const l=Number(n);if(!e.has(l)&&(e.add(l),e.size>Pe)){c=!0;break e}}if(c){const r=`Test-value analysis was skipped: more than ${Pe} tests were found in the die data, so no test findings were computed. Pass testNumbers: [...] to analyse specific tests.`;return console.warn(`[wafermap] analyzeWaferMap: ${r}`),{testNumbers:[],warning:{code:"test-count-capped",message:r,severity:"warning"}}}return{testNumbers:o([...e].sort((r,n)=>r-n))}}function U(s,a,i,o){const e=Ce(s,o.testNumbers,i);if(e.warning)return{findings:[],warning:e.warning};const c=e.testNumbers,r=new Map;for(let p=0;p<a.length;p++)for(const b of a[p].dieKeys)r.set(b,p);const n=a.length,l=[],t=[];for(const p of s){const b=r.get(T(p));b!==void 0&&(l.push(p),t.push(b))}const u=[],f=new Float64Array(n),m=new Float64Array(n),g=new Float64Array(n);for(const p of c){f.fill(0),m.fill(0),g.fill(0);let b=0,y=0,M=0,k;for(let h=0;h<l.length;h++){const v=l[h].testValues?.[p];if(v===void 0)continue;k===void 0&&(k=v);const w=v-k,$=t[h];f[$]+=1,m[$]+=w,g[$]+=w*w,b+=1,y+=w,M+=w*w}if(k!==void 0)for(let h=0;h<n;h++){const S=f[h],v=b-S;if(S<o.minimumSampleSize||v<o.minimumSampleSize)continue;const w=m[h]/S,D=(y-m[h])/v,z=w+k,N=D+k,B=S>1?Math.max(0,g[h]-S*w*w)/(S-1):0,d=v>1?Math.max(0,M-g[h]-v*D*D)/(v-1):0,{pValue:F,effectSize:R,delta:V}=Ne(S,z,B,v,N,d),P=a[h],{label:q,unit:G,...X}=nt(p,i),J=N!==0?V/Math.abs(N):void 0;u.push({id:`test:${p}:${P.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:p,label:q,unit:G,...X},comparison:{family:P.family,left:P.label,right:Y(P.family)},effect:{direction:V===0?"different":V>0?"higher":"lower",absoluteDelta:V,relativeDelta:J,effectSize:R},stats:{method:"welch-z-approx",pValue:F,sampleSizeLeft:S,sampleSizeRight:v},summary:Be(P.label,q,V,J,P.family,G),highlight:{kind:"region",regionFamily:P.family,regionKeys:[P.key],dieKeys:[...P.dieKeys]}})}}return fe(u),{activeTestNumbers:c,findings:u.filter(p=>{const b=p.stats.adjustedPValue??p.stats.pValue??1,y=Math.abs(p.effect.effectSize??0);return b<=o.significanceLevel&&y>=o.minimumEffectSize}).map(p=>({...p,severity:ce(p.stats.adjustedPValue??p.stats.pValue??1,p.effect.effectSize??0)}))}}function st(s,a,i,o){if(!i?.length)return[];const e=i.filter(n=>H(n)&&(n.limitLow!==void 0||n.limitHigh!==void 0));if(!e.length)return[];const c=[],r=new Map(s.map(n=>[T(n),n]));for(const n of e){const l=n.testNumber;for(const t of a){const u=new Map;for(const m of t){const g=[];for(const p of m.dieKeys){const b=r.get(p);b&&g.push(b)}u.set(m.key,g)}const f=[];for(const m of t){const g=u.get(m.key),p=[];for(const[z,N]of u)if(z!==m.key)for(const B of N)p.push(B);const b=z=>z.testValues?.[l]!==void 0,y=z=>ve(le(z.testValues?.[l],n)),M=g.filter(b),k=p.filter(b);if(M.length<o.minimumSampleSize||k.length<o.minimumSampleSize)continue;const h=M.filter(y).length,S=k.filter(y).length,v=h/M.length,w=S/k.length,$=v-w,D=A(h,M.length,S,k.length);f.push({id:`specLimit:${l}:${m.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:l,label:x(n,l),unit:n.unit,...te(n)},comparison:{family:m.family,left:m.label,right:Y(m.family)},effect:{direction:$===0?"different":$>0?"higher":"lower",absoluteDelta:$,relativeDelta:w===0?void 0:$/w,effectSize:$},stats:{method:"two-proportion-z",pValue:D,sampleSizeLeft:M.length,sampleSizeRight:k.length},summary:`${m.label} limit fail rate for ${x(n,l)} is ${(Math.abs($)*100).toFixed(1)} pp ${$>0?"higher":"lower"} than the rest of the wafer`,highlight:{kind:"region",regionFamily:m.family,regionKeys:[m.key],dieKeys:[...m.dieKeys]}})}fe(f),c.push(...f.filter(m=>{const g=m.stats.adjustedPValue??m.stats.pValue??1,p=Math.abs(m.effect.absoluteDelta??0),b=Math.abs(m.effect.relativeDelta??0);return g<=o.significanceLevel&&(p>=o.minimumEffectSize||b>=o.minimumRelativeEffect)}).map(m=>({...m,severity:ce(m.stats.adjustedPValue??m.stats.pValue??1,m.effect.effectSize??0)})))}}return c}function O(s){return s.highlight.regionKeys??[]}function ot(s){const a=s.variable.bin??s.variable.index??"";return`${s.comparison.family}\0${s.variable.kind}\0${a}\0${s.effect.direction}`}function _(s,a){const i=O(s)[0]??"",o=I(i);return o.family==="ring"?o.ring??0:o.family==="sector"?Se(a).indexOf(o.sector??""):0}function at(s,a,i){if(a==="quadrant"){const r=new Map;for(const u of s){const f=I(O(u)[0]??"").quadrant;f&&r.set(f,u)}const n=[...r.keys()],l=new Set,t=[];for(const u of n){if(l.has(u))continue;const f=[],m=[u];for(l.add(u);m.length;){const g=m.pop();f.push(g);for(const p of n)!l.has(p)&&Ye(g,p)&&(l.add(p),m.push(p))}t.push(f.map(g=>r.get(g)))}return t}const o=[...s].sort((r,n)=>_(r,i)-_(n,i)),e=[];let c=[];for(const r of o){if(c.length===0){c.push(r);continue}const n=_(c[c.length-1],i);_(r,i)-n===1?c.push(r):(e.push(c),c=[r])}if(c.length&&e.push(c),a==="sector"&&e.length>1){const n=Se(i).length,l=_(e[0][0],i),t=_(e[e.length-1][e[e.length-1].length-1],i);if(l===0&&t===n-1){const u=e.pop();e[0]=[...u,...e[0]]}}return e}function rt(s,a){const i=s.map(r=>I(O(r)[0]??"").ring).filter(r=>r!==void 0).sort((r,n)=>r-n),o=i[0],e=i[i.length-1];let c="";return o===e&&o===1&&a>1?c=" (core)":o===e&&o===a&&a>1&&(c=" (edge)"),o===e?`Ring ${o}${c}`:`Rings ${o}\u2013${e}`}function lt(s,a){const i=s.map(o=>I(O(o)[0]??"").sector??"");return i.length===1?`Sector ${i[0]}`:`Sectors ${i[0]}\u2013${i[i.length-1]}`}function ft(s){const a=s.map(i=>I(O(i)[0]??"").quadrant??"");return a.length===1?a[0]:a.length===2?`Quadrants ${a[0]} & ${a[1]}`:`Quadrants ${a.slice(0,-1).join(", ")} & ${a[a.length-1]}`}function me(s){return[...new Set(s)]}function ct(s,a){const i=s[0],o=i.comparison.family,e=i.variable.kind,c=me(s.flatMap(h=>h.highlight.dieKeys??[])),r=me(s.flatMap(O)),n=new Set(c),l=o==="ring"?rt(s,a.ringCount):o==="sector"?lt(s,a.sectorCount):ft(s),t=e==="softBin"?a.softEligibleDies:e==="test"?a.testDies:a.eligibleDies,u=t.filter(h=>n.has(T(h))),f=t.filter(h=>!n.has(T(h)));let m,g,p,b,y;if(e==="test"){const h=i.variable.index,S=d=>d.testValues?.[h],v=u.map(S).filter(d=>d!==void 0),w=f.map(S).filter(d=>d!==void 0),{pValue:$,effectSize:D,delta:z}=it(v,w),N=ne(w),B=N!==0?z/Math.abs(N):void 0;m={direction:z===0?"different":z>0?"higher":"lower",absoluteDelta:z,relativeDelta:B,effectSize:D},g={method:"welch-z-approx",pValue:$,sampleSizeLeft:v.length,sampleSizeRight:w.length},p=ce($,D),b=Be(l,i.variable.label,z,B,o,i.variable.unit),y=`test:${h}`}else if(e==="yield"){const h=new Set(a.passBins),S=u.filter(R=>R.hbin!==void 0),v=f.filter(R=>R.hbin!==void 0),w=S.length,$=v.length,D=S.filter(R=>h.has(R.hbin)).length,z=v.filter(R=>h.has(R.hbin)).length,N=w>0?D/w:0,B=$>0?z/$:0,d=N-B,F=A(D,w,z,$);m={direction:d===0?"different":d>0?"higher":"lower",absoluteDelta:d,relativeDelta:B===0?void 0:d/B,effectSize:d},g={method:"two-proportion-z",pValue:F,sampleSizeLeft:w,sampleSizeRight:$},p=ie(F,d,m.relativeDelta),b=ke(l,d,o),y="yield"}else if(e==="functionalTest"){const h=i.variable.index,S=a.testDefs?.find(F=>F.testNumber===h);let v=0,w=0,$=0,D=0;for(const F of u){const R=ee(F,h,S);R!==void 0&&(w++,R&&v++)}for(const F of f){const R=ee(F,h,S);R!==void 0&&(D++,R&&$++)}const z=w>0?v/w:0,N=D>0?$/D:0,B=z-N,d=A(v,w,$,D);m={direction:B===0?"different":B>0?"higher":"lower",absoluteDelta:B,relativeDelta:N===0?void 0:B/N,effectSize:B},g={method:"two-proportion-z",pValue:d,sampleSizeLeft:w,sampleSizeRight:D},p=ie(d,B,m.relativeDelta),b=Re(l,x(S,h),B,o),y=`functional:${h}`}else{const h=i.variable.bin,S=d=>e==="softBin"?d.sbin:d.hbin,v=u.filter(d=>S(d)===h).length,w=f.filter(d=>S(d)===h).length,$=v/u.length,D=w/f.length,z=$-D,N=A(v,u.length,w,f.length);m={direction:z===0?"different":z>0?"higher":"lower",absoluteDelta:z,relativeDelta:D===0?void 0:z/D,effectSize:z},g={method:"two-proportion-z",pValue:N,sampleSizeLeft:u.length,sampleSizeRight:f.length},p=ie(N,z,m.relativeDelta);const B=e==="softBin"?a.sbinDefs:a.hbinDefs;b=Me(l,Fe(h,B,e==="softBin"?"SBin":"HBin"),z,o),y=`${e}:${h}`}const M=me(r.map(h=>h.replace(`${o}:`,""))).join("-"),k=e==="hardBin"||e==="softBin"?{kind:"bin",bin:i.variable.bin,regionKeys:r,dieKeys:c}:{kind:"region",regionFamily:o,regionKeys:r,dieKeys:c};return{...i,id:`${y}:${o}:${M}`,severity:p,comparison:{family:o,left:l,right:Y(o)},effect:m,stats:g,summary:b,highlight:k,relatedIds:s.map(h=>h.id)}}function ut(s,a){const i=new Set(["ring","quadrant","sector"]),o=[],e=new Map;for(const r of s){if(!i.has(r.comparison.family)){o.push(r);continue}const n=ot(r),l=e.get(n)??[];l.push(r),e.set(n,l)}const c=[];for(const r of e.values()){if(r.length===1){c.push(r[0]);continue}const n=r[0].comparison.family;for(const l of at(r,n,a.sectorCount))c.push(l.length===1?l[0]:ct(l,a))}return[...o,...c]}function dt(s){const a=new Map,i=new Map;for(const e of s){const c=T(e);e.hbin!==void 0&&e.hbin!==null&&(a.get(e.hbin)??a.set(e.hbin,new Set).get(e.hbin)).add(c),e.sbin!==void 0&&e.sbin!==null&&(i.get(e.sbin)??i.set(e.sbin,new Set).get(e.sbin)).add(c)}const o=new Set;for(const[e,c]of a)for(const[r,n]of i){if(c.size!==n.size)continue;let l=!0;for(const t of c)if(!n.has(t)){l=!1;break}l&&o.add(`${e}:${r}`)}return o}function gt(s){return`${s.comparison.family}\0${s.comparison.left}`}function ht(s,a,i){const o=(n,l)=>{n.absorbedIds=[...n.absorbedIds??[],l.id]},e=new Map;for(const n of s){const l=gt(n);(e.get(l)??e.set(l,[]).get(l)).push(n)}const c=dt(a),r=new Set;for(const n of e.values()){for(const t of n)if(!(t.variable.kind!=="hardBin"||t.variable.bin===void 0))for(const u of n){if(u.variable.kind!=="softBin"||u.variable.bin===void 0||r.has(u.id)||!c.has(`${t.variable.bin}:${u.variable.bin}`)||u.effect.direction!==t.effect.direction)continue;o(t,u),r.add(u.id);const f=t.variable.label.replace(/^HBin\s+/,""),m=u.variable.label.replace(/^SBin\s+/,""),g=f===m?`HBin and SBin ${f} (same dies)`:`${t.variable.label} and ${u.variable.label} (same dies)`;t.summary=t.summary.replace(t.variable.label,g),t.variable.label=g;break}if(i.length!==1)continue;const l=n.find(t=>t.variable.kind==="yield");if(l){for(const t of n)if(!(t===l||r.has(t.id)||!((t.variable.kind==="hardBin"||t.variable.kind==="softBin")&&t.variable.bin===i[0]))){o(l,t),r.add(t.id);for(const f of t.absorbedIds??[])l.absorbedIds=[...l.absorbedIds??[],f]}}}}export function analyzeWaferMap(s,a={}){const{resolved:i,warnings:o}=Ue(a),e=Ke(s),c={...i,passBins:e.passBins??j.passBins,ringCount:e.ringCount??j.ringCount},r=e.isLotStack,n=e.aggrMethod,l=!r||n==="mode"||n==="countBin"||n==="percent",t=e.dies.filter(d=>Xe(d)),u=t.filter(ye),f=Ge(c,t.length),m=e.dies.filter(d=>ae(d)),g=m.filter(ye),p=qe(g,e.wafer,f.ringCount),b=Ae(g,e.wafer,f.ringCount),y=Oe(g,e.reticleConfig),M=Ie(m,!1),k=He(g,e.wafer,f.sectorCount),h=[];l&&h.push(...W(t,p,f.passBins,f),...W(t,b,f.passBins,f),...W(t,y,f.passBins,f),...W(t,M,f.passBins,f),...W(t,k,f.passBins,f)),l&&h.push(...L(t,p,"hard",e.hbinDefs,"hardBin",f),...L(t,b,"hard",e.hbinDefs,"hardBin",f),...L(t,y,"hard",e.hbinDefs,"hardBin",f),...L(t,M,"hard",e.hbinDefs,"hardBin",f),...L(t,k,"hard",e.hbinDefs,"hardBin",f));{const d=t.filter(F=>F.sbin!==void 0);h.push(...L(d,p,"soft",e.sbinDefs,"softBin",f),...L(d,b,"soft",e.sbinDefs,"softBin",f),...L(d,y,"soft",e.sbinDefs,"softBin",f),...L(d,M,"soft",e.sbinDefs,"softBin",f),...L(d,k,"soft",e.sbinDefs,"softBin",f))}const S=[...o];let v;if(f.enableTestValueAnalysis){const d=U(t,p,e.testDefs,f),F=U(t,b,e.testDefs,f),R=U(t,y,e.testDefs,f),V=U(t,M,e.testDefs,f),P=U(t,k,e.testDefs,f);h.push(...d.findings,...F.findings,...R.findings,...V.findings,...P.findings),d.warning&&S.push(d.warning),v=d.activeTestNumbers,h.push(...st(t,[p,b,y,M,k],e.testDefs,f)),h.push(...Q(t,p,e.testDefs,f),...Q(t,b,e.testDefs,f),...Q(t,y,e.testDefs,f),...Q(t,M,e.testDefs,f),...Q(t,k,e.testDefs,f))}{const d=Je(r,l,e.testDefs);(!r||l||d!==void 0)&&h.push(..._e(u,e.wafer,{...f,isFailingDie:d}))}const w=ut(h,{eligibleDies:t,softEligibleDies:t.filter(d=>d.sbin!==void 0),testDies:t,passBins:f.passBins,ringCount:f.ringCount,sectorCount:f.sectorCount,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs});h.length=0,h.push(...w);let $;if(l){const d=We(u,e.wafer,{passBins:f.passBins,ringCount:f.ringCount});if($=d??void 0,d!==null&&d.pattern!=="random"&&d.pattern!=="none"){let Ee=function(C){const E=O(C).map(K=>I(K).ring).filter(K=>K!==void 0);if(E.length===0)return!0;const pe=E.some(K=>K===Le),be=E.some(K=>K===1);return Z==="edge-ring"||Z==="edge-local"?pe:Z==="center"?be:Z==="donut"?!pe&&!be:!0};const R={center:"Center cluster",donut:"Donut","edge-ring":"Edge-ring","edge-local":"Edge-local",scratch:"Scratch","near-full":"Near-full"}[d.pattern]??d.pattern,V=d.confidence==="high"?"unusual":d.confidence==="medium"?"notable":"info",P=d.features,q=C=>`${(C*100).toFixed(0)}%`,G=d.pattern==="edge-ring"||d.pattern==="edge-local"?`${q(P.edgeRdd)} of edge dies failing`:d.pattern==="center"||d.pattern==="donut"?`centroid at ${q(P.centroidDistNorm)} of wafer radius from centre`:d.pattern==="scratch"?`linear score ${P.linearScore.toFixed(2)}, eccentricity ${P.eccentricity.toFixed(2)}`:`${q(P.globalRdd)} of dies failing`,X=t.filter(C=>{const E=C.hbin??C.sbin;return E!==void 0&&!new Set(f.passBins).has(E)}),J={"edge-ring":["ring","edge-arc"],"edge-local":["edge-arc","sector","quadrant"],center:["ring","cluster"],donut:["ring"],scratch:["cluster","sector","quadrant"],"near-full":["ring"]},Ve=new Set(J[d.pattern]??[]),Z=d.pattern,Le=f.ringCount,oe=[];for(const C of h){if(!Ve.has(C.comparison.family)||C.comparison.family==="ring"&&!Ee(C))continue;oe.push(C.id);const E=C.comparison.family==="ring"||C.comparison.family==="quadrant"||C.comparison.family==="sector";V!=="info"&&E&&(C.severity="info")}h.push({id:`spatial-pattern:${d.pattern}`,level:"wafer",severity:V,variable:{kind:"spatialPattern",label:R},comparison:{family:"spatial-pattern",left:R,right:"Wafer"},effect:{direction:"different",effectSize:P.globalRdd},stats:{method:"geometry",sampleSizeLeft:X.length,sampleSizeRight:t.length},summary:`Spatial pattern: ${R.toLowerCase()} (${d.confidence} confidence) \u2014 ${G}${d.note?` [${d.note}]`:""}`,highlight:{kind:"dies",dieKeys:X.map(C=>T(C))},relatedIds:oe.length>0?oe:void 0})}}h.sort((d,F)=>{const R=d.stats.adjustedPValue??d.stats.pValue??1,V=F.stats.adjustedPValue??F.stats.pValue??1;return R!==V?R-V:Math.abs(F.effect.absoluteDelta??0)-Math.abs(d.effect.absoluteDelta??0)});const D=Ze(e.dies,t.length,e.yield.yieldPercent);r&&(D.isLotStack=!0,n&&(D.aggregationMethod=n));const z=et(e.dies,e.testDefs);z&&(D.testSpecYield=z);const N=computeFunctionalYield(e.dies,e.testDefs);if(N&&(D.functionalYield=N),$&&(D.spatialPattern=$),v===void 0&&f.computePerTestStats){const d=Ce(t,f.testNumbers,e.testDefs);d.warning&&S.push(d.warning),v=d.testNumbers}if(v?.length){const d=tt(e.dies,v,e.testDefs,f.minimumSampleSize);d&&(D.perTestStats=d);const F=Te([{dies:e.dies}],e.testDefs,v);F&&(D.capability=F)}Object.assign(D,xe(e.dies,e.testDefs));const B=je([{dies:e.dies,wafer:e.wafer,passBins:f.passBins,ringCount:f.ringCount}]);return B&&(D.regionYield=B),S.length>0&&(D.warnings=S),ht(h,t,f.passBins),{level:"wafer",hasNotableFindings:h.some(d=>d.severity==="notable"||d.severity==="unusual"),findings:h,wafer:e.wafer.metadata??void 0,stats:D}}
1
+ import{normalizeInput as et}from"./normalizeInput.js";import{computeCapability as tt,computeTestFlagYield as nt,computeRegionYield as it}from"./summaryFigures.js";import{binPassSets as Fe}from"../renderer/binColors.js";import{diePassStatus as Pe,isYieldEligibleDie as ge,getDieKey as Ce,isPositionedDie as xe,positionKey as pe}from"../core/dies.js";import{dieHasValues as st,testsPresent as Le,testValue as I}from"../core/dieTable.js";import{getTestPassStatus as ae,isParametricTest as W}from"../renderer/buildWaferMap.js";import{markedTestLabel as q,derivedFields as re}from"../renderer/testLabel.js";import{buildQuadrantRegions as ot,buildReticlePositionRegions as at,buildRingRegions as rt,buildSectorRegions as ft,buildTestSiteRegions as lt,sectorCompassNames as Ve,areQuadrantsAdjacent as ct,parseRegionKey as Y}from"./regions.js";import{buildClusterFindings as ut}from"./clusterDetection.js";import{classifyPattern as dt}from"./patternClassification.js";import{fiveNumberSummary as mt,normalCdf as Ee}from"./math.js";import{mean as fe,clamp01 as Ke}from"../core/utils.js";import{classifySpec as be,isOutOfSpec as Oe}from"../renderer/spec.js";const ht={significanceLevel:{min:1e-6,max:1},minimumEffectSize:{min:0,max:1},minimumRelativeEffect:{min:0,max:Number.MAX_SAFE_INTEGER}},Te=[4,8,16,32];export function resolveOptions(t){const i=Object.fromEntries(Object.entries(t).filter(([,r])=>r!==void 0)),s={...A,...i},n=[],e=[];for(const r of["significanceLevel","minimumEffectSize","minimumRelativeEffect"]){const{min:u,max:a}=ht[r],c=s[r];if(typeof c!="number"||!Number.isFinite(c)){e.push(`${r}=${String(c)} is not a finite number (using ${A[r]})`),s[r]=A[r];continue}const f=Math.min(a,Math.max(u,c));f!==c&&(e.push(`${r}=${c} is outside ${u}\u2013${a} (using ${f})`),s[r]=f)}if(Te.includes(s.sectorCount)||(e.push(`sectorCount=${s.sectorCount} is not one of ${Te.join(", ")} (using ${A.sectorCount})`),s.sectorCount=A.sectorCount),e.length){const r=`analyzeWaferMap: ${e.join("; ")}. The analysis ran with the corrected values.`;console.warn(`[wafermap] ${r}`),n.push({code:"analysis-option-corrected",message:r,severity:"warning"})}const l=["passBins","ringCount"].filter(r=>i[r]!==void 0);if(l.length){const r=`analyzeWaferMap: ${l.join(" and ")} ${l.length>1?"are":"is"} no longer an analysis option and was ignored. Set ${l.length>1?"them":"it"} on buildWaferMap; the analysis used the value the map was built with.`;console.warn(`[wafermap] ${r}`),n.push({code:"analysis-option-corrected",message:r,severity:"warning"})}const o=["enableYieldAnalysis","enableHardBinAnalysis","enableSoftBinAnalysis","enableReticlePositionAnalysis","enableTestSiteAnalysis","enableClusterAnalysis","enableAngularAnalysis","enablePatternClassification"].filter(r=>i[r]!==void 0);if(o.length){const r=`analyzeWaferMap: ${o.join(", ")} ${o.length>1?"are":"is"} no longer an option and was ignored: every analysis now runs. Filter the findings (filterFindings) to show fewer.`;console.warn(`[wafermap] ${r}`),n.push({code:"analysis-option-corrected",message:r,severity:"warning"})}return{resolved:s,warnings:n}}const A={ringCount:4,passBins:[1],significanceLevel:.05,minimumEffectSize:.2,minimumRelativeEffect:1,minimumSampleSize:5,enableTestValueAnalysis:!1,computePerTestStats:!1,sectorCount:8,minimumClusterSize:5};function gt(t,i){const s={...t};return s.minimumClusterSize=Math.max(5,Math.round(i*.003)),s}function pt(t){return ge(t)?t.hbin!==void 0||t.sbin!==void 0||st(t):!1}function bt(t,i,s){if(!t||i)return;const n=(s??[]).filter(e=>W(e)&&(e.limitLow!==void 0||e.limitHigh!==void 0));if(n.length!==0)return e=>{for(const l of n)if(Oe(be(I(e,l.testNumber),l)))return!0;return!1}}function yt(t,i,s){const n=new Set,e=new Set,l=new Set,o=new Map,r=new Map;for(const u of Le(t))n.add(u);for(const u of t)u.hbin!==void 0&&e.add(u.hbin),u.sbin!==void 0&&l.add(u.sbin),ge(u)&&(u.hbin!==void 0&&o.set(u.hbin,(o.get(u.hbin)??0)+1),u.sbin!==void 0&&r.set(u.sbin,(r.get(u.sbin)??0)+1));return{totalDies:t.length,analyzedDies:i,excludedDies:t.length-i,yieldPercent:s,testsConsidered:[...n].sort((u,a)=>u-a),hardBinsConsidered:[...e].sort((u,a)=>u-a),softBinsConsidered:[...l].sort((u,a)=>u-a),...o.size?{hardBinCounts:Object.fromEntries(o)}:{},...r.size?{softBinCounts:Object.fromEntries(r)}:{}}}function St(t,i){if(!i?.length)return;const s=i.filter(e=>W(e)&&(e.limitLow!==void 0||e.limitHigh!==void 0));if(!s.length)return;const n=[];for(const e of s){const l=e.testNumber;let o=0,r=0,u=0,a=0;for(const c of t){if(c.partial||c.edgeExcluded)continue;const f=be(I(c,l),e);f!==null&&(a++,f==="failLow"?r++:f==="failHigh"?u++:o++)}n.push({testNumber:l,label:e.name,passDies:o,failLowDies:r,failHighDies:u,totalDies:a,yieldPercent:a>0?o/a*100:null})}return n.length?n:void 0}export function computeFunctionalYield(t,i){if(!i?.length)return;const s=i.filter(e=>!W(e));if(!s.length)return;const n=[];for(const e of s){const l=e.testNumber;let o=0,r=0;for(const a of t){if(a.partial||a.edgeExcluded)continue;const c=ae(a,l,e);c!==void 0&&(c?o++:r++)}const u=o+r;n.push({testNumber:l,label:q(e,l),...re(e),passDies:o,failDies:r,totalDies:u,passRatePercent:u>0?o/u*100:null})}return n.length?n:void 0}function wt(t,i,s,n){const e=[];for(const l of i){const o=[];for(const f of t){if(f.partial||f.edgeExcluded)continue;const b=I(f,l);b!==void 0&&Number.isFinite(b)&&o.push(b)}if(o.length<n)continue;const r=fe(o),u=Math.sqrt(De(o,r)),a=mt(Float64Array.from(o)),c=s?.find(f=>f.testNumber===l)?.name??String(l);e.push({testNumber:l,label:c,count:o.length,min:a.min,max:a.max,mean:r,stddev:u,median:a.median,q1:a.q1,q3:a.q3})}return e.length?e:void 0}export function twoProportionZ(t,i,s,n){const e=(t+s)/(i+n),l=e*(1-e)*(1/i+1/n);return!Number.isFinite(l)||l<=0?0:(t/i-s/n)/Math.sqrt(l)}export function zPValue(t){return Ke(2*(1-Ee(Math.abs(t))))}function T(t,i,s,n){const e=twoProportionZ(t,i,s,n);return e===0?1:zPValue(e)}export function adjustPValues(t){const i=new Map;for(const s of t){const n=`${s.variable.kind}:${s.comparison.family}`,e=i.get(n)??[];e.push(s),i.set(n,e)}for(const s of i.values()){const n=[...s].filter(o=>o.stats.pValue!==void 0).sort((o,r)=>(o.stats.pValue??1)-(r.stats.pValue??1)),e=n.length;let l=1;for(let o=e-1;o>=0;o--){const r=n[o],u=r.stats.pValue??1,a=Math.min(1,Math.min(l,u*e/(o+1)));r.stats.adjustedPValue=a,l=a}}return t}export function rateRelativeEffect(t){const i=(t.passRate?t.n-t.hits:t.hits)/t.n,s=(t.passRate?t.restN-t.restHits:t.restHits)/t.restN;return s===0?i===0?0:1/0:Math.abs(i-s)/s}export function severityForFinding(t,i,s){const n=Math.abs(i),e=s;return t<=.01&&(n>=.3||e>=2.5)?"unusual":t<=.05&&(n>=.2||e>=1.5)?"notable":"info"}export function severityForScore(t,i){return t<=.01&&Math.abs(i)>=.5?"unusual":t<=.05&&Math.abs(i)>=.15?"notable":"info"}function le(t){return t==="reticle-position"?"other reticle positions":t==="test-site"?"other test sites":"the rest of the map"}function _(t){return t==="reticle-position"?"Other reticle positions":t==="test-site"?"Other test sites":"Rest of map"}function ye(t,i,s){const n=(Math.abs(i)*100).toFixed(1);return`${t} yield is ${n} percentage points ${i>0?"higher":"lower"} than ${le(s)}`}function Se(t,i){return i==="quadrant"&&!t.startsWith("Quadrant")?`quadrant ${t}`:t}function He(t){return/^(Rings|Sectors|Quadrants)\b/.test(t)?"have":"has"}function we(t,i,s,n){const e=Se(t,n),l=(Math.abs(s)*100).toFixed(1);return`${e} ${He(e)} ${i} occurrence ${l} percentage points ${s>0?"higher":"lower"} than ${le(n)}`}function ve(t,i,s,n){const e=Se(t,n),l=(Math.abs(s)*100).toFixed(1);return`${e} ${He(e)} ${i} pass rate ${l} percentage points ${s>0?"higher":"lower"} than ${le(n)}`}function $e(t,i,s,n,e,l){const o=Se(t,e),r=le(e),u=s>0?"higher":"lower";if(n!==void 0&&Number.isFinite(n)){const c=(Math.abs(n)*100).toFixed(1);return`${o} mean ${i} is ${c}% ${u} than ${r}`}const a=l?` ${l}`:"";return`${o} mean ${i} is ${Math.abs(s).toPrecision(3)}${a} ${u} than ${r}`}function qe(t,i,s){const n=i?.find(e=>e.bin===t);return n?.name?`${s} ${t} (${n.name})`:`${s} ${t}`}function vt(t,i){const s=i?.find(n=>n.testNumber===t);return{label:q(s,t),unit:s?.unit,...re(s)}}const Ae=new WeakMap,je=new WeakMap,Ie=new WeakMap;function Z(t,i){const s=i.highlight.dieKeys;return s&&Ie.set(s,t),i}let Q=null,J;const We=new WeakMap;export function candidatesOf(t){return We.get(t)}function Ye(t,i,s,n){if(!Q)return;const e=Qe(t);if(!e)return;const l=t.id.slice(0,t.id.indexOf(":")),o=l==="specLimit"?"limitFail":l,{kind:r,bin:u,index:a}=t.variable;Q.push({key:[o,r,u??"",a??"",e.family,e.key].join("|"),source:o,variable:t.variable,label:t.variable.label,comparison:t.comparison,region:e,z:i,delta:t.effect.absoluteDelta??0,effectSize:t.effect.effectSize??0,relativeDelta:t.effect.relativeDelta,rate:s,mean:n})}export function describeRegional(t,i,s){const n=t.comparison.left,e=t.region.family;switch(t.source){case"yield":return ye(n,i,e);case"hardBin":case"softBin":return we(n,t.label,i,e);case"functional":return ve(n,t.label.replace(/ pass rate$/,""),i,e);case"limitFail":return _e(n,t.label,i);case"test":return $e(n,t.label,i,s,e,t.variable.unit)}}function _e(t,i,s){return`${t} limit fail rate for ${i} is ${(Math.abs(s)*100).toFixed(1)} pp ${s>0?"higher":"lower"} than the rest of the wafer`}const U=new WeakMap;function ce(t,i){return U.set(t,i),t}function Qe(t){const i=t.highlight.dieKeys;return i?Ie.get(i):void 0}const Ue=new WeakMap;function ke(t){let i=Ue.get(t);return i||Ue.set(t,i=t.map(pe)),i}function $t(t){let i=Ae.get(t);return i||Ae.set(t,i=t.map(Ce)),i}function ue(t,i){let s=je.get(t);s||je.set(t,s={members:new Set(t),byFamily:new WeakMap});const n=s.byFamily.get(i);if(n)return n;const{members:e}=s,l=new Map;for(const o of i)l.set(o.key,o.dies.filter(r=>e.has(r)));return s.byFamily.set(i,l),l}function de(t,i){adjustPValues(t);for(const n of t){const e=U.get(n);Ye(n,twoProportionZ(e.hits,e.n,e.restHits,e.restN),e)}const s=t.filter(n=>passesRateGate(n.stats.adjustedPValue??n.stats.pValue??1,n.effect.absoluteDelta??0,rateRelativeEffect(U.get(n)),i));return s.filter(n=>!kt(n,s,i)).map(n=>({...n,severity:severityForFinding(n.stats.adjustedPValue??n.stats.pValue??1,n.effect.absoluteDelta??0,rateRelativeEffect(U.get(n)))}))}export function passesRateGate(t,i,s,n){return t<=n.significanceLevel&&(Math.abs(i)>=n.minimumEffectSize||s>=n.minimumRelativeEffect)}function kt(t,i,s){const n=U.get(t);if(!n)return!1;const e=t.stats.pValue??1;let{restHits:l,restN:o}=n;for(const c of Ge(t,i)){const f=U.get(c);f&&(l-=f.hits,o-=f.n)}if(o===n.restN)return!1;if(o<s.minimumSampleSize)return!0;const r=n.hits/n.n-l/o;if(Math.sign(r)!==Math.sign(t.effect.absoluteDelta??0))return!0;const u=e>0?(t.stats.adjustedPValue??e)/e:1,a=Math.min(1,T(n.hits,n.n,l,o)*u);return!passesRateGate(a,r,rateRelativeEffect({...n,restHits:l,restN:o}),s)}function Ge(t,i){const s=t.stats.pValue??1;return i.filter(n=>n!==t&&n.effect.direction!==t.effect.direction&&n.variable.kind===t.variable.kind&&n.variable.bin===t.variable.bin&&n.variable.index===t.variable.index&&n.comparison.family===t.comparison.family&&(n.stats.pValue??1)<s)}const ze=new WeakMap;export function welchOfSums(t,i,s,n){const e=(l,o,r)=>r>1?Math.max(0,l-o*o/r)/(r-1):0;return welchFromStats(t.n,t.sum/t.n,e(t.sq,t.sum,t.n),i,s/i,e(n,s,i))}function zt(t,i,s){const n=ze.get(t);if(!n)return!1;let{restN:e,restSum:l,restSq:o}=n;for(const b of Ge(t,i)){const p=ze.get(b);p&&(e-=p.n,l-=p.sum,o-=p.sq)}if(e===n.restN)return!1;if(e<s.minimumSampleSize)return!0;const{pValue:r,effectSize:u,delta:a}=welchOfSums(n,e,l,o);if(Math.sign(a)!==Math.sign(t.effect.absoluteDelta??0))return!0;const c=t.stats.pValue??1,f=c>0?(t.stats.adjustedPValue??c)/c:1;return!(Math.min(1,r*f)<=s.significanceLevel&&Math.abs(u)>=s.minimumEffectSize)}function ee(t,i,s,n){const e=new Set(s),l=ue(t,i),o=new Map,r=new Map;for(const[a,c]of l){let f=0,b=0;for(const p of c)p.hbin!==void 0&&(b++,e.has(p.hbin)&&f++);o.set(a,f),r.set(a,b)}const u=[];for(const a of i){const c=r.get(a.key)??0,f=o.get(a.key)??0;let b=0,p=0;for(const[D,w]of r)D!==a.key&&(b+=w,p+=o.get(D)??0);if(c<n.minimumSampleSize||b<n.minimumSampleSize)continue;const g=f/c,m=p/b,y=g-m,z=T(f,c,p,b);u.push(ce(Z(a,{id:`yield:${a.key}`,level:"wafer",severity:"info",variable:{kind:"yield",label:"Yield"},comparison:{family:a.family,left:a.label,right:_(a.family)},effect:{direction:y===0?"different":y>0?"higher":"lower",absoluteDelta:y,relativeDelta:m===0?void 0:y/m,effectSize:y},stats:{method:"two-proportion-z",pValue:z,sampleSizeLeft:c,sampleSizeRight:b},summary:ye(a.label,y,a.family),highlight:{kind:"region",regionFamily:a.family,regionKeys:[a.key],dieKeys:[...a.dieKeys]}}),{hits:f,n:c,restHits:p,restN:b,passRate:!0}))}return de(u,n)}function E(t,i,s,n,e,l){const o=g=>s==="soft"?g.sbin:g.hbin,r=[...new Set(t.map(o).filter(g=>g!==void 0))].sort((g,m)=>g-m),u=ue(t,i),a=new Map,c=new Map;for(const[g,m]of u){const y=new Map;for(const z of m){const D=o(z);D!==void 0&&y.set(D,(y.get(D)??0)+1)}a.set(g,y),c.set(g,m.length)}const f=[],b=e==="hardBin"?"HBin":"SBin",p=s==="hard"?new Set(l.passBins):l.softPassBins??Fe(t,l.passBins).soft;for(const g of i){const m=c.get(g.key)??0,y=a.get(g.key);let z=0;const D=new Map;for(const[w,h]of a)if(w!==g.key){z+=c.get(w)??0;for(const[v,S]of h)D.set(v,(D.get(v)??0)+S)}if(!(m<l.minimumSampleSize||z<l.minimumSampleSize))for(const w of r){const h=y.get(w)??0,v=D.get(w)??0,S=h/m,$=v/z,k=S-$,M=T(h,m,v,z),R=qe(w,n,b);f.push(ce(Z(g,{id:`${e}:${w}:${g.key}`,level:"wafer",severity:"info",variable:{kind:e,bin:w,label:R},comparison:{family:g.family,left:g.label,right:_(g.family)},effect:{direction:k===0?"different":k>0?"higher":"lower",absoluteDelta:k,relativeDelta:$===0?void 0:k/$,effectSize:k},stats:{method:"two-proportion-z",pValue:M,sampleSizeLeft:m,sampleSizeRight:z},summary:we(g.label,R,k,g.family),highlight:{kind:"bin",bin:w,regionKeys:[g.key],dieKeys:[...g.dieKeys]}}),{hits:h,n:m,restHits:v,restN:z,passRate:p.has(w)}))}}return de(f,l)}function te(t,i,s,n){const e=(s??[]).filter(c=>!W(c));if(!e.length)return[];const l=e.map(c=>c.testNumber),o=ue(t,i),r=new Map,u=new Map;for(const[c,f]of o){const b=new Uint32Array(e.length),p=new Uint32Array(e.length);for(const g of f)for(let m=0;m<e.length;m++){const y=ae(g,l[m],e[m]);y!==void 0&&(p[m]++,y&&b[m]++)}r.set(c,b),u.set(c,p)}const a=[];for(const c of i){const f=r.get(c.key),b=u.get(c.key);for(let p=0;p<e.length;p++){let g=0,m=0;for(const[$,k]of u)$!==c.key&&(m+=k[p],g+=r.get($)[p]);const y=b[p],z=f[p];if(y<n.minimumSampleSize||m<n.minimumSampleSize)continue;const D=z/y,w=g/m,h=D-w,v=T(z,y,g,m),S=l[p];a.push(ce(Z(c,{id:`functional:${S}:${c.key}`,level:"wafer",severity:"info",variable:{kind:"functionalTest",index:S,label:`${q(e[p],S)} pass rate`,...re(e[p])},comparison:{family:c.family,left:c.label,right:_(c.family)},effect:{direction:h===0?"different":h>0?"higher":"lower",absoluteDelta:h,relativeDelta:w===0?void 0:h/w,effectSize:h},stats:{method:"two-proportion-z",pValue:v,sampleSizeLeft:y,sampleSizeRight:m},summary:ve(c.label,q(e[p],S),h,c.family),highlight:{kind:"region",regionFamily:c.family,regionKeys:[c.key],dieKeys:[...c.dieKeys]}}),{hits:z,n:y,restHits:g,restN:m,passRate:!0}))}}return de(a,n)}function De(t,i){return t.length<2?0:t.reduce((s,n)=>s+(n-i)**2,0)/(t.length-1)}export function welchFromStats(t,i,s,n,e,l){const o=Math.sqrt(s/t+l/n),r=i-e;if(!Number.isFinite(o)||o===0)return{pValue:1,effectSize:0,delta:r,z:0};const u=r/o,a=Math.sqrt(Math.max(0,(s+l)/2)),c=a===0?r:r/a;return{pValue:Ke(2*(1-Ee(Math.abs(u)))),effectSize:c,delta:r,z:u}}function Dt(t,i){const s=fe(t),n=fe(i);return welchFromStats(t.length,s,De(t,s),i.length,n,De(i,n))}const Xe=250;function Ze(t,i,s){const n=o=>o.filter(r=>W(s?.find(u=>u.testNumber===r)));if(i)return{testNumbers:n(i.slice().sort((o,r)=>o-r))};const e=Le(t,"values");if(e.length>Xe){const o=`Test-value analysis was skipped: more than ${Xe} tests were found in the die data, so no test findings were computed. Pass testNumbers: [...] to analyse specific tests.`;return console.warn(`[wafermap] analyzeWaferMap: ${o}`),{testNumbers:[],warning:{code:"test-count-capped",message:o,severity:"warning"}}}return{testNumbers:n(e)}}function ne(t,i,s,n){const e=Ze(t,n.testNumbers,s);if(e.warning)return{findings:[],warning:e.warning};const l=e.testNumbers,o=new Map;for(let m=0;m<i.length;m++)for(const y of ke(i[m].dies))o.set(y,m);const r=i.length,u=[],a=[];for(const m of t){const y=o.get(pe(m));y!==void 0&&(u.push(m),a.push(y))}const c=[],f=new Float64Array(r),b=new Float64Array(r),p=new Float64Array(r);for(const m of l){f.fill(0),b.fill(0),p.fill(0);let y=0,z=0,D=0,w;for(let h=0;h<u.length;h++){const v=u[h],S=I(v,m);if(S===void 0)continue;w===void 0&&(w=S);const $=S-w,k=a[h];f[k]+=1,b[k]+=$,p[k]+=$*$,y+=1,z+=$,D+=$*$}if(w!==void 0)for(let h=0;h<r;h++){const v=f[h],S=y-v;if(v<n.minimumSampleSize||S<n.minimumSampleSize)continue;const $=b[h]/v,M=(z-b[h])/S,R=$+w,P=M+w,F=v>1?Math.max(0,p[h]-v*$*$)/(v-1):0,d=S>1?Math.max(0,D-p[h]-S*M*M)/(S-1):0,{pValue:N,effectSize:B,delta:L,z:V}=welchFromStats(v,R,F,S,P,d),x=i[h],{label:ie,unit:X,...me}=vt(m,s),se=P!==0?L/Math.abs(P):void 0,K=Z(x,{id:`test:${m}:${x.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:m,label:ie,unit:X,...me},comparison:{family:x.family,left:x.label,right:_(x.family)},effect:{direction:L===0?"different":L>0?"higher":"lower",absoluteDelta:L,relativeDelta:se,effectSize:B},stats:{method:"welch-z-approx",pValue:N,sampleSizeLeft:v,sampleSizeRight:S},summary:$e(x.label,ie,L,se,x.family,X),highlight:{kind:"region",regionFamily:x.family,regionKeys:[x.key],dieKeys:[...x.dieKeys]}}),oe={n:v,sum:b[h],sq:p[h],restN:S,restSum:z-b[h],restSq:D-p[h]};ze.set(K,oe),Ye(K,V,void 0,oe),c.push(K)}}adjustPValues(c);const g=c.filter(m=>{const y=m.stats.adjustedPValue??m.stats.pValue??1,z=Math.abs(m.effect.effectSize??0);return y<=n.significanceLevel&&z>=n.minimumEffectSize});return{activeTestNumbers:l,findings:g.filter(m=>!zt(m,g,n)).map(m=>({...m,severity:severityForScore(m.stats.adjustedPValue??m.stats.pValue??1,m.effect.effectSize??0)}))}}function Rt(t,i,s,n){if(!s?.length)return[];const e=s.filter(o=>W(o)&&(o.limitLow!==void 0||o.limitHigh!==void 0));if(!e.length)return[];const l=[];for(const o of e){const r=o.testNumber;for(const u of i){const a=ue(t,u),c=[],f=new Map;let b=0,p=0;for(const[g,m]of a){let y=0,z=0;for(const D of m){const w=I(D,r);w!==void 0&&(y++,Oe(be(w,o))&&z++)}f.set(g,{valid:y,fail:z}),b+=y,p+=z}for(const g of u){const m=f.get(g.key),y=m.valid,z=b-m.valid;if(y<n.minimumSampleSize||z<n.minimumSampleSize)continue;const D=m.fail,w=p-m.fail,h=D/y,v=w/z,S=h-v,$=T(D,y,w,z);c.push(ce(Z(g,{id:`specLimit:${r}:${g.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:r,label:q(o,r),unit:o.unit,...re(o)},comparison:{family:g.family,left:g.label,right:_(g.family)},effect:{direction:S===0?"different":S>0?"higher":"lower",absoluteDelta:S,relativeDelta:v===0?void 0:S/v,effectSize:S},stats:{method:"two-proportion-z",pValue:$,sampleSizeLeft:y,sampleSizeRight:z},summary:_e(g.label,q(o,r),S),highlight:{kind:"region",regionFamily:g.family,regionKeys:[g.key],dieKeys:[...g.dieKeys]}}),{hits:D,n:y,restHits:w,restN:z,passRate:!1}))}l.push(...de(c,n))}}return l}function Re(t,i){return t==="softBin"?i.softEligibleDies:t==="test"?i.testDies:i.eligibleDies}function j(t){return t.highlight.regionKeys??[]}function Mt(t){const i=t.variable.bin??t.variable.index??"";return`${t.comparison.family}\0${t.variable.kind}\0${i}\0${t.effect.direction}`}function G(t,i){const s=j(t)[0]??"",n=Y(s);return n.family==="ring"?n.ring??0:n.family==="sector"?Ve(i).indexOf(n.sector??""):0}function Bt(t,i,s){if(i==="quadrant"){const o=new Map;for(const c of t){const f=Y(j(c)[0]??"").quadrant;f&&o.set(f,c)}const r=[...o.keys()],u=new Set,a=[];for(const c of r){if(u.has(c))continue;const f=[],b=[c];for(u.add(c);b.length;){const p=b.pop();f.push(p);for(const g of r)!u.has(g)&&ct(p,g)&&(u.add(g),b.push(g))}a.push(f.map(p=>o.get(p)))}return a}const n=[...t].sort((o,r)=>G(o,s)-G(r,s)),e=[];let l=[];for(const o of n){if(l.length===0){l.push(o);continue}const r=G(l[l.length-1],s);G(o,s)-r===1?l.push(o):(e.push(l),l=[o])}if(l.length&&e.push(l),i==="sector"&&e.length>1){const r=Ve(s).length,u=G(e[0][0],s),a=G(e[e.length-1][e[e.length-1].length-1],s);if(u===0&&a===r-1){const c=e.pop();e[0]=[...c,...e[0]]}}return e}function Nt(t,i){const s=t.map(o=>Y(j(o)[0]??"").ring).filter(o=>o!==void 0).sort((o,r)=>o-r),n=s[0],e=s[s.length-1];let l="";return n===e&&n===1&&i>1?l=" (core)":n===e&&n===i&&i>1&&(l=" (edge)"),n===e?`Ring ${n}${l}`:`Rings ${n}\u2013${e}`}function Ft(t,i){const s=t.map(n=>Y(j(n)[0]??"").sector??"");return s.length===1?`Sector ${s[0]}`:`Sectors ${s[0]}\u2013${s[s.length-1]}`}function Pt(t){const i=t.map(s=>Y(j(s)[0]??"").quadrant??"");return i.length===1?i[0]:i.length===2?`Quadrants ${i[0]} & ${i[1]}`:`Quadrants ${i.slice(0,-1).join(", ")} & ${i[i.length-1]}`}function Me(t){return[...new Set(t)]}function Ct(t,i){const s=t[0],n=s.comparison.family,e=s.variable.kind,l=Me(t.flatMap(j));let o,r;const u=t.map(Qe);if(u.every(h=>h!==void 0)){const h=new Set;o=[];for(const S of u){const $=ke(S.dies);for(let k=0;k<$.length;k++)h.has($[k])||(h.add($[k]),o.push(S.dieKeys[k]))}const v=ke(Re(s.variable.kind,i));r=S=>h.has(v[S])}else{o=Me(t.flatMap(S=>S.highlight.dieKeys??[]));const h=new Set(o),v=$t(Re(s.variable.kind,i));r=S=>h.has(v[S])}const a=n==="ring"?Nt(t,i.ringCount):n==="sector"?Ft(t,i.sectorCount):Pt(t),c=Re(e,i),f=[],b=[];for(let h=0;h<c.length;h++)(r(h)?f:b).push(c[h]);let p,g,m,y,z;if(e==="test"){const h=s.variable.index,v=d=>I(d,h),S=f.map(v).filter(d=>d!==void 0),$=b.map(v).filter(d=>d!==void 0),{pValue:k,effectSize:M,delta:R}=Dt(S,$),P=fe($),F=P!==0?R/Math.abs(P):void 0;p={direction:R===0?"different":R>0?"higher":"lower",absoluteDelta:R,relativeDelta:F,effectSize:M},g={method:"welch-z-approx",pValue:k,sampleSizeLeft:S.length,sampleSizeRight:$.length},m=severityForScore(k,M),y=$e(a,s.variable.label,R,F,n,s.variable.unit),z=`test:${h}`}else if(e==="yield"){const h=new Set(i.passBins),v=f.filter(B=>B.hbin!==void 0),S=b.filter(B=>B.hbin!==void 0),$=v.length,k=S.length,M=v.filter(B=>h.has(B.hbin)).length,R=S.filter(B=>h.has(B.hbin)).length,P=$>0?M/$:0,F=k>0?R/k:0,d=P-F,N=T(M,$,R,k);p={direction:d===0?"different":d>0?"higher":"lower",absoluteDelta:d,relativeDelta:F===0?void 0:d/F,effectSize:d},g={method:"two-proportion-z",pValue:N,sampleSizeLeft:$,sampleSizeRight:k},m=severityForFinding(N,d,rateRelativeEffect({hits:M,n:$,restHits:R,restN:k,passRate:!0})),y=ye(a,d,n),z="yield"}else if(e==="functionalTest"){const h=s.variable.index,v=i.testDefs?.find(N=>N.testNumber===h);let S=0,$=0,k=0,M=0;for(const N of f){const B=ae(N,h,v);B!==void 0&&($++,B&&S++)}for(const N of b){const B=ae(N,h,v);B!==void 0&&(M++,B&&k++)}const R=$>0?S/$:0,P=M>0?k/M:0,F=R-P,d=T(S,$,k,M);p={direction:F===0?"different":F>0?"higher":"lower",absoluteDelta:F,relativeDelta:P===0?void 0:F/P,effectSize:F},g={method:"two-proportion-z",pValue:d,sampleSizeLeft:$,sampleSizeRight:M},m=severityForFinding(d,F,rateRelativeEffect({hits:S,n:$,restHits:k,restN:M,passRate:!0})),y=ve(a,q(v,h),F,n),z=`functional:${h}`}else{const h=s.variable.bin,v=d=>e==="softBin"?d.sbin:d.hbin,S=f.filter(d=>v(d)===h).length,$=b.filter(d=>v(d)===h).length,k=S/f.length,M=$/b.length,R=k-M,P=T(S,f.length,$,b.length);p={direction:R===0?"different":R>0?"higher":"lower",absoluteDelta:R,relativeDelta:M===0?void 0:R/M,effectSize:R},g={method:"two-proportion-z",pValue:P,sampleSizeLeft:f.length,sampleSizeRight:b.length},m=severityForFinding(P,R,rateRelativeEffect({hits:S,n:f.length,restHits:$,restN:b.length,passRate:!1}));const F=e==="softBin"?i.sbinDefs:i.hbinDefs;y=we(a,qe(h,F,e==="softBin"?"SBin":"HBin"),R,n),z=`${e}:${h}`}const D=Me(l.map(h=>h.replace(`${n}:`,""))).join("-"),w=e==="hardBin"||e==="softBin"?{kind:"bin",bin:s.variable.bin,regionKeys:l,dieKeys:o}:{kind:"region",regionFamily:n,regionKeys:l,dieKeys:o};return{...s,id:`${z}:${n}:${D}`,severity:m,comparison:{family:n,left:a,right:_(n)},effect:p,stats:g,summary:y,highlight:w,relatedIds:t.map(h=>h.id)}}function xt(t,i){const s=new Set(["ring","quadrant","sector"]),n=[],e=new Map;for(const o of t){if(!s.has(o.comparison.family)){n.push(o);continue}const r=Mt(o),u=e.get(r)??[];u.push(o),e.set(r,u)}const l=[];for(const o of e.values()){if(o.length===1){l.push(o[0]);continue}const r=o[0].comparison.family;for(const u of Bt(o,r,i.sectorCount))l.push(u.length===1?u[0]:Ct(u,i))}return[...n,...l]}function Lt(t){const i=new Map,s=new Map;for(const e of t){const l=pe(e);e.hbin!==void 0&&e.hbin!==null&&(i.get(e.hbin)??i.set(e.hbin,new Set).get(e.hbin)).add(l),e.sbin!==void 0&&e.sbin!==null&&(s.get(e.sbin)??s.set(e.sbin,new Set).get(e.sbin)).add(l)}const n=new Set;for(const[e,l]of i)for(const[o,r]of s){if(l.size!==r.size)continue;let u=!0;for(const a of l)if(!r.has(a)){u=!1;break}u&&n.add(`${e}:${o}`)}return n}function Vt(t,i){const s=new Set(i);let n;for(const e of t){const l=Pe(e,s);if(l!==void 0&&l){if(e.sbin==null||n!==void 0&&e.sbin!==n)return;n=e.sbin}}if(n!==void 0){for(const e of t)if(e.sbin===n&&Pe(e,s)!==!0)return;return n}}function Et(t){return`${t.comparison.family}\0${t.comparison.left}`}export function redundancyFacts(t,i){return{coincident:Lt(t),yieldSoftBin:Vt(t,i),passBins:i}}export function collapseRedundantFindings(t,i){const{coincident:s,yieldSoftBin:n,passBins:e}=i,l=(u,a)=>{u.absorbedIds=[...u.absorbedIds??[],a.id]},o=new Map;for(const u of t){const a=Et(u);(o.get(a)??o.set(a,[]).get(a)).push(u)}const r=new Set;for(const u of o.values()){for(const c of u)if(!(c.variable.kind!=="hardBin"||c.variable.bin===void 0))for(const f of u){if(f.variable.kind!=="softBin"||f.variable.bin===void 0||r.has(f.id)||!s.has(`${c.variable.bin}:${f.variable.bin}`)||f.effect.direction!==c.effect.direction)continue;l(c,f),r.add(f.id);const b=c.variable.label.replace(/^HBin\s+/,""),p=f.variable.label.replace(/^SBin\s+/,""),g=b===p?`HBin and SBin ${b} (same dies)`:`${c.variable.label} and ${f.variable.label} (same dies)`;c.summary=c.summary.replace(c.variable.label,g),c.variable.label=g;break}const a=u.find(c=>c.variable.kind==="yield");if(a){for(const c of u)if(!(c===a||r.has(c.id)||!(c.variable.kind==="hardBin"&&e.length===1&&c.variable.bin===e[0]||c.variable.kind==="softBin"&&c.variable.bin===n))){l(a,c),r.add(c.id);for(const b of c.absorbedIds??[])a.absorbedIds=[...a.absorbedIds??[],b]}}}}export function analyzeWaferMap(t,i={}){const s=Q,n=J,e=[];Q=e,J=void 0;try{const l=Kt(t,i);return We.set(l,{candidates:e,facts:J}),l}finally{Q=s,J=n}}function Kt(t,i){const{resolved:s,warnings:n}=resolveOptions(i),e=et(t),l={...s,passBins:e.passBins??A.passBins,ringCount:e.ringCount??A.ringCount},o=e.isLotStack,r=e.aggrMethod,u=!o||r==="mode"||r==="countBin"||r==="percent",a=e.dies.filter(d=>pt(d)),c=a.filter(xe),f=gt(l,a.length);f.softPassBins=Fe(a,f.passBins).soft;const b=e.dies.filter(d=>ge(d)),p=b.filter(xe),g=rt(p,e.wafer,f.ringCount),m=ot(p,e.wafer,f.ringCount),y=at(p,e.reticleConfig),z=lt(b,!1),D=ft(p,e.wafer,f.sectorCount),w=[];u&&w.push(...ee(a,g,f.passBins,f),...ee(a,m,f.passBins,f),...ee(a,y,f.passBins,f),...ee(a,z,f.passBins,f),...ee(a,D,f.passBins,f)),u&&w.push(...E(a,g,"hard",e.hbinDefs,"hardBin",f),...E(a,m,"hard",e.hbinDefs,"hardBin",f),...E(a,y,"hard",e.hbinDefs,"hardBin",f),...E(a,z,"hard",e.hbinDefs,"hardBin",f),...E(a,D,"hard",e.hbinDefs,"hardBin",f));{const d=a.filter(N=>N.sbin!==void 0);w.push(...E(d,g,"soft",e.sbinDefs,"softBin",f),...E(d,m,"soft",e.sbinDefs,"softBin",f),...E(d,y,"soft",e.sbinDefs,"softBin",f),...E(d,z,"soft",e.sbinDefs,"softBin",f),...E(d,D,"soft",e.sbinDefs,"softBin",f))}const h=[...n];let v;if(f.enableTestValueAnalysis){const d=ne(a,g,e.testDefs,f),N=ne(a,m,e.testDefs,f),B=ne(a,y,e.testDefs,f),L=ne(a,z,e.testDefs,f),V=ne(a,D,e.testDefs,f);w.push(...d.findings,...N.findings,...B.findings,...L.findings,...V.findings),d.warning&&h.push(d.warning),v=d.activeTestNumbers,w.push(...Rt(a,[g,m,y,z,D],e.testDefs,f)),w.push(...te(a,g,e.testDefs,f),...te(a,m,e.testDefs,f),...te(a,y,e.testDefs,f),...te(a,z,e.testDefs,f),...te(a,D,e.testDefs,f))}{const d=bt(o,u,e.testDefs);(!o||u||d!==void 0)&&w.push(...ut(c,e.wafer,{...f,isFailingDie:d}))}const S=xt(w,{eligibleDies:a,softEligibleDies:a.filter(d=>d.sbin!==void 0),testDies:a,passBins:f.passBins,ringCount:f.ringCount,sectorCount:f.sectorCount,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs});w.length=0,w.push(...S);let $;if(u){const d=dt(c,e.wafer,{passBins:f.passBins,ringCount:f.ringCount});if($=d??void 0,d!==null&&d.pattern!=="random"&&d.pattern!=="none"){let Je=function(C){const O=j(C).map(H=>Y(H).ring).filter(H=>H!==void 0);if(O.length===0)return!0;const Be=O.some(H=>H===oe),Ne=O.some(H=>H===1);return K==="edge-ring"||K==="edge-local"?Be:K==="center"?Ne:K==="donut"?!Be&&!Ne:!0};const B={center:"Center cluster",donut:"Donut","edge-ring":"Edge-ring","edge-local":"Edge-local",scratch:"Scratch","near-full":"Near-full"}[d.pattern]??d.pattern,L=d.confidence==="high"?"unusual":d.confidence==="medium"?"notable":"info",V=d.features,x=C=>`${(C*100).toFixed(0)}%`,ie=d.pattern==="edge-ring"||d.pattern==="edge-local"?`${x(V.edgeRdd)} of edge dies failing`:d.pattern==="center"||d.pattern==="donut"?`centroid at ${x(V.centroidDistNorm)} of wafer radius from centre`:d.pattern==="scratch"?`linear score ${V.linearScore.toFixed(2)}, eccentricity ${V.eccentricity.toFixed(2)}`:`${x(V.globalRdd)} of dies failing`,X=a.filter(C=>{const O=C.hbin??C.sbin;return O!==void 0&&!new Set(f.passBins).has(O)}),me={"edge-ring":["ring","edge-arc"],"edge-local":["edge-arc","sector","quadrant"],center:["ring","cluster"],donut:["ring"],scratch:["cluster","sector","quadrant"],"near-full":["ring"]},se=new Set(me[d.pattern]??[]),K=d.pattern,oe=f.ringCount,he=[];for(const C of w){if(!se.has(C.comparison.family)||C.comparison.family==="ring"&&!Je(C))continue;he.push(C.id);const O=C.comparison.family==="ring"||C.comparison.family==="quadrant"||C.comparison.family==="sector";L!=="info"&&O&&(C.severity="info")}w.push({id:`spatial-pattern:${d.pattern}`,level:"wafer",severity:L,variable:{kind:"spatialPattern",label:B},comparison:{family:"spatial-pattern",left:B,right:"Wafer"},effect:{direction:"different",effectSize:V.globalRdd},stats:{method:"geometry",sampleSizeLeft:X.length,sampleSizeRight:a.length},summary:`Spatial pattern: ${B.toLowerCase()} (${d.confidence} confidence) \u2014 ${ie}${d.note?` [${d.note}]`:""}`,highlight:{kind:"dies",dieKeys:X.map(C=>Ce(C))},relatedIds:he.length>0?he:void 0})}}w.sort((d,N)=>{const B=d.stats.adjustedPValue??d.stats.pValue??1,L=N.stats.adjustedPValue??N.stats.pValue??1;return B!==L?B-L:Math.abs(N.effect.absoluteDelta??0)-Math.abs(d.effect.absoluteDelta??0)});const k=yt(e.dies,a.length,e.yield.yieldPercent);o&&(k.isLotStack=!0,r&&(k.aggregationMethod=r));const M=St(e.dies,e.testDefs);M&&(k.testSpecYield=M);const R=computeFunctionalYield(e.dies,e.testDefs);if(R&&(k.functionalYield=R),$&&(k.spatialPattern=$),v===void 0&&f.computePerTestStats){const d=Ze(a,f.testNumbers,e.testDefs);d.warning&&h.push(d.warning),v=d.testNumbers}if(v?.length){const d=wt(e.dies,v,e.testDefs,f.minimumSampleSize);d&&(k.perTestStats=d);const N=tt([{dies:e.dies}],e.testDefs,v);N&&(k.capability=N)}Object.assign(k,nt(e.dies,e.testDefs));const P=it([{dies:e.dies,wafer:e.wafer,passBins:f.passBins,ringCount:f.ringCount}]);P&&(k.regionYield=P),h.length>0&&(k.warnings=h);const F=redundancyFacts(a,f.passBins);return collapseRedundantFindings(w,F),Q&&(J=F),{level:"wafer",hasNotableFindings:w.some(d=>d.severity==="notable"||d.severity==="unusual"),findings:w,wafer:e.wafer.metadata??void 0,stats:k}}
@@ -1 +1 @@
1
- import{isYieldEligibleDie as f}from"../core/dies.js";import{quantile as r}from"./math.js";export function buildTestBoxplotData(m,l){return m.map((n,u)=>{const i=n.label??`#${u}`,o=n.testStats?.find(t=>t.testNumber===l);if(o){const{min:t,q1:a,median:s,q3:N,max:d,count:c}=o;return{label:i,min:t,q1:a,median:s,q3:N,max:d,count:c}}const e=(n.dies??[]).filter(t=>f(t)).map(t=>t.testValues?.[l]).filter(t=>t!==void 0&&Number.isFinite(t)).sort((t,a)=>t-a);return e.length===0?{label:i,min:NaN,q1:NaN,median:NaN,q3:NaN,max:NaN,count:0}:{label:i,min:e[0],q1:r(e,.25),median:r(e,.5),q3:r(e,.75),max:e[e.length-1],count:e.length}})}
1
+ import{isYieldEligibleDie as N}from"../core/dies.js";import{testValue as d}from"../core/dieTable.js";import{fiveNumberSummary as p}from"./math.js";export function buildTestBoxplotData(u,r){return u.map((o,m)=>{const i=o.label??`#${m}`,a=o.testStats?.find(t=>t.testNumber===r);if(a){const{min:t,q1:e,median:s,q3:f,max:l,count:c}=a;return{label:i,min:t,q1:e,median:s,q3:f,max:l,count:c}}const n=[];for(const t of o.dies??[]){if(!N(t))continue;const e=d(t,r);e!==void 0&&Number.isFinite(e)&&n.push(e)}return n.length===0?{label:i,min:NaN,q1:NaN,median:NaN,q3:NaN,max:NaN,count:0}:{label:i,...p(Float64Array.from(n)),count:n.length}})}
@@ -64,9 +64,9 @@ export declare function buildCapabilityData(items: CapabilityItem[], testDefs: T
64
64
  /**
65
65
  * @internal {@link buildCapabilityData} as a {@link Chunked} computation, for a
66
66
  * caller rendering on the main thread — a lot-sized call walks every die of
67
- * every wafer once per test and then sorts every value of every test, which is
68
- * seconds of uninterruptible work at lot scale. Steps are one slice of the die
69
- * pass, or one test's sort. Same result, same order; the synchronous entry
67
+ * every wafer once per test and then finds the quartiles of every test's values,
68
+ * which is seconds of uninterruptible work at lot scale. Steps are one slice of
69
+ * the die pass, or one test's quartiles. Same result, same order; the synchronous entry
70
70
  * above is this function drained.
71
71
  */
72
72
  export declare function buildCapabilityDataSteps(items: CapabilityItem[], testDefs: TestDef[]): Chunked<CapabilityDatum[]>;
@@ -81,9 +81,9 @@ export interface PooledTestStats {
81
81
  stats: Map<number, DescriptiveStats>;
82
82
  /**
83
83
  * Per test, how many pooled values fell outside the test's own spec limits —
84
- * the spec-yield tally, counted off the sorted values this pass already held.
84
+ * the spec-yield tally, counted off the values this pass already held.
85
85
  * Only tests carrying at least one limit appear. `fail` is
86
- * `countOutOfSpecSorted` — the same rule as the per-die `classifySpec`.
86
+ * `countOutOfSpec` — the per-die `classifySpec` rule, counted.
87
87
  *
88
88
  * This is here rather than the sorted arrays themselves because the result is
89
89
  * memoised: a tally is a handful of numbers per test, whereas the values are
@@ -109,8 +109,8 @@ export declare function clearPooledTestStatsCache(): void;
109
109
  * third time inside `buildCapabilityData` for the Ppk column beside them — all
110
110
  * reading the same values off the same dies and all of them O(dies × tests).
111
111
  * On a 50-wafer lot of 4,000 dies × 100 tests that was 40 s of a 41 s panel
112
- * render. One pass collects the values, one sort per test serves the quantiles
113
- * of both the table and the chart, and the spec tally reads the sorted array.
112
+ * render. One pass collects the values, one quartile selection per test serves
113
+ * both the table and the chart, and the spec tally counts the same values.
114
114
  *
115
115
  * {@link Chunked}: one step per slice of the die pass, one per test thereafter.
116
116
  */
@@ -1 +1 @@
1
- import{isYieldEligibleDie as v}from"../core/dies.js";import{drain as O}from"../core/utils.js";import{isParametricTest as B}from"../renderer/buildWaferMap.js";import{countOutOfSpecSorted as D,hasSpecLimits as H}from"../renderer/spec.js";import{testLabel as E,derivedFields as P}from"../renderer/testLabel.js";import{describeSorted as V,quantile as k}from"./math.js";const _=25e4;function z(n){if(n.specLow!==void 0&&n.specHigh!==void 0&&n.specHigh>n.specLow)return{lsl:n.specLow,usl:n.specHigh,basis:"spec"};if(n.limitLow!==void 0&&n.limitHigh!==void 0&&n.limitHigh>n.limitLow)return{lsl:n.limitLow,usl:n.limitHigh,basis:"test"}}function x(n){const s=new Map,t=new Map;for(const p of n){const e=p.testNumber;if(e===void 0||!B(p))continue;t.set(e,p);const i=z(p);i&&s.set(e,i)}return{defByTestNumber:t,specByTest:s}}function*C(n,s,t){const p=new Map,e=[];for(const l of s.keys())p.set(l,e.length),e.push(l);const i=e.length,r=new Float64Array(i),o=new Float64Array(i),m=new Float64Array(i),h=new Float64Array(i),T=new Float64Array(i),b=new Float64Array(i),w=new Float64Array(i),g=new Float64Array(i),d=t?new Array(i):[],S=i<=64,A=Math.max(500,Math.floor(_/Math.max(1,i)));let f=0;for(const l of n){b.fill(0),w.fill(0),g.fill(0);for(const a of l.dies??[]){if(++f>=A&&(f=0,yield),!v(a))continue;const y=a.testValues;if(y){if(S){for(let u=0;u<i;u++){const c=y[e[u]];c===void 0||!Number.isFinite(c)||(r[u]++,o[u]+=c,m[u]+=c*c,b[u]++,w[u]+=c,g[u]+=c*c,t&&(d[u]??=[]).push(c))}continue}for(const u in y){const c=p.get(+u);if(c===void 0)continue;const N=y[+u];N===void 0||!Number.isFinite(N)||(r[c]++,o[c]+=N,m[c]+=N*N,b[c]++,w[c]+=N,g[c]+=N*N,t&&(d[c]??=[]).push(N))}}}for(let a=0;a<i;a++){const y=b[a];if(y<2)continue;const u=w[a]/y,c=(g[a]-y*u*u)/(y-1);h[a]+=(y-1)*Math.max(0,c),T[a]+=y-1}}const M=new Map;for(let l=0;l<i;l++){if(r[l]===0)continue;const a=e[l];M.set(a,{n:r[l],sum:o[l],sumSq:m[l],withinNumerator:h[l],withinDenominator:T[l]}),t&&d[l]&&t.set(a,d[l])}return M}function I(n,s,t){return O(C(n,s,t))}function L(n,s,t,p){const{n:e,sum:i,sumSq:r,withinNumerator:o,withinDenominator:m}=p,h=i/e,T=e>=2?Math.max(0,(r-e*h*h)/(e-1)):0,b=Math.sqrt(T),w=m>0?Math.sqrt(o/m):NaN;let g=null,d=null,S=null,A=null;if(t){const{lsl:f,usl:M}=t,l=M-f;S=b>0?l/(6*b):null,A=b>0?Math.min((M-h)/(3*b),(h-f)/(3*b)):null,g=Number.isFinite(w)&&w>0?l/(6*w):null,d=Number.isFinite(w)&&w>0?Math.min((M-h)/(3*w),(h-f)/(3*w)):null}return{testNumber:n,label:E(s,n),unit:s.unit,...P(s),hasSpec:t!==void 0,lsl:t?.lsl,usl:t?.usl,...t?{limitBasis:t.basis}:{},mean:h,stdOverall:b,stdWithin:w,n:e,cp:g,cpk:d,pp:S,ppk:A}}function q(n){return n.sort((s,t)=>s.hasSpec!==t.hasSpec?s.hasSpec?-1:1:s.hasSpec?s.ppk===null&&t.ppk===null?0:s.ppk===null?1:t.ppk===null?-1:s.ppk-t.ppk:t.stdOverall-s.stdOverall)}export function buildCapabilityData(n,s){return O(buildCapabilityDataSteps(n,s))}export function*buildCapabilityDataSteps(n,s){return(yield*pooledTestStatsSteps(n,s)).capability.slice()}const F=new Map,R=4,$=new WeakMap;let j=1;function W(n,s){const t=[];for(const e of n){const i=e.dies;if(!i){t.push(0);continue}let r=$.get(i);r===void 0&&(r=j++,$.set(i,r)),t.push(r)}const p=s.map(e=>`${e.testNumber}${e.testType??"P"}${e.limitLow??""}${e.limitHigh??""}${e.specLow??""}${e.specHigh??""}${e.name??""}${e.unit??""}`);return`${t.join(",")}${p.join("")}`}export function clearPooledTestStatsCache(){F.clear()}export function*pooledTestStatsSteps(n,s){const t=W(n,s),p=F.get(t);if(p)return F.delete(t),F.set(t,p),p;const e={stats:new Map,specTally:new Map,capability:[]},{defByTestNumber:i,specByTest:r}=x(s);if(i.size===0)return e;const o=new Map,m=yield*C(n,i,o),h=new Map,T=new Map,b=[];for(const g of s){yield;const d=g.testNumber;if(d===void 0)continue;const S=m.get(d);if(!S||S.n===0||!i.has(d))continue;const A=o.get(d);o.delete(d);const f=Float64Array.from(A);f.sort(),h.set(d,V(f)),H(g)&&T.set(d,{n:f.length,fail:D(f,g)});const M=r.get(d),l=L(d,g,M,S);let a;if(M){const y=M.usl-M.lsl;a=u=>(u-M.lsl)/y}else{const y=f[0],u=f[f.length-1]-y;a=u>0?c=>(c-y)/u:()=>.5}b.push({...l,min:a(f[0]),q1:a(k(f,.25)),median:a(k(f,.5)),q3:a(k(f,.75)),max:a(f[f.length-1])})}const w={stats:h,specTally:T,capability:q(b)};return F.set(t,w),F.size>R&&F.delete(F.keys().next().value),w}export function buildCapabilityFigures(n,s){const{defByTestNumber:t,specByTest:p}=x(s);if(t.size===0)return[];const e=I(n,t,null),i=[];for(const[r,o]of e)o.n!==0&&i.push(L(r,t.get(r),p.get(r),o));return q(i)}export function poolCapabilityFigures(n,s){const{defByTestNumber:t,specByTest:p}=x(s),e=new Map;for(const r of n)for(const o of r){if(!t.has(o.testNumber)||o.n===0)continue;let m=e.get(o.testNumber);m||(m={n:0,sum:0,sumSq:0,withinNumerator:0,withinDenominator:0},e.set(o.testNumber,m));const h=o.stdOverall*o.stdOverall;m.n+=o.n,m.sum+=o.n*o.mean,m.sumSq+=(o.n-1)*h+o.n*o.mean*o.mean,o.n>=2&&(m.withinNumerator+=(o.n-1)*h,m.withinDenominator+=o.n-1)}const i=[];for(const[r,o]of e)i.push(L(r,t.get(r),p.get(r),o));return q(i)}
1
+ import{isYieldEligibleDie as B}from"../core/dies.js";import{testValuesReader as D}from"../core/dieTable.js";import{drain as q}from"../core/utils.js";import{isParametricTest as v}from"../renderer/buildWaferMap.js";import{countOutOfSpec as H,hasSpecLimits as E}from"../renderer/spec.js";import{testLabel as P,derivedFields as V}from"../renderer/testLabel.js";import{describeValues as _}from"./math.js";const I=25e4;function z(n){if(n.specLow!==void 0&&n.specHigh!==void 0&&n.specHigh>n.specLow)return{lsl:n.specLow,usl:n.specHigh,basis:"spec"};if(n.limitLow!==void 0&&n.limitHigh!==void 0&&n.limitHigh>n.limitLow)return{lsl:n.limitLow,usl:n.limitHigh,basis:"test"}}function x(n){const s=new Map,e=new Map;for(const l of n){const t=l.testNumber;if(t===void 0||!v(l))continue;e.set(t,l);const i=z(l);i&&s.set(t,i)}return{defByTestNumber:e,specByTest:s}}function*C(n,s,e){const l=[...s.keys()],t=l.length,i=new Float64Array(t),r=new Float64Array(t),o=new Float64Array(t),f=new Float64Array(t),p=new Float64Array(t),N=new Float64Array(t),w=new Float64Array(t),m=new Float64Array(t);let d=0;if(e)for(const a of n)d+=a.dies?.length??0;const M=e?Array.from({length:t},()=>new Float64Array(d)):[],S=D(l),h=new Float64Array(t),b=Math.max(500,Math.floor(I/Math.max(1,t)));let F=0;for(const a of n){N.fill(0),w.fill(0),m.fill(0);for(const u of a.dies??[])if(++F>=b&&(F=0,yield),!!B(u)){S(u,h);for(let c=0;c<t;c++){const g=h[c];Number.isFinite(g)&&(i[c]++,r[c]+=g,o[c]+=g*g,N[c]++,w[c]+=g,m[c]+=g*g,e&&(M[c][i[c]-1]=g))}}for(let u=0;u<t;u++){const c=N[u];if(c<2)continue;const g=w[u]/c,$=(m[u]-c*g*g)/(c-1);f[u]+=(c-1)*Math.max(0,$),p[u]+=c-1}}const y=new Map;for(let a=0;a<t;a++){if(i[a]===0)continue;const u=l[a];y.set(u,{n:i[a],sum:r[a],sumSq:o[a],withinNumerator:f[a],withinDenominator:p[a]}),e&&e.set(u,M[a].subarray(0,i[a]))}return y}function R(n,s,e){return q(C(n,s,e))}function A(n,s,e,l){const{n:t,sum:i,sumSq:r,withinNumerator:o,withinDenominator:f}=l,p=i/t,N=t>=2?Math.max(0,(r-t*p*p)/(t-1)):0,w=Math.sqrt(N),m=f>0?Math.sqrt(o/f):NaN;let d=null,M=null,S=null,h=null;if(e){const{lsl:b,usl:F}=e,y=F-b;S=w>0?y/(6*w):null,h=w>0?Math.min((F-p)/(3*w),(p-b)/(3*w)):null,d=Number.isFinite(m)&&m>0?y/(6*m):null,M=Number.isFinite(m)&&m>0?Math.min((F-p)/(3*m),(p-b)/(3*m)):null}return{testNumber:n,label:P(s,n),unit:s.unit,...V(s),hasSpec:e!==void 0,lsl:e?.lsl,usl:e?.usl,...e?{limitBasis:e.basis}:{},mean:p,stdOverall:w,stdWithin:m,n:t,cp:d,cpk:M,pp:S,ppk:h}}function L(n){return n.sort((s,e)=>s.hasSpec!==e.hasSpec?s.hasSpec?-1:1:s.hasSpec?s.ppk===null&&e.ppk===null?0:s.ppk===null?1:e.ppk===null?-1:s.ppk-e.ppk:e.stdOverall-s.stdOverall)}export function buildCapabilityData(n,s){return q(buildCapabilityDataSteps(n,s))}export function*buildCapabilityDataSteps(n,s){return(yield*pooledTestStatsSteps(n,s)).capability.slice()}const T=new Map,j=4,O=new WeakMap;let W=1;function Y(n,s){const e=[];for(const t of n){const i=t.dies;if(!i){e.push(0);continue}let r=O.get(i);r===void 0&&(r=W++,O.set(i,r)),e.push(r)}const l=s.filter(t=>t.testNumber!==void 0&&v(t)).map(t=>`${t.testNumber}${t.testType??"P"}${t.limitLow??""}${t.limitHigh??""}${t.specLow??""}${t.specHigh??""}${t.name??""}${t.unit??""}`);return`${e.join(",")}${l.join("")}`}export function clearPooledTestStatsCache(){T.clear();for(const n of k.values())n.work.return(void 0);k.clear()}export function*pooledTestStatsSteps(n,s){const e=Y(n,s),l=T.get(e);if(l)return T.delete(e),T.set(e,l),l;let t=k.get(e);t||(t={work:K(n,s,e),callers:0},k.set(e,t));const i=t;i.callers++;try{for(;;){if(i.result)return i.result;const r=i.work.next();if(r.done)return i.result=r.value,k.delete(e),r.value;yield}}finally{i.callers--,!i.result&&i.callers===0&&(i.work.return(void 0),k.get(e)===i&&k.delete(e))}}const k=new Map;function*K(n,s,e){const l={stats:new Map,specTally:new Map,capability:[]},{defByTestNumber:t,specByTest:i}=x(s);if(t.size===0)return l;const r=new Map,o=yield*C(n,t,r),f=new Map,p=new Map,N=[];for(const m of s){yield;const d=m.testNumber;if(d===void 0)continue;const M=o.get(d);if(!M||M.n===0||!t.has(d))continue;const S=r.get(d);r.delete(d),E(m)&&p.set(d,{n:S.length,fail:H(S,m)});const h=_(S);f.set(d,h);const b=i.get(d),F=A(d,m,b,M);let y;if(b){const a=b.usl-b.lsl;y=u=>(u-b.lsl)/a}else{const a=h.min,u=h.max-a;y=u>0?c=>(c-a)/u:()=>.5}N.push({...F,min:y(h.min),q1:y(h.q1),median:y(h.median),q3:y(h.q3),max:y(h.max)})}const w={stats:f,specTally:p,capability:L(N)};return T.set(e,w),T.size>j&&T.delete(T.keys().next().value),w}export function buildCapabilityFigures(n,s){const{defByTestNumber:e,specByTest:l}=x(s);if(e.size===0)return[];const t=R(n,e,null),i=[];for(const[r,o]of t)o.n!==0&&i.push(A(r,e.get(r),l.get(r),o));return L(i)}export function poolCapabilityFigures(n,s){const{defByTestNumber:e,specByTest:l}=x(s),t=new Map;for(const r of n)for(const o of r){if(!e.has(o.testNumber)||o.n===0)continue;let f=t.get(o.testNumber);f||(f={n:0,sum:0,sumSq:0,withinNumerator:0,withinDenominator:0},t.set(o.testNumber,f));const p=o.stdOverall*o.stdOverall;f.n+=o.n,f.sum+=o.n*o.mean,f.sumSq+=(o.n-1)*p+o.n*o.mean*o.mean,o.n>=2&&(f.withinNumerator+=(o.n-1)*p,f.withinDenominator+=o.n-1)}const i=[];for(const[r,o]of t)i.push(A(r,e.get(r),l.get(r),o));return L(i)}
@@ -1,4 +1,5 @@
1
- import type { Die, PositionedDie, Wafer } from '../core/index.js';
1
+ import type { Die, PositionedDie } from '../core/dies.js';
2
+ import type { Wafer } from '../core/wafer.js';
2
3
  import type { StatsFinding } from './types.js';
3
4
  interface ClusterOptions {
4
5
  passBins: number[];
@@ -1 +1 @@
1
- import{normalCdf as ct}from"./math.js";import{getDieKey as q}from"../core/dies.js";import{findConnectedComponents as at}from"./connectedComponents.js";const rt=["E","ENE","NE","NNE","N","NNW","NW","WNW","W","WSW","SW","SSW","S","SSE","SE","ESE"];function ft(n,s){const i=(Math.atan2(s,n)+2*Math.PI)%(2*Math.PI);return rt[Math.round(i/(2*Math.PI)*16)%16]}function lt(n,s,i,r){const f=r!==void 0?Math.abs(r):0;return n<=.01&&(s>=.25||f>=2||i>=.1)?"unusual":n<=.05&&(s>=.15||f>=1||i>=.03)?"notable":"info"}function ut(n,s,i){if(s===0||i<=0)return n>0?0:1;if(i>=1)return n<=s?0:1;const r=s*i,f=Math.sqrt(s*i*(1-i));if(f===0)return n>r?0:1;const S=(n-.5-r)/f;return 1-ct(S)}export function buildClusterFindings(n,s,i){const{passBins:r,significanceLevel:f,minimumEffectSize:S,minimumRelativeEffect:O,minimumSampleSize:W,minimumClusterSize:T,ringCount:_}=i,G=new Set(r);if(n.length<W)return[];const H=e=>{const a=e.hbin??e.sbin;return a===void 0||!G.has(a)},J=i.isFailingDie??H,X=n.filter(J),u=X.length/n.length,m=n[0],I=m?m.width:10,w=m?m.height:10,M=Math.max(I,w)*1.5,b=new Map;for(const e of n)b.set(q(e),e);const B=Math.ceil(M/I),R=Math.ceil(M/w),Q=at(X),A=[],$=s.center.x,x=s.center.y,U=s.radius;for(const e of Q){if(e.length<T)continue;const a=new Set(e.map(t=>q(t))),h=new Set(a);for(const t of e)for(let c=-R;c<=R;c++)for(let z=-B;z<=B;z++){const D=`${t.x+z},${t.y+c}`;if(h.has(D))continue;const N=b.get(D);N&&Math.hypot(N.physX-t.physX,N.physY-t.physY)<=M&&h.add(D)}const p=[...h].map(t=>b.get(t)).filter(Boolean),o=e.length,E=p.length;if(E<W)continue;const C=o/E,d=C-u,y=ut(o,E,u),g=u>0?d/u:void 0,Z=d>=S||g!==void 0&&g>=O;if(y>f||!Z)continue;const K=e.reduce((t,c)=>t+c.physX,0)/o,V=e.reduce((t,c)=>t+c.physY,0)/o;let l=e[0],k=1/0;for(const t of e){const c=Math.hypot(t.physX-K,t.physY-V);c<k&&(k=c,l=t)}const F=e.map(t=>(Math.atan2(t.physY-x,t.physX-$)+2*Math.PI)%(2*Math.PI));let P=F[0],Y=F[0];for(const t of F)P=Math.min(P,t),Y=Math.max(Y,t);const tt=(Y-P)*180/Math.PI,et=1-1/_,v=e.filter(t=>Math.hypot(t.physX-$,t.physY-x)/U>et).length>o/2&&tt<120,L=v?"edge-arc":"cluster",j=ft(K-$,V-x),nt=v?`Edge arc ~${j}`:`Cluster at (${l.x}, ${l.y})`,st=o/n.length,it=lt(y,d,st,g),ot=[...a];A.push({id:`${L}:${l.x},${l.y}`,level:"wafer",severity:it,variable:{kind:"yield",label:"Yield"},comparison:{family:L,left:nt,right:"Rest of wafer"},effect:{direction:"lower",absoluteDelta:-d,relativeDelta:g!==void 0?-g:void 0,effectSize:d},stats:{method:"binomial",pValue:y,adjustedPValue:y,sampleSizeLeft:o,sampleSizeRight:n.length-o},summary:v?`Edge arc near ${j}: ${o} contiguous failing dies (${(C*100).toFixed(0)}% vs ${(u*100).toFixed(0)}% background)`:`Failure cluster at (${l.x}, ${l.y}): ${o} contiguous failing dies (${(C*100).toFixed(0)}% vs ${(u*100).toFixed(0)}% background)`,highlight:{kind:"dies",dieKeys:ot}})}return A.sort((e,a)=>{const h=e.stats.pValue??1,p=a.stats.pValue??1;return h!==p?h-p:Math.abs(a.effect.effectSize??0)-Math.abs(e.effect.effectSize??0)})}
1
+ import{normalCdf as ot}from"./math.js";import{getDieKey as ct,gridKey as N}from"../core/dies.js";import{findConnectedComponents as at}from"./connectedComponents.js";const rt=["E","ENE","NE","NNE","N","NNW","NW","WNW","W","WSW","SW","SSW","S","SSE","SE","ESE"];function ft(n,s){const i=(Math.atan2(s,n)+2*Math.PI)%(2*Math.PI);return rt[Math.round(i/(2*Math.PI)*16)%16]}function lt(n,s,i,r){const f=r!==void 0?Math.abs(r):0;return n<=.01&&(s>=.25||f>=2||i>=.1)?"unusual":n<=.05&&(s>=.15||f>=1||i>=.03)?"notable":"info"}function ut(n,s,i){if(s===0||i<=0)return n>0?0:1;if(i>=1)return n<=s?0:1;const r=s*i,f=Math.sqrt(s*i*(1-i));if(f===0)return n>r?0:1;const y=(n-.5-r)/f;return 1-ot(y)}export function buildClusterFindings(n,s,i){const{passBins:r,significanceLevel:f,minimumEffectSize:y,minimumRelativeEffect:q,minimumSampleSize:W,minimumClusterSize:O,ringCount:T}=i,_=new Set(r);if(n.length<W)return[];const G=e=>{const a=e.hbin??e.sbin;return a===void 0||!_.has(a)},H=i.isFailingDie??G,X=n.filter(H),u=X.length/n.length,g=n[0],I=g?g.width:10,B=g?g.height:10,S=Math.max(I,B)*1.5,M=new Map;for(const e of n)M.set(N(e.x,e.y),e);const R=Math.ceil(S/I),w=Math.ceil(S/B),J=at(X),A=[],b=s.center.x,x=s.center.y,Q=s.radius;for(const e of J){if(e.length<O)continue;const a=new Set(e.map(t=>N(t.x,t.y)));for(const t of e)for(let c=-w;c<=w;c++)for(let v=-R;v<=R;v++){const z=N(t.x+v,t.y+c);if(a.has(z))continue;const D=M.get(z);D&&Math.hypot(D.physX-t.physX,D.physY-t.physY)<=S&&a.add(z)}const m=[...a].map(t=>M.get(t)).filter(Boolean),o=e.length,E=m.length;if(E<W)continue;const $=o/E,h=$-u,p=ut(o,E,u),d=u>0?h/u:void 0,U=h>=y||d!==void 0&&d>=q;if(p>f||!U)continue;const K=e.reduce((t,c)=>t+c.physX,0)/o,V=e.reduce((t,c)=>t+c.physY,0)/o;let l=e[0],k=1/0;for(const t of e){const c=Math.hypot(t.physX-K,t.physY-V);c<k&&(k=c,l=t)}const C=e.map(t=>(Math.atan2(t.physY-x,t.physX-b)+2*Math.PI)%(2*Math.PI));let F=C[0],P=C[0];for(const t of C)F=Math.min(F,t),P=Math.max(P,t);const Z=(P-F)*180/Math.PI,tt=1-1/T,Y=e.filter(t=>Math.hypot(t.physX-b,t.physY-x)/Q>tt).length>o/2&&Z<120,L=Y?"edge-arc":"cluster",j=ft(K-b,V-x),et=Y?`Edge arc ~${j}`:`Cluster at (${l.x}, ${l.y})`,nt=o/n.length,st=lt(p,h,nt,d),it=e.map(t=>ct(t));A.push({id:`${L}:${l.x},${l.y}`,level:"wafer",severity:st,variable:{kind:"yield",label:"Yield"},comparison:{family:L,left:et,right:"Rest of wafer"},effect:{direction:"lower",absoluteDelta:-h,relativeDelta:d!==void 0?-d:void 0,effectSize:h},stats:{method:"binomial",pValue:p,adjustedPValue:p,sampleSizeLeft:o,sampleSizeRight:n.length-o},summary:Y?`Edge arc near ${j}: ${o} contiguous failing dies (${($*100).toFixed(0)}% vs ${(u*100).toFixed(0)}% background)`:`Failure cluster at (${l.x}, ${l.y}): ${o} contiguous failing dies (${($*100).toFixed(0)}% vs ${(u*100).toFixed(0)}% background)`,highlight:{kind:"dies",dieKeys:it}})}return A.sort((e,a)=>{const m=e.stats.pValue??1,o=a.stats.pValue??1;return m!==o?m-o:Math.abs(a.effect.effectSize??0)-Math.abs(e.effect.effectSize??0)})}
@@ -1 +1 @@
1
- import{getDieKey as y}from"../core/dies.js";export function findConnectedComponents(s){if(s.length===0)return[];const r=new Map;for(const e of s)r.set(y(e),e);const n=new Set,u=[];for(const e of s){const f=y(e);if(n.has(f))continue;const p=[],c=[e];for(n.add(f);c.length>0;){const d=c.pop();p.push(d);for(let t=-1;t<=1;t++)for(let o=-1;o<=1;o++){if(o===0&&t===0)continue;const i=`${d.x+o},${d.y+t}`;if(n.has(i))continue;const h=r.get(i);h&&(n.add(i),c.push(h))}}u.push(p)}return u}
1
+ import{gridKey as i}from"../core/dies.js";export function findConnectedComponents(s){if(s.length===0)return[];const u=new Map;for(const n of s)u.set(i(n.x,n.y),n);const e=new Set,f=[];for(const n of s){const p=i(n.x,n.y);if(e.has(p))continue;const y=[],c=[n];for(e.add(p);c.length>0;){const d=c.pop();y.push(d);for(let t=-1;t<=1;t++)for(let o=-1;o<=1;o++){if(o===0&&t===0)continue;const r=i(d.x+o,d.y+t);if(e.has(r))continue;const h=u.get(r);h&&(e.add(r),c.push(h))}}f.push(y)}return f}