@wafertools/wafermap 0.31.0 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/AGENTS.md +12 -12
  2. package/CHANGELOG.md +172 -0
  3. package/README.md +3 -3
  4. package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
  5. package/dist/packages/canvas-adapter/dieList.js +7 -7
  6. package/dist/packages/canvas-adapter/drilldown.js +1 -1
  7. package/dist/packages/canvas-adapter/insightsTab.js +1 -1
  8. package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
  9. package/dist/packages/canvas-adapter/renderWaferMap.d.ts +17 -4
  10. package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
  11. package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
  12. package/dist/packages/canvas-adapter/toCanvas.d.ts +23 -2
  13. package/dist/packages/canvas-adapter/toCanvas.js +1 -1
  14. package/dist/packages/canvas-adapter/toolbar.js +2 -2
  15. package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
  16. package/dist/packages/canvas-adapter/userGuideHtml.js +25 -15
  17. package/dist/packages/canvas-adapter/version.d.ts +2 -2
  18. package/dist/packages/canvas-adapter/version.js +1 -1
  19. package/dist/packages/core/aggregates.js +1 -1
  20. package/dist/packages/core/dieTable.d.ts +137 -0
  21. package/dist/packages/core/dieTable.js +1 -0
  22. package/dist/packages/core/dies.d.ts +22 -0
  23. package/dist/packages/core/dies.js +1 -1
  24. package/dist/packages/core/utils.d.ts +14 -0
  25. package/dist/packages/core/utils.js +1 -1
  26. package/dist/packages/renderer/buildView.d.ts +3 -3
  27. package/dist/packages/renderer/buildView.js +1 -1
  28. package/dist/packages/renderer/buildWaferMap.d.ts +29 -11
  29. package/dist/packages/renderer/buildWaferMap.js +1 -1
  30. package/dist/packages/renderer/columnarInput.d.ts +87 -0
  31. package/dist/packages/renderer/columnarInput.js +1 -0
  32. package/dist/packages/renderer/derivedTests/apply.d.ts +8 -6
  33. package/dist/packages/renderer/derivedTests/apply.js +1 -1
  34. package/dist/packages/renderer/index.d.ts +1 -0
  35. package/dist/packages/renderer/spec.d.ts +3 -7
  36. package/dist/packages/renderer/spec.js +1 -1
  37. package/dist/packages/stats/analyzeWaferLot.js +1 -1
  38. package/dist/packages/stats/analyzeWaferMap.d.ts +134 -2
  39. package/dist/packages/stats/analyzeWaferMap.js +1 -1
  40. package/dist/packages/stats/boxplot.js +1 -1
  41. package/dist/packages/stats/capability.d.ts +7 -7
  42. package/dist/packages/stats/capability.js +1 -1
  43. package/dist/packages/stats/clusterDetection.js +1 -1
  44. package/dist/packages/stats/connectedComponents.js +1 -1
  45. package/dist/packages/stats/correlation.js +1 -1
  46. package/dist/packages/stats/filterFindings.d.ts +5 -0
  47. package/dist/packages/stats/filterFindings.js +1 -1
  48. package/dist/packages/stats/histogram.js +1 -1
  49. package/dist/packages/stats/math.d.ts +22 -6
  50. package/dist/packages/stats/math.js +1 -1
  51. package/dist/packages/stats/normalizeInput.js +1 -1
  52. package/dist/packages/stats/patternClassification.d.ts +3 -3
  53. package/dist/packages/stats/patternClassification.js +1 -1
  54. package/dist/packages/stats/regions.d.ts +7 -0
  55. package/dist/packages/stats/regions.js +1 -1
  56. package/dist/packages/stats/renderSummaryReport.js +18 -18
  57. package/dist/packages/stats/scatter.js +1 -1
  58. package/dist/packages/stats/sweep.js +1 -1
  59. package/dist/packages/stats/testPassRate.js +1 -1
  60. package/dist/packages/stats/trend.js +1 -1
  61. package/dist/packages/stats/types.d.ts +5 -0
  62. package/dist/packages/worker/index.js +1 -1
  63. package/dist/packages/worker/wafermap.worker.d.ts +3 -0
  64. package/dist/packages/worker/wafermap.worker.js +1 -1
  65. package/package.json +1 -1
