@wafertools/wafermap 0.33.0 → 0.34.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 (142) hide show
  1. package/AGENTS.md +6 -6
  2. package/CHANGELOG.md +212 -0
  3. package/README.md +5 -5
  4. package/dist/packages/canvas-adapter/chartPopulation.d.ts +15 -10
  5. package/dist/packages/canvas-adapter/chartPopulation.js +1 -1
  6. package/dist/packages/canvas-adapter/charts/barPanel.d.ts +7 -0
  7. package/dist/packages/canvas-adapter/charts/barPanel.js +1 -1
  8. package/dist/packages/canvas-adapter/charts/binCluster.d.ts +7 -1
  9. package/dist/packages/canvas-adapter/charts/binCluster.js +1 -1
  10. package/dist/packages/canvas-adapter/charts/boxplot.js +1 -1
  11. package/dist/packages/canvas-adapter/charts/capability.js +1 -1
  12. package/dist/packages/canvas-adapter/charts/chartShell.d.ts +55 -3
  13. package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
  14. package/dist/packages/canvas-adapter/charts/correlation.js +1 -2
  15. package/dist/packages/canvas-adapter/charts/groupedBarPlot.d.ts +2 -0
  16. package/dist/packages/canvas-adapter/charts/groupedBarPlot.js +1 -1
  17. package/dist/packages/canvas-adapter/charts/histogram.d.ts +16 -0
  18. package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
  19. package/dist/packages/canvas-adapter/charts/palette.d.ts +2 -0
  20. package/dist/packages/canvas-adapter/charts/palette.js +1 -1
  21. package/dist/packages/canvas-adapter/charts/plotChart.d.ts +27 -0
  22. package/dist/packages/canvas-adapter/charts/plotChart.js +1 -0
  23. package/dist/packages/canvas-adapter/charts/pointInteractions.d.ts +45 -0
  24. package/dist/packages/canvas-adapter/charts/pointInteractions.js +1 -0
  25. package/dist/packages/canvas-adapter/charts/regionYieldDiagram.d.ts +2 -0
  26. package/dist/packages/canvas-adapter/charts/regionYieldDiagram.js +1 -1
  27. package/dist/packages/canvas-adapter/charts/scatter.d.ts +19 -0
  28. package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
  29. package/dist/packages/canvas-adapter/charts/sweep.d.ts +12 -0
  30. package/dist/packages/canvas-adapter/charts/sweep.js +1 -1
  31. package/dist/packages/canvas-adapter/charts/testPassRate.d.ts +7 -0
  32. package/dist/packages/canvas-adapter/charts/testPassRate.js +1 -1
  33. package/dist/packages/canvas-adapter/charts/trend.d.ts +11 -0
  34. package/dist/packages/canvas-adapter/charts/trend.js +1 -1
  35. package/dist/packages/canvas-adapter/confirmDialog.d.ts +13 -0
  36. package/dist/packages/canvas-adapter/confirmDialog.js +1 -0
  37. package/dist/packages/canvas-adapter/dataTab.d.ts +131 -0
  38. package/dist/packages/canvas-adapter/dataTab.js +1 -0
  39. package/dist/packages/canvas-adapter/dieList.d.ts +39 -4
  40. package/dist/packages/canvas-adapter/dieList.js +6 -7
  41. package/dist/packages/canvas-adapter/drilldown.d.ts +20 -3
  42. package/dist/packages/canvas-adapter/drilldown.js +1 -1
  43. package/dist/packages/canvas-adapter/guideWindow.d.ts +96 -0
  44. package/dist/packages/canvas-adapter/guideWindow.js +1 -0
  45. package/dist/packages/canvas-adapter/identityHeader.d.ts +16 -0
  46. package/dist/packages/canvas-adapter/identityHeader.js +1 -1
  47. package/dist/packages/canvas-adapter/index.d.ts +3 -3
  48. package/dist/packages/canvas-adapter/index.js +1 -1
  49. package/dist/packages/canvas-adapter/insightsTab.d.ts +73 -54
  50. package/dist/packages/canvas-adapter/insightsTab.js +1 -1
  51. package/dist/packages/canvas-adapter/maplessSummary.d.ts +2 -2
  52. package/dist/packages/canvas-adapter/plotEditor.d.ts +29 -0
  53. package/dist/packages/canvas-adapter/plotEditor.js +1 -0
  54. package/dist/packages/canvas-adapter/plotItems.d.ts +32 -0
  55. package/dist/packages/canvas-adapter/plotItems.js +1 -0
  56. package/dist/packages/canvas-adapter/plotModal.d.ts +30 -0
  57. package/dist/packages/canvas-adapter/plotModal.js +1 -0
  58. package/dist/packages/canvas-adapter/plotStore.d.ts +52 -0
  59. package/dist/packages/canvas-adapter/plotStore.js +1 -0
  60. package/dist/packages/canvas-adapter/plotTab.d.ts +30 -0
  61. package/dist/packages/canvas-adapter/plotTab.js +1 -0
  62. package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +3 -2
  63. package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
  64. package/dist/packages/canvas-adapter/renderWaferMap.d.ts +21 -2
  65. package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
  66. package/dist/packages/canvas-adapter/summaryPanel.d.ts +27 -8
  67. package/dist/packages/canvas-adapter/summaryPanel.js +2 -4
  68. package/dist/packages/canvas-adapter/summaryRail.d.ts +7 -0
  69. package/dist/packages/canvas-adapter/summaryRail.js +1 -0
  70. package/dist/packages/canvas-adapter/sweepEditor.d.ts +17 -0
  71. package/dist/packages/canvas-adapter/sweepEditor.js +1 -0
  72. package/dist/packages/canvas-adapter/tableExport.d.ts +22 -0
  73. package/dist/packages/canvas-adapter/tableExport.js +3 -0
  74. package/dist/packages/canvas-adapter/toolbar.d.ts +25 -97
  75. package/dist/packages/canvas-adapter/toolbar.js +2 -2
  76. package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
  77. package/dist/packages/canvas-adapter/userGuideHtml.js +337 -42
  78. package/dist/packages/canvas-adapter/version.d.ts +2 -2
  79. package/dist/packages/canvas-adapter/version.js +1 -1
  80. package/dist/packages/canvas-adapter/viewSwitch.d.ts +13 -0
  81. package/dist/packages/canvas-adapter/viewSwitch.js +1 -0
  82. package/dist/packages/canvas-adapter/virtualTable.d.ts +44 -0
  83. package/dist/packages/canvas-adapter/virtualTable.js +1 -0
  84. package/dist/packages/core/classify.d.ts +3 -2
  85. package/dist/packages/core/classify.js +1 -1
  86. package/dist/packages/core/passBins.d.ts +17 -4
  87. package/dist/packages/core/passBins.js +1 -1
  88. package/dist/packages/core/ringCount.d.ts +22 -0
  89. package/dist/packages/core/ringCount.js +1 -0
  90. package/dist/packages/core/tableCsv.d.ts +43 -0
  91. package/dist/packages/core/tableCsv.js +3 -0
  92. package/dist/packages/renderer/binColors.d.ts +6 -6
  93. package/dist/packages/renderer/binColors.js +1 -1
  94. package/dist/packages/renderer/buildView.d.ts +1 -1
  95. package/dist/packages/renderer/buildView.js +1 -1
  96. package/dist/packages/renderer/buildWaferMap.js +1 -1
  97. package/dist/packages/stats/analyzeWaferLot.js +1 -1
  98. package/dist/packages/stats/analyzeWaferMap.js +1 -1
  99. package/dist/packages/stats/binPareto.d.ts +1 -1
  100. package/dist/packages/stats/binPareto.js +1 -1
  101. package/dist/packages/stats/histogram.d.ts +27 -0
  102. package/dist/packages/stats/histogram.js +1 -1
  103. package/dist/packages/stats/index.d.ts +2 -0
  104. package/dist/packages/stats/index.js +1 -1
  105. package/dist/packages/stats/lotDrift.js +1 -1
  106. package/dist/packages/stats/lotPattern.js +1 -1
  107. package/dist/packages/stats/math.d.ts +9 -6
  108. package/dist/packages/stats/math.js +1 -1
  109. package/dist/packages/stats/patternClassification.d.ts +1 -1
  110. package/dist/packages/stats/patternClassification.js +1 -1
  111. package/dist/packages/stats/plotData.d.ts +258 -0
  112. package/dist/packages/stats/plotData.js +1 -0
  113. package/dist/packages/stats/plotId.d.ts +3 -0
  114. package/dist/packages/stats/plotId.js +1 -0
  115. package/dist/packages/stats/plotSpec.d.ts +119 -0
  116. package/dist/packages/stats/plotSpec.js +4 -0
  117. package/dist/packages/stats/presentation.d.ts +2 -0
  118. package/dist/packages/stats/presentation.js +1 -1
  119. package/dist/packages/stats/regions.d.ts +6 -0
  120. package/dist/packages/stats/regions.js +1 -1
  121. package/dist/packages/stats/renderSummaryReport.d.ts +8 -3
  122. package/dist/packages/stats/renderSummaryReport.js +15 -15
  123. package/dist/packages/stats/reportHtml.d.ts +1 -1
  124. package/dist/packages/stats/reportHtml.js +36 -29
  125. package/dist/packages/stats/scatter.d.ts +6 -0
  126. package/dist/packages/stats/scatter.js +1 -1
  127. package/dist/packages/stats/sweep.d.ts +37 -1
  128. package/dist/packages/stats/sweep.js +1 -1
  129. package/dist/packages/stats/sweepText.d.ts +14 -0
  130. package/dist/packages/stats/sweepText.js +1 -0
  131. package/dist/packages/stats/sweepXFromName.d.ts +14 -0
  132. package/dist/packages/stats/sweepXFromName.js +1 -1
  133. package/dist/packages/stats/synthesis.d.ts +14 -4
  134. package/dist/packages/stats/synthesis.js +1 -1
  135. package/dist/packages/stats/testPassRate.d.ts +6 -0
  136. package/dist/packages/stats/testPassRate.js +1 -1
  137. package/dist/packages/stats/trend.js +1 -1
  138. package/dist/packages/stats/types.d.ts +1 -1
  139. package/dist/packages/stats/yield.d.ts +15 -6
  140. package/dist/packages/stats/yield.js +1 -1
  141. package/llms.txt +15 -3
  142. package/package.json +3 -3
