@wafertools/wafermap 0.21.1

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 (138) hide show
  1. package/CHANGELOG.md +751 -0
  2. package/LICENSE +21 -0
  3. package/README.md +79 -0
  4. package/dist/index.d.ts +4 -0
  5. package/dist/index.js +1 -0
  6. package/dist/packages/canvas-adapter/canvasTheme.d.ts +28 -0
  7. package/dist/packages/canvas-adapter/canvasTheme.js +1 -0
  8. package/dist/packages/canvas-adapter/charts/barPanel.d.ts +52 -0
  9. package/dist/packages/canvas-adapter/charts/barPanel.js +1 -0
  10. package/dist/packages/canvas-adapter/charts/binCluster.d.ts +17 -0
  11. package/dist/packages/canvas-adapter/charts/binCluster.js +1 -0
  12. package/dist/packages/canvas-adapter/charts/boxplot.d.ts +45 -0
  13. package/dist/packages/canvas-adapter/charts/boxplot.js +1 -0
  14. package/dist/packages/canvas-adapter/charts/capability.d.ts +41 -0
  15. package/dist/packages/canvas-adapter/charts/capability.js +1 -0
  16. package/dist/packages/canvas-adapter/charts/chartShell.d.ts +156 -0
  17. package/dist/packages/canvas-adapter/charts/chartShell.js +1 -0
  18. package/dist/packages/canvas-adapter/charts/correlation.d.ts +35 -0
  19. package/dist/packages/canvas-adapter/charts/correlation.js +1 -0
  20. package/dist/packages/canvas-adapter/charts/histogram.d.ts +30 -0
  21. package/dist/packages/canvas-adapter/charts/histogram.js +1 -0
  22. package/dist/packages/canvas-adapter/charts/index.d.ts +16 -0
  23. package/dist/packages/canvas-adapter/charts/index.js +1 -0
  24. package/dist/packages/canvas-adapter/charts/palette.d.ts +26 -0
  25. package/dist/packages/canvas-adapter/charts/palette.js +1 -0
  26. package/dist/packages/canvas-adapter/charts/regionYieldDiagram.d.ts +18 -0
  27. package/dist/packages/canvas-adapter/charts/regionYieldDiagram.js +1 -0
  28. package/dist/packages/canvas-adapter/charts/scatter.d.ts +37 -0
  29. package/dist/packages/canvas-adapter/charts/scatter.js +1 -0
  30. package/dist/packages/canvas-adapter/icons.d.ts +2 -0
  31. package/dist/packages/canvas-adapter/icons.js +42 -0
  32. package/dist/packages/canvas-adapter/index.d.ts +12 -0
  33. package/dist/packages/canvas-adapter/index.js +1 -0
  34. package/dist/packages/canvas-adapter/insightsTab.d.ts +76 -0
  35. package/dist/packages/canvas-adapter/insightsTab.js +1 -0
  36. package/dist/packages/canvas-adapter/metadataBadge.d.ts +26 -0
  37. package/dist/packages/canvas-adapter/metadataBadge.js +1 -0
  38. package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +181 -0
  39. package/dist/packages/canvas-adapter/renderWaferGallery.js +20 -0
  40. package/dist/packages/canvas-adapter/renderWaferMap.d.ts +316 -0
  41. package/dist/packages/canvas-adapter/renderWaferMap.js +1 -0
  42. package/dist/packages/canvas-adapter/summaryPanel.d.ts +262 -0
  43. package/dist/packages/canvas-adapter/summaryPanel.js +3 -0
  44. package/dist/packages/canvas-adapter/toCanvas.d.ts +106 -0
  45. package/dist/packages/canvas-adapter/toCanvas.js +1 -0
  46. package/dist/packages/canvas-adapter/toolbar.d.ts +496 -0
  47. package/dist/packages/canvas-adapter/toolbar.js +9 -0
  48. package/dist/packages/canvas-adapter/userGuideHtml.d.ts +2 -0
  49. package/dist/packages/canvas-adapter/userGuideHtml.js +968 -0
  50. package/dist/packages/canvas-adapter/version.d.ts +3 -0
  51. package/dist/packages/canvas-adapter/version.js +1 -0
  52. package/dist/packages/core/aggregates.d.ts +54 -0
  53. package/dist/packages/core/aggregates.js +1 -0
  54. package/dist/packages/core/classify.d.ts +24 -0
  55. package/dist/packages/core/classify.js +1 -0
  56. package/dist/packages/core/dies.d.ts +92 -0
  57. package/dist/packages/core/dies.js +1 -0
  58. package/dist/packages/core/index.d.ts +13 -0
  59. package/dist/packages/core/index.js +1 -0
  60. package/dist/packages/core/inference/grid.d.ts +35 -0
  61. package/dist/packages/core/inference/grid.js +1 -0
  62. package/dist/packages/core/inference/index.d.ts +7 -0
  63. package/dist/packages/core/inference/index.js +1 -0
  64. package/dist/packages/core/inference/pitch.d.ts +26 -0
  65. package/dist/packages/core/inference/pitch.js +1 -0
  66. package/dist/packages/core/inference/wafer.d.ts +30 -0
  67. package/dist/packages/core/inference/wafer.js +1 -0
  68. package/dist/packages/core/metadata.d.ts +31 -0
  69. package/dist/packages/core/metadata.js +1 -0
  70. package/dist/packages/core/probe.d.ts +16 -0
  71. package/dist/packages/core/probe.js +1 -0
  72. package/dist/packages/core/reticle.d.ts +68 -0
  73. package/dist/packages/core/reticle.js +1 -0
  74. package/dist/packages/core/transforms.d.ts +142 -0
  75. package/dist/packages/core/transforms.js +1 -0
  76. package/dist/packages/core/utils.d.ts +60 -0
  77. package/dist/packages/core/utils.js +1 -0
  78. package/dist/packages/core/wafer.d.ts +45 -0
  79. package/dist/packages/core/wafer.js +1 -0
  80. package/dist/packages/renderer/buildView.d.ts +420 -0
  81. package/dist/packages/renderer/buildView.js +1 -0
  82. package/dist/packages/renderer/buildWaferMap.d.ts +543 -0
  83. package/dist/packages/renderer/buildWaferMap.js +1 -0
  84. package/dist/packages/renderer/colorMap.d.ts +46 -0
  85. package/dist/packages/renderer/colorMap.js +1 -0
  86. package/dist/packages/renderer/colorSchemes.d.ts +40 -0
  87. package/dist/packages/renderer/colorSchemes.js +1 -0
  88. package/dist/packages/renderer/fmt.d.ts +30 -0
  89. package/dist/packages/renderer/fmt.js +1 -0
  90. package/dist/packages/renderer/index.d.ts +7 -0
  91. package/dist/packages/renderer/index.js +1 -0
  92. package/dist/packages/stats/analyzeWaferLot.d.ts +5 -0
  93. package/dist/packages/stats/analyzeWaferLot.js +1 -0
  94. package/dist/packages/stats/analyzeWaferMap.d.ts +13 -0
  95. package/dist/packages/stats/analyzeWaferMap.js +1 -0
  96. package/dist/packages/stats/binPareto.d.ts +49 -0
  97. package/dist/packages/stats/binPareto.js +1 -0
  98. package/dist/packages/stats/boxplot.d.ts +39 -0
  99. package/dist/packages/stats/boxplot.js +1 -0
  100. package/dist/packages/stats/capability.d.ts +55 -0
  101. package/dist/packages/stats/capability.js +1 -0
  102. package/dist/packages/stats/clusterDetection.d.ts +18 -0
  103. package/dist/packages/stats/clusterDetection.js +1 -0
  104. package/dist/packages/stats/correlation.d.ts +55 -0
  105. package/dist/packages/stats/correlation.js +1 -0
  106. package/dist/packages/stats/facets.d.ts +74 -0
  107. package/dist/packages/stats/facets.js +1 -0
  108. package/dist/packages/stats/filterFindings.d.ts +9 -0
  109. package/dist/packages/stats/filterFindings.js +1 -0
  110. package/dist/packages/stats/findingsNarrative.d.ts +3 -0
  111. package/dist/packages/stats/findingsNarrative.js +1 -0
  112. package/dist/packages/stats/histogram.d.ts +42 -0
  113. package/dist/packages/stats/histogram.js +1 -0
  114. package/dist/packages/stats/index.d.ts +27 -0
  115. package/dist/packages/stats/index.js +1 -0
  116. package/dist/packages/stats/math.d.ts +10 -0
  117. package/dist/packages/stats/math.js +1 -0
  118. package/dist/packages/stats/patternClassification.d.ts +107 -0
  119. package/dist/packages/stats/patternClassification.js +1 -0
  120. package/dist/packages/stats/regions.d.ts +57 -0
  121. package/dist/packages/stats/regions.js +1 -0
  122. package/dist/packages/stats/renderFindingsReport.d.ts +7 -0
  123. package/dist/packages/stats/renderFindingsReport.js +41 -0
  124. package/dist/packages/stats/renderSummaryReport.d.ts +60 -0
  125. package/dist/packages/stats/renderSummaryReport.js +68 -0
  126. package/dist/packages/stats/reportHtml.d.ts +42 -0
  127. package/dist/packages/stats/reportHtml.js +301 -0
  128. package/dist/packages/stats/scatter.d.ts +24 -0
  129. package/dist/packages/stats/scatter.js +1 -0
  130. package/dist/packages/stats/types.d.ts +269 -0
  131. package/dist/packages/stats/types.js +1 -0
  132. package/dist/packages/stats/yield.d.ts +56 -0
  133. package/dist/packages/stats/yield.js +1 -0
  134. package/dist/packages/worker/index.d.ts +44 -0
  135. package/dist/packages/worker/index.js +1 -0
  136. package/dist/packages/worker/wafermap.worker.d.ts +44 -0
  137. package/dist/packages/worker/wafermap.worker.js +1 -0
  138. package/package.json +122 -0