@@ -1 +1 @@
1
- import{normalizeInput as Ke}from"./normalizeInput.js";import{computeCapability as Te,computeTestFlagYield as xe,computeRegionYield as je}from"./summaryFigures.js";import{isYieldEligibleDie as ae,getDieKey as T,isPositionedDie as ye}from"../core/dies.js";import{getTestPassStatus as ee,isParametricTest as H}from"../renderer/buildWaferMap.js";import{markedTestLabel as x,derivedFields as te}from"../renderer/testLabel.js";import{buildQuadrantRegions as Ae,buildReticlePositionRegions as Oe,buildRingRegions as qe,buildSectorRegions as He,buildTestSiteRegions as Ie,sectorCompassNames as Se,areQuadrantsAdjacent as Ye,parseRegionKey as I}from"./regions.js";import{buildClusterFindings as _e}from"./clusterDetection.js";import{classifyPattern as We}from"./patternClassification.js";import{normalCdf as we}from"./math.js";import{quantile as re}from"./math.js";import{mean as ne,clamp01 as $e}from"../core/utils.js";import{classifySpec as le,isOutOfSpec as ve}from"../renderer/spec.js";const Qe={significanceLevel:{min:1e-6,max:1},minimumEffectSize:{min:0,max:1},minimumRelativeEffect:{min:0,max:Number.MAX_SAFE_INTEGER}},De=[4,8,16,32];function Ue(s){const a=Object.fromEntries(Object.entries(s).filter(([,n])=>n!==void 0)),i={...j,...a},o=[],e=[];for(const n of["significanceLevel","minimumEffectSize","minimumRelativeEffect"]){const{min:l,max:t}=Qe[n],u=i[n];if(typeof u!="number"||!Number.isFinite(u)){e.push(`${n}=${String(u)} is not a finite number (using ${j[n]})`),i[n]=j[n];continue}const f=Math.min(t,Math.max(l,u));f!==u&&(e.push(`${n}=${u} is outside ${l}\u2013${t} (using ${f})`),i[n]=f)}if(De.includes(i.sectorCount)||(e.push(`sectorCount=${i.sectorCount} is not one of ${De.join(", ")} (using ${j.sectorCount})`),i.sectorCount=j.sectorCount),e.length){const n=`analyzeWaferMap: ${e.join("; ")}. The analysis ran with the corrected values.`;console.warn(`[wafermap] ${n}`),o.push({code:"analysis-option-corrected",message:n,severity:"warning"})}const c=["passBins","ringCount"].filter(n=>a[n]!==void 0);if(c.length){const n=`analyzeWaferMap: ${c.join(" and ")} ${c.length>1?"are":"is"} no longer an analysis option and was ignored. Set ${c.length>1?"them":"it"} on buildWaferMap; the analysis used the value the map was built with.`;console.warn(`[wafermap] ${n}`),o.push({code:"analysis-option-corrected",message:n,severity:"warning"})}const r=["enableYieldAnalysis","enableHardBinAnalysis","enableSoftBinAnalysis","enableReticlePositionAnalysis","enableTestSiteAnalysis","enableClusterAnalysis","enableAngularAnalysis","enablePatternClassification"].filter(n=>a[n]!==void 0);if(r.length){const n=`analyzeWaferMap: ${r.join(", ")} ${r.length>1?"are":"is"} no longer an option and was ignored: every analysis now runs. Filter the findings (filterFindings) to show fewer.`;console.warn(`[wafermap] ${n}`),o.push({code:"analysis-option-corrected",message:n,severity:"warning"})}return{resolved:i,warnings:o}}const j={ringCount:4,passBins:[1],significanceLevel:.05,minimumEffectSize:.2,minimumRelativeEffect:1,minimumSampleSize:5,enableTestValueAnalysis:!1,computePerTestStats:!1,sectorCount:8,minimumClusterSize:5};function Ge(s,a){const i={...s};return i.minimumClusterSize=Math.max(5,Math.round(a*.003)),i}function Xe(s){return ae(s)?s.hbin!==void 0||s.sbin!==void 0||s.testValues!==void 0&&Object.keys(s.testValues).length>0:!1}function Je(s,a,i){if(!s||a)return;const o=(i??[]).filter(e=>H(e)&&(e.limitLow!==void 0||e.limitHigh!==void 0));if(o.length!==0)return e=>{for(const c of o)if(ve(le(e.testValues?.[c.testNumber],c)))return!0;return!1}}function Ze(s,a,i){const o=new Set,e=new Set,c=new Set,r=new Map,n=new Map;for(const l of s){if(l.testValues)for(const t of Object.keys(l.testValues))o.add(Number(t));if(l.testPass)for(const t of Object.keys(l.testPass))o.add(Number(t));l.hbin!==void 0&&e.add(l.hbin),l.sbin!==void 0&&c.add(l.sbin),ae(l)&&(l.hbin!==void 0&&r.set(l.hbin,(r.get(l.hbin)??0)+1),l.sbin!==void 0&&n.set(l.sbin,(n.get(l.sbin)??0)+1))}return{totalDies:s.length,analyzedDies:a,excludedDies:s.length-a,yieldPercent:i,testsConsidered:[...o].sort((l,t)=>l-t),hardBinsConsidered:[...e].sort((l,t)=>l-t),softBinsConsidered:[...c].sort((l,t)=>l-t),...r.size?{hardBinCounts:Object.fromEntries(r)}:{},...n.size?{softBinCounts:Object.fromEntries(n)}:{}}}function et(s,a){if(!a?.length)return;const i=a.filter(e=>H(e)&&(e.limitLow!==void 0||e.limitHigh!==void 0));if(!i.length)return;const o=[];for(const e of i){const c=e.testNumber;let r=0,n=0,l=0,t=0;for(const u of s){if(u.partial||u.edgeExcluded)continue;const f=le(u.testValues?.[c],e);f!==null&&(t++,f==="failLow"?n++:f==="failHigh"?l++:r++)}o.push({testNumber:c,label:e.name,passDies:r,failLowDies:n,failHighDies:l,totalDies:t,yieldPercent:t>0?r/t*100:null})}return o.length?o:void 0}export function computeFunctionalYield(s,a){if(!a?.length)return;const i=a.filter(e=>!H(e));if(!i.length)return;const o=[];for(const e of i){const c=e.testNumber;let r=0,n=0;for(const t of s){if(t.partial||t.edgeExcluded)continue;const u=ee(t,c,e);u!==void 0&&(u?r++:n++)}const l=r+n;o.push({testNumber:c,label:x(e,c),...te(e),passDies:r,failDies:n,totalDies:l,passRatePercent:l>0?r/l*100:null})}return o.length?o:void 0}function tt(s,a,i,o){const e=[];for(const c of a){const r=[];for(const f of s){if(f.partial||f.edgeExcluded)continue;const m=f.testValues?.[c];m!==void 0&&Number.isFinite(m)&&r.push(m)}if(r.length<o)continue;const n=ne(r),l=Math.sqrt(he(r,n)),t=Float64Array.from(r);t.sort();const u=i?.find(f=>f.testNumber===c)?.name??String(c);e.push({testNumber:c,label:u,count:r.length,min:t[0],max:t[t.length-1],mean:n,stddev:l,median:re(t,.5),q1:re(t,.25),q3:re(t,.75)})}return e.length?e:void 0}function A(s,a,i,o){const e=(s+i)/(a+o),c=e*(1-e)*(1/a+1/o);if(!Number.isFinite(c)||c<=0)return 1;const r=(s/a-i/o)/Math.sqrt(c);return $e(2*(1-we(Math.abs(r))))}function fe(s){const a=new Map;for(const i of s){const o=`${i.variable.kind}:${i.comparison.family}`,e=a.get(o)??[];e.push(i),a.set(o,e)}for(const i of a.values()){const o=[...i].filter(r=>r.stats.pValue!==void 0).sort((r,n)=>(r.stats.pValue??1)-(n.stats.pValue??1)),e=o.length;let c=1;for(let r=e-1;r>=0;r--){const n=o[r],l=n.stats.pValue??1,t=Math.min(1,Math.min(c,l*e/(r+1)));n.stats.adjustedPValue=t,c=t}}return s}function ie(s,a,i){const o=Math.abs(a),e=i!==void 0?Math.abs(i):0;return s<=.01&&(o>=.3||e>=2.5)?"unusual":s<=.05&&(o>=.2||e>=1.5)?"notable":"info"}function ce(s,a){return s<=.01&&Math.abs(a)>=.5?"unusual":s<=.05&&Math.abs(a)>=.15?"notable":"info"}function se(s){return s==="reticle-position"?"other reticle positions":s==="test-site"?"other test sites":"the rest of the map"}function Y(s){return s==="reticle-position"?"Other reticle positions":s==="test-site"?"Other test sites":"Rest of map"}function ke(s,a,i){const o=(Math.abs(a)*100).toFixed(1);return`${s} yield is ${o} percentage points ${a>0?"higher":"lower"} than ${se(i)}`}function ue(s,a){return a==="quadrant"&&!s.startsWith("Quadrant")?`quadrant ${s}`:s}function ze(s){return/^(Rings|Sectors|Quadrants)\b/.test(s)?"have":"has"}function Me(s,a,i,o){const e=ue(s,o),c=(Math.abs(i)*100).toFixed(1);return`${e} ${ze(e)} ${a} occurrence ${c} percentage points ${i>0?"higher":"lower"} than ${se(o)}`}function Re(s,a,i,o){const e=ue(s,o),c=(Math.abs(i)*100).toFixed(1);return`${e} ${ze(e)} ${a} pass rate ${c} percentage points ${i>0?"higher":"lower"} than ${se(o)}`}function Be(s,a,i,o,e,c){const r=ue(s,e),n=se(e),l=i>0?"higher":"lower";if(o!==void 0&&Number.isFinite(o)){const u=(Math.abs(o)*100).toFixed(1);return`${r} mean ${a} is ${u}% ${l} than ${n}`}const t=c?` ${c}`:"";return`${r} mean ${a} is ${Math.abs(i).toPrecision(3)}${t} ${l} than ${n}`}function Fe(s,a,i){const o=a?.find(e=>e.bin===s);return o?.name?`${i} ${s} (${o.name})`:`${i} ${s}`}function nt(s,a){const i=a?.find(o=>o.testNumber===s);return{label:x(i,s),unit:i?.unit,...te(i)}}function de(s,a){const i=new Map(s.map(e=>[T(e),e])),o=new Map;for(const e of a){const c=[];for(const r of e.dieKeys){const n=i.get(r);n&&c.push(n)}o.set(e.key,c)}return o}function ge(s,a){return fe(s),s.filter(i=>{const o=i.stats.adjustedPValue??i.stats.pValue??1,e=Math.abs(i.effect.absoluteDelta??0),c=Math.abs(i.effect.relativeDelta??0);return o<=a.significanceLevel&&(e>=a.minimumEffectSize||c>=a.minimumRelativeEffect)}).map(i=>({...i,severity:ie(i.stats.adjustedPValue??i.stats.pValue??1,i.effect.absoluteDelta??0,i.effect.relativeDelta)}))}function W(s,a,i,o){const e=new Set(i),c=de(s,a),r=new Map,n=new Map;for(const[t,u]of c){let f=0,m=0;for(const g of u)g.hbin!==void 0&&(m++,e.has(g.hbin)&&f++);r.set(t,f),n.set(t,m)}const l=[];for(const t of a){const u=n.get(t.key)??0,f=r.get(t.key)??0;let m=0,g=0;for(const[k,h]of n)k!==t.key&&(m+=h,g+=r.get(k)??0);if(u<o.minimumSampleSize||m<o.minimumSampleSize)continue;const p=f/u,b=g/m,y=p-b,M=A(f,u,g,m);l.push({id:`yield:${t.key}`,level:"wafer",severity:"info",variable:{kind:"yield",label:"Yield"},comparison:{family:t.family,left:t.label,right:Y(t.family)},effect:{direction:y===0?"different":y>0?"higher":"lower",absoluteDelta:y,relativeDelta:b===0?void 0:y/b,effectSize:y},stats:{method:"two-proportion-z",pValue:M,sampleSizeLeft:u,sampleSizeRight:m},summary:ke(t.label,y,t.family),highlight:{kind:"region",regionFamily:t.family,regionKeys:[t.key],dieKeys:[...t.dieKeys]}})}return ge(l,o)}function L(s,a,i,o,e,c){const r=g=>i==="soft"?g.sbin:g.hbin,n=[...new Set(s.map(r).filter(g=>g!==void 0))].sort((g,p)=>g-p),l=de(s,a),t=new Map,u=new Map;for(const[g,p]of l){const b=new Map;for(const y of p){const M=r(y);M!==void 0&&b.set(M,(b.get(M)??0)+1)}t.set(g,b),u.set(g,p.length)}const f=[],m=e==="hardBin"?"HBin":"SBin";for(const g of a){const p=u.get(g.key)??0,b=t.get(g.key);let y=0;const M=new Map;for(const[k,h]of t)if(k!==g.key){y+=u.get(k)??0;for(const[S,v]of h)M.set(S,(M.get(S)??0)+v)}if(!(p<c.minimumSampleSize||y<c.minimumSampleSize))for(const k of n){const h=b.get(k)??0,S=M.get(k)??0,v=h/p,w=S/y,$=v-w,D=A(h,p,S,y),z=Fe(k,o,m);f.push({id:`${e}:${k}:${g.key}`,level:"wafer",severity:"info",variable:{kind:e,bin:k,label:z},comparison:{family:g.family,left:g.label,right:Y(g.family)},effect:{direction:$===0?"different":$>0?"higher":"lower",absoluteDelta:$,relativeDelta:w===0?void 0:$/w,effectSize:$},stats:{method:"two-proportion-z",pValue:D,sampleSizeLeft:p,sampleSizeRight:y},summary:Me(g.label,z,$,g.family),highlight:{kind:"bin",bin:k,regionKeys:[g.key],dieKeys:[...g.dieKeys]}})}}return ge(f,c)}function Q(s,a,i,o){const e=(i??[]).filter(u=>!H(u));if(!e.length)return[];const c=e.map(u=>u.testNumber),r=de(s,a),n=new Map,l=new Map;for(const[u,f]of r){const m=new Uint32Array(e.length),g=new Uint32Array(e.length);for(const p of f)for(let b=0;b<e.length;b++){const y=ee(p,c[b],e[b]);y!==void 0&&(g[b]++,y&&m[b]++)}n.set(u,m),l.set(u,g)}const t=[];for(const u of a){const f=n.get(u.key),m=l.get(u.key);for(let g=0;g<e.length;g++){let p=0,b=0;for(const[$,D]of l)$!==u.key&&(b+=D[g],p+=n.get($)[g]);const y=m[g],M=f[g];if(y<o.minimumSampleSize||b<o.minimumSampleSize)continue;const k=M/y,h=p/b,S=k-h,v=A(M,y,p,b),w=c[g];t.push({id:`functional:${w}:${u.key}`,level:"wafer",severity:"info",variable:{kind:"functionalTest",index:w,label:`${x(e[g],w)} pass rate`,...te(e[g])},comparison:{family:u.family,left:u.label,right:Y(u.family)},effect:{direction:S===0?"different":S>0?"higher":"lower",absoluteDelta:S,relativeDelta:h===0?void 0:S/h,effectSize:S},stats:{method:"two-proportion-z",pValue:v,sampleSizeLeft:y,sampleSizeRight:b},summary:Re(u.label,x(e[g],w),S,u.family),highlight:{kind:"region",regionFamily:u.family,regionKeys:[u.key],dieKeys:[...u.dieKeys]}})}}return ge(t,o)}function he(s,a){return s.length<2?0:s.reduce((i,o)=>i+(o-a)**2,0)/(s.length-1)}function Ne(s,a,i,o,e,c){const r=Math.sqrt(i/s+c/o),n=a-e;if(!Number.isFinite(r)||r===0)return{pValue:1,effectSize:0,delta:n};const l=n/r,t=Math.sqrt(Math.max(0,(i+c)/2)),u=t===0?n:n/t;return{pValue:$e(2*(1-we(Math.abs(l)))),effectSize:u,delta:n}}function it(s,a){const i=ne(s),o=ne(a);return Ne(s.length,i,he(s,i),a.length,o,he(a,o))}const Pe=250;function Ce(s,a,i){const o=r=>r.filter(n=>H(i?.find(l=>l.testNumber===n)));if(a)return{testNumbers:o(a.slice().sort((r,n)=>r-n))};const e=new Set;let c=!1;e:for(const r of s)for(const n of Object.keys(r.testValues??{})){const l=Number(n);if(!e.has(l)&&(e.add(l),e.size>Pe)){c=!0;break e}}if(c){const r=`Test-value analysis was skipped: more than ${Pe} tests were found in the die data, so no test findings were computed. Pass testNumbers: [...] to analyse specific tests.`;return console.warn(`[wafermap] analyzeWaferMap: ${r}`),{testNumbers:[],warning:{code:"test-count-capped",message:r,severity:"warning"}}}return{testNumbers:o([...e].sort((r,n)=>r-n))}}function U(s,a,i,o){const e=Ce(s,o.testNumbers,i);if(e.warning)return{findings:[],warning:e.warning};const c=e.testNumbers,r=new Map;for(let p=0;p<a.length;p++)for(const b of a[p].dieKeys)r.set(b,p);const n=a.length,l=[],t=[];for(const p of s){const b=r.get(T(p));b!==void 0&&(l.push(p),t.push(b))}const u=[],f=new Float64Array(n),m=new Float64Array(n),g=new Float64Array(n);for(const p of c){f.fill(0),m.fill(0),g.fill(0);let b=0,y=0,M=0,k;for(let h=0;h<l.length;h++){const v=l[h].testValues?.[p];if(v===void 0)continue;k===void 0&&(k=v);const w=v-k,$=t[h];f[$]+=1,m[$]+=w,g[$]+=w*w,b+=1,y+=w,M+=w*w}if(k!==void 0)for(let h=0;h<n;h++){const S=f[h],v=b-S;if(S<o.minimumSampleSize||v<o.minimumSampleSize)continue;const w=m[h]/S,D=(y-m[h])/v,z=w+k,N=D+k,B=S>1?Math.max(0,g[h]-S*w*w)/(S-1):0,d=v>1?Math.max(0,M-g[h]-v*D*D)/(v-1):0,{pValue:F,effectSize:R,delta:V}=Ne(S,z,B,v,N,d),P=a[h],{label:q,unit:G,...X}=nt(p,i),J=N!==0?V/Math.abs(N):void 0;u.push({id:`test:${p}:${P.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:p,label:q,unit:G,...X},comparison:{family:P.family,left:P.label,right:Y(P.family)},effect:{direction:V===0?"different":V>0?"higher":"lower",absoluteDelta:V,relativeDelta:J,effectSize:R},stats:{method:"welch-z-approx",pValue:F,sampleSizeLeft:S,sampleSizeRight:v},summary:Be(P.label,q,V,J,P.family,G),highlight:{kind:"region",regionFamily:P.family,regionKeys:[P.key],dieKeys:[...P.dieKeys]}})}}return fe(u),{activeTestNumbers:c,findings:u.filter(p=>{const b=p.stats.adjustedPValue??p.stats.pValue??1,y=Math.abs(p.effect.effectSize??0);return b<=o.significanceLevel&&y>=o.minimumEffectSize}).map(p=>({...p,severity:ce(p.stats.adjustedPValue??p.stats.pValue??1,p.effect.effectSize??0)}))}}function st(s,a,i,o){if(!i?.length)return[];const e=i.filter(n=>H(n)&&(n.limitLow!==void 0||n.limitHigh!==void 0));if(!e.length)return[];const c=[],r=new Map(s.map(n=>[T(n),n]));for(const n of e){const l=n.testNumber;for(const t of a){const u=new Map;for(const m of t){const g=[];for(const p of m.dieKeys){const b=r.get(p);b&&g.push(b)}u.set(m.key,g)}const f=[];for(const m of t){const g=u.get(m.key),p=[];for(const[z,N]of u)if(z!==m.key)for(const B of N)p.push(B);const b=z=>z.testValues?.[l]!==void 0,y=z=>ve(le(z.testValues?.[l],n)),M=g.filter(b),k=p.filter(b);if(M.length<o.minimumSampleSize||k.length<o.minimumSampleSize)continue;const h=M.filter(y).length,S=k.filter(y).length,v=h/M.length,w=S/k.length,$=v-w,D=A(h,M.length,S,k.length);f.push({id:`specLimit:${l}:${m.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:l,label:x(n,l),unit:n.unit,...te(n)},comparison:{family:m.family,left:m.label,right:Y(m.family)},effect:{direction:$===0?"different":$>0?"higher":"lower",absoluteDelta:$,relativeDelta:w===0?void 0:$/w,effectSize:$},stats:{method:"two-proportion-z",pValue:D,sampleSizeLeft:M.length,sampleSizeRight:k.length},summary:`${m.label} limit fail rate for ${x(n,l)} is ${(Math.abs($)*100).toFixed(1)} pp ${$>0?"higher":"lower"} than the rest of the wafer`,highlight:{kind:"region",regionFamily:m.family,regionKeys:[m.key],dieKeys:[...m.dieKeys]}})}fe(f),c.push(...f.filter(m=>{const g=m.stats.adjustedPValue??m.stats.pValue??1,p=Math.abs(m.effect.absoluteDelta??0),b=Math.abs(m.effect.relativeDelta??0);return g<=o.significanceLevel&&(p>=o.minimumEffectSize||b>=o.minimumRelativeEffect)}).map(m=>({...m,severity:ce(m.stats.adjustedPValue??m.stats.pValue??1,m.effect.effectSize??0)})))}}return c}function O(s){return s.highlight.regionKeys??[]}function ot(s){const a=s.variable.bin??s.variable.index??"";return`${s.comparison.family}\0${s.variable.kind}\0${a}\0${s.effect.direction}`}function _(s,a){const i=O(s)[0]??"",o=I(i);return o.family==="ring"?o.ring??0:o.family==="sector"?Se(a).indexOf(o.sector??""):0}function at(s,a,i){if(a==="quadrant"){const r=new Map;for(const u of s){const f=I(O(u)[0]??"").quadrant;f&&r.set(f,u)}const n=[...r.keys()],l=new Set,t=[];for(const u of n){if(l.has(u))continue;const f=[],m=[u];for(l.add(u);m.length;){const g=m.pop();f.push(g);for(const p of n)!l.has(p)&&Ye(g,p)&&(l.add(p),m.push(p))}t.push(f.map(g=>r.get(g)))}return t}const o=[...s].sort((r,n)=>_(r,i)-_(n,i)),e=[];let c=[];for(const r of o){if(c.length===0){c.push(r);continue}const n=_(c[c.length-1],i);_(r,i)-n===1?c.push(r):(e.push(c),c=[r])}if(c.length&&e.push(c),a==="sector"&&e.length>1){const n=Se(i).length,l=_(e[0][0],i),t=_(e[e.length-1][e[e.length-1].length-1],i);if(l===0&&t===n-1){const u=e.pop();e[0]=[...u,...e[0]]}}return e}function rt(s,a){const i=s.map(r=>I(O(r)[0]??"").ring).filter(r=>r!==void 0).sort((r,n)=>r-n),o=i[0],e=i[i.length-1];let c="";return o===e&&o===1&&a>1?c=" (core)":o===e&&o===a&&a>1&&(c=" (edge)"),o===e?`Ring ${o}${c}`:`Rings ${o}\u2013${e}`}function lt(s,a){const i=s.map(o=>I(O(o)[0]??"").sector??"");return i.length===1?`Sector ${i[0]}`:`Sectors ${i[0]}\u2013${i[i.length-1]}`}function ft(s){const a=s.map(i=>I(O(i)[0]??"").quadrant??"");return a.length===1?a[0]:a.length===2?`Quadrants ${a[0]} & ${a[1]}`:`Quadrants ${a.slice(0,-1).join(", ")} & ${a[a.length-1]}`}function me(s){return[...new Set(s)]}function ct(s,a){const i=s[0],o=i.comparison.family,e=i.variable.kind,c=me(s.flatMap(h=>h.highlight.dieKeys??[])),r=me(s.flatMap(O)),n=new Set(c),l=o==="ring"?rt(s,a.ringCount):o==="sector"?lt(s,a.sectorCount):ft(s),t=e==="softBin"?a.softEligibleDies:e==="test"?a.testDies:a.eligibleDies,u=t.filter(h=>n.has(T(h))),f=t.filter(h=>!n.has(T(h)));let m,g,p,b,y;if(e==="test"){const h=i.variable.index,S=d=>d.testValues?.[h],v=u.map(S).filter(d=>d!==void 0),w=f.map(S).filter(d=>d!==void 0),{pValue:$,effectSize:D,delta:z}=it(v,w),N=ne(w),B=N!==0?z/Math.abs(N):void 0;m={direction:z===0?"different":z>0?"higher":"lower",absoluteDelta:z,relativeDelta:B,effectSize:D},g={method:"welch-z-approx",pValue:$,sampleSizeLeft:v.length,sampleSizeRight:w.length},p=ce($,D),b=Be(l,i.variable.label,z,B,o,i.variable.unit),y=`test:${h}`}else if(e==="yield"){const h=new Set(a.passBins),S=u.filter(R=>R.hbin!==void 0),v=f.filter(R=>R.hbin!==void 0),w=S.length,$=v.length,D=S.filter(R=>h.has(R.hbin)).length,z=v.filter(R=>h.has(R.hbin)).length,N=w>0?D/w:0,B=$>0?z/$:0,d=N-B,F=A(D,w,z,$);m={direction:d===0?"different":d>0?"higher":"lower",absoluteDelta:d,relativeDelta:B===0?void 0:d/B,effectSize:d},g={method:"two-proportion-z",pValue:F,sampleSizeLeft:w,sampleSizeRight:$},p=ie(F,d,m.relativeDelta),b=ke(l,d,o),y="yield"}else if(e==="functionalTest"){const h=i.variable.index,S=a.testDefs?.find(F=>F.testNumber===h);let v=0,w=0,$=0,D=0;for(const F of u){const R=ee(F,h,S);R!==void 0&&(w++,R&&v++)}for(const F of f){const R=ee(F,h,S);R!==void 0&&(D++,R&&$++)}const z=w>0?v/w:0,N=D>0?$/D:0,B=z-N,d=A(v,w,$,D);m={direction:B===0?"different":B>0?"higher":"lower",absoluteDelta:B,relativeDelta:N===0?void 0:B/N,effectSize:B},g={method:"two-proportion-z",pValue:d,sampleSizeLeft:w,sampleSizeRight:D},p=ie(d,B,m.relativeDelta),b=Re(l,x(S,h),B,o),y=`functional:${h}`}else{const h=i.variable.bin,S=d=>e==="softBin"?d.sbin:d.hbin,v=u.filter(d=>S(d)===h).length,w=f.filter(d=>S(d)===h).length,$=v/u.length,D=w/f.length,z=$-D,N=A(v,u.length,w,f.length);m={direction:z===0?"different":z>0?"higher":"lower",absoluteDelta:z,relativeDelta:D===0?void 0:z/D,effectSize:z},g={method:"two-proportion-z",pValue:N,sampleSizeLeft:u.length,sampleSizeRight:f.length},p=ie(N,z,m.relativeDelta);const B=e==="softBin"?a.sbinDefs:a.hbinDefs;b=Me(l,Fe(h,B,e==="softBin"?"SBin":"HBin"),z,o),y=`${e}:${h}`}const M=me(r.map(h=>h.replace(`${o}:`,""))).join("-"),k=e==="hardBin"||e==="softBin"?{kind:"bin",bin:i.variable.bin,regionKeys:r,dieKeys:c}:{kind:"region",regionFamily:o,regionKeys:r,dieKeys:c};return{...i,id:`${y}:${o}:${M}`,severity:p,comparison:{family:o,left:l,right:Y(o)},effect:m,stats:g,summary:b,highlight:k,relatedIds:s.map(h=>h.id)}}function ut(s,a){const i=new Set(["ring","quadrant","sector"]),o=[],e=new Map;for(const r of s){if(!i.has(r.comparison.family)){o.push(r);continue}const n=ot(r),l=e.get(n)??[];l.push(r),e.set(n,l)}const c=[];for(const r of e.values()){if(r.length===1){c.push(r[0]);continue}const n=r[0].comparison.family;for(const l of at(r,n,a.sectorCount))c.push(l.length===1?l[0]:ct(l,a))}return[...o,...c]}function dt(s){const a=new Map,i=new Map;for(const e of s){const c=T(e);e.hbin!==void 0&&e.hbin!==null&&(a.get(e.hbin)??a.set(e.hbin,new Set).get(e.hbin)).add(c),e.sbin!==void 0&&e.sbin!==null&&(i.get(e.sbin)??i.set(e.sbin,new Set).get(e.sbin)).add(c)}const o=new Set;for(const[e,c]of a)for(const[r,n]of i){if(c.size!==n.size)continue;let l=!0;for(const t of c)if(!n.has(t)){l=!1;break}l&&o.add(`${e}:${r}`)}return o}function gt(s){return`${s.comparison.family}\0${s.comparison.left}`}function ht(s,a,i){const o=(n,l)=>{n.absorbedIds=[...n.absorbedIds??[],l.id]},e=new Map;for(const n of s){const l=gt(n);(e.get(l)??e.set(l,[]).get(l)).push(n)}const c=dt(a),r=new Set;for(const n of e.values()){for(const t of n)if(!(t.variable.kind!=="hardBin"||t.variable.bin===void 0))for(const u of n){if(u.variable.kind!=="softBin"||u.variable.bin===void 0||r.has(u.id)||!c.has(`${t.variable.bin}:${u.variable.bin}`)||u.effect.direction!==t.effect.direction)continue;o(t,u),r.add(u.id);const f=t.variable.label.replace(/^HBin\s+/,""),m=u.variable.label.replace(/^SBin\s+/,""),g=f===m?`HBin and SBin ${f} (same dies)`:`${t.variable.label} and ${u.variable.label} (same dies)`;t.summary=t.summary.replace(t.variable.label,g),t.variable.label=g;break}if(i.length!==1)continue;const l=n.find(t=>t.variable.kind==="yield");if(l){for(const t of n)if(!(t===l||r.has(t.id)||!((t.variable.kind==="hardBin"||t.variable.kind==="softBin")&&t.variable.bin===i[0]))){o(l,t),r.add(t.id);for(const f of t.absorbedIds??[])l.absorbedIds=[...l.absorbedIds??[],f]}}}}export function analyzeWaferMap(s,a={}){const{resolved:i,warnings:o}=Ue(a),e=Ke(s),c={...i,passBins:e.passBins??j.passBins,ringCount:e.ringCount??j.ringCount},r=e.isLotStack,n=e.aggrMethod,l=!r||n==="mode"||n==="countBin"||n==="percent",t=e.dies.filter(d=>Xe(d)),u=t.filter(ye),f=Ge(c,t.length),m=e.dies.filter(d=>ae(d)),g=m.filter(ye),p=qe(g,e.wafer,f.ringCount),b=Ae(g,e.wafer,f.ringCount),y=Oe(g,e.reticleConfig),M=Ie(m,!1),k=He(g,e.wafer,f.sectorCount),h=[];l&&h.push(...W(t,p,f.passBins,f),...W(t,b,f.passBins,f),...W(t,y,f.passBins,f),...W(t,M,f.passBins,f),...W(t,k,f.passBins,f)),l&&h.push(...L(t,p,"hard",e.hbinDefs,"hardBin",f),...L(t,b,"hard",e.hbinDefs,"hardBin",f),...L(t,y,"hard",e.hbinDefs,"hardBin",f),...L(t,M,"hard",e.hbinDefs,"hardBin",f),...L(t,k,"hard",e.hbinDefs,"hardBin",f));{const d=t.filter(F=>F.sbin!==void 0);h.push(...L(d,p,"soft",e.sbinDefs,"softBin",f),...L(d,b,"soft",e.sbinDefs,"softBin",f),...L(d,y,"soft",e.sbinDefs,"softBin",f),...L(d,M,"soft",e.sbinDefs,"softBin",f),...L(d,k,"soft",e.sbinDefs,"softBin",f))}const S=[...o];let v;if(f.enableTestValueAnalysis){const d=U(t,p,e.testDefs,f),F=U(t,b,e.testDefs,f),R=U(t,y,e.testDefs,f),V=U(t,M,e.testDefs,f),P=U(t,k,e.testDefs,f);h.push(...d.findings,...F.findings,...R.findings,...V.findings,...P.findings),d.warning&&S.push(d.warning),v=d.activeTestNumbers,h.push(...st(t,[p,b,y,M,k],e.testDefs,f)),h.push(...Q(t,p,e.testDefs,f),...Q(t,b,e.testDefs,f),...Q(t,y,e.testDefs,f),...Q(t,M,e.testDefs,f),...Q(t,k,e.testDefs,f))}{const d=Je(r,l,e.testDefs);(!r||l||d!==void 0)&&h.push(..._e(u,e.wafer,{...f,isFailingDie:d}))}const w=ut(h,{eligibleDies:t,softEligibleDies:t.filter(d=>d.sbin!==void 0),testDies:t,passBins:f.passBins,ringCount:f.ringCount,sectorCount:f.sectorCount,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs});h.length=0,h.push(...w);let $;if(l){const d=We(u,e.wafer,{passBins:f.passBins,ringCount:f.ringCount});if($=d??void 0,d!==null&&d.pattern!=="random"&&d.pattern!=="none"){let Ee=function(C){const E=O(C).map(K=>I(K).ring).filter(K=>K!==void 0);if(E.length===0)return!0;const pe=E.some(K=>K===Le),be=E.some(K=>K===1);return Z==="edge-ring"||Z==="edge-local"?pe:Z==="center"?be:Z==="donut"?!pe&&!be:!0};const R={center:"Center cluster",donut:"Donut","edge-ring":"Edge-ring","edge-local":"Edge-local",scratch:"Scratch","near-full":"Near-full"}[d.pattern]??d.pattern,V=d.confidence==="high"?"unusual":d.confidence==="medium"?"notable":"info",P=d.features,q=C=>`${(C*100).toFixed(0)}%`,G=d.pattern==="edge-ring"||d.pattern==="edge-local"?`${q(P.edgeRdd)} of edge dies failing`:d.pattern==="center"||d.pattern==="donut"?`centroid at ${q(P.centroidDistNorm)} of wafer radius from centre`:d.pattern==="scratch"?`linear score ${P.linearScore.toFixed(2)}, eccentricity ${P.eccentricity.toFixed(2)}`:`${q(P.globalRdd)} of dies failing`,X=t.filter(C=>{const E=C.hbin??C.sbin;return E!==void 0&&!new Set(f.passBins).has(E)}),J={"edge-ring":["ring","edge-arc"],"edge-local":["edge-arc","sector","quadrant"],center:["ring","cluster"],donut:["ring"],scratch:["cluster","sector","quadrant"],"near-full":["ring"]},Ve=new Set(J[d.pattern]??[]),Z=d.pattern,Le=f.ringCount,oe=[];for(const C of h){if(!Ve.has(C.comparison.family)||C.comparison.family==="ring"&&!Ee(C))continue;oe.push(C.id);const E=C.comparison.family==="ring"||C.comparison.family==="quadrant"||C.comparison.family==="sector";V!=="info"&&E&&(C.severity="info")}h.push({id:`spatial-pattern:${d.pattern}`,level:"wafer",severity:V,variable:{kind:"spatialPattern",label:R},comparison:{family:"spatial-pattern",left:R,right:"Wafer"},effect:{direction:"different",effectSize:P.globalRdd},stats:{method:"geometry",sampleSizeLeft:X.length,sampleSizeRight:t.length},summary:`Spatial pattern: ${R.toLowerCase()} (${d.confidence} confidence) \u2014 ${G}${d.note?` [${d.note}]`:""}`,highlight:{kind:"dies",dieKeys:X.map(C=>T(C))},relatedIds:oe.length>0?oe:void 0})}}h.sort((d,F)=>{const R=d.stats.adjustedPValue??d.stats.pValue??1,V=F.stats.adjustedPValue??F.stats.pValue??1;return R!==V?R-V:Math.abs(F.effect.absoluteDelta??0)-Math.abs(d.effect.absoluteDelta??0)});const D=Ze(e.dies,t.length,e.yield.yieldPercent);r&&(D.isLotStack=!0,n&&(D.aggregationMethod=n));const z=et(e.dies,e.testDefs);z&&(D.testSpecYield=z);const N=computeFunctionalYield(e.dies,e.testDefs);if(N&&(D.functionalYield=N),$&&(D.spatialPattern=$),v===void 0&&f.computePerTestStats){const d=Ce(t,f.testNumbers,e.testDefs);d.warning&&S.push(d.warning),v=d.testNumbers}if(v?.length){const d=tt(e.dies,v,e.testDefs,f.minimumSampleSize);d&&(D.perTestStats=d);const F=Te([{dies:e.dies}],e.testDefs,v);F&&(D.capability=F)}Object.assign(D,xe(e.dies,e.testDefs));const B=je([{dies:e.dies,wafer:e.wafer,passBins:f.passBins,ringCount:f.ringCount}]);return B&&(D.regionYield=B),S.length>0&&(D.warnings=S),ht(h,t,f.passBins),{level:"wafer",hasNotableFindings:h.some(d=>d.severity==="notable"||d.severity==="unusual"),findings:h,wafer:e.wafer.metadata??void 0,stats:D}}
1
+ import{normalizeInput as et}from"./normalizeInput.js";import{computeCapability as tt,computeTestFlagYield as nt,computeRegionYield as it}from"./summaryFigures.js";import{binPassSets as Fe}from"../renderer/binColors.js";import{diePassStatus as Pe,isYieldEligibleDie as ge,getDieKey as Ce,isPositionedDie as xe,positionKey as pe}from"../core/dies.js";import{dieHasValues as st,testsPresent as Le,testValue as I}from"../core/dieTable.js";import{getTestPassStatus as ae,isParametricTest as W}from"../renderer/buildWaferMap.js";import{markedTestLabel as q,derivedFields as re}from"../renderer/testLabel.js";import{buildQuadrantRegions as ot,buildReticlePositionRegions as at,buildRingRegions as rt,buildSectorRegions as ft,buildTestSiteRegions as lt,sectorCompassNames as Ve,areQuadrantsAdjacent as ct,parseRegionKey as Y}from"./regions.js";import{buildClusterFindings as ut}from"./clusterDetection.js";import{classifyPattern as dt}from"./patternClassification.js";import{fiveNumberSummary as mt,normalCdf as Ee}from"./math.js";import{mean as fe,clamp01 as Ke}from"../core/utils.js";import{classifySpec as be,isOutOfSpec as Oe}from"../renderer/spec.js";const ht={significanceLevel:{min:1e-6,max:1},minimumEffectSize:{min:0,max:1},minimumRelativeEffect:{min:0,max:Number.MAX_SAFE_INTEGER}},Te=[4,8,16,32];export function resolveOptions(t){const i=Object.fromEntries(Object.entries(t).filter(([,r])=>r!==void 0)),s={...A,...i},n=[],e=[];for(const r of["significanceLevel","minimumEffectSize","minimumRelativeEffect"]){const{min:u,max:a}=ht[r],c=s[r];if(typeof c!="number"||!Number.isFinite(c)){e.push(`${r}=${String(c)} is not a finite number (using ${A[r]})`),s[r]=A[r];continue}const f=Math.min(a,Math.max(u,c));f!==c&&(e.push(`${r}=${c} is outside ${u}\u2013${a} (using ${f})`),s[r]=f)}if(Te.includes(s.sectorCount)||(e.push(`sectorCount=${s.sectorCount} is not one of ${Te.join(", ")} (using ${A.sectorCount})`),s.sectorCount=A.sectorCount),e.length){const r=`analyzeWaferMap: ${e.join("; ")}. The analysis ran with the corrected values.`;console.warn(`[wafermap] ${r}`),n.push({code:"analysis-option-corrected",message:r,severity:"warning"})}const l=["passBins","ringCount"].filter(r=>i[r]!==void 0);if(l.length){const r=`analyzeWaferMap: ${l.join(" and ")} ${l.length>1?"are":"is"} no longer an analysis option and was ignored. Set ${l.length>1?"them":"it"} on buildWaferMap; the analysis used the value the map was built with.`;console.warn(`[wafermap] ${r}`),n.push({code:"analysis-option-corrected",message:r,severity:"warning"})}const o=["enableYieldAnalysis","enableHardBinAnalysis","enableSoftBinAnalysis","enableReticlePositionAnalysis","enableTestSiteAnalysis","enableClusterAnalysis","enableAngularAnalysis","enablePatternClassification"].filter(r=>i[r]!==void 0);if(o.length){const r=`analyzeWaferMap: ${o.join(", ")} ${o.length>1?"are":"is"} no longer an option and was ignored: every analysis now runs. Filter the findings (filterFindings) to show fewer.`;console.warn(`[wafermap] ${r}`),n.push({code:"analysis-option-corrected",message:r,severity:"warning"})}return{resolved:s,warnings:n}}const A={ringCount:4,passBins:[1],significanceLevel:.05,minimumEffectSize:.2,minimumRelativeEffect:1,minimumSampleSize:5,enableTestValueAnalysis:!1,computePerTestStats:!1,sectorCount:8,minimumClusterSize:5};function gt(t,i){const s={...t};return s.minimumClusterSize=Math.max(5,Math.round(i*.003)),s}function pt(t){return ge(t)?t.hbin!==void 0||t.sbin!==void 0||st(t):!1}function bt(t,i,s){if(!t||i)return;const n=(s??[]).filter(e=>W(e)&&(e.limitLow!==void 0||e.limitHigh!==void 0));if(n.length!==0)return e=>{for(const l of n)if(Oe(be(I(e,l.testNumber),l)))return!0;return!1}}function yt(t,i,s){const n=new Set,e=new Set,l=new Set,o=new Map,r=new Map;for(const u of Le(t))n.add(u);for(const u of t)u.hbin!==void 0&&e.add(u.hbin),u.sbin!==void 0&&l.add(u.sbin),ge(u)&&(u.hbin!==void 0&&o.set(u.hbin,(o.get(u.hbin)??0)+1),u.sbin!==void 0&&r.set(u.sbin,(r.get(u.sbin)??0)+1));return{totalDies:t.length,analyzedDies:i,excludedDies:t.length-i,yieldPercent:s,testsConsidered:[...n].sort((u,a)=>u-a),hardBinsConsidered:[...e].sort((u,a)=>u-a),softBinsConsidered:[...l].sort((u,a)=>u-a),...o.size?{hardBinCounts:Object.fromEntries(o)}:{},...r.size?{softBinCounts:Object.fromEntries(r)}:{}}}function St(t,i){if(!i?.length)return;const s=i.filter(e=>W(e)&&(e.limitLow!==void 0||e.limitHigh!==void 0));if(!s.length)return;const n=[];for(const e of s){const l=e.testNumber;let o=0,r=0,u=0,a=0;for(const c of t){if(c.partial||c.edgeExcluded)continue;const f=be(I(c,l),e);f!==null&&(a++,f==="failLow"?r++:f==="failHigh"?u++:o++)}n.push({testNumber:l,label:e.name,passDies:o,failLowDies:r,failHighDies:u,totalDies:a,yieldPercent:a>0?o/a*100:null})}return n.length?n:void 0}export function computeFunctionalYield(t,i){if(!i?.length)return;const s=i.filter(e=>!W(e));if(!s.length)return;const n=[];for(const e of s){const l=e.testNumber;let o=0,r=0;for(const a of t){if(a.partial||a.edgeExcluded)continue;const c=ae(a,l,e);c!==void 0&&(c?o++:r++)}const u=o+r;n.push({testNumber:l,label:q(e,l),...re(e),passDies:o,failDies:r,totalDies:u,passRatePercent:u>0?o/u*100:null})}return n.length?n:void 0}function wt(t,i,s,n){const e=[];for(const l of i){const o=[];for(const f of t){if(f.partial||f.edgeExcluded)continue;const b=I(f,l);b!==void 0&&Number.isFinite(b)&&o.push(b)}if(o.length<n)continue;const r=fe(o),u=Math.sqrt(De(o,r)),a=mt(Float64Array.from(o)),c=s?.find(f=>f.testNumber===l)?.name??String(l);e.push({testNumber:l,label:c,count:o.length,min:a.min,max:a.max,mean:r,stddev:u,median:a.median,q1:a.q1,q3:a.q3})}return e.length?e:void 0}export function twoProportionZ(t,i,s,n){const e=(t+s)/(i+n),l=e*(1-e)*(1/i+1/n);return!Number.isFinite(l)||l<=0?0:(t/i-s/n)/Math.sqrt(l)}export function zPValue(t){return Ke(2*(1-Ee(Math.abs(t))))}function T(t,i,s,n){const e=twoProportionZ(t,i,s,n);return e===0?1:zPValue(e)}export function adjustPValues(t){const i=new Map;for(const s of t){const n=`${s.variable.kind}:${s.comparison.family}`,e=i.get(n)??[];e.push(s),i.set(n,e)}for(const s of i.values()){const n=[...s].filter(o=>o.stats.pValue!==void 0).sort((o,r)=>(o.stats.pValue??1)-(r.stats.pValue??1)),e=n.length;let l=1;for(let o=e-1;o>=0;o--){const r=n[o],u=r.stats.pValue??1,a=Math.min(1,Math.min(l,u*e/(o+1)));r.stats.adjustedPValue=a,l=a}}return t}export function rateRelativeEffect(t){const i=(t.passRate?t.n-t.hits:t.hits)/t.n,s=(t.passRate?t.restN-t.restHits:t.restHits)/t.restN;return s===0?i===0?0:1/0:Math.abs(i-s)/s}export function severityForFinding(t,i,s){const n=Math.abs(i),e=s;return t<=.01&&(n>=.3||e>=2.5)?"unusual":t<=.05&&(n>=.2||e>=1.5)?"notable":"info"}export function severityForScore(t,i){return t<=.01&&Math.abs(i)>=.5?"unusual":t<=.05&&Math.abs(i)>=.15?"notable":"info"}function le(t){return t==="reticle-position"?"other reticle positions":t==="test-site"?"other test sites":"the rest of the map"}function _(t){return t==="reticle-position"?"Other reticle positions":t==="test-site"?"Other test sites":"Rest of map"}function ye(t,i,s){const n=(Math.abs(i)*100).toFixed(1);return`${t} yield is ${n} percentage points ${i>0?"higher":"lower"} than ${le(s)}`}function Se(t,i){return i==="quadrant"&&!t.startsWith("Quadrant")?`quadrant ${t}`:t}function He(t){return/^(Rings|Sectors|Quadrants)\b/.test(t)?"have":"has"}function we(t,i,s,n){const e=Se(t,n),l=(Math.abs(s)*100).toFixed(1);return`${e} ${He(e)} ${i} occurrence ${l} percentage points ${s>0?"higher":"lower"} than ${le(n)}`}function ve(t,i,s,n){const e=Se(t,n),l=(Math.abs(s)*100).toFixed(1);return`${e} ${He(e)} ${i} pass rate ${l} percentage points ${s>0?"higher":"lower"} than ${le(n)}`}function $e(t,i,s,n,e,l){const o=Se(t,e),r=le(e),u=s>0?"higher":"lower";if(n!==void 0&&Number.isFinite(n)){const c=(Math.abs(n)*100).toFixed(1);return`${o} mean ${i} is ${c}% ${u} than ${r}`}const a=l?` ${l}`:"";return`${o} mean ${i} is ${Math.abs(s).toPrecision(3)}${a} ${u} than ${r}`}function qe(t,i,s){const n=i?.find(e=>e.bin===t);return n?.name?`${s} ${t} (${n.name})`:`${s} ${t}`}function vt(t,i){const s=i?.find(n=>n.testNumber===t);return{label:q(s,t),unit:s?.unit,...re(s)}}const Ae=new WeakMap,je=new WeakMap,Ie=new WeakMap;function Z(t,i){const s=i.highlight.dieKeys;return s&&Ie.set(s,t),i}let Q=null,J;const We=new WeakMap;export function candidatesOf(t){return We.get(t)}function Ye(t,i,s,n){if(!Q)return;const e=Qe(t);if(!e)return;const l=t.id.slice(0,t.id.indexOf(":")),o=l==="specLimit"?"limitFail":l,{kind:r,bin:u,index:a}=t.variable;Q.push({key:[o,r,u??"",a??"",e.family,e.key].join("|"),source:o,variable:t.variable,label:t.variable.label,comparison:t.comparison,region:e,z:i,delta:t.effect.absoluteDelta??0,effectSize:t.effect.effectSize??0,relativeDelta:t.effect.relativeDelta,rate:s,mean:n})}export function describeRegional(t,i,s){const n=t.comparison.left,e=t.region.family;switch(t.source){case"yield":return ye(n,i,e);case"hardBin":case"softBin":return we(n,t.label,i,e);case"functional":return ve(n,t.label.replace(/ pass rate$/,""),i,e);case"limitFail":return _e(n,t.label,i);case"test":return $e(n,t.label,i,s,e,t.variable.unit)}}function _e(t,i,s){return`${t} limit fail rate for ${i} is ${(Math.abs(s)*100).toFixed(1)} pp ${s>0?"higher":"lower"} than the rest of the wafer`}const U=new WeakMap;function ce(t,i){return U.set(t,i),t}function Qe(t){const i=t.highlight.dieKeys;return i?Ie.get(i):void 0}const Ue=new WeakMap;function ke(t){let i=Ue.get(t);return i||Ue.set(t,i=t.map(pe)),i}function $t(t){let i=Ae.get(t);return i||Ae.set(t,i=t.map(Ce)),i}function ue(t,i){let s=je.get(t);s||je.set(t,s={members:new Set(t),byFamily:new WeakMap});const n=s.byFamily.get(i);if(n)return n;const{members:e}=s,l=new Map;for(const o of i)l.set(o.key,o.dies.filter(r=>e.has(r)));return s.byFamily.set(i,l),l}function de(t,i){adjustPValues(t);for(const n of t){const e=U.get(n);Ye(n,twoProportionZ(e.hits,e.n,e.restHits,e.restN),e)}const s=t.filter(n=>passesRateGate(n.stats.adjustedPValue??n.stats.pValue??1,n.effect.absoluteDelta??0,rateRelativeEffect(U.get(n)),i));return s.filter(n=>!kt(n,s,i)).map(n=>({...n,severity:severityForFinding(n.stats.adjustedPValue??n.stats.pValue??1,n.effect.absoluteDelta??0,rateRelativeEffect(U.get(n)))}))}export function passesRateGate(t,i,s,n){return t<=n.significanceLevel&&(Math.abs(i)>=n.minimumEffectSize||s>=n.minimumRelativeEffect)}function kt(t,i,s){const n=U.get(t);if(!n)return!1;const e=t.stats.pValue??1;let{restHits:l,restN:o}=n;for(const c of Ge(t,i)){const f=U.get(c);f&&(l-=f.hits,o-=f.n)}if(o===n.restN)return!1;if(o<s.minimumSampleSize)return!0;const r=n.hits/n.n-l/o;if(Math.sign(r)!==Math.sign(t.effect.absoluteDelta??0))return!0;const u=e>0?(t.stats.adjustedPValue??e)/e:1,a=Math.min(1,T(n.hits,n.n,l,o)*u);return!passesRateGate(a,r,rateRelativeEffect({...n,restHits:l,restN:o}),s)}function Ge(t,i){const s=t.stats.pValue??1;return i.filter(n=>n!==t&&n.effect.direction!==t.effect.direction&&n.variable.kind===t.variable.kind&&n.variable.bin===t.variable.bin&&n.variable.index===t.variable.index&&n.comparison.family===t.comparison.family&&(n.stats.pValue??1)<s)}const ze=new WeakMap;export function welchOfSums(t,i,s,n){const e=(l,o,r)=>r>1?Math.max(0,l-o*o/r)/(r-1):0;return welchFromStats(t.n,t.sum/t.n,e(t.sq,t.sum,t.n),i,s/i,e(n,s,i))}function zt(t,i,s){const n=ze.get(t);if(!n)return!1;let{restN:e,restSum:l,restSq:o}=n;for(const b of Ge(t,i)){const p=ze.get(b);p&&(e-=p.n,l-=p.sum,o-=p.sq)}if(e===n.restN)return!1;if(e<s.minimumSampleSize)return!0;const{pValue:r,effectSize:u,delta:a}=welchOfSums(n,e,l,o);if(Math.sign(a)!==Math.sign(t.effect.absoluteDelta??0))return!0;const c=t.stats.pValue??1,f=c>0?(t.stats.adjustedPValue??c)/c:1;return!(Math.min(1,r*f)<=s.significanceLevel&&Math.abs(u)>=s.minimumEffectSize)}function ee(t,i,s,n){const e=new Set(s),l=ue(t,i),o=new Map,r=new Map;for(const[a,c]of l){let f=0,b=0;for(const p of c)p.hbin!==void 0&&(b++,e.has(p.hbin)&&f++);o.set(a,f),r.set(a,b)}const u=[];for(const a of i){const c=r.get(a.key)??0,f=o.get(a.key)??0;let b=0,p=0;for(const[D,w]of r)D!==a.key&&(b+=w,p+=o.get(D)??0);if(c<n.minimumSampleSize||b<n.minimumSampleSize)continue;const g=f/c,m=p/b,y=g-m,z=T(f,c,p,b);u.push(ce(Z(a,{id:`yield:${a.key}`,level:"wafer",severity:"info",variable:{kind:"yield",label:"Yield"},comparison:{family:a.family,left:a.label,right:_(a.family)},effect:{direction:y===0?"different":y>0?"higher":"lower",absoluteDelta:y,relativeDelta:m===0?void 0:y/m,effectSize:y},stats:{method:"two-proportion-z",pValue:z,sampleSizeLeft:c,sampleSizeRight:b},summary:ye(a.label,y,a.family),highlight:{kind:"region",regionFamily:a.family,regionKeys:[a.key],dieKeys:[...a.dieKeys]}}),{hits:f,n:c,restHits:p,restN:b,passRate:!0}))}return de(u,n)}function E(t,i,s,n,e,l){const o=g=>s==="soft"?g.sbin:g.hbin,r=[...new Set(t.map(o).filter(g=>g!==void 0))].sort((g,m)=>g-m),u=ue(t,i),a=new Map,c=new Map;for(const[g,m]of u){const y=new Map;for(const z of m){const D=o(z);D!==void 0&&y.set(D,(y.get(D)??0)+1)}a.set(g,y),c.set(g,m.length)}const f=[],b=e==="hardBin"?"HBin":"SBin",p=s==="hard"?new Set(l.passBins):l.softPassBins??Fe(t,l.passBins).soft;for(const g of i){const m=c.get(g.key)??0,y=a.get(g.key);let z=0;const D=new Map;for(const[w,h]of a)if(w!==g.key){z+=c.get(w)??0;for(const[v,S]of h)D.set(v,(D.get(v)??0)+S)}if(!(m<l.minimumSampleSize||z<l.minimumSampleSize))for(const w of r){const h=y.get(w)??0,v=D.get(w)??0,S=h/m,$=v/z,k=S-$,M=T(h,m,v,z),R=qe(w,n,b);f.push(ce(Z(g,{id:`${e}:${w}:${g.key}`,level:"wafer",severity:"info",variable:{kind:e,bin:w,label:R},comparison:{family:g.family,left:g.label,right:_(g.family)},effect:{direction:k===0?"different":k>0?"higher":"lower",absoluteDelta:k,relativeDelta:$===0?void 0:k/$,effectSize:k},stats:{method:"two-proportion-z",pValue:M,sampleSizeLeft:m,sampleSizeRight:z},summary:we(g.label,R,k,g.family),highlight:{kind:"bin",bin:w,regionKeys:[g.key],dieKeys:[...g.dieKeys]}}),{hits:h,n:m,restHits:v,restN:z,passRate:p.has(w)}))}}return de(f,l)}function te(t,i,s,n){const e=(s??[]).filter(c=>!W(c));if(!e.length)return[];const l=e.map(c=>c.testNumber),o=ue(t,i),r=new Map,u=new Map;for(const[c,f]of o){const b=new Uint32Array(e.length),p=new Uint32Array(e.length);for(const g of f)for(let m=0;m<e.length;m++){const y=ae(g,l[m],e[m]);y!==void 0&&(p[m]++,y&&b[m]++)}r.set(c,b),u.set(c,p)}const a=[];for(const c of i){const f=r.get(c.key),b=u.get(c.key);for(let p=0;p<e.length;p++){let g=0,m=0;for(const[$,k]of u)$!==c.key&&(m+=k[p],g+=r.get($)[p]);const y=b[p],z=f[p];if(y<n.minimumSampleSize||m<n.minimumSampleSize)continue;const D=z/y,w=g/m,h=D-w,v=T(z,y,g,m),S=l[p];a.push(ce(Z(c,{id:`functional:${S}:${c.key}`,level:"wafer",severity:"info",variable:{kind:"functionalTest",index:S,label:`${q(e[p],S)} pass rate`,...re(e[p])},comparison:{family:c.family,left:c.label,right:_(c.family)},effect:{direction:h===0?"different":h>0?"higher":"lower",absoluteDelta:h,relativeDelta:w===0?void 0:h/w,effectSize:h},stats:{method:"two-proportion-z",pValue:v,sampleSizeLeft:y,sampleSizeRight:m},summary:ve(c.label,q(e[p],S),h,c.family),highlight:{kind:"region",regionFamily:c.family,regionKeys:[c.key],dieKeys:[...c.dieKeys]}}),{hits:z,n:y,restHits:g,restN:m,passRate:!0}))}}return de(a,n)}function De(t,i){return t.length<2?0:t.reduce((s,n)=>s+(n-i)**2,0)/(t.length-1)}export function welchFromStats(t,i,s,n,e,l){const o=Math.sqrt(s/t+l/n),r=i-e;if(!Number.isFinite(o)||o===0)return{pValue:1,effectSize:0,delta:r,z:0};const u=r/o,a=Math.sqrt(Math.max(0,(s+l)/2)),c=a===0?r:r/a;return{pValue:Ke(2*(1-Ee(Math.abs(u)))),effectSize:c,delta:r,z:u}}function Dt(t,i){const s=fe(t),n=fe(i);return welchFromStats(t.length,s,De(t,s),i.length,n,De(i,n))}const Xe=250;function Ze(t,i,s){const n=o=>o.filter(r=>W(s?.find(u=>u.testNumber===r)));if(i)return{testNumbers:n(i.slice().sort((o,r)=>o-r))};const e=Le(t,"values");if(e.length>Xe){const o=`Test-value analysis was skipped: more than ${Xe} tests were found in the die data, so no test findings were computed. Pass testNumbers: [...] to analyse specific tests.`;return console.warn(`[wafermap] analyzeWaferMap: ${o}`),{testNumbers:[],warning:{code:"test-count-capped",message:o,severity:"warning"}}}return{testNumbers:n(e)}}function ne(t,i,s,n){const e=Ze(t,n.testNumbers,s);if(e.warning)return{findings:[],warning:e.warning};const l=e.testNumbers,o=new Map;for(let m=0;m<i.length;m++)for(const y of ke(i[m].dies))o.set(y,m);const r=i.length,u=[],a=[];for(const m of t){const y=o.get(pe(m));y!==void 0&&(u.push(m),a.push(y))}const c=[],f=new Float64Array(r),b=new Float64Array(r),p=new Float64Array(r);for(const m of l){f.fill(0),b.fill(0),p.fill(0);let y=0,z=0,D=0,w;for(let h=0;h<u.length;h++){const v=u[h],S=I(v,m);if(S===void 0)continue;w===void 0&&(w=S);const $=S-w,k=a[h];f[k]+=1,b[k]+=$,p[k]+=$*$,y+=1,z+=$,D+=$*$}if(w!==void 0)for(let h=0;h<r;h++){const v=f[h],S=y-v;if(v<n.minimumSampleSize||S<n.minimumSampleSize)continue;const $=b[h]/v,M=(z-b[h])/S,R=$+w,P=M+w,F=v>1?Math.max(0,p[h]-v*$*$)/(v-1):0,d=S>1?Math.max(0,D-p[h]-S*M*M)/(S-1):0,{pValue:N,effectSize:B,delta:L,z:V}=welchFromStats(v,R,F,S,P,d),x=i[h],{label:ie,unit:X,...me}=vt(m,s),se=P!==0?L/Math.abs(P):void 0,K=Z(x,{id:`test:${m}:${x.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:m,label:ie,unit:X,...me},comparison:{family:x.family,left:x.label,right:_(x.family)},effect:{direction:L===0?"different":L>0?"higher":"lower",absoluteDelta:L,relativeDelta:se,effectSize:B},stats:{method:"welch-z-approx",pValue:N,sampleSizeLeft:v,sampleSizeRight:S},summary:$e(x.label,ie,L,se,x.family,X),highlight:{kind:"region",regionFamily:x.family,regionKeys:[x.key],dieKeys:[...x.dieKeys]}}),oe={n:v,sum:b[h],sq:p[h],restN:S,restSum:z-b[h],restSq:D-p[h]};ze.set(K,oe),Ye(K,V,void 0,oe),c.push(K)}}adjustPValues(c);const g=c.filter(m=>{const y=m.stats.adjustedPValue??m.stats.pValue??1,z=Math.abs(m.effect.effectSize??0);return y<=n.significanceLevel&&z>=n.minimumEffectSize});return{activeTestNumbers:l,findings:g.filter(m=>!zt(m,g,n)).map(m=>({...m,severity:severityForScore(m.stats.adjustedPValue??m.stats.pValue??1,m.effect.effectSize??0)}))}}function Rt(t,i,s,n){if(!s?.length)return[];const e=s.filter(o=>W(o)&&(o.limitLow!==void 0||o.limitHigh!==void 0));if(!e.length)return[];const l=[];for(const o of e){const r=o.testNumber;for(const u of i){const a=ue(t,u),c=[],f=new Map;let b=0,p=0;for(const[g,m]of a){let y=0,z=0;for(const D of m){const w=I(D,r);w!==void 0&&(y++,Oe(be(w,o))&&z++)}f.set(g,{valid:y,fail:z}),b+=y,p+=z}for(const g of u){const m=f.get(g.key),y=m.valid,z=b-m.valid;if(y<n.minimumSampleSize||z<n.minimumSampleSize)continue;const D=m.fail,w=p-m.fail,h=D/y,v=w/z,S=h-v,$=T(D,y,w,z);c.push(ce(Z(g,{id:`specLimit:${r}:${g.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:r,label:q(o,r),unit:o.unit,...re(o)},comparison:{family:g.family,left:g.label,right:_(g.family)},effect:{direction:S===0?"different":S>0?"higher":"lower",absoluteDelta:S,relativeDelta:v===0?void 0:S/v,effectSize:S},stats:{method:"two-proportion-z",pValue:$,sampleSizeLeft:y,sampleSizeRight:z},summary:_e(g.label,q(o,r),S),highlight:{kind:"region",regionFamily:g.family,regionKeys:[g.key],dieKeys:[...g.dieKeys]}}),{hits:D,n:y,restHits:w,restN:z,passRate:!1}))}l.push(...de(c,n))}}return l}function Re(t,i){return t==="softBin"?i.softEligibleDies:t==="test"?i.testDies:i.eligibleDies}function j(t){return t.highlight.regionKeys??[]}function Mt(t){const i=t.variable.bin??t.variable.index??"";return`${t.comparison.family}\0${t.variable.kind}\0${i}\0${t.effect.direction}`}function G(t,i){const s=j(t)[0]??"",n=Y(s);return n.family==="ring"?n.ring??0:n.family==="sector"?Ve(i).indexOf(n.sector??""):0}function Bt(t,i,s){if(i==="quadrant"){const o=new Map;for(const c of t){const f=Y(j(c)[0]??"").quadrant;f&&o.set(f,c)}const r=[...o.keys()],u=new Set,a=[];for(const c of r){if(u.has(c))continue;const f=[],b=[c];for(u.add(c);b.length;){const p=b.pop();f.push(p);for(const g of r)!u.has(g)&&ct(p,g)&&(u.add(g),b.push(g))}a.push(f.map(p=>o.get(p)))}return a}const n=[...t].sort((o,r)=>G(o,s)-G(r,s)),e=[];let l=[];for(const o of n){if(l.length===0){l.push(o);continue}const r=G(l[l.length-1],s);G(o,s)-r===1?l.push(o):(e.push(l),l=[o])}if(l.length&&e.push(l),i==="sector"&&e.length>1){const r=Ve(s).length,u=G(e[0][0],s),a=G(e[e.length-1][e[e.length-1].length-1],s);if(u===0&&a===r-1){const c=e.pop();e[0]=[...c,...e[0]]}}return e}function Nt(t,i){const s=t.map(o=>Y(j(o)[0]??"").ring).filter(o=>o!==void 0).sort((o,r)=>o-r),n=s[0],e=s[s.length-1];let l="";return n===e&&n===1&&i>1?l=" (core)":n===e&&n===i&&i>1&&(l=" (edge)"),n===e?`Ring ${n}${l}`:`Rings ${n}\u2013${e}`}function Ft(t,i){const s=t.map(n=>Y(j(n)[0]??"").sector??"");return s.length===1?`Sector ${s[0]}`:`Sectors ${s[0]}\u2013${s[s.length-1]}`}function Pt(t){const i=t.map(s=>Y(j(s)[0]??"").quadrant??"");return i.length===1?i[0]:i.length===2?`Quadrants ${i[0]} & ${i[1]}`:`Quadrants ${i.slice(0,-1).join(", ")} & ${i[i.length-1]}`}function Me(t){return[...new Set(t)]}function Ct(t,i){const s=t[0],n=s.comparison.family,e=s.variable.kind,l=Me(t.flatMap(j));let o,r;const u=t.map(Qe);if(u.every(h=>h!==void 0)){const h=new Set;o=[];for(const S of u){const $=ke(S.dies);for(let k=0;k<$.length;k++)h.has($[k])||(h.add($[k]),o.push(S.dieKeys[k]))}const v=ke(Re(s.variable.kind,i));r=S=>h.has(v[S])}else{o=Me(t.flatMap(S=>S.highlight.dieKeys??[]));const h=new Set(o),v=$t(Re(s.variable.kind,i));r=S=>h.has(v[S])}const a=n==="ring"?Nt(t,i.ringCount):n==="sector"?Ft(t,i.sectorCount):Pt(t),c=Re(e,i),f=[],b=[];for(let h=0;h<c.length;h++)(r(h)?f:b).push(c[h]);let p,g,m,y,z;if(e==="test"){const h=s.variable.index,v=d=>I(d,h),S=f.map(v).filter(d=>d!==void 0),$=b.map(v).filter(d=>d!==void 0),{pValue:k,effectSize:M,delta:R}=Dt(S,$),P=fe($),F=P!==0?R/Math.abs(P):void 0;p={direction:R===0?"different":R>0?"higher":"lower",absoluteDelta:R,relativeDelta:F,effectSize:M},g={method:"welch-z-approx",pValue:k,sampleSizeLeft:S.length,sampleSizeRight:$.length},m=severityForScore(k,M),y=$e(a,s.variable.label,R,F,n,s.variable.unit),z=`test:${h}`}else if(e==="yield"){const h=new Set(i.passBins),v=f.filter(B=>B.hbin!==void 0),S=b.filter(B=>B.hbin!==void 0),$=v.length,k=S.length,M=v.filter(B=>h.has(B.hbin)).length,R=S.filter(B=>h.has(B.hbin)).length,P=$>0?M/$:0,F=k>0?R/k:0,d=P-F,N=T(M,$,R,k);p={direction:d===0?"different":d>0?"higher":"lower",absoluteDelta:d,relativeDelta:F===0?void 0:d/F,effectSize:d},g={method:"two-proportion-z",pValue:N,sampleSizeLeft:$,sampleSizeRight:k},m=severityForFinding(N,d,rateRelativeEffect({hits:M,n:$,restHits:R,restN:k,passRate:!0})),y=ye(a,d,n),z="yield"}else if(e==="functionalTest"){const h=s.variable.index,v=i.testDefs?.find(N=>N.testNumber===h);let S=0,$=0,k=0,M=0;for(const N of f){const B=ae(N,h,v);B!==void 0&&($++,B&&S++)}for(const N of b){const B=ae(N,h,v);B!==void 0&&(M++,B&&k++)}const R=$>0?S/$:0,P=M>0?k/M:0,F=R-P,d=T(S,$,k,M);p={direction:F===0?"different":F>0?"higher":"lower",absoluteDelta:F,relativeDelta:P===0?void 0:F/P,effectSize:F},g={method:"two-proportion-z",pValue:d,sampleSizeLeft:$,sampleSizeRight:M},m=severityForFinding(d,F,rateRelativeEffect({hits:S,n:$,restHits:k,restN:M,passRate:!0})),y=ve(a,q(v,h),F,n),z=`functional:${h}`}else{const h=s.variable.bin,v=d=>e==="softBin"?d.sbin:d.hbin,S=f.filter(d=>v(d)===h).length,$=b.filter(d=>v(d)===h).length,k=S/f.length,M=$/b.length,R=k-M,P=T(S,f.length,$,b.length);p={direction:R===0?"different":R>0?"higher":"lower",absoluteDelta:R,relativeDelta:M===0?void 0:R/M,effectSize:R},g={method:"two-proportion-z",pValue:P,sampleSizeLeft:f.length,sampleSizeRight:b.length},m=severityForFinding(P,R,rateRelativeEffect({hits:S,n:f.length,restHits:$,restN:b.length,passRate:!1}));const F=e==="softBin"?i.sbinDefs:i.hbinDefs;y=we(a,qe(h,F,e==="softBin"?"SBin":"HBin"),R,n),z=`${e}:${h}`}const D=Me(l.map(h=>h.replace(`${n}:`,""))).join("-"),w=e==="hardBin"||e==="softBin"?{kind:"bin",bin:s.variable.bin,regionKeys:l,dieKeys:o}:{kind:"region",regionFamily:n,regionKeys:l,dieKeys:o};return{...s,id:`${z}:${n}:${D}`,severity:m,comparison:{family:n,left:a,right:_(n)},effect:p,stats:g,summary:y,highlight:w,relatedIds:t.map(h=>h.id)}}function xt(t,i){const s=new Set(["ring","quadrant","sector"]),n=[],e=new Map;for(const o of t){if(!s.has(o.comparison.family)){n.push(o);continue}const r=Mt(o),u=e.get(r)??[];u.push(o),e.set(r,u)}const l=[];for(const o of e.values()){if(o.length===1){l.push(o[0]);continue}const r=o[0].comparison.family;for(const u of Bt(o,r,i.sectorCount))l.push(u.length===1?u[0]:Ct(u,i))}return[...n,...l]}function Lt(t){const i=new Map,s=new Map;for(const e of t){const l=pe(e);e.hbin!==void 0&&e.hbin!==null&&(i.get(e.hbin)??i.set(e.hbin,new Set).get(e.hbin)).add(l),e.sbin!==void 0&&e.sbin!==null&&(s.get(e.sbin)??s.set(e.sbin,new Set).get(e.sbin)).add(l)}const n=new Set;for(const[e,l]of i)for(const[o,r]of s){if(l.size!==r.size)continue;let u=!0;for(const a of l)if(!r.has(a)){u=!1;break}u&&n.add(`${e}:${o}`)}return n}function Vt(t,i){const s=new Set(i);let n;for(const e of t){const l=Pe(e,s);if(l!==void 0&&l){if(e.sbin==null||n!==void 0&&e.sbin!==n)return;n=e.sbin}}if(n!==void 0){for(const e of t)if(e.sbin===n&&Pe(e,s)!==!0)return;return n}}function Et(t){return`${t.comparison.family}\0${t.comparison.left}`}export function redundancyFacts(t,i){return{coincident:Lt(t),yieldSoftBin:Vt(t,i),passBins:i}}export function collapseRedundantFindings(t,i){const{coincident:s,yieldSoftBin:n,passBins:e}=i,l=(u,a)=>{u.absorbedIds=[...u.absorbedIds??[],a.id]},o=new Map;for(const u of t){const a=Et(u);(o.get(a)??o.set(a,[]).get(a)).push(u)}const r=new Set;for(const u of o.values()){for(const c of u)if(!(c.variable.kind!=="hardBin"||c.variable.bin===void 0))for(const f of u){if(f.variable.kind!=="softBin"||f.variable.bin===void 0||r.has(f.id)||!s.has(`${c.variable.bin}:${f.variable.bin}`)||f.effect.direction!==c.effect.direction)continue;l(c,f),r.add(f.id);const b=c.variable.label.replace(/^HBin\s+/,""),p=f.variable.label.replace(/^SBin\s+/,""),g=b===p?`HBin and SBin ${b} (same dies)`:`${c.variable.label} and ${f.variable.label} (same dies)`;c.summary=c.summary.replace(c.variable.label,g),c.variable.label=g;break}const a=u.find(c=>c.variable.kind==="yield");if(a){for(const c of u)if(!(c===a||r.has(c.id)||!(c.variable.kind==="hardBin"&&e.length===1&&c.variable.bin===e[0]||c.variable.kind==="softBin"&&c.variable.bin===n))){l(a,c),r.add(c.id);for(const b of c.absorbedIds??[])a.absorbedIds=[...a.absorbedIds??[],b]}}}}export function analyzeWaferMap(t,i={}){const s=Q,n=J,e=[];Q=e,J=void 0;try{const l=Kt(t,i);return We.set(l,{candidates:e,facts:J}),l}finally{Q=s,J=n}}function Kt(t,i){const{resolved:s,warnings:n}=resolveOptions(i),e=et(t),l={...s,passBins:e.passBins??A.passBins,ringCount:e.ringCount??A.ringCount},o=e.isLotStack,r=e.aggrMethod,u=!o||r==="mode"||r==="countBin"||r==="percent",a=e.dies.filter(d=>pt(d)),c=a.filter(xe),f=gt(l,a.length);f.softPassBins=Fe(a,f.passBins).soft;const b=e.dies.filter(d=>ge(d)),p=b.filter(xe),g=rt(p,e.wafer,f.ringCount),m=ot(p,e.wafer,f.ringCount),y=at(p,e.reticleConfig),z=lt(b,!1),D=ft(p,e.wafer,f.sectorCount),w=[];u&&w.push(...ee(a,g,f.passBins,f),...ee(a,m,f.passBins,f),...ee(a,y,f.passBins,f),...ee(a,z,f.passBins,f),...ee(a,D,f.passBins,f)),u&&w.push(...E(a,g,"hard",e.hbinDefs,"hardBin",f),...E(a,m,"hard",e.hbinDefs,"hardBin",f),...E(a,y,"hard",e.hbinDefs,"hardBin",f),...E(a,z,"hard",e.hbinDefs,"hardBin",f),...E(a,D,"hard",e.hbinDefs,"hardBin",f));{const d=a.filter(N=>N.sbin!==void 0);w.push(...E(d,g,"soft",e.sbinDefs,"softBin",f),...E(d,m,"soft",e.sbinDefs,"softBin",f),...E(d,y,"soft",e.sbinDefs,"softBin",f),...E(d,z,"soft",e.sbinDefs,"softBin",f),...E(d,D,"soft",e.sbinDefs,"softBin",f))}const h=[...n];let v;if(f.enableTestValueAnalysis){const d=ne(a,g,e.testDefs,f),N=ne(a,m,e.testDefs,f),B=ne(a,y,e.testDefs,f),L=ne(a,z,e.testDefs,f),V=ne(a,D,e.testDefs,f);w.push(...d.findings,...N.findings,...B.findings,...L.findings,...V.findings),d.warning&&h.push(d.warning),v=d.activeTestNumbers,w.push(...Rt(a,[g,m,y,z,D],e.testDefs,f)),w.push(...te(a,g,e.testDefs,f),...te(a,m,e.testDefs,f),...te(a,y,e.testDefs,f),...te(a,z,e.testDefs,f),...te(a,D,e.testDefs,f))}{const d=bt(o,u,e.testDefs);(!o||u||d!==void 0)&&w.push(...ut(c,e.wafer,{...f,isFailingDie:d}))}const S=xt(w,{eligibleDies:a,softEligibleDies:a.filter(d=>d.sbin!==void 0),testDies:a,passBins:f.passBins,ringCount:f.ringCount,sectorCount:f.sectorCount,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs});w.length=0,w.push(...S);let $;if(u){const d=dt(c,e.wafer,{passBins:f.passBins,ringCount:f.ringCount});if($=d??void 0,d!==null&&d.pattern!=="random"&&d.pattern!=="none"){let Je=function(C){const O=j(C).map(H=>Y(H).ring).filter(H=>H!==void 0);if(O.length===0)return!0;const Be=O.some(H=>H===oe),Ne=O.some(H=>H===1);return K==="edge-ring"||K==="edge-local"?Be:K==="center"?Ne:K==="donut"?!Be&&!Ne:!0};const B={center:"Center cluster",donut:"Donut","edge-ring":"Edge-ring","edge-local":"Edge-local",scratch:"Scratch","near-full":"Near-full"}[d.pattern]??d.pattern,L=d.confidence==="high"?"unusual":d.confidence==="medium"?"notable":"info",V=d.features,x=C=>`${(C*100).toFixed(0)}%`,ie=d.pattern==="edge-ring"||d.pattern==="edge-local"?`${x(V.edgeRdd)} of edge dies failing`:d.pattern==="center"||d.pattern==="donut"?`centroid at ${x(V.centroidDistNorm)} of wafer radius from centre`:d.pattern==="scratch"?`linear score ${V.linearScore.toFixed(2)}, eccentricity ${V.eccentricity.toFixed(2)}`:`${x(V.globalRdd)} of dies failing`,X=a.filter(C=>{const O=C.hbin??C.sbin;return O!==void 0&&!new Set(f.passBins).has(O)}),me={"edge-ring":["ring","edge-arc"],"edge-local":["edge-arc","sector","quadrant"],center:["ring","cluster"],donut:["ring"],scratch:["cluster","sector","quadrant"],"near-full":["ring"]},se=new Set(me[d.pattern]??[]),K=d.pattern,oe=f.ringCount,he=[];for(const C of w){if(!se.has(C.comparison.family)||C.comparison.family==="ring"&&!Je(C))continue;he.push(C.id);const O=C.comparison.family==="ring"||C.comparison.family==="quadrant"||C.comparison.family==="sector";L!=="info"&&O&&(C.severity="info")}w.push({id:`spatial-pattern:${d.pattern}`,level:"wafer",severity:L,variable:{kind:"spatialPattern",label:B},comparison:{family:"spatial-pattern",left:B,right:"Wafer"},effect:{direction:"different",effectSize:V.globalRdd},stats:{method:"geometry",sampleSizeLeft:X.length,sampleSizeRight:a.length},summary:`Spatial pattern: ${B.toLowerCase()} (${d.confidence} confidence) \u2014 ${ie}${d.note?` [${d.note}]`:""}`,highlight:{kind:"dies",dieKeys:X.map(C=>Ce(C))},relatedIds:he.length>0?he:void 0})}}w.sort((d,N)=>{const B=d.stats.adjustedPValue??d.stats.pValue??1,L=N.stats.adjustedPValue??N.stats.pValue??1;return B!==L?B-L:Math.abs(N.effect.absoluteDelta??0)-Math.abs(d.effect.absoluteDelta??0)});const k=yt(e.dies,a.length,e.yield.yieldPercent);o&&(k.isLotStack=!0,r&&(k.aggregationMethod=r));const M=St(e.dies,e.testDefs);M&&(k.testSpecYield=M);const R=computeFunctionalYield(e.dies,e.testDefs);if(R&&(k.functionalYield=R),$&&(k.spatialPattern=$),v===void 0&&f.computePerTestStats){const d=Ze(a,f.testNumbers,e.testDefs);d.warning&&h.push(d.warning),v=d.testNumbers}if(v?.length){const d=wt(e.dies,v,e.testDefs,f.minimumSampleSize);d&&(k.perTestStats=d);const N=tt([{dies:e.dies}],e.testDefs,v);N&&(k.capability=N)}Object.assign(k,nt(e.dies,e.testDefs));const P=it([{dies:e.dies,wafer:e.wafer,passBins:f.passBins,ringCount:f.ringCount}]);P&&(k.regionYield=P),h.length>0&&(k.warnings=h);const F=redundancyFacts(a,f.passBins);return collapseRedundantFindings(w,F),Q&&(J=F),{level:"wafer",hasNotableFindings:w.some(d=>d.severity==="notable"||d.severity==="unusual"),findings:w,wafer:e.wafer.metadata??void 0,stats:k}}
@@ -1 +1 @@
1
- import{isYieldEligibleDie as f}from"../core/dies.js";import{quantile as r}from"./math.js";export function buildTestBoxplotData(m,l){return m.map((n,u)=>{const i=n.label??`#${u}`,o=n.testStats?.find(t=>t.testNumber===l);if(o){const{min:t,q1:a,median:s,q3:N,max:d,count:c}=o;return{label:i,min:t,q1:a,median:s,q3:N,max:d,count:c}}const e=(n.dies??[]).filter(t=>f(t)).map(t=>t.testValues?.[l]).filter(t=>t!==void 0&&Number.isFinite(t)).sort((t,a)=>t-a);return e.length===0?{label:i,min:NaN,q1:NaN,median:NaN,q3:NaN,max:NaN,count:0}:{label:i,min:e[0],q1:r(e,.25),median:r(e,.5),q3:r(e,.75),max:e[e.length-1],count:e.length}})}
1
+ import{isYieldEligibleDie as N}from"../core/dies.js";import{testValue as d}from"../core/dieTable.js";import{fiveNumberSummary as p}from"./math.js";export function buildTestBoxplotData(u,r){return u.map((o,m)=>{const i=o.label??`#${m}`,a=o.testStats?.find(t=>t.testNumber===r);if(a){const{min:t,q1:e,median:s,q3:f,max:l,count:c}=a;return{label:i,min:t,q1:e,median:s,q3:f,max:l,count:c}}const n=[];for(const t of o.dies??[]){if(!N(t))continue;const e=d(t,r);e!==void 0&&Number.isFinite(e)&&n.push(e)}return n.length===0?{label:i,min:NaN,q1:NaN,median:NaN,q3:NaN,max:NaN,count:0}:{label:i,...p(Float64Array.from(n)),count:n.length}})}
@@ -64,9 +64,9 @@ export declare function buildCapabilityData(items: CapabilityItem[], testDefs: T
64
64
  /**
65
65
  * @internal {@link buildCapabilityData} as a {@link Chunked} computation, for a
66
66
  * caller rendering on the main thread — a lot-sized call walks every die of
67
- * every wafer once per test and then sorts every value of every test, which is
68
- * seconds of uninterruptible work at lot scale. Steps are one slice of the die
69
- * pass, or one test's sort. Same result, same order; the synchronous entry
67
+ * every wafer once per test and then finds the quartiles of every test's values,
68
+ * which is seconds of uninterruptible work at lot scale. Steps are one slice of
69
+ * the die pass, or one test's quartiles. Same result, same order; the synchronous entry
70
70
  * above is this function drained.
71
71
  */
72
72
  export declare function buildCapabilityDataSteps(items: CapabilityItem[], testDefs: TestDef[]): Chunked<CapabilityDatum[]>;
@@ -81,9 +81,9 @@ export interface PooledTestStats {
81
81
  stats: Map<number, DescriptiveStats>;
82
82
  /**
83
83
  * Per test, how many pooled values fell outside the test's own spec limits —
84
- * the spec-yield tally, counted off the sorted values this pass already held.
84
+ * the spec-yield tally, counted off the values this pass already held.
85
85
  * Only tests carrying at least one limit appear. `fail` is
86
- * `countOutOfSpecSorted` — the same rule as the per-die `classifySpec`.
86
+ * `countOutOfSpec` — the per-die `classifySpec` rule, counted.
87
87
  *
88
88
  * This is here rather than the sorted arrays themselves because the result is
89
89
  * memoised: a tally is a handful of numbers per test, whereas the values are
@@ -109,8 +109,8 @@ export declare function clearPooledTestStatsCache(): void;
109
109
  * third time inside `buildCapabilityData` for the Ppk column beside them — all
110
110
  * reading the same values off the same dies and all of them O(dies × tests).
111
111
  * On a 50-wafer lot of 4,000 dies × 100 tests that was 40 s of a 41 s panel
112
- * render. One pass collects the values, one sort per test serves the quantiles
113
- * of both the table and the chart, and the spec tally reads the sorted array.
112
+ * render. One pass collects the values, one quartile selection per test serves
113
+ * both the table and the chart, and the spec tally counts the same values.
114
114
  *
115
115
  * {@link Chunked}: one step per slice of the die pass, one per test thereafter.
116
116
  */
@@ -1 +1 @@
1
- import{isYieldEligibleDie as v}from"../core/dies.js";import{drain as O}from"../core/utils.js";import{isParametricTest as B}from"../renderer/buildWaferMap.js";import{countOutOfSpecSorted as D,hasSpecLimits as H}from"../renderer/spec.js";import{testLabel as E,derivedFields as P}from"../renderer/testLabel.js";import{describeSorted as V,quantile as k}from"./math.js";const _=25e4;function z(n){if(n.specLow!==void 0&&n.specHigh!==void 0&&n.specHigh>n.specLow)return{lsl:n.specLow,usl:n.specHigh,basis:"spec"};if(n.limitLow!==void 0&&n.limitHigh!==void 0&&n.limitHigh>n.limitLow)return{lsl:n.limitLow,usl:n.limitHigh,basis:"test"}}function x(n){const s=new Map,t=new Map;for(const p of n){const e=p.testNumber;if(e===void 0||!B(p))continue;t.set(e,p);const i=z(p);i&&s.set(e,i)}return{defByTestNumber:t,specByTest:s}}function*C(n,s,t){const p=new Map,e=[];for(const l of s.keys())p.set(l,e.length),e.push(l);const i=e.length,r=new Float64Array(i),o=new Float64Array(i),m=new Float64Array(i),h=new Float64Array(i),T=new Float64Array(i),b=new Float64Array(i),w=new Float64Array(i),g=new Float64Array(i),d=t?new Array(i):[],S=i<=64,A=Math.max(500,Math.floor(_/Math.max(1,i)));let f=0;for(const l of n){b.fill(0),w.fill(0),g.fill(0);for(const a of l.dies??[]){if(++f>=A&&(f=0,yield),!v(a))continue;const y=a.testValues;if(y){if(S){for(let u=0;u<i;u++){const c=y[e[u]];c===void 0||!Number.isFinite(c)||(r[u]++,o[u]+=c,m[u]+=c*c,b[u]++,w[u]+=c,g[u]+=c*c,t&&(d[u]??=[]).push(c))}continue}for(const u in y){const c=p.get(+u);if(c===void 0)continue;const N=y[+u];N===void 0||!Number.isFinite(N)||(r[c]++,o[c]+=N,m[c]+=N*N,b[c]++,w[c]+=N,g[c]+=N*N,t&&(d[c]??=[]).push(N))}}}for(let a=0;a<i;a++){const y=b[a];if(y<2)continue;const u=w[a]/y,c=(g[a]-y*u*u)/(y-1);h[a]+=(y-1)*Math.max(0,c),T[a]+=y-1}}const M=new Map;for(let l=0;l<i;l++){if(r[l]===0)continue;const a=e[l];M.set(a,{n:r[l],sum:o[l],sumSq:m[l],withinNumerator:h[l],withinDenominator:T[l]}),t&&d[l]&&t.set(a,d[l])}return M}function I(n,s,t){return O(C(n,s,t))}function L(n,s,t,p){const{n:e,sum:i,sumSq:r,withinNumerator:o,withinDenominator:m}=p,h=i/e,T=e>=2?Math.max(0,(r-e*h*h)/(e-1)):0,b=Math.sqrt(T),w=m>0?Math.sqrt(o/m):NaN;let g=null,d=null,S=null,A=null;if(t){const{lsl:f,usl:M}=t,l=M-f;S=b>0?l/(6*b):null,A=b>0?Math.min((M-h)/(3*b),(h-f)/(3*b)):null,g=Number.isFinite(w)&&w>0?l/(6*w):null,d=Number.isFinite(w)&&w>0?Math.min((M-h)/(3*w),(h-f)/(3*w)):null}return{testNumber:n,label:E(s,n),unit:s.unit,...P(s),hasSpec:t!==void 0,lsl:t?.lsl,usl:t?.usl,...t?{limitBasis:t.basis}:{},mean:h,stdOverall:b,stdWithin:w,n:e,cp:g,cpk:d,pp:S,ppk:A}}function q(n){return n.sort((s,t)=>s.hasSpec!==t.hasSpec?s.hasSpec?-1:1:s.hasSpec?s.ppk===null&&t.ppk===null?0:s.ppk===null?1:t.ppk===null?-1:s.ppk-t.ppk:t.stdOverall-s.stdOverall)}export function buildCapabilityData(n,s){return O(buildCapabilityDataSteps(n,s))}export function*buildCapabilityDataSteps(n,s){return(yield*pooledTestStatsSteps(n,s)).capability.slice()}const F=new Map,R=4,$=new WeakMap;let j=1;function W(n,s){const t=[];for(const e of n){const i=e.dies;if(!i){t.push(0);continue}let r=$.get(i);r===void 0&&(r=j++,$.set(i,r)),t.push(r)}const p=s.map(e=>`${e.testNumber}${e.testType??"P"}${e.limitLow??""}${e.limitHigh??""}${e.specLow??""}${e.specHigh??""}${e.name??""}${e.unit??""}`);return`${t.join(",")}${p.join("")}`}export function clearPooledTestStatsCache(){F.clear()}export function*pooledTestStatsSteps(n,s){const t=W(n,s),p=F.get(t);if(p)return F.delete(t),F.set(t,p),p;const e={stats:new Map,specTally:new Map,capability:[]},{defByTestNumber:i,specByTest:r}=x(s);if(i.size===0)return e;const o=new Map,m=yield*C(n,i,o),h=new Map,T=new Map,b=[];for(const g of s){yield;const d=g.testNumber;if(d===void 0)continue;const S=m.get(d);if(!S||S.n===0||!i.has(d))continue;const A=o.get(d);o.delete(d);const f=Float64Array.from(A);f.sort(),h.set(d,V(f)),H(g)&&T.set(d,{n:f.length,fail:D(f,g)});const M=r.get(d),l=L(d,g,M,S);let a;if(M){const y=M.usl-M.lsl;a=u=>(u-M.lsl)/y}else{const y=f[0],u=f[f.length-1]-y;a=u>0?c=>(c-y)/u:()=>.5}b.push({...l,min:a(f[0]),q1:a(k(f,.25)),median:a(k(f,.5)),q3:a(k(f,.75)),max:a(f[f.length-1])})}const w={stats:h,specTally:T,capability:q(b)};return F.set(t,w),F.size>R&&F.delete(F.keys().next().value),w}export function buildCapabilityFigures(n,s){const{defByTestNumber:t,specByTest:p}=x(s);if(t.size===0)return[];const e=I(n,t,null),i=[];for(const[r,o]of e)o.n!==0&&i.push(L(r,t.get(r),p.get(r),o));return q(i)}export function poolCapabilityFigures(n,s){const{defByTestNumber:t,specByTest:p}=x(s),e=new Map;for(const r of n)for(const o of r){if(!t.has(o.testNumber)||o.n===0)continue;let m=e.get(o.testNumber);m||(m={n:0,sum:0,sumSq:0,withinNumerator:0,withinDenominator:0},e.set(o.testNumber,m));const h=o.stdOverall*o.stdOverall;m.n+=o.n,m.sum+=o.n*o.mean,m.sumSq+=(o.n-1)*h+o.n*o.mean*o.mean,o.n>=2&&(m.withinNumerator+=(o.n-1)*h,m.withinDenominator+=o.n-1)}const i=[];for(const[r,o]of e)i.push(L(r,t.get(r),p.get(r),o));return q(i)}
1
+ import{isYieldEligibleDie as B}from"../core/dies.js";import{testValuesReader as D}from"../core/dieTable.js";import{drain as q}from"../core/utils.js";import{isParametricTest as v}from"../renderer/buildWaferMap.js";import{countOutOfSpec as H,hasSpecLimits as E}from"../renderer/spec.js";import{testLabel as P,derivedFields as V}from"../renderer/testLabel.js";import{describeValues as _}from"./math.js";const I=25e4;function z(n){if(n.specLow!==void 0&&n.specHigh!==void 0&&n.specHigh>n.specLow)return{lsl:n.specLow,usl:n.specHigh,basis:"spec"};if(n.limitLow!==void 0&&n.limitHigh!==void 0&&n.limitHigh>n.limitLow)return{lsl:n.limitLow,usl:n.limitHigh,basis:"test"}}function x(n){const s=new Map,e=new Map;for(const l of n){const t=l.testNumber;if(t===void 0||!v(l))continue;e.set(t,l);const i=z(l);i&&s.set(t,i)}return{defByTestNumber:e,specByTest:s}}function*C(n,s,e){const l=[...s.keys()],t=l.length,i=new Float64Array(t),r=new Float64Array(t),o=new Float64Array(t),f=new Float64Array(t),p=new Float64Array(t),N=new Float64Array(t),w=new Float64Array(t),m=new Float64Array(t);let d=0;if(e)for(const a of n)d+=a.dies?.length??0;const M=e?Array.from({length:t},()=>new Float64Array(d)):[],S=D(l),h=new Float64Array(t),b=Math.max(500,Math.floor(I/Math.max(1,t)));let F=0;for(const a of n){N.fill(0),w.fill(0),m.fill(0);for(const u of a.dies??[])if(++F>=b&&(F=0,yield),!!B(u)){S(u,h);for(let c=0;c<t;c++){const g=h[c];Number.isFinite(g)&&(i[c]++,r[c]+=g,o[c]+=g*g,N[c]++,w[c]+=g,m[c]+=g*g,e&&(M[c][i[c]-1]=g))}}for(let u=0;u<t;u++){const c=N[u];if(c<2)continue;const g=w[u]/c,$=(m[u]-c*g*g)/(c-1);f[u]+=(c-1)*Math.max(0,$),p[u]+=c-1}}const y=new Map;for(let a=0;a<t;a++){if(i[a]===0)continue;const u=l[a];y.set(u,{n:i[a],sum:r[a],sumSq:o[a],withinNumerator:f[a],withinDenominator:p[a]}),e&&e.set(u,M[a].subarray(0,i[a]))}return y}function R(n,s,e){return q(C(n,s,e))}function A(n,s,e,l){const{n:t,sum:i,sumSq:r,withinNumerator:o,withinDenominator:f}=l,p=i/t,N=t>=2?Math.max(0,(r-t*p*p)/(t-1)):0,w=Math.sqrt(N),m=f>0?Math.sqrt(o/f):NaN;let d=null,M=null,S=null,h=null;if(e){const{lsl:b,usl:F}=e,y=F-b;S=w>0?y/(6*w):null,h=w>0?Math.min((F-p)/(3*w),(p-b)/(3*w)):null,d=Number.isFinite(m)&&m>0?y/(6*m):null,M=Number.isFinite(m)&&m>0?Math.min((F-p)/(3*m),(p-b)/(3*m)):null}return{testNumber:n,label:P(s,n),unit:s.unit,...V(s),hasSpec:e!==void 0,lsl:e?.lsl,usl:e?.usl,...e?{limitBasis:e.basis}:{},mean:p,stdOverall:w,stdWithin:m,n:t,cp:d,cpk:M,pp:S,ppk:h}}function L(n){return n.sort((s,e)=>s.hasSpec!==e.hasSpec?s.hasSpec?-1:1:s.hasSpec?s.ppk===null&&e.ppk===null?0:s.ppk===null?1:e.ppk===null?-1:s.ppk-e.ppk:e.stdOverall-s.stdOverall)}export function buildCapabilityData(n,s){return q(buildCapabilityDataSteps(n,s))}export function*buildCapabilityDataSteps(n,s){return(yield*pooledTestStatsSteps(n,s)).capability.slice()}const T=new Map,j=4,O=new WeakMap;let W=1;function Y(n,s){const e=[];for(const t of n){const i=t.dies;if(!i){e.push(0);continue}let r=O.get(i);r===void 0&&(r=W++,O.set(i,r)),e.push(r)}const l=s.filter(t=>t.testNumber!==void 0&&v(t)).map(t=>`${t.testNumber}${t.testType??"P"}${t.limitLow??""}${t.limitHigh??""}${t.specLow??""}${t.specHigh??""}${t.name??""}${t.unit??""}`);return`${e.join(",")}${l.join("")}`}export function clearPooledTestStatsCache(){T.clear();for(const n of k.values())n.work.return(void 0);k.clear()}export function*pooledTestStatsSteps(n,s){const e=Y(n,s),l=T.get(e);if(l)return T.delete(e),T.set(e,l),l;let t=k.get(e);t||(t={work:K(n,s,e),callers:0},k.set(e,t));const i=t;i.callers++;try{for(;;){if(i.result)return i.result;const r=i.work.next();if(r.done)return i.result=r.value,k.delete(e),r.value;yield}}finally{i.callers--,!i.result&&i.callers===0&&(i.work.return(void 0),k.get(e)===i&&k.delete(e))}}const k=new Map;function*K(n,s,e){const l={stats:new Map,specTally:new Map,capability:[]},{defByTestNumber:t,specByTest:i}=x(s);if(t.size===0)return l;const r=new Map,o=yield*C(n,t,r),f=new Map,p=new Map,N=[];for(const m of s){yield;const d=m.testNumber;if(d===void 0)continue;const M=o.get(d);if(!M||M.n===0||!t.has(d))continue;const S=r.get(d);r.delete(d),E(m)&&p.set(d,{n:S.length,fail:H(S,m)});const h=_(S);f.set(d,h);const b=i.get(d),F=A(d,m,b,M);let y;if(b){const a=b.usl-b.lsl;y=u=>(u-b.lsl)/a}else{const a=h.min,u=h.max-a;y=u>0?c=>(c-a)/u:()=>.5}N.push({...F,min:y(h.min),q1:y(h.q1),median:y(h.median),q3:y(h.q3),max:y(h.max)})}const w={stats:f,specTally:p,capability:L(N)};return T.set(e,w),T.size>j&&T.delete(T.keys().next().value),w}export function buildCapabilityFigures(n,s){const{defByTestNumber:e,specByTest:l}=x(s);if(e.size===0)return[];const t=R(n,e,null),i=[];for(const[r,o]of t)o.n!==0&&i.push(A(r,e.get(r),l.get(r),o));return L(i)}export function poolCapabilityFigures(n,s){const{defByTestNumber:e,specByTest:l}=x(s),t=new Map;for(const r of n)for(const o of r){if(!e.has(o.testNumber)||o.n===0)continue;let f=t.get(o.testNumber);f||(f={n:0,sum:0,sumSq:0,withinNumerator:0,withinDenominator:0},t.set(o.testNumber,f));const p=o.stdOverall*o.stdOverall;f.n+=o.n,f.sum+=o.n*o.mean,f.sumSq+=(o.n-1)*p+o.n*o.mean*o.mean,o.n>=2&&(f.withinNumerator+=(o.n-1)*p,f.withinDenominator+=o.n-1)}const i=[];for(const[r,o]of t)i.push(A(r,e.get(r),l.get(r),o));return L(i)}
@@ -1 +1 @@
1
- import{normalCdf as ct}from"./math.js";import{getDieKey as q}from"../core/dies.js";import{findConnectedComponents as at}from"./connectedComponents.js";const rt=["E","ENE","NE","NNE","N","NNW","NW","WNW","W","WSW","SW","SSW","S","SSE","SE","ESE"];function ft(n,s){const i=(Math.atan2(s,n)+2*Math.PI)%(2*Math.PI);return rt[Math.round(i/(2*Math.PI)*16)%16]}function lt(n,s,i,r){const f=r!==void 0?Math.abs(r):0;return n<=.01&&(s>=.25||f>=2||i>=.1)?"unusual":n<=.05&&(s>=.15||f>=1||i>=.03)?"notable":"info"}function ut(n,s,i){if(s===0||i<=0)return n>0?0:1;if(i>=1)return n<=s?0:1;const r=s*i,f=Math.sqrt(s*i*(1-i));if(f===0)return n>r?0:1;const S=(n-.5-r)/f;return 1-ct(S)}export function buildClusterFindings(n,s,i){const{passBins:r,significanceLevel:f,minimumEffectSize:S,minimumRelativeEffect:O,minimumSampleSize:W,minimumClusterSize:T,ringCount:_}=i,G=new Set(r);if(n.length<W)return[];const H=e=>{const a=e.hbin??e.sbin;return a===void 0||!G.has(a)},J=i.isFailingDie??H,X=n.filter(J),u=X.length/n.length,m=n[0],I=m?m.width:10,w=m?m.height:10,M=Math.max(I,w)*1.5,b=new Map;for(const e of n)b.set(q(e),e);const B=Math.ceil(M/I),R=Math.ceil(M/w),Q=at(X),A=[],$=s.center.x,x=s.center.y,U=s.radius;for(const e of Q){if(e.length<T)continue;const a=new Set(e.map(t=>q(t))),h=new Set(a);for(const t of e)for(let c=-R;c<=R;c++)for(let z=-B;z<=B;z++){const D=`${t.x+z},${t.y+c}`;if(h.has(D))continue;const N=b.get(D);N&&Math.hypot(N.physX-t.physX,N.physY-t.physY)<=M&&h.add(D)}const p=[...h].map(t=>b.get(t)).filter(Boolean),o=e.length,E=p.length;if(E<W)continue;const C=o/E,d=C-u,y=ut(o,E,u),g=u>0?d/u:void 0,Z=d>=S||g!==void 0&&g>=O;if(y>f||!Z)continue;const K=e.reduce((t,c)=>t+c.physX,0)/o,V=e.reduce((t,c)=>t+c.physY,0)/o;let l=e[0],k=1/0;for(const t of e){const c=Math.hypot(t.physX-K,t.physY-V);c<k&&(k=c,l=t)}const F=e.map(t=>(Math.atan2(t.physY-x,t.physX-$)+2*Math.PI)%(2*Math.PI));let P=F[0],Y=F[0];for(const t of F)P=Math.min(P,t),Y=Math.max(Y,t);const tt=(Y-P)*180/Math.PI,et=1-1/_,v=e.filter(t=>Math.hypot(t.physX-$,t.physY-x)/U>et).length>o/2&&tt<120,L=v?"edge-arc":"cluster",j=ft(K-$,V-x),nt=v?`Edge arc ~${j}`:`Cluster at (${l.x}, ${l.y})`,st=o/n.length,it=lt(y,d,st,g),ot=[...a];A.push({id:`${L}:${l.x},${l.y}`,level:"wafer",severity:it,variable:{kind:"yield",label:"Yield"},comparison:{family:L,left:nt,right:"Rest of wafer"},effect:{direction:"lower",absoluteDelta:-d,relativeDelta:g!==void 0?-g:void 0,effectSize:d},stats:{method:"binomial",pValue:y,adjustedPValue:y,sampleSizeLeft:o,sampleSizeRight:n.length-o},summary:v?`Edge arc near ${j}: ${o} contiguous failing dies (${(C*100).toFixed(0)}% vs ${(u*100).toFixed(0)}% background)`:`Failure cluster at (${l.x}, ${l.y}): ${o} contiguous failing dies (${(C*100).toFixed(0)}% vs ${(u*100).toFixed(0)}% background)`,highlight:{kind:"dies",dieKeys:ot}})}return A.sort((e,a)=>{const h=e.stats.pValue??1,p=a.stats.pValue??1;return h!==p?h-p:Math.abs(a.effect.effectSize??0)-Math.abs(e.effect.effectSize??0)})}
1
+ import{normalCdf as ot}from"./math.js";import{getDieKey as ct,gridKey as N}from"../core/dies.js";import{findConnectedComponents as at}from"./connectedComponents.js";const rt=["E","ENE","NE","NNE","N","NNW","NW","WNW","W","WSW","SW","SSW","S","SSE","SE","ESE"];function ft(n,s){const i=(Math.atan2(s,n)+2*Math.PI)%(2*Math.PI);return rt[Math.round(i/(2*Math.PI)*16)%16]}function lt(n,s,i,r){const f=r!==void 0?Math.abs(r):0;return n<=.01&&(s>=.25||f>=2||i>=.1)?"unusual":n<=.05&&(s>=.15||f>=1||i>=.03)?"notable":"info"}function ut(n,s,i){if(s===0||i<=0)return n>0?0:1;if(i>=1)return n<=s?0:1;const r=s*i,f=Math.sqrt(s*i*(1-i));if(f===0)return n>r?0:1;const y=(n-.5-r)/f;return 1-ot(y)}export function buildClusterFindings(n,s,i){const{passBins:r,significanceLevel:f,minimumEffectSize:y,minimumRelativeEffect:q,minimumSampleSize:W,minimumClusterSize:O,ringCount:T}=i,_=new Set(r);if(n.length<W)return[];const G=e=>{const a=e.hbin??e.sbin;return a===void 0||!_.has(a)},H=i.isFailingDie??G,X=n.filter(H),u=X.length/n.length,g=n[0],I=g?g.width:10,B=g?g.height:10,S=Math.max(I,B)*1.5,M=new Map;for(const e of n)M.set(N(e.x,e.y),e);const R=Math.ceil(S/I),w=Math.ceil(S/B),J=at(X),A=[],b=s.center.x,x=s.center.y,Q=s.radius;for(const e of J){if(e.length<O)continue;const a=new Set(e.map(t=>N(t.x,t.y)));for(const t of e)for(let c=-w;c<=w;c++)for(let v=-R;v<=R;v++){const z=N(t.x+v,t.y+c);if(a.has(z))continue;const D=M.get(z);D&&Math.hypot(D.physX-t.physX,D.physY-t.physY)<=S&&a.add(z)}const m=[...a].map(t=>M.get(t)).filter(Boolean),o=e.length,E=m.length;if(E<W)continue;const $=o/E,h=$-u,p=ut(o,E,u),d=u>0?h/u:void 0,U=h>=y||d!==void 0&&d>=q;if(p>f||!U)continue;const K=e.reduce((t,c)=>t+c.physX,0)/o,V=e.reduce((t,c)=>t+c.physY,0)/o;let l=e[0],k=1/0;for(const t of e){const c=Math.hypot(t.physX-K,t.physY-V);c<k&&(k=c,l=t)}const C=e.map(t=>(Math.atan2(t.physY-x,t.physX-b)+2*Math.PI)%(2*Math.PI));let F=C[0],P=C[0];for(const t of C)F=Math.min(F,t),P=Math.max(P,t);const Z=(P-F)*180/Math.PI,tt=1-1/T,Y=e.filter(t=>Math.hypot(t.physX-b,t.physY-x)/Q>tt).length>o/2&&Z<120,L=Y?"edge-arc":"cluster",j=ft(K-b,V-x),et=Y?`Edge arc ~${j}`:`Cluster at (${l.x}, ${l.y})`,nt=o/n.length,st=lt(p,h,nt,d),it=e.map(t=>ct(t));A.push({id:`${L}:${l.x},${l.y}`,level:"wafer",severity:st,variable:{kind:"yield",label:"Yield"},comparison:{family:L,left:et,right:"Rest of wafer"},effect:{direction:"lower",absoluteDelta:-h,relativeDelta:d!==void 0?-d:void 0,effectSize:h},stats:{method:"binomial",pValue:p,adjustedPValue:p,sampleSizeLeft:o,sampleSizeRight:n.length-o},summary:Y?`Edge arc near ${j}: ${o} contiguous failing dies (${($*100).toFixed(0)}% vs ${(u*100).toFixed(0)}% background)`:`Failure cluster at (${l.x}, ${l.y}): ${o} contiguous failing dies (${($*100).toFixed(0)}% vs ${(u*100).toFixed(0)}% background)`,highlight:{kind:"dies",dieKeys:it}})}return A.sort((e,a)=>{const m=e.stats.pValue??1,o=a.stats.pValue??1;return m!==o?m-o:Math.abs(a.effect.effectSize??0)-Math.abs(e.effect.effectSize??0)})}
@@ -1 +1 @@
1
- import{getDieKey as y}from"../core/dies.js";export function findConnectedComponents(s){if(s.length===0)return[];const r=new Map;for(const e of s)r.set(y(e),e);const n=new Set,u=[];for(const e of s){const f=y(e);if(n.has(f))continue;const p=[],c=[e];for(n.add(f);c.length>0;){const d=c.pop();p.push(d);for(let t=-1;t<=1;t++)for(let o=-1;o<=1;o++){if(o===0&&t===0)continue;const i=`${d.x+o},${d.y+t}`;if(n.has(i))continue;const h=r.get(i);h&&(n.add(i),c.push(h))}}u.push(p)}return u}
1
+ import{gridKey as i}from"../core/dies.js";export function findConnectedComponents(s){if(s.length===0)return[];const u=new Map;for(const n of s)u.set(i(n.x,n.y),n);const e=new Set,f=[];for(const n of s){const p=i(n.x,n.y);if(e.has(p))continue;const y=[],c=[n];for(e.add(p);c.length>0;){const d=c.pop();y.push(d);for(let t=-1;t<=1;t++)for(let o=-1;o<=1;o++){if(o===0&&t===0)continue;const r=i(d.x+o,d.y+t);if(e.has(r))continue;const h=u.get(r);h&&(e.add(r),c.push(h))}}f.push(y)}return f}
@@ -1 +1 @@
1
- import{isParametricTest as X}from"../renderer/buildWaferMap.js";import{markedTestLabel as Y,derivedFields as v}from"../renderer/testLabel.js";export function pearsonFromSums(e,r,a,i,c,d){if(e<3)return null;const m=r/e,f=a/e,s=d/e-m*f,h=i/e-m*m,w=c/e-f*f,I=Math.sqrt(h*w);return I===0?null:Math.max(-1,Math.min(1,s/I))}export function pearsonOfPairs(e){let r=0,a=0,i=0,c=0,d=0,m=0;for(let f=0;f<e.length;f++){const{x:s,y:h}=e[f];!Number.isFinite(s)||!Number.isFinite(h)||(r++,a+=s,i+=h,c+=s*s,d+=h*h,m+=s*h)}return{r:pearsonFromSums(r,a,i,c,d,m),n:r}}export const CORRELATION_DIE_BUDGET=25e3;function P(e,r){const a=[],i=e/r;for(let c=0;c<r;c++)a.push(Math.floor(c*i));return a}export function correlationSampleNote(e){if(e)return`From a ${e.used.toLocaleString()}-die sample of ${e.of.toLocaleString()}, spread evenly across the lot`}function L(e){return e.filter(r=>r.testNumber!==void 0&&X(r)).map(r=>({testNumber:r.testNumber,label:Y(r,r.testNumber),unit:r.unit,...v(r)}))}export function buildCorrelationMatrix(e,r){const a=L(r);if(a.length<2)return{tests:a,cells:[]};const i=a.length,c=a.map(n=>n.testNumber),d=i*(i-1)/2,m=new Float64Array(d),f=new Float64Array(i),s=new Float64Array(d),h=new Float64Array(d),w=new Float64Array(d),I=new Float64Array(d),A=new Float64Array(d);function R(n,o){return n*i-(n*(n+1)>>1)+(o-n-1)}const g=e.filter(n=>n.testValues!==void 0),t=g.length>CORRELATION_DIE_BUDGET,l=t?P(g.length,CORRELATION_DIE_BUDGET).map(n=>g[n]):g,u=new Float64Array(i),p=new Uint8Array(i);for(const n of l){p.fill(0);for(let o=0;o<i;o++){const b=n.testValues[c[o]];b!==void 0&&Number.isFinite(b)&&(u[o]=b,p[o]=1)}for(let o=0;o<i;o++){if(!p[o])continue;f[o]++;const b=u[o];for(let F=o+1;F<i;F++){if(!p[F])continue;const M=u[F],N=R(o,F);m[N]++,s[N]+=b,h[N]+=M,w[N]+=b*b,I[N]+=M*M,A[N]+=b*M}}}const y=n=>pearsonFromSums(m[n],s[n],h[n],w[n],I[n],A[n]),x=[];for(let n=0;n<i;n++)for(let o=0;o<i;o++){if(o===n){x.push({xIndex:o,yIndex:n,r:1,n:f[o]});continue}const b=Math.min(o,n),F=Math.max(o,n),M=R(b,F);x.push({xIndex:o,yIndex:n,r:y(M),n:m[M]})}return t?{tests:a,cells:x,sample:{of:g.length,used:l.length}}:{tests:a,cells:x}}export function filterCorrelationMatrix(e,{threshold:r=.3,minTests:a=6,maxTests:i=20}={}){const c=[];let d=0,m=0,f=null;for(const t of e.cells){if(t.xIndex>=t.yIndex||t.r===null)continue;const l=Math.abs(t.r);c.push({xi:t.xIndex,yi:t.yIndex,absR:l}),(f===null||l>Math.abs(f.r))&&(f={xLabel:e.tests[t.xIndex].label,yLabel:e.tests[t.yIndex].label,r:t.r})}c.sort((t,l)=>l.absR-t.absR);const s=new Set;for(const{xi:t,yi:l,absR:u}of c){const p=u<r;s.size>=i||p&&s.size>=a||(s.add(t),s.size<i&&s.add(l))}if(s.size<Math.min(a,e.tests.length))for(let t=0;t<e.tests.length&&s.size<Math.min(i,a,e.tests.length);t++)s.add(t);const h=(()=>{const t=Array.from(s),l=new Map,u=new Map;for(const{xi:y,yi:x,absR:n}of c)!s.has(y)||!s.has(x)||(l.set(y,(l.get(y)??0)+n),l.set(x,(l.get(x)??0)+n),u.set(y,(u.get(y)??0)+1),u.set(x,(u.get(x)??0)+1));const p=y=>(u.get(y)??0)>0?l.get(y)/u.get(y):0;return t.sort((y,x)=>p(x)-p(y))})(),w=h.map(t=>e.tests[t]),I=new Map(h.map((t,l)=>[t,l])),A=new Set(w.map(t=>t.testNumber)),R=e.cells.filter(t=>A.has(e.tests[t.xIndex].testNumber)&&A.has(e.tests[t.yIndex].testNumber)).map(t=>({xIndex:I.get(t.xIndex),yIndex:I.get(t.yIndex),r:t.r,n:t.n}));let g=0;for(const{xi:t,yi:l,absR:u}of c)s.has(t)&&s.has(l)?u>=.7?d++:u>=.4&&m++:u<r&&g++;return{matrix:{tests:w,cells:R},strongPairs:d,moderatePairs:m,hiddenWeakPairs:g,strongestPair:f}}
1
+ import{dieHasValues as X,testValue as Y}from"../core/dieTable.js";import{isParametricTest as v}from"../renderer/buildWaferMap.js";import{markedTestLabel as P,derivedFields as L}from"../renderer/testLabel.js";export function pearsonFromSums(e,r,a,i,c,d){if(e<3)return null;const m=r/e,f=a/e,s=d/e-m*f,h=i/e-m*m,w=c/e-f*f,I=Math.sqrt(h*w);return I===0?null:Math.max(-1,Math.min(1,s/I))}export function pearsonOfPairs(e){let r=0,a=0,i=0,c=0,d=0,m=0;for(let f=0;f<e.length;f++){const{x:s,y:h}=e[f];!Number.isFinite(s)||!Number.isFinite(h)||(r++,a+=s,i+=h,c+=s*s,d+=h*h,m+=s*h)}return{r:pearsonFromSums(r,a,i,c,d,m),n:r}}export const CORRELATION_DIE_BUDGET=25e3;function S(e,r){const a=[],i=e/r;for(let c=0;c<r;c++)a.push(Math.floor(c*i));return a}export function correlationSampleNote(e){if(e)return`From a ${e.used.toLocaleString()}-die sample of ${e.of.toLocaleString()}, spread evenly across the lot`}function z(e){return e.filter(r=>r.testNumber!==void 0&&v(r)).map(r=>({testNumber:r.testNumber,label:P(r,r.testNumber),unit:r.unit,...L(r)}))}export function buildCorrelationMatrix(e,r){const a=z(r);if(a.length<2)return{tests:a,cells:[]};const i=a.length,c=a.map(n=>n.testNumber),d=i*(i-1)/2,m=new Float64Array(d),f=new Float64Array(i),s=new Float64Array(d),h=new Float64Array(d),w=new Float64Array(d),I=new Float64Array(d),A=new Float64Array(d);function R(n,o){return n*i-(n*(n+1)>>1)+(o-n-1)}const g=e.filter(X),t=g.length>CORRELATION_DIE_BUDGET,l=t?S(g.length,CORRELATION_DIE_BUDGET).map(n=>g[n]):g,u=new Float64Array(i),x=new Uint8Array(i);for(const n of l){x.fill(0);for(let o=0;o<i;o++){const b=Y(n,c[o]);b!==void 0&&Number.isFinite(b)&&(u[o]=b,x[o]=1)}for(let o=0;o<i;o++){if(!x[o])continue;f[o]++;const b=u[o];for(let F=o+1;F<i;F++){if(!x[F])continue;const M=u[F],N=R(o,F);m[N]++,s[N]+=b,h[N]+=M,w[N]+=b*b,I[N]+=M*M,A[N]+=b*M}}}const y=n=>pearsonFromSums(m[n],s[n],h[n],w[n],I[n],A[n]),p=[];for(let n=0;n<i;n++)for(let o=0;o<i;o++){if(o===n){p.push({xIndex:o,yIndex:n,r:1,n:f[o]});continue}const b=Math.min(o,n),F=Math.max(o,n),M=R(b,F);p.push({xIndex:o,yIndex:n,r:y(M),n:m[M]})}return t?{tests:a,cells:p,sample:{of:g.length,used:l.length}}:{tests:a,cells:p}}export function filterCorrelationMatrix(e,{threshold:r=.3,minTests:a=6,maxTests:i=20}={}){const c=[];let d=0,m=0,f=null;for(const t of e.cells){if(t.xIndex>=t.yIndex||t.r===null)continue;const l=Math.abs(t.r);c.push({xi:t.xIndex,yi:t.yIndex,absR:l}),(f===null||l>Math.abs(f.r))&&(f={xLabel:e.tests[t.xIndex].label,yLabel:e.tests[t.yIndex].label,r:t.r})}c.sort((t,l)=>l.absR-t.absR);const s=new Set;for(const{xi:t,yi:l,absR:u}of c){const x=u<r;s.size>=i||x&&s.size>=a||(s.add(t),s.size<i&&s.add(l))}if(s.size<Math.min(a,e.tests.length))for(let t=0;t<e.tests.length&&s.size<Math.min(i,a,e.tests.length);t++)s.add(t);const h=(()=>{const t=Array.from(s),l=new Map,u=new Map;for(const{xi:y,yi:p,absR:n}of c)!s.has(y)||!s.has(p)||(l.set(y,(l.get(y)??0)+n),l.set(p,(l.get(p)??0)+n),u.set(y,(u.get(y)??0)+1),u.set(p,(u.get(p)??0)+1));const x=y=>(u.get(y)??0)>0?l.get(y)/u.get(y):0;return t.sort((y,p)=>x(p)-x(y))})(),w=h.map(t=>e.tests[t]),I=new Map(h.map((t,l)=>[t,l])),A=new Set(w.map(t=>t.testNumber)),R=e.cells.filter(t=>A.has(e.tests[t.xIndex].testNumber)&&A.has(e.tests[t.yIndex].testNumber)).map(t=>({xIndex:I.get(t.xIndex),yIndex:I.get(t.yIndex),r:t.r,n:t.n}));let g=0;for(const{xi:t,yi:l,absR:u}of c)s.has(t)&&s.has(l)?u>=.7?d++:u>=.4&&m++:u<r&&g++;return{matrix:{tests:w,cells:R},strongPairs:d,moderatePairs:m,hiddenWeakPairs:g,strongestPair:f}}
@@ -24,4 +24,9 @@ export declare function visibleFindings<T extends {
24
24
  id: string;
25
25
  absorbedIds?: string[];
26
26
  }>(findings: T[]): T[];
27
+ /**
28
+ * What makes two findings "the same pattern" on different wafers. One key for
29
+ * the lot's wafer count and the gallery's per-card highlight of a lot finding.
30
+ */
31
+ export declare function findingPatternKey(f: StatsFinding): string;
27
32
  //# sourceMappingURL=filterFindings.d.ts.map
@@ -1 +1 @@
1
- function s(i){return Array.isArray(i)?i:[i]}export function filterFindings(i,n){let e=i.findings;if(n.severity!==void 0){const t=new Set(s(n.severity));e=e.filter(d=>t.has(d.severity))}if(n.kind!==void 0){const t=new Set(s(n.kind));e=e.filter(d=>t.has(d.variable.kind))}if(n.family!==void 0){const t=new Set(s(n.family));e=e.filter(d=>t.has(d.comparison.family))}if(n.level!==void 0){const t=new Set(s(n.level));e=e.filter(d=>t.has(d.level))}return e}export function visibleFindings(i){const n=new Set(i.flatMap(e=>e.absorbedIds??[]));return i.filter(e=>!n.has(e.id))}
1
+ function r(e){return Array.isArray(e)?e:[e]}export function filterFindings(e,i){let n=e.findings;if(i.severity!==void 0){const t=new Set(r(i.severity));n=n.filter(a=>t.has(a.severity))}if(i.kind!==void 0){const t=new Set(r(i.kind));n=n.filter(a=>t.has(a.variable.kind))}if(i.family!==void 0){const t=new Set(r(i.family));n=n.filter(a=>t.has(a.comparison.family))}if(i.level!==void 0){const t=new Set(r(i.level));n=n.filter(a=>t.has(a.level))}return n}export function visibleFindings(e){const i=new Set(e.flatMap(n=>n.absorbedIds??[]));return e.filter(n=>!i.has(n.id))}export function findingPatternKey(e){return[e.variable.kind,e.variable.index??"",e.variable.bin??"",e.comparison.family,e.comparison.left,e.effect.direction].join("|")}
@@ -1 +1 @@
1
- export function collectTestValues(d,u){const t=[];for(const i of d)for(const a of i.dies??[]){const s=a.testValues?.[u];s!==void 0&&Number.isFinite(s)&&t.push(s)}return t}export function testValueExtent(d,u){let t=1/0,i=-1/0;for(const a of d)for(const s of a.dies??[]){const n=s.testValues?.[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,a,s){let n=collectTestValues(d,u);if(s&&(n=n.filter(e=>e>=s.lo&&e<=s.hi)),n.length===0)return[];let f=n[0],r=n[0];for(let e=1;e<n.length;e++)n[e]<f&&(f=n[e]),n[e]>r&&(r=n[e]);const l=i!==void 0?Math.min(f,i):f,M=((a!==void 0?Math.max(r,a):r)-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,a,s){const n=new Map;let f=1/0,r=-1/0;for(const o of d){const m=[];for(const y of o.items)for(const p of y.dies??[]){const c=p.testValues?.[u];if(c!==void 0&&Number.isFinite(c)){if(s&&(c<s.lo||c>s.hi))continue;m.push(c),c<f&&(f=c),c>r&&(r=c)}}n.set(o.key,m)}const l=d.map(o=>o.key).filter(o=>n.get(o).length>0);if(l.length===0)return{ranges:[],series:[]};const g=i!==void 0?Math.min(f,i):f,h=((a!==void 0?Math.max(r,a):r)-g||1)/t,e=Array.from({length:t},(o,m)=>({rangeLow:g+m*h,rangeHigh:g+(m+1)*h})),x=l.map(o=>{const m=new Array(t).fill(0);for(const y of n.get(o)){const p=Math.min(t-1,Math.floor((y-g)/h));m[p]++}return{groupKey:o,counts:m}});return{ranges:e,series:x}}
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}}
@@ -9,27 +9,43 @@ export declare function normalCdf(value: number): number;
9
9
  *
10
10
  * `ArrayLike<number>`, not `number[]`, so a caller holding its values in a
11
11
  * `Float64Array` does not have to copy them back into a plain array to be
12
- * read — see `pooledTestStatsSteps`, which sorts in one. */
12
+ * read. */
13
13
  export declare function quantile(sorted: ArrayLike<number>, q: number): number;
14
- /** Descriptive statistics of one population of values — see {@link describeSorted}. */
14
+ /** Descriptive statistics of one population of values — see {@link describeValues}. */
15
15
  export interface DescriptiveStats {
16
16
  min: number;
17
17
  max: number;
18
18
  mean: number;
19
19
  count: number;
20
- /** Population standard deviation (divide by n) — see {@link describeSorted}. */
20
+ /** Population standard deviation (divide by n) — see {@link describeValues}. */
21
21
  stddev: number;
22
22
  median: number;
23
23
  q1: number;
24
24
  q3: number;
25
25
  }
26
- /** Min/max/mean/count/σ and the quartiles of a pre-sorted array — the one copy
27
- * of the descriptive-statistics formulas behind the Test Values table.
26
+ /** Min/max/mean/count/σ and the quartiles of `values` — the one copy of the
27
+ * descriptive-statistics formulas behind the Test Values table.
28
+ *
29
+ * **Reorders `values`** (a partial sort, in place): the quartiles are found by
30
+ * selection, not by sorting every value. They are the same order statistics a
31
+ * sort would give, interpolated as {@link quantile} does, so the figures are
32
+ * identical; a full sort was ~24 ms per test at 266k values in Chrome and made
33
+ * up almost all of a large lot panel's time. The mean and σ sum in whatever
34
+ * order `values` arrives in.
28
35
  *
29
36
  * σ is the POPULATION standard deviation (divide by n), which is what this
30
37
  * table has always shown; the capability indices next to it deliberately use
31
38
  * the sample form (n−1) and say so. Two-pass, not `Σx² − n·x̄²`: the moments
32
39
  * form loses most of its significant digits when the variance is small beside
33
40
  * the mean, which is the normal shape of a passing parametric test. */
34
- export declare function describeSorted(sorted: ArrayLike<number>): DescriptiveStats;
41
+ export declare function describeValues(values: Float64Array): DescriptiveStats;
42
+ /** Min, quartiles and max of `values` — what sorting them and reading the
43
+ * ends and {@link quantile}s would give — by selection. **Reorders `values`.** */
44
+ export declare function fiveNumberSummary(values: Float64Array): {
45
+ min: number;
46
+ q1: number;
47
+ median: number;
48
+ q3: number;
49
+ max: number;
50
+ };
35
51
  //# sourceMappingURL=math.d.ts.map
@@ -1 +1 @@
1
- export function errorFunction(n){const t=n<0?-1:1,c=Math.abs(n),a=.254829592,i=-.284496736,o=1.421413741,r=-1.453152027,s=1.061405429,e=1/(1+.3275911*c),u=1-((((s*e+r)*e+o)*e+i)*e+a)*e*Math.exp(-c*c);return t*u}export function normalCdf(n){return .5*(1+errorFunction(n/Math.sqrt(2)))}export function quantile(n,t){if(n.length===0)return NaN;const c=t*(n.length-1),a=Math.floor(c),i=Math.ceil(c);return n[a]+(n[i]-n[a])*(c-a)}export function describeSorted(n){const t=n.length;let c=0;for(let o=0;o<t;o++)c+=n[o];const a=c/t;let i=0;for(let o=0;o<t;o++)i+=(n[o]-a)**2;return{min:n[0],max:n[t-1],mean:a,count:t,stddev:Math.sqrt(i/t),median:quantile(n,.5),q1:quantile(n,.25),q3:quantile(n,.75)}}
1
+ export function errorFunction(t){const o=t<0?-1:1,n=Math.abs(t),e=.254829592,c=-.284496736,s=1.421413741,i=-1.453152027,f=1.061405429,r=1/(1+.3275911*n),u=1-((((f*r+i)*r+s)*r+c)*r+e)*r*Math.exp(-n*n);return o*u}export function normalCdf(t){return .5*(1+errorFunction(t/Math.sqrt(2)))}export function quantile(t,o){if(t.length===0)return NaN;const n=o*(t.length-1),e=Math.floor(n),c=Math.ceil(n);return t[e]+(t[c]-t[e])*(n-e)}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 c=0;for(let u=0;u<o;u++)c+=(t[u]-e)**2;const{min:s,q1:i,median:f,q3:l,max:r}=fiveNumberSummary(t);return{min:s,max:r,mean:e,count:o,stddev:Math.sqrt(c/o),median:f,q1:i,q3:l}}export function fiveNumberSummary(t){let o=1/0,n=-1/0;for(let i=0;i<t.length;i++){const f=t[i];f<o&&(o=f),f>n&&(n=f)}t.length===0&&(o=NaN,n=NaN);const{q1:e,median:c,q3:s}=y(t);return{min:o,q1:e,median:c,q3:s,max:n}}function y(t){const o=t.length;if(o===0)return{q1:NaN,median:NaN,q3:NaN};const n=o-1,e=(M,q,N)=>{const p=M*n,h=Math.floor(p),a=g(t,h,q,N);if(p===h)return a;let m=1/0;for(let x=h+1;x<=N;x++)t[x]<m&&(m=t[x]);return a+(m-a)*(p-h)},c=Math.floor(.5*n),s=e(.5,0,n),i=Math.floor(.25*n),f=i===c?e(.25,c,n):u(.25,i,c),r=Math.floor(.75*n)===c?e(.75,c,n):e(.75,c+1,n);return{q1:f,median:s,q3:r};function u(M,q,N){const p=M*n,h=g(t,q,0,N-1);if(p===q)return h;let a=t[N];for(let m=q+1;m<N;m++)t[m]<a&&(a=t[m]);return h+(a-h)*(p-q)}}function g(t,o,n,e){for(;n<e;){const c=t[n],s=t[n+e>>>1],i=t[e],f=c<s?s<i?s:c<i?i:c:c<i?c:s<i?i:s;let l=n,r=e;for(;l<=r;){for(;t[l]<f;)l++;for(;t[r]>f;)r--;if(l<=r){const u=t[l];t[l]=t[r],t[r]=u,l++,r--}}if(o<=r)e=r;else if(o>=l)n=l;else break}return t[o]}
@@ -1 +1 @@
1
- import{buildWaferMap as e}from"../renderer/buildWaferMap.js";export function normalizeInput(r){return"wafer"in r&&"dies"in r&&"view"in r?r:e(r)}
1
+ import{buildWaferMap as o,isBuiltMap as a}from"../renderer/buildWaferMap.js";export function normalizeInput(r){return a(r)?r:o(r)}
@@ -81,14 +81,14 @@ export interface PatternThresholds {
81
81
  * | Pattern | Recall | Notes |
82
82
  * |------------|--------|--------------------------------------------|
83
83
  * | Near-full | 100% | |
84
- * | Edge-ring | 74% | |
84
+ * | Edge-ring | 75% | |
85
85
  * | Edge-local | 65% | |
86
86
  * | Center | 60% | |
87
87
  * | Random | 59% | |
88
- * | Scratch | 26% | Fragmented patterns harder to detect |
88
+ * | Scratch | 24% | Fragmented patterns harder to detect |
89
89
  * | Donut | 15% | Geometrically similar to center with noise |
90
90
  *
91
- * Overall accuracy: 64% exact match, 86% detection rate (any pattern flagged).
91
+ * Overall accuracy: 64% exact match, 86.4% detection rate (any pattern flagged).
92
92
  */
93
93
  export declare const DEFAULT_PATTERN_THRESHOLDS: PatternThresholds;
94
94
  /**
@@ -1 +1 @@
1
- import{findConnectedComponents as J}from"./connectedComponents.js";import{clamp01 as K}from"../core/utils.js";export const DEFAULT_PATTERN_THRESHOLDS={nearFullGlobalRdd:.6,edgeRingEdgeRdd:.18,edgeRingEdgeRddHigh:.3,edgeRingRadialSpread:.45,edgeRingAngularSpread:.6,edgeLocalEdgeRdd:.1,edgeLocalCentroidDist:.76,donutP25Dist:.4,centerCentroidDist:.22,centerCentroidDistHigh:.1,centerEdgeRdd:.35,donutEdgeRdd:.22,scratchLinearScore:.25,scratchLinearScoreHigh:.45,scratchEccentricity:.75,minimumFailingDies:5};function F(t,o,s){const i=t.physX-o.center.x,r=t.physY-o.center.y,d=Math.sqrt(i*i+r*r)/o.radius;return Math.min(s,Math.max(1,Math.floor(d*s)+1))===s}function Q(t,o,s){if(t.length<3)return 0;let i=0,r=0,d=0;for(const g of t){const l=g.physX-o,D=g.physY-s;i+=l*l,r+=D*D,d+=l*D}i/=t.length,r/=t.length,d/=t.length;const c=i+r,n=i*r-d*d,a=Math.max(0,(c/2)**2-n),m=c/2+Math.sqrt(a),R=c/2-Math.sqrt(a);if(m<=0)return 0;const f=K(R/m);return Math.sqrt(1-f)}function V(t,o){if(o===0)return 0;const s=new Map,i=new Map,r=new Map,d=new Map;for(const n of t)s.set(n.y,(s.get(n.y)??0)+1),i.set(n.x,(i.get(n.x)??0)+1),r.set(n.x-n.y,(r.get(n.x-n.y)??0)+1),d.set(n.x+n.y,(d.get(n.x+n.y)??0)+1);let c=0;for(const n of s.values())n>c&&(c=n);for(const n of i.values())n>c&&(c=n);for(const n of r.values())n>c&&(c=n);for(const n of d.values())n>c&&(c=n);return c/o}function W(t,o,s,i){const r=o.length,d=t.length,c=r>0?d/r:0,n=o.filter(e=>F(e,s,i)),a=t.filter(e=>F(e,s,i)),m=n.length>0?a.length/n.length:0,R=J(t),f=R.length>0?[...R].sort((e,h)=>h.length-e.length):[t],g=f[0],l=f.slice(0,5).flat(),D=g.length,O=d>0?D/d:0,u=s.center.x,p=s.center.y,y=s.radius;let E=0,N=0,M=1/0,x=0;for(const e of g){E+=e.physX,N+=e.physY;const h=Math.sqrt((e.physX-u)**2+(e.physY-p)**2);h<M&&(M=h),h>x&&(x=h)}const H=E/g.length,P=N/g.length,T=Math.sqrt((H-u)**2+(P-p)**2)/y,I=M===1/0?0:M/y,v=Math.min(x/y,1),C=t.map(e=>Math.sqrt((e.physX-u)**2+(e.physY-p)**2)/y).sort((e,h)=>e-h),w=C[Math.floor(C.length*.25)]??0,b=o.filter(e=>Math.sqrt((e.physX-u)**2+(e.physY-p)**2)/y<.5).length,X=o.filter(e=>Math.sqrt((e.physX-u)**2+(e.physY-p)**2)/y>=.5).length,z=t.filter(e=>Math.sqrt((e.physX-u)**2+(e.physY-p)**2)/y<.5).length,A=t.filter(e=>Math.sqrt((e.physX-u)**2+(e.physY-p)**2)/y>=.5).length,Y=b>0?z/b:0,L=X>0?A/X:0,_=L>0?Y/L:Y>0?2:1,G=l.reduce((e,h)=>e+h.physX,0)/l.length,k=l.reduce((e,h)=>e+h.physY,0)/l.length,U=Q(l,G,k),B=V(l,l.length),S=16,q=new Set;for(const e of a){const h=(Math.atan2(e.physY-p,e.physX-u)+2*Math.PI)%(2*Math.PI);q.add(Math.floor(h/(2*Math.PI)*S)%S)}const j=a.length>0?q.size/S:0;return{globalRdd:c,edgeRdd:m,centroidDistNorm:T,minDistNorm:I,maxDistNorm:v,p25DistNorm:w,eccentricity:U,linearScore:B,salienceSize:D,salienceFraction:O,edgeAngularSpread:j,innerOuterRatio:_}}function Z(t,o,s){const i=Math.max(5,Math.round(s*.003)),r=t.salienceSize>=i;if(t.globalRdd<o.nearFullGlobalRdd&&t.salienceFraction<.1)return{pattern:"random",confidence:"low"};if(t.globalRdd>=o.nearFullGlobalRdd)return{pattern:"near-full",confidence:t.globalRdd>=.8?"high":"medium"};if(r&&t.linearScore>=o.scratchLinearScore&&t.eccentricity>=o.scratchEccentricity){const a=t.linearScore>=o.scratchLinearScoreHigh?"high":"medium";return{pattern:"scratch",confidence:a,note:a==="medium"?"Scratch detection is less reliable for fragmented or diagonal patterns":void 0}}const d=r&&t.edgeRdd>=o.edgeLocalEdgeRdd&&t.maxDistNorm>=.7;if(d&&t.p25DistNorm>=o.edgeLocalCentroidDist&&t.maxDistNorm-t.minDistNorm<=o.edgeRingRadialSpread)return{pattern:"edge-ring",confidence:t.edgeRdd>=o.edgeRingEdgeRddHigh?"high":"medium"};if(d&&t.p25DistNorm>=.55&&t.p25DistNorm<o.edgeLocalCentroidDist)return{pattern:"edge-local",confidence:t.edgeRdd>=o.edgeRingEdgeRdd?"medium":"low"};const c=r&&t.centroidDistNorm<=o.centerCentroidDist&&t.edgeRdd<=o.centerEdgeRdd,n="Center and donut patterns have similar geometry; classification may be imprecise";if(c&&t.p25DistNorm<o.donutP25Dist){const a=t.centroidDistNorm<=o.centerCentroidDistHigh?"high":"medium";return{pattern:"center",confidence:a,note:a!=="high"?n:void 0}}return c&&t.p25DistNorm>=o.donutP25Dist&&t.p25DistNorm<.55?{pattern:"donut",confidence:"medium",note:n}:{pattern:"random",confidence:"low"}}export function classifyPattern(t,o,s){const i={...DEFAULT_PATTERN_THRESHOLDS},r=s.ringCount??4,d=new Set(s.passBins),c=t.filter(g=>{const l=g.hbin??g.sbin;return l!==void 0&&!d.has(l)}),n=Math.max(i.minimumFailingDies,Math.round(t.length*.003));if(c.length<n)return null;const a=W(c,t,o,r),{pattern:m,confidence:R,note:f}=Z(a,i,t.length);return{pattern:m,confidence:R,features:a,...f?{note:f}:{}}}
1
+ import{findConnectedComponents as J}from"./connectedComponents.js";import{clamp01 as K}from"../core/utils.js";export const DEFAULT_PATTERN_THRESHOLDS={nearFullGlobalRdd:.6,edgeRingEdgeRdd:.18,edgeRingEdgeRddHigh:.3,edgeRingRadialSpread:.45,edgeRingAngularSpread:.6,edgeLocalEdgeRdd:.1,edgeLocalCentroidDist:.76,donutP25Dist:.4,centerCentroidDist:.22,centerCentroidDistHigh:.1,centerEdgeRdd:.35,donutEdgeRdd:.22,scratchLinearScore:.25,scratchLinearScoreHigh:.45,scratchEccentricity:.75,minimumFailingDies:5};function q(e,t,r){const i=e.physX-t.center.x,s=e.physY-t.center.y,d=Math.sqrt(i*i+s*s)/t.radius;return Math.min(r,Math.max(1,Math.floor(d*r)+1))===r}function Q(e,t,r){if(e.length<3)return 0;let i=0,s=0,d=0;for(const h of e){const l=h.physX-t,D=h.physY-r;i+=l*l,s+=D*D,d+=l*D}i/=e.length,s/=e.length,d/=e.length;const c=i+s,n=i*s-d*d,a=Math.max(0,(c/2)**2-n),u=c/2+Math.sqrt(a),f=c/2-Math.sqrt(a);if(u<=0)return 0;const R=K(f/u);return Math.sqrt(1-R)}function V(e,t){if(t===0)return 0;const r=new Map,i=new Map,s=new Map,d=new Map;for(const n of e)r.set(n.y,(r.get(n.y)??0)+1),i.set(n.x,(i.get(n.x)??0)+1),s.set(n.x-n.y,(s.get(n.x-n.y)??0)+1),d.set(n.x+n.y,(d.get(n.x+n.y)??0)+1);let c=0;for(const n of r.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);for(const n of d.values())n>c&&(c=n);return c/t}function W(e,t,r,i){const s=t.length,d=e.length,c=s>0?d/s:0,n=t.filter(o=>q(o,r,i)),a=e.filter(o=>q(o,r,i)),u=n.length>0?a.length/n.length:0,f=J(e),R=f.length>0?[...f].sort((o,g)=>g.length-o.length):[e],h=R[0],l=R.slice(0,5).flat(),D=h.length,H=d>0?D/d:0,m=r.center.x,p=r.center.y,y=r.radius;let E=0,N=0,M=1/0,S=0;for(const o of h){E+=o.physX,N+=o.physY;const g=Math.sqrt((o.physX-m)**2+(o.physY-p)**2);g<M&&(M=g),g>S&&(S=g)}const O=E/h.length,P=N/h.length,T=Math.sqrt((O-m)**2+(P-p)**2)/y,I=M===1/0?0:M/y,A=Math.min(S/y,1),C=e.map(o=>Math.sqrt((o.physX-m)**2+(o.physY-p)**2)/y).sort((o,g)=>o-g),v=C[Math.floor(C.length*.25)]??0,b=t.filter(o=>Math.sqrt((o.physX-m)**2+(o.physY-p)**2)/y<.5).length,L=t.filter(o=>Math.sqrt((o.physX-m)**2+(o.physY-p)**2)/y>=.5).length,w=e.filter(o=>Math.sqrt((o.physX-m)**2+(o.physY-p)**2)/y<.5).length,z=e.filter(o=>Math.sqrt((o.physX-m)**2+(o.physY-p)**2)/y>=.5).length,X=b>0?w/b:0,Y=L>0?z/L:0,G=Y>0?X/Y:X>0?2:1,_=l.reduce((o,g)=>o+g.physX,0)/l.length,k=l.reduce((o,g)=>o+g.physY,0)/l.length,U=Q(l,_,k),B=V(l,l.length),x=16,F=new Set;for(const o of a){const g=(Math.atan2(o.physY-p,o.physX-m)+2*Math.PI)%(2*Math.PI);F.add(Math.floor(g/(2*Math.PI)*x)%x)}const j=a.length>0?F.size/x:0;return{globalRdd:c,edgeRdd:u,centroidDistNorm:T,minDistNorm:I,maxDistNorm:A,p25DistNorm:v,eccentricity:U,linearScore:B,salienceSize:D,salienceFraction:H,edgeAngularSpread:j,innerOuterRatio:G}}function Z(e,t,r){const i=Math.max(5,Math.round(r*.003)),s=e.salienceSize>=i;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(s&&e.linearScore>=t.scratchLinearScore&&e.eccentricity>=t.scratchEccentricity){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 d=s&&e.edgeRdd>=t.edgeLocalEdgeRdd&&e.maxDistNorm>=.7;if(d&&e.p25DistNorm>=t.edgeLocalCentroidDist&&e.maxDistNorm-e.minDistNorm<=t.edgeRingRadialSpread)return{pattern:"edge-ring",confidence:e.edgeRdd>=t.edgeRingEdgeRddHigh?"high":"medium"};if(d&&e.p25DistNorm>=.55&&e.p25DistNorm<t.edgeLocalCentroidDist)return{pattern:"edge-local",confidence:e.edgeRdd>=t.edgeRingEdgeRdd?"medium":"low"};const c=s&&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 function classifyPattern(e,t,r){const i={...DEFAULT_PATTERN_THRESHOLDS},s=r.ringCount??4,d=new Set(r.passBins),c=e.filter(h=>{const l=h.hbin??h.sbin;return l!==void 0&&!d.has(l)}),n=Math.max(i.minimumFailingDies,Math.round(e.length*.003));if(c.length<n)return null;const a=W(c,e,t,s),{pattern:u,confidence:f,note:R}=Z(a,i,e.length);return{pattern:u,confidence:f,features:a,...R?{note:R}:{}}}
@@ -5,7 +5,14 @@ export interface StatsRegion {
5
5
  family: 'ring' | 'quadrant' | 'reticle-position' | 'test-site' | 'sector';
6
6
  key: string;
7
7
  label: string;
8
+ /** `getDieKey` of each member — what findings carry, for highlighting. */
8
9
  dieKeys: string[];
10
+ /**
11
+ * The member dies themselves, parallel to `dieKeys`. Analysis reads these:
12
+ * finding a region's dies again by key string cost more than a quarter of a
13
+ * large wafer's analysis.
14
+ */
15
+ dies: Die[];
9
16
  }
10
17
  export interface RegionYieldDatum {
11
18
  /** Region identity, e.g. `ring:2` or `quadrant:NE` — parse with `parseRegionKey`. */
@@ -1 +1 @@
1
- import{classifyDie as E,getRingLabel as W}from"../core/classify.js";import{getReticleCell as b}from"../core/reticle.js";import{isPositionedDie as M,diePassStatus as k,getDieKey as x}from"../core/dies.js";function y(t){return x(t)}export function buildRegionYieldData(t,o,r,e,n){const i=new Map,a=[];for(let s=0;s<o.length;s++){const c=t[s];if(!c?.length)continue;const u=new Set(typeof e=="function"?e(s):e),f=n(c.filter(M),o[s],r),S=new Map(c.map(l=>[y(l),l]));for(const l of f){a.includes(l.key)||a.push(l.key);const g=i.get(l.key)??{label:l.label,pass:0,total:0};for(const m of l.dieKeys){const d=S.get(m);if(!d||d.partial||d.edgeExcluded)continue;const p=k(d,u);p!==void 0&&(g.total++,p&&g.pass++)}i.set(l.key,g)}}return a.map(s=>{const c=i.get(s);return c.total===0?null:{key:s,label:c.label,n:c.total,passDies:c.pass,yieldPercent:c.pass/c.total*100}}).filter(s=>s!==null)}const R=["E","ENE","NE","NNE","N","NNW","NW","WNW","W","WSW","SW","SSW","S","SSE","SE","ESE"],K=["E","NE","N","NW","W","SW","S","SE"],q=["E","N","W","S"];export function sectorCompassNames(t){const o=[4,8,16,32].includes(t)?t:16;return o===4?q:o===8?K:R}const P={NE:["NW","SE"],NW:["NE","SW"],SE:["NE","SW"],SW:["NW","SE"]};export function areQuadrantsAdjacent(t,o){return P[t]?.includes(o)??!1}export function parseRegionKey(t){return t.startsWith("ring:")?{family:"ring",ring:Number(t.slice(5))}:t.startsWith("quadrant:")?{family:"quadrant",quadrant:t.slice(9)}:t.startsWith("sector:")?{family:"sector",sector:t.slice(7)}:{family:"unknown"}}export function buildRingRegions(t,o,r){const e=new Map;for(const n of t){const{ring:i}=E(n,o,{ringCount:r}),a=`ring:${i}`,s=e.get(a)??{family:"ring",key:a,label:W(i,r),dieKeys:[]};s.dieKeys.push(y(n)),e.set(a,s)}return[...e.values()].sort((n,i)=>n.key.localeCompare(i.key))}export function buildQuadrantRegions(t,o,r){const e=new Map;for(const i of t){const{quadrant:a}=E(i,o,{ringCount:r}),s=`quadrant:${a}`,c=e.get(s)??{family:"quadrant",key:s,label:a,dieKeys:[]};c.dieKeys.push(y(i)),e.set(s,c)}const n=new Map([["quadrant:NE",0],["quadrant:NW",1],["quadrant:SE",2],["quadrant:SW",3]]);return[...e.values()].sort((i,a)=>(n.get(i.key)??4)-(n.get(a.key)??4))}export function buildReticlePositionRegions(t,o){if(!o)return[];const r=new Map;for(const e of t){const{column:n,row:i}=b(e,o),a=`reticle-position:cell:${n},${i}`,s=r.get(a)??{family:"reticle-position",key:a,label:`Reticle cell (${n}, ${i})`,dieKeys:[]};s.dieKeys.push(y(e)),r.set(a,s)}return[...r.values()].sort((e,n)=>e.key.localeCompare(n.key))}const w=3;export function buildTestSiteRegions(t,o=!1){const r=new Map;for(const e of t){if(e.siteNum===void 0)continue;const n=r.get(e.siteNum)??[];n.push(y(e)),r.set(e.siteNum,n)}if(!o){let e=0;for(const n of r.values())n.length>=w&&e++;if(e<2)return[]}return[...r.entries()].sort(([e],[n])=>e-n).map(([e,n])=>({family:"test-site",key:`test-site:${e}`,label:`Site ${e}`,dieKeys:n}))}export function buildSectorRegions(t,o,r){const e=[4,8,16,32].includes(r)?r:16,n=sectorCompassNames(e),i=new Map,a=o.center.x,s=o.center.y,c=o.radius;for(const u of t){const f=u.physX-a,S=u.physY-s;if(Math.hypot(f,S)/c<.2)continue;const g=Math.PI/e,m=(Math.atan2(S,f)+2*Math.PI+g)%(2*Math.PI),d=Math.floor(m/(2*Math.PI)*e)%e,p=n[d],N=`sector:${p}`,h=i.get(N)??{family:"sector",key:N,label:`Sector ${p}`,dieKeys:[]};h.dieKeys.push(y(u)),i.set(N,h)}return[...i.values()].sort((u,f)=>u.key.localeCompare(f.key))}
1
+ import{classifyDie as N,getRingLabel as W}from"../core/classify.js";import{getReticleCell as b}from"../core/reticle.js";import{isPositionedDie as x,diePassStatus as M,getDieKey as R}from"../core/dies.js";function p(n){return R(n)}export function buildRegionYieldData(n,o,r,e,t){const i=new Map,a=[];for(let s=0;s<o.length;s++){const c=n[s];if(!c?.length)continue;const u=new Set(typeof e=="function"?e(s):e),d=t(c.filter(x),o[s],r);for(const l of d){a.includes(l.key)||a.push(l.key);const g=i.get(l.key)??{label:l.label,pass:0,total:0};for(const f of l.dies){if(f.partial||f.edgeExcluded)continue;const y=M(f,u);y!==void 0&&(g.total++,y&&g.pass++)}i.set(l.key,g)}}return a.map(s=>{const c=i.get(s);return c.total===0?null:{key:s,label:c.label,n:c.total,passDies:c.pass,yieldPercent:c.pass/c.total*100}}).filter(s=>s!==null)}const k=["E","ENE","NE","NNE","N","NNW","NW","WNW","W","WSW","SW","SSW","S","SSE","SE","ESE"],q=["E","NE","N","NW","W","SW","S","SE"],K=["E","N","W","S"];export function sectorCompassNames(n){const o=[4,8,16,32].includes(n)?n:16;return o===4?K:o===8?q:k}const P={NE:["NW","SE"],NW:["NE","SW"],SE:["NE","SW"],SW:["NW","SE"]};export function areQuadrantsAdjacent(n,o){return P[n]?.includes(o)??!1}export function parseRegionKey(n){return n.startsWith("ring:")?{family:"ring",ring:Number(n.slice(5))}:n.startsWith("quadrant:")?{family:"quadrant",quadrant:n.slice(9)}:n.startsWith("sector:")?{family:"sector",sector:n.slice(7)}:{family:"unknown"}}export function buildRingRegions(n,o,r){const e=new Map;for(const t of n){const{ring:i}=N(t,o,{ringCount:r}),a=`ring:${i}`,s=e.get(a)??{family:"ring",key:a,label:W(i,r),dieKeys:[],dies:[]};s.dieKeys.push(p(t)),s.dies.push(t),e.set(a,s)}return[...e.values()].sort((t,i)=>t.key.localeCompare(i.key))}export function buildQuadrantRegions(n,o,r){const e=new Map;for(const i of n){const{quadrant:a}=N(i,o,{ringCount:r}),s=`quadrant:${a}`,c=e.get(s)??{family:"quadrant",key:s,label:a,dieKeys:[],dies:[]};c.dieKeys.push(p(i)),c.dies.push(i),e.set(s,c)}const t=new Map([["quadrant:NE",0],["quadrant:NW",1],["quadrant:SE",2],["quadrant:SW",3]]);return[...e.values()].sort((i,a)=>(t.get(i.key)??4)-(t.get(a.key)??4))}export function buildReticlePositionRegions(n,o){if(!o)return[];const r=new Map;for(const e of n){const{column:t,row:i}=b(e,o),a=`reticle-position:cell:${t},${i}`,s=r.get(a)??{family:"reticle-position",key:a,label:`Reticle cell (${t}, ${i})`,dieKeys:[],dies:[]};s.dieKeys.push(p(e)),s.dies.push(e),r.set(a,s)}return[...r.values()].sort((e,t)=>e.key.localeCompare(t.key))}const w=3;export function buildTestSiteRegions(n,o=!1){const r=new Map;for(const e of n){if(e.siteNum===void 0)continue;const t=r.get(e.siteNum)??[];t.push(e),r.set(e.siteNum,t)}if(!o){let e=0;for(const t of r.values())t.length>=w&&e++;if(e<2)return[]}return[...r.entries()].sort(([e],[t])=>e-t).map(([e,t])=>({family:"test-site",key:`test-site:${e}`,label:`Site ${e}`,dieKeys:t.map(p),dies:t}))}export function buildSectorRegions(n,o,r){const e=[4,8,16,32].includes(r)?r:16,t=sectorCompassNames(e),i=new Map,a=o.center.x,s=o.center.y,c=o.radius;for(const u of n){const d=u.physX-a,l=u.physY-s;if(Math.hypot(d,l)/c<.2)continue;const f=Math.PI/e,y=(Math.atan2(l,d)+2*Math.PI+f)%(2*Math.PI),E=Math.floor(y/(2*Math.PI)*e)%e,h=t[E],S=`sector:${h}`,m=i.get(S)??{family:"sector",key:S,label:`Sector ${h}`,dieKeys:[],dies:[]};m.dieKeys.push(p(u)),m.dies.push(u),i.set(S,m)}return[...i.values()].sort((u,d)=>u.key.localeCompare(d.key))}