@@ -1 +1 @@
1
- import{analyzeWaferMap as H,candidatesOf as U,describeRegional as Q,resolveOptions as se,adjustPValues as ae,twoProportionZ as X,zPValue as W,welchOfSums as Z,rateRelativeEffect as re,passesRateGate as oe,severityForFinding as J,severityForScore as ee,collapseRedundantFindings as ce,findContiguousRuns as le,mergedRegionLabel as fe}from"./analyzeWaferMap.js";import{normalizeInput as de}from"./normalizeInput.js";import{poolCapability as ue,poolRegionYield as me,poolPassRates as ge}from"./summaryFigures.js";import{mergeTestDefs as pe}from"./mergeTestDefs.js";import{findingPatternKey as he}from"./filterFindings.js";import{median as te}from"../core/utils.js";import{robustFence as ne}from"./math.js";import{findLotPattern as ye}from"./lotPattern.js";import{buildDriftFindings as be}from"./lotDrift.js";import{claimForPattern as we,weakestMultiplier as ve}from"./analyzeWaferMap.js";import{PATTERN_LABELS as Se}from"./patternClassification.js";import{describeWaferPopulation as ze,populationStat as Ie}from"./population.js";const T=8,ke={3:[.97,.994],4:[.829,.926],5:[.71,.821],6:[.625,.74],7:[.568,.68]},Me=3;export function outlierWafers(i){const c=i.length;if(c<3)return null;const o=te([...i]),s=e=>Math.abs(e-o)>=Me;if(c>=T){const e=ne(i,1.5,T),f=ne(i,3,T);if(!e||!f)return null;const u=(e.hi-e.lo)/4,a=[];return i.forEach((d,t)=>{if(d>=e.lo&&d<=e.hi||!s(d))return;const r=d<f.lo||d>f.hi?"unusual":"notable";a.push({index:t,severity:r,statistic:(d-o)/u})}),{method:"tukey-fence",outliers:a}}const m=i.map((e,f)=>f).sort((e,f)=>i[e]-i[f]),p=i[m[c-1]]-i[m[0]];if(p===0)return null;const[n,l]=ke[c],g=[],h=(e,f)=>{const u=Math.abs(i[e]-i[f])/p;u>n&&s(i[e])&&g.push({index:e,severity:u>l?"unusual":"notable",statistic:u})};return h(m[0],m[1]),h(m[c-1],m[c-2]),{method:"dixon-q",outliers:g}}const A=2;function _(i,c){const o={info:0,notable:1,unusual:2};return o[i]>=o[c]?i:c}const Pe=new Set(["ring","quadrant","sector"]),Re=new Set(["ring","quadrant","sector","reticle-position","test-site"]),xe=new Set(["yield","hardBin","softBin","functionalTest","test"]);function $e(i){return Re.has(i.comparison.family)&&xe.has(i.variable.kind)}function Be(i,c){const o=i.map(n=>n.facts);if(!o.length||o.some(n=>!n))return{coincident:new Set,yieldSoftBin:void 0,passBins:[],sectorCount:c};const[s,...m]=o,p=m.every(n=>n.passBins.length===s.passBins.length&&n.passBins.every(l=>s.passBins.includes(l)));return{coincident:new Set([...s.coincident].filter(n=>m.every(l=>l.coincident.has(n)))),yieldSoftBin:m.every(n=>n.yieldSoftBin===s.yieldSoftBin)?s.yieldSoftBin:void 0,passBins:p?s.passBins:[],sectorCount:c}}function j(i,c){let o=0,s=0,m=0,p=0,n=0,l=0,g=0,h=0,e=0,f=!0,u=0;for(const{waferIndex:t,c:r}of i){const y=c(t,r),M=r.rate??r.mean,S=Math.sqrt(M.n+y.n);if(r.rate)o+=S*X(r.rate.hits,r.rate.n,y.hits,y.n),m+=r.rate.hits,p+=r.rate.n,n+=y.hits,l+=y.n;else{const P=Z(r.mean,y.n,y.sum,y.sq);o+=S*P.z,g+=r.mean.n*P.effectSize,h+=r.mean.n*P.delta,u+=r.mean.n,r.relativeDelta===void 0?f=!1:e+=r.mean.n*r.relativeDelta}s+=S*S}const a=s>0?o/Math.sqrt(s):0,d=i[0].c;if(d.rate){const t=n/l,r=m/p-t;return{z:a,delta:r,relativeDelta:t===0?void 0:r/t,size:re({hits:m,n:p,restHits:n,restN:l,passRate:d.rate.passRate})}}return{z:a,delta:h/u,relativeDelta:f?e/u:void 0,size:g/u}}function O(i,c,o,s){return i.rate?oe(c,o.delta,o.size,s):c<=s.significanceLevel&&Math.abs(o.size)>=s.minimumEffectSize}function De(i,c,o,s){const m=new Map;for(const{waferIndex:a,byKey:d}of i)for(const t of d.values())(m.get(t.key)??m.set(t.key,[]).get(t.key)).push({waferIndex:a,c:t});const p=new Map(i.map(a=>[a.waferIndex,a.byKey])),n=(a,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},l=[];for(const[a,d]of m){if(d.length<A)continue;const t=j(d,n);if(t.z===0)continue;const r=d[0].c;l.push({key:a,entries:d,combined:t,finding:{id:`lot-region:${a}`,level:"lot",severity:"info",variable:{...r.variable,label:r.label},comparison:{...r.comparison},effect:{direction:t.z>0?"higher":"lower",absoluteDelta:t.delta,relativeDelta:t.relativeDelta,effectSize:r.rate?t.delta:t.size},stats:{method:"stouffer-z",pValue:W(t.z),sampleSizeLeft:0,sampleSizeRight:0},summary:"",highlight:{kind:"wafer",waferIndices:[]}}})}ae(l.map(a=>a.finding));const g=l.filter(a=>O(a.entries[0].c,a.finding.stats.adjustedPValue??1,a.combined,c)),h=(a,d)=>{const t=a.entries[0].c,r=d.entries[0].c;return t.source===r.source&&t.variable.kind===r.variable.kind&&t.variable.bin===r.variable.bin&&t.variable.index===r.variable.index&&t.region.family===r.region.family},e=[],f=new Map;for(const a of g){const d=a.finding.stats.pValue??1,t=g.filter(b=>b!==a&&h(a,b)&&b.finding.effect.direction!==a.finding.effect.direction&&(b.finding.stats.pValue??1)<d);if(t.length){const b=($,v)=>{const z=n($,v);for(const B of t){const I=p.get($)?.get(B.key);I&&(I.rate?(z.hits-=I.rate.hits,z.n-=I.rate.n):(z.n-=I.mean.n,z.sum-=I.mean.sum,z.sq-=I.mean.sq))}return z},k=a.entries.filter($=>b($.waferIndex,$.c).n>=c.minimumSampleSize);if(!k.length)continue;const x=j(k,b),L=d>0?(a.finding.stats.adjustedPValue??d)/d:1;if(Math.sign(x.z)!==Math.sign(a.combined.z)||!O(a.entries[0].c,Math.min(1,W(x.z)*L),x,c))continue}const r=Math.sign(a.combined.z),y=a.entries.filter(b=>Math.sign(b.c.delta)===r),M=i.length,S=a.entries[0].c,P=a.finding.stats.adjustedPValue??d,K=S.rate?J(P,a.combined.delta,a.combined.size):ee(P,a.combined.size),w=_(K,y.length/M>=.6?"unusual":"info"),N={};for(const b of y)N[b.waferIndex]=[...b.c.region.dieKeys];const E={...a.finding,severity:w,stats:{...a.finding.stats,sampleSizeLeft:y.length,sampleSizeRight:M-y.length},summary:`${Q(S,a.combined.delta,a.combined.relativeDelta)} \u2014 ${a.finding.effect.direction} on ${y.length}/${M} wafers, all wafers' data combined`,highlight:{kind:"wafer",waferIndices:y.map(b=>b.waferIndex),dieKeysByWafer:N}};e.push(E),f.set(E,a)}const u=Ee(f,p,i.length,c,s,n);return e.splice(0,e.length,...e.filter(a=>!u.replaced.has(a)),...u.findings),ce(e,o),e}function Ee(i,c,o,s,m,p){const n=new Map;for(const[h,e]of i){const f=e.entries[0].c;if(!Pe.has(f.region.family))continue;const u=[f.source,f.variable.kind,f.variable.bin??"",f.variable.index??"",f.region.family,h.effect.direction].join("|");(n.get(u)??n.set(u,[]).get(u)).push({finding:h,c:f,key:e.key})}const l=[],g=new Set;for(const h of n.values()){if(h.length<2)continue;const e=h[0].c.region.family;for(const f of le(h,e,s.sectorCount,u=>u.c.region.key)){if(f.length<2)continue;const u=Le(f,c,o,s,m,p);if(u){l.push(u);for(const a of f)g.add(a.finding)}}}return{findings:l,replaced:g}}function Le(i,c,o,s,m,p){const n=i[0].c,l=i[0].finding.effect.direction==="higher"?1:-1,g=fe(n.region.family,i.map(w=>w.c.region.key),m),h=[];for(const[w,N]of c){const E=i.map(v=>N.get(v.key));if(E.some(v=>!v))continue;const b=E,k=b[0];let x,L;if(k.rate){const v=b.reduce((R,D)=>R+D.rate.hits,0),z=b.reduce((R,D)=>R+D.rate.n,0),B=k.rate.hits+k.rate.restHits-v,I=k.rate.n+k.rate.restN-z;if(z<s.minimumSampleSize||I<s.minimumSampleSize)continue;x={...k,rate:{hits:v,n:z,restHits:B,restN:I,passRate:k.rate.passRate}},L=X(v,z,B,I)}else{const v=k.mean,z=b.reduce((q,F)=>q+F.mean.n,0),B=b.reduce((q,F)=>q+F.mean.sum,0),I=b.reduce((q,F)=>q+F.mean.sq,0),R=v.n+v.restN-z,D=v.sum+v.restSum-B,C=v.sq+v.restSq-I;if(z<s.minimumSampleSize||R<s.minimumSampleSize)continue;const V={n:z,sum:B,sq:I,restN:R,restSum:D,restSq:C,shift:v.shift},Y=Z(V,R,D,C),G=D/R+v.shift;x={...k,mean:V,relativeDelta:G!==0?Y.delta/Math.abs(G):void 0},L=Y.z}const $=[...new Set(b.flatMap(v=>[...v.region.dieKeys]))];h.push({waferIndex:w,c:x,z:L,dieKeys:$})}if(h.length<A)return;const e=j(h,p);if(e.z===0||Math.sign(e.z)!==l)return;const f=W(e.z),u=ve(i.map(w=>w.finding)),a=Math.min(1,f*u);if(!O(n,a,e,s))return;const d=h.filter(w=>Math.sign(w.z)===l&&W(w.z)<=s.significanceLevel);if(!d.length)return;const t=n.rate?J(a,e.delta,e.size):ee(a,e.size),r=_(t,d.length/o>=.6?"unusual":"info"),y={};for(const w of d)y[w.waferIndex]=w.dieKeys;const M=i.map(w=>w.c.region.key.slice(w.c.region.key.indexOf(":")+1)).join("-"),S={...n,comparison:{...n.comparison,left:g}},P=i[0].finding,K=P.effect.direction;return{...P,id:`lot-region:${i[0].key.split("|").slice(0,-1).join("|")}|${n.region.family}:${M}`,severity:r,comparison:{...P.comparison,left:g},effect:{direction:K,absoluteDelta:e.delta,relativeDelta:e.relativeDelta,effectSize:n.rate?e.delta:e.size},stats:{method:"stouffer-z",pValue:f,adjustedPValue:a,sampleSizeLeft:d.length,sampleSizeRight:o-d.length},summary:`${Q(S,e.delta,e.relativeDelta)} \u2014 ${K} on ${d.length}/${o} wafers, all wafers' data combined`,highlight:{kind:"wafer",waferIndices:d.map(w=>w.waferIndex),dieKeysByWafer:y},relatedIds:i.map(w=>w.finding.id)}}const ie={"Edge-ring":"edge","Edge-local":"edge","Center cluster":"centre or donut",Donut:"centre or donut"};function qe(i,c){const o=new Map,s=n=>{const l=n.variable.kind==="spatialPattern"?ie[n.comparison.left]:void 0;return l?`spatialPattern|${l}`:he(n)};for(const n of i){const l=new Set,g=new Map;for(const e of n.summary.findings)if(e.variable.kind==="spatialPattern")for(const f of e.relatedIds??[])g.set(f,s(e));const h=new Set(n.summary.findings.flatMap(e=>e.absorbedIds??[]));for(const e of n.summary.findings){if(h.has(e.id)||$e(e)||e.variable.kind==="spatialPattern"&&c?.carriers.has(n.waferIndex))continue;const f=s(e);if(l.has(f))continue;l.add(f);const u=o.get(f)??{finding:e,waferIndices:[],severity:e.severity,labels:new Map,dieKeysByWafer:{},explainedBy:new Map};u.waferIndices.push(n.waferIndex);const a=g.get(e.id);if(a&&u.explainedBy.set(a,(u.explainedBy.get(a)??0)+1),e.variable.kind==="spatialPattern"){u.labels.set(e.comparison.left,(u.labels.get(e.comparison.left)??0)+1);const d=e.highlight.dieKeys;d&&(u.dieKeysByWafer[n.waferIndex]=d)}u.severity=_(u.severity,e.severity),o.set(f,u)}}const m=[];for(const[n,l]of o){if(l.waferIndices.length<A)continue;const g=l.waferIndices.length/Math.max(1,i.length),h=g>=.6||l.severity==="unusual"?"unusual":"notable",e=l.finding;m.push({id:`lot-repeat:${n}`,level:"lot",severity:h,variable:{...e.variable},comparison:{...e.comparison},effect:{direction:e.effect.direction,absoluteDelta:e.effect.absoluteDelta,relativeDelta:e.effect.relativeDelta,effectSize:e.effect.effectSize},stats:{method:"wafer-finding-frequency",sampleSizeLeft:l.waferIndices.length,sampleSizeRight:i.length-l.waferIndices.length},summary:`${Ne(e,l.labels)} \u2014 seen on ${l.waferIndices.length}/${i.length} wafers (${Math.round(g*100)}%)`,highlight:{kind:"wafer",waferIndices:l.waferIndices,...l.labels.size?{dieKeysByWafer:l.dieKeysByWafer}:{}}})}const p=new Map(m.map(n=>[n.id,n]));for(const[n,l]of o){const g=p.get(`lot-repeat:${n}`);if(g)for(const[h,e]of l.explainedBy){const f=p.get(`lot-repeat:${h}`);!f||e!==l.waferIndices.length||(f.relatedIds??=[]).push(g.id)}}return m}function Fe(i,c){const{classification:o,carriers:s}=i,m=Se[o.pattern],p=s.size/Math.max(1,c),n={};for(const[l,g]of s)n[l]=g;return{id:"lot-repeat:spatialPattern|lot",level:"lot",severity:p>=.6||o.confidence==="high"?"unusual":"notable",variable:{kind:"spatialPattern",label:m},comparison:{family:"spatial-pattern",left:m,right:"Wafer"},effect:{direction:"different",effectSize:o.features.globalRdd},stats:{method:"wafer-finding-frequency",sampleSizeLeft:s.size,sampleSizeRight:c-s.size},summary:`Spatial pattern: ${m.toLowerCase()} \u2014 seen on ${s.size}/${c} wafers (${Math.round(p*100)}%)`,highlight:{kind:"wafer",waferIndices:[...s.keys()].sort((l,g)=>l-g),dieKeysByWafer:n}}}function Ke(i){const c=/^Rings? (\d+)(?:\s*[–-]\s*(\d+))?/.exec(i.comparison.left);if(!c)return[];const o=+c[1],s=c[2]?+c[2]:o;return Array.from({length:Math.max(0,s-o+1)},(m,p)=>o+p)}function Ne(i,c){if(!c.size)return i.summary;const o=p=>p.toLowerCase();if(c.size===1)return`Spatial pattern: ${o([...c.keys()][0])}`;const s=ie[i.comparison.left],m=[...c].sort((p,n)=>n[1]-p[1]).map(([p,n])=>`${o(p)} on ${n}`);return`Spatial pattern: ${s} (${m.join(", ")})`}function We(i,c){const o=Ie(c,"median"),s=i.map(g=>({waferIndex:g.waferIndex,yieldPercent:g.summary.stats.yieldPercent})).filter(g=>g.yieldPercent!==null),m=s.map(g=>g.yieldPercent),p=outlierWafers(m);if(!p)return[];const n=te(m),l=[];for(const{index:g,severity:h,statistic:e}of p.outliers){const f=s[g],u=f.yieldPercent-n;l.push({id:`inter-wafer:yield:${f.waferIndex}`,level:"inter-wafer",severity:h,variable:{kind:"yield",label:"Yield"},comparison:{family:"wafer",left:`Wafer ${f.waferIndex+1}`,right:o.charAt(0).toUpperCase()+o.slice(1)},effect:{direction:u>0?"higher":"lower",absoluteDelta:u/100,relativeDelta:n===0?void 0:u/n,effectSize:e},stats:{method:p.method,sampleSizeLeft:1,sampleSizeRight:s.length-1},summary:`Wafer ${f.waferIndex+1} yield is ${Math.abs(u).toFixed(1)} percentage points ${u>0?"higher":"lower"} than the ${o}`,highlight:{kind:"wafer",waferIndices:[f.waferIndex]}})}return l}export function analyzeWaferLot(i,c={}){const o=i.map(de),s=o.map((t,r)=>({waferIndex:r,summary:c.perWaferSummaries?.[r]??H(t,c)})),m=s.map(({waferIndex:t,summary:r})=>U(r)??U(H(o[t],c))),p=m.map((t,r)=>({waferIndex:r,byKey:new Map(t.candidates.map(y=>[y.key,y]))})),n=se(c).resolved,l=ye(o,n),g=l&&l.carriers.size>=A?l:null,h=[...De(p,n,Be(m,n.sectorCount),o[0]?.ringCount??n.ringCount),...qe(s,g),...We(s,ze(s.map(t=>t.summary.wafer))),...be(s,n.significanceLevel)];if(g){const t=Fe(g,s.length);t.relatedIds=we([g.classification.pattern],t.severity,h,Ke,n.ringCount),t.relatedIds.length||delete t.relatedIds,h.push(t)}h.sort((t,r)=>{const y=t.severity==="unusual"?2:t.severity==="notable"?1:0,M=r.severity==="unusual"?2:r.severity==="notable"?1:0;return y!==M?M-y:t.summary.localeCompare(r.summary,"en")});const e=s.map(t=>t.summary.wafer).filter(t=>!!t),f={},u=[];if(e.length>0){const t=new Set;for(const r of e)for(const y of Object.keys(r))t.add(y);t.delete("wafer"),t.delete("waferId");for(const r of t){const y=e.filter(S=>r in S).map(S=>S[r]);y.every(S=>S===y[0])?f[r]=y[0]:u.push(r)}}const a=s.map(({waferIndex:t,summary:r})=>({waferIndex:t,yieldPercent:r.stats.yieldPercent})),d=s.map(({waferIndex:t,summary:r})=>r.stats.perTestStats?.length?{waferIndex:t,tests:r.stats.perTestStats}:null).filter(t=>t!==null);return{level:"lot",hasNotableFindings:h.some(t=>t.severity!=="info")||s.some(t=>t.summary.hasNotableFindings),findings:h,lot:Object.keys(f).length>0?f:void 0,...u.length>0?{mixedIdentityFields:u}:{},stats:{waferCount:i.length,...Ae(o,s.map(t=>t.summary))},lotYieldSeries:a,perWafer:s,...d.length>0?{perWaferTestStats:d}:{}}}function Ae(i,c){const o={...ge(c)},s=ue(c,pe(i).defs);s&&(o.capability=s);const m=me(c,i.map(p=>p.ringCount));return m&&(o.regionYield=m),o}
1
+ import{requireRingCount as se}from"../core/ringCount.js";import{analyzeWaferMap as H,candidatesOf as U,describeRegional as Q,resolveOptions as ae,adjustPValues as re,twoProportionZ as X,zPValue as W,welchOfSums as Z,rateRelativeEffect as oe,passesRateGate as ce,severityForFinding as J,severityForScore as ee,collapseRedundantFindings as le,findContiguousRuns as fe,mergedRegionLabel as de}from"./analyzeWaferMap.js";import{normalizeInput as ue}from"./normalizeInput.js";import{poolCapability as me,poolRegionYield as ge,poolPassRates as pe}from"./summaryFigures.js";import{mergeTestDefs as he}from"./mergeTestDefs.js";import{findingPatternKey as ye}from"./filterFindings.js";import{median as te}from"../core/utils.js";import{robustFence as ne}from"./math.js";import{findLotPattern as be}from"./lotPattern.js";import{buildDriftFindings as we}from"./lotDrift.js";import{claimForPattern as ve,weakestMultiplier as Se}from"./analyzeWaferMap.js";import{PATTERN_LABELS as ze}from"./patternClassification.js";import{describeWaferPopulation as Ie,populationStat as ke}from"./population.js";const T=8,Me={3:[.97,.994],4:[.829,.926],5:[.71,.821],6:[.625,.74],7:[.568,.68]},Re=3;export function outlierWafers(i){const o=i.length;if(o<3)return null;const r=te([...i]),s=e=>Math.abs(e-r)>=Re;if(o>=T){const e=ne(i,1.5,T),l=ne(i,3,T);if(!e||!l)return null;const f=(e.hi-e.lo)/4,a=[];return i.forEach((d,h)=>{if(d>=e.lo&&d<=e.hi||!s(d))return;const t=d<l.lo||d>l.hi?"unusual":"notable";a.push({index:h,severity:t,statistic:(d-r)/f})}),{method:"tukey-fence",outliers:a}}const u=i.map((e,l)=>l).sort((e,l)=>i[e]-i[l]),p=i[u[o-1]]-i[u[0]];if(p===0)return null;const[n,c]=Me[o],g=[],y=(e,l)=>{const f=Math.abs(i[e]-i[l])/p;f>n&&s(i[e])&&g.push({index:e,severity:f>c?"unusual":"notable",statistic:f})};return y(u[0],u[1]),y(u[o-1],u[o-2]),{method:"dixon-q",outliers:g}}const A=2;function _(i,o){const r={info:0,notable:1,unusual:2};return r[i]>=r[o]?i:o}const Pe=new Set(["ring","quadrant","sector"]),xe=new Set(["ring","quadrant","sector","reticle-position","test-site"]),$e=new Set(["yield","hardBin","softBin","functionalTest","test"]);function Be(i){return xe.has(i.comparison.family)&&$e.has(i.variable.kind)}function De(i,o){const r=i.map(n=>n.facts);if(!r.length||r.some(n=>!n))return{coincident:new Set,yieldSoftBin:void 0,passBins:[],sectorCount:o};const[s,...u]=r,p=u.every(n=>n.passBins.length===s.passBins.length&&n.passBins.every(c=>s.passBins.includes(c)));return{coincident:new Set([...s.coincident].filter(n=>u.every(c=>c.coincident.has(n)))),yieldSoftBin:u.every(n=>n.yieldSoftBin===s.yieldSoftBin)?s.yieldSoftBin:void 0,passBins:p?s.passBins:[],sectorCount:o}}function j(i,o){let r=0,s=0,u=0,p=0,n=0,c=0,g=0,y=0,e=0,l=!0,f=0;for(const{waferIndex:h,c:t}of i){const m=o(h,t),S=t.rate??t.mean,M=Math.sqrt(S.n+m.n);if(t.rate)r+=M*X(t.rate.hits,t.rate.n,m.hits,m.n),u+=t.rate.hits,p+=t.rate.n,n+=m.hits,c+=m.n;else{const z=Z(t.mean,m.n,m.sum,m.sq);r+=M*z.z,g+=t.mean.n*z.effectSize,y+=t.mean.n*z.delta,f+=t.mean.n,t.relativeDelta===void 0?l=!1:e+=t.mean.n*t.relativeDelta}s+=M*M}const a=s>0?r/Math.sqrt(s):0,d=i[0].c;if(d.rate){const h=n/c,t=u/p-h;return{z:a,delta:t,relativeDelta:h===0?void 0:t/h,size:oe({hits:u,n:p,restHits:n,restN:c,passRate:d.rate.passRate})}}return{z:a,delta:y/f,relativeDelta:l?e/f:void 0,size:g/f}}function O(i,o,r,s){return i.rate?ce(o,r.delta,r.size,s):o<=s.significanceLevel&&Math.abs(r.size)>=s.minimumEffectSize}function Ee(i,o,r,s){const u=new Map;for(const{waferIndex:a,byKey:d}of i)for(const h of d.values())(u.get(h.key)??u.set(h.key,[]).get(h.key)).push({waferIndex:a,c:h});const p=new Map(i.map(a=>[a.waferIndex,a.byKey])),n=(a,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},c=[];for(const[a,d]of u){if(d.length<A)continue;const h=j(d,n);if(h.z===0)continue;const t=d[0].c;c.push({key:a,entries:d,combined:h,finding:{id:`lot-region:${a}`,level:"lot",severity:"info",variable:{...t.variable,label:t.label},comparison:{...t.comparison},effect:{direction:h.z>0?"higher":"lower",absoluteDelta:h.delta,relativeDelta:h.relativeDelta,effectSize:t.rate?h.delta:h.size},stats:{method:"stouffer-z",pValue:W(h.z),sampleSizeLeft:0,sampleSizeRight:0},summary:"",highlight:{kind:"wafer",waferIndices:[]}}})}re(c.map(a=>a.finding));const g=c.filter(a=>O(a.entries[0].c,a.finding.stats.adjustedPValue??1,a.combined,o)),y=(a,d)=>{const h=a.entries[0].c,t=d.entries[0].c;return h.source===t.source&&h.variable.kind===t.variable.kind&&h.variable.bin===t.variable.bin&&h.variable.index===t.variable.index&&h.region.family===t.region.family},e=[],l=new Map;for(const a of g){const d=a.finding.stats.pValue??1,h=g.filter(b=>b!==a&&y(a,b)&&b.finding.effect.direction!==a.finding.effect.direction&&(b.finding.stats.pValue??1)<d);if(h.length){const b=($,v)=>{const I=n($,v);for(const B of h){const k=p.get($)?.get(B.key);k&&(k.rate?(I.hits-=k.rate.hits,I.n-=k.rate.n):(I.n-=k.mean.n,I.sum-=k.mean.sum,I.sq-=k.mean.sq))}return I},R=a.entries.filter($=>b($.waferIndex,$.c).n>=o.minimumSampleSize);if(!R.length)continue;const x=j(R,b),L=d>0?(a.finding.stats.adjustedPValue??d)/d:1;if(Math.sign(x.z)!==Math.sign(a.combined.z)||!O(a.entries[0].c,Math.min(1,W(x.z)*L),x,o))continue}const t=Math.sign(a.combined.z),m=a.entries.filter(b=>Math.sign(b.c.delta)===t),S=i.length,M=a.entries[0].c,z=a.finding.stats.adjustedPValue??d,K=M.rate?J(z,a.combined.delta,a.combined.size):ee(z,a.combined.size),w=_(K,m.length/S>=.6?"unusual":"info"),N={};for(const b of m)N[b.waferIndex]=[...b.c.region.dieKeys];const E={...a.finding,severity:w,stats:{...a.finding.stats,sampleSizeLeft:m.length,sampleSizeRight:S-m.length},summary:`${Q(M,a.combined.delta,a.combined.relativeDelta)} \u2014 ${a.finding.effect.direction} on ${m.length}/${S} wafers, all wafers' data combined`,highlight:{kind:"wafer",waferIndices:m.map(b=>b.waferIndex),dieKeysByWafer:N}};e.push(E),l.set(E,a)}const f=Le(l,p,i.length,o,s,n);return e.splice(0,e.length,...e.filter(a=>!f.replaced.has(a)),...f.findings),le(e,r),e}function Le(i,o,r,s,u,p){const n=new Map;for(const[y,e]of i){const l=e.entries[0].c;if(!Pe.has(l.region.family))continue;const f=[l.source,l.variable.kind,l.variable.bin??"",l.variable.index??"",l.region.family,y.effect.direction].join("|");(n.get(f)??n.set(f,[]).get(f)).push({finding:y,c:l,key:e.key})}const c=[],g=new Set;for(const y of n.values()){if(y.length<2)continue;const e=y[0].c.region.family;for(const l of fe(y,e,s.sectorCount,f=>f.c.region.key)){if(l.length<2)continue;const f=qe(l,o,r,s,u,p);if(f){c.push(f);for(const a of l)g.add(a.finding)}}}return{findings:c,replaced:g}}function qe(i,o,r,s,u,p){const n=i[0].c,c=i[0].finding.effect.direction==="higher"?1:-1,g=de(n.region.family,i.map(w=>w.c.region.key),u),y=[];for(const[w,N]of o){const E=i.map(v=>N.get(v.key));if(E.some(v=>!v))continue;const b=E,R=b[0];let x,L;if(R.rate){const v=b.reduce((P,D)=>P+D.rate.hits,0),I=b.reduce((P,D)=>P+D.rate.n,0),B=R.rate.hits+R.rate.restHits-v,k=R.rate.n+R.rate.restN-I;if(I<s.minimumSampleSize||k<s.minimumSampleSize)continue;x={...R,rate:{hits:v,n:I,restHits:B,restN:k,passRate:R.rate.passRate}},L=X(v,I,B,k)}else{const v=R.mean,I=b.reduce((q,F)=>q+F.mean.n,0),B=b.reduce((q,F)=>q+F.mean.sum,0),k=b.reduce((q,F)=>q+F.mean.sq,0),P=v.n+v.restN-I,D=v.sum+v.restSum-B,C=v.sq+v.restSq-k;if(I<s.minimumSampleSize||P<s.minimumSampleSize)continue;const V={n:I,sum:B,sq:k,restN:P,restSum:D,restSq:C,shift:v.shift},Y=Z(V,P,D,C),G=D/P+v.shift;x={...R,mean:V,relativeDelta:G!==0?Y.delta/Math.abs(G):void 0},L=Y.z}const $=[...new Set(b.flatMap(v=>[...v.region.dieKeys]))];y.push({waferIndex:w,c:x,z:L,dieKeys:$})}if(y.length<A)return;const e=j(y,p);if(e.z===0||Math.sign(e.z)!==c)return;const l=W(e.z),f=Se(i.map(w=>w.finding)),a=Math.min(1,l*f);if(!O(n,a,e,s))return;const d=y.filter(w=>Math.sign(w.z)===c&&W(w.z)<=s.significanceLevel);if(!d.length)return;const h=n.rate?J(a,e.delta,e.size):ee(a,e.size),t=_(h,d.length/r>=.6?"unusual":"info"),m={};for(const w of d)m[w.waferIndex]=w.dieKeys;const S=i.map(w=>w.c.region.key.slice(w.c.region.key.indexOf(":")+1)).join("-"),M={...n,comparison:{...n.comparison,left:g}},z=i[0].finding,K=z.effect.direction;return{...z,id:`lot-region:${i[0].key.split("|").slice(0,-1).join("|")}|${n.region.family}:${S}`,severity:t,comparison:{...z.comparison,left:g},effect:{direction:K,absoluteDelta:e.delta,relativeDelta:e.relativeDelta,effectSize:n.rate?e.delta:e.size},stats:{method:"stouffer-z",pValue:l,adjustedPValue:a,sampleSizeLeft:d.length,sampleSizeRight:r-d.length},summary:`${Q(M,e.delta,e.relativeDelta)} \u2014 ${K} on ${d.length}/${r} wafers, all wafers' data combined`,highlight:{kind:"wafer",waferIndices:d.map(w=>w.waferIndex),dieKeysByWafer:m},relatedIds:i.map(w=>w.finding.id)}}const ie={"Edge-ring":"edge","Edge-local":"edge","Center cluster":"centre or donut",Donut:"centre or donut"};function Fe(i,o){const r=new Map,s=n=>{const c=n.variable.kind==="spatialPattern"?ie[n.comparison.left]:void 0;return c?`spatialPattern|${c}`:ye(n)};for(const n of i){const c=new Set,g=new Map;for(const e of n.summary.findings)if(e.variable.kind==="spatialPattern")for(const l of e.relatedIds??[])g.set(l,s(e));const y=new Set(n.summary.findings.flatMap(e=>e.absorbedIds??[]));for(const e of n.summary.findings){if(y.has(e.id)||Be(e)||e.variable.kind==="spatialPattern"&&o?.carriers.has(n.waferIndex))continue;const l=s(e);if(c.has(l))continue;c.add(l);const f=r.get(l)??{finding:e,waferIndices:[],severity:e.severity,labels:new Map,dieKeysByWafer:{},explainedBy:new Map};f.waferIndices.push(n.waferIndex);const a=g.get(e.id);if(a&&f.explainedBy.set(a,(f.explainedBy.get(a)??0)+1),e.variable.kind==="spatialPattern"){f.labels.set(e.comparison.left,(f.labels.get(e.comparison.left)??0)+1);const d=e.highlight.dieKeys;d&&(f.dieKeysByWafer[n.waferIndex]=d)}f.severity=_(f.severity,e.severity),r.set(l,f)}}const u=[];for(const[n,c]of r){if(c.waferIndices.length<A)continue;const g=c.waferIndices.length/Math.max(1,i.length),y=g>=.6||c.severity==="unusual"?"unusual":"notable",e=c.finding;u.push({id:`lot-repeat:${n}`,level:"lot",severity:y,variable:{...e.variable},comparison:{...e.comparison},effect:{direction:e.effect.direction,absoluteDelta:e.effect.absoluteDelta,relativeDelta:e.effect.relativeDelta,effectSize:e.effect.effectSize},stats:{method:"wafer-finding-frequency",sampleSizeLeft:c.waferIndices.length,sampleSizeRight:i.length-c.waferIndices.length},summary:`${We(e,c.labels)} \u2014 seen on ${c.waferIndices.length}/${i.length} wafers (${Math.round(g*100)}%)`,highlight:{kind:"wafer",waferIndices:c.waferIndices,...c.labels.size?{dieKeysByWafer:c.dieKeysByWafer}:{}}})}const p=new Map(u.map(n=>[n.id,n]));for(const[n,c]of r){const g=p.get(`lot-repeat:${n}`);if(g)for(const[y,e]of c.explainedBy){const l=p.get(`lot-repeat:${y}`);!l||e!==c.waferIndices.length||(l.relatedIds??=[]).push(g.id)}}return u}function Ke(i,o){const{classification:r,carriers:s}=i,u=ze[r.pattern],p=s.size/Math.max(1,o),n={};for(const[c,g]of s)n[c]=g;return{id:"lot-repeat:spatialPattern|lot",level:"lot",severity:p>=.6||r.confidence==="high"?"unusual":"notable",variable:{kind:"spatialPattern",label:u},comparison:{family:"spatial-pattern",left:u,right:"Wafer"},effect:{direction:"different",effectSize:r.features.globalRdd},stats:{method:"wafer-finding-frequency",sampleSizeLeft:s.size,sampleSizeRight:o-s.size},summary:`Spatial pattern: ${u.toLowerCase()} \u2014 seen on ${s.size}/${o} wafers (${Math.round(p*100)}%)`,highlight:{kind:"wafer",waferIndices:[...s.keys()].sort((c,g)=>c-g),dieKeysByWafer:n}}}function Ne(i){const o=/^Rings? (\d+)(?:\s*[–-]\s*(\d+))?/.exec(i.comparison.left);if(!o)return[];const r=+o[1],s=o[2]?+o[2]:r;return Array.from({length:Math.max(0,s-r+1)},(u,p)=>r+p)}function We(i,o){if(!o.size)return i.summary;const r=p=>p.toLowerCase();if(o.size===1)return`Spatial pattern: ${r([...o.keys()][0])}`;const s=ie[i.comparison.left],u=[...o].sort((p,n)=>n[1]-p[1]).map(([p,n])=>`${r(p)} on ${n}`);return`Spatial pattern: ${s} (${u.join(", ")})`}function Ae(i,o){const r=ke(o,"median"),s=i.map(g=>({waferIndex:g.waferIndex,yieldPercent:g.summary.stats.yieldPercent})).filter(g=>g.yieldPercent!==null),u=s.map(g=>g.yieldPercent),p=outlierWafers(u);if(!p)return[];const n=te(u),c=[];for(const{index:g,severity:y,statistic:e}of p.outliers){const l=s[g],f=l.yieldPercent-n;c.push({id:`inter-wafer:yield:${l.waferIndex}`,level:"inter-wafer",severity:y,variable:{kind:"yield",label:"Yield"},comparison:{family:"wafer",left:`Wafer ${l.waferIndex+1}`,right:r.charAt(0).toUpperCase()+r.slice(1)},effect:{direction:f>0?"higher":"lower",absoluteDelta:f/100,relativeDelta:n===0?void 0:f/n,effectSize:e},stats:{method:p.method,sampleSizeLeft:1,sampleSizeRight:s.length-1},summary:`Wafer ${l.waferIndex+1} yield is ${Math.abs(f).toFixed(1)} percentage points ${f>0?"higher":"lower"} than the ${r}`,highlight:{kind:"wafer",waferIndices:[l.waferIndex]}})}return c}export function analyzeWaferLot(i,o={}){const r=i.map(ue),s=r.map((t,m)=>({waferIndex:m,summary:o.perWaferSummaries?.[m]??H(t,o)})),u=s.map(({waferIndex:t,summary:m})=>U(m)??U(H(r[t],o))),p=u.map((t,m)=>({waferIndex:m,byKey:new Map(t.candidates.map(S=>[S.key,S]))})),n=ae(o).resolved,c=r.length>0?se(r[0],"analyzeWaferLot"):void 0,g=be(r,n),y=g&&g.carriers.size>=A?g:null,e=[...c===void 0?[]:Ee(p,n,De(u,n.sectorCount),c),...Fe(s,y),...Ae(s,Ie(s.map(t=>t.summary.wafer))),...we(s,n.significanceLevel)];if(y&&c!==void 0){const t=Ke(y,s.length);t.relatedIds=ve([y.classification.pattern],t.severity,e,Ne,c),t.relatedIds.length||delete t.relatedIds,e.push(t)}e.sort((t,m)=>{const S=t.severity==="unusual"?2:t.severity==="notable"?1:0,M=m.severity==="unusual"?2:m.severity==="notable"?1:0;return S!==M?M-S:t.summary.localeCompare(m.summary,"en")});const l=s.map(t=>t.summary.wafer).filter(t=>!!t),f={},a=[];if(l.length>0){const t=new Set;for(const m of l)for(const S of Object.keys(m))t.add(S);t.delete("wafer"),t.delete("waferId");for(const m of t){const S=l.filter(z=>m in z).map(z=>z[m]);S.every(z=>z===S[0])?f[m]=S[0]:a.push(m)}}const d=s.map(({waferIndex:t,summary:m})=>({waferIndex:t,yieldPercent:m.stats.yieldPercent})),h=s.map(({waferIndex:t,summary:m})=>m.stats.perTestStats?.length?{waferIndex:t,tests:m.stats.perTestStats}:null).filter(t=>t!==null);return{level:"lot",hasNotableFindings:e.some(t=>t.severity!=="info")||s.some(t=>t.summary.hasNotableFindings),findings:e,lot:Object.keys(f).length>0?f:void 0,...a.length>0?{mixedIdentityFields:a}:{},stats:{waferCount:i.length,...Te(r,s.map(t=>t.summary))},lotYieldSeries:d,perWafer:s,...h.length>0?{perWaferTestStats:h}:{}}}function Te(i,o){const r={...pe(o)},s=me(o,he(i).defs);s&&(r.capability=s);const u=ge(o,i.map(p=>p.ringCount));return u&&(r.regionYield=u),r}
@@ -1 +1 @@
1
- import{normalizeInput as Ge}from"./normalizeInput.js";import{computeCapability as Xe,computeTestFlagYield as Ze,computeRegionYield as Je}from"./summaryFigures.js";import{binPassSets as Me}from"../renderer/binColors.js";import{diePassStatus as Be,isYieldEligibleDie as de,getDieKey as Ne,isPositionedDie as Fe,positionKey as me}from"../core/dies.js";import{dieHasValues as et,testsPresent as Pe,testValue as j}from"../core/dieTable.js";import{getTestPassStatus as se,isParametricTest as Y}from"../renderer/buildWaferMap.js";import{markedTestLabel as T,derivedFields as oe}from"../renderer/testLabel.js";import{buildQuadrantRegions as tt,buildReticlePositionRegions as nt,buildRingRegions as it,buildSectorRegions as st,buildTestSiteRegions as ot,sectorCompassNames as xe,areQuadrantsAdjacent as at,parseRegionKey as _,QUADRANT_CYCLE as pe,regionAngleBins as rt}from"./regions.js";import{buildClusterFindings as ft}from"./clusterDetection.js";import{classifyPattern as ct,patternExplains as lt,patternFailVerdict as ut,PATTERN_LABELS as dt}from"./patternClassification.js";import{benjaminiHochberg as mt,fiveNumberSummary as pt,normalCdf as Ce}from"./math.js";import{mean as ae,clamp01 as Ve,maxOf as ht}from"../core/utils.js";import{classifySpec as re,isOutOfSpec as he}from"../renderer/spec.js";const gt={significanceLevel:{min:1e-6,max:1},minimumEffectSize:{min:0,max:1},minimumRelativeEffect:{min:0,max:Number.MAX_SAFE_INTEGER}},Le=[4,8,16];export function resolveOptions(e){const i=Object.fromEntries(Object.entries(e).filter(([,f])=>f!==void 0)),s={...I,...i},n=[],t=[];for(const f of["significanceLevel","minimumEffectSize","minimumRelativeEffect"]){const{min:l,max:o}=gt[f],u=s[f];if(typeof u!="number"||!Number.isFinite(u)){t.push(`${f}=${String(u)} is not a finite number (using ${I[f]})`),s[f]=I[f];continue}const a=Math.min(o,Math.max(l,u));a!==u&&(t.push(`${f}=${u} is outside ${l}\u2013${o} (using ${a})`),s[f]=a)}if(Le.includes(s.sectorCount)||(t.push(`sectorCount=${s.sectorCount} is not one of ${Le.join(", ")} (using ${I.sectorCount})`),s.sectorCount=I.sectorCount),t.length){const f=`analyzeWaferMap: ${t.join("; ")}. The analysis ran with the corrected values.`;console.warn(`[wafermap] ${f}`),n.push({code:"analysis-option-corrected",message:f,severity:"warning"})}const c=["passBins","ringCount"].filter(f=>i[f]!==void 0);if(c.length){const f=`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] ${f}`),n.push({code:"analysis-option-corrected",message:f,severity:"warning"})}const r=["enableYieldAnalysis","enableHardBinAnalysis","enableSoftBinAnalysis","enableReticlePositionAnalysis","enableTestSiteAnalysis","enableClusterAnalysis","enableAngularAnalysis","enablePatternClassification"].filter(f=>i[f]!==void 0);if(r.length){const f=`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] ${f}`),n.push({code:"analysis-option-corrected",message:f,severity:"warning"})}return{resolved:s,warnings:n}}const I={ringCount:4,passBins:[1],significanceLevel:.05,minimumEffectSize:.2,minimumRelativeEffect:1,minimumSampleSize:5,enableTestValueAnalysis:!1,computePerTestStats:!1,sectorCount:8,minimumClusterSize:5};function bt(e,i){const s={...e};return s.minimumClusterSize=Math.max(5,Math.round(i*.003)),s}function yt(e){return de(e)?e.hbin!==void 0||e.sbin!==void 0||et(e):!1}function St(e,i,s){if(!e||i)return;const n=(s??[]).filter(t=>Y(t)&&(t.limitLow!==void 0||t.limitHigh!==void 0));if(n.length!==0)return t=>{for(const c of n)if(he(re(j(t,c.testNumber),c)))return!0;return!1}}function wt(e,i,s){const n=new Set,t=new Set,c=new Set,r=new Map,f=new Map;for(const l of Pe(e))n.add(l);for(const l of e)l.hbin!==void 0&&t.add(l.hbin),l.sbin!==void 0&&c.add(l.sbin),de(l)&&(l.hbin!==void 0&&r.set(l.hbin,(r.get(l.hbin)??0)+1),l.sbin!==void 0&&f.set(l.sbin,(f.get(l.sbin)??0)+1));return{totalDies:e.length,analyzedDies:i,excludedDies:e.length-i,yieldPercent:s,testsConsidered:[...n].sort((l,o)=>l-o),hardBinsConsidered:[...t].sort((l,o)=>l-o),softBinsConsidered:[...c].sort((l,o)=>l-o),...r.size?{hardBinCounts:Object.fromEntries(r)}:{},...f.size?{softBinCounts:Object.fromEntries(f)}:{}}}function vt(e,i){if(!i?.length)return;const s=i.filter(t=>Y(t)&&(t.limitLow!==void 0||t.limitHigh!==void 0));if(!s.length)return;const n=[];for(const t of s){const c=t.testNumber;let r=0,f=0,l=0,o=0;for(const u of e){if(u.partial||u.edgeExcluded)continue;const a=re(j(u,c),t);a!==null&&(o++,a==="failLow"?f++:a==="failHigh"?l++:r++)}n.push({testNumber:c,label:t.name,passDies:r,failLowDies:f,failHighDies:l,totalDies:o,yieldPercent:o>0?r/o*100:null})}return n.length?n:void 0}export function computeFunctionalYield(e,i){if(!i?.length)return;const s=i.filter(t=>!Y(t));if(!s.length)return;const n=[];for(const t of s){const c=t.testNumber;let r=0,f=0;for(const o of e){if(o.partial||o.edgeExcluded)continue;const u=se(o,c,t);u!==void 0&&(u?r++:f++)}const l=r+f;n.push({testNumber:c,label:T(t,c),...oe(t),passDies:r,failDies:f,totalDies:l,passRatePercent:l>0?r/l*100:null})}return n.length?n:void 0}function $t(e,i,s,n){const t=[];for(const c of i){const r=[];for(const a of e){if(a.partial||a.edgeExcluded)continue;const h=j(a,c);h!==void 0&&Number.isFinite(h)&&r.push(h)}if(r.length<n)continue;const f=ae(r),l=Math.sqrt(ze(r,f)),o=pt(Float64Array.from(r)),u=s?.find(a=>a.testNumber===c)?.name??String(c);t.push({testNumber:c,label:u,count:r.length,min:o.min,max:o.max,mean:f,stddev:l,median:o.median,q1:o.q1,q3:o.q3})}return t.length?t:void 0}export function twoProportionZ(e,i,s,n){const t=(e+s)/(i+n),c=t*(1-t)*(1/i+1/n);return!Number.isFinite(c)||c<=0?0:(e/i-s/n)/Math.sqrt(c)}export function zPValue(e){return Ve(2*(1-Ce(Math.abs(e))))}function O(e,i,s,n){const t=twoProportionZ(e,i,s,n);return t===0?1:zPValue(t)}export function adjustPValues(e){const i=new Map;for(const s of e){const n=`${s.variable.kind}:${s.comparison.family}`,t=i.get(n)??[];t.push(s),i.set(n,t)}for(const s of i.values()){const n=s.filter(c=>c.stats.pValue!==void 0),t=mt(n.map(c=>c.stats.pValue));n.forEach((c,r)=>{c.stats.adjustedPValue=t[r]})}return e}export function rateRelativeEffect(e){const i=(e.passRate?e.n-e.hits:e.hits)/e.n,s=(e.passRate?e.restN-e.restHits:e.restHits)/e.restN;return s===0?i===0?0:1/0:Math.abs(i-s)/s}export function severityForFinding(e,i,s){const n=Math.abs(i),t=s;return e<=.01&&(n>=.3||t>=2.5)?"unusual":e<=.05&&(n>=.2||t>=1.5)?"notable":"info"}export function severityForScore(e,i){return e<=.01&&Math.abs(i)>=.5?"unusual":e<=.05&&Math.abs(i)>=.15?"notable":"info"}function fe(e){return e==="reticle-position"?"other reticle positions":e==="test-site"?"other test sites":"the rest of the map"}function Q(e){return e==="reticle-position"?"Other reticle positions":e==="test-site"?"Other test sites":"Rest of map"}function ge(e,i,s){const n=(Math.abs(i)*100).toFixed(1);return`${e} yield is ${n} percentage points ${i>0?"higher":"lower"} than ${fe(s)}`}function be(e,i){return i==="quadrant"&&!e.startsWith("Quadrant")?`quadrant ${e}`:e}function Ke(e){return/^(Rings|Sectors|Quadrants)\b/.test(e)?"have":"has"}function ye(e,i,s,n){const t=be(e,n),c=(Math.abs(s)*100).toFixed(1);return`${t} ${Ke(t)} ${i} occurrence ${c} percentage points ${s>0?"higher":"lower"} than ${fe(n)}`}function Se(e,i,s,n){const t=be(e,n),c=(Math.abs(s)*100).toFixed(1);return`${t} ${Ke(t)} ${i} pass rate ${c} percentage points ${s>0?"higher":"lower"} than ${fe(n)}`}function we(e,i,s,n,t,c){const r=be(e,t),f=fe(t),l=s>0?"higher":"lower";if(n!==void 0&&Number.isFinite(n)){const u=(Math.abs(n)*100).toFixed(1);return`${r} mean ${i} is ${u}% ${l} than ${f}`}const o=c?` ${c}`:"";return`${r} mean ${i} is ${Math.abs(s).toPrecision(3)}${o} ${l} than ${f}`}function Ee(e,i,s){const n=i?.find(t=>t.bin===e);return n?.name?`${s} ${e} (${n.name})`:`${s} ${e}`}function zt(e,i){const s=i?.find(n=>n.testNumber===e);return{label:T(s,e),unit:s?.unit,...oe(s)}}const Oe=new WeakMap,Te=new WeakMap,qe=new WeakMap;function Z(e,i){const s=i.highlight.dieKeys;return s&&qe.set(s,e),i}let U=null,J;const He=new WeakMap;export function candidatesOf(e){return He.get(e)}function Ae(e,i,s,n){if(!U)return;const t=je(e);if(!t)return;const c=e.id.slice(0,e.id.indexOf(":")),r=c==="specLimit"?"limitFail":c,{kind:f,bin:l,index:o}=e.variable;U.push({key:[r,f,l??"",o??"",t.family,t.key].join("|"),source:r,variable:e.variable,label:e.variable.label,comparison:e.comparison,region:t,z:i,delta:e.effect.absoluteDelta??0,effectSize:e.effect.effectSize??0,relativeDelta:e.effect.relativeDelta,rate:s,mean:n})}export function describeRegional(e,i,s){const n=e.comparison.left,t=e.region.family;switch(e.source){case"yield":return ge(n,i,t);case"hardBin":case"softBin":return ye(n,e.label,i,t);case"functional":return Se(n,e.label.replace(/ pass rate$/,""),i,t);case"limitFail":return ve(n,e.label,i);case"test":return we(n,e.label,i,s,t,e.variable.unit)}}function ve(e,i,s){return`${e} limit fail rate for ${i} is ${(Math.abs(s)*100).toFixed(1)} pp ${s>0?"higher":"lower"} than the rest of the wafer`}const G=new WeakMap;function ce(e,i){return G.set(e,i),e}function je(e){const i=e.highlight.dieKeys;return i?qe.get(i):void 0}const Ie=new WeakMap;function W(e){let i=Ie.get(e);return i||Ie.set(e,i=e.map(me)),i}function kt(e){let i=Oe.get(e);return i||Oe.set(e,i=e.map(Ne)),i}function le(e,i){let s=Te.get(e);s||Te.set(e,s={members:new Set(e),byFamily:new WeakMap});const n=s.byFamily.get(i);if(n)return n;const{members:t}=s,c=new Map;for(const r of i)c.set(r.key,r.dies.filter(f=>t.has(f)));return s.byFamily.set(i,c),c}function ue(e,i){adjustPValues(e);for(const n of e){const t=G.get(n);Ae(n,twoProportionZ(t.hits,t.n,t.restHits,t.restN),t)}const s=e.filter(n=>passesRateGate(n.stats.adjustedPValue??n.stats.pValue??1,n.effect.absoluteDelta??0,rateRelativeEffect(G.get(n)),i));return s.filter(n=>!Dt(n,s,i)).map(n=>({...n,severity:severityForFinding(n.stats.adjustedPValue??n.stats.pValue??1,n.effect.absoluteDelta??0,rateRelativeEffect(G.get(n)))}))}export function passesRateGate(e,i,s,n){return e<=n.significanceLevel&&(Math.abs(i)>=n.minimumEffectSize||s>=n.minimumRelativeEffect)}export function claimForPattern(e,i,s,n,t){const c=[];for(const r of s){const f=r.comparison.family,l=f==="ring"?n(r):[];e.some(o=>lt(o,f,l,t))&&(c.push(r.id),i!=="info"&&(f==="ring"||f==="quadrant"||f==="sector")&&(r.severity="info"))}return c}function Dt(e,i,s){const n=G.get(e);if(!n)return!1;const t=e.stats.pValue??1;let{restHits:c,restN:r}=n;for(const u of We(e,i)){const a=G.get(u);a&&(c-=a.hits,r-=a.n)}if(r===n.restN)return!1;if(r<s.minimumSampleSize)return!0;const f=n.hits/n.n-c/r;if(Math.sign(f)!==Math.sign(e.effect.absoluteDelta??0))return!0;const l=t>0?(e.stats.adjustedPValue??t)/t:1,o=Math.min(1,O(n.hits,n.n,c,r)*l);return!passesRateGate(o,f,rateRelativeEffect({...n,restHits:c,restN:r}),s)}function We(e,i){const s=e.stats.pValue??1;return i.filter(n=>n!==e&&n.effect.direction!==e.effect.direction&&n.variable.kind===e.variable.kind&&n.variable.bin===e.variable.bin&&n.variable.index===e.variable.index&&n.comparison.family===e.comparison.family&&(n.stats.pValue??1)<s)}const $e=new WeakMap;export function welchOfSums(e,i,s,n){const t=(c,r,f)=>f>1?Math.max(0,c-r*r/f)/(f-1):0;return welchFromStats(e.n,e.sum/e.n,t(e.sq,e.sum,e.n),i,s/i,t(n,s,i))}function Rt(e,i,s){const n=$e.get(e);if(!n)return!1;let{restN:t,restSum:c,restSq:r}=n;for(const h of We(e,i)){const g=$e.get(h);g&&(t-=g.n,c-=g.sum,r-=g.sq)}if(t===n.restN)return!1;if(t<s.minimumSampleSize)return!0;const{pValue:f,effectSize:l,delta:o}=welchOfSums(n,t,c,r);if(Math.sign(o)!==Math.sign(e.effect.absoluteDelta??0))return!0;const u=e.stats.pValue??1,a=u>0?(e.stats.adjustedPValue??u)/u:1;return!(Math.min(1,f*a)<=s.significanceLevel&&Math.abs(l)>=s.minimumEffectSize)}function ee(e,i,s,n){const t=new Set(s),c=le(e,i),r=new Map,f=new Map;for(const[o,u]of c){let a=0,h=0;for(const g of u)g.hbin!==void 0&&(h++,t.has(g.hbin)&&a++);r.set(o,a),f.set(o,h)}const l=[];for(const o of i){const u=f.get(o.key)??0,a=r.get(o.key)??0;let h=0,g=0;for(const[k,S]of f)k!==o.key&&(h+=S,g+=r.get(k)??0);if(u<n.minimumSampleSize||h<n.minimumSampleSize)continue;const p=a/u,d=g/h,y=p-d,w=O(a,u,g,h);l.push(ce(Z(o,{id:`yield:${o.key}`,level:"wafer",severity:"info",variable:{kind:"yield",label:"Yield"},comparison:{family:o.family,left:o.label,right:Q(o.family)},effect:{direction:y===0?"different":y>0?"higher":"lower",absoluteDelta:y,relativeDelta:d===0?void 0:y/d,effectSize:y},stats:{method:"two-proportion-z",pValue:w,sampleSizeLeft:u,sampleSizeRight:h},summary:ge(o.label,y,o.family),highlight:{kind:"region",regionFamily:o.family,regionKeys:[o.key],dieKeys:[...o.dieKeys]}}),{hits:a,n:u,restHits:g,restN:h,passRate:!0}))}return ue(l,n)}function E(e,i,s,n,t,c){const r=p=>s==="soft"?p.sbin:p.hbin,f=[...new Set(e.map(r).filter(p=>p!==void 0))].sort((p,d)=>p-d),l=le(e,i),o=new Map,u=new Map;for(const[p,d]of l){const y=new Map;for(const w of d){const k=r(w);k!==void 0&&y.set(k,(y.get(k)??0)+1)}o.set(p,y),u.set(p,d.length)}const a=[],h=t==="hardBin"?"HBin":"SBin",g=s==="hard"?new Set(c.passBins):c.softPassBins??Me(e,c.passBins).soft;for(const p of i){const d=u.get(p.key)??0,y=o.get(p.key);let w=0;const k=new Map;for(const[S,$]of o)if(S!==p.key){w+=u.get(S)??0;for(const[N,F]of $)k.set(N,(k.get(N)??0)+F)}if(!(d<c.minimumSampleSize||w<c.minimumSampleSize))for(const S of f){const $=y.get(S)??0,N=k.get(S)??0,F=$/d,b=N/w,v=F-b,M=O($,d,N,w),z=Ee(S,n,h);a.push(ce(Z(p,{id:`${t}:${S}:${p.key}`,level:"wafer",severity:"info",variable:{kind:t,bin:S,label:z},comparison:{family:p.family,left:p.label,right:Q(p.family)},effect:{direction:v===0?"different":v>0?"higher":"lower",absoluteDelta:v,relativeDelta:b===0?void 0:v/b,effectSize:v},stats:{method:"two-proportion-z",pValue:M,sampleSizeLeft:d,sampleSizeRight:w},summary:ye(p.label,z,v,p.family),highlight:{kind:"bin",bin:S,regionKeys:[p.key],dieKeys:[...p.dieKeys]}}),{hits:$,n:d,restHits:N,restN:w,passRate:g.has(S)}))}}return ue(a,c)}function te(e,i,s,n){const t=(s??[]).filter(u=>!Y(u));if(!t.length)return[];const c=t.map(u=>u.testNumber),r=le(e,i),f=new Map,l=new Map;for(const[u,a]of r){const h=new Uint32Array(t.length),g=new Uint32Array(t.length);for(const p of a)for(let d=0;d<t.length;d++){const y=se(p,c[d],t[d]);y!==void 0&&(g[d]++,y&&h[d]++)}f.set(u,h),l.set(u,g)}const o=[];for(const u of i){const a=f.get(u.key),h=l.get(u.key);for(let g=0;g<t.length;g++){let p=0,d=0;for(const[b,v]of l)b!==u.key&&(d+=v[g],p+=f.get(b)[g]);const y=h[g],w=a[g];if(y<n.minimumSampleSize||d<n.minimumSampleSize)continue;const k=w/y,S=p/d,$=k-S,N=O(w,y,p,d),F=c[g];o.push(ce(Z(u,{id:`functional:${F}:${u.key}`,level:"wafer",severity:"info",variable:{kind:"functionalTest",index:F,label:`${T(t[g],F)} pass rate`,...oe(t[g])},comparison:{family:u.family,left:u.label,right:Q(u.family)},effect:{direction:$===0?"different":$>0?"higher":"lower",absoluteDelta:$,relativeDelta:S===0?void 0:$/S,effectSize:$},stats:{method:"two-proportion-z",pValue:N,sampleSizeLeft:y,sampleSizeRight:d},summary:Se(u.label,T(t[g],F),$,u.family),highlight:{kind:"region",regionFamily:u.family,regionKeys:[u.key],dieKeys:[...u.dieKeys]}}),{hits:w,n:y,restHits:p,restN:d,passRate:!0}))}}return ue(o,n)}function ze(e,i){return e.length<2?0:e.reduce((s,n)=>s+(n-i)**2,0)/(e.length-1)}export function welchFromStats(e,i,s,n,t,c){const r=Math.sqrt(s/e+c/n),f=i-t;if(!Number.isFinite(r)||r===0)return{pValue:1,effectSize:0,delta:f,z:0};const l=f/r,o=Math.sqrt(Math.max(0,(s+c)/2)),u=o===0?f:f/o;return{pValue:Ve(2*(1-Ce(Math.abs(l)))),effectSize:u,delta:f,z:l}}function Mt(e,i){const s=ae(e),n=ae(i);return welchFromStats(e.length,s,ze(e,s),i.length,n,ze(i,n))}const Ye=250;function _e(e,i,s){const n=r=>r.filter(f=>Y(s?.find(l=>l.testNumber===f)));if(i)return{testNumbers:n(i.slice().sort((r,f)=>r-f))};const t=Pe(e,"values");if(t.length>Ye){const r=`Test-value analysis was skipped: more than ${Ye} 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:n(t)}}function ne(e,i,s,n){const t=_e(e,n.testNumbers,s);if(t.warning)return{findings:[],warning:t.warning};const c=t.testNumbers,r=new Map;for(let d=0;d<i.length;d++)for(const y of W(i[d].dies))r.set(y,d);const f=i.length,l=[],o=[];for(const d of e){const y=r.get(me(d));y!==void 0&&(l.push(d),o.push(y))}const u=[],a=new Float64Array(f),h=new Float64Array(f),g=new Float64Array(f);for(const d of c){a.fill(0),h.fill(0),g.fill(0);let y=0,w=0,k=0,S;for(let $=0;$<l.length;$++){const N=l[$],F=j(N,d);if(F===void 0)continue;S===void 0&&(S=F);const b=F-S,v=o[$];a[v]+=1,h[v]+=b,g[v]+=b*b,y+=1,w+=b,k+=b*b}if(S!==void 0)for(let $=0;$<f;$++){const N=a[$],F=y-N;if(N<n.minimumSampleSize||F<n.minimumSampleSize)continue;const b=h[$]/N,M=(w-h[$])/F,z=b+S,D=M+S,C=N>1?Math.max(0,g[$]-N*b*b)/(N-1):0,m=F>1?Math.max(0,k-g[$]-F*M*M)/(F-1):0,{pValue:R,effectSize:P,delta:B,z:x}=welchFromStats(N,z,C,F,D,m),V=i[$],{label:L,unit:q,...H}=zt(d,s),K=D!==0?B/Math.abs(D):void 0,A=Z(V,{id:`test:${d}:${V.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:d,label:L,unit:q,...H},comparison:{family:V.family,left:V.label,right:Q(V.family)},effect:{direction:B===0?"different":B>0?"higher":"lower",absoluteDelta:B,relativeDelta:K,effectSize:P},stats:{method:"welch-z-approx",pValue:R,sampleSizeLeft:N,sampleSizeRight:F},summary:we(V.label,L,B,K,V.family,q),highlight:{kind:"region",regionFamily:V.family,regionKeys:[V.key],dieKeys:[...V.dieKeys]}}),Re={n:N,sum:h[$],sq:g[$],restN:F,restSum:w-h[$],restSq:k-g[$],shift:S};$e.set(A,Re),Ae(A,x,void 0,Re),u.push(A)}}adjustPValues(u);const p=u.filter(d=>{const y=d.stats.adjustedPValue??d.stats.pValue??1,w=Math.abs(d.effect.effectSize??0);return y<=n.significanceLevel&&w>=n.minimumEffectSize});return{activeTestNumbers:c,findings:p.filter(d=>!Rt(d,p,n)).map(d=>({...d,severity:severityForScore(d.stats.adjustedPValue??d.stats.pValue??1,d.effect.effectSize??0)}))}}function Bt(e,i,s,n){if(!s?.length)return[];const t=s.filter(r=>Y(r)&&(r.limitLow!==void 0||r.limitHigh!==void 0));if(!t.length)return[];const c=[];for(const r of t){const f=r.testNumber;for(const l of i){const o=le(e,l),u=[],a=new Map;let h=0,g=0;for(const[p,d]of o){let y=0,w=0;for(const k of d){const S=j(k,f);S!==void 0&&(y++,he(re(S,r))&&w++)}a.set(p,{valid:y,fail:w}),h+=y,g+=w}for(const p of l){const d=a.get(p.key),y=d.valid,w=h-d.valid;if(y<n.minimumSampleSize||w<n.minimumSampleSize)continue;const k=d.fail,S=g-d.fail,$=k/y,N=S/w,F=$-N,b=O(k,y,S,w);u.push(ce(Z(p,{id:`specLimit:${f}:${p.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:f,label:T(r,f),unit:r.unit,...oe(r)},comparison:{family:p.family,left:p.label,right:Q(p.family)},effect:{direction:F===0?"different":F>0?"higher":"lower",absoluteDelta:F,relativeDelta:N===0?void 0:F/N,effectSize:F},stats:{method:"two-proportion-z",pValue:b,sampleSizeLeft:y,sampleSizeRight:w},summary:ve(p.label,T(r,f),F),highlight:{kind:"region",regionFamily:p.family,regionKeys:[p.key],dieKeys:[...p.dieKeys]}}),{hits:k,n:y,restHits:S,restN:w,passRate:!1}))}c.push(...ue(u,n))}}return c}function ke(e,i){return e==="softBin"?i.softEligibleDies:e==="test"?i.testDies:i.eligibleDies}function Nt(e){return e.id.startsWith("specLimit:")}function ie(e){return e.highlight.regionKeys??[]}function Ft(e){const i=e.variable.bin??e.variable.index??"";return`${e.comparison.family}\0${Qe(e)}\0${e.variable.kind}\0${i}\0${e.effect.direction}`}function Pt(e,i){const s=_(e);return s.family==="ring"?s.ring??0:s.family==="sector"?xe(i).indexOf(s.sector??""):0}export function inQuadrantOrder(e){const i=new Set(e),s=pe.findIndex((t,c)=>i.has(t)&&!i.has(pe[(c+3)%4])),n=s<0?0:s;return Array.from({length:4},(t,c)=>pe[(n+c)%4]).filter(t=>i.has(t))}export function findContiguousRuns(e,i,s,n){if(i==="quadrant"){const l=new Map;for(const h of e){const g=_(n(h)).quadrant;g&&l.set(g,h)}const o=[...l.keys()],u=new Set,a=[];for(const h of o){if(u.has(h))continue;const g=[],p=[h];for(u.add(h);p.length;){const d=p.pop();g.push(d);for(const y of o)!u.has(y)&&at(d,y)&&(u.add(y),p.push(y))}a.push(inQuadrantOrder(g).map(d=>l.get(d)))}return a}const t=l=>Pt(n(l),s),c=[...e].sort((l,o)=>t(l)-t(o)),r=[];let f=[];for(const l of c){if(f.length===0){f.push(l);continue}const o=t(f[f.length-1]);t(l)-o===1?f.push(l):(r.push(f),f=[l])}if(f.length&&r.push(f),i==="sector"&&r.length>1){const o=xe(s).length,u=t(r[0][0]),a=t(r[r.length-1][r[r.length-1].length-1]);if(u===0&&a===o-1){const h=r.pop();r[0]=[...h,...r[0]]}}return r}export function mergedRegionLabel(e,i,s){if(e==="ring"){const t=i.map(l=>_(l).ring).filter(l=>l!==void 0).sort((l,o)=>l-o),c=t[0],r=t[t.length-1];let f="";return c===r&&c===1&&s>1?f=" (core)":c===r&&c===s&&s>1&&(f=" (edge)"),c===r?`Ring ${c}${f}`:`Rings ${c}\u2013${r}`}if(e==="sector"){const t=i.map(c=>_(c).sector??"");return t.length===1?`Sector ${t[0]}`:`Sectors ${t[0]}\u2013${t[t.length-1]}`}const n=i.map(t=>_(t).quadrant??"");return n.length===1?n[0]:n.length===2?`Quadrants ${n[0]} & ${n[1]}`:`Quadrants ${n.slice(0,-1).join(", ")} & ${n[n.length-1]}`}function De(e){return[...new Set(e)]}export function weakestMultiplier(e){return ht(e.map(i=>{const s=i.stats.pValue??1;return s>0?(i.stats.adjustedPValue??s)/s:1}))}function X(e,i){return Math.min(1,e*i)}function xt(e,i){const s=e[0],n=s.comparison.family,t=s.variable.kind,c=De(e.flatMap(ie));let r,f;const l=e.map(je);if(l.every(b=>b!==void 0)){const b=new Set;r=[];for(const M of l){const z=W(M.dies);for(let D=0;D<z.length;D++)b.has(z[D])||(b.add(z[D]),r.push(M.dieKeys[D]))}const v=W(ke(s.variable.kind,i));f=M=>b.has(v[M])}else{r=De(e.flatMap(M=>M.highlight.dieKeys??[]));const b=new Set(r),v=kt(ke(s.variable.kind,i));f=M=>b.has(v[M])}const o=mergedRegionLabel(n,e.map(b=>ie(b)[0]??""),i.ringCount),u=ke(t,i),a=i.regionDies[n],h=W(u),g=[],p=[];for(let b=0;b<u.length;b++)a.has(h[b])&&(f(b)?g:p).push(u[b]);const d=weakestMultiplier(e);let y,w,k,S,$;if(Nt(s)){const b=s.variable.index,v=i.testDefs?.find(x=>x.testNumber===b),M=x=>{let V=0,L=0;for(const q of x){const H=j(q,b);H!==void 0&&(V++,v&&he(re(H,v))&&L++)}return{valid:V,fail:L}},z=M(g),D=M(p),C=z.valid>0?z.fail/z.valid:0,m=D.valid>0?D.fail/D.valid:0,R=C-m,P=O(z.fail,z.valid,D.fail,D.valid);y={direction:R===0?"different":R>0?"higher":"lower",absoluteDelta:R,relativeDelta:m===0?void 0:R/m,effectSize:R},w={method:"two-proportion-z",pValue:P,sampleSizeLeft:z.valid,sampleSizeRight:D.valid};const B=X(P,d);k=severityForFinding(B,R,rateRelativeEffect({hits:z.fail,n:z.valid,restHits:D.fail,restN:D.valid,passRate:!1})),S=ve(o,T(v,b),R),$=`specLimit:${b}`}else if(t==="test"){const b=s.variable.index,v=x=>j(x,b),M=g.map(v).filter(x=>x!==void 0),z=p.map(v).filter(x=>x!==void 0),{pValue:D,effectSize:C,delta:m}=Mt(M,z),R=ae(z),P=R!==0?m/Math.abs(R):void 0;y={direction:m===0?"different":m>0?"higher":"lower",absoluteDelta:m,relativeDelta:P,effectSize:C},w={method:"welch-z-approx",pValue:D,sampleSizeLeft:M.length,sampleSizeRight:z.length};const B=X(D,d);k=severityForScore(B,C),S=we(o,s.variable.label,m,P,n,s.variable.unit),$=`test:${b}`}else if(t==="yield"){const b=new Set(i.passBins),v=g.filter(L=>L.hbin!==void 0),M=p.filter(L=>L.hbin!==void 0),z=v.length,D=M.length,C=v.filter(L=>b.has(L.hbin)).length,m=M.filter(L=>b.has(L.hbin)).length,R=z>0?C/z:0,P=D>0?m/D:0,B=R-P,x=O(C,z,m,D);y={direction:B===0?"different":B>0?"higher":"lower",absoluteDelta:B,relativeDelta:P===0?void 0:B/P,effectSize:B},w={method:"two-proportion-z",pValue:x,sampleSizeLeft:z,sampleSizeRight:D};const V=X(x,d);k=severityForFinding(V,B,rateRelativeEffect({hits:C,n:z,restHits:m,restN:D,passRate:!0})),S=ge(o,B,n),$="yield"}else if(t==="functionalTest"){const b=s.variable.index,v=i.testDefs?.find(V=>V.testNumber===b);let M=0,z=0,D=0,C=0;for(const V of g){const L=se(V,b,v);L!==void 0&&(z++,L&&M++)}for(const V of p){const L=se(V,b,v);L!==void 0&&(C++,L&&D++)}const m=z>0?M/z:0,R=C>0?D/C:0,P=m-R,B=O(M,z,D,C);y={direction:P===0?"different":P>0?"higher":"lower",absoluteDelta:P,relativeDelta:R===0?void 0:P/R,effectSize:P},w={method:"two-proportion-z",pValue:B,sampleSizeLeft:z,sampleSizeRight:C};const x=X(B,d);k=severityForFinding(x,P,rateRelativeEffect({hits:M,n:z,restHits:D,restN:C,passRate:!0})),S=Se(o,T(v,b),P,n),$=`functional:${b}`}else{const b=s.variable.bin,v=x=>t==="softBin"?x.sbin:x.hbin,M=g.filter(x=>v(x)===b).length,z=p.filter(x=>v(x)===b).length,D=M/g.length,C=z/p.length,m=D-C,R=O(M,g.length,z,p.length);y={direction:m===0?"different":m>0?"higher":"lower",absoluteDelta:m,relativeDelta:C===0?void 0:m/C,effectSize:m},w={method:"two-proportion-z",pValue:R,sampleSizeLeft:g.length,sampleSizeRight:p.length};const P=X(R,d);k=severityForFinding(P,m,rateRelativeEffect({hits:M,n:g.length,restHits:z,restN:p.length,passRate:!1}));const B=t==="softBin"?i.sbinDefs:i.hbinDefs;S=ye(o,Ee(b,B,t==="softBin"?"SBin":"HBin"),m,n),$=`${t}:${b}`}w={...w,adjustedPValue:X(w.pValue??1,d)};const N=De(c.map(b=>b.replace(`${n}:`,""))).join("-"),F=t==="hardBin"||t==="softBin"?{kind:"bin",bin:s.variable.bin,regionKeys:c,dieKeys:r}:{kind:"region",regionFamily:n,regionKeys:c,dieKeys:r};return{...s,id:`${$}:${n}:${N}`,severity:k,comparison:{family:n,left:o,right:Q(n)},effect:y,stats:w,summary:S,highlight:F,relatedIds:e.map(b=>b.id)}}function Ct(e,i){const s=new Set(["ring","quadrant","sector"]),n=[],t=new Map;for(const r of e){if(!s.has(r.comparison.family)){n.push(r);continue}const f=Ft(r),l=t.get(f)??[];l.push(r),t.set(f,l)}const c=[];for(const r of t.values()){if(r.length===1){c.push(r[0]);continue}const f=r[0].comparison.family;for(const l of findContiguousRuns(r,f,i.sectorCount,o=>ie(o)[0]??""))c.push(l.length===1?l[0]:xt(l,i))}return[...n,...c]}function Vt(e){const i=new Map,s=new Map;for(const t of e){const c=me(t);t.hbin!==void 0&&t.hbin!==null&&(i.get(t.hbin)??i.set(t.hbin,new Set).get(t.hbin)).add(c),t.sbin!==void 0&&t.sbin!==null&&(s.get(t.sbin)??s.set(t.sbin,new Set).get(t.sbin)).add(c)}const n=new Set;for(const[t,c]of i)for(const[r,f]of s){if(c.size!==f.size)continue;let l=!0;for(const o of c)if(!f.has(o)){l=!1;break}l&&n.add(`${t}:${r}`)}return n}function Lt(e,i){const s=new Set(i);let n;for(const t of e){const c=Be(t,s);if(c!==void 0&&c){if(t.sbin==null||n!==void 0&&t.sbin!==n)return;n=t.sbin}}if(n!==void 0){for(const t of e)if(t.sbin===n&&Be(t,s)!==!0)return;return n}}function Kt(e){return`${e.comparison.family}\0${e.comparison.left}`}export function redundancyFacts(e,i,s){return{coincident:Vt(e),yieldSoftBin:Lt(e,i),passBins:i,sectorCount:s}}const Et=3/5;function Qe(e){return/^(?:lot-region:)?([^:|]+)/.exec(e.id)?.[1]??e.id}function Ot(e){const i=ie(e);if(i.length)return i;const s=/(ring|sector|quadrant):([^:|]+)$/.exec(e.id);return s?s[2].split("-").map(n=>`${s[1]}:${n}`):[]}function Ue(e,i){const s=new Set;for(const n of Ot(e)){const t=rt(n,i);if(!t)return;for(const c of t)s.add(c)}return s.size?s:void 0}export function collapseRedundantFindings(e,i){const{coincident:s,yieldSoftBin:n,passBins:t}=i,c=(o,u)=>{o.absorbedIds=[...o.absorbedIds??[],u.id]},r=new Map;for(const o of e){const u=Kt(o);(r.get(u)??r.set(u,[]).get(u)).push(o)}const f=new Set;for(const o of r.values()){for(const a of o)if(!(a.variable.kind!=="hardBin"||a.variable.bin===void 0))for(const h of o){if(h.variable.kind!=="softBin"||h.variable.bin===void 0||f.has(h.id)||!s.has(`${a.variable.bin}:${h.variable.bin}`)||h.effect.direction!==a.effect.direction)continue;c(a,h),f.add(h.id);const g=a.variable.label.replace(/^HBin\s+/,""),p=h.variable.label.replace(/^SBin\s+/,""),d=g===p?`HBin and SBin ${g} (same dies)`:`${a.variable.label} and ${h.variable.label} (same dies)`;a.summary=a.summary.replace(a.variable.label,d),a.variable.label=d;break}const u=o.find(a=>a.variable.kind==="yield");if(u){for(const a of o)if(!(a===u||f.has(a.id)||!(a.variable.kind==="hardBin"&&t.length===1&&a.variable.bin===t[0]||a.variable.kind==="softBin"&&a.variable.bin===n))){c(u,a),f.add(a.id);for(const g of a.absorbedIds??[])u.absorbedIds=[...u.absorbedIds??[],g]}}}const l=new Map;for(const o of e){if(f.has(o.id)||o.comparison.family!=="sector"&&o.comparison.family!=="quadrant")continue;const u=[Qe(o),o.variable.kind,o.variable.bin??"",o.variable.index??"",o.effect.direction].join("\0");(l.get(u)??l.set(u,[]).get(u)).push(o)}for(const o of l.values()){const u=[];for(const a of o.filter(h=>h.comparison.family==="sector")){const h=Ue(a,i.sectorCount);if(h)for(const g of o.filter(p=>p.comparison.family==="quadrant")){const p=Ue(g,i.sectorCount);if(!p)continue;let d=0;for(const w of h)p.has(w)&&d++;const y=h.size+p.size-d;d/y>=Et-1e-9&&u.push({sector:a,quadrant:g,overlap:d/y})}}u.sort((a,h)=>h.overlap-a.overlap);for(const{sector:a,quadrant:h}of u){if(f.has(a.id)||f.has(h.id))continue;const g=(h.stats.pValue??1)<(a.stats.pValue??1),[p,d]=g?[h,a]:[a,h];c(p,d),f.add(d.id),p.absorbedIds=[...p.absorbedIds??[],...d.absorbedIds??[]]}}}export function analyzeWaferMap(e,i={}){const s=U,n=J,t=[];U=t,J=void 0;try{const c=Tt(e,i);return He.set(c,{candidates:t,facts:J}),c}finally{U=s,J=n}}function Tt(e,i){const{resolved:s,warnings:n}=resolveOptions(i),t=Ge(e),c={...s,passBins:t.passBins??I.passBins,ringCount:t.ringCount??I.ringCount},r=t.isLotStack,f=t.aggrMethod,l=!r||f==="mode"||f==="countBin"||f==="percent",o=t.dies.filter(m=>yt(m)),u=o.filter(Fe),a=bt(c,o.length);a.softPassBins=Me(o,a.passBins).soft;const h=t.dies.filter(m=>de(m)),g=h.filter(Fe),p=it(g,t.wafer,a.ringCount),d=tt(g,t.wafer,a.ringCount),y=nt(g,t.reticleConfig),w=ot(h,!1),k=st(g,t.wafer,a.sectorCount),S=[];l&&S.push(...ee(o,p,a.passBins,a),...ee(o,d,a.passBins,a),...ee(o,y,a.passBins,a),...ee(o,w,a.passBins,a),...ee(o,k,a.passBins,a)),l&&S.push(...E(o,p,"hard",t.hbinDefs,"hardBin",a),...E(o,d,"hard",t.hbinDefs,"hardBin",a),...E(o,y,"hard",t.hbinDefs,"hardBin",a),...E(o,w,"hard",t.hbinDefs,"hardBin",a),...E(o,k,"hard",t.hbinDefs,"hardBin",a));{const m=o.filter(R=>R.sbin!==void 0);S.push(...E(m,p,"soft",t.sbinDefs,"softBin",a),...E(m,d,"soft",t.sbinDefs,"softBin",a),...E(m,y,"soft",t.sbinDefs,"softBin",a),...E(m,w,"soft",t.sbinDefs,"softBin",a),...E(m,k,"soft",t.sbinDefs,"softBin",a))}const $=[...n];let N;if(a.enableTestValueAnalysis){const m=ne(o,p,t.testDefs,a),R=ne(o,d,t.testDefs,a),P=ne(o,y,t.testDefs,a),B=ne(o,w,t.testDefs,a),x=ne(o,k,t.testDefs,a);S.push(...m.findings,...R.findings,...P.findings,...B.findings,...x.findings),m.warning&&$.push(m.warning),N=m.activeTestNumbers,S.push(...Bt(o,[p,d,y,w,k],t.testDefs,a)),S.push(...te(o,p,t.testDefs,a),...te(o,d,t.testDefs,a),...te(o,y,t.testDefs,a),...te(o,w,t.testDefs,a),...te(o,k,t.testDefs,a))}{const m=St(r,l,t.testDefs);(!r||l||m!==void 0)&&S.push(...ft(u,t.wafer,{...a,isFailingDie:m}))}const F=Ct(S,{eligibleDies:o,softEligibleDies:o.filter(m=>m.sbin!==void 0),testDies:o,passBins:a.passBins,ringCount:a.ringCount,sectorCount:a.sectorCount,regionDies:{ring:new Set(p.flatMap(m=>W(m.dies))),quadrant:new Set(d.flatMap(m=>W(m.dies))),sector:new Set(k.flatMap(m=>W(m.dies)))},hbinDefs:t.hbinDefs,sbinDefs:t.sbinDefs,testDefs:t.testDefs});S.length=0,S.push(...F);let b;if(l){const m=ct(u,t.wafer,{passBins:a.passBins,ringCount:a.ringCount});if(b=m??void 0,m!==null&&m.pattern!=="random"&&m.pattern!=="none"){const R=dt[m.pattern],P=m.confidence==="high"?"unusual":m.confidence==="medium"?"notable":"info",B=m.features,x=K=>`${(K*100).toFixed(0)}%`,V=m.pattern==="edge-ring"||m.pattern==="edge-local"?`${x(B.edgeRdd)} of edge dies failing`:m.pattern==="center"||m.pattern==="donut"?`centroid at ${x(B.centroidDistNorm)} of wafer radius from centre`:m.pattern==="scratch"?`linear score ${B.linearScore.toFixed(2)}, eccentricity ${B.eccentricity.toFixed(2)}`:`${x(B.globalRdd)} of dies failing`,L=new Set(a.passBins),q=o.filter(K=>ut(K,L)===!0),H=claimForPattern([m.pattern],P,S,K=>ie(K).map(A=>_(A).ring).filter(A=>A!==void 0),a.ringCount);S.push({id:`spatial-pattern:${m.pattern}`,level:"wafer",severity:P,variable:{kind:"spatialPattern",label:R},comparison:{family:"spatial-pattern",left:R,right:"Wafer"},effect:{direction:"different",effectSize:B.globalRdd},stats:{method:"geometry",sampleSizeLeft:q.length,sampleSizeRight:o.length},summary:`Spatial pattern: ${R.toLowerCase()} (${m.confidence} confidence) \u2014 ${V}${m.note?` [${m.note}]`:""}`,highlight:{kind:"dies",dieKeys:q.map(K=>Ne(K))},relatedIds:H.length>0?H:void 0})}}S.sort((m,R)=>{const P=m.stats.adjustedPValue??m.stats.pValue??1,B=R.stats.adjustedPValue??R.stats.pValue??1;return P!==B?P-B:Math.abs(R.effect.absoluteDelta??0)-Math.abs(m.effect.absoluteDelta??0)});const v=wt(t.dies,o.length,t.yield.yieldPercent);r&&(v.isLotStack=!0,f&&(v.aggregationMethod=f));const M=vt(t.dies,t.testDefs);M&&(v.testSpecYield=M);const z=computeFunctionalYield(t.dies,t.testDefs);if(z&&(v.functionalYield=z),b&&(v.spatialPattern=b),N===void 0&&a.computePerTestStats){const m=_e(o,a.testNumbers,t.testDefs);m.warning&&$.push(m.warning),N=m.testNumbers}if(N?.length){const m=$t(t.dies,N,t.testDefs,a.minimumSampleSize);m&&(v.perTestStats=m);const R=Xe([{dies:t.dies}],t.testDefs,N);R&&(v.capability=R)}Object.assign(v,Ze(t.dies,t.testDefs));const D=Je([{dies:t.dies,wafer:t.wafer,passBins:a.passBins,ringCount:a.ringCount}]);D&&(v.regionYield=D),$.length>0&&(v.warnings=$);const C=redundancyFacts(o,a.passBins,a.sectorCount);return collapseRedundantFindings(S,C),U&&(J=C),{level:"wafer",hasNotableFindings:S.some(m=>m.severity==="notable"||m.severity==="unusual"),findings:S,wafer:t.wafer.metadata??void 0,stats:v}}
1
+ import{normalizeInput as Ge}from"./normalizeInput.js";import{computeCapability as Xe,computeTestFlagYield as Ze,computeRegionYield as Je}from"./summaryFigures.js";import{binPassSets as Me}from"../renderer/binColors.js";import{diePassStatus as Be,isYieldEligibleDie as de,getDieKey as Ne,isPositionedDie as Fe,positionKey as me}from"../core/dies.js";import{dieHasValues as et,testsPresent as Pe,testValue as j}from"../core/dieTable.js";import{getTestPassStatus as se,isParametricTest as W}from"../renderer/buildWaferMap.js";import{markedTestLabel as T,derivedFields as oe}from"../renderer/testLabel.js";import{buildQuadrantRegions as tt,buildReticlePositionRegions as nt,buildRingRegions as it,buildSectorRegions as st,buildTestSiteRegions as ot,sectorCompassNames as xe,areQuadrantsAdjacent as at,parseRegionKey as Y,QUADRANT_CYCLE as pe,regionAngleBins as rt}from"./regions.js";import{buildClusterFindings as ft}from"./clusterDetection.js";import{classifyPattern as lt,patternExplains as ct,patternFailVerdict as ut,PATTERN_LABELS as dt}from"./patternClassification.js";import{requirePassBins as mt}from"../core/passBins.js";import{requireRingCount as pt}from"../core/ringCount.js";import{benjaminiHochberg as ht,fiveNumberSummary as gt,normalCdf as Ce,sampleVariance as he}from"./math.js";import{mean as ae,clamp01 as Ve,maxOf as bt}from"../core/utils.js";import{classifySpec as re,isOutOfSpec as ge}from"../renderer/spec.js";const yt={significanceLevel:{min:1e-6,max:1},minimumEffectSize:{min:0,max:1},minimumRelativeEffect:{min:0,max:Number.MAX_SAFE_INTEGER}},Le=[4,8,16];export function resolveOptions(t){const i=Object.fromEntries(Object.entries(t).filter(([,f])=>f!==void 0)),s={...X,...i},n=[],e=[];for(const f of["significanceLevel","minimumEffectSize","minimumRelativeEffect"]){const{min:c,max:o}=yt[f],u=s[f];if(typeof u!="number"||!Number.isFinite(u)){e.push(`${f}=${String(u)} is not a finite number (using ${X[f]})`),s[f]=X[f];continue}const a=Math.min(o,Math.max(c,u));a!==u&&(e.push(`${f}=${u} is outside ${c}\u2013${o} (using ${a})`),s[f]=a)}if(Le.includes(s.sectorCount)||(e.push(`sectorCount=${s.sectorCount} is not one of ${Le.join(", ")} (using ${X.sectorCount})`),s.sectorCount=X.sectorCount),e.length){const f=`analyzeWaferMap: ${e.join("; ")}. The analysis ran with the corrected values.`;console.warn(`[wafermap] ${f}`),n.push({code:"analysis-option-corrected",message:f,severity:"warning"})}const l=["passBins","ringCount"].filter(f=>i[f]!==void 0);if(l.length){const f=`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] ${f}`),n.push({code:"analysis-option-corrected",message:f,severity:"warning"})}const r=["enableYieldAnalysis","enableHardBinAnalysis","enableSoftBinAnalysis","enableReticlePositionAnalysis","enableTestSiteAnalysis","enableClusterAnalysis","enableAngularAnalysis","enablePatternClassification"].filter(f=>i[f]!==void 0);if(r.length){const f=`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] ${f}`),n.push({code:"analysis-option-corrected",message:f,severity:"warning"})}return{resolved:s,warnings:n}}const X={significanceLevel:.05,minimumEffectSize:.2,minimumRelativeEffect:1,minimumSampleSize:5,enableTestValueAnalysis:!1,computePerTestStats:!1,sectorCount:8,minimumClusterSize:5};function St(t,i){const s={...t};return s.minimumClusterSize=Math.max(5,Math.round(i*.003)),s}function wt(t){return de(t)?t.hbin!==void 0||t.sbin!==void 0||et(t):!1}function vt(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(ge(re(j(e,l.testNumber),l)))return!0;return!1}}function $t(t,i,s){const n=new Set,e=new Set,l=new Set,r=new Map,f=new Map;for(const c of Pe(t))n.add(c);for(const c of t)c.hbin!==void 0&&e.add(c.hbin),c.sbin!==void 0&&l.add(c.sbin),de(c)&&(c.hbin!==void 0&&r.set(c.hbin,(r.get(c.hbin)??0)+1),c.sbin!==void 0&&f.set(c.sbin,(f.get(c.sbin)??0)+1));return{totalDies:t.length,analyzedDies:i,excludedDies:t.length-i,yieldPercent:s,testsConsidered:[...n].sort((c,o)=>c-o),hardBinsConsidered:[...e].sort((c,o)=>c-o),softBinsConsidered:[...l].sort((c,o)=>c-o),...r.size?{hardBinCounts:Object.fromEntries(r)}:{},...f.size?{softBinCounts:Object.fromEntries(f)}:{}}}function zt(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 r=0,f=0,c=0,o=0;for(const u of t){if(u.partial||u.edgeExcluded)continue;const a=re(j(u,l),e);a!==null&&(o++,a==="failLow"?f++:a==="failHigh"?c++:r++)}n.push({testNumber:l,label:e.name,passDies:r,failLowDies:f,failHighDies:c,totalDies:o,yieldPercent:o>0?r/o*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 r=0,f=0;for(const o of t){if(o.partial||o.edgeExcluded)continue;const u=se(o,l,e);u!==void 0&&(u?r++:f++)}const c=r+f;n.push({testNumber:l,label:T(e,l),...oe(e),passDies:r,failDies:f,totalDies:c,passRatePercent:c>0?r/c*100:null})}return n.length?n:void 0}function kt(t,i,s,n){const e=[];for(const l of i){const r=[];for(const a of t){if(a.partial||a.edgeExcluded)continue;const h=j(a,l);h!==void 0&&Number.isFinite(h)&&r.push(h)}if(r.length<n)continue;const f=ae(r),c=Math.sqrt(he(r,f)),o=gt(Float64Array.from(r)),u=s?.find(a=>a.testNumber===l)?.name??String(l);e.push({testNumber:l,label:u,count:r.length,min:o.min,max:o.max,mean:f,stddev:c,median:o.median,q1:o.q1,q3:o.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 Ve(2*(1-Ce(Math.abs(t))))}function O(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(l=>l.stats.pValue!==void 0),e=ht(n.map(l=>l.stats.pValue));n.forEach((l,r)=>{l.stats.adjustedPValue=e[r]})}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 fe(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 be(t,i,s){const n=(Math.abs(i)*100).toFixed(1);return`${t} yield is ${n} percentage points ${i>0?"higher":"lower"} than ${fe(s)}`}function ye(t,i){return i==="quadrant"&&!t.startsWith("Quadrant")?`quadrant ${t}`:t}function Ke(t){return/^(Rings|Sectors|Quadrants)\b/.test(t)?"have":"has"}function Se(t,i,s,n){const e=ye(t,n),l=(Math.abs(s)*100).toFixed(1);return`${e} ${Ke(e)} ${i} occurrence ${l} percentage points ${s>0?"higher":"lower"} than ${fe(n)}`}function we(t,i,s,n){const e=ye(t,n),l=(Math.abs(s)*100).toFixed(1);return`${e} ${Ke(e)} ${i} pass rate ${l} percentage points ${s>0?"higher":"lower"} than ${fe(n)}`}function ve(t,i,s,n,e,l){const r=ye(t,e),f=fe(e),c=s>0?"higher":"lower";if(n!==void 0&&Number.isFinite(n)){const u=(Math.abs(n)*100).toFixed(1);return`${r} mean ${i} is ${u}% ${c} than ${f}`}const o=l?` ${l}`:"";return`${r} mean ${i} is ${Math.abs(s).toPrecision(3)}${o} ${c} than ${f}`}function Ee(t,i,s){const n=i?.find(e=>e.bin===t);return n?.name?`${s} ${t} (${n.name})`:`${s} ${t}`}function Dt(t,i){const s=i?.find(n=>n.testNumber===t);return{label:T(s,t),unit:s?.unit,...oe(s)}}const Oe=new WeakMap,Te=new WeakMap,qe=new WeakMap;function Z(t,i){const s=i.highlight.dieKeys;return s&&qe.set(s,t),i}let Q=null,J;const He=new WeakMap;export function candidatesOf(t){return He.get(t)}function Ae(t,i,s,n){if(!Q)return;const e=je(t);if(!e)return;const l=t.id.slice(0,t.id.indexOf(":")),r=l==="specLimit"?"limitFail":l,{kind:f,bin:c,index:o}=t.variable;Q.push({key:[r,f,c??"",o??"",e.family,e.key].join("|"),source:r,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 be(n,i,e);case"hardBin":case"softBin":return Se(n,t.label,i,e);case"functional":return we(n,t.label.replace(/ pass rate$/,""),i,e);case"limitFail":return $e(n,t.label,i);case"test":return ve(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 le(t,i){return U.set(t,i),t}function je(t){const i=t.highlight.dieKeys;return i?qe.get(i):void 0}const Ie=new WeakMap;function I(t){let i=Ie.get(t);return i||Ie.set(t,i=t.map(me)),i}function Rt(t){let i=Oe.get(t);return i||Oe.set(t,i=t.map(Ne)),i}function ce(t,i){let s=Te.get(t);s||Te.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 r of i)l.set(r.key,r.dies.filter(f=>e.has(f)));return s.byFamily.set(i,l),l}function ue(t,i){adjustPValues(t);for(const n of t){const e=U.get(n);Ae(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=>!Mt(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)}export function claimForPattern(t,i,s,n,e){const l=[];for(const r of s){const f=r.comparison.family,c=f==="ring"?n(r):[];t.some(o=>ct(o,f,c,e))&&(l.push(r.id),i!=="info"&&(f==="ring"||f==="quadrant"||f==="sector")&&(r.severity="info"))}return l}function Mt(t,i,s){const n=U.get(t);if(!n)return!1;const e=t.stats.pValue??1;let{restHits:l,restN:r}=n;for(const u of We(t,i)){const a=U.get(u);a&&(l-=a.hits,r-=a.n)}if(r===n.restN)return!1;if(r<s.minimumSampleSize)return!0;const f=n.hits/n.n-l/r;if(Math.sign(f)!==Math.sign(t.effect.absoluteDelta??0))return!0;const c=e>0?(t.stats.adjustedPValue??e)/e:1,o=Math.min(1,O(n.hits,n.n,l,r)*c);return!passesRateGate(o,f,rateRelativeEffect({...n,restHits:l,restN:r}),s)}function We(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,r,f)=>f>1?Math.max(0,l-r*r/f)/(f-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 Bt(t,i,s){const n=ze.get(t);if(!n)return!1;let{restN:e,restSum:l,restSq:r}=n;for(const h of We(t,i)){const g=ze.get(h);g&&(e-=g.n,l-=g.sum,r-=g.sq)}if(e===n.restN)return!1;if(e<s.minimumSampleSize)return!0;const{pValue:f,effectSize:c,delta:o}=welchOfSums(n,e,l,r);if(Math.sign(o)!==Math.sign(t.effect.absoluteDelta??0))return!0;const u=t.stats.pValue??1,a=u>0?(t.stats.adjustedPValue??u)/u:1;return!(Math.min(1,f*a)<=s.significanceLevel&&Math.abs(c)>=s.minimumEffectSize)}function ee(t,i,s,n){const e=new Set(s),l=ce(t,i),r=new Map,f=new Map;for(const[o,u]of l){let a=0,h=0;for(const g of u)g.hbin!==void 0&&(h++,e.has(g.hbin)&&a++);r.set(o,a),f.set(o,h)}const c=[];for(const o of i){const u=f.get(o.key)??0,a=r.get(o.key)??0;let h=0,g=0;for(const[k,S]of f)k!==o.key&&(h+=S,g+=r.get(k)??0);if(u<n.minimumSampleSize||h<n.minimumSampleSize)continue;const p=a/u,d=g/h,y=p-d,w=O(a,u,g,h);c.push(le(Z(o,{id:`yield:${o.key}`,level:"wafer",severity:"info",variable:{kind:"yield",label:"Yield"},comparison:{family:o.family,left:o.label,right:_(o.family)},effect:{direction:y===0?"different":y>0?"higher":"lower",absoluteDelta:y,relativeDelta:d===0?void 0:y/d,effectSize:y},stats:{method:"two-proportion-z",pValue:w,sampleSizeLeft:u,sampleSizeRight:h},summary:be(o.label,y,o.family),highlight:{kind:"region",regionFamily:o.family,regionKeys:[o.key],dieKeys:[...o.dieKeys]}}),{hits:a,n:u,restHits:g,restN:h,passRate:!0}))}return ue(c,n)}function E(t,i,s,n,e,l){const r=p=>s==="soft"?p.sbin:p.hbin,f=[...new Set(t.map(r).filter(p=>p!==void 0))].sort((p,d)=>p-d),c=ce(t,i),o=new Map,u=new Map;for(const[p,d]of c){const y=new Map;for(const w of d){const k=r(w);k!==void 0&&y.set(k,(y.get(k)??0)+1)}o.set(p,y),u.set(p,d.length)}const a=[],h=e==="hardBin"?"HBin":"SBin",g=s==="hard"?new Set(l.passBins):l.softPassBins??Me(t,l.passBins).soft;for(const p of i){const d=u.get(p.key)??0,y=o.get(p.key);let w=0;const k=new Map;for(const[S,$]of o)if(S!==p.key){w+=u.get(S)??0;for(const[N,F]of $)k.set(N,(k.get(N)??0)+F)}if(!(d<l.minimumSampleSize||w<l.minimumSampleSize))for(const S of f){const $=y.get(S)??0,N=k.get(S)??0,F=$/d,b=N/w,v=F-b,M=O($,d,N,w),z=Ee(S,n,h);a.push(le(Z(p,{id:`${e}:${S}:${p.key}`,level:"wafer",severity:"info",variable:{kind:e,bin:S,label:z},comparison:{family:p.family,left:p.label,right:_(p.family)},effect:{direction:v===0?"different":v>0?"higher":"lower",absoluteDelta:v,relativeDelta:b===0?void 0:v/b,effectSize:v},stats:{method:"two-proportion-z",pValue:M,sampleSizeLeft:d,sampleSizeRight:w},summary:Se(p.label,z,v,p.family),highlight:{kind:"bin",bin:S,regionKeys:[p.key],dieKeys:[...p.dieKeys]}}),{hits:$,n:d,restHits:N,restN:w,passRate:g.has(S)}))}}return ue(a,l)}function te(t,i,s,n){const e=(s??[]).filter(u=>!W(u));if(!e.length)return[];const l=e.map(u=>u.testNumber),r=ce(t,i),f=new Map,c=new Map;for(const[u,a]of r){const h=new Uint32Array(e.length),g=new Uint32Array(e.length);for(const p of a)for(let d=0;d<e.length;d++){const y=se(p,l[d],e[d]);y!==void 0&&(g[d]++,y&&h[d]++)}f.set(u,h),c.set(u,g)}const o=[];for(const u of i){const a=f.get(u.key),h=c.get(u.key);for(let g=0;g<e.length;g++){let p=0,d=0;for(const[b,v]of c)b!==u.key&&(d+=v[g],p+=f.get(b)[g]);const y=h[g],w=a[g];if(y<n.minimumSampleSize||d<n.minimumSampleSize)continue;const k=w/y,S=p/d,$=k-S,N=O(w,y,p,d),F=l[g];o.push(le(Z(u,{id:`functional:${F}:${u.key}`,level:"wafer",severity:"info",variable:{kind:"functionalTest",index:F,label:`${T(e[g],F)} pass rate`,...oe(e[g])},comparison:{family:u.family,left:u.label,right:_(u.family)},effect:{direction:$===0?"different":$>0?"higher":"lower",absoluteDelta:$,relativeDelta:S===0?void 0:$/S,effectSize:$},stats:{method:"two-proportion-z",pValue:N,sampleSizeLeft:y,sampleSizeRight:d},summary:we(u.label,T(e[g],F),$,u.family),highlight:{kind:"region",regionFamily:u.family,regionKeys:[u.key],dieKeys:[...u.dieKeys]}}),{hits:w,n:y,restHits:p,restN:d,passRate:!0}))}}return ue(o,n)}export function welchFromStats(t,i,s,n,e,l){const r=Math.sqrt(s/t+l/n),f=i-e;if(!Number.isFinite(r)||r===0)return{pValue:1,effectSize:0,delta:f,z:0};const c=f/r,o=Math.sqrt(Math.max(0,(s+l)/2)),u=o===0?f:f/o;return{pValue:Ve(2*(1-Ce(Math.abs(c)))),effectSize:u,delta:f,z:c}}function Nt(t,i){const s=ae(t),n=ae(i);return welchFromStats(t.length,s,he(t,s),i.length,n,he(i,n))}const Ye=250;function _e(t,i,s){const n=r=>r.filter(f=>W(s?.find(c=>c.testNumber===f)));if(i)return{testNumbers:n(i.slice().sort((r,f)=>r-f))};const e=Pe(t,"values");if(e.length>Ye){const r=`Test-value analysis was skipped: more than ${Ye} 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:n(e)}}function ne(t,i,s,n){const e=_e(t,n.testNumbers,s);if(e.warning)return{findings:[],warning:e.warning};const l=e.testNumbers,r=new Map;for(let d=0;d<i.length;d++)for(const y of I(i[d].dies))r.set(y,d);const f=i.length,c=[],o=[];for(const d of t){const y=r.get(me(d));y!==void 0&&(c.push(d),o.push(y))}const u=[],a=new Float64Array(f),h=new Float64Array(f),g=new Float64Array(f);for(const d of l){a.fill(0),h.fill(0),g.fill(0);let y=0,w=0,k=0,S;for(let $=0;$<c.length;$++){const N=c[$],F=j(N,d);if(F===void 0)continue;S===void 0&&(S=F);const b=F-S,v=o[$];a[v]+=1,h[v]+=b,g[v]+=b*b,y+=1,w+=b,k+=b*b}if(S!==void 0)for(let $=0;$<f;$++){const N=a[$],F=y-N;if(N<n.minimumSampleSize||F<n.minimumSampleSize)continue;const b=h[$]/N,M=(w-h[$])/F,z=b+S,D=M+S,C=N>1?Math.max(0,g[$]-N*b*b)/(N-1):0,m=F>1?Math.max(0,k-g[$]-F*M*M)/(F-1):0,{pValue:R,effectSize:P,delta:B,z:x}=welchFromStats(N,z,C,F,D,m),V=i[$],{label:L,unit:q,...H}=Dt(d,s),K=D!==0?B/Math.abs(D):void 0,A=Z(V,{id:`test:${d}:${V.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:d,label:L,unit:q,...H},comparison:{family:V.family,left:V.label,right:_(V.family)},effect:{direction:B===0?"different":B>0?"higher":"lower",absoluteDelta:B,relativeDelta:K,effectSize:P},stats:{method:"welch-z-approx",pValue:R,sampleSizeLeft:N,sampleSizeRight:F},summary:ve(V.label,L,B,K,V.family,q),highlight:{kind:"region",regionFamily:V.family,regionKeys:[V.key],dieKeys:[...V.dieKeys]}}),Re={n:N,sum:h[$],sq:g[$],restN:F,restSum:w-h[$],restSq:k-g[$],shift:S};ze.set(A,Re),Ae(A,x,void 0,Re),u.push(A)}}adjustPValues(u);const p=u.filter(d=>{const y=d.stats.adjustedPValue??d.stats.pValue??1,w=Math.abs(d.effect.effectSize??0);return y<=n.significanceLevel&&w>=n.minimumEffectSize});return{activeTestNumbers:l,findings:p.filter(d=>!Bt(d,p,n)).map(d=>({...d,severity:severityForScore(d.stats.adjustedPValue??d.stats.pValue??1,d.effect.effectSize??0)}))}}function Ft(t,i,s,n){if(!s?.length)return[];const e=s.filter(r=>W(r)&&(r.limitLow!==void 0||r.limitHigh!==void 0));if(!e.length)return[];const l=[];for(const r of e){const f=r.testNumber;for(const c of i){const o=ce(t,c),u=[],a=new Map;let h=0,g=0;for(const[p,d]of o){let y=0,w=0;for(const k of d){const S=j(k,f);S!==void 0&&(y++,ge(re(S,r))&&w++)}a.set(p,{valid:y,fail:w}),h+=y,g+=w}for(const p of c){const d=a.get(p.key),y=d.valid,w=h-d.valid;if(y<n.minimumSampleSize||w<n.minimumSampleSize)continue;const k=d.fail,S=g-d.fail,$=k/y,N=S/w,F=$-N,b=O(k,y,S,w);u.push(le(Z(p,{id:`specLimit:${f}:${p.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:f,label:T(r,f),unit:r.unit,...oe(r)},comparison:{family:p.family,left:p.label,right:_(p.family)},effect:{direction:F===0?"different":F>0?"higher":"lower",absoluteDelta:F,relativeDelta:N===0?void 0:F/N,effectSize:F},stats:{method:"two-proportion-z",pValue:b,sampleSizeLeft:y,sampleSizeRight:w},summary:$e(p.label,T(r,f),F),highlight:{kind:"region",regionFamily:p.family,regionKeys:[p.key],dieKeys:[...p.dieKeys]}}),{hits:k,n:y,restHits:S,restN:w,passRate:!1}))}l.push(...ue(u,n))}}return l}function ke(t,i){return t==="softBin"?i.softEligibleDies:t==="test"?i.testDies:i.eligibleDies}function Pt(t){return t.id.startsWith("specLimit:")}function ie(t){return t.highlight.regionKeys??[]}function xt(t){const i=t.variable.bin??t.variable.index??"";return`${t.comparison.family}\0${Qe(t)}\0${t.variable.kind}\0${i}\0${t.effect.direction}`}function Ct(t,i){const s=Y(t);return s.family==="ring"?s.ring??0:s.family==="sector"?xe(i).indexOf(s.sector??""):0}export function inQuadrantOrder(t){const i=new Set(t),s=pe.findIndex((e,l)=>i.has(e)&&!i.has(pe[(l+3)%4])),n=s<0?0:s;return Array.from({length:4},(e,l)=>pe[(n+l)%4]).filter(e=>i.has(e))}export function findContiguousRuns(t,i,s,n){if(i==="quadrant"){const c=new Map;for(const h of t){const g=Y(n(h)).quadrant;g&&c.set(g,h)}const o=[...c.keys()],u=new Set,a=[];for(const h of o){if(u.has(h))continue;const g=[],p=[h];for(u.add(h);p.length;){const d=p.pop();g.push(d);for(const y of o)!u.has(y)&&at(d,y)&&(u.add(y),p.push(y))}a.push(inQuadrantOrder(g).map(d=>c.get(d)))}return a}const e=c=>Ct(n(c),s),l=[...t].sort((c,o)=>e(c)-e(o)),r=[];let f=[];for(const c of l){if(f.length===0){f.push(c);continue}const o=e(f[f.length-1]);e(c)-o===1?f.push(c):(r.push(f),f=[c])}if(f.length&&r.push(f),i==="sector"&&r.length>1){const o=xe(s).length,u=e(r[0][0]),a=e(r[r.length-1][r[r.length-1].length-1]);if(u===0&&a===o-1){const h=r.pop();r[0]=[...h,...r[0]]}}return r}export function mergedRegionLabel(t,i,s){if(t==="ring"){const e=i.map(c=>Y(c).ring).filter(c=>c!==void 0).sort((c,o)=>c-o),l=e[0],r=e[e.length-1];let f="";return l===r&&l===1&&s>1?f=" (core)":l===r&&l===s&&s>1&&(f=" (edge)"),l===r?`Ring ${l}${f}`:`Rings ${l}\u2013${r}`}if(t==="sector"){const e=i.map(l=>Y(l).sector??"");return e.length===1?`Sector ${e[0]}`:`Sectors ${e[0]}\u2013${e[e.length-1]}`}const n=i.map(e=>Y(e).quadrant??"");return n.length===1?n[0]:n.length===2?`Quadrants ${n[0]} & ${n[1]}`:`Quadrants ${n.slice(0,-1).join(", ")} & ${n[n.length-1]}`}function De(t){return[...new Set(t)]}export function weakestMultiplier(t){return bt(t.map(i=>{const s=i.stats.pValue??1;return s>0?(i.stats.adjustedPValue??s)/s:1}))}function G(t,i){return Math.min(1,t*i)}function Vt(t,i){const s=t[0],n=s.comparison.family,e=s.variable.kind,l=De(t.flatMap(ie));let r,f;const c=t.map(je);if(c.every(b=>b!==void 0)){const b=new Set;r=[];for(const M of c){const z=I(M.dies);for(let D=0;D<z.length;D++)b.has(z[D])||(b.add(z[D]),r.push(M.dieKeys[D]))}const v=I(ke(s.variable.kind,i));f=M=>b.has(v[M])}else{r=De(t.flatMap(M=>M.highlight.dieKeys??[]));const b=new Set(r),v=Rt(ke(s.variable.kind,i));f=M=>b.has(v[M])}const o=mergedRegionLabel(n,t.map(b=>ie(b)[0]??""),i.ringCount),u=ke(e,i),a=i.regionDies[n],h=I(u),g=[],p=[];for(let b=0;b<u.length;b++)a.has(h[b])&&(f(b)?g:p).push(u[b]);const d=weakestMultiplier(t);let y,w,k,S,$;if(Pt(s)){const b=s.variable.index,v=i.testDefs?.find(x=>x.testNumber===b),M=x=>{let V=0,L=0;for(const q of x){const H=j(q,b);H!==void 0&&(V++,v&&ge(re(H,v))&&L++)}return{valid:V,fail:L}},z=M(g),D=M(p),C=z.valid>0?z.fail/z.valid:0,m=D.valid>0?D.fail/D.valid:0,R=C-m,P=O(z.fail,z.valid,D.fail,D.valid);y={direction:R===0?"different":R>0?"higher":"lower",absoluteDelta:R,relativeDelta:m===0?void 0:R/m,effectSize:R},w={method:"two-proportion-z",pValue:P,sampleSizeLeft:z.valid,sampleSizeRight:D.valid};const B=G(P,d);k=severityForFinding(B,R,rateRelativeEffect({hits:z.fail,n:z.valid,restHits:D.fail,restN:D.valid,passRate:!1})),S=$e(o,T(v,b),R),$=`specLimit:${b}`}else if(e==="test"){const b=s.variable.index,v=x=>j(x,b),M=g.map(v).filter(x=>x!==void 0),z=p.map(v).filter(x=>x!==void 0),{pValue:D,effectSize:C,delta:m}=Nt(M,z),R=ae(z),P=R!==0?m/Math.abs(R):void 0;y={direction:m===0?"different":m>0?"higher":"lower",absoluteDelta:m,relativeDelta:P,effectSize:C},w={method:"welch-z-approx",pValue:D,sampleSizeLeft:M.length,sampleSizeRight:z.length};const B=G(D,d);k=severityForScore(B,C),S=ve(o,s.variable.label,m,P,n,s.variable.unit),$=`test:${b}`}else if(e==="yield"){const b=new Set(i.passBins),v=g.filter(L=>L.hbin!==void 0),M=p.filter(L=>L.hbin!==void 0),z=v.length,D=M.length,C=v.filter(L=>b.has(L.hbin)).length,m=M.filter(L=>b.has(L.hbin)).length,R=z>0?C/z:0,P=D>0?m/D:0,B=R-P,x=O(C,z,m,D);y={direction:B===0?"different":B>0?"higher":"lower",absoluteDelta:B,relativeDelta:P===0?void 0:B/P,effectSize:B},w={method:"two-proportion-z",pValue:x,sampleSizeLeft:z,sampleSizeRight:D};const V=G(x,d);k=severityForFinding(V,B,rateRelativeEffect({hits:C,n:z,restHits:m,restN:D,passRate:!0})),S=be(o,B,n),$="yield"}else if(e==="functionalTest"){const b=s.variable.index,v=i.testDefs?.find(V=>V.testNumber===b);let M=0,z=0,D=0,C=0;for(const V of g){const L=se(V,b,v);L!==void 0&&(z++,L&&M++)}for(const V of p){const L=se(V,b,v);L!==void 0&&(C++,L&&D++)}const m=z>0?M/z:0,R=C>0?D/C:0,P=m-R,B=O(M,z,D,C);y={direction:P===0?"different":P>0?"higher":"lower",absoluteDelta:P,relativeDelta:R===0?void 0:P/R,effectSize:P},w={method:"two-proportion-z",pValue:B,sampleSizeLeft:z,sampleSizeRight:C};const x=G(B,d);k=severityForFinding(x,P,rateRelativeEffect({hits:M,n:z,restHits:D,restN:C,passRate:!0})),S=we(o,T(v,b),P,n),$=`functional:${b}`}else{const b=s.variable.bin,v=x=>e==="softBin"?x.sbin:x.hbin,M=g.filter(x=>v(x)===b).length,z=p.filter(x=>v(x)===b).length,D=M/g.length,C=z/p.length,m=D-C,R=O(M,g.length,z,p.length);y={direction:m===0?"different":m>0?"higher":"lower",absoluteDelta:m,relativeDelta:C===0?void 0:m/C,effectSize:m},w={method:"two-proportion-z",pValue:R,sampleSizeLeft:g.length,sampleSizeRight:p.length};const P=G(R,d);k=severityForFinding(P,m,rateRelativeEffect({hits:M,n:g.length,restHits:z,restN:p.length,passRate:!1}));const B=e==="softBin"?i.sbinDefs:i.hbinDefs;S=Se(o,Ee(b,B,e==="softBin"?"SBin":"HBin"),m,n),$=`${e}:${b}`}w={...w,adjustedPValue:G(w.pValue??1,d)};const N=De(l.map(b=>b.replace(`${n}:`,""))).join("-"),F=e==="hardBin"||e==="softBin"?{kind:"bin",bin:s.variable.bin,regionKeys:l,dieKeys:r}:{kind:"region",regionFamily:n,regionKeys:l,dieKeys:r};return{...s,id:`${$}:${n}:${N}`,severity:k,comparison:{family:n,left:o,right:_(n)},effect:y,stats:w,summary:S,highlight:F,relatedIds:t.map(b=>b.id)}}function Lt(t,i){const s=new Set(["ring","quadrant","sector"]),n=[],e=new Map;for(const r of t){if(!s.has(r.comparison.family)){n.push(r);continue}const f=xt(r),c=e.get(f)??[];c.push(r),e.set(f,c)}const l=[];for(const r of e.values()){if(r.length===1){l.push(r[0]);continue}const f=r[0].comparison.family;for(const c of findContiguousRuns(r,f,i.sectorCount,o=>ie(o)[0]??""))l.push(c.length===1?c[0]:Vt(c,i))}return[...n,...l]}function Kt(t){const i=new Map,s=new Map;for(const e of t){const l=me(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[r,f]of s){if(l.size!==f.size)continue;let c=!0;for(const o of l)if(!f.has(o)){c=!1;break}c&&n.add(`${e}:${r}`)}return n}function Et(t,i){const s=new Set(i);let n;for(const e of t){const l=Be(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&&Be(e,s)!==!0)return;return n}}function Ot(t){return`${t.comparison.family}\0${t.comparison.left}`}export function redundancyFacts(t,i,s){return{coincident:Kt(t),yieldSoftBin:Et(t,i),passBins:i,sectorCount:s}}const Tt=3/5;function Qe(t){return/^(?:lot-region:)?([^:|]+)/.exec(t.id)?.[1]??t.id}function qt(t){const i=ie(t);if(i.length)return i;const s=/(ring|sector|quadrant):([^:|]+)$/.exec(t.id);return s?s[2].split("-").map(n=>`${s[1]}:${n}`):[]}function Ue(t,i){const s=new Set;for(const n of qt(t)){const e=rt(n,i);if(!e)return;for(const l of e)s.add(l)}return s.size?s:void 0}export function collapseRedundantFindings(t,i){const{coincident:s,yieldSoftBin:n,passBins:e}=i,l=(o,u)=>{o.absorbedIds=[...o.absorbedIds??[],u.id]},r=new Map;for(const o of t){const u=Ot(o);(r.get(u)??r.set(u,[]).get(u)).push(o)}const f=new Set;for(const o of r.values()){for(const a of o)if(!(a.variable.kind!=="hardBin"||a.variable.bin===void 0))for(const h of o){if(h.variable.kind!=="softBin"||h.variable.bin===void 0||f.has(h.id)||!s.has(`${a.variable.bin}:${h.variable.bin}`)||h.effect.direction!==a.effect.direction)continue;l(a,h),f.add(h.id);const g=a.variable.label.replace(/^HBin\s+/,""),p=h.variable.label.replace(/^SBin\s+/,""),d=g===p?`HBin and SBin ${g} (same dies)`:`${a.variable.label} and ${h.variable.label} (same dies)`;a.summary=a.summary.replace(a.variable.label,d),a.variable.label=d;break}const u=o.find(a=>a.variable.kind==="yield");if(u){for(const a of o)if(!(a===u||f.has(a.id)||!(a.variable.kind==="hardBin"&&e.length===1&&a.variable.bin===e[0]||a.variable.kind==="softBin"&&a.variable.bin===n))){l(u,a),f.add(a.id);for(const g of a.absorbedIds??[])u.absorbedIds=[...u.absorbedIds??[],g]}}}const c=new Map;for(const o of t){if(f.has(o.id)||o.comparison.family!=="sector"&&o.comparison.family!=="quadrant")continue;const u=[Qe(o),o.variable.kind,o.variable.bin??"",o.variable.index??"",o.effect.direction].join("\0");(c.get(u)??c.set(u,[]).get(u)).push(o)}for(const o of c.values()){const u=[];for(const a of o.filter(h=>h.comparison.family==="sector")){const h=Ue(a,i.sectorCount);if(h)for(const g of o.filter(p=>p.comparison.family==="quadrant")){const p=Ue(g,i.sectorCount);if(!p)continue;let d=0;for(const w of h)p.has(w)&&d++;const y=h.size+p.size-d;d/y>=Tt-1e-9&&u.push({sector:a,quadrant:g,overlap:d/y})}}u.sort((a,h)=>h.overlap-a.overlap);for(const{sector:a,quadrant:h}of u){if(f.has(a.id)||f.has(h.id))continue;const g=(h.stats.pValue??1)<(a.stats.pValue??1),[p,d]=g?[h,a]:[a,h];l(p,d),f.add(d.id),p.absorbedIds=[...p.absorbedIds??[],...d.absorbedIds??[]]}}}export function analyzeWaferMap(t,i={}){const s=Q,n=J,e=[];Q=e,J=void 0;try{const l=Ht(t,i);return He.set(l,{candidates:e,facts:J}),l}finally{Q=s,J=n}}function Ht(t,i){const{resolved:s,warnings:n}=resolveOptions(i),e=Ge(t),l={...s,passBins:[...mt(e,"analyzeWaferMap")],ringCount:pt(e,"analyzeWaferMap")},r=e.isLotStack,f=e.aggrMethod,c=!r||f==="mode"||f==="countBin"||f==="percent",o=e.dies.filter(m=>wt(m)),u=o.filter(Fe),a=St(l,o.length);a.softPassBins=Me(o,a.passBins).soft;const h=e.dies.filter(m=>de(m)),g=h.filter(Fe),p=it(g,e.wafer,a.ringCount),d=tt(g,e.wafer,a.ringCount),y=nt(g,e.reticleConfig),w=ot(h,!1),k=st(g,e.wafer,a.sectorCount),S=[];c&&S.push(...ee(o,p,a.passBins,a),...ee(o,d,a.passBins,a),...ee(o,y,a.passBins,a),...ee(o,w,a.passBins,a),...ee(o,k,a.passBins,a)),c&&S.push(...E(o,p,"hard",e.hbinDefs,"hardBin",a),...E(o,d,"hard",e.hbinDefs,"hardBin",a),...E(o,y,"hard",e.hbinDefs,"hardBin",a),...E(o,w,"hard",e.hbinDefs,"hardBin",a),...E(o,k,"hard",e.hbinDefs,"hardBin",a));{const m=o.filter(R=>R.sbin!==void 0);S.push(...E(m,p,"soft",e.sbinDefs,"softBin",a),...E(m,d,"soft",e.sbinDefs,"softBin",a),...E(m,y,"soft",e.sbinDefs,"softBin",a),...E(m,w,"soft",e.sbinDefs,"softBin",a),...E(m,k,"soft",e.sbinDefs,"softBin",a))}const $=[...n];let N;if(a.enableTestValueAnalysis){const m=ne(o,p,e.testDefs,a),R=ne(o,d,e.testDefs,a),P=ne(o,y,e.testDefs,a),B=ne(o,w,e.testDefs,a),x=ne(o,k,e.testDefs,a);S.push(...m.findings,...R.findings,...P.findings,...B.findings,...x.findings),m.warning&&$.push(m.warning),N=m.activeTestNumbers,S.push(...Ft(o,[p,d,y,w,k],e.testDefs,a)),S.push(...te(o,p,e.testDefs,a),...te(o,d,e.testDefs,a),...te(o,y,e.testDefs,a),...te(o,w,e.testDefs,a),...te(o,k,e.testDefs,a))}{const m=vt(r,c,e.testDefs);(!r||c||m!==void 0)&&S.push(...ft(u,e.wafer,{...a,isFailingDie:m}))}const F=Lt(S,{eligibleDies:o,softEligibleDies:o.filter(m=>m.sbin!==void 0),testDies:o,passBins:a.passBins,ringCount:a.ringCount,sectorCount:a.sectorCount,regionDies:{ring:new Set(p.flatMap(m=>I(m.dies))),quadrant:new Set(d.flatMap(m=>I(m.dies))),sector:new Set(k.flatMap(m=>I(m.dies)))},hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs});S.length=0,S.push(...F);let b;if(c){const m=lt(u,e.wafer,{passBins:a.passBins,ringCount:a.ringCount});if(b=m??void 0,m!==null&&m.pattern!=="random"&&m.pattern!=="none"){const R=dt[m.pattern],P=m.confidence==="high"?"unusual":m.confidence==="medium"?"notable":"info",B=m.features,x=K=>`${(K*100).toFixed(0)}%`,V=m.pattern==="edge-ring"||m.pattern==="edge-local"?`${x(B.edgeRdd)} of edge dies failing`:m.pattern==="center"||m.pattern==="donut"?`centroid at ${x(B.centroidDistNorm)} of wafer radius from centre`:m.pattern==="scratch"?`linear score ${B.linearScore.toFixed(2)}, eccentricity ${B.eccentricity.toFixed(2)}`:`${x(B.globalRdd)} of dies failing`,L=new Set(a.passBins),q=o.filter(K=>ut(K,L)===!0),H=claimForPattern([m.pattern],P,S,K=>ie(K).map(A=>Y(A).ring).filter(A=>A!==void 0),a.ringCount);S.push({id:`spatial-pattern:${m.pattern}`,level:"wafer",severity:P,variable:{kind:"spatialPattern",label:R},comparison:{family:"spatial-pattern",left:R,right:"Wafer"},effect:{direction:"different",effectSize:B.globalRdd},stats:{method:"geometry",sampleSizeLeft:q.length,sampleSizeRight:o.length},summary:`Spatial pattern: ${R.toLowerCase()} (${m.confidence} confidence) \u2014 ${V}${m.note?` [${m.note}]`:""}`,highlight:{kind:"dies",dieKeys:q.map(K=>Ne(K))},relatedIds:H.length>0?H:void 0})}}S.sort((m,R)=>{const P=m.stats.adjustedPValue??m.stats.pValue??1,B=R.stats.adjustedPValue??R.stats.pValue??1;return P!==B?P-B:Math.abs(R.effect.absoluteDelta??0)-Math.abs(m.effect.absoluteDelta??0)});const v=$t(e.dies,o.length,e.yield.yieldPercent);r&&(v.isLotStack=!0,f&&(v.aggregationMethod=f));const M=zt(e.dies,e.testDefs);M&&(v.testSpecYield=M);const z=computeFunctionalYield(e.dies,e.testDefs);if(z&&(v.functionalYield=z),b&&(v.spatialPattern=b),N===void 0&&a.computePerTestStats){const m=_e(o,a.testNumbers,e.testDefs);m.warning&&$.push(m.warning),N=m.testNumbers}if(N?.length){const m=kt(e.dies,N,e.testDefs,a.minimumSampleSize);m&&(v.perTestStats=m);const R=Xe([{dies:e.dies}],e.testDefs,N);R&&(v.capability=R)}Object.assign(v,Ze(e.dies,e.testDefs));const D=Je([{dies:e.dies,wafer:e.wafer,passBins:a.passBins,ringCount:a.ringCount}]);D&&(v.regionYield=D),$.length>0&&(v.warnings=$);const C=redundancyFacts(o,a.passBins,a.sectorCount);return collapseRedundantFindings(S,C),Q&&(J=C),{level:"wafer",hasNotableFindings:S.some(m=>m.severity==="notable"||m.severity==="unusual"),findings:S,wafer:e.wafer.metadata??void 0,stats:v}}
@@ -65,5 +65,5 @@ export declare function buildBinClusterData(groups: {
65
65
  * `binPassSets(dies, passBins).soft`) — hard pass numbers applied to a soft
66
66
  * tally pin the wrong rows.
67
67
  */
68
- export declare function sortBinsForDisplay(entries: Iterable<[bin: number, count: number]>, passBins?: Iterable<number>): Array<[bin: number, count: number]>;
68
+ export declare function sortBinsForDisplay(entries: Iterable<[bin: number, count: number]>, passBins: Iterable<number>): Array<[bin: number, count: number]>;
69
69
  //# sourceMappingURL=binPareto.d.ts.map
@@ -1 +1 @@
1
- import{isYieldEligibleDie as p}from"../core/dies.js";export function buildBinParetoData(l,u){const s=new Map;let n=0;for(const o of l){const i=u==="hbin"?o.hardBinCounts:o.softBinCounts;if(i){for(const[a,t]of Object.entries(i)){const e=Number(a);n+=t,s.set(e,(s.get(e)??0)+t)}continue}for(const a of o.dies??[]){if(!p(a))continue;const t=u==="hbin"?a.hbin:a.sbin;t!==void 0&&(n++,s.set(t,(s.get(t)??0)+1))}}const c=Array.from(s.entries()).map(([o,i])=>({label:`Bin ${o}`,value:i,percent:n>0?i/n*100:0,itemCount:1,binCode:o}));return c.sort((o,i)=>i.value-o.value),c}export function buildBinClusterData(l,u){const s=l.map(t=>t.key),n=new Map,c=[];function o(t,e,f){let r=n.get(t);r||(r={total:0,counts:[]},n.set(t,r),c.push(t)),r.total+=f,r.counts[e]=(r.counts[e]??0)+f}l.forEach((t,e)=>{for(const f of t.items){const r=u==="hbin"?f.hardBinCounts:f.softBinCounts;if(r){for(const[d,b]of Object.entries(r))o(Number(d),e,b);continue}for(const d of f.dies??[]){if(!p(d))continue;const b=u==="hbin"?d.hbin:d.sbin;b!==void 0&&o(b,e,1)}}});const a=c.slice().sort((t,e)=>n.get(e).total-n.get(t).total).map(t=>{const e=n.get(t);return{binCode:t,label:`Bin ${t}`,total:e.total,counts:s.map((f,r)=>e.counts[r]??0)}});return{groups:s,bins:a}}export function sortBinsForDisplay(l,u=[1]){const s=new Set(u);return[...l].sort((n,c)=>{const o=s.has(n[0]),i=s.has(c[0]);return o!==i?o?-1:1:o&&i?n[0]-c[0]:c[1]-n[1]||n[0]-c[0]})}
1
+ import{isYieldEligibleDie as p}from"../core/dies.js";export function buildBinParetoData(l,u){const s=new Map;let n=0;for(const o of l){const i=u==="hbin"?o.hardBinCounts:o.softBinCounts;if(i){for(const[a,t]of Object.entries(i)){const e=Number(a);n+=t,s.set(e,(s.get(e)??0)+t)}continue}for(const a of o.dies??[]){if(!p(a))continue;const t=u==="hbin"?a.hbin:a.sbin;t!==void 0&&(n++,s.set(t,(s.get(t)??0)+1))}}const c=Array.from(s.entries()).map(([o,i])=>({label:`Bin ${o}`,value:i,percent:n>0?i/n*100:0,itemCount:1,binCode:o}));return c.sort((o,i)=>i.value-o.value),c}export function buildBinClusterData(l,u){const s=l.map(t=>t.key),n=new Map,c=[];function o(t,e,f){let r=n.get(t);r||(r={total:0,counts:[]},n.set(t,r),c.push(t)),r.total+=f,r.counts[e]=(r.counts[e]??0)+f}l.forEach((t,e)=>{for(const f of t.items){const r=u==="hbin"?f.hardBinCounts:f.softBinCounts;if(r){for(const[d,b]of Object.entries(r))o(Number(d),e,b);continue}for(const d of f.dies??[]){if(!p(d))continue;const b=u==="hbin"?d.hbin:d.sbin;b!==void 0&&o(b,e,1)}}});const a=c.slice().sort((t,e)=>n.get(e).total-n.get(t).total).map(t=>{const e=n.get(t);return{binCode:t,label:`Bin ${t}`,total:e.total,counts:s.map((f,r)=>e.counts[r]??0)}});return{groups:s,bins:a}}export function sortBinsForDisplay(l,u){const s=new Set(u);return[...l].sort((n,c)=>{const o=s.has(n[0]),i=s.has(c[0]);return o!==i?o?-1:1:o&&i?n[0]-c[0]:c[1]-n[1]||n[0]-c[0]})}
@@ -6,6 +6,8 @@ export interface HistogramBucket {
6
6
  }
7
7
  export interface HistogramItem {
8
8
  label?: string;
9
+ /** The wafer this item is, when it is one (an Insights wafer index): lets a click on a bucket say which wafers its dies are on. */
10
+ key?: number;
9
11
  dies?: Die[];
10
12
  }
11
13
  /** One overlaid series in a faceted histogram — per-group counts over the shared bucket ranges. */
@@ -59,6 +61,31 @@ clip?: {
59
61
  lo: number;
60
62
  hi: number;
61
63
  }): HistogramBucket[];
64
+ /**
65
+ * The width the buckets were laid out with, read back from them. Every count AND every pick of a bucket's dies bins with
66
+ * this one number, so the dies a click selects are exactly the bar's count: recomputing the width from the span in one
67
+ * place and from the bucket edges in another can differ by a rounding error, which moves a die on an edge to the next bar.
68
+ */
69
+ export declare function bucketWidthOf(buckets: ReadonlyArray<{
70
+ rangeLow: number;
71
+ rangeHigh: number;
72
+ }>): number;
73
+ /** The bucket a value falls in: equal widths from `min`, the last bucket closed at the top. THE binning rule. */
74
+ export declare function bucketIndexOf(v: number, min: number, width: number, bucketCount: number): number;
75
+ /**
76
+ * The dies behind one bucket of a histogram built by `buildTestHistogramData` over `items`, per item: every die whose
77
+ * value lands in it by the same rule that counted it (so the dies a click picks out are exactly the bar's `count`).
78
+ * Values outside the buckets' range, which a clipped axis dropped, are not in any bucket.
79
+ */
80
+ export declare function diesInBucket(items: HistogramItem[], testNumber: number, buckets: ReadonlyArray<Pick<HistogramBucket, 'rangeLow' | 'rangeHigh'>>, index: number,
81
+ /** The clip the buckets were built with, if any: values outside it were not counted, so they are not picked. */
82
+ clip?: {
83
+ lo: number;
84
+ hi: number;
85
+ }): Array<{
86
+ item: HistogramItem;
87
+ dies: Die[];
88
+ }>;
62
89
  /**
63
90
  * Faceted histogram: one count-series per group over a *shared* set of
64
91
  * buckets, so the series overlay and compare directly. The bucket range
@@ -1 +1 @@
1
- import{testValue as v}from"../core/dieTable.js";export function collectTestValues(d,u){const t=[];for(const i of d)for(const f of i.dies??[]){const o=v(f,u);o!==void 0&&Number.isFinite(o)&&t.push(o)}return t}export function testValueExtent(d,u){let t=1/0,i=-1/0;for(const f of d)for(const o of f.dies??[]){const n=v(o,u);n!==void 0&&Number.isFinite(n)&&(n<t&&(t=n),n>i&&(i=n))}return t===1/0?{min:NaN,max:NaN}:{min:t,max:i}}export function buildTestHistogramData(d,u,t=16,i,f,o){let n=collectTestValues(d,u);if(o&&(n=n.filter(e=>e>=o.lo&&e<=o.hi)),n.length===0)return[];let r=n[0],a=n[0];for(let e=1;e<n.length;e++)n[e]<r&&(r=n[e]),n[e]>a&&(a=n[e]);const l=i!==void 0?Math.min(r,i):r,M=((f!==void 0?Math.max(a,f):a)-l||1)/t,h=Array.from({length:t},(e,x)=>({rangeLow:l+x*M,rangeHigh:l+(x+1)*M,count:0}));for(const e of n){const x=Math.min(t-1,Math.floor((e-l)/M));h[x].count++}return h}export function buildTestHistogramSeries(d,u,t=16,i,f,o){const n=new Map;let r=1/0,a=-1/0;for(const s of d){const m=[];for(const y of s.items)for(const p of y.dies??[]){const c=v(p,u);if(c!==void 0&&Number.isFinite(c)){if(o&&(c<o.lo||c>o.hi))continue;m.push(c),c<r&&(r=c),c>a&&(a=c)}}n.set(s.key,m)}const l=d.map(s=>s.key).filter(s=>n.get(s).length>0);if(l.length===0)return{ranges:[],series:[]};const g=i!==void 0?Math.min(r,i):r,h=((f!==void 0?Math.max(a,f):a)-g||1)/t,e=Array.from({length:t},(s,m)=>({rangeLow:g+m*h,rangeHigh:g+(m+1)*h})),x=l.map(s=>{const m=new Array(t).fill(0);for(const y of n.get(s)){const p=Math.min(t-1,Math.floor((y-g)/h));m[p]++}return{groupKey:s,counts:m}});return{ranges:e,series:x}}
1
+ import{testValue as v}from"../core/dieTable.js";export function collectTestValues(o,r){const n=[];for(const e of o)for(const s of e.dies??[]){const i=v(s,r);i!==void 0&&Number.isFinite(i)&&n.push(i)}return n}export function testValueExtent(o,r){let n=1/0,e=-1/0;for(const s of o)for(const i of s.dies??[]){const t=v(i,r);t!==void 0&&Number.isFinite(t)&&(t<n&&(n=t),t>e&&(e=t))}return n===1/0?{min:NaN,max:NaN}:{min:n,max:e}}export function buildTestHistogramData(o,r,n=16,e,s,i){let t=collectTestValues(o,r);if(i&&(t=t.filter(f=>f>=i.lo&&f<=i.hi)),t.length===0)return[];let c=t[0],a=t[0];for(let f=1;f<t.length;f++)t[f]<c&&(c=t[f]),t[f]>a&&(a=t[f]);const u=e!==void 0?Math.min(c,e):c,d=((s!==void 0?Math.max(a,s):a)-u||1)/n,x=Array.from({length:n},(f,M)=>({rangeLow:u+M*d,rangeHigh:u+(M+1)*d,count:0})),y=bucketWidthOf(x);for(const f of t)x[bucketIndexOf(f,u,y,n)].count++;return x}export function bucketWidthOf(o){const r=o.length;return(o[r-1].rangeHigh-o[0].rangeLow)/r||1}export function bucketIndexOf(o,r,n,e){return Math.min(e-1,Math.floor((o-r)/n))}export function diesInBucket(o,r,n,e,s){if(n.length===0||e<0||e>=n.length)return[];const i=n[0].rangeLow,t=n[n.length-1].rangeHigh,c=bucketWidthOf(n),a=[];for(const u of o){const m=[];for(const p of u.dies??[]){const d=v(p,r);d===void 0||!Number.isFinite(d)||d<i||d>t||s&&(d<s.lo||d>s.hi)||bucketIndexOf(d,i,c,n.length)===e&&m.push(p)}m.length&&a.push({item:u,dies:m})}return a}export function buildTestHistogramSeries(o,r,n=16,e,s,i){const t=new Map;let c=1/0,a=-1/0;for(const h of o){const l=[];for(const N of h.items)for(const w of N.dies??[]){const g=v(w,r);if(g!==void 0&&Number.isFinite(g)){if(i&&(g<i.lo||g>i.hi))continue;l.push(g),g<c&&(c=g),g>a&&(a=g)}}t.set(h.key,l)}const u=o.map(h=>h.key).filter(h=>t.get(h).length>0);if(u.length===0)return{ranges:[],series:[]};const m=e!==void 0?Math.min(c,e):c,x=((s!==void 0?Math.max(a,s):a)-m||1)/n,y=Array.from({length:n},(h,l)=>({rangeLow:m+l*x,rangeHigh:m+(l+1)*x})),f=bucketWidthOf(y),M=u.map(h=>{const l=new Array(n).fill(0);for(const N of t.get(h))l[bucketIndexOf(N,m,f,n)]++;return{groupKey:h,counts:l}});return{ranges:y,series:M}}
@@ -13,4 +13,6 @@ export type { FacetField, FacetValue, FacetCuration, FacetItem, BuildFacetTableO
13
13
  export type { MetadataKeySelection } from './metadataColumns.js';
14
14
  export { mergeTestDefs } from './mergeTestDefs.js';
15
15
  export type { MergedTestDefs, TestDefConflict, TestDefConflictKind } from './mergeTestDefs.js';
16
+ export { readPlotsFile, writePlotsFile } from './plotSpec.js';
17
+ export type { PlotSpec, ReadPlotsResult } from './plotSpec.js';
16
18
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- export*from"./types.js";export{analyzeWaferMap}from"./analyzeWaferMap.js";export{analyzeWaferLot}from"./analyzeWaferLot.js";export{filterFindings,visibleFindings}from"./filterFindings.js";export{setReportOpener}from"./renderFindingsReport.js";export{renderWaferReportHtml,renderLotReportHtml}from"./renderSummaryReport.js";export{buildFacetTable,facetValueOf,FACET_NONE_VALUE}from"./facets.js";export{mergeTestDefs}from"./mergeTestDefs.js";
1
+ export*from"./types.js";export{analyzeWaferMap}from"./analyzeWaferMap.js";export{analyzeWaferLot}from"./analyzeWaferLot.js";export{filterFindings,visibleFindings}from"./filterFindings.js";export{setReportOpener}from"./renderFindingsReport.js";export{renderWaferReportHtml,renderLotReportHtml}from"./renderSummaryReport.js";export{buildFacetTable,facetValueOf,FACET_NONE_VALUE}from"./facets.js";export{mergeTestDefs}from"./mergeTestDefs.js";export{readPlotsFile,writePlotsFile}from"./plotSpec.js";
@@ -1 +1 @@
1
- import{benjaminiHochberg as v,mannKendall as b}from"./math.js";import{fmt as y}from"../renderer/fmt.js";export const DRIFT_MIN_WAFERS=5;const N=2,M=.5,S=5,x=1;function _(u){const d=[],a=u.map(n=>({waferIndex:n.waferIndex,y:n.summary.stats.yieldPercent})).filter(n=>n.y!==null&&n.y!==void 0);if(a.length>=DRIFT_MIN_WAFERS){const n=b(a.map(e=>e.y));n&&d.push({kind:"yield",label:"Yield",values:a.map(e=>e.y),waferIndices:a.map(e=>e.waferIndex),mk:n})}const r=new Map;for(const{waferIndex:n,summary:e}of u)for(const t of e.stats.perTestStats??[]){if(!Number.isFinite(t.mean)||!(t.count>0))continue;const l=r.get(t.testNumber)??{label:t.label,rows:[]};l.rows.push({waferIndex:n,mean:t.mean,n:t.count,sd:t.stddev}),r.set(t.testNumber,l)}for(const[n,{label:e,rows:t}]of r){if(t.length<DRIFT_MIN_WAFERS)continue;const l=b(t.map(s=>s.mean));if(!l)continue;const m=t.reduce((s,f)=>s+f.n,0),o=Math.sqrt(t.reduce((s,f)=>s+f.n*f.sd**2,0)/m);d.push({kind:"test",testNumber:n,label:e,values:t.map(s=>s.mean),waferIndices:t.map(s=>s.waferIndex),sigma:o,mk:l})}return d}export function buildDriftFindings(u,d){const a=_(u);if(!a.length)return[];const r=v(a.map(e=>e.mk.pValue)),n=[];return a.forEach((e,t)=>{if(r[t]>d)return;const{slope:l,intercept:m,n:o}=e.mk,s=m,i=m+l*(o-1)-s,p=i>0,h=e.kind==="test"&&e.sigma&&e.sigma>0?Math.abs(i)/e.sigma:void 0;if(e.kind==="yield"?Math.abs(i)<N:h===void 0||h<M)return;const g=(e.kind==="yield"?Math.abs(i)>=S:(h??0)>=x)&&r[t]<=.01?"unusual":"notable",k=p?"rises":"falls",c={first:e.values[0],last:e.values[e.values.length-1]},I=e.kind==="yield"?`${c.first.toFixed(1)}% \u2192 ${c.last.toFixed(1)}%`:`${y(c.first)} \u2192 ${y(c.last)}`,w=e.kind==="yield"?"on successive wafers":"across the wafers";n.push({id:e.kind==="yield"?"drift:yield":`drift:test:${e.testNumber}`,level:"inter-wafer",severity:g,variable:e.kind==="yield"?{kind:"yield",label:"Yield"}:{kind:"test",index:e.testNumber,label:e.label},comparison:{family:"wafer",left:"Wafers in input order",right:"a constant level"},effect:{direction:p?"higher":"lower",absoluteDelta:e.kind==="yield"?i/100:i,...e.kind==="yield"?{}:{relativeDelta:s!==0?i/Math.abs(s):void 0},effectSize:e.kind==="yield"?i/100:i/(e.sigma||1)},stats:{method:"mann-kendall",pValue:e.mk.pValue,adjustedPValue:r[t],sampleSizeLeft:o,sampleSizeRight:0},summary:`${e.label} ${k} ${w}: ${I} over ${o} wafers (input order)`,highlight:{kind:"wafer",waferIndices:e.waferIndices}})}),n}
1
+ import{benjaminiHochberg as v,mannKendall as b}from"./math.js";import{fmt as y}from"../renderer/fmt.js";export const DRIFT_MIN_WAFERS=5;const M=2,N=.5,x=5,S=1;function _(m){const d=[],i=m.map(n=>({waferIndex:n.waferIndex,y:n.summary.stats.yieldPercent})).filter(n=>n.y!==null&&n.y!==void 0);if(i.length>=DRIFT_MIN_WAFERS){const n=b(i.map(e=>e.y));n&&d.push({kind:"yield",label:"Yield",values:i.map(e=>e.y),waferIndices:i.map(e=>e.waferIndex),mk:n})}const r=new Map;for(const{waferIndex:n,summary:e}of m)for(const t of e.stats.perTestStats??[]){if(!Number.isFinite(t.mean)||!(t.count>0))continue;const l=r.get(t.testNumber)??{label:t.label,rows:[]};l.rows.push({waferIndex:n,mean:t.mean,n:t.count,sd:t.stddev}),r.set(t.testNumber,l)}for(const[n,{label:e,rows:t}]of r){if(t.length<DRIFT_MIN_WAFERS)continue;const l=b(t.map(s=>s.mean));if(!l)continue;const o=t.reduce((s,u)=>s+Math.max(0,u.n-1),0),f=o>0?Math.sqrt(t.reduce((s,u)=>s+Math.max(0,u.n-1)*u.sd**2,0)/o):0;d.push({kind:"test",testNumber:n,label:e,values:t.map(s=>s.mean),waferIndices:t.map(s=>s.waferIndex),sigma:f,mk:l})}return d}export function buildDriftFindings(m,d){const i=_(m);if(!i.length)return[];const r=v(i.map(e=>e.mk.pValue)),n=[];return i.forEach((e,t)=>{if(r[t]>d)return;const{slope:l,intercept:o,n:f}=e.mk,s=o,a=o+l*(f-1)-s,p=a>0,h=e.kind==="test"&&e.sigma&&e.sigma>0?Math.abs(a)/e.sigma:void 0;if(e.kind==="yield"?Math.abs(a)<M:h===void 0||h<N)return;const g=(e.kind==="yield"?Math.abs(a)>=x:(h??0)>=S)&&r[t]<=.01?"unusual":"notable",k=p?"rises":"falls",c={first:e.values[0],last:e.values[e.values.length-1]},I=e.kind==="yield"?`${c.first.toFixed(1)}% \u2192 ${c.last.toFixed(1)}%`:`${y(c.first)} \u2192 ${y(c.last)}`,w=e.kind==="yield"?"on successive wafers":"across the wafers";n.push({id:e.kind==="yield"?"drift:yield":`drift:test:${e.testNumber}`,level:"inter-wafer",severity:g,variable:e.kind==="yield"?{kind:"yield",label:"Yield"}:{kind:"test",index:e.testNumber,label:e.label},comparison:{family:"wafer",left:"Wafers in input order",right:"a constant level"},effect:{direction:p?"higher":"lower",absoluteDelta:e.kind==="yield"?a/100:a,...e.kind==="yield"?{}:{relativeDelta:s!==0?a/Math.abs(s):void 0},effectSize:e.kind==="yield"?a/100:a/(e.sigma||1)},stats:{method:"mann-kendall",pValue:e.mk.pValue,adjustedPValue:r[t],sampleSizeLeft:f,sampleSizeRight:0},summary:`${e.label} ${k} ${w}: ${I} over ${f} wafers (input order)`,highlight:{kind:"wafer",waferIndices:e.waferIndices}})}),n}
@@ -1 +1 @@
1
- import{getDieKey as R,isPositionedDie as v,isYieldEligibleDie as L,positionKey as h}from"../core/dies.js";import{itemPassBins as j}from"../core/passBins.js";import{classifyFailingDies as C,patternFailVerdict as K}from"./patternClassification.js";import{benjaminiHochberg as D,binomialUpperTail as F}from"./math.js";import{passesRateGate as G,rateRelativeEffect as T,twoProportionZ as V,zPValue as W}from"./analyzeWaferMap.js";const q=.01;export function findLotPattern(H,c){const l=[];let f,k=0,w=0;if(H.forEach((e,n)=>{if(e.isLotStack)return;const s=new Set(j(e)),i=[],t=[];for(const a of e.dies){if(!L(a)||!v(a))continue;const o=K(a,s);o!==void 0&&(i.push(a),t.push(o))}if(!i.length)return;const r=(a,o)=>Math.abs(a-o)<=q*Math.max(Math.abs(a),Math.abs(o));if(!f)f=e,k=i[0].width,w=i[0].height;else if(!r(e.wafer.radius,f.wafer.radius)||!r(i[0].width,k)||!r(i[0].height,w))return;l.push({index:n,dies:i,fails:t})}),!f||l.length<2)return null;const d=new Map;let z=0,m=0;for(const e of l)e.dies.forEach((n,s)=>{const i=h(n);let t=d.get(i);t||d.set(i,t={die:n,n:0,k:0}),t.n++,e.fails[s]&&(t.k++,m++),z++});if(m===0)return null;const E=m/z,p=[...d.values()],M=p.map(e=>{let n=0,s=0;for(let i=-1;i<=1;i++)for(let t=-1;t<=1;t++){const r=i===0&&t===0?e:d.get(h({x:e.die.x+i,y:e.die.y+t}));r&&(n+=r.n,s+=r.k)}return{n,k:s}}),N=D(M.map(e=>F(e.k,e.n,E))),x=new Set,S=[];p.forEach((e,n)=>{N[n]>c.significanceLevel||e.k/e.n<=E||(x.add(h(e.die)),S.push(e.die))});const u=C(S,p.map(e=>e.die),f.wafer,f.ringCount??c.ringCount);if(!u||u.pattern==="random"||u.pattern==="none")return null;const b=l.map(e=>{let n=0,s=0,i=0,t=0;const r=[];e.dies.forEach((o,y)=>{x.has(h(o))?(s++,e.fails[y]&&(n++,r.push(R(o)))):(t++,e.fails[y]&&i++)});const a=s>=c.minimumSampleSize&&t>=c.minimumSampleSize?V(n,s,i,t):0;return{index:e.index,z:a,hits:n,n:s,restHits:i,restN:t,inside:r}}),P=D(b.map(e=>e.z>0?W(e.z):1)),g=new Map;return b.forEach((e,n)=>{if(e.z<=0)return;const s=e.hits/e.n-e.restHits/e.restN,i=T({hits:e.hits,n:e.n,restHits:e.restHits,restN:e.restN,passRate:!1});G(P[n],s,i,c)&&g.set(e.index,e.inside)}),g.size?{classification:u,carriers:g}:null}
1
+ import{requireRingCount as P}from"../core/ringCount.js";import{getDieKey as v,isPositionedDie as L,isYieldEligibleDie as j,positionKey as h}from"../core/dies.js";import{itemPassBins as K}from"../core/passBins.js";import{classifyFailingDies as q,patternFailVerdict as C}from"./patternClassification.js";import{benjaminiHochberg as D,binomialUpperTail as F}from"./math.js";import{passesRateGate as G,rateRelativeEffect as T,twoProportionZ as V,zPValue as W}from"./analyzeWaferMap.js";const A=.01;export function findLotPattern(H,c){const l=[];let f,k=0,w=0;if(H.forEach((e,n)=>{if(e.isLotStack)return;const s=new Set(K(e)),t=[],i=[];for(const o of e.dies){if(!j(o)||!L(o))continue;const a=C(o,s);a!==void 0&&(t.push(o),i.push(a))}if(!t.length)return;const r=(o,a)=>Math.abs(o-a)<=A*Math.max(Math.abs(o),Math.abs(a));if(!f)f=e,k=t[0].width,w=t[0].height;else if(!r(e.wafer.radius,f.wafer.radius)||!r(t[0].width,k)||!r(t[0].height,w))return;l.push({index:n,dies:t,fails:i})}),!f||l.length<2)return null;const d=new Map;let z=0,p=0;for(const e of l)e.dies.forEach((n,s)=>{const t=h(n);let i=d.get(t);i||d.set(t,i={die:n,n:0,k:0}),i.n++,e.fails[s]&&(i.k++,p++),z++});if(p===0)return null;const E=p/z,m=[...d.values()],M=m.map(e=>{let n=0,s=0;for(let t=-1;t<=1;t++)for(let i=-1;i<=1;i++){const r=t===0&&i===0?e:d.get(h({x:e.die.x+t,y:e.die.y+i}));r&&(n+=r.n,s+=r.k)}return{n,k:s}}),N=D(M.map(e=>F(e.k,e.n,E))),x=new Set,S=[];m.forEach((e,n)=>{N[n]>c.significanceLevel||e.k/e.n<=E||(x.add(h(e.die)),S.push(e.die))});const u=q(S,m.map(e=>e.die),f.wafer,P(f,"the lot pattern"));if(!u||u.pattern==="random"||u.pattern==="none")return null;const b=l.map(e=>{let n=0,s=0,t=0,i=0;const r=[];e.dies.forEach((a,y)=>{x.has(h(a))?(s++,e.fails[y]&&(n++,r.push(v(a)))):(i++,e.fails[y]&&t++)});const o=s>=c.minimumSampleSize&&i>=c.minimumSampleSize?V(n,s,t,i):0;return{index:e.index,z:o,hits:n,n:s,restHits:t,restN:i,inside:r}}),R=D(b.map(e=>e.z>0?W(e.z):1)),g=new Map;return b.forEach((e,n)=>{if(e.z<=0)return;const s=e.hits/e.n-e.restHits/e.restN,t=T({hits:e.hits,n:e.n,restHits:e.restHits,restN:e.restN,passRate:!1});G(R[n],s,t,c)&&g.set(e.index,e.inside)}),g.size?{classification:u,carriers:g}:null}
@@ -41,13 +41,18 @@ export declare function robustFence(values: ArrayLike<number>, k?: number, minCo
41
41
  lo: number;
42
42
  hi: number;
43
43
  } | null;
44
+ /** The one variance rule: the sample variance, Σ(x − mean)² / (n − 1); 0 for fewer than two values.
45
+ * `mean` is the values' mean when the caller already has it. Two-pass, not `Σx² − n·x̄²`: the moments
46
+ * form loses most of its significant digits when the variance is small beside the mean, which is the
47
+ * normal shape of a passing parametric test. */
48
+ export declare function sampleVariance(values: ArrayLike<number>, mean?: number): number;
44
49
  /** Descriptive statistics of one population of values — see {@link describeValues}. */
45
50
  export interface DescriptiveStats {
46
51
  min: number;
47
52
  max: number;
48
53
  mean: number;
49
54
  count: number;
50
- /** Population standard deviation (divide by n) — see {@link describeValues}. */
55
+ /** Sample standard deviation (divide by n−1; 0 for fewer than two values) — see {@link sampleVariance}. */
51
56
  stddev: number;
52
57
  median: number;
53
58
  q1: number;
@@ -63,11 +68,9 @@ export interface DescriptiveStats {
63
68
  * up almost all of a large lot panel's time. The mean and σ sum in whatever
64
69
  * order `values` arrives in.
65
70
  *
66
- * σ is the POPULATION standard deviation (divide by n), which is what this
67
- * table has always shown; the capability indices next to it deliberately use
68
- * the sample form (n−1) and say so. Two-pass, not `Σx² − n·x̄²`: the moments
69
- * form loses most of its significant digits when the variance is small beside
70
- * the mean, which is the normal shape of a passing parametric test. */
71
+ * σ is the sample standard deviation (divide by n−1), the same rule as the
72
+ * capability indices and every other σ in the library — see
73
+ * {@link sampleVariance}. */
71
74
  export declare function describeValues(values: Float64Array): DescriptiveStats;
72
75
  /** Min, quartiles and max of `values` — what sorting them and reading the
73
76
  * ends and {@link quantile}s would give — by selection. **Reorders `values`.** */
@@ -1 +1 @@
1
- export function errorFunction(t){const o=t<0?-1:1,n=Math.abs(t),e=.254829592,r=-.284496736,c=1.421413741,i=-1.453152027,s=1.061405429,l=1/(1+.3275911*n),u=1-((((s*l+i)*l+c)*l+r)*l+e)*l*Math.exp(-n*n);return o*u}export function normalCdf(t){return .5*(1+errorFunction(t/Math.sqrt(2)))}export function binomialUpperTail(t,o,n){if(t<=0)return 1;if(t>o||n<=0)return 0;if(n>=1)return 1;let e=t*Math.log(n)+(o-t)*Math.log1p(-n);for(let s=0;s<t;s++)e+=Math.log(o-s)-Math.log(s+1);let r=Math.exp(e),c=0;const i=n/(1-n);for(let s=t;s<=o;s++)c+=r,r*=(o-s)/(s+1)*i;return Math.min(1,c)}export function benjaminiHochberg(t){const o=t.length,n=t.map((c,i)=>i).sort((c,i)=>t[c]-t[i]),e=new Array(o);let r=1;for(let c=o-1;c>=0;c--){const i=n[c];r=Math.min(r,t[i]*o/(c+1),1),e[i]=r}return e}export function quantile(t,o){if(t.length===0)return NaN;const n=o*(t.length-1),e=Math.floor(n),r=Math.ceil(n);return t[e]+(t[r]-t[e])*(n-e)}export function robustFence(t,o=1.5,n=8){const e=Float64Array.from(Array.from(t).filter(s=>Number.isFinite(s)));if(e.length<n)return null;const{q1:r,q3:c}=fiveNumberSummary(e),i=c-r;return i===0?null:{lo:r-o*i,hi:c+o*i}}export function describeValues(t){const o=t.length;let n=0;for(let u=0;u<o;u++)n+=t[u];const e=n/o;let r=0;for(let u=0;u<o;u++)r+=(t[u]-e)**2;const{min:c,q1:i,median:s,q3:h,max:l}=fiveNumberSummary(t);return{min:c,max:l,mean:e,count:o,stddev:Math.sqrt(r/o),median:s,q1:i,q3:h}}export function fiveNumberSummary(t){let o=1/0,n=-1/0;for(let i=0;i<t.length;i++){const s=t[i];s<o&&(o=s),s>n&&(n=s)}t.length===0&&(o=NaN,n=NaN);const{q1:e,median:r,q3:c}=b(t);return{min:o,q1:e,median:r,q3:c,max:n}}function b(t){const o=t.length;if(o===0)return{q1:NaN,median:NaN,q3:NaN};const n=o-1,e=(p,g,f)=>{const a=p*n,m=Math.floor(a),q=N(t,m,g,f);if(a===m)return q;let M=1/0;for(let x=m+1;x<=f;x++)t[x]<M&&(M=t[x]);return q+(M-q)*(a-m)},r=Math.floor(.5*n),c=e(.5,0,n),i=Math.floor(.25*n),s=i===r?e(.25,r,n):u(.25,i,r),l=Math.floor(.75*n)===r?e(.75,r,n):e(.75,r+1,n);return{q1:s,median:c,q3:l};function u(p,g,f){const a=p*n,m=N(t,g,0,f-1);if(a===g)return m;let q=t[f];for(let M=g+1;M<f;M++)t[M]<q&&(q=t[M]);return m+(q-m)*(a-g)}}function N(t,o,n,e){for(;n<e;){const r=t[n],c=t[n+e>>>1],i=t[e],s=r<c?c<i?c:r<i?i:r:r<i?r:c<i?i:c;let h=n,l=e;for(;h<=l;){for(;t[h]<s;)h++;for(;t[l]>s;)l--;if(h<=l){const u=t[h];t[h]=t[l],t[l]=u,h++,l--}}if(o<=l)e=l;else if(o>=h)n=h;else break}return t[o]}export function mannKendall(t){const o=t.length;if(o<3)return null;let n=0;const e=[];for(let f=0;f<o-1;f++)for(let a=f+1;a<o;a++){const m=t[a]-t[f];n+=m>0?1:m<0?-1:0,e.push(m/(a-f))}const r=new Map;for(const f of t)r.set(f,(r.get(f)??0)+1);let c=o*(o-1)*(2*o+5)/18;for(const f of r.values())f>1&&(c-=f*(f-1)*(2*f+5)/18);if(c<=0)return null;const i=n===0?0:(n-Math.sign(n))/Math.sqrt(c),s=Math.min(1,2*(1-normalCdf(Math.abs(i))));e.sort((f,a)=>f-a);const h=e.length>>1,l=e.length%2?e[h]:(e[h-1]+e[h])/2,u=t.map((f,a)=>f-l*a).sort((f,a)=>f-a),p=u.length>>1,g=u.length%2?u[p]:(u[p-1]+u[p])/2;return{n:o,s:n,z:i,pValue:s,slope:l,intercept:g}}
1
+ export function errorFunction(t){const o=t<0?-1:1,n=Math.abs(t),r=.254829592,e=-.284496736,i=1.421413741,c=-1.453152027,s=1.061405429,l=1/(1+.3275911*n),a=1-((((s*l+c)*l+i)*l+e)*l+r)*l*Math.exp(-n*n);return o*a}export function normalCdf(t){return .5*(1+errorFunction(t/Math.sqrt(2)))}export function binomialUpperTail(t,o,n){if(t<=0)return 1;if(t>o||n<=0)return 0;if(n>=1)return 1;let r=t*Math.log(n)+(o-t)*Math.log1p(-n);for(let s=0;s<t;s++)r+=Math.log(o-s)-Math.log(s+1);let e=Math.exp(r),i=0;const c=n/(1-n);for(let s=t;s<=o;s++)i+=e,e*=(o-s)/(s+1)*c;return Math.min(1,i)}export function benjaminiHochberg(t){const o=t.length,n=t.map((i,c)=>c).sort((i,c)=>t[i]-t[c]),r=new Array(o);let e=1;for(let i=o-1;i>=0;i--){const c=n[i];e=Math.min(e,t[c]*o/(i+1),1),r[c]=e}return r}export function quantile(t,o){if(t.length===0)return NaN;const n=o*(t.length-1),r=Math.floor(n),e=Math.ceil(n);return t[r]+(t[e]-t[r])*(n-r)}export function robustFence(t,o=1.5,n=8){const r=Float64Array.from(Array.from(t).filter(s=>Number.isFinite(s)));if(r.length<n)return null;const{q1:e,q3:i}=fiveNumberSummary(r),c=i-e;return c===0?null:{lo:e-o*c,hi:i+o*c}}export function sampleVariance(t,o){const n=t.length;if(n<2)return 0;if(o===void 0){let e=0;for(let i=0;i<n;i++)e+=t[i];o=e/n}let r=0;for(let e=0;e<n;e++)r+=(t[e]-o)**2;return r/(n-1)}export function describeValues(t){const o=t.length;let n=0;for(let a=0;a<o;a++)n+=t[a];const r=n/o,e=Math.sqrt(sampleVariance(t,r)),{min:i,q1:c,median:s,q3:u,max:l}=fiveNumberSummary(t);return{min:i,max:l,mean:r,count:o,stddev:e,median:s,q1:c,q3:u}}export function fiveNumberSummary(t){let o=1/0,n=-1/0;for(let c=0;c<t.length;c++){const s=t[c];s<o&&(o=s),s>n&&(n=s)}t.length===0&&(o=NaN,n=NaN);const{q1:r,median:e,q3:i}=d(t);return{min:o,q1:r,median:e,q3:i,max:n}}function d(t){const o=t.length;if(o===0)return{q1:NaN,median:NaN,q3:NaN};const n=o-1,r=(M,g,f)=>{const h=M*n,m=Math.floor(h),x=N(t,m,g,f);if(h===m)return x;let p=1/0;for(let q=m+1;q<=f;q++)t[q]<p&&(p=t[q]);return x+(p-x)*(h-m)},e=Math.floor(.5*n),i=r(.5,0,n),c=Math.floor(.25*n),s=c===e?r(.25,e,n):a(.25,c,e),l=Math.floor(.75*n)===e?r(.75,e,n):r(.75,e+1,n);return{q1:s,median:i,q3:l};function a(M,g,f){const h=M*n,m=N(t,g,0,f-1);if(h===g)return m;let x=t[f];for(let p=g+1;p<f;p++)t[p]<x&&(x=t[p]);return m+(x-m)*(h-g)}}function N(t,o,n,r){for(;n<r;){const e=t[n],i=t[n+r>>>1],c=t[r],s=e<i?i<c?i:e<c?c:e:e<c?e:i<c?c:i;let u=n,l=r;for(;u<=l;){for(;t[u]<s;)u++;for(;t[l]>s;)l--;if(u<=l){const a=t[u];t[u]=t[l],t[l]=a,u++,l--}}if(o<=l)r=l;else if(o>=u)n=u;else break}return t[o]}export function mannKendall(t){const o=t.length;if(o<3)return null;let n=0;const r=[];for(let f=0;f<o-1;f++)for(let h=f+1;h<o;h++){const m=t[h]-t[f];n+=m>0?1:m<0?-1:0,r.push(m/(h-f))}const e=new Map;for(const f of t)e.set(f,(e.get(f)??0)+1);let i=o*(o-1)*(2*o+5)/18;for(const f of e.values())f>1&&(i-=f*(f-1)*(2*f+5)/18);if(i<=0)return null;const c=n===0?0:(n-Math.sign(n))/Math.sqrt(i),s=Math.min(1,2*(1-normalCdf(Math.abs(c))));r.sort((f,h)=>f-h);const u=r.length>>1,l=r.length%2?r[u]:(r[u-1]+r[u])/2,a=t.map((f,h)=>f-l*h).sort((f,h)=>f-h),M=a.length>>1,g=a.length%2?a[M]:(a[M-1]+a[M])/2;return{n:o,s:n,z:c,pValue:s,slope:l,intercept:g}}
@@ -129,7 +129,7 @@ export declare function patternExplains(pattern: PatternLabel, family: string, r
129
129
  */
130
130
  export declare function classifyPattern(dies: PositionedDie[], wafer: Wafer, options: {
131
131
  passBins: number[];
132
- ringCount?: number;
132
+ ringCount: number;
133
133
  }): PatternClassification | null;
134
134
  /**
135
135
  * {@link classifyPattern} over a failing set already chosen — a wafer's failing
@@ -1 +1 @@
1
- import{findConnectedComponents as j}from"./connectedComponents.js";import{clamp01 as J}from"../core/utils.js";export const DEFAULT_PATTERN_THRESHOLDS={nearFullGlobalRdd:.6,edgeRingEdgeRdd:.18,edgeRingEdgeRddHigh:.3,edgeRingRadialSpread:.45,edgeRingAngularSpread:.6,edgeLocalEdgeRdd:.1,edgeLocalCentroidDist:.76,donutP25Dist:.4,centerCentroidDist:.22,centerCentroidDistHigh:.1,centerEdgeRdd:.35,donutEdgeRdd:.22,scratchLinearScore:.25,scratchLinearScoreHigh:.45,scratchEccentricity:.75,scratchMinReach:.35,minimumFailingDies:5};function q(e,t,r){const d=e.physX-t.center.x,i=e.physY-t.center.y,s=Math.sqrt(d*d+i*i)/t.radius;return Math.min(r,Math.max(1,Math.floor(s*r)+1))===r}function K(e,t,r){if(e.length<3)return 0;let d=0,i=0,s=0;for(const f of e){const g=f.physX-t,R=f.physY-r;d+=g*g,i+=R*R,s+=g*R}d/=e.length,i/=e.length,s/=e.length;const c=d+i,n=d*i-s*s,a=Math.max(0,(c/2)**2-n),h=c/2+Math.sqrt(a),y=c/2-Math.sqrt(a);if(h<=0)return 0;const D=J(y/h);return Math.sqrt(1-D)}function Q(e,t){if(t===0)return 0;const r=new Map,d=new Map,i=new Map,s=new Map;for(const n of e)r.set(n.y,(r.get(n.y)??0)+1),d.set(n.x,(d.get(n.x)??0)+1),i.set(n.x-n.y,(i.get(n.x-n.y)??0)+1),s.set(n.x+n.y,(s.get(n.x+n.y)??0)+1);let c=0;for(const n of r.values())n>c&&(c=n);for(const n of d.values())n>c&&(c=n);for(const n of i.values())n>c&&(c=n);for(const n of s.values())n>c&&(c=n);return c/t}function W(e,t,r,d){const i=t.length,s=e.length,c=i>0?s/i:0,n=t.filter(o=>q(o,r,d)),a=e.filter(o=>q(o,r,d)),h=n.length>0?a.length/n.length:0,y=j(e),D=y.length>0?[...y].sort((o,l)=>l.length-o.length):[e],f=D[0],g=D.slice(0,5).flat(),R=f.length,T=s>0?R/s:0,u=r.center.x,m=r.center.y,p=r.radius;let S=0,N=0,M=1/0,x=0;for(const o of f){S+=o.physX,N+=o.physY;const l=Math.sqrt((o.physX-u)**2+(o.physY-m)**2);l<M&&(M=l),l>x&&(x=l)}const H=S/f.length,O=N/f.length,P=Math.sqrt((H-u)**2+(O-m)**2)/p,A=M===1/0?0:M/p,I=Math.min(x/p,1),C=e.map(o=>Math.sqrt((o.physX-u)**2+(o.physY-m)**2)/p).sort((o,l)=>o-l),v=C[Math.floor(C.length*.25)]??0,b=t.filter(o=>Math.sqrt((o.physX-u)**2+(o.physY-m)**2)/p<.5).length,L=t.filter(o=>Math.sqrt((o.physX-u)**2+(o.physY-m)**2)/p>=.5).length,_=e.filter(o=>Math.sqrt((o.physX-u)**2+(o.physY-m)**2)/p<.5).length,w=e.filter(o=>Math.sqrt((o.physX-u)**2+(o.physY-m)**2)/p>=.5).length,F=b>0?_/b:0,X=L>0?w/L:0,z=X>0?F/X:F>0?2:1,G=g.reduce((o,l)=>o+l.physX,0)/g.length,k=g.reduce((o,l)=>o+l.physY,0)/g.length,B=K(g,G,k),U=Q(g,g.length),E=16,Y=new Set;for(const o of a){const l=(Math.atan2(o.physY-m,o.physX-u)+2*Math.PI)%(2*Math.PI);Y.add(Math.floor(l/(2*Math.PI)*E)%E)}const V=a.length>0?Y.size/E:0;return{globalRdd:c,edgeRdd:h,centroidDistNorm:P,minDistNorm:A,maxDistNorm:I,p25DistNorm:v,eccentricity:B,linearScore:U,salienceSize:R,salienceFraction:T,edgeAngularSpread:V,innerOuterRatio:z}}function Z(e,t,r){const d=Math.max(5,Math.round(r*.003)),i=e.salienceSize>=d;if(e.globalRdd<t.nearFullGlobalRdd&&e.p25DistNorm>=t.edgeLocalCentroidDist&&e.edgeAngularSpread>=t.edgeRingAngularSpread&&e.edgeRdd>=t.edgeLocalEdgeRdd)return{pattern:"edge-ring",confidence:e.edgeRdd>=t.edgeRingEdgeRddHigh?"high":"medium"};if(e.globalRdd<t.nearFullGlobalRdd&&e.salienceFraction<.1)return{pattern:"random",confidence:"low"};if(e.globalRdd>=t.nearFullGlobalRdd)return{pattern:"near-full",confidence:e.globalRdd>=.8?"high":"medium"};if(i&&e.linearScore>=t.scratchLinearScore&&e.eccentricity>=t.scratchEccentricity&&e.maxDistNorm>=t.scratchMinReach){const a=e.linearScore>=t.scratchLinearScoreHigh?"high":"medium";return{pattern:"scratch",confidence:a,note:a==="medium"?"Scratch detection is less reliable for fragmented or diagonal patterns":void 0}}const s=i&&e.edgeRdd>=t.edgeLocalEdgeRdd&&e.maxDistNorm>=.7;if(s&&e.p25DistNorm>=t.edgeLocalCentroidDist&&e.maxDistNorm-e.minDistNorm<=t.edgeRingRadialSpread)return{pattern:"edge-ring",confidence:e.edgeRdd>=t.edgeRingEdgeRddHigh?"high":"medium"};if(s&&e.p25DistNorm>=.55&&e.p25DistNorm<t.edgeLocalCentroidDist)return{pattern:"edge-local",confidence:e.edgeRdd>=t.edgeRingEdgeRdd?"medium":"low"};const c=i&&e.centroidDistNorm<=t.centerCentroidDist&&e.edgeRdd<=t.centerEdgeRdd,n="Center and donut patterns have similar geometry; classification may be imprecise";if(c&&e.p25DistNorm<t.donutP25Dist){const a=e.centroidDistNorm<=t.centerCentroidDistHigh?"high":"medium";return{pattern:"center",confidence:a,note:a!=="high"?n:void 0}}return c&&e.p25DistNorm>=t.donutP25Dist&&e.p25DistNorm<.55?{pattern:"donut",confidence:"medium",note:n}:{pattern:"random",confidence:"low"}}export const PATTERN_LABELS={center:"Center cluster",donut:"Donut","edge-ring":"Edge-ring","edge-local":"Edge-local",scratch:"Scratch","near-full":"Near-full",random:"Random",none:"None"};export function patternFailVerdict(e,t){const r=e.hbin??e.sbin;return r===void 0?void 0:!t.has(r)}export function patternExplains(e,t,r,d){if(!{"edge-ring":["ring","edge-arc"],"edge-local":["edge-arc","sector","quadrant"],center:["ring","cluster"],donut:["ring"],scratch:["cluster","sector","quadrant"],"near-full":["ring"]}[e]?.includes(t))return!1;if(t!=="ring"||r.length===0)return!0;const s=r.includes(d),c=r.includes(1);return e==="edge-ring"||e==="edge-local"?s:e==="center"?c:e==="donut"?!s&&!c:!0}export function classifyPattern(e,t,r){const d=new Set(r.passBins),i=e.filter(s=>patternFailVerdict(s,d)===!0);return classifyFailingDies(i,e,t,r.ringCount??4)}export function classifyFailingDies(e,t,r,d){const i={...DEFAULT_PATTERN_THRESHOLDS},s=Math.max(i.minimumFailingDies,Math.round(t.length*.003));if(e.length<s)return null;const c=W(e,t,r,d),{pattern:n,confidence:a,note:h}=Z(c,i,t.length);return{pattern:n,confidence:a,features:c,...h?{note:h}:{}}}
1
+ import{findConnectedComponents as j}from"./connectedComponents.js";import{clamp01 as J}from"../core/utils.js";export const DEFAULT_PATTERN_THRESHOLDS={nearFullGlobalRdd:.6,edgeRingEdgeRdd:.18,edgeRingEdgeRddHigh:.3,edgeRingRadialSpread:.45,edgeRingAngularSpread:.6,edgeLocalEdgeRdd:.1,edgeLocalCentroidDist:.76,donutP25Dist:.4,centerCentroidDist:.22,centerCentroidDistHigh:.1,centerEdgeRdd:.35,donutEdgeRdd:.22,scratchLinearScore:.25,scratchLinearScoreHigh:.45,scratchEccentricity:.75,scratchMinReach:.35,minimumFailingDies:5};function q(e,t,r){const d=e.physX-t.center.x,i=e.physY-t.center.y,s=Math.sqrt(d*d+i*i)/t.radius;return Math.min(r,Math.max(1,Math.floor(s*r)+1))===r}function K(e,t,r){if(e.length<3)return 0;let d=0,i=0,s=0;for(const f of e){const g=f.physX-t,R=f.physY-r;d+=g*g,i+=R*R,s+=g*R}d/=e.length,i/=e.length,s/=e.length;const c=d+i,n=d*i-s*s,a=Math.max(0,(c/2)**2-n),h=c/2+Math.sqrt(a),y=c/2-Math.sqrt(a);if(h<=0)return 0;const D=J(y/h);return Math.sqrt(1-D)}function Q(e,t){if(t===0)return 0;const r=new Map,d=new Map,i=new Map,s=new Map;for(const n of e)r.set(n.y,(r.get(n.y)??0)+1),d.set(n.x,(d.get(n.x)??0)+1),i.set(n.x-n.y,(i.get(n.x-n.y)??0)+1),s.set(n.x+n.y,(s.get(n.x+n.y)??0)+1);let c=0;for(const n of r.values())n>c&&(c=n);for(const n of d.values())n>c&&(c=n);for(const n of i.values())n>c&&(c=n);for(const n of s.values())n>c&&(c=n);return c/t}function W(e,t,r,d){const i=t.length,s=e.length,c=i>0?s/i:0,n=t.filter(o=>q(o,r,d)),a=e.filter(o=>q(o,r,d)),h=n.length>0?a.length/n.length:0,y=j(e),D=y.length>0?[...y].sort((o,l)=>l.length-o.length):[e],f=D[0],g=D.slice(0,5).flat(),R=f.length,T=s>0?R/s:0,u=r.center.x,m=r.center.y,p=r.radius;let S=0,N=0,M=1/0,x=0;for(const o of f){S+=o.physX,N+=o.physY;const l=Math.sqrt((o.physX-u)**2+(o.physY-m)**2);l<M&&(M=l),l>x&&(x=l)}const H=S/f.length,O=N/f.length,P=Math.sqrt((H-u)**2+(O-m)**2)/p,A=M===1/0?0:M/p,I=Math.min(x/p,1),C=e.map(o=>Math.sqrt((o.physX-u)**2+(o.physY-m)**2)/p).sort((o,l)=>o-l),v=C[Math.floor(C.length*.25)]??0,b=t.filter(o=>Math.sqrt((o.physX-u)**2+(o.physY-m)**2)/p<.5).length,L=t.filter(o=>Math.sqrt((o.physX-u)**2+(o.physY-m)**2)/p>=.5).length,_=e.filter(o=>Math.sqrt((o.physX-u)**2+(o.physY-m)**2)/p<.5).length,w=e.filter(o=>Math.sqrt((o.physX-u)**2+(o.physY-m)**2)/p>=.5).length,F=b>0?_/b:0,X=L>0?w/L:0,z=X>0?F/X:F>0?2:1,G=g.reduce((o,l)=>o+l.physX,0)/g.length,k=g.reduce((o,l)=>o+l.physY,0)/g.length,B=K(g,G,k),U=Q(g,g.length),E=16,Y=new Set;for(const o of a){const l=(Math.atan2(o.physY-m,o.physX-u)+2*Math.PI)%(2*Math.PI);Y.add(Math.floor(l/(2*Math.PI)*E)%E)}const V=a.length>0?Y.size/E:0;return{globalRdd:c,edgeRdd:h,centroidDistNorm:P,minDistNorm:A,maxDistNorm:I,p25DistNorm:v,eccentricity:B,linearScore:U,salienceSize:R,salienceFraction:T,edgeAngularSpread:V,innerOuterRatio:z}}function Z(e,t,r){const d=Math.max(5,Math.round(r*.003)),i=e.salienceSize>=d;if(e.globalRdd<t.nearFullGlobalRdd&&e.p25DistNorm>=t.edgeLocalCentroidDist&&e.edgeAngularSpread>=t.edgeRingAngularSpread&&e.edgeRdd>=t.edgeLocalEdgeRdd)return{pattern:"edge-ring",confidence:e.edgeRdd>=t.edgeRingEdgeRddHigh?"high":"medium"};if(e.globalRdd<t.nearFullGlobalRdd&&e.salienceFraction<.1)return{pattern:"random",confidence:"low"};if(e.globalRdd>=t.nearFullGlobalRdd)return{pattern:"near-full",confidence:e.globalRdd>=.8?"high":"medium"};if(i&&e.linearScore>=t.scratchLinearScore&&e.eccentricity>=t.scratchEccentricity&&e.maxDistNorm>=t.scratchMinReach){const a=e.linearScore>=t.scratchLinearScoreHigh?"high":"medium";return{pattern:"scratch",confidence:a,note:a==="medium"?"Scratch detection is less reliable for fragmented or diagonal patterns":void 0}}const s=i&&e.edgeRdd>=t.edgeLocalEdgeRdd&&e.maxDistNorm>=.7;if(s&&e.p25DistNorm>=t.edgeLocalCentroidDist&&e.maxDistNorm-e.minDistNorm<=t.edgeRingRadialSpread)return{pattern:"edge-ring",confidence:e.edgeRdd>=t.edgeRingEdgeRddHigh?"high":"medium"};if(s&&e.p25DistNorm>=.55&&e.p25DistNorm<t.edgeLocalCentroidDist)return{pattern:"edge-local",confidence:e.edgeRdd>=t.edgeRingEdgeRdd?"medium":"low"};const c=i&&e.centroidDistNorm<=t.centerCentroidDist&&e.edgeRdd<=t.centerEdgeRdd,n="Center and donut patterns have similar geometry; classification may be imprecise";if(c&&e.p25DistNorm<t.donutP25Dist){const a=e.centroidDistNorm<=t.centerCentroidDistHigh?"high":"medium";return{pattern:"center",confidence:a,note:a!=="high"?n:void 0}}return c&&e.p25DistNorm>=t.donutP25Dist&&e.p25DistNorm<.55?{pattern:"donut",confidence:"medium",note:n}:{pattern:"random",confidence:"low"}}export const PATTERN_LABELS={center:"Center cluster",donut:"Donut","edge-ring":"Edge-ring","edge-local":"Edge-local",scratch:"Scratch","near-full":"Near-full",random:"Random",none:"None"};export function patternFailVerdict(e,t){const r=e.hbin??e.sbin;return r===void 0?void 0:!t.has(r)}export function patternExplains(e,t,r,d){if(!{"edge-ring":["ring","edge-arc"],"edge-local":["edge-arc","sector","quadrant"],center:["ring","cluster"],donut:["ring"],scratch:["cluster","sector","quadrant"],"near-full":["ring"]}[e]?.includes(t))return!1;if(t!=="ring"||r.length===0)return!0;const s=r.includes(d),c=r.includes(1);return e==="edge-ring"||e==="edge-local"?s:e==="center"?c:e==="donut"?!s&&!c:!0}export function classifyPattern(e,t,r){const d=new Set(r.passBins),i=e.filter(s=>patternFailVerdict(s,d)===!0);return classifyFailingDies(i,e,t,r.ringCount)}export function classifyFailingDies(e,t,r,d){const i={...DEFAULT_PATTERN_THRESHOLDS},s=Math.max(i.minimumFailingDies,Math.round(t.length*.003));if(e.length<s)return null;const c=W(e,t,r,d),{pattern:n,confidence:a,note:h}=Z(c,i,t.length);return{pattern:n,confidence:a,features:c,...h?{note:h}:{}}}