@@ -0,0 +1 @@
1
+ import{HARD_BIN_GREY as t,VIRIDIS as l,lerpKp as a,valueToGreyscale as i,hardBinColor as s,wangHash as u}from"./colorMap.js";const b=new Map;export function registerColorScheme(e,f){b.set(e,f)}export function getColorScheme(e){return b.get(e??"default")??b.get("default")}export function listColorSchemes(){return[...b.entries()].map(([e,f])=>({name:e,label:f.label}))}function c(e,f){const r=e.length-1;return n=>e[u(n^f)%r+1]}const d=[[0,0,255],[0,255,255],[255,255,0],[255,0,0]];registerColorScheme("default",{label:"Default",forBin:s,forValue:e=>a(d,e)}),registerColorScheme("viridis",{label:"Viridis",forBin:e=>a(l,Math.min(e,16)/16),forValue:e=>a(l,1-e)}),registerColorScheme("greyscale",{label:"Greyscale",forBin:c(t,2654435769),forValue:e=>i(1-e)});const B=["#aaaaaa","#51b3ec","#5199ec","#517fec","#5165ec","#5b51ec","#7a51ec","#1891d8","#1871d8","#1851d8","#1832d8","#2518d8","#4b18d8","#116697","#115097","#113997","#112397","#1a1197","#351197","#f39349","#f3a749","#f3bb49","#f3d149","#f3eb49","#e16a0e","#e1820e","#e19b0e","#e1b70e","#e1d70e","#9e4a0a","#9e5c0a","#9e6d0a","#9e810a","#9e970a","#54e8b7","#54e8ca","#54e8de","#54dee8","#54cae8","#1dd396","#1dd3af","#1dd3c7","#1dc7d3","#1dafd3","#149469","#14947b","#14948c","#148c94","#147b94","#bb5ce0","#d35ce0","#e05cd5","#e05cbf","#e05cab","#9b26c9","#b926c9","#c926bc","#c926a1","#c92688","#6d1b8d","#821b8d","#8d1b84","#8d1b71","#8d1b60"],I=[[0,32,77],[54,68,130],[107,107,145],[180,154,108],[253,228,32]];registerColorScheme("accessible",{label:"Accessible (CVD-safe / Cividis)",forBin:c(B,2654435769),forValue:e=>a(I,1-e)});const A=["#888888","#0d0887","#5302a3","#8b0aa5","#b83289","#db5c68","#f48849","#febc2a","#f0f921","#2c0594","#6a00a8","#a62098","#d0456d","#ec7958","#fad44c"],S=[[13,8,135],[126,3,168],[204,71,120],[248,149,64],[240,249,33]];registerColorScheme("plasma",{label:"Plasma",forBin:c(A,2654435769),forValue:e=>a(S,1-e)});const T=["#aaaaaa","#000004","#1b0c41","#4a0c6b","#781c6d","#a52c60","#cf4446","#ed6925","#fb9b06","#f7d13d","#fcffa4","#2e0a47","#8e1e6e","#c8424b","#f5a623"],V=[[0,0,4],[87,16,110],[188,55,84],[249,142,9],[252,255,164]];registerColorScheme("inferno",{label:"Inferno",forBin:c(T,2654435769),forValue:e=>a(V,1-e)});const _=["#aaaaaa","#27ae60","#e74c3c","#f39c12","#2ecc71","#c0392b","#f1c40f","#1e8449","#e67e22","#a9cce3","#922b21","#82e0aa","#d4e6f1","#fdebd0","#d5f5e3"],m=[[46,204,113],[241,196,15],[231,76,60]];registerColorScheme("traffic",{label:"Traffic (Green\u2013Yellow\u2013Red)",forBin:c(_,2654435769),forValue:e=>a(m,e)});const C=["#aaaaaa","#2980b9","#e74c3c","#1abc9c","#f39c12","#8e44ad","#e67e22","#3498db","#c0392b","#16a085","#f1c40f","#154360","#922b21","#d6eaf8","#fdedec"];registerColorScheme("thermal",{label:"Thermal (Blue\u2013Cyan\u2013Yellow\u2013Red)",forBin:c(C,2654435769),forValue:e=>a(d,e)});const o=[[0,0,128],[0,0,255],[0,255,255],[255,255,0],[255,0,0],[128,0,0]];registerColorScheme("jet",{label:"Jet (MATLAB rainbow)",forBin:e=>a(o,Math.min(e,16)/16),forValue:e=>a(o,e)});
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Plain-language labels for lot-stack aggregation methods. Maps internal API terms to the words
3
+ * shown to the user (e.g. `countBin` → "occurrence count"). Shared by the canvas map title and the
4
+ * DOM summary panel so both name the method identically.
5
+ */
6
+ export declare const AGGREGATION_METHOD_LABELS: Record<string, string>;
7
+ /** Format an aggregation method for display, falling back to the raw key or "aggregated". */
8
+ export declare function fmtAggregationMethod(method: string | undefined): string;
9
+ /**
10
+ * Map internal bin terms to plain language for display — "HBin 2" →
11
+ * "hard bin 2" (CLAUDE.md: internal API terms must never appear in the UI).
12
+ * Shared by the Summary panel and the printable reports so both surfaces
13
+ * speak the same language; stats-level `StatsFinding` text keeps the
14
+ * compact internal terms (they're also machine-consumed and test-asserted).
15
+ */
16
+ export declare function plainBinTerms(text: string): string;
17
+ /**
18
+ * Format a numeric value for display (tooltips, overlays, single-value labels).
19
+ *
20
+ * - With a unit: always uses SI prefix (e.g. `12 µV`).
21
+ * - Without a unit, `fallbackFormat: 'si'`: SI prefix with no unit suffix (e.g. `12 µ`).
22
+ * - Without a unit, `fallbackFormat: 'engineering'` (default): fixed decimal for [0.1, 9999],
23
+ * engineering notation (E±N, multiples of 3) outside that range.
24
+ */
25
+ export declare function fmt(v: number, unit?: string, fallbackFormat?: 'si' | 'engineering'): string;
26
+ export declare function fmtColorbarAxis(vRef: number, name: string | null | undefined, unit: string | undefined, fallbackFormat?: 'si' | 'engineering'): {
27
+ tickFmt: (v: number) => string;
28
+ axisLabel: string;
29
+ };
30
+ //# sourceMappingURL=fmt.d.ts.map
@@ -0,0 +1 @@
1
+ const p=[[1e12,"T"],[1e9,"G"],[1e6,"M"],[1e3,"k"],[1,""],[.001,"m"],[1e-6,"\xB5"],[1e-9,"n"],[1e-12,"p"],[1e-15,"f"]],g={T:1e12,G:1e9,M:1e6,k:1e3,m:.001,\u00B5:1e-6,u:1e-6,n:1e-9,p:1e-12,f:1e-15},E=["Hz","V","A","W","F","\u03A9","Ohm","S","H","J","C","s"];function m(t){for(const e of E)if(t.length===e.length+1&&t.endsWith(e)){const s=t[0];if(s in g)return{preScale:g[s],base:e}}return{preScale:1,base:t}}function F(t,e){const{preScale:s,base:n}=m(e),o=t*s,i=Math.abs(o),[r,u]=p.find(([f])=>i>=f*.9999)??[1e-15,"f"],a=o/r,l=Math.abs(a),c=l>=100?0:l>=10?1:2;return`${a.toFixed(c)} ${u}${n}`}function A(t){const e=Math.abs(t),s=Math.floor(Math.log10(e)/3)*3,n=Math.max(-15,Math.min(12,s)),o=t/Math.pow(10,n),i=Math.abs(o),r=i>=100?0:i>=10?1:2,u=n===0?"":`E${n>0?"+":""}${n}`;return`${o.toFixed(r)}${u}`}export const AGGREGATION_METHOD_LABELS={mean:"mean",median:"median",stddev:"std dev",min:"min",max:"max",count:"count",countBin:"occurrence count",percent:"occurrence %"};export function fmtAggregationMethod(t){return AGGREGATION_METHOD_LABELS[t??""]??t??"aggregated"}export function plainBinTerms(t){return t.replace(/\bHBin\b/g,"hard bin").replace(/\bSBin\b/g,"soft bin")}export function fmt(t,e,s){if(!isFinite(t))return String(t);const n=Math.abs(t);return n===0?e?`0 ${e}`:"0":e!==void 0?F(t,e):s==="si"?F(t,""):n>=.1&&n<1e4?n>=1e3?t.toFixed(0):n>=100?t.toFixed(1):n>=10?t.toFixed(2):t.toFixed(3):A(t)}function h(t){return e=>{if(!isFinite(e))return String(e);if(e===0)return"0";const s=e/t,n=Math.abs(s),o=n>=100?0:n>=10?1:2;return s.toFixed(o)}}export function fmtColorbarAxis(t,e,s,n="engineering"){const o=Math.abs(t);if(s){const{preScale:c,base:f}=m(s),d=o*c,[b,x]=d===0?[1,""]:p.find(([S])=>d>=S*.9999)??[1e-15,"f"],M=`${x}${f}`,$=e?`${e} (${M})`:M;return{tickFmt:h(b/c),axisLabel:$}}if(o===0||o>=.1&&o<1e4)return{tickFmt:c=>fmt(c,void 0,n),axisLabel:e??""};if(n==="si"){const[c]=p.find(([x])=>o>=x*.9999)??[1e-15,"f"],f=Math.round(Math.log10(c)),d=f===0?"":`\xD710E${f}`,b=e?d?`${e} (${d})`:e:d;return{tickFmt:h(c),axisLabel:b}}const i=Math.floor(Math.log10(o)/3)*3,r=Math.max(-15,Math.min(12,i)),u=Math.pow(10,r),a=r===0?"":`\xD710E${r}`,l=e?a?`${e} (${a})`:e:a;return{tickFmt:h(u),axisLabel:l}}
@@ -0,0 +1,7 @@
1
+ export { buildView, resolveTestNumber, findTestDef, getUniqueTestNumbers, generateTextOverlay, getDieKey, buildHoverText, buildMapTitle, } from './buildView.js';
2
+ export type { View, MapTitleParts, PlotMode, ViewRect, ViewText, ViewHoverPoint, ViewOverlay, ViewOptions, } from './buildView.js';
3
+ export * from './buildWaferMap.js';
4
+ export type { WaferMetadata, DieMetadata } from '../core/metadata.js';
5
+ export { hardBinColor, hardBinGreyscale, valueToViridis, valueToGreyscale, softBinColor, contrastTextColor, } from './colorMap.js';
6
+ export * from './colorSchemes.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ export{buildView,resolveTestNumber,findTestDef,getUniqueTestNumbers,generateTextOverlay,getDieKey,buildHoverText,buildMapTitle}from"./buildView.js";export*from"./buildWaferMap.js";export{hardBinColor,hardBinGreyscale,valueToViridis,valueToGreyscale,softBinColor,contrastTextColor}from"./colorMap.js";export*from"./colorSchemes.js";
@@ -0,0 +1,5 @@
1
+ import type { AnalyzeWaferLotInput, AnalyzeWaferMapOptions, LotStatsSummary, StatsSummary } from './types.js';
2
+ export declare function analyzeWaferLot(items: AnalyzeWaferLotInput, options?: AnalyzeWaferMapOptions & {
3
+ perWaferSummaries?: StatsSummary[];
4
+ }): LotStatsSummary;
5
+ //# sourceMappingURL=analyzeWaferLot.d.ts.map
@@ -0,0 +1 @@
1
+ import{analyzeWaferMap as m}from"./analyzeWaferMap.js";import{median as u}from"../core/utils.js";const y=1.3,h=2;function g(o,s){const r={info:0,notable:1,unusual:2};return r[o]>=r[s]?o:s}function w(o){const s=new Map;for(const l of o){const a=new Set;for(const i of l.summary.findings){const t=[i.variable.kind,i.variable.index??"",i.variable.bin??"",i.comparison.family,i.comparison.left,i.effect.direction].join("|");if(a.has(t))continue;a.add(t);const n=s.get(t)??{finding:i,waferIndices:[],severity:i.severity};n.waferIndices.push(l.waferIndex),n.severity=g(n.severity,i.severity),s.set(t,n)}}const r=[];for(const[l,a]of s){if(a.waferIndices.length<h)continue;const i=a.waferIndices.length/Math.max(1,o.length),t=i>=.6||a.severity==="unusual"?"unusual":"notable",n=a.finding;r.push({id:`lot-repeat:${l}`,level:"lot",severity:t,variable:{...n.variable},comparison:{...n.comparison},effect:{direction:n.effect.direction,absoluteDelta:n.effect.absoluteDelta,relativeDelta:n.effect.relativeDelta,effectSize:n.effect.effectSize},stats:{method:"wafer-finding-frequency",sampleSizeLeft:a.waferIndices.length,sampleSizeRight:o.length-a.waferIndices.length},summary:`${n.summary} \u2014 seen on ${a.waferIndices.length}/${o.length} wafers (${Math.round(i*100)}%)`,highlight:{kind:"wafer",waferIndices:a.waferIndices}})}return r}function b(o){const s=o.map(t=>({waferIndex:t.waferIndex,yieldPercent:t.summary.stats.yieldPercent})).filter(t=>t.yieldPercent!==null);if(s.length<3)return[];const r=s.map(t=>t.yieldPercent),l=u(r),a=u(r.map(t=>Math.abs(t-l)));if(!Number.isFinite(a)||a===0)return[];const i=[];for(const t of s){const n=.6745*(t.yieldPercent-l)/a;if(Math.abs(n)<y)continue;const e=t.yieldPercent-l;i.push({id:`inter-wafer:yield:${t.waferIndex}`,level:"inter-wafer",severity:Math.abs(n)>=2?"unusual":"notable",variable:{kind:"yield",label:"Yield"},comparison:{family:"wafer",left:`Wafer ${t.waferIndex+1}`,right:"Lot median"},effect:{direction:e>0?"higher":"lower",absoluteDelta:e,relativeDelta:l===0?void 0:e/l,effectSize:n},stats:{method:"mad-z-score",sampleSizeLeft:1,sampleSizeRight:s.length-1},summary:`Wafer ${t.waferIndex+1} yield is ${Math.abs(e).toFixed(1)} percentage points ${e>0?"higher":"lower"} than the lot median`,highlight:{kind:"wafer",waferIndices:[t.waferIndex]}})}return i}export function analyzeWaferLot(o,s={}){const r=o.map((e,f)=>({waferIndex:f,summary:s.perWaferSummaries?.[f]??m(e,s)})),l=[...w(r),...b(r)].sort((e,f)=>{const c=e.severity==="unusual"?2:e.severity==="notable"?1:0,d=f.severity==="unusual"?2:f.severity==="notable"?1:0;return c!==d?d-c:e.summary.localeCompare(f.summary,"en")}),a=r.find(e=>e.summary.wafer)?.summary.wafer,i=a?Object.fromEntries(Object.entries(a).filter(([e])=>e!=="wafer"&&e!=="waferId")):void 0,t=r.map(({waferIndex:e,summary:f})=>({waferIndex:e,yieldPercent:f.stats.yieldPercent})),n=r.map(({waferIndex:e,summary:f})=>f.stats.perTestStats?.length?{waferIndex:e,tests:f.stats.perTestStats}:null).filter(e=>e!==null);return{level:"lot",hasNotableFindings:l.some(e=>e.severity!=="info")||r.some(e=>e.summary.hasNotableFindings),findings:l,lot:Object.keys(i??{}).length>0?i:void 0,stats:{waferCount:o.length},lotYieldSeries:t,perWafer:r,...n.length>0?{perWaferTestStats:n}:{}}}
@@ -0,0 +1,13 @@
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';
4
+ /**
5
+ * Per-test pass rate for functional (`testType: 'F'`) tests — the functional
6
+ * counterpart of `computeTestSpecYield`. Same population convention: partial and
7
+ * edge-excluded dies are skipped; the denominator is dies with a recorded
8
+ * verdict for the test (read via `getTestPassStatus`, so legacy 0/1-encoded
9
+ * functional data is counted identically); dies never tested count as neither.
10
+ */
11
+ export declare function computeFunctionalYield(dies: Die[], testDefs: TestDef[] | undefined): StatsSummary['stats']['functionalYield'];
12
+ export declare function analyzeWaferMap(input: AnalyzeWaferMapInput, options?: AnalyzeWaferMapOptions): StatsSummary;
13
+ //# sourceMappingURL=analyzeWaferMap.d.ts.map
@@ -0,0 +1 @@
1
+ import{isYieldEligibleDie as X,getDieKey as T}from"../core/dies.js";import{buildWaferMap as De,getTestPassStatus as ue,isParametricTest as q}from"../renderer/buildWaferMap.js";import{buildQuadrantRegions as $e,buildReticlePositionRegions as ke,buildRingRegions as ze,buildSectorRegions as ve,buildTestSiteRegions as Me,sectorCompassNames as de,areQuadrantsAdjacent as Re,parseRegionKey as j}from"./regions.js";import{buildClusterFindings as Be}from"./clusterDetection.js";import{classifyPattern as Ve}from"./patternClassification.js";import{normalCdf as ge}from"./math.js";import{quantile as Z}from"./math.js";import{mean as G,clamp01 as he}from"../core/utils.js";const Fe={ringCount:4,passBins:[1],significanceLevel:.05,minimumEffectSize:.2,minimumRelativeEffect:1,minimumSampleSize:5,includePartial:!1,includeEdgeExcluded:!1,enableYieldAnalysis:!0,enableHardBinAnalysis:!0,enableSoftBinAnalysis:!0,enableTestValueAnalysis:!1,computePerTestStats:!1,enableReticlePositionAnalysis:!0,enableClusterAnalysis:!0,enableAngularAnalysis:!0,sectorCount:8,enablePatternClassification:!0,minimumClusterSize:5};function Pe(s,r){const o={...s};return o.minimumClusterSize=Math.max(5,Math.round(r*.003)),o}function Le(s){return"wafer"in s&&"dies"in s&&"view"in s?s:De(s)}function Ne(s,r){return X(s,r)?s.hbin!==void 0||s.sbin!==void 0||s.testValues!==void 0&&Object.keys(s.testValues).length>0:!1}function Ce(s,r,o){if(!s||r)return;const e=(o??[]).filter(n=>q(n)&&(n.limitLow!==void 0||n.limitHigh!==void 0));if(e.length!==0)return n=>{for(const l of e){const a=l.testNumber,i=n.testValues?.[a];if(i!==void 0&&(l.limitLow!==void 0&&i<l.limitLow||l.limitHigh!==void 0&&i>l.limitHigh))return!0}return!1}}function Ae(s,r,o){const e=new Set,n=new Set,l=new Set,a=new Map,i=new Map;for(const t of s){if(t.testValues)for(const c of Object.keys(t.testValues))e.add(Number(c));if(t.testPass)for(const c of Object.keys(t.testPass))e.add(Number(c));t.hbin!==void 0&&n.add(t.hbin),t.sbin!==void 0&&l.add(t.sbin),X(t)&&(t.hbin!==void 0&&a.set(t.hbin,(a.get(t.hbin)??0)+1),t.sbin!==void 0&&i.set(t.sbin,(i.get(t.sbin)??0)+1))}return{totalDies:s.length,analyzedDies:r,excludedDies:s.length-r,yieldPercent:o,testsConsidered:[...e].sort((t,c)=>t-c),hardBinsConsidered:[...n].sort((t,c)=>t-c),softBinsConsidered:[...l].sort((t,c)=>t-c),...a.size?{hardBinCounts:Object.fromEntries(a)}:{},...i.size?{softBinCounts:Object.fromEntries(i)}:{}}}function Ke(s,r){if(!r?.length)return;const o=r.filter(n=>q(n)&&(n.limitLow!==void 0||n.limitHigh!==void 0));if(!o.length)return;const e=[];for(const n of o){const l=n.testNumber;let a=0,i=0,t=0,c=0;for(const u of s){if(u.partial||u.edgeExcluded)continue;const b=u.testValues?.[l];b!==void 0&&(c++,n.limitLow!==void 0&&b<n.limitLow?i++:n.limitHigh!==void 0&&b>n.limitHigh?t++:a++)}e.push({testNumber:l,label:n.name,passDies:a,failLowDies:i,failHighDies:t,totalDies:c,yieldPercent:c>0?a/c*100:null})}return e.length?e:void 0}export function computeFunctionalYield(s,r){if(!r?.length)return;const o=r.filter(n=>!q(n));if(!o.length)return;const e=[];for(const n of o){const l=n.testNumber;let a=0,i=0;for(const c of s){if(c.partial||c.edgeExcluded)continue;const u=ue(c,l,n);u!==void 0&&(u?a++:i++)}const t=a+i;e.push({testNumber:l,label:n.name,passDies:a,failDies:i,totalDies:t,passRatePercent:t>0?a/t*100:null})}return e.length?e:void 0}function Ee(s,r,o,e){const n=[];for(const l of r){const a=[];for(const b of s){if(b.partial||b.edgeExcluded)continue;const g=b.testValues?.[l];g!==void 0&&a.push(g)}if(a.length<e)continue;const i=G(a),t=Math.sqrt(re(a,i)),c=a.slice().sort((b,g)=>b-g),u=o?.find(b=>b.testNumber===l)?.name??String(l);n.push({testNumber:l,label:u,count:a.length,min:c[0],max:c[c.length-1],mean:i,stddev:t,median:Z(c,.5),q1:Z(c,.25),q3:Z(c,.75)})}return n.length?n:void 0}function H(s,r,o,e){const n=(s+o)/(r+e),l=n*(1-n)*(1/r+1/e);if(!Number.isFinite(l)||l<=0)return 1;const a=(s/r-o/e)/Math.sqrt(l);return he(2*(1-ge(Math.abs(a))))}function ee(s){const r=new Map;for(const o of s){const e=`${o.variable.kind}:${o.comparison.family}`,n=r.get(e)??[];n.push(o),r.set(e,n)}for(const o of r.values()){const e=[...o].filter(a=>a.stats.pValue!==void 0).sort((a,i)=>(a.stats.pValue??1)-(i.stats.pValue??1)),n=e.length;let l=1;for(let a=n-1;a>=0;a--){const i=e[a],t=i.stats.pValue??1,c=Math.min(1,Math.min(l,t*n/(a+1)));i.stats.adjustedPValue=c,l=c}}return s}function te(s,r,o){const e=Math.abs(r),n=o!==void 0?Math.abs(o):0;return s<=.01&&(e>=.3||n>=2.5)?"unusual":s<=.05&&(e>=.2||n>=1.5)?"notable":"info"}function ne(s,r){return s<=.01&&Math.abs(r)>=.5?"unusual":s<=.05&&Math.abs(r)>=.15?"notable":"info"}function J(s){return s==="reticle-position"?"other reticle positions":s==="test-site"?"other test sites":"the rest of the map"}function O(s){return s==="reticle-position"?"Other reticle positions":s==="test-site"?"Other test sites":"Rest of map"}function me(s,r,o){const e=(Math.abs(r)*100).toFixed(1);return`${s} yield is ${e} percentage points ${r>0?"higher":"lower"} than ${J(o)}`}function ie(s,r){return r==="quadrant"&&!s.startsWith("Quadrant")?`quadrant ${s}`:s}function pe(s,r,o,e){const n=ie(s,e),l=(Math.abs(o)*100).toFixed(1);return`${n} has ${r} occurrence ${l} percentage points ${o>0?"higher":"lower"} than ${J(e)}`}function xe(s,r,o,e){const n=ie(s,e),l=(Math.abs(o)*100).toFixed(1);return`${n} has ${r} pass rate ${l} percentage points ${o>0?"higher":"lower"} than ${J(e)}`}function be(s,r,o,e,n,l){const a=ie(s,n),i=J(n),t=o>0?"higher":"lower";if(e!==void 0&&Number.isFinite(e)){const u=(Math.abs(e)*100).toFixed(1);return`${a} mean ${r} is ${u}% ${t} than ${i}`}const c=l?` ${l}`:"";return`${a} mean ${r} is ${Math.abs(o).toPrecision(3)}${c} ${t} than ${i}`}function ye(s,r,o){const e=r?.find(n=>n.bin===s);return e?.name?`${o} ${s} (${e.name})`:`${o} ${s}`}function Te(s,r){const o=r?.find(e=>e.testNumber===s);return{label:o?.name??`Test ${s}`,unit:o?.unit}}function se(s,r){const o=new Map(s.map(n=>[T(n),n])),e=new Map;for(const n of r){const l=[];for(const a of n.dieKeys){const i=o.get(a);i&&l.push(i)}e.set(n.key,l)}return e}function oe(s,r){return ee(s),s.filter(o=>{const e=o.stats.adjustedPValue??o.stats.pValue??1,n=Math.abs(o.effect.absoluteDelta??0),l=Math.abs(o.effect.relativeDelta??0);return e<=r.significanceLevel&&(n>=r.minimumEffectSize||l>=r.minimumRelativeEffect)}).map(o=>({...o,severity:te(o.stats.adjustedPValue??o.stats.pValue??1,o.effect.absoluteDelta??0,o.effect.relativeDelta)}))}function Y(s,r,o,e){const n=new Set(o),l=se(s,r),a=new Map,i=new Map;for(const[c,u]of l){let b=0,g=0;for(const d of u)d.hbin!==void 0&&(g++,n.has(d.hbin)&&b++);a.set(c,b),i.set(c,g)}const t=[];for(const c of r){const u=i.get(c.key)??0,b=a.get(c.key)??0;let g=0,d=0;for(const[$,p]of i)$!==c.key&&(g+=p,d+=a.get($)??0);if(u<e.minimumSampleSize||g<e.minimumSampleSize)continue;const h=b/u,m=d/g,S=h-m,z=H(b,u,d,g);t.push({id:`yield:${c.key}`,level:"wafer",severity:"info",variable:{kind:"yield",label:"Yield"},comparison:{family:c.family,left:c.label,right:O(c.family)},effect:{direction:S===0?"different":S>0?"higher":"lower",absoluteDelta:S,relativeDelta:m===0?void 0:S/m,effectSize:S},stats:{method:"two-proportion-z",pValue:z,sampleSizeLeft:u,sampleSizeRight:g},summary:me(c.label,S,c.family),highlight:{kind:"region",regionFamily:c.family,regionKeys:[c.key],dieKeys:[...c.dieKeys]}})}return oe(t,e)}function L(s,r,o,e,n,l){const a=d=>o==="soft"?d.sbin:d.hbin,i=[...new Set(s.map(a).filter(d=>d!==void 0))].sort((d,h)=>d-h),t=se(s,r),c=new Map,u=new Map;for(const[d,h]of t){const m=new Map;for(const S of h){const z=a(S);z!==void 0&&m.set(z,(m.get(z)??0)+1)}c.set(d,m),u.set(d,h.length)}const b=[],g=n==="hardBin"?"HBin":"SBin";for(const d of r){const h=u.get(d.key)??0,m=c.get(d.key);let S=0;const z=new Map;for(const[$,p]of c)if($!==d.key){S+=u.get($)??0;for(const[w,k]of p)z.set(w,(z.get(w)??0)+k)}if(!(h<l.minimumSampleSize||S<l.minimumSampleSize))for(const $ of i){const p=m.get($)??0,w=z.get($)??0,k=p/h,f=w/S,y=k-f,v=H(p,h,w,S),D=ye($,e,g);b.push({id:`${n}:${$}:${d.key}`,level:"wafer",severity:"info",variable:{kind:n,bin:$,label:D},comparison:{family:d.family,left:d.label,right:O(d.family)},effect:{direction:y===0?"different":y>0?"higher":"lower",absoluteDelta:y,relativeDelta:f===0?void 0:y/f,effectSize:y},stats:{method:"two-proportion-z",pValue:v,sampleSizeLeft:h,sampleSizeRight:S},summary:pe(d.label,D,y,d.family),highlight:{kind:"bin",bin:$,regionKeys:[d.key],dieKeys:[...d.dieKeys]}})}}return oe(b,l)}function Q(s,r,o,e){const n=(o??[]).filter(u=>!q(u));if(!n.length)return[];const l=n.map(u=>u.testNumber),a=se(s,r),i=new Map,t=new Map;for(const[u,b]of a){const g=new Uint32Array(n.length),d=new Uint32Array(n.length);for(const h of b)for(let m=0;m<n.length;m++){const S=ue(h,l[m],n[m]);S!==void 0&&(d[m]++,S&&g[m]++)}i.set(u,g),t.set(u,d)}const c=[];for(const u of r){const b=i.get(u.key),g=t.get(u.key);for(let d=0;d<n.length;d++){let h=0,m=0;for(const[y,v]of t)y!==u.key&&(m+=v[d],h+=i.get(y)[d]);const S=g[d],z=b[d];if(S<e.minimumSampleSize||m<e.minimumSampleSize)continue;const $=z/S,p=h/m,w=$-p,k=H(z,S,h,m),f=l[d];c.push({id:`functional:${f}:${u.key}`,level:"wafer",severity:"info",variable:{kind:"functionalTest",index:f,label:`${n[d].name} pass rate`},comparison:{family:u.family,left:u.label,right:O(u.family)},effect:{direction:w===0?"different":w>0?"higher":"lower",absoluteDelta:w,relativeDelta:p===0?void 0:w/p,effectSize:w},stats:{method:"two-proportion-z",pValue:k,sampleSizeLeft:S,sampleSizeRight:m},summary:xe(u.label,n[d].name,w,u.family),highlight:{kind:"region",regionFamily:u.family,regionKeys:[u.key],dieKeys:[...u.dieKeys]}})}}return oe(c,e)}function re(s,r){return s.length<2?0:s.reduce((o,e)=>o+(e-r)**2,0)/(s.length-1)}function Se(s,r,o,e,n,l){const a=Math.sqrt(o/s+l/e),i=r-n;if(!Number.isFinite(a)||a===0)return{pValue:1,effectSize:0,delta:i};const t=i/a,c=Math.sqrt(Math.max(0,(o+l)/2)),u=c===0?i:i/c;return{pValue:he(2*(1-ge(Math.abs(t)))),effectSize:u,delta:i}}function qe(s,r){const o=G(s),e=G(r);return Se(s.length,o,re(s,o),r.length,e,re(r,e))}const ae=250;function we(s,r,o){const e=a=>a.filter(i=>q(o?.find(t=>t.testNumber===i)));if(r)return{testNumbers:e(r.slice().sort((a,i)=>a-i))};const n=new Set;let l=!1;e:for(const a of s)for(const i of Object.keys(a.testValues??{})){const t=Number(i);if(!n.has(t)&&(n.add(t),n.size>ae)){l=!0;break e}}if(l){const a=`[wafermap] analyzeWaferMap: more than ${ae} tests found in die data. Pass testNumbers: [...] in options to enable test value analysis for specific tests. Auto-cap threshold is ${ae}.`;return console.warn(a),{testNumbers:[],warning:a}}return{testNumbers:e([...n].sort((a,i)=>a-i))}}function _(s,r,o,e){const n=we(s,e.testNumbers,o);if(n.warning)return{findings:[],warning:n.warning};const l=n.testNumbers,a=new Map;for(let h=0;h<r.length;h++)for(const m of r[h].dieKeys)a.set(m,h);const i=r.length,t=[],c=[];for(const h of s){const m=a.get(T(h));m!==void 0&&(t.push(h),c.push(m))}const u=[],b=new Float64Array(i),g=new Float64Array(i),d=new Float64Array(i);for(const h of l){b.fill(0),g.fill(0),d.fill(0);let m=0,S=0,z=0,$;for(let p=0;p<t.length;p++){const k=t[p].testValues?.[h];if(k===void 0)continue;$===void 0&&($=k);const f=k-$,y=c[p];b[y]+=1,g[y]+=f,d[y]+=f*f,m+=1,S+=f,z+=f*f}if($!==void 0)for(let p=0;p<i;p++){const w=b[p],k=m-w;if(w<e.minimumSampleSize||k<e.minimumSampleSize)continue;const f=g[p]/w,v=(S-g[p])/k,D=f+$,M=v+$,V=w>1?Math.max(0,d[p]-w*f*f)/(w-1):0,R=k>1?Math.max(0,z-d[p]-k*v*v)/(k-1):0,{pValue:C,effectSize:P,delta:A}=Se(w,D,V,k,M,R),F=r[p],{label:W,unit:U}=Te(h,o),x=M!==0?A/Math.abs(M):void 0;u.push({id:`test:${h}:${F.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:h,label:W,unit:U},comparison:{family:F.family,left:F.label,right:O(F.family)},effect:{direction:A===0?"different":A>0?"higher":"lower",absoluteDelta:A,relativeDelta:x,effectSize:P},stats:{method:"welch-z-approx",pValue:C,sampleSizeLeft:w,sampleSizeRight:k},summary:be(F.label,W,A,x,F.family,U),highlight:{kind:"region",regionFamily:F.family,regionKeys:[F.key],dieKeys:[...F.dieKeys]}})}}return ee(u),{activeTestNumbers:l,findings:u.filter(h=>{const m=h.stats.adjustedPValue??h.stats.pValue??1,S=Math.abs(h.effect.effectSize??0);return m<=e.significanceLevel&&S>=e.minimumEffectSize}).map(h=>({...h,severity:ne(h.stats.adjustedPValue??h.stats.pValue??1,h.effect.effectSize??0)}))}}function je(s,r,o,e){if(!o?.length)return[];const n=o.filter(i=>q(i)&&(i.limitLow!==void 0||i.limitHigh!==void 0));if(!n.length)return[];const l=[],a=new Map(s.map(i=>[T(i),i]));for(const i of n){const t=i.testNumber;for(const c of r){const u=new Map;for(const g of c){const d=[];for(const h of g.dieKeys){const m=a.get(h);m&&d.push(m)}u.set(g.key,d)}const b=[];for(const g of c){const d=u.get(g.key),h=[];for(const[D,M]of u)if(D!==g.key)for(const V of M)h.push(V);const m=D=>D.testValues?.[t]!==void 0,S=D=>{const M=D.testValues?.[t];return M===void 0?!1:i.limitLow!==void 0&&M<i.limitLow||i.limitHigh!==void 0&&M>i.limitHigh},z=d.filter(m),$=h.filter(m);if(z.length<e.minimumSampleSize||$.length<e.minimumSampleSize)continue;const p=z.filter(S).length,w=$.filter(S).length,k=p/z.length,f=w/$.length,y=k-f,v=H(p,z.length,w,$.length);b.push({id:`specLimit:${t}:${g.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:t,label:i.name,unit:i.unit},comparison:{family:g.family,left:g.label,right:O(g.family)},effect:{direction:y===0?"different":y>0?"higher":"lower",absoluteDelta:y,relativeDelta:f===0?void 0:y/f,effectSize:y},stats:{method:"two-proportion-z",pValue:v,sampleSizeLeft:z.length,sampleSizeRight:$.length},summary:`${g.label} spec-fail rate for ${i.name} is ${(Math.abs(y)*100).toFixed(1)} pp ${y>0?"higher":"lower"} than the rest of the wafer`,highlight:{kind:"region",regionFamily:g.family,regionKeys:[g.key],dieKeys:[...g.dieKeys]}})}ee(b),l.push(...b.filter(g=>{const d=g.stats.adjustedPValue??g.stats.pValue??1,h=Math.abs(g.effect.absoluteDelta??0),m=Math.abs(g.effect.relativeDelta??0);return d<=e.significanceLevel&&(h>=e.minimumEffectSize||m>=e.minimumRelativeEffect)}).map(g=>({...g,severity:ne(g.stats.adjustedPValue??g.stats.pValue??1,g.effect.effectSize??0)})))}}return l}function E(s){return s.highlight.regionKeys??[]}function He(s){const r=s.variable.bin??s.variable.index??"";return`${s.comparison.family}\0${s.variable.kind}\0${r}\0${s.effect.direction}`}function I(s,r){const o=E(s)[0]??"",e=j(o);return e.family==="ring"?e.ring??0:e.family==="sector"?de(r).indexOf(e.sector??""):0}function Oe(s,r,o){if(r==="quadrant"){const a=new Map;for(const u of s){const b=j(E(u)[0]??"").quadrant;b&&a.set(b,u)}const i=[...a.keys()],t=new Set,c=[];for(const u of i){if(t.has(u))continue;const b=[],g=[u];for(t.add(u);g.length;){const d=g.pop();b.push(d);for(const h of i)!t.has(h)&&Re(d,h)&&(t.add(h),g.push(h))}c.push(b.map(d=>a.get(d)))}return c}const e=[...s].sort((a,i)=>I(a,o)-I(i,o)),n=[];let l=[];for(const a of e){if(l.length===0){l.push(a);continue}const i=I(l[l.length-1],o);I(a,o)-i===1?l.push(a):(n.push(l),l=[a])}if(l.length&&n.push(l),r==="sector"&&n.length>1){const i=de(o).length,t=I(n[0][0],o),c=I(n[n.length-1][n[n.length-1].length-1],o);if(t===0&&c===i-1){const u=n.pop();n[0]=[...u,...n[0]]}}return n}function Ie(s,r){const o=s.map(a=>j(E(a)[0]??"").ring).filter(a=>a!==void 0).sort((a,i)=>a-i),e=o[0],n=o[o.length-1];let l="";return e===n&&e===1&&r>1?l=" (core)":e===n&&e===r&&r>1&&(l=" (edge)"),e===n?`Ring ${e}${l}`:`Rings ${e}\u2013${n}`}function Ye(s,r){const o=s.map(e=>j(E(e)[0]??"").sector??"");return o.length===1?`Sector ${o[0]}`:`Sectors ${o[0]}\u2013${o[o.length-1]}`}function Qe(s){const r=s.map(o=>j(E(o)[0]??"").quadrant??"");return r.length===1?r[0]:r.length===2?`Quadrants ${r[0]} & ${r[1]}`:`Quadrants ${r.slice(0,-1).join(", ")} & ${r[r.length-1]}`}function le(s){return[...new Set(s)]}function _e(s,r){const o=s[0],e=o.comparison.family,n=o.variable.kind,l=le(s.flatMap(p=>p.highlight.dieKeys??[])),a=le(s.flatMap(E)),i=new Set(l),t=e==="ring"?Ie(s,r.ringCount):e==="sector"?Ye(s,r.sectorCount):Qe(s),c=n==="softBin"?r.softEligibleDies:n==="test"?r.testDies:r.eligibleDies,u=c.filter(p=>i.has(T(p))),b=c.filter(p=>!i.has(T(p)));let g,d,h,m,S;if(n==="test"){const p=o.variable.index,w=R=>R.testValues?.[p],k=u.map(w).filter(R=>R!==void 0),f=b.map(w).filter(R=>R!==void 0),{pValue:y,effectSize:v,delta:D}=qe(k,f),M=G(f),V=M!==0?D/Math.abs(M):void 0;g={direction:D===0?"different":D>0?"higher":"lower",absoluteDelta:D,relativeDelta:V,effectSize:v},d={method:"welch-z-approx",pValue:y,sampleSizeLeft:k.length,sampleSizeRight:f.length},h=ne(y,v),m=be(t,o.variable.label,D,V,e,o.variable.unit),S=`test:${p}`}else if(n==="yield"){const p=new Set(r.passBins),w=u.filter(P=>P.hbin!==void 0),k=b.filter(P=>P.hbin!==void 0),f=w.length,y=k.length,v=w.filter(P=>p.has(P.hbin)).length,D=k.filter(P=>p.has(P.hbin)).length,M=f>0?v/f:0,V=y>0?D/y:0,R=M-V,C=H(v,f,D,y);g={direction:R===0?"different":R>0?"higher":"lower",absoluteDelta:R,relativeDelta:V===0?void 0:R/V,effectSize:R},d={method:"two-proportion-z",pValue:C,sampleSizeLeft:f,sampleSizeRight:y},h=te(C,R,g.relativeDelta),m=me(t,R,e),S="yield"}else{const p=o.variable.bin,w=R=>n==="softBin"?R.sbin:R.hbin,k=u.filter(R=>w(R)===p).length,f=b.filter(R=>w(R)===p).length,y=k/u.length,v=f/b.length,D=y-v,M=H(k,u.length,f,b.length);g={direction:D===0?"different":D>0?"higher":"lower",absoluteDelta:D,relativeDelta:v===0?void 0:D/v,effectSize:D},d={method:"two-proportion-z",pValue:M,sampleSizeLeft:u.length,sampleSizeRight:b.length},h=te(M,D,g.relativeDelta);const V=n==="softBin"?r.sbinDefs:r.hbinDefs;m=pe(t,ye(p,V,n==="softBin"?"SBin":"HBin"),D,e),S=`${n}:${p}`}const z=le(a.map(p=>p.replace(`${e}:`,""))).join("-"),$=n==="hardBin"||n==="softBin"?{kind:"bin",bin:o.variable.bin,regionKeys:a,dieKeys:l}:{kind:"region",regionFamily:e,regionKeys:a,dieKeys:l};return{...o,id:`${S}:${e}:${z}`,severity:h,comparison:{family:e,left:t,right:O(e)},effect:g,stats:d,summary:m,highlight:$,relatedIds:s.map(p=>p.id)}}function We(s,r){const o=new Set(["ring","quadrant","sector"]),e=[],n=new Map;for(const a of s){if(!o.has(a.comparison.family)){e.push(a);continue}const i=He(a),t=n.get(i)??[];t.push(a),n.set(i,t)}const l=[];for(const a of n.values()){if(a.length===1){l.push(a[0]);continue}const i=a[0].comparison.family;for(const t of Oe(a,i,r.sectorCount))l.push(t.length===1?t[0]:_e(t,r))}return[...e,...l]}export function analyzeWaferMap(s,r={}){const o={...Fe,...r},e=Le(s),n=e.isLotStack,l=e.aggrMethod,a=!n||l==="mode"||l==="countBin"||l==="percent",i=e.dies.filter(f=>Ne(f,o)),t=Pe(o,i.length),c=e.dies.filter(f=>X(f,t)),u=ze(c,e.wafer,t.ringCount),b=$e(c,e.wafer,t.ringCount),g=t.enableReticlePositionAnalysis?ke(c,e.reticleConfig):[],d=t.enableTestSiteAnalysis===!1?[]:Me(c,t.enableTestSiteAnalysis===!0),h=t.enableAngularAnalysis?ve(c,e.wafer,t.sectorCount):[],m=[];if(t.enableYieldAnalysis&&a&&m.push(...Y(i,u,t.passBins,t),...Y(i,b,t.passBins,t),...Y(i,g,t.passBins,t),...Y(i,d,t.passBins,t),...Y(i,h,t.passBins,t)),t.enableHardBinAnalysis&&a&&m.push(...L(i,u,"hard",e.hbinDefs,"hardBin",t),...L(i,b,"hard",e.hbinDefs,"hardBin",t),...L(i,g,"hard",e.hbinDefs,"hardBin",t),...L(i,d,"hard",e.hbinDefs,"hardBin",t),...L(i,h,"hard",e.hbinDefs,"hardBin",t)),t.enableSoftBinAnalysis){const f=i.filter(y=>y.sbin!==void 0);m.push(...L(f,u,"soft",e.sbinDefs,"softBin",t),...L(f,b,"soft",e.sbinDefs,"softBin",t),...L(f,g,"soft",e.sbinDefs,"softBin",t),...L(f,d,"soft",e.sbinDefs,"softBin",t),...L(f,h,"soft",e.sbinDefs,"softBin",t))}const S=[];let z;if(t.enableTestValueAnalysis){const f=_(i,u,e.testDefs,t),y=_(i,b,e.testDefs,t),v=_(i,g,e.testDefs,t),D=_(i,d,e.testDefs,t),M=_(i,h,e.testDefs,t);m.push(...f.findings,...y.findings,...v.findings,...D.findings,...M.findings),f.warning&&S.push(f.warning),z=f.activeTestNumbers,m.push(...je(i,[u,b,g,d,h],e.testDefs,t)),m.push(...Q(i,u,e.testDefs,t),...Q(i,b,e.testDefs,t),...Q(i,g,e.testDefs,t),...Q(i,d,e.testDefs,t),...Q(i,h,e.testDefs,t))}if(t.enableClusterAnalysis){const f=Ce(n,a,e.testDefs);(!n||a||f!==void 0)&&m.push(...Be(i,e.wafer,{...t,isFailingDie:f}))}const $=We(m,{eligibleDies:i,softEligibleDies:i.filter(f=>f.sbin!==void 0),testDies:i,passBins:t.passBins,ringCount:t.ringCount,sectorCount:t.sectorCount,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs});if(m.length=0,m.push(...$),t.enablePatternClassification&&a){const f=Ve(i,e.wafer,{passBins:t.passBins,ringCount:t.ringCount});if(f!==null&&f.pattern!=="random"&&f.pattern!=="none"){let U=function(B){const N=E(B).map(K=>j(K).ring).filter(K=>K!==void 0);if(N.length===0)return!0;const fe=N.some(K=>K===W),ce=N.some(K=>K===1);return F==="edge-ring"||F==="edge-local"?fe:F==="center"?ce:F==="donut"?!fe&&!ce:!0};const v={center:"Center cluster",donut:"Donut","edge-ring":"Edge-ring","edge-local":"Edge-local",scratch:"Scratch","near-full":"Near-full"}[f.pattern]??f.pattern,D=f.confidence==="high"?"unusual":f.confidence==="medium"?"notable":"info",M=f.features,V=B=>`${(B*100).toFixed(0)}%`,R=f.pattern==="edge-ring"||f.pattern==="edge-local"?`${V(M.edgeRdd)} of edge dies failing`:f.pattern==="center"||f.pattern==="donut"?`centroid at ${V(M.centroidDistNorm)} of wafer radius from centre`:f.pattern==="scratch"?`linear score ${M.linearScore.toFixed(2)}, eccentricity ${M.eccentricity.toFixed(2)}`:`${V(M.globalRdd)} of dies failing`,C=i.filter(B=>{const N=B.hbin??B.sbin;return N!==void 0&&!new Set(t.passBins).has(N)}),P={"edge-ring":["ring","edge-arc"],"edge-local":["edge-arc","sector","quadrant"],center:["ring","cluster"],donut:["ring"],scratch:["cluster","sector","quadrant"],"near-full":["ring"]},A=new Set(P[f.pattern]??[]),F=f.pattern,W=t.ringCount,x=[];for(const B of m){if(!A.has(B.comparison.family)||B.comparison.family==="ring"&&!U(B))continue;x.push(B.id);const N=B.comparison.family==="ring"||B.comparison.family==="quadrant"||B.comparison.family==="sector";D!=="info"&&N&&(B.severity="info")}m.push({id:`spatial-pattern:${f.pattern}`,level:"wafer",severity:D,variable:{kind:"spatialPattern",label:v},comparison:{family:"spatial-pattern",left:v,right:"Wafer"},effect:{direction:"different",effectSize:M.globalRdd},stats:{method:"geometry",sampleSizeLeft:C.length,sampleSizeRight:i.length},summary:`Spatial pattern: ${v.toLowerCase()} (${f.confidence} confidence) \u2014 ${R}${f.note?` [${f.note}]`:""}`,highlight:{kind:"dies",dieKeys:C.map(B=>T(B))},relatedIds:x.length>0?x:void 0})}}m.sort((f,y)=>{const v=f.stats.adjustedPValue??f.stats.pValue??1,D=y.stats.adjustedPValue??y.stats.pValue??1;return v!==D?v-D:Math.abs(y.effect.absoluteDelta??0)-Math.abs(f.effect.absoluteDelta??0)});const p=Ae(e.dies,i.length,e.yield.yieldPercent);n&&(p.isLotStack=!0,l&&(p.aggregationMethod=l));const w=Ke(e.dies,e.testDefs);w&&(p.testSpecYield=w);const k=computeFunctionalYield(e.dies,e.testDefs);if(k&&(p.functionalYield=k),z===void 0&&t.computePerTestStats){const f=we(i,t.testNumbers,e.testDefs);f.warning&&S.push(f.warning),z=f.testNumbers}if(z?.length){const f=Ee(e.dies,z,e.testDefs,t.minimumSampleSize);f&&(p.perTestStats=f)}return S.length>0&&(p.warnings=S),{level:"wafer",hasNotableFindings:m.some(f=>f.severity==="notable"||f.severity==="unusual"),findings:m,wafer:e.wafer.metadata??void 0,stats:p}}
@@ -0,0 +1,49 @@
1
+ import type { Die } from '../core/dies.js';
2
+ import type { ChartDatum } from './yield.js';
3
+ export type BinType = 'hbin' | 'sbin';
4
+ export interface BinItem {
5
+ dies?: Die[];
6
+ /**
7
+ * Precomputed bin counts for this item (e.g. `StatsSummary.stats.hardBinCounts`/
8
+ * `.softBinCounts`, already scoped to the same yield-eligible population).
9
+ * When supplied, used directly instead of re-walking `dies` — avoids
10
+ * re-deriving the same counts `analyzeWaferMap` already computed.
11
+ */
12
+ hardBinCounts?: Record<number, number>;
13
+ softBinCounts?: Record<number, number>;
14
+ }
15
+ /**
16
+ * One bar per bin, across all `items`, sorted by count descending. Excludes
17
+ * partial/edge-excluded dies via `isYieldEligibleDie` — the same population
18
+ * the adjacent Yield panel uses — so the two panels' totals reconcile for
19
+ * the same wafer/group. Items carrying precomputed `hardBinCounts`/
20
+ * `softBinCounts` (see `BinItem`) contribute those directly, skipping the
21
+ * per-die walk for that item.
22
+ */
23
+ export declare function buildBinParetoData(items: BinItem[], binType: BinType): ChartDatum[];
24
+ /** One bin's clustered bars: a count per group (aligned to `BinClusterData.groups`). */
25
+ export interface BinCluster {
26
+ binCode: number;
27
+ label: string;
28
+ /** Total dies of this bin across all groups — drives pareto ordering. */
29
+ total: number;
30
+ /** Per-group die counts for this bin, aligned to `BinClusterData.groups`. */
31
+ counts: number[];
32
+ }
33
+ /** Clustered bin pareto: the group list (legend order) plus one cluster per bin. */
34
+ export interface BinClusterData {
35
+ groups: string[];
36
+ bins: BinCluster[];
37
+ }
38
+ /**
39
+ * Clustered bin pareto: every group's bin counts side by side, so groups
40
+ * compare directly bin-by-bin. Unlike the plain pareto, this is never a
41
+ * restrict-to-one-group view — all groups are always shown at once,
42
+ * matching tsmap's actual behavior (a wholly different panel swapped in for
43
+ * bin pareto when grouping is active, not a variant of it).
44
+ */
45
+ export declare function buildBinClusterData(groups: {
46
+ key: string;
47
+ items: BinItem[];
48
+ }[], binType: BinType): BinClusterData;
49
+ //# sourceMappingURL=binPareto.d.ts.map
@@ -0,0 +1 @@
1
+ import{isYieldEligibleDie as m}from"../core/dies.js";export function buildBinParetoData(l,a){const s=new Map;let i=0;for(const o of l){const r=a==="hbin"?o.hardBinCounts:o.softBinCounts;if(r){for(const[c,t]of Object.entries(r)){const n=Number(c);i+=t,s.set(n,(s.get(n)??0)+t)}continue}for(const c of o.dies??[]){if(!m(c))continue;const t=a==="hbin"?c.hbin:c.sbin;t!==void 0&&(i++,s.set(t,(s.get(t)??0)+1))}}const f=Array.from(s.entries()).map(([o,r])=>({label:`Bin ${o}`,value:r,percent:i>0?r/i*100:0,itemCount:1,binCode:o}));return f.sort((o,r)=>r.value-o.value),f}export function buildBinClusterData(l,a){const s=l.map(t=>t.key),i=new Map,f=[];function o(t,n,u){let e=i.get(t);e||(e={total:0,counts:[]},i.set(t,e),f.push(t)),e.total+=u,e.counts[n]=(e.counts[n]??0)+u}l.forEach((t,n)=>{for(const u of t.items){const e=a==="hbin"?u.hardBinCounts:u.softBinCounts;if(e){for(const[d,b]of Object.entries(e))o(Number(d),n,b);continue}for(const d of u.dies??[]){if(!m(d))continue;const b=a==="hbin"?d.hbin:d.sbin;b!==void 0&&o(b,n,1)}}});const c=f.slice().sort((t,n)=>i.get(n).total-i.get(t).total).map(t=>{const n=i.get(t);return{binCode:t,label:`Bin ${t}`,total:n.total,counts:s.map((u,e)=>n.counts[e]??0)}});return{groups:s,bins:c}}
@@ -0,0 +1,39 @@
1
+ import type { Die } from '../core/dies.js';
2
+ export interface BoxplotDatum {
3
+ label: string;
4
+ min: number;
5
+ q1: number;
6
+ median: number;
7
+ q3: number;
8
+ max: number;
9
+ count: number;
10
+ }
11
+ export interface BoxplotItem {
12
+ label?: string;
13
+ dies?: Die[];
14
+ /**
15
+ * Precomputed per-test five-number summaries for this item (e.g.
16
+ * `StatsSummary.stats.perTestStats`, or one lot wafer's
17
+ * `LotStatsSummary.perWaferTestStats[i].tests`) — already computed once by
18
+ * `analyzeWaferMap`. When the requested `testNumber` is present here,
19
+ * `buildTestBoxplotData` uses it directly instead of re-scanning `dies`.
20
+ */
21
+ testStats?: Array<{
22
+ testNumber: number;
23
+ min: number;
24
+ q1: number;
25
+ median: number;
26
+ q3: number;
27
+ max: number;
28
+ count: number;
29
+ }>;
30
+ }
31
+ /**
32
+ * One row per item, for `testNumber`. Items with no valid values for that
33
+ * test get `count: 0`. Excludes `partial`/`edge-excluded` dies via
34
+ * `isYieldEligibleDie` when falling back to a raw scan — the same
35
+ * population every other per-test computation in this package uses, so a
36
+ * boxplot's stats agree with the summary panel's for the same wafer/test.
37
+ */
38
+ export declare function buildTestBoxplotData(items: BoxplotItem[], testNumber: number): BoxplotDatum[];
39
+ //# sourceMappingURL=boxplot.d.ts.map
@@ -0,0 +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}})}
@@ -0,0 +1,55 @@
1
+ import type { Die } from '../core/dies.js';
2
+ import { type TestDef } from '../renderer/buildWaferMap.js';
3
+ export interface CapabilityDatum {
4
+ testNumber: number;
5
+ label: string;
6
+ unit?: string;
7
+ /**
8
+ * Whether this test has both `limitLow` and `limitHigh` defined. When
9
+ * false, `lsl`/`usl`/`cp`/`cpk`/`pp`/`ppk` are all absent/null — there is
10
+ * no spec to measure capability against — and the five-number summary
11
+ * below is normalized onto the test's own observed [min, max] instead of
12
+ * [lsl, usl].
13
+ */
14
+ hasSpec: boolean;
15
+ lsl?: number;
16
+ usl?: number;
17
+ mean: number;
18
+ /** Sample stddev, ddof=1, pooled across every die across all items. */
19
+ stdOverall: number;
20
+ /** Pooled within-wafer sample stddev. NaN if no item contributed ≥2 values. */
21
+ stdWithin: number;
22
+ n: number;
23
+ /** Cp/Cpk use `stdWithin`; null when stdWithin is NaN or 0, or when `hasSpec` is false. */
24
+ cp: number | null;
25
+ cpk: number | null;
26
+ /** Pp/Ppk use `stdOverall`; null when stdOverall is 0, or when `hasSpec` is false. */
27
+ pp: number | null;
28
+ ppk: number | null;
29
+ /**
30
+ * Five-number summary of all values, normalized: `(v - lsl) / (usl - lsl)`
31
+ * when `hasSpec`, otherwise `(v - dataMin) / (dataMax - dataMin)` (0.5 for
32
+ * every value when the test has zero variance).
33
+ */
34
+ min: number;
35
+ q1: number;
36
+ median: number;
37
+ q3: number;
38
+ max: number;
39
+ }
40
+ export interface CapabilityItem {
41
+ /** One item's dies — typically one wafer, the natural short-term subgroup for Cp/Cpk. */
42
+ dies?: Die[];
43
+ }
44
+ /**
45
+ * Process-capability data for every test in `testDefs` that has at least one
46
+ * recorded value. Tests with both `limitLow` and `limitHigh` defined get the
47
+ * full Cp/Cpk/Pp/Ppk treatment, normalized onto [0,1] via their spec (see
48
+ * `CapabilityDatum.hasSpec`); tests with only one limit or none still appear
49
+ * (`hasSpec: false`), normalized onto their own observed range instead, so a
50
+ * lot where most tests lack full limits doesn't render as an empty chart.
51
+ * Sorted spec'd-first (worst-Ppk-first within that tier), then unspec'd
52
+ * (most-variable-first within that tier) — see the sort at the bottom.
53
+ */
54
+ export declare function buildCapabilityData(items: CapabilityItem[], testDefs: TestDef[]): CapabilityDatum[];
55
+ //# sourceMappingURL=capability.d.ts.map
@@ -0,0 +1 @@
1
+ import{isYieldEligibleDie as z}from"../core/dies.js";import{isParametricTest as A}from"../renderer/buildWaferMap.js";import{quantile as b}from"./math.js";export function buildCapabilityData(T,q){const k=new Map,N=new Map;for(const t of q){const n=t.testNumber;if(n===void 0||!A(t))continue;N.set(n,t);const o=t.limitLow,e=t.limitHigh;o!==void 0&&e!==void 0&&e>o&&k.set(n,{lsl:o,usl:e})}if(N.size===0)return[];const M=new Map,g=t=>{let n=M.get(t);return n||(n={n:0,sum:0,sumSq:0,withinNumerator:0,withinDenominator:0,allValues:[]},M.set(t,n)),n};for(const t of T){const n=new Map;for(const o of t.dies??[]){if(!z(o))continue;const e=o.testValues;if(e)for(const f of Object.keys(e)){const u=Number(f);if(!N.has(u))continue;const i=e[u];if(i===void 0||!Number.isFinite(i))continue;const a=g(u);a.n++,a.sum+=i,a.sumSq+=i*i,a.allValues.push(i);let s=n.get(u);s||(s={wn:0,wsum:0,wsumSq:0},n.set(u,s)),s.wn++,s.wsum+=i,s.wsumSq+=i*i}}for(const[o,e]of n){if(e.wn<2)continue;const f=g(o),u=e.wsum/e.wn,i=(e.wsumSq-e.wn*u*u)/(e.wn-1);f.withinNumerator+=(e.wn-1)*Math.max(0,i),f.withinDenominator+=e.wn-1}}const S=[];for(const t of q){const n=t.testNumber;if(n===void 0)continue;const o=M.get(n);if(!o||o.n===0)continue;const{n:e,sum:f,sumSq:u,withinNumerator:i,withinDenominator:a,allValues:s}=o;s.sort((l,h)=>l-h);const r=f/e,B=e>=2?Math.max(0,(u-e*r*r)/(e-1)):0,p=Math.sqrt(B),c=a>0?Math.sqrt(i/a):NaN,v=k.get(n),x=v!==void 0;let d,w,y=null,O=null,V=null,D=null,m;if(x){({lsl:d,usl:w}=v);const l=w-d;V=p>0?l/(6*p):null,D=p>0?Math.min((w-r)/(3*p),(r-d)/(3*p)):null,y=Number.isFinite(c)&&c>0?l/(6*c):null,O=Number.isFinite(c)&&c>0?Math.min((w-r)/(3*c),(r-d)/(3*c)):null,m=h=>(h-d)/l}else{const l=s[0],F=s[s.length-1]-l;m=F>0?j=>(j-l)/F:()=>.5}S.push({testNumber:n,label:t.name??`Test ${n}`,unit:t.unit,hasSpec:x,lsl:d,usl:w,mean:r,stdOverall:p,stdWithin:c,n:e,cp:y,cpk:O,pp:V,ppk:D,min:m(s[0]),q1:m(b(s,.25)),median:m(b(s,.5)),q3:m(b(s,.75)),max:m(s[s.length-1])})}return S.sort((t,n)=>t.hasSpec!==n.hasSpec?t.hasSpec?-1:1:t.hasSpec?t.ppk===null&&n.ppk===null?0:t.ppk===null?1:n.ppk===null?-1:t.ppk-n.ppk:n.stdOverall-t.stdOverall),S}
@@ -0,0 +1,18 @@
1
+ import type { Die, Wafer } from '../core/index.js';
2
+ import type { StatsFinding } from './types.js';
3
+ interface ClusterOptions {
4
+ passBins: number[];
5
+ significanceLevel: number;
6
+ minimumEffectSize: number;
7
+ minimumRelativeEffect: number;
8
+ minimumSampleSize: number;
9
+ minimumClusterSize: number;
10
+ ringCount: number;
11
+ includePartial: boolean;
12
+ includeEdgeExcluded: boolean;
13
+ /** Optional override for the failure predicate. Default: hard bin, then soft bin, not in passBins. */
14
+ isFailingDie?: (die: Die) => boolean;
15
+ }
16
+ export declare function buildClusterFindings(dies: Die[], wafer: Wafer, options: ClusterOptions): StatsFinding[];
17
+ export {};
18
+ //# sourceMappingURL=clusterDetection.d.ts.map
@@ -0,0 +1 @@
1
+ import{normalCdf as rt}from"./math.js";import{getDieKey as $}from"../core/dies.js";const ft=["E","ENE","NE","NNE","N","NNW","NW","WNW","W","WSW","SW","SSW","S","SSE","SE","ESE"];function ut(n,o){const i=(Math.atan2(o,n)+2*Math.PI)%(2*Math.PI);return ft[Math.round(i/(2*Math.PI)*16)%16]}function lt(n,o,i,d){const g=d!==void 0?Math.abs(d):0;return n<=.01&&(o>=.25||g>=2||i>=.1)?"unusual":n<=.05&&(o>=.15||g>=1||i>=.03)?"notable":"info"}function ht(n,o,i){if(o===0||i<=0)return n>0?0:1;if(i>=1)return n<=o?0:1;const d=o*i,g=Math.sqrt(o*i*(1-i));if(g===0)return n>d?0:1;const x=(n-.5-d)/g;return 1-rt(x)}export function buildClusterFindings(n,o,i){const{passBins:d,significanceLevel:g,minimumEffectSize:x,minimumRelativeEffect:G,minimumSampleSize:I,minimumClusterSize:H,ringCount:J}=i,Q=new Set(d);if(n.length<I)return[];const U=t=>{const c=t.hbin??t.sbin;return c===void 0||!Q.has(c)},Z=i.isFailingDie??U,E=n.filter(Z),y=E.length/n.length,M=n[0],B=M?M.width:10,K=M?M.height:10,F=Math.max(B,K)*1.5,R=new Map;for(const t of E)R.set($(t),t);const P=new Map;for(const t of n)P.set($(t),t);const A=Math.ceil(F/B),k=Math.ceil(F/K),b=new Set,V=[];for(const t of E){const c=$(t);if(b.has(c))continue;const r=[],u=[t];for(b.add(c);u.length>0;){const s=u.pop();r.push(s);for(let l=-1;l<=1;l++)for(let h=-1;h<=1;h++){if(h===0&&l===0)continue;const f=`${s.x+h},${s.y+l}`;if(b.has(f))continue;const p=R.get(f);p&&(b.add(f),u.push(p))}}V.push(r)}const L=[],v=o.center.x,Y=o.center.y,tt=o.radius;for(const t of V){if(t.length<H)continue;const c=new Set(t.map(e=>$(e))),r=new Set(c);for(const e of t)for(let a=-k;a<=k;a++)for(let N=-A;N<=A;N++){const W=`${e.x+N},${e.y+a}`;if(r.has(W))continue;const X=P.get(W);X&&Math.hypot(X.physX-e.physX,X.physY-e.physY)<=F&&r.add(W)}const u=[...r].map(e=>P.get(e)).filter(Boolean),s=t.length,l=u.length;if(l<I)continue;const h=s/l,f=h-y,p=ht(s,l,y),S=y>0?f/y:void 0,et=f>=x||S!==void 0&&S>=G;if(p>g||!et)continue;const q=t.reduce((e,a)=>e+a.physX,0)/s,j=t.reduce((e,a)=>e+a.physY,0)/s;let m=t[0],O=1/0;for(const e of t){const a=Math.hypot(e.physX-q,e.physY-j);a<O&&(O=a,m=e)}const w=t.map(e=>(Math.atan2(e.physY-Y,e.physX-v)+2*Math.PI)%(2*Math.PI));let z=w[0],C=w[0];for(const e of w)z=Math.min(z,e),C=Math.max(C,e);const nt=(C-z)*180/Math.PI,st=1-1/J,D=t.filter(e=>Math.hypot(e.physX-v,e.physY-Y)/tt>st).length>s/2&&nt<120,T=D?"edge-arc":"cluster",_=ut(q-v,j-Y),ot=D?`Edge arc ~${_}`:`Cluster at (${m.x}, ${m.y})`,it=s/n.length,ct=lt(p,f,it,S),at=[...c];L.push({id:`${T}:${m.x},${m.y}`,level:"wafer",severity:ct,variable:{kind:"yield",label:"Yield"},comparison:{family:T,left:ot,right:"Rest of wafer"},effect:{direction:"lower",absoluteDelta:-f,relativeDelta:S!==void 0?-S:void 0,effectSize:f},stats:{method:"binomial",pValue:p,adjustedPValue:p,sampleSizeLeft:s,sampleSizeRight:n.length-s},summary:D?`Edge arc near ${_}: ${s} contiguous failing dies (${(h*100).toFixed(0)}% vs ${(y*100).toFixed(0)}% background)`:`Failure cluster at (${m.x}, ${m.y}): ${s} contiguous failing dies (${(h*100).toFixed(0)}% vs ${(y*100).toFixed(0)}% background)`,highlight:{kind:"dies",dieKeys:at}})}return L.sort((t,c)=>{const r=t.stats.pValue??1,u=c.stats.pValue??1;return r!==u?r-u:Math.abs(c.effect.effectSize??0)-Math.abs(t.effect.effectSize??0)})}
@@ -0,0 +1,55 @@
1
+ import type { Die } from '../core/dies.js';
2
+ import { type TestDef } from '../renderer/buildWaferMap.js';
3
+ /** Minimal per-test identity carried on a correlation matrix's axes. */
4
+ export interface CorrelationTestInfo {
5
+ testNumber: number;
6
+ label: string;
7
+ unit?: string;
8
+ }
9
+ export interface CorrelationCell {
10
+ xIndex: number;
11
+ yIndex: number;
12
+ r: number | null;
13
+ }
14
+ export interface CorrelationMatrix {
15
+ tests: CorrelationTestInfo[];
16
+ cells: CorrelationCell[];
17
+ }
18
+ /**
19
+ * Pearson correlation matrix for every test in `testDefs` across every die
20
+ * in `dies`.
21
+ *
22
+ * Running-accumulator algorithm: one die-walk, 6 Float64 accumulators per
23
+ * upper-triangle pair (count, sumX, sumY, sumXX, sumYY, sumXY). No pair
24
+ * arrays stored — O(N²) memory, O(N×D + N²) time.
25
+ */
26
+ export declare function buildCorrelationMatrix(dies: Die[], testDefs: TestDef[]): CorrelationMatrix;
27
+ export interface CorrelationSummary {
28
+ /** Filtered matrix — tests trimmed to [minTests, maxTests] by significance. */
29
+ matrix: CorrelationMatrix;
30
+ /** Upper-triangle pairs with |r| ≥ 0.7 (across the full input matrix). */
31
+ strongPairs: number;
32
+ /** Upper-triangle pairs with 0.4 ≤ |r| < 0.7 (across the full input matrix). */
33
+ moderatePairs: number;
34
+ /** Upper-triangle pairs with |r| < threshold that were excluded from display. */
35
+ hiddenWeakPairs: number;
36
+ /** Strongest pair by |r|, or null if no non-null off-diagonal cells exist. */
37
+ strongestPair: {
38
+ xLabel: string;
39
+ yLabel: string;
40
+ r: number;
41
+ } | null;
42
+ }
43
+ /**
44
+ * Filter a correlation matrix to tests involved in significant pairs, clamped to
45
+ * [minTests, maxTests]. Pairs are ranked by |r|; the threshold gates which pairs
46
+ * count as "significant" for display selection, but all pair counts are computed
47
+ * over the full input matrix for the summary line. Original test-number order is
48
+ * preserved so matrix axes stay sorted.
49
+ */
50
+ export declare function filterCorrelationMatrix(matrix: CorrelationMatrix, { threshold, minTests, maxTests }?: {
51
+ threshold?: number;
52
+ minTests?: number;
53
+ maxTests?: number;
54
+ }): CorrelationSummary;
55
+ //# sourceMappingURL=correlation.d.ts.map
@@ -0,0 +1 @@
1
+ import{isParametricTest as A}from"../renderer/buildWaferMap.js";function F(i){return i.filter(c=>c.testNumber!==void 0&&A(c)).map(c=>({testNumber:c.testNumber,label:c.name,unit:c.unit}))}export function buildCorrelationMatrix(i,c){const u=F(c);if(u.length<2)return{tests:u,cells:[]};const l=u.length,y=u.map(t=>t.testNumber),f=l*(l-1)/2,b=new Float64Array(f),x=new Float64Array(f),r=new Float64Array(f),I=new Float64Array(f),p=new Float64Array(f),g=new Float64Array(f);function w(t,e){return t*l-(t*(t+1)>>1)+(e-t-1)}for(const t of i){if(!t.testValues)continue;const e=new Float64Array(l),o=new Uint8Array(l);for(let n=0;n<l;n++){const s=t.testValues[y[n]];s!==void 0&&Number.isFinite(s)&&(e[n]=s,o[n]=1)}for(let n=0;n<l;n++){if(!o[n])continue;const s=e[n];for(let a=n+1;a<l;a++){if(!o[a])continue;const d=e[a],m=w(n,a);b[m]++,x[m]+=s,r[m]+=d,I[m]+=s*s,p[m]+=d*d,g[m]+=s*d}}}function M(t){const e=b[t];if(e<3)return null;const o=x[t]/e,n=r[t]/e,s=g[t]/e-o*n,a=I[t]/e-o*o,d=p[t]/e-n*n,m=Math.sqrt(a*d);return m===0?null:Math.max(-1,Math.min(1,s/m))}const h=[];for(let t=0;t<l;t++)for(let e=0;e<l;e++){if(e===t){h.push({xIndex:e,yIndex:t,r:1});continue}const o=Math.min(e,t),n=Math.max(e,t),s=M(w(o,n));h.push({xIndex:e,yIndex:t,r:s})}return{tests:u,cells:h}}export function filterCorrelationMatrix(i,{threshold:c=.3,minTests:u=6,maxTests:l=20}={}){const y=[];let f=0,b=0,x=null;for(const t of i.cells){if(t.xIndex>=t.yIndex||t.r===null)continue;const e=Math.abs(t.r);y.push({xi:t.xIndex,yi:t.yIndex,absR:e}),(x===null||e>Math.abs(x.r))&&(x={xLabel:i.tests[t.xIndex].label,yLabel:i.tests[t.yIndex].label,r:t.r})}y.sort((t,e)=>e.absR-t.absR);const r=new Set;for(const{xi:t,yi:e,absR:o}of y){const n=o<c;r.size>=l||n&&r.size>=u||(r.add(t),r.size<l&&r.add(e))}if(r.size<Math.min(u,i.tests.length))for(let t=0;t<i.tests.length&&r.size<Math.min(l,u,i.tests.length);t++)r.add(t);const I=(()=>{const t=Array.from(r),e=new Map,o=new Map;for(const{xi:s,yi:a,absR:d}of y)!r.has(s)||!r.has(a)||(e.set(s,(e.get(s)??0)+d),e.set(a,(e.get(a)??0)+d),o.set(s,(o.get(s)??0)+1),o.set(a,(o.get(a)??0)+1));const n=s=>(o.get(s)??0)>0?e.get(s)/o.get(s):0;return t.sort((s,a)=>n(a)-n(s))})(),p=I.map(t=>i.tests[t]),g=new Map(I.map((t,e)=>[t,e])),w=new Set(p.map(t=>t.testNumber)),M=i.cells.filter(t=>w.has(i.tests[t.xIndex].testNumber)&&w.has(i.tests[t.yIndex].testNumber)).map(t=>({xIndex:g.get(t.xIndex),yIndex:g.get(t.yIndex),r:t.r}));let h=0;for(const{xi:t,yi:e,absR:o}of y)r.has(t)&&r.has(e)?o>=.7?f++:o>=.4&&b++:o<c&&h++;return{matrix:{tests:p,cells:M},strongPairs:f,moderatePairs:b,hiddenWeakPairs:h,strongestPair:x}}
@@ -0,0 +1,74 @@
1
+ import type { WaferMetadata } from '../core/metadata.js';
2
+ /** Bucket label for wafers with no value for the active facet field — kept
3
+ * visible (not dropped) so grouped output stays honest about what it's missing. */
4
+ export declare const FACET_NONE_VALUE = "(none)";
5
+ /**
6
+ * camelCase/snake_case key → "Title Case" label — the one label convention
7
+ * every metadata surface in this library uses for an uncurated field
8
+ * (`DEFAULT_FACET_CURATION`'s own `label`s, e.g. "Program", intentionally
9
+ * differ and are reserved for facet/group-by UI, not general display — see
10
+ * `buildFacetTable`'s callers in canvas-adapter). Shared by the canvas
11
+ * (Summary panel, metadata badge) and stats (HTML report) layers so a field
12
+ * is never labelled two different ways depending which surface rendered it.
13
+ */
14
+ export { prettyKey } from '../core/utils.js';
15
+ /** One distinct value of a facet field, with how much data it covers. */
16
+ export interface FacetValue {
17
+ value: string;
18
+ waferCount: number;
19
+ dieCount: number;
20
+ }
21
+ /** A metadata field that wafers can be grouped/split on, plus its distinct values. */
22
+ export interface FacetField {
23
+ /** WaferMetadata key (e.g. `lot`, `temperature`, or a caller-supplied extra key). */
24
+ key: string;
25
+ /** Human-readable label — curated, or the raw key for uncurated fields. */
26
+ label: string;
27
+ /** Distinct non-empty values, sorted by coverage (wafer count desc, then label). */
28
+ values: FacetValue[];
29
+ /** True when the field has more than one distinct value — i.e. splitting on it actually partitions the data. */
30
+ splittable: boolean;
31
+ }
32
+ /** Curation for one metadata key: label + whether it's offered as a facet by default + date handling. */
33
+ export interface FacetCuration {
34
+ label: string;
35
+ /** Show this field in the facet table by default. Defaults to true when omitted. */
36
+ facet?: boolean;
37
+ /** Value is an ISO datetime; facet by its date portion only, not full timestamp. */
38
+ date?: boolean;
39
+ }
40
+ /** Default curation for wmap's own known `WaferMetadata` keys. Callers extend
41
+ * (not replace) this via `buildFacetTable`'s `curation` option — their own
42
+ * app-specific extra keys layer on top rather than needing to redeclare these. */
43
+ export declare const DEFAULT_FACET_CURATION: Record<string, FacetCuration>;
44
+ /** The faceting value of one metadata key for one wafer's metadata — the raw
45
+ * value, except date-curated fields are truncated to date-only so grouping
46
+ * is by day, not timestamp. */
47
+ export declare function facetValueOf(metadata: WaferMetadata | undefined, key: string, curation?: Record<string, FacetCuration>): string | undefined;
48
+ export interface FacetItem {
49
+ metadata?: WaferMetadata;
50
+ /** Die count for this wafer, if known — used for `FacetValue.dieCount` coverage. Omit if not tracked; defaults to 0. */
51
+ dieCount?: number;
52
+ }
53
+ /** Fields present with the same value on every item's `metadata` — the
54
+ * conservative lot identity a mixed population can safely display. A field
55
+ * that varies per item (e.g. `waferId`) is naturally excluded, since it
56
+ * won't be common across all items. Returns `{}` for an empty list (no
57
+ * false lot-level claim about zero wafers). */
58
+ export declare function commonMetadata(items: Array<{
59
+ metadata?: WaferMetadata;
60
+ }>): WaferMetadata;
61
+ export interface BuildFacetTableOptions {
62
+ /** Extra/override curation entries layered on top of `DEFAULT_FACET_CURATION` — a caller's own app-specific keys (or a relabel of a default one) without losing wmap's built-in defaults. */
63
+ curation?: Record<string, FacetCuration>;
64
+ /** Restrict to fields curated `facet: true` (the default) plus any uncurated key. Pass `false` to include curated-but-`facet:false` fields too. */
65
+ facetableOnly?: boolean;
66
+ }
67
+ /**
68
+ * Build the distinct-values table over a set of wafers' metadata. One entry
69
+ * per key present on at least one item's `metadata`; curated keys appear
70
+ * first in `DEFAULT_FACET_CURATION`/`curation` order, uncurated keys after
71
+ * (labelled by their raw key). Counts are exact over the full input.
72
+ */
73
+ export declare function buildFacetTable(items: FacetItem[], options?: BuildFacetTableOptions): FacetField[];
74
+ //# sourceMappingURL=facets.d.ts.map
@@ -0,0 +1 @@
1
+ import{compareNatural as m}from"../core/utils.js";export const FACET_NONE_VALUE="(none)";export{prettyKey}from"../core/utils.js";export const DEFAULT_FACET_CURATION={split:{label:"Split"},lot:{label:"Lot"},product:{label:"Product"},testProgram:{label:"Program"},temperature:{label:"Temperature"},testDate:{label:"Test date",date:!0},operator:{label:"Operator"},waferId:{label:"Wafer",facet:!1}};function C(e){const n=e.indexOf("T");return n>0?e.slice(0,n):e}export function facetValueOf(e,n,r=DEFAULT_FACET_CURATION){const a=e?.[n];if(a==null||a==="")return;const t=String(a);return r[n]?.date?C(t):t}export function commonMetadata(e){if(!e.length)return{};const n=e[0].metadata??{},r={};for(const[a,t]of Object.entries(n))t==null||t===""||e.every(d=>(d.metadata??{})[a]===t)&&(r[a]=t);return r}export function buildFacetTable(e,n={}){const r={...DEFAULT_FACET_CURATION,...n.curation},a=n.facetableOnly??!0,t=new Set;for(const o of e)for(const f of Object.keys(o.metadata??{}))t.add(f);const b=[...Object.keys(r).filter(o=>t.has(o)),...[...t].filter(o=>!(o in r))],p=[];for(const o of b){const f=r[o];if(a&&f&&f.facet===!1)continue;const u=new Map;for(const c of e){const l=facetValueOf(c.metadata,o,r)??FACET_NONE_VALUE,i=u.get(l)??{waferCount:0,dieCount:0};i.waferCount+=1,i.dieCount+=c.dieCount??0,u.set(l,i)}if(u.size===0)continue;const s=Array.from(u,([c,l])=>({value:c,waferCount:l.waferCount,dieCount:l.dieCount}));s.sort((c,l)=>(c.value===FACET_NONE_VALUE?1:0)-(l.value===FACET_NONE_VALUE?1:0)||l.waferCount-c.waferCount||m(c.value,l.value)),p.push({key:o,label:f?.label??o,values:s,splittable:s.length>1})}return p}
@@ -0,0 +1,9 @@
1
+ import type { StatsFinding, StatsSummary, LotStatsSummary, StatsSeverity, StatsVariableKind, StatsComparisonFamily, StatsLevel } from './types.js';
2
+ export interface FindingsFilter {
3
+ severity?: StatsSeverity | StatsSeverity[];
4
+ kind?: StatsVariableKind | StatsVariableKind[];
5
+ family?: StatsComparisonFamily | StatsComparisonFamily[];
6
+ level?: StatsLevel | StatsLevel[];
7
+ }
8
+ export declare function filterFindings(source: StatsSummary | LotStatsSummary, filter: FindingsFilter): StatsFinding[];
9
+ //# sourceMappingURL=filterFindings.d.ts.map
@@ -0,0 +1 @@
1
+ function t(s){return Array.isArray(s)?s:[s]}export function filterFindings(s,n){let e=s.findings;if(n.severity!==void 0){const i=new Set(t(n.severity));e=e.filter(d=>i.has(d.severity))}if(n.kind!==void 0){const i=new Set(t(n.kind));e=e.filter(d=>i.has(d.variable.kind))}if(n.family!==void 0){const i=new Set(t(n.family));e=e.filter(d=>i.has(d.comparison.family))}if(n.level!==void 0){const i=new Set(t(n.level));e=e.filter(d=>i.has(d.level))}return e}
@@ -0,0 +1,3 @@
1
+ import type { StatsFinding } from './types.js';
2
+ export declare function buildFindingsNarrative(findings: StatsFinding[]): string;
3
+ //# sourceMappingURL=findingsNarrative.d.ts.map
@@ -0,0 +1 @@
1
+ const _=["spatial-pattern","ring","sector","quadrant","cluster","edge-arc","reticle-position"],y={unusual:0,notable:1,info:2},S={E:0,ENE:22.5,NE:45,NNE:67.5,N:90,NNW:112.5,NW:135,WNW:157.5,W:180,WSW:202.5,SW:225,SSW:247.5,S:270,SSE:292.5,SE:315,ESE:337.5};function m(e){return e.stats.adjustedPValue??e.stats.pValue??1}function b(e){return[...e].sort((n,t)=>{const o=y[n.severity]-y[t.severity];return o!==0?o:m(n)-m(t)})[0]}function w(e){return e.some(t=>t.variable.kind==="yield")?e.filter(t=>t.variable.kind!=="hardBin"&&t.variable.kind!=="softBin"):e}function $(e){return e.replace(/^(Rings?|Quadrants?|Sectors?)\s+/i,"").split(/[,&]|–|\band\b/).map(n=>n.trim()).filter(Boolean)}function W(e,n){const t=new Set($(e));return $(n).some(o=>t.has(o))}function v(e){return e.variable.kind==="yield"?"yield":e.variable.kind==="hardBin"?`HBin ${e.variable.bin??e.variable.index??"?"}`:e.variable.kind==="softBin"?`SBin ${e.variable.bin??e.variable.index??"?"}`:e.variable.label}function h(e){let n=0,t=0;for(const o of e)o.effect.direction==="higher"?n++:o.effect.direction==="lower"&&t++;return n>t?"higher":t>n?"lower":"mixed"}function d(e){return e==="higher"?"elevated":e==="lower"?"reduced":"shifted"}function p(e){const n=new Set,t=[];for(const o of e){const r=v(o);n.has(r)||(n.add(r),t.push(r))}return t.length===1?t[0]:t.length===2?`${t[0]} and ${t[1]}`:t.length===3?`${t[0]}, ${t[1]}, and ${t[2]}`:`${t[0]}, ${t[1]}, and ${t.length-2} more`}function E(e){const n=new Set,t=[];for(const o of e){const r=o.comparison.left;n.has(r)||(n.add(r),t.push(r))}return t.length===1?t[0]:t.length===2?`${t[0]} and ${t[1]}`:`${t.slice(0,-1).join(", ")}, and ${t[t.length-1]}`}function j(e){return e.length!==1?!1:!/^(Rings|Quadrants|Sectors)\b/.test(e[0].comparison.left)}function A(e){const n=h(e),t=e.every(c=>c.comparison.left.includes("(edge)")),o=e.every(c=>c.comparison.left.includes("(core)"));let r,a;return o?(r="The core ring",a=!0):t&&e.length>1?(r="The edge rings",a=!1):(r=E(e),a=j(e)),`${r} show${a?"s":""} ${d(n)} ${p(e)}.`}function B(e){const n=h(e),t=s=>s.highlight.kind==="region"&&s.highlight.regionKeys.length>1||s.comparison.left.startsWith("Quadrants"),o=e.filter(s=>t(s)),r=e.filter(s=>!t(s)),a=[];r.length===1?a.push(`the ${r[0].comparison.left} quadrant`):r.length>1&&a.push(`the ${E(r)} quadrants`);for(const s of o)a.push(s.comparison.left.replace(/^Quadrants/,"quadrants"));const c=a.join(" and "),u=r.length===1&&o.length===0;return`${c.charAt(0).toUpperCase()+c.slice(1)} show${u?"s":""} ${d(n)} ${p(e)}.`}function P(e){const n=h(e),t=[],o=new Set;for(const u of e){const i=u.comparison.left.replace(/^Sectors? /,"");o.has(i)||(o.add(i),t.push(i))}let r,a;return t.length===1?(r=`the ${t[0]} ${e[0].comparison.left.startsWith("Sectors")?"sectors":"region"}`,a=!e[0].comparison.left.startsWith("Sectors")):(r=`the ${t.length===2?t.join(" and "):`${t.slice(0,-1).join(", ")}, and ${t[t.length-1]}`} sectors`,a=!1),`${r.charAt(0).toUpperCase()+r.slice(1)} show${a?"s":""} ${d(n)} ${p(e)}.`}function q(e){if(e.length===1)return`A failure cluster at ${e[0].comparison.left.replace(/^Cluster at /,"")}.`;const t=e.reduce((o,r)=>(o.stats.sampleSizeLeft??0)>=(r.stats.sampleSizeLeft??0)?o:r).comparison.left.replace(/^Cluster at /,"");return`${e.length} failure clusters identified; the largest at ${t}.`}function x(e){const n=e[0].comparison.left.replace(/^Edge arc ~/,"");return e.length===1?`An edge arc near ${n} shows localised failures.`:`${e.length} edge arcs detected, including near ${n}.`}function C(e){const n=h(e);return e.length===1?`Reticle-position variation: ${e[0].comparison.left} shows ${d(n)} ${p(e)}.`:`Reticle-position variation across ${e.length} cells (${d(n)} ${p(e)}).`}function Y(e,n){const t=e.stats.method==="geometry"?` (${e.severity==="unusual"?"high":e.severity==="notable"?"medium":"low"} confidence)`:"",o=new Set(e.relatedIds??[]),r=n.find(c=>o.has(c.id)&&(c.comparison.family==="edge-arc"||c.comparison.family==="cluster"));let a="";return r&&(r.comparison.family==="edge-arc"?a=`; failures concentrated at the wafer edge near ${r.comparison.left.replace(/^Edge arc ~/,"")}`:a=`; failures concentrated near ${r.comparison.left.replace(/^Cluster at /,"")}`),`${e.comparison.left} failure pattern detected${t}${a}.`}function F(e){const n=e.replace(/\.$/,"");return n.charAt(0).toLowerCase()+n.slice(1)}function M(e){const n=e.filter(t=>t.variable.kind==="yield");return n.length>0?h(n):h(e)}function k(e,n){const t=new Map;for(const i of n){const s=t.get(i.comparison.left)??[];s.push(i),t.set(i.comparison.left,s)}const o=[],r=[];for(const i of t.values()){const s=M(i);s==="higher"?o.push(...i.filter(l=>l.effect.direction==="higher")):s==="lower"?r.push(...i.filter(l=>l.effect.direction==="lower")):o.push(...i)}const a=o.map(i=>i.comparison.left),c=r.filter(i=>!a.some(s=>W(s,i.comparison.left)));if(o.length>0&&c.length>0){const i=n.some(f=>f.variable.kind==="yield"),[s,l]=i?[c,o]:[o,c];return`${e(s).replace(/\.$/,"")} while ${F(e(l))}.`}if(o.length>0&&r.length>0&&c.length===0)return e(o);const u=M(n);if(u!=="mixed"&&n.some(i=>i.variable.kind==="yield")){const i=n.filter(s=>s.effect.direction===u);if(i.length>0)return e(i)}return e(n)}function N(e){let n=0,t=0,o=0;for(const r of e)for(const a of $(r.comparison.left)){const c=S[a];if(c===void 0)continue;const u=c*Math.PI/180;n+=Math.cos(u),t+=Math.sin(u),o++}return o===0||n===0&&t===0?null:(Math.atan2(t,n)*180/Math.PI+360)%360}function I(e){let n="E",t=360;for(const[o,r]of Object.entries(S)){const a=Math.abs((e-r+540)%360-180);a<t&&(t=a,n=o)}return n}function O(e){const n=e.filter(u=>u.effect.direction==="higher"),t=e.filter(u=>u.effect.direction==="lower");if(n.length===0||t.length===0)return null;const o=new Set(e.map(v));if(o.size!==1)return null;const r=N(n),a=N(t);return r===null||a===null||Math.abs((r-a+540)%360-180)<120?null:`${[...o][0]} increases from ${I(a)} toward ${I(r)} across the wafer.`}function T(e){const n=w(e);if(n.length===0)return"";const t=["sector","quadrant","ring"],o=new Map;for(const i of n){const s=o.get(i.comparison.family)??[];s.push(i),o.set(i.comparison.family,s)}const r=[...o.keys()].sort((i,s)=>{const l=Math.min(...o.get(i).map(m)),f=Math.min(...o.get(s).map(m));return l!==f?l-f:t.indexOf(i)-t.indexOf(s)})[0],a=o.get(r),c=O(a);return c||k(r==="ring"?A:r==="quadrant"?B:P,a)}function L(e,n){switch(e){case"cluster":return q(n);case"edge-arc":return x(n);case"reticle-position":return k(C,n);default:return""}}const R=new Set(["ring","quadrant","sector"]);export function buildFindingsNarrative(e){if(e.length===0)return"";const n=e.filter(s=>s.severity!=="info");if(n.length===0)return"Minor spatial variation detected; no strongly significant patterns.";const t=[],o=n.filter(s=>s.comparison.family==="spatial-pattern"),r=o.length?b(o):null,a=new Set(r?.relatedIds??[]);r&&t.push(Y(r,n));const c=n.filter(s=>s!==r&&s.comparison.family!=="spatial-pattern"&&!a.has(s.id));let u=c.filter(s=>R.has(s.comparison.family));if(r&&u.length){const s=w(u);s.length>0&&s.every(f=>f.variable.kind==="yield"&&f.effect.direction==="higher")&&(u=[])}const i=new Set;if(u.length){const s=T(u);if(s){t.push(s);for(const l of R)i.add(l)}}else if(!r){const s=b(c);if(s){const l=s.comparison.family,f=L(l,c.filter(g=>g.comparison.family===l));f&&(t.push(f),i.add(l))}}if(t.length<3){const s=c.filter(l=>(l.comparison.family==="cluster"||l.comparison.family==="edge-arc")&&!i.has(l.comparison.family));if(s.length){const l=s[0].comparison.family,f=L(l,s.filter(g=>g.comparison.family===l));f&&t.push(f)}}return t.slice(0,3).join(" ")}
@@ -0,0 +1,42 @@
1
+ import type { Die } from '../core/dies.js';
2
+ export interface HistogramBucket {
3
+ rangeLow: number;
4
+ rangeHigh: number;
5
+ count: number;
6
+ }
7
+ export interface HistogramItem {
8
+ label?: string;
9
+ dies?: Die[];
10
+ }
11
+ /** One overlaid series in a faceted histogram — per-group counts over the shared bucket ranges. */
12
+ export interface HistogramSeries {
13
+ groupKey: string;
14
+ /** Count per bucket, aligned to the shared `ranges` array. */
15
+ counts: number[];
16
+ }
17
+ /** Faceted histogram: shared bucket ranges plus one count-series per group. */
18
+ export interface HistogramSeriesData {
19
+ ranges: Array<{
20
+ rangeLow: number;
21
+ rangeHigh: number;
22
+ }>;
23
+ series: HistogramSeries[];
24
+ }
25
+ /**
26
+ * Histogram of one test's values across `items`, divided into `bucketCount`
27
+ * equal-width buckets. If `limitLow`/`limitHigh` are given, the axis range
28
+ * is expanded to include them so limit lines always draw.
29
+ */
30
+ export declare function buildTestHistogramData(items: HistogramItem[], testNumber: number, bucketCount?: number, limitLow?: number, limitHigh?: number): HistogramBucket[];
31
+ /**
32
+ * Faceted histogram: one count-series per group over a *shared* set of
33
+ * buckets, so the series overlay and compare directly. The bucket range
34
+ * spans every group's dies (and the limits when given) so all series align
35
+ * on one axis. Groups are returned in the order given; empty groups (no
36
+ * valid values for this test) are omitted.
37
+ */
38
+ export declare function buildTestHistogramSeries(groups: {
39
+ key: string;
40
+ items: HistogramItem[];
41
+ }[], testNumber: number, bucketCount?: number, limitLow?: number, limitHigh?: number): HistogramSeriesData;
42
+ //# sourceMappingURL=histogram.d.ts.map
@@ -0,0 +1 @@
1
+ export function buildTestHistogramData(g,M,a=16,m,h){const n=[];for(const e of g)for(const i of e.dies??[]){const t=i.testValues?.[M];t!==void 0&&Number.isFinite(t)&&n.push(t)}if(n.length===0)return[];let s=n[0],o=n[0];for(let e=1;e<n.length;e++)n[e]<s&&(s=n[e]),n[e]>o&&(o=n[e]);const f=m!==void 0?Math.min(s,m):s,u=((h!==void 0?Math.max(o,h):o)-f||1)/a,d=Array.from({length:a},(e,i)=>({rangeLow:f+i*u,rangeHigh:f+(i+1)*u,count:0}));for(const e of n){const i=Math.min(a-1,Math.floor((e-f)/u));d[i].count++}return d}export function buildTestHistogramSeries(g,M,a=16,m,h){const n=new Map;let s=1/0,o=-1/0;for(const t of g){const r=[];for(const x of t.items)for(const p of x.dies??[]){const c=p.testValues?.[M];c!==void 0&&Number.isFinite(c)&&(r.push(c),c<s&&(s=c),c>o&&(o=c))}n.set(t.key,r)}const f=g.map(t=>t.key).filter(t=>n.get(t).length>0);if(f.length===0)return{ranges:[],series:[]};const l=m!==void 0?Math.min(s,m):s,d=((h!==void 0?Math.max(o,h):o)-l||1)/a,e=Array.from({length:a},(t,r)=>({rangeLow:l+r*d,rangeHigh:l+(r+1)*d})),i=f.map(t=>{const r=new Array(a).fill(0);for(const x of n.get(t)){const p=Math.min(a-1,Math.floor((x-l)/d));r[p]++}return{groupKey:t,counts:r}});return{ranges:e,series:i}}
@@ -0,0 +1,27 @@
1
+ export * from './types.js';
2
+ export * from './regions.js';
3
+ export * from './analyzeWaferMap.js';
4
+ export * from './analyzeWaferLot.js';
5
+ export * from './filterFindings.js';
6
+ export { classifyPattern } from './patternClassification.js';
7
+ export type { PatternLabel, PatternClassification, PatternFeatures } from './patternClassification.js';
8
+ export { renderFindingsReportHtml, openHtmlReport, setReportOpener } from './renderFindingsReport.js';
9
+ export type { SummaryReportParams, LotSummaryReportParams } from './renderSummaryReport.js';
10
+ export { renderSummaryReportHtml, renderLotSummaryReportHtml } from './renderSummaryReport.js';
11
+ export { buildFacetTable, facetValueOf, FACET_NONE_VALUE, DEFAULT_FACET_CURATION } from './facets.js';
12
+ export type { FacetField, FacetValue, FacetCuration, FacetItem, BuildFacetTableOptions } from './facets.js';
13
+ export { buildCapabilityData } from './capability.js';
14
+ export type { CapabilityDatum, CapabilityItem } from './capability.js';
15
+ export { buildCorrelationMatrix, filterCorrelationMatrix } from './correlation.js';
16
+ export type { CorrelationTestInfo, CorrelationCell, CorrelationMatrix, CorrelationSummary } from './correlation.js';
17
+ export { buildTestBoxplotData } from './boxplot.js';
18
+ export type { BoxplotDatum, BoxplotItem } from './boxplot.js';
19
+ export { buildTestHistogramData, buildTestHistogramSeries } from './histogram.js';
20
+ export type { HistogramBucket, HistogramItem, HistogramSeries, HistogramSeriesData } from './histogram.js';
21
+ export { buildScatterData, buildScatterDataGrouped } from './scatter.js';
22
+ export type { ScatterPoint, ScatterItem } from './scatter.js';
23
+ export { buildYieldData, buildYieldDataCombined } from './yield.js';
24
+ export type { ChartDatum, YieldItem, YieldSortBy } from './yield.js';
25
+ export { buildBinParetoData, buildBinClusterData } from './binPareto.js';
26
+ export type { BinType, BinItem, BinCluster, BinClusterData } from './binPareto.js';
27
+ //# sourceMappingURL=index.d.ts.map