@wafertools/wafermap 0.30.2 → 0.30.4

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 (88) hide show
  1. package/AGENTS.md +16 -5
  2. package/CHANGELOG.md +432 -0
  3. package/README.md +6 -3
  4. package/dist/packages/canvas-adapter/chartPopulation.d.ts +48 -0
  5. package/dist/packages/canvas-adapter/chartPopulation.js +1 -0
  6. package/dist/packages/canvas-adapter/charts/barPanel.d.ts +4 -1
  7. package/dist/packages/canvas-adapter/charts/barPanel.js +1 -1
  8. package/dist/packages/canvas-adapter/charts/boxplot.d.ts +4 -1
  9. package/dist/packages/canvas-adapter/charts/boxplot.js +1 -1
  10. package/dist/packages/canvas-adapter/charts/capability.js +1 -1
  11. package/dist/packages/canvas-adapter/charts/chartShell.d.ts +82 -8
  12. package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
  13. package/dist/packages/canvas-adapter/charts/correlation.js +2 -2
  14. package/dist/packages/canvas-adapter/charts/groupedBarPlot.js +1 -1
  15. package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
  16. package/dist/packages/canvas-adapter/charts/regionYieldDiagram.js +1 -1
  17. package/dist/packages/canvas-adapter/charts/scatter.d.ts +5 -1
  18. package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
  19. package/dist/packages/canvas-adapter/charts/sweep.d.ts +31 -0
  20. package/dist/packages/canvas-adapter/charts/sweep.js +1 -0
  21. package/dist/packages/canvas-adapter/charts/testPassRate.js +1 -1
  22. package/dist/packages/canvas-adapter/charts/trend.d.ts +3 -1
  23. package/dist/packages/canvas-adapter/charts/trend.js +1 -1
  24. package/dist/packages/canvas-adapter/dieList.js +2 -2
  25. package/dist/packages/canvas-adapter/drilldown.d.ts +23 -0
  26. package/dist/packages/canvas-adapter/drilldown.js +1 -0
  27. package/dist/packages/canvas-adapter/icons.js +1 -1
  28. package/dist/packages/canvas-adapter/insightsTab.d.ts +65 -3
  29. package/dist/packages/canvas-adapter/insightsTab.js +1 -1
  30. package/dist/packages/canvas-adapter/maplessSummary.js +1 -1
  31. package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
  32. package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
  33. package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
  34. package/dist/packages/canvas-adapter/toCanvas.js +1 -1
  35. package/dist/packages/canvas-adapter/toolbar.d.ts +30 -2
  36. package/dist/packages/canvas-adapter/toolbar.js +2 -2
  37. package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
  38. package/dist/packages/canvas-adapter/userGuideHtml.js +162 -37
  39. package/dist/packages/canvas-adapter/version.d.ts +2 -2
  40. package/dist/packages/canvas-adapter/version.js +1 -1
  41. package/dist/packages/canvas-adapter/warnings.js +1 -1
  42. package/dist/packages/core/dies.d.ts +1 -1
  43. package/dist/packages/renderer/axisTicks.d.ts +32 -0
  44. package/dist/packages/renderer/axisTicks.js +1 -0
  45. package/dist/packages/renderer/binColors.js +1 -1
  46. package/dist/packages/renderer/buildView.d.ts +11 -37
  47. package/dist/packages/renderer/buildView.js +1 -1
  48. package/dist/packages/renderer/buildWaferMap.d.ts +97 -12
  49. package/dist/packages/renderer/buildWaferMap.js +1 -1
  50. package/dist/packages/renderer/deprecate.d.ts +13 -5
  51. package/dist/packages/renderer/deprecate.js +1 -1
  52. package/dist/packages/renderer/derivedTests/apply.d.ts +85 -0
  53. package/dist/packages/renderer/derivedTests/apply.js +1 -0
  54. package/dist/packages/renderer/derivedTests/evaluate.d.ts +48 -0
  55. package/dist/packages/renderer/derivedTests/evaluate.js +1 -0
  56. package/dist/packages/renderer/derivedTests/parser.d.ts +148 -0
  57. package/dist/packages/renderer/derivedTests/parser.js +2 -0
  58. package/dist/packages/renderer/fmt.d.ts +24 -2
  59. package/dist/packages/renderer/fmt.js +1 -1
  60. package/dist/packages/renderer/index.d.ts +1 -0
  61. package/dist/packages/renderer/index.js +1 -1
  62. package/dist/packages/renderer/spec.d.ts +66 -0
  63. package/dist/packages/renderer/spec.js +1 -0
  64. package/dist/packages/renderer/testLabel.d.ts +154 -0
  65. package/dist/packages/renderer/testLabel.js +1 -0
  66. package/dist/packages/stats/analyzeWaferMap.js +1 -1
  67. package/dist/packages/stats/capability.d.ts +8 -2
  68. package/dist/packages/stats/capability.js +1 -1
  69. package/dist/packages/stats/correlation.d.ts +4 -1
  70. package/dist/packages/stats/correlation.js +1 -1
  71. package/dist/packages/stats/deprecated.d.ts +4 -1
  72. package/dist/packages/stats/deprecated.js +1 -1
  73. package/dist/packages/stats/index.d.ts +2 -2
  74. package/dist/packages/stats/index.js +1 -1
  75. package/dist/packages/stats/mergeTestDefs.d.ts +1 -1
  76. package/dist/packages/stats/mergeTestDefs.js +1 -1
  77. package/dist/packages/stats/renderFindingsReport.js +8 -28
  78. package/dist/packages/stats/renderSummaryReport.js +19 -28
  79. package/dist/packages/stats/reportHtml.d.ts +27 -0
  80. package/dist/packages/stats/reportHtml.js +35 -14
  81. package/dist/packages/stats/sweep.d.ts +186 -0
  82. package/dist/packages/stats/sweep.js +1 -0
  83. package/dist/packages/stats/sweepXFromName.d.ts +27 -0
  84. package/dist/packages/stats/sweepXFromName.js +1 -0
  85. package/dist/packages/stats/testPassRate.js +1 -1
  86. package/dist/packages/stats/types.d.ts +37 -4
  87. package/llms.txt +3 -2
  88. package/package.json +1 -1
@@ -1 +1 @@
1
- import{normalizeInput as Ce}from"./normalizeInput.js";import{computeCapability as Ne,computeTestFlagYield as Pe,computeRegionYield as Ve}from"./summaryFigures.js";import{isYieldEligibleDie as te,getDieKey as T,isPositionedDie as ge}from"../core/dies.js";import{getTestPassStatus as me,isParametricTest as O}from"../renderer/buildWaferMap.js";import{buildQuadrantRegions as Le,buildReticlePositionRegions as Ee,buildRingRegions as Ke,buildSectorRegions as Te,buildTestSiteRegions as xe,sectorCompassNames as he,areQuadrantsAdjacent as je,parseRegionKey as q}from"./regions.js";import{buildClusterFindings as Ae}from"./clusterDetection.js";import{classifyPattern as Oe}from"./patternClassification.js";import{normalCdf as pe}from"./math.js";import{quantile as ne}from"./math.js";import{mean as J,clamp01 as be}from"../core/utils.js";const qe={significanceLevel:{min:1e-6,max:1},minimumEffectSize:{min:0,max:1},minimumRelativeEffect:{min:0,max:Number.MAX_SAFE_INTEGER}},ye=[4,8,16,32];function He(s){const a=Object.fromEntries(Object.entries(s).filter(([,t])=>t!==void 0)),i={...x,...a},o=[],e=[];for(const t of["significanceLevel","minimumEffectSize","minimumRelativeEffect"]){const{min:l,max:n}=qe[t],u=i[t];if(typeof u!="number"||!Number.isFinite(u)){e.push(`${t}=${String(u)} is not a finite number (using ${x[t]})`),i[t]=x[t];continue}const f=Math.min(n,Math.max(l,u));f!==u&&(e.push(`${t}=${u} is outside ${l}\u2013${n} (using ${f})`),i[t]=f)}if(ye.includes(i.sectorCount)||(e.push(`sectorCount=${i.sectorCount} is not one of ${ye.join(", ")} (using ${x.sectorCount})`),i.sectorCount=x.sectorCount),e.length){const t=`analyzeWaferMap: ${e.join("; ")}. The analysis ran with the corrected values.`;console.warn(`[wafermap] ${t}`),o.push({code:"analysis-option-corrected",message:t,severity:"warning"})}const c=["passBins","ringCount"].filter(t=>a[t]!==void 0);if(c.length){const t=`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] ${t}`),o.push({code:"analysis-option-corrected",message:t,severity:"warning"})}const r=["enableYieldAnalysis","enableHardBinAnalysis","enableSoftBinAnalysis","enableReticlePositionAnalysis","enableTestSiteAnalysis","enableClusterAnalysis","enableAngularAnalysis","enablePatternClassification"].filter(t=>a[t]!==void 0);if(r.length){const t=`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] ${t}`),o.push({code:"analysis-option-corrected",message:t,severity:"warning"})}return{resolved:i,warnings:o}}const x={ringCount:4,passBins:[1],significanceLevel:.05,minimumEffectSize:.2,minimumRelativeEffect:1,minimumSampleSize:5,enableTestValueAnalysis:!1,computePerTestStats:!1,sectorCount:8,minimumClusterSize:5};function Ie(s,a){const i={...s};return i.minimumClusterSize=Math.max(5,Math.round(a*.003)),i}function Ye(s){return te(s)?s.hbin!==void 0||s.sbin!==void 0||s.testValues!==void 0&&Object.keys(s.testValues).length>0:!1}function _e(s,a,i){if(!s||a)return;const o=(i??[]).filter(e=>O(e)&&(e.limitLow!==void 0||e.limitHigh!==void 0));if(o.length!==0)return e=>{for(const c of o){const r=c.testNumber,t=e.testValues?.[r];if(t!==void 0&&(c.limitLow!==void 0&&t<c.limitLow||c.limitHigh!==void 0&&t>c.limitHigh))return!0}return!1}}function We(s,a,i){const o=new Set,e=new Set,c=new Set,r=new Map,t=new Map;for(const l of s){if(l.testValues)for(const n of Object.keys(l.testValues))o.add(Number(n));if(l.testPass)for(const n of Object.keys(l.testPass))o.add(Number(n));l.hbin!==void 0&&e.add(l.hbin),l.sbin!==void 0&&c.add(l.sbin),te(l)&&(l.hbin!==void 0&&r.set(l.hbin,(r.get(l.hbin)??0)+1),l.sbin!==void 0&&t.set(l.sbin,(t.get(l.sbin)??0)+1))}return{totalDies:s.length,analyzedDies:a,excludedDies:s.length-a,yieldPercent:i,testsConsidered:[...o].sort((l,n)=>l-n),hardBinsConsidered:[...e].sort((l,n)=>l-n),softBinsConsidered:[...c].sort((l,n)=>l-n),...r.size?{hardBinCounts:Object.fromEntries(r)}:{},...t.size?{softBinCounts:Object.fromEntries(t)}:{}}}function Qe(s,a){if(!a?.length)return;const i=a.filter(e=>O(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,t=0,l=0,n=0;for(const u of s){if(u.partial||u.edgeExcluded)continue;const f=u.testValues?.[c];f!==void 0&&(n++,e.limitLow!==void 0&&f<e.limitLow?t++:e.limitHigh!==void 0&&f>e.limitHigh?l++:r++)}o.push({testNumber:c,label:e.name,passDies:r,failLowDies:t,failHighDies:l,totalDies:n,yieldPercent:n>0?r/n*100:null})}return o.length?o:void 0}export function computeFunctionalYield(s,a){if(!a?.length)return;const i=a.filter(e=>!O(e));if(!i.length)return;const o=[];for(const e of i){const c=e.testNumber;let r=0,t=0;for(const n of s){if(n.partial||n.edgeExcluded)continue;const u=me(n,c,e);u!==void 0&&(u?r++:t++)}const l=r+t;o.push({testNumber:c,label:e.name,passDies:r,failDies:t,totalDies:l,passRatePercent:l>0?r/l*100:null})}return o.length?o:void 0}function Ue(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 h=f.testValues?.[c];h!==void 0&&Number.isFinite(h)&&r.push(h)}if(r.length<o)continue;const t=J(r),l=Math.sqrt(fe(r,t)),n=Float64Array.from(r);n.sort();const u=i?.find(f=>f.testNumber===c)?.name??String(c);e.push({testNumber:c,label:u,count:r.length,min:n[0],max:n[n.length-1],mean:t,stddev:l,median:ne(n,.5),q1:ne(n,.25),q3:ne(n,.75)})}return e.length?e:void 0}function H(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 be(2*(1-pe(Math.abs(r))))}function ie(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,t)=>(r.stats.pValue??1)-(t.stats.pValue??1)),e=o.length;let c=1;for(let r=e-1;r>=0;r--){const t=o[r],l=t.stats.pValue??1,n=Math.min(1,Math.min(c,l*e/(r+1)));t.stats.adjustedPValue=n,c=n}}return s}function se(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 oe(s,a){return s<=.01&&Math.abs(a)>=.5?"unusual":s<=.05&&Math.abs(a)>=.15?"notable":"info"}function Z(s){return s==="reticle-position"?"other reticle positions":s==="test-site"?"other test sites":"the rest of the map"}function I(s){return s==="reticle-position"?"Other reticle positions":s==="test-site"?"Other test sites":"Rest of map"}function Se(s,a,i){const o=(Math.abs(a)*100).toFixed(1);return`${s} yield is ${o} percentage points ${a>0?"higher":"lower"} than ${Z(i)}`}function ae(s,a){return a==="quadrant"&&!s.startsWith("Quadrant")?`quadrant ${s}`:s}function we(s,a,i,o){const e=ae(s,o),c=(Math.abs(i)*100).toFixed(1);return`${e} has ${a} occurrence ${c} percentage points ${i>0?"higher":"lower"} than ${Z(o)}`}function Ge(s,a,i,o){const e=ae(s,o),c=(Math.abs(i)*100).toFixed(1);return`${e} has ${a} pass rate ${c} percentage points ${i>0?"higher":"lower"} than ${Z(o)}`}function $e(s,a,i,o,e,c){const r=ae(s,e),t=Z(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 ${t}`}const n=c?` ${c}`:"";return`${r} mean ${a} is ${Math.abs(i).toPrecision(3)}${n} ${l} than ${t}`}function ve(s,a,i){const o=a?.find(e=>e.bin===s);return o?.name?`${i} ${s} (${o.name})`:`${i} ${s}`}function Xe(s,a){const i=a?.find(o=>o.testNumber===s);return{label:i?.name??`Test ${s}`,unit:i?.unit}}function re(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 t=i.get(r);t&&c.push(t)}o.set(e.key,c)}return o}function le(s,a){return ie(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:se(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=re(s,a),r=new Map,t=new Map;for(const[n,u]of c){let f=0,h=0;for(const g of u)g.hbin!==void 0&&(h++,e.has(g.hbin)&&f++);r.set(n,f),t.set(n,h)}const l=[];for(const n of a){const u=t.get(n.key)??0,f=r.get(n.key)??0;let h=0,g=0;for(const[w,m]of t)w!==n.key&&(h+=m,g+=r.get(w)??0);if(u<o.minimumSampleSize||h<o.minimumSampleSize)continue;const p=f/u,b=g/h,y=p-b,z=H(f,u,g,h);l.push({id:`yield:${n.key}`,level:"wafer",severity:"info",variable:{kind:"yield",label:"Yield"},comparison:{family:n.family,left:n.label,right:I(n.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:z,sampleSizeLeft:u,sampleSizeRight:h},summary:Se(n.label,y,n.family),highlight:{kind:"region",regionFamily:n.family,regionKeys:[n.key],dieKeys:[...n.dieKeys]}})}return le(l,o)}function L(s,a,i,o,e,c){const r=g=>i==="soft"?g.sbin:g.hbin,t=[...new Set(s.map(r).filter(g=>g!==void 0))].sort((g,p)=>g-p),l=re(s,a),n=new Map,u=new Map;for(const[g,p]of l){const b=new Map;for(const y of p){const z=r(y);z!==void 0&&b.set(z,(b.get(z)??0)+1)}n.set(g,b),u.set(g,p.length)}const f=[],h=e==="hardBin"?"HBin":"SBin";for(const g of a){const p=u.get(g.key)??0,b=n.get(g.key);let y=0;const z=new Map;for(const[w,m]of n)if(w!==g.key){y+=u.get(w)??0;for(const[S,v]of m)z.set(S,(z.get(S)??0)+v)}if(!(p<c.minimumSampleSize||y<c.minimumSampleSize))for(const w of t){const m=b.get(w)??0,S=z.get(w)??0,v=m/p,D=S/y,$=v-D,k=H(m,p,S,y),M=ve(w,o,h);f.push({id:`${e}:${w}:${g.key}`,level:"wafer",severity:"info",variable:{kind:e,bin:w,label:M},comparison:{family:g.family,left:g.label,right:I(g.family)},effect:{direction:$===0?"different":$>0?"higher":"lower",absoluteDelta:$,relativeDelta:D===0?void 0:$/D,effectSize:$},stats:{method:"two-proportion-z",pValue:k,sampleSizeLeft:p,sampleSizeRight:y},summary:we(g.label,M,$,g.family),highlight:{kind:"bin",bin:w,regionKeys:[g.key],dieKeys:[...g.dieKeys]}})}}return le(f,c)}function Q(s,a,i,o){const e=(i??[]).filter(u=>!O(u));if(!e.length)return[];const c=e.map(u=>u.testNumber),r=re(s,a),t=new Map,l=new Map;for(const[u,f]of r){const h=new Uint32Array(e.length),g=new Uint32Array(e.length);for(const p of f)for(let b=0;b<e.length;b++){const y=me(p,c[b],e[b]);y!==void 0&&(g[b]++,y&&h[b]++)}t.set(u,h),l.set(u,g)}const n=[];for(const u of a){const f=t.get(u.key),h=l.get(u.key);for(let g=0;g<e.length;g++){let p=0,b=0;for(const[$,k]of l)$!==u.key&&(b+=k[g],p+=t.get($)[g]);const y=h[g],z=f[g];if(y<o.minimumSampleSize||b<o.minimumSampleSize)continue;const w=z/y,m=p/b,S=w-m,v=H(z,y,p,b),D=c[g];n.push({id:`functional:${D}:${u.key}`,level:"wafer",severity:"info",variable:{kind:"functionalTest",index:D,label:`${e[g].name} pass rate`},comparison:{family:u.family,left:u.label,right:I(u.family)},effect:{direction:S===0?"different":S>0?"higher":"lower",absoluteDelta:S,relativeDelta:m===0?void 0:S/m,effectSize:S},stats:{method:"two-proportion-z",pValue:v,sampleSizeLeft:y,sampleSizeRight:b},summary:Ge(u.label,e[g].name,S,u.family),highlight:{kind:"region",regionFamily:u.family,regionKeys:[u.key],dieKeys:[...u.dieKeys]}})}}return le(n,o)}function fe(s,a){return s.length<2?0:s.reduce((i,o)=>i+(o-a)**2,0)/(s.length-1)}function De(s,a,i,o,e,c){const r=Math.sqrt(i/s+c/o),t=a-e;if(!Number.isFinite(r)||r===0)return{pValue:1,effectSize:0,delta:t};const l=t/r,n=Math.sqrt(Math.max(0,(i+c)/2)),u=n===0?t:t/n;return{pValue:be(2*(1-pe(Math.abs(l)))),effectSize:u,delta:t}}function Je(s,a){const i=J(s),o=J(a);return De(s.length,i,fe(s,i),a.length,o,fe(a,o))}const ke=250;function ze(s,a,i){const o=r=>r.filter(t=>O(i?.find(l=>l.testNumber===t)));if(a)return{testNumbers:o(a.slice().sort((r,t)=>r-t))};const e=new Set;let c=!1;e:for(const r of s)for(const t of Object.keys(r.testValues??{})){const l=Number(t);if(!e.has(l)&&(e.add(l),e.size>ke)){c=!0;break e}}if(c){const r=`Test-value analysis was skipped: more than ${ke} 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,t)=>r-t))}}function U(s,a,i,o){const e=ze(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 t=a.length,l=[],n=[];for(const p of s){const b=r.get(T(p));b!==void 0&&(l.push(p),n.push(b))}const u=[],f=new Float64Array(t),h=new Float64Array(t),g=new Float64Array(t);for(const p of c){f.fill(0),h.fill(0),g.fill(0);let b=0,y=0,z=0,w;for(let m=0;m<l.length;m++){const v=l[m].testValues?.[p];if(v===void 0)continue;w===void 0&&(w=v);const D=v-w,$=n[m];f[$]+=1,h[$]+=D,g[$]+=D*D,b+=1,y+=D,z+=D*D}if(w!==void 0)for(let m=0;m<t;m++){const S=f[m],v=b-S;if(S<o.minimumSampleSize||v<o.minimumSampleSize)continue;const D=h[m]/S,k=(y-h[m])/v,M=D+w,R=k+w,P=S>1?Math.max(0,g[m]-S*D*D)/(S-1):0,d=v>1?Math.max(0,z-g[m]-v*k*k)/(v-1):0,{pValue:N,effectSize:B,delta:V}=De(S,M,P,v,R,d),F=a[m],{label:A,unit:G}=Xe(p,i),_=R!==0?V/Math.abs(R):void 0;u.push({id:`test:${p}:${F.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:p,label:A,unit:G},comparison:{family:F.family,left:F.label,right:I(F.family)},effect:{direction:V===0?"different":V>0?"higher":"lower",absoluteDelta:V,relativeDelta:_,effectSize:B},stats:{method:"welch-z-approx",pValue:N,sampleSizeLeft:S,sampleSizeRight:v},summary:$e(F.label,A,V,_,F.family,G),highlight:{kind:"region",regionFamily:F.family,regionKeys:[F.key],dieKeys:[...F.dieKeys]}})}}return ie(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:oe(p.stats.adjustedPValue??p.stats.pValue??1,p.effect.effectSize??0)}))}}function Ze(s,a,i,o){if(!i?.length)return[];const e=i.filter(t=>O(t)&&(t.limitLow!==void 0||t.limitHigh!==void 0));if(!e.length)return[];const c=[],r=new Map(s.map(t=>[T(t),t]));for(const t of e){const l=t.testNumber;for(const n of a){const u=new Map;for(const h of n){const g=[];for(const p of h.dieKeys){const b=r.get(p);b&&g.push(b)}u.set(h.key,g)}const f=[];for(const h of n){const g=u.get(h.key),p=[];for(const[M,R]of u)if(M!==h.key)for(const P of R)p.push(P);const b=M=>M.testValues?.[l]!==void 0,y=M=>{const R=M.testValues?.[l];return R===void 0?!1:t.limitLow!==void 0&&R<t.limitLow||t.limitHigh!==void 0&&R>t.limitHigh},z=g.filter(b),w=p.filter(b);if(z.length<o.minimumSampleSize||w.length<o.minimumSampleSize)continue;const m=z.filter(y).length,S=w.filter(y).length,v=m/z.length,D=S/w.length,$=v-D,k=H(m,z.length,S,w.length);f.push({id:`specLimit:${l}:${h.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:l,label:t.name,unit:t.unit},comparison:{family:h.family,left:h.label,right:I(h.family)},effect:{direction:$===0?"different":$>0?"higher":"lower",absoluteDelta:$,relativeDelta:D===0?void 0:$/D,effectSize:$},stats:{method:"two-proportion-z",pValue:k,sampleSizeLeft:z.length,sampleSizeRight:w.length},summary:`${h.label} spec-fail rate for ${t.name} is ${(Math.abs($)*100).toFixed(1)} pp ${$>0?"higher":"lower"} than the rest of the wafer`,highlight:{kind:"region",regionFamily:h.family,regionKeys:[h.key],dieKeys:[...h.dieKeys]}})}ie(f),c.push(...f.filter(h=>{const g=h.stats.adjustedPValue??h.stats.pValue??1,p=Math.abs(h.effect.absoluteDelta??0),b=Math.abs(h.effect.relativeDelta??0);return g<=o.significanceLevel&&(p>=o.minimumEffectSize||b>=o.minimumRelativeEffect)}).map(h=>({...h,severity:oe(h.stats.adjustedPValue??h.stats.pValue??1,h.effect.effectSize??0)})))}}return c}function j(s){return s.highlight.regionKeys??[]}function et(s){const a=s.variable.bin??s.variable.index??"";return`${s.comparison.family}\0${s.variable.kind}\0${a}\0${s.effect.direction}`}function Y(s,a){const i=j(s)[0]??"",o=q(i);return o.family==="ring"?o.ring??0:o.family==="sector"?he(a).indexOf(o.sector??""):0}function tt(s,a,i){if(a==="quadrant"){const r=new Map;for(const u of s){const f=q(j(u)[0]??"").quadrant;f&&r.set(f,u)}const t=[...r.keys()],l=new Set,n=[];for(const u of t){if(l.has(u))continue;const f=[],h=[u];for(l.add(u);h.length;){const g=h.pop();f.push(g);for(const p of t)!l.has(p)&&je(g,p)&&(l.add(p),h.push(p))}n.push(f.map(g=>r.get(g)))}return n}const o=[...s].sort((r,t)=>Y(r,i)-Y(t,i)),e=[];let c=[];for(const r of o){if(c.length===0){c.push(r);continue}const t=Y(c[c.length-1],i);Y(r,i)-t===1?c.push(r):(e.push(c),c=[r])}if(c.length&&e.push(c),a==="sector"&&e.length>1){const t=he(i).length,l=Y(e[0][0],i),n=Y(e[e.length-1][e[e.length-1].length-1],i);if(l===0&&n===t-1){const u=e.pop();e[0]=[...u,...e[0]]}}return e}function nt(s,a){const i=s.map(r=>q(j(r)[0]??"").ring).filter(r=>r!==void 0).sort((r,t)=>r-t),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 it(s,a){const i=s.map(o=>q(j(o)[0]??"").sector??"");return i.length===1?`Sector ${i[0]}`:`Sectors ${i[0]}\u2013${i[i.length-1]}`}function st(s){const a=s.map(i=>q(j(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 ce(s){return[...new Set(s)]}function ot(s,a){const i=s[0],o=i.comparison.family,e=i.variable.kind,c=ce(s.flatMap(m=>m.highlight.dieKeys??[])),r=ce(s.flatMap(j)),t=new Set(c),l=o==="ring"?nt(s,a.ringCount):o==="sector"?it(s,a.sectorCount):st(s),n=e==="softBin"?a.softEligibleDies:e==="test"?a.testDies:a.eligibleDies,u=n.filter(m=>t.has(T(m))),f=n.filter(m=>!t.has(T(m)));let h,g,p,b,y;if(e==="test"){const m=i.variable.index,S=d=>d.testValues?.[m],v=u.map(S).filter(d=>d!==void 0),D=f.map(S).filter(d=>d!==void 0),{pValue:$,effectSize:k,delta:M}=Je(v,D),R=J(D),P=R!==0?M/Math.abs(R):void 0;h={direction:M===0?"different":M>0?"higher":"lower",absoluteDelta:M,relativeDelta:P,effectSize:k},g={method:"welch-z-approx",pValue:$,sampleSizeLeft:v.length,sampleSizeRight:D.length},p=oe($,k),b=$e(l,i.variable.label,M,P,o,i.variable.unit),y=`test:${m}`}else if(e==="yield"){const m=new Set(a.passBins),S=u.filter(B=>B.hbin!==void 0),v=f.filter(B=>B.hbin!==void 0),D=S.length,$=v.length,k=S.filter(B=>m.has(B.hbin)).length,M=v.filter(B=>m.has(B.hbin)).length,R=D>0?k/D:0,P=$>0?M/$:0,d=R-P,N=H(k,D,M,$);h={direction:d===0?"different":d>0?"higher":"lower",absoluteDelta:d,relativeDelta:P===0?void 0:d/P,effectSize:d},g={method:"two-proportion-z",pValue:N,sampleSizeLeft:D,sampleSizeRight:$},p=se(N,d,h.relativeDelta),b=Se(l,d,o),y="yield"}else{const m=i.variable.bin,S=d=>e==="softBin"?d.sbin:d.hbin,v=u.filter(d=>S(d)===m).length,D=f.filter(d=>S(d)===m).length,$=v/u.length,k=D/f.length,M=$-k,R=H(v,u.length,D,f.length);h={direction:M===0?"different":M>0?"higher":"lower",absoluteDelta:M,relativeDelta:k===0?void 0:M/k,effectSize:M},g={method:"two-proportion-z",pValue:R,sampleSizeLeft:u.length,sampleSizeRight:f.length},p=se(R,M,h.relativeDelta);const P=e==="softBin"?a.sbinDefs:a.hbinDefs;b=we(l,ve(m,P,e==="softBin"?"SBin":"HBin"),M,o),y=`${e}:${m}`}const z=ce(r.map(m=>m.replace(`${o}:`,""))).join("-"),w=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}:${z}`,severity:p,comparison:{family:o,left:l,right:I(o)},effect:h,stats:g,summary:b,highlight:w,relatedIds:s.map(m=>m.id)}}function at(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 t=et(r),l=e.get(t)??[];l.push(r),e.set(t,l)}const c=[];for(const r of e.values()){if(r.length===1){c.push(r[0]);continue}const t=r[0].comparison.family;for(const l of tt(r,t,a.sectorCount))c.push(l.length===1?l[0]:ot(l,a))}return[...o,...c]}function rt(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,t]of i){if(c.size!==t.size)continue;let l=!0;for(const n of c)if(!t.has(n)){l=!1;break}l&&o.add(`${e}:${r}`)}return o}function lt(s){return`${s.comparison.family}\0${s.comparison.left}`}function ft(s,a,i){const o=(t,l)=>{t.absorbedIds=[...t.absorbedIds??[],l.id]},e=new Map;for(const t of s){const l=lt(t);(e.get(l)??e.set(l,[]).get(l)).push(t)}const c=rt(a),r=new Set;for(const t of e.values()){for(const n of t)if(!(n.variable.kind!=="hardBin"||n.variable.bin===void 0))for(const u of t){if(u.variable.kind!=="softBin"||u.variable.bin===void 0||r.has(u.id)||!c.has(`${n.variable.bin}:${u.variable.bin}`)||u.effect.direction!==n.effect.direction)continue;o(n,u),r.add(u.id);const f=n.variable.label.replace(/^HBin\s+/,""),h=u.variable.label.replace(/^SBin\s+/,""),g=f===h?`HBin and SBin ${f} (same dies)`:`${n.variable.label} and ${u.variable.label} (same dies)`;n.summary=n.summary.replace(n.variable.label,g),n.variable.label=g;break}if(i.length!==1)continue;const l=t.find(n=>n.variable.kind==="yield");if(l){for(const n of t)if(!(n===l||r.has(n.id)||!((n.variable.kind==="hardBin"||n.variable.kind==="softBin")&&n.variable.bin===i[0]))){o(l,n),r.add(n.id);for(const f of n.absorbedIds??[])l.absorbedIds=[...l.absorbedIds??[],f]}}}}export function analyzeWaferMap(s,a={}){const{resolved:i,warnings:o}=He(a),e=Ce(s),c={...i,passBins:e.passBins??x.passBins,ringCount:e.ringCount??x.ringCount},r=e.isLotStack,t=e.aggrMethod,l=!r||t==="mode"||t==="countBin"||t==="percent",n=e.dies.filter(d=>Ye(d)),u=n.filter(ge),f=Ie(c,n.length),h=e.dies.filter(d=>te(d)),g=h.filter(ge),p=Ke(g,e.wafer,f.ringCount),b=Le(g,e.wafer,f.ringCount),y=Ee(g,e.reticleConfig),z=xe(h,!1),w=Te(g,e.wafer,f.sectorCount),m=[];l&&m.push(...W(n,p,f.passBins,f),...W(n,b,f.passBins,f),...W(n,y,f.passBins,f),...W(n,z,f.passBins,f),...W(n,w,f.passBins,f)),l&&m.push(...L(n,p,"hard",e.hbinDefs,"hardBin",f),...L(n,b,"hard",e.hbinDefs,"hardBin",f),...L(n,y,"hard",e.hbinDefs,"hardBin",f),...L(n,z,"hard",e.hbinDefs,"hardBin",f),...L(n,w,"hard",e.hbinDefs,"hardBin",f));{const d=n.filter(N=>N.sbin!==void 0);m.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,z,"soft",e.sbinDefs,"softBin",f),...L(d,w,"soft",e.sbinDefs,"softBin",f))}const S=[...o];let v;if(f.enableTestValueAnalysis){const d=U(n,p,e.testDefs,f),N=U(n,b,e.testDefs,f),B=U(n,y,e.testDefs,f),V=U(n,z,e.testDefs,f),F=U(n,w,e.testDefs,f);m.push(...d.findings,...N.findings,...B.findings,...V.findings,...F.findings),d.warning&&S.push(d.warning),v=d.activeTestNumbers,m.push(...Ze(n,[p,b,y,z,w],e.testDefs,f)),m.push(...Q(n,p,e.testDefs,f),...Q(n,b,e.testDefs,f),...Q(n,y,e.testDefs,f),...Q(n,z,e.testDefs,f),...Q(n,w,e.testDefs,f))}{const d=_e(r,l,e.testDefs);(!r||l||d!==void 0)&&m.push(...Ae(u,e.wafer,{...f,isFailingDie:d}))}const D=at(m,{eligibleDies:n,softEligibleDies:n.filter(d=>d.sbin!==void 0),testDies:n,passBins:f.passBins,ringCount:f.ringCount,sectorCount:f.sectorCount,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs});m.length=0,m.push(...D);let $;if(l){const d=Oe(u,e.wafer,{passBins:f.passBins,ringCount:f.ringCount});if($=d??void 0,d!==null&&d.pattern!=="random"&&d.pattern!=="none"){let Fe=function(C){const E=j(C).map(K=>q(K).ring).filter(K=>K!==void 0);if(E.length===0)return!0;const ue=E.some(K=>K===Be),de=E.some(K=>K===1);return X==="edge-ring"||X==="edge-local"?ue:X==="center"?de:X==="donut"?!ue&&!de:!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,V=d.confidence==="high"?"unusual":d.confidence==="medium"?"notable":"info",F=d.features,A=C=>`${(C*100).toFixed(0)}%`,G=d.pattern==="edge-ring"||d.pattern==="edge-local"?`${A(F.edgeRdd)} of edge dies failing`:d.pattern==="center"||d.pattern==="donut"?`centroid at ${A(F.centroidDistNorm)} of wafer radius from centre`:d.pattern==="scratch"?`linear score ${F.linearScore.toFixed(2)}, eccentricity ${F.eccentricity.toFixed(2)}`:`${A(F.globalRdd)} of dies failing`,_=n.filter(C=>{const E=C.hbin??C.sbin;return E!==void 0&&!new Set(f.passBins).has(E)}),Me={"edge-ring":["ring","edge-arc"],"edge-local":["edge-arc","sector","quadrant"],center:["ring","cluster"],donut:["ring"],scratch:["cluster","sector","quadrant"],"near-full":["ring"]},Re=new Set(Me[d.pattern]??[]),X=d.pattern,Be=f.ringCount,ee=[];for(const C of m){if(!Re.has(C.comparison.family)||C.comparison.family==="ring"&&!Fe(C))continue;ee.push(C.id);const E=C.comparison.family==="ring"||C.comparison.family==="quadrant"||C.comparison.family==="sector";V!=="info"&&E&&(C.severity="info")}m.push({id:`spatial-pattern:${d.pattern}`,level:"wafer",severity:V,variable:{kind:"spatialPattern",label:B},comparison:{family:"spatial-pattern",left:B,right:"Wafer"},effect:{direction:"different",effectSize:F.globalRdd},stats:{method:"geometry",sampleSizeLeft:_.length,sampleSizeRight:n.length},summary:`Spatial pattern: ${B.toLowerCase()} (${d.confidence} confidence) \u2014 ${G}${d.note?` [${d.note}]`:""}`,highlight:{kind:"dies",dieKeys:_.map(C=>T(C))},relatedIds:ee.length>0?ee:void 0})}}m.sort((d,N)=>{const B=d.stats.adjustedPValue??d.stats.pValue??1,V=N.stats.adjustedPValue??N.stats.pValue??1;return B!==V?B-V:Math.abs(N.effect.absoluteDelta??0)-Math.abs(d.effect.absoluteDelta??0)});const k=We(e.dies,n.length,e.yield.yieldPercent);r&&(k.isLotStack=!0,t&&(k.aggregationMethod=t));const M=Qe(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(n,f.testNumbers,e.testDefs);d.warning&&S.push(d.warning),v=d.testNumbers}if(v?.length){const d=Ue(e.dies,v,e.testDefs,f.minimumSampleSize);d&&(k.perTestStats=d);const N=Ne([{dies:e.dies}],e.testDefs,v);N&&(k.capability=N)}Object.assign(k,Pe(e.dies,e.testDefs));const P=Ve([{dies:e.dies,wafer:e.wafer,passBins:f.passBins,ringCount:f.ringCount}]);return P&&(k.regionYield=P),S.length>0&&(k.warnings=S),ft(m,n,f.passBins),{level:"wafer",hasNotableFindings:m.some(d=>d.severity==="notable"||d.severity==="unusual"),findings:m,wafer:e.wafer.metadata??void 0,stats:k}}
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}}
@@ -7,6 +7,10 @@ export interface CapabilityDatum {
7
7
  testNumber: number;
8
8
  label: string;
9
9
  unit?: string;
10
+ /** Computed from other tests rather than measured — see `TestCapability.derived`. */
11
+ derived?: true;
12
+ /** The expression a `derived` test was computed from, for display. */
13
+ expression?: string;
10
14
  /**
11
15
  * Whether this test has both `limitLow` and `limitHigh` defined. When
12
16
  * false, `lsl`/`usl`/`cp`/`cpk`/`pp`/`ppk` are all absent/null — there is
@@ -17,6 +21,8 @@ export interface CapabilityDatum {
17
21
  hasSpec: boolean;
18
22
  lsl?: number;
19
23
  usl?: number;
24
+ /** Which limits `lsl`/`usl` are — see `TestCapability.limitBasis`. */
25
+ limitBasis?: 'spec' | 'test';
20
26
  mean: number;
21
27
  /** Sample stddev, ddof=1, pooled across every die across all items. */
22
28
  stdOverall: number;
@@ -76,8 +82,8 @@ export interface PooledTestStats {
76
82
  /**
77
83
  * Per test, how many pooled values fell outside the test's own spec limits —
78
84
  * the spec-yield tally, counted off the sorted values this pass already held.
79
- * Only tests carrying at least one limit appear. `fail` counts `v < limitLow`
80
- * or `v > limitHigh`, matching the per-die judgement everywhere else.
85
+ * Only tests carrying at least one limit appear. `fail` is
86
+ * `countOutOfSpecSorted` — the same rule as the per-die `classifySpec`.
81
87
  *
82
88
  * This is here rather than the sorted arrays themselves because the result is
83
89
  * memoised: a tally is a handful of numbers per test, whereas the values are
@@ -1 +1 @@
1
- import{isYieldEligibleDie as O}from"../core/dies.js";import{drain as C}from"../core/utils.js";import{isParametricTest as $}from"../renderer/buildWaferMap.js";import{describeSorted as E,quantile as k}from"./math.js";const L=25e4;function x(o){const e=new Map,t=new Map;for(const l of o){const n=l.testNumber;if(n===void 0||!$(l))continue;t.set(n,l);const i=l.limitLow,r=l.limitHigh;i!==void 0&&r!==void 0&&r>i&&e.set(n,{lsl:i,usl:r})}return{defByTestNumber:t,specByTest:e}}function*v(o,e,t){const l=new Map,n=[];for(const f of e.keys())l.set(f,n.length),n.push(f);const i=n.length,r=new Float64Array(i),s=new Float64Array(i),d=new Float64Array(i),w=new Float64Array(i),F=new Float64Array(i),g=new Float64Array(i),y=new Float64Array(i),b=new Float64Array(i),h=t?new Array(i):[],S=i<=64,A=Math.max(500,Math.floor(L/Math.max(1,i)));let u=0;for(const f of o){g.fill(0),y.fill(0),b.fill(0);for(const m of f.dies??[]){if(++u>=A&&(u=0,yield),!O(m))continue;const p=m.testValues;if(p){if(S){for(let c=0;c<i;c++){const a=p[n[c]];a===void 0||!Number.isFinite(a)||(r[c]++,s[c]+=a,d[c]+=a*a,g[c]++,y[c]+=a,b[c]+=a*a,t&&(h[c]??=[]).push(a))}continue}for(const c in p){const a=l.get(+c);if(a===void 0)continue;const N=p[+c];N===void 0||!Number.isFinite(N)||(r[a]++,s[a]+=N,d[a]+=N*N,g[a]++,y[a]+=N,b[a]+=N*N,t&&(h[a]??=[]).push(N))}}}for(let m=0;m<i;m++){const p=g[m];if(p<2)continue;const c=y[m]/p,a=(b[m]-p*c*c)/(p-1);w[m]+=(p-1)*Math.max(0,a),F[m]+=p-1}}const M=new Map;for(let f=0;f<i;f++){if(r[f]===0)continue;const m=n[f];M.set(m,{n:r[f],sum:s[f],sumSq:d[f],withinNumerator:w[f],withinDenominator:F[f]}),t&&h[f]&&t.set(m,h[f])}return M}function P(o,e,t){return C(v(o,e,t))}function q(o,e,t,l){const{n,sum:i,sumSq:r,withinNumerator:s,withinDenominator:d}=l,w=i/n,F=n>=2?Math.max(0,(r-n*w*w)/(n-1)):0,g=Math.sqrt(F),y=d>0?Math.sqrt(s/d):NaN;let b=null,h=null,S=null,A=null;if(t){const{lsl:u,usl:M}=t,f=M-u;S=g>0?f/(6*g):null,A=g>0?Math.min((M-w)/(3*g),(w-u)/(3*g)):null,b=Number.isFinite(y)&&y>0?f/(6*y):null,h=Number.isFinite(y)&&y>0?Math.min((M-w)/(3*y),(w-u)/(3*y)):null}return{testNumber:o,label:e.name??`Test ${o}`,unit:e.unit,hasSpec:t!==void 0,lsl:t?.lsl,usl:t?.usl,mean:w,stdOverall:g,stdWithin:y,n,cp:b,cpk:h,pp:S,ppk:A}}function B(o){return o.sort((e,t)=>e.hasSpec!==t.hasSpec?e.hasSpec?-1:1:e.hasSpec?e.ppk===null&&t.ppk===null?0:e.ppk===null?1:t.ppk===null?-1:e.ppk-t.ppk:t.stdOverall-e.stdOverall)}export function buildCapabilityData(o,e){return C(buildCapabilityDataSteps(o,e))}export function*buildCapabilityDataSteps(o,e){return(yield*pooledTestStatsSteps(o,e)).capability.slice()}const T=new Map,H=4,D=new WeakMap;let V=1;function _(o,e){const t=[];for(const n of o){const i=n.dies;if(!i){t.push(0);continue}let r=D.get(i);r===void 0&&(r=V++,D.set(i,r)),t.push(r)}const l=e.map(n=>`${n.testNumber}${n.testType??"P"}${n.limitLow??""}${n.limitHigh??""}${n.name??""}${n.unit??""}`);return`${t.join(",")}${l.join("")}`}export function clearPooledTestStatsCache(){T.clear()}function z(o,e){let t=0,l=o.length;for(;t<l;){const n=t+l>>1;o[n]<e?t=n+1:l=n}return t}function I(o,e){let t=0,l=o.length;for(;t<l;){const n=t+l>>1;o[n]<=e?t=n+1:l=n}return t}export function*pooledTestStatsSteps(o,e){const t=_(o,e),l=T.get(t);if(l)return T.delete(t),T.set(t,l),l;const n={stats:new Map,specTally:new Map,capability:[]},{defByTestNumber:i,specByTest:r}=x(e);if(i.size===0)return n;const s=new Map,d=yield*v(o,i,s),w=new Map,F=new Map,g=[];for(const b of e){yield;const h=b.testNumber;if(h===void 0)continue;const S=d.get(h);if(!S||S.n===0||!i.has(h))continue;const A=s.get(h);s.delete(h);const u=Float64Array.from(A);if(u.sort(),w.set(h,E(u)),b.limitLow!==void 0||b.limitHigh!==void 0){const p=b.limitLow!==void 0?z(u,b.limitLow):0,c=b.limitHigh!==void 0?u.length-I(u,b.limitHigh):0;F.set(h,{n:u.length,fail:p+c})}const M=r.get(h),f=q(h,b,M,S);let m;if(M){const p=M.usl-M.lsl;m=c=>(c-M.lsl)/p}else{const p=u[0],c=u[u.length-1]-p;m=c>0?a=>(a-p)/c:()=>.5}g.push({...f,min:m(u[0]),q1:m(k(u,.25)),median:m(k(u,.5)),q3:m(k(u,.75)),max:m(u[u.length-1])})}const y={stats:w,specTally:F,capability:B(g)};return T.set(t,y),T.size>H&&T.delete(T.keys().next().value),y}export function buildCapabilityFigures(o,e){const{defByTestNumber:t,specByTest:l}=x(e);if(t.size===0)return[];const n=P(o,t,null),i=[];for(const[r,s]of n)s.n!==0&&i.push(q(r,t.get(r),l.get(r),s));return B(i)}export function poolCapabilityFigures(o,e){const{defByTestNumber:t,specByTest:l}=x(e),n=new Map;for(const r of o)for(const s of r){if(!t.has(s.testNumber)||s.n===0)continue;let d=n.get(s.testNumber);d||(d={n:0,sum:0,sumSq:0,withinNumerator:0,withinDenominator:0},n.set(s.testNumber,d));const w=s.stdOverall*s.stdOverall;d.n+=s.n,d.sum+=s.n*s.mean,d.sumSq+=(s.n-1)*w+s.n*s.mean*s.mean,s.n>=2&&(d.withinNumerator+=(s.n-1)*w,d.withinDenominator+=s.n-1)}const i=[];for(const[r,s]of n)i.push(q(r,t.get(r),l.get(r),s));return B(i)}
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)}
@@ -3,8 +3,11 @@ import { type TestDef } from '../renderer/buildWaferMap.js';
3
3
  /** Minimal per-test identity carried on a correlation matrix's axes. */
4
4
  export interface CorrelationTestInfo {
5
5
  testNumber: number;
6
+ /** Display name; a derived test's starts with `"† "` (`markedTestLabel`). */
6
7
  label: string;
7
8
  unit?: string;
9
+ derived?: true;
10
+ expression?: string;
8
11
  }
9
12
  export interface CorrelationCell {
10
13
  xIndex: number;
@@ -61,7 +64,7 @@ export interface CorrelationMatrix {
61
64
  * Correlation is the only computation here that is quadratic in tests *and*
62
65
  * linear in dies: a 400k-die, 50-test lot is 1,225 pairs per die — 490 million
63
66
  * pair updates, each touching six accumulators — which in a browser presents as
64
- * a panel that never renders (tsmap WMAP_ISSUES #61).
67
+ * a panel that never renders.
65
68
  *
66
69
  * 25,000 is far past the point where more dies change a Pearson coefficient: the
67
70
  * standard error of r is about `(1 - r²)/sqrt(n)`, so at n = 25,000 it is under
@@ -1 +1 @@
1
- import{isParametricTest as X}from"../renderer/buildWaferMap.js";export function pearsonFromSums(e,i,a,r,c,d){if(e<3)return null;const m=i/e,f=a/e,s=d/e-m*f,h=r/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 i=0,a=0,r=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)||(i++,a+=s,r+=h,c+=s*s,d+=h*h,m+=s*h)}return{r:pearsonFromSums(i,a,r,c,d,m),n:i}}export const CORRELATION_DIE_BUDGET=25e3;function Y(e,i){const a=[],r=e/i;for(let c=0;c<i;c++)a.push(Math.floor(c*r));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 v(e){return e.filter(i=>i.testNumber!==void 0&&X(i)).map(i=>({testNumber:i.testNumber,label:i.name,unit:i.unit}))}export function buildCorrelationMatrix(e,i){const a=v(i);if(a.length<2)return{tests:a,cells:[]};const r=a.length,c=a.map(n=>n.testNumber),d=r*(r-1)/2,m=new Float64Array(d),f=new Float64Array(r),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*r-(n*(n+1)>>1)+(o-n-1)}const g=e.filter(n=>n.testValues!==void 0),t=g.length>CORRELATION_DIE_BUDGET,l=t?Y(g.length,CORRELATION_DIE_BUDGET).map(n=>g[n]):g,u=new Float64Array(r),p=new Uint8Array(r);for(const n of l){p.fill(0);for(let o=0;o<r;o++){const b=n.testValues[c[o]];b!==void 0&&Number.isFinite(b)&&(u[o]=b,p[o]=1)}for(let o=0;o<r;o++){if(!p[o])continue;f[o]++;const b=u[o];for(let F=o+1;F<r;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<r;n++)for(let o=0;o<r;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:i=.3,minTests:a=6,maxTests:r=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<i;s.size>=r||p&&s.size>=a||(s.add(t),s.size<r&&s.add(l))}if(s.size<Math.min(a,e.tests.length))for(let t=0;t<e.tests.length&&s.size<Math.min(r,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<i&&g++;return{matrix:{tests:w,cells:R},strongPairs:d,moderatePairs:m,hiddenWeakPairs:g,strongestPair:f}}
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}}
@@ -19,7 +19,7 @@ export declare const buildTestBoxplotData: typeof buildTestBoxplotDataImpl;
19
19
  export declare const buildTestTrendData: typeof buildTestTrendDataImpl;
20
20
  /** @deprecated Removed in 0.31.0. Compute the die-weighted mean from `perWaferTestStats` (mean × count) on `analyzeWaferLot`'s result. */
21
21
  export declare const trendCentre: typeof trendCentreImpl;
22
- /** @deprecated Removed in 0.31.0. `analyzeWaferMap` and `analyzeWaferLot` now return per-test pass rates as `stats.testSpecYield` (spec limits), `stats.testFlagYield` (tester verdicts) and `stats.functionalYield`, with `stats.specVerdictDisagreementDies.` */
22
+ /** @deprecated Removed in 0.31.0. `analyzeWaferMap` and `analyzeWaferLot` now return per-test pass rates as `stats.testSpecYield` (test limits), `stats.testFlagYield` (tester verdicts) and `stats.functionalYield`, with `stats.specVerdictDisagreementDies.` */
23
23
  export declare const buildTestPassRateData: typeof buildTestPassRateDataImpl;
24
24
  /** @deprecated Removed in 0.31.0. Read which pass rates exist from `stats.testSpecYield`, `stats.testFlagYield` and `stats.functionalYield` on `analyzeWaferMap`'s or `analyzeWaferLot`'s result. */
25
25
  export declare const hasJudgeableTests: typeof hasJudgeableTestsImpl;
@@ -79,4 +79,7 @@ export declare const renderSummaryReportHtml: typeof renderSummaryReportHtmlImpl
79
79
  export declare const renderLotSummaryReportHtml: typeof renderLotSummaryReportHtmlImpl;
80
80
  /** @deprecated Removed in 0.31.0. `buildFacetTable` applies it by default. */
81
81
  export declare const DEFAULT_FACET_CURATION: Record<string, import("./facets.js").FacetCuration>;
82
+ import { renderFindingsReportHtml as renderFindingsReportHtmlImpl } from './renderFindingsReport.js';
83
+ /** @deprecated Removed in 0.31.0. Use `renderWaferReportHtml(result, summary)` or `renderLotReportHtml(results)`: their Findings section is the same table, alongside the population and yield it was found in. */
84
+ export declare const renderFindingsReportHtml: typeof renderFindingsReportHtmlImpl;
82
85
  //# sourceMappingURL=deprecated.d.ts.map
@@ -1 +1 @@
1
- import{deprecated as e}from"../renderer/deprecate.js";import{buildCapabilityData as s}from"./capability.js";import{buildCorrelationMatrix as r,filterCorrelationMatrix as i}from"./correlation.js";import{buildTestBoxplotData as o}from"./boxplot.js";import{buildTestTrendData as n,trendCentre as l}from"./trend.js";import{buildTestPassRateData as d,hasJudgeableTests as p}from"./testPassRate.js";import{buildTestHistogramData as m,buildTestHistogramSeries as u}from"./histogram.js";import{buildScatterData as c,buildScatterDataGrouped as f}from"./scatter.js";import{buildYieldData as b,buildYieldDataCombined as g}from"./yield.js";import{buildBinParetoData as h,buildBinClusterData as y}from"./binPareto.js";const t="If you depend on it, say so at https://github.com/wafertools/wafermap/issues.";export const buildCapabilityData=e(s,"buildCapabilityData",`analyzeWaferMap and analyzeWaferLot now return Cp/Cpk/Pp/Ppk as stats.capability (enable computePerTestStats); a lot's uses the pooled within-wafer stddev. ${t}`),buildCorrelationMatrix=e(r,"buildCorrelationMatrix",`It prepared data for the Insights correlation matrix, which wmap draws itself (insights: { enabled: true }); there is no data replacement yet. ${t}`),filterCorrelationMatrix=e(i,"filterCorrelationMatrix",`It prepared data for the Insights correlation matrix, which wmap draws itself (insights: { enabled: true }); there is no data replacement. ${t}`),buildTestBoxplotData=e(o,"buildTestBoxplotData",`Five-number summaries are stats.perTestStats (enable computePerTestStats), and perWaferTestStats on analyzeWaferLot's result. ${t}`),buildTestTrendData=e(n,"buildTestTrendData",`Per-wafer means and stddevs in slot order are perWaferTestStats on analyzeWaferLot's result (enable computePerTestStats). ${t}`),trendCentre=e(l,"trendCentre",`Compute the die-weighted mean from perWaferTestStats (mean \xD7 count) on analyzeWaferLot's result. ${t}`),buildTestPassRateData=e(d,"buildTestPassRateData",`analyzeWaferMap and analyzeWaferLot now return per-test pass rates as stats.testSpecYield (spec limits), stats.testFlagYield (tester verdicts) and stats.functionalYield, with stats.specVerdictDisagreementDies. ${t}`),hasJudgeableTests=e(p,"hasJudgeableTests",`Read which pass rates exist from stats.testSpecYield, stats.testFlagYield and stats.functionalYield on analyzeWaferMap's or analyzeWaferLot's result. ${t}`),buildTestHistogramData=e(m,"buildTestHistogramData",`It prepared data for the Insights histogram, which wmap draws itself (insights: { enabled: true }); there is no data replacement. ${t}`),buildTestHistogramSeries=e(u,"buildTestHistogramSeries",`It prepared data for the Insights histogram, which wmap draws itself (insights: { enabled: true }); there is no data replacement. ${t}`),buildScatterData=e(c,"buildScatterData",`It prepared data for the Insights scatter plot, which wmap draws itself (insights: { enabled: true }); there is no data replacement. ${t}`),buildScatterDataGrouped=e(f,"buildScatterDataGrouped",`It prepared data for the Insights scatter plot, which wmap draws itself (insights: { enabled: true }); there is no data replacement. ${t}`),buildYieldData=e(b,"buildYieldData",`Per-wafer yield is lotYieldSeries on analyzeWaferLot's result, and stats.yieldPercent on each summary. ${t}`),buildYieldDataCombined=e(g,"buildYieldDataCombined",`Per-wafer yield is lotYieldSeries on analyzeWaferLot's result; weight it by each wafer's die count to combine groups. ${t}`),buildBinParetoData=e(h,"buildBinParetoData",`Bin counts are stats.hardBinCounts and stats.softBinCounts on analyzeWaferMap's result. ${t}`),buildBinClusterData=e(y,"buildBinClusterData",`Bin counts per wafer are stats.hardBinCounts and stats.softBinCounts on each perWafer summary of analyzeWaferLot's result. ${t}`);import{deprecatedValue as R}from"../renderer/deprecate.js";import{buildRingRegions as D,buildQuadrantRegions as T,buildSectorRegions as I,buildReticlePositionRegions as C,buildTestSiteRegions as x,buildRegionYieldData as S,areQuadrantsAdjacent as w,parseRegionKey as $,sectorCompassNames as M}from"./regions.js";import{classifyPattern as W}from"./patternClassification.js";import{computeFunctionalYield as Y}from"./analyzeWaferMap.js";import{resolveMetadataColumns as P,discoverDieMetadataKeys as z}from"./metadataColumns.js";import{openHtmlReport as L}from"./renderFindingsReport.js";import{renderSummaryReportHtml as A,renderLotSummaryReportHtml as H}from"./renderSummaryReport.js";import{DEFAULT_FACET_CURATION as B}from"./facets.js";const F="analyzeWaferMap returns the classification, with its geometry features, as stats.spatialPattern for every wafer, and a detected pattern as a finding (comparison.family 'spatial-pattern').",E="analyzeWaferMap returns the same figures as stats.functionalYield.",a="The die list resolves its metadata columns itself (the dieList.metadataColumns option).";export const buildRingRegions=e(D,"buildRingRegions",`Ring yield is stats.regionYield.ring on analyzeWaferMap's or analyzeWaferLot's result; ring findings are in findings. ${t}`),buildQuadrantRegions=e(T,"buildQuadrantRegions",`Quadrant yield is stats.regionYield.quadrant on analyzeWaferMap's or analyzeWaferLot's result; quadrant findings are in findings. ${t}`),buildSectorRegions=e(I,"buildSectorRegions",`Sector findings are in analyzeWaferMap's findings (comparison.family 'sector'). ${t}`),buildReticlePositionRegions=e(C,"buildReticlePositionRegions",`Reticle-position findings are in analyzeWaferMap's findings; a die's reticle cell is getReticleCell(die, reticleConfig). ${t}`),buildTestSiteRegions=e(x,"buildTestSiteRegions",`Test-site findings are in analyzeWaferMap's findings (comparison.family 'test-site'). ${t}`),buildRegionYieldData=e(S,"buildRegionYieldData",`analyzeWaferMap and analyzeWaferLot now return ring and quadrant yield as stats.regionYield. ${t}`),areQuadrantsAdjacent=e(w,"areQuadrantsAdjacent",`It is an internal helper of the quadrant findings, which analyzeWaferMap already reports. ${t}`),parseRegionKey=e($,"parseRegionKey",`Findings name their region in prose (comparison.left) and region yield in RegionYield.label; region keys are identities, not to be parsed. ${t}`),sectorCompassNames=e(M,"sectorCompassNames",`Sector findings name their sector in prose (comparison.left). ${t}`),classifyPattern=e(W,"classifyPattern",`${F} ${t}`),computeFunctionalYield=e(Y,"computeFunctionalYield",`${E} ${t}`),resolveMetadataColumns=e(P,"resolveMetadataColumns",`${a} ${t}`),discoverDieMetadataKeys=e(z,"discoverDieMetadataKeys",`${a} ${t}`),openHtmlReport=e(L,"openHtmlReport",`Open reports with openReportModal(html), or route them into your host with setReportOpener. ${t}`),renderSummaryReportHtml=e(A,"renderSummaryReportHtml",`Use renderWaferReportHtml(result, summary), which reads pass bins and ring count from the built map. ${t}`),renderLotSummaryReportHtml=e(H,"renderLotSummaryReportHtml",`Use renderLotReportHtml(results), which reads each wafer's pass bins and ring count from its built map. ${t}`),DEFAULT_FACET_CURATION=R(B,"DEFAULT_FACET_CURATION");
1
+ import{deprecated as e}from"../renderer/deprecate.js";import{buildCapabilityData as s}from"./capability.js";import{buildCorrelationMatrix as r,filterCorrelationMatrix as i}from"./correlation.js";import{buildTestBoxplotData as o}from"./boxplot.js";import{buildTestTrendData as n,trendCentre as l}from"./trend.js";import{buildTestPassRateData as d,hasJudgeableTests as p}from"./testPassRate.js";import{buildTestHistogramData as m,buildTestHistogramSeries as u}from"./histogram.js";import{buildScatterData as c,buildScatterDataGrouped as f}from"./scatter.js";import{buildYieldData as b,buildYieldDataCombined as g}from"./yield.js";import{buildBinParetoData as h,buildBinClusterData as y}from"./binPareto.js";const t="If you depend on it, say so at https://github.com/wafertools/wafermap/issues.";export const buildCapabilityData=e(s,"buildCapabilityData",`analyzeWaferMap and analyzeWaferLot now return Cp/Cpk/Pp/Ppk as stats.capability (enable computePerTestStats); a lot's uses the pooled within-wafer stddev. ${t}`),buildCorrelationMatrix=e(r,"buildCorrelationMatrix",`It prepared data for the Insights correlation matrix, which wmap draws itself (insights: { enabled: true }); there is no data replacement yet. ${t}`),filterCorrelationMatrix=e(i,"filterCorrelationMatrix",`It prepared data for the Insights correlation matrix, which wmap draws itself (insights: { enabled: true }); there is no data replacement. ${t}`),buildTestBoxplotData=e(o,"buildTestBoxplotData",`Five-number summaries are stats.perTestStats (enable computePerTestStats), and perWaferTestStats on analyzeWaferLot's result. ${t}`),buildTestTrendData=e(n,"buildTestTrendData",`Per-wafer means and stddevs in slot order are perWaferTestStats on analyzeWaferLot's result (enable computePerTestStats). ${t}`),trendCentre=e(l,"trendCentre",`Compute the die-weighted mean from perWaferTestStats (mean \xD7 count) on analyzeWaferLot's result. ${t}`),buildTestPassRateData=e(d,"buildTestPassRateData",`analyzeWaferMap and analyzeWaferLot now return per-test pass rates as stats.testSpecYield (test limits), stats.testFlagYield (tester verdicts) and stats.functionalYield, with stats.specVerdictDisagreementDies. ${t}`),hasJudgeableTests=e(p,"hasJudgeableTests",`Read which pass rates exist from stats.testSpecYield, stats.testFlagYield and stats.functionalYield on analyzeWaferMap's or analyzeWaferLot's result. ${t}`),buildTestHistogramData=e(m,"buildTestHistogramData",`It prepared data for the Insights histogram, which wmap draws itself (insights: { enabled: true }); there is no data replacement. ${t}`),buildTestHistogramSeries=e(u,"buildTestHistogramSeries",`It prepared data for the Insights histogram, which wmap draws itself (insights: { enabled: true }); there is no data replacement. ${t}`),buildScatterData=e(c,"buildScatterData",`It prepared data for the Insights scatter plot, which wmap draws itself (insights: { enabled: true }); there is no data replacement. ${t}`),buildScatterDataGrouped=e(f,"buildScatterDataGrouped",`It prepared data for the Insights scatter plot, which wmap draws itself (insights: { enabled: true }); there is no data replacement. ${t}`),buildYieldData=e(b,"buildYieldData",`Per-wafer yield is lotYieldSeries on analyzeWaferLot's result, and stats.yieldPercent on each summary. ${t}`),buildYieldDataCombined=e(g,"buildYieldDataCombined",`Per-wafer yield is lotYieldSeries on analyzeWaferLot's result; weight it by each wafer's die count to combine groups. ${t}`),buildBinParetoData=e(h,"buildBinParetoData",`Bin counts are stats.hardBinCounts and stats.softBinCounts on analyzeWaferMap's result. ${t}`),buildBinClusterData=e(y,"buildBinClusterData",`Bin counts per wafer are stats.hardBinCounts and stats.softBinCounts on each perWafer summary of analyzeWaferLot's result. ${t}`);import{deprecatedValue as R}from"../renderer/deprecate.js";import{buildRingRegions as D,buildQuadrantRegions as I,buildSectorRegions as T,buildReticlePositionRegions as C,buildTestSiteRegions as x,buildRegionYieldData as S,areQuadrantsAdjacent as w,parseRegionKey as $,sectorCompassNames as M}from"./regions.js";import{classifyPattern as W}from"./patternClassification.js";import{computeFunctionalYield as Y}from"./analyzeWaferMap.js";import{resolveMetadataColumns as H,discoverDieMetadataKeys as P}from"./metadataColumns.js";import{openHtmlReport as z}from"./renderFindingsReport.js";import{renderSummaryReportHtml as L,renderLotSummaryReportHtml as A}from"./renderSummaryReport.js";import{DEFAULT_FACET_CURATION as F}from"./facets.js";const B="analyzeWaferMap returns the classification, with its geometry features, as stats.spatialPattern for every wafer, and a detected pattern as a finding (comparison.family 'spatial-pattern').",E="analyzeWaferMap returns the same figures as stats.functionalYield.",a="The die list resolves its metadata columns itself (the dieList.metadataColumns option).";export const buildRingRegions=e(D,"buildRingRegions",`Ring yield is stats.regionYield.ring on analyzeWaferMap's or analyzeWaferLot's result; ring findings are in findings. ${t}`),buildQuadrantRegions=e(I,"buildQuadrantRegions",`Quadrant yield is stats.regionYield.quadrant on analyzeWaferMap's or analyzeWaferLot's result; quadrant findings are in findings. ${t}`),buildSectorRegions=e(T,"buildSectorRegions",`Sector findings are in analyzeWaferMap's findings (comparison.family 'sector'). ${t}`),buildReticlePositionRegions=e(C,"buildReticlePositionRegions",`Reticle-position findings are in analyzeWaferMap's findings; a die's reticle cell is getReticleCell(die, reticleConfig). ${t}`),buildTestSiteRegions=e(x,"buildTestSiteRegions",`Test-site findings are in analyzeWaferMap's findings (comparison.family 'test-site'). ${t}`),buildRegionYieldData=e(S,"buildRegionYieldData",`analyzeWaferMap and analyzeWaferLot now return ring and quadrant yield as stats.regionYield. ${t}`),areQuadrantsAdjacent=e(w,"areQuadrantsAdjacent",`It is an internal helper of the quadrant findings, which analyzeWaferMap already reports. ${t}`),parseRegionKey=e($,"parseRegionKey",`Findings name their region in prose (comparison.left) and region yield in RegionYield.label; region keys are identities, not to be parsed. ${t}`),sectorCompassNames=e(M,"sectorCompassNames",`Sector findings name their sector in prose (comparison.left). ${t}`),classifyPattern=e(W,"classifyPattern",`${B} ${t}`),computeFunctionalYield=e(Y,"computeFunctionalYield",`${E} ${t}`),resolveMetadataColumns=e(H,"resolveMetadataColumns",`${a} ${t}`),discoverDieMetadataKeys=e(P,"discoverDieMetadataKeys",`${a} ${t}`),openHtmlReport=e(z,"openHtmlReport",`Open reports with openReportModal(html), or route them into your host with setReportOpener. ${t}`),renderSummaryReportHtml=e(L,"renderSummaryReportHtml",`Use renderWaferReportHtml(result, summary), which reads pass bins and ring count from the built map. ${t}`),renderLotSummaryReportHtml=e(A,"renderLotSummaryReportHtml",`Use renderLotReportHtml(results), which reads each wafer's pass bins and ring count from its built map. ${t}`),DEFAULT_FACET_CURATION=R(F,"DEFAULT_FACET_CURATION");import{renderFindingsReportHtml as v}from"./renderFindingsReport.js";export const renderFindingsReportHtml=e(v,"renderFindingsReportHtml",`Use renderWaferReportHtml(result, summary) or renderLotReportHtml(results): their Findings section is the same table, alongside the population and yield it was found in. ${t}`);
@@ -4,7 +4,7 @@ export * from './analyzeWaferMap.js';
4
4
  export * from './analyzeWaferLot.js';
5
5
  export * from './filterFindings.js';
6
6
  export type { PatternLabel, PatternClassification, PatternFeatures } from './patternClassification.js';
7
- export { setReportOpener, renderFindingsReportHtml } from './renderFindingsReport.js';
7
+ export { setReportOpener } from './renderFindingsReport.js';
8
8
  export { renderWaferReportHtml, renderLotReportHtml } from './renderSummaryReport.js';
9
9
  export type { ReportMap } from './renderSummaryReport.js';
10
10
  export type { SummaryReportParams, LotSummaryReportParams } from './renderSummaryReport.js';
@@ -22,5 +22,5 @@ export type { HistogramBucket, HistogramItem, HistogramSeries, HistogramSeriesDa
22
22
  export type { ScatterPoint, ScatterItem } from './scatter.js';
23
23
  export type { ChartDatum, YieldItem, YieldSortBy } from './yield.js';
24
24
  export type { BinType, BinItem, BinCluster, BinClusterData } from './binPareto.js';
25
- export { buildCapabilityData, buildCorrelationMatrix, filterCorrelationMatrix, buildTestBoxplotData, buildTestTrendData, trendCentre, buildTestPassRateData, hasJudgeableTests, buildTestHistogramData, buildTestHistogramSeries, buildScatterData, buildScatterDataGrouped, buildYieldData, buildYieldDataCombined, buildBinParetoData, buildBinClusterData, buildRingRegions, buildQuadrantRegions, buildSectorRegions, buildReticlePositionRegions, buildTestSiteRegions, buildRegionYieldData, areQuadrantsAdjacent, parseRegionKey, sectorCompassNames, classifyPattern, computeFunctionalYield, resolveMetadataColumns, discoverDieMetadataKeys, openHtmlReport, renderSummaryReportHtml, renderLotSummaryReportHtml, DEFAULT_FACET_CURATION, } from './deprecated.js';
25
+ export { buildCapabilityData, buildCorrelationMatrix, filterCorrelationMatrix, buildTestBoxplotData, buildTestTrendData, trendCentre, buildTestPassRateData, hasJudgeableTests, buildTestHistogramData, buildTestHistogramSeries, buildScatterData, buildScatterDataGrouped, buildYieldData, buildYieldDataCombined, buildBinParetoData, buildBinClusterData, buildRingRegions, buildQuadrantRegions, buildSectorRegions, buildReticlePositionRegions, buildTestSiteRegions, buildRegionYieldData, areQuadrantsAdjacent, parseRegionKey, sectorCompassNames, classifyPattern, computeFunctionalYield, resolveMetadataColumns, discoverDieMetadataKeys, openHtmlReport, renderSummaryReportHtml, renderLotSummaryReportHtml, DEFAULT_FACET_CURATION, renderFindingsReportHtml, } from './deprecated.js';
26
26
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- export*from"./types.js";export*from"./regions.js";export*from"./analyzeWaferMap.js";export*from"./analyzeWaferLot.js";export*from"./filterFindings.js";export{setReportOpener,renderFindingsReportHtml}from"./renderFindingsReport.js";export{renderWaferReportHtml,renderLotReportHtml}from"./renderSummaryReport.js";export{buildFacetTable,facetValueOf,FACET_NONE_VALUE}from"./facets.js";export{mergeTestDefs}from"./mergeTestDefs.js";export{buildCapabilityData,buildCorrelationMatrix,filterCorrelationMatrix,buildTestBoxplotData,buildTestTrendData,trendCentre,buildTestPassRateData,hasJudgeableTests,buildTestHistogramData,buildTestHistogramSeries,buildScatterData,buildScatterDataGrouped,buildYieldData,buildYieldDataCombined,buildBinParetoData,buildBinClusterData,buildRingRegions,buildQuadrantRegions,buildSectorRegions,buildReticlePositionRegions,buildTestSiteRegions,buildRegionYieldData,areQuadrantsAdjacent,parseRegionKey,sectorCompassNames,classifyPattern,computeFunctionalYield,resolveMetadataColumns,discoverDieMetadataKeys,openHtmlReport,renderSummaryReportHtml,renderLotSummaryReportHtml,DEFAULT_FACET_CURATION}from"./deprecated.js";
1
+ export*from"./types.js";export*from"./regions.js";export*from"./analyzeWaferMap.js";export*from"./analyzeWaferLot.js";export*from"./filterFindings.js";export{setReportOpener}from"./renderFindingsReport.js";export{renderWaferReportHtml,renderLotReportHtml}from"./renderSummaryReport.js";export{buildFacetTable,facetValueOf,FACET_NONE_VALUE}from"./facets.js";export{mergeTestDefs}from"./mergeTestDefs.js";export{buildCapabilityData,buildCorrelationMatrix,filterCorrelationMatrix,buildTestBoxplotData,buildTestTrendData,trendCentre,buildTestPassRateData,hasJudgeableTests,buildTestHistogramData,buildTestHistogramSeries,buildScatterData,buildScatterDataGrouped,buildYieldData,buildYieldDataCombined,buildBinParetoData,buildBinClusterData,buildRingRegions,buildQuadrantRegions,buildSectorRegions,buildReticlePositionRegions,buildTestSiteRegions,buildRegionYieldData,areQuadrantsAdjacent,parseRegionKey,sectorCompassNames,classifyPattern,computeFunctionalYield,resolveMetadataColumns,discoverDieMetadataKeys,openHtmlReport,renderSummaryReportHtml,renderLotSummaryReportHtml,DEFAULT_FACET_CURATION,renderFindingsReportHtml}from"./deprecated.js";
@@ -1,6 +1,6 @@
1
1
  import type { TestDef, WaferWarning } from '../renderer/buildWaferMap.js';
2
2
  /** Which field disagreed. `'limits'` is the soft tier; the rest are hard. */
3
- export type TestDefConflictKind = 'name' | 'unit' | 'testType' | 'limits';
3
+ export type TestDefConflictKind = 'name' | 'unit' | 'testType' | 'limits' | 'specLimits';
4
4
  /** One test number whose defs could not be reconciled across the population. */
5
5
  export interface TestDefConflict {
6
6
  testNumber: number;
@@ -1 +1 @@
1
- const k=1e-6;function w(t,e){if(t===e)return!0;if(!Number.isFinite(t)||!Number.isFinite(e))return!1;const i=Math.max(Math.abs(t),Math.abs(e));return Math.abs(t-e)<=1e-6*i}function v(t){const e=t.name?.trim();return e||void 0}function y(t){const e=t.unit?.trim();return e||void 0}function L(t){return t.toLowerCase()}function h(t,e){const i=new Set,s=[];for(const o of t){const r=e(o);i.has(r)||(i.add(r),s.push(o))}return s}function g(t){const e=[];for(const i of t)e.some(s=>w(s,i))||e.push(i);return e}function T(t){return`${t.testNumber} (${t.values.join(" vs ")})`}function b(t,e=5){const i=t.slice(0,e).map(T).join(", "),s=t.length-Math.min(e,t.length);return s>0?`${i}, and ${s} more`:i}export function mergeTestDefs(t){const e=new Map,i=[];for(const r of t)for(const u of r?.testDefs??[]){const f=u.testNumber;let d=e.get(f);d||(d=[],e.set(f,d),i.push(f)),d.push(u)}const s=[],o=[];for(const r of i){const u=e.get(r),f=h(u.map(v).filter(n=>n!==void 0),L),d=h(u.map(y).filter(n=>n!==void 0),n=>n),l=h(u.map(n=>n.testType).filter(n=>n!==void 0),n=>n),p=f.length>1?{testNumber:r,kind:"name",excluded:!0,values:f}:d.length>1?{testNumber:r,kind:"unit",excluded:!0,values:d}:l.length>1?{testNumber:r,kind:"testType",excluded:!0,values:l}:void 0;if(p){o.push(p);continue}const a=g(u.map(n=>n.limitLow).filter(n=>n!==void 0)),c=g(u.map(n=>n.limitHigh).filter(n=>n!==void 0)),m=a.length>1||c.length>1;m&&o.push({testNumber:r,kind:"limits",excluded:!1,values:(a.length>1?a:c).map(n=>String(n))}),s.push({testNumber:r,name:f[0]??"",...d[0]!==void 0?{unit:d[0]}:{},...l[0]!==void 0?{testType:l[0]}:{},...u.find(n=>n.logScale!==void 0)?{logScale:u.find(n=>n.logScale!==void 0).logScale}:{},...!m&&a[0]!==void 0?{limitLow:a[0]}:{},...!m&&c[0]!==void 0?{limitHigh:c[0]}:{}})}return{defs:s,conflicts:o,warnings:N(o)}}function N(t){const e=[],i=t.filter(o=>o.excluded),s=t.filter(o=>o.kind==="limits");return i.length>0&&e.push({code:"test-def-collision",severity:"error",message:`${i.length} test ${i.length===1?"number describes":"numbers describe"} a different measurement in different wafers, so the same number cannot identify one test across this population. Affected: `+b(i)+". These tests are withheld from every cross-wafer chart, report and map mode \u2014 pooling them would mix unrelated measurements into one distribution. Each wafer's own view is unaffected. Load one test program at a time to see them."}),s.length>0&&e.push({code:"test-limit-conflict",severity:"warning",message:`${s.length} ${s.length===1?"test is":"tests are"} held to different spec limits in different wafers. Affected: `+b(s)+". The measured values are still comparable, so distributions include them, but capability (Cp/Cpk/Pp/Ppk), spec yield and the limit lines are withheld for these tests \u2014 there is no single spec to judge the merged population against."}),e}
1
+ const H=1e-6;function x(n,t){if(n===t)return!0;if(!Number.isFinite(n)||!Number.isFinite(t))return!1;const i=Math.max(Math.abs(n),Math.abs(t));return Math.abs(n-t)<=1e-6*i}function T(n){const t=n.name?.trim();return t||void 0}function I(n){const t=n.unit?.trim();return t||void 0}function S(n){return n.toLowerCase()}function h(n,t){const i=new Set,r=[];for(const l of n){const s=t(l);i.has(s)||(i.add(s),r.push(l))}return r}function v(n){const t=[];for(const i of n)t.some(r=>x(r,i))||t.push(i);return t}function N(n){return`${n.testNumber} (${n.values.join(" vs ")})`}function y(n,t=5){const i=n.slice(0,t).map(N).join(", "),r=n.length-Math.min(t,n.length);return r>0?`${i}, and ${r} more`:i}export function mergeTestDefs(n){const t=new Map,i=[];for(const s of n)for(const o of s?.testDefs??[]){const u=o.testNumber;let d=t.get(u);d||(d=[],t.set(u,d),i.push(u)),d.push(o)}const r=[],l=[];for(const s of i){const o=t.get(s),u=h(o.map(T).filter(e=>e!==void 0),S),d=h(o.map(I).filter(e=>e!==void 0),e=>e),g=h(o.map(e=>e.testType).filter(e=>e!==void 0),e=>e),$=u.length>1?{testNumber:s,kind:"name",excluded:!0,values:u}:d.length>1?{testNumber:s,kind:"unit",excluded:!0,values:d}:g.length>1?{testNumber:s,kind:"testType",excluded:!0,values:g}:void 0;if($){l.push($);continue}const f=v(o.map(e=>e.limitLow).filter(e=>e!==void 0)),a=v(o.map(e=>e.limitHigh).filter(e=>e!==void 0)),p=h(o.filter(e=>e.limitLow!==void 0).map(e=>e.limitLowInclusive!==!1),e=>String(e)),b=h(o.filter(e=>e.limitHigh!==void 0).map(e=>e.limitHighInclusive!==!1),e=>String(e)),c=f.length>1||a.length>1||p.length>1||b.length>1;c&&l.push({testNumber:s,kind:"limits",excluded:!1,values:f.length>1?f.map(e=>String(e)):a.length>1?a.map(e=>String(e)):p.length>1?p.map(e=>`${e?"\u2265":">"} ${f[0]}`):b.map(e=>`${e?"\u2264":"<"} ${a[0]}`)});const m=v(o.map(e=>e.specLow).filter(e=>e!==void 0)),w=v(o.map(e=>e.specHigh).filter(e=>e!==void 0)),L=m.length>1||w.length>1;L&&l.push({testNumber:s,kind:"specLimits",excluded:!1,values:(m.length>1?m:w).map(e=>String(e))}),r.push({testNumber:s,name:u[0]??"",...d[0]!==void 0?{unit:d[0]}:{},...g[0]!==void 0?{testType:g[0]}:{},...o.find(e=>e.logScale!==void 0)?{logScale:o.find(e=>e.logScale!==void 0).logScale}:{},...!c&&f[0]!==void 0?{limitLow:f[0]}:{},...!c&&a[0]!==void 0?{limitHigh:a[0]}:{},...!c&&p[0]===!1?{limitLowInclusive:!1}:{},...!c&&b[0]===!1?{limitHighInclusive:!1}:{},...!L&&m[0]!==void 0?{specLow:m[0]}:{},...!L&&w[0]!==void 0?{specHigh:w[0]}:{},...o.some(e=>e.derived)?{derived:!0}:{},...o.find(e=>e.expression!==void 0)?{expression:o.find(e=>e.expression!==void 0).expression}:{}})}return{defs:r,conflicts:l,warnings:k(l)}}function k(n){const t=[],i=n.filter(s=>s.excluded),r=n.filter(s=>s.kind==="limits"),l=n.filter(s=>s.kind==="specLimits");return i.length>0&&t.push({code:"test-def-collision",severity:"error",message:`${i.length} test ${i.length===1?"number describes":"numbers describe"} a different measurement in different wafers, so the same number cannot identify one test across this population. Affected: `+y(i)+". These tests are withheld from every cross-wafer chart, report and map mode \u2014 pooling them would mix unrelated measurements into one distribution. Each wafer's own view is unaffected. Load one test program at a time to see them."}),r.length>0&&t.push({code:"test-limit-conflict",severity:"warning",message:`${r.length} ${r.length===1?"test is":"tests are"} held to different test limits in different wafers. Affected: `+y(r)+". The measured values are still comparable, so distributions include them, but the limit yield, the limit lines, and capability measured against the test limits are withheld for these tests \u2014 there is no single pair of limits to judge the merged population against."}),l.length>0&&t.push({code:"test-limit-conflict",severity:"warning",message:`${l.length} ${l.length===1?"test has":"tests have"} different spec limits in different wafers. Affected: `+y(l)+". Their spec limits are withheld for the merged population, so capability uses the test limits where they agree."}),t}
@@ -1,41 +1,21 @@
1
- import{describeWaferPopulation as b,populationLabel as h}from"./population.js";import{formatFindingDelta as g,formatFindingCoverage as v,formatFindingTooltip as y,buildMetadataRows as d,renderDefinitionList as p,renderSection as c,renderSeverityBadge as R,reportStyles as $}from"./reportHtml.js";import{escHtml as o}from"../core/utils.js";import{buildFindingsNarrative as F}from"./findingsNarrative.js";import{plainBinTerms as f}from"../renderer/fmt.js";import{visibleFindings as S}from"./filterFindings.js";function T(e,n){if(e.level==="lot"){const a=e,l=a.perWafer.map(r=>r.summary.wafer?.wafer??r.summary.wafer?.waferId).filter(r=>r!=null).map(String).join(", ");return p([...d(a.perWafer.map(r=>({metadata:r.summary.wafer}))),...l?[{label:"Wafers",value:l}]:[],{label:"Wafer count",value:String(a.stats.waferCount)},{label:"Generated",value:n}])}const t=e,i=t.stats.yieldPercent!==null?`${t.stats.yieldPercent.toFixed(1)}%`:"N/A";return p([...d([{metadata:t.wafer}]),{label:"Total dies",value:String(t.stats.totalDies)},{label:"Analysed dies",value:String(t.stats.analyzedDies)},{label:"Yield",value:i},{label:"Generated",value:n}])}function H(e,n){return e.length?e.map(t=>`<tr title="${o(y(t))}">
2
- <td class="tight">${R(t.severity)}</td>
3
- <td class="tight">${o(t.comparison.left)}</td>
4
- <td>${o(f(t.variable.label))}</td>
5
- <td class="numeric">${o(g(t))}</td>
6
- <td class="numeric">${o(v(t,n))}</td>
7
- </tr>`).join(`
8
- `):'<tr><td colspan="5" class="no-data">No significant findings</td></tr>'}function L(e,n){return`<table class="report-table findings-table compact">
9
- <thead>
10
- <tr>
11
- <th>Severity</th>
12
- <th>Region</th>
13
- <th>Metric</th>
14
- <th class="numeric">Delta</th>
15
- <th class="numeric">${n!==void 0?"Wafers":"N (region/rest)"}</th>
16
- </tr>
17
- </thead>
18
- <tbody>
19
- ${H(e,n)}
20
- </tbody>
21
- </table>`}export function renderFindingsReportHtml(e,n={}){const t=e.level==="lot"?b(e.perWafer.map(w=>w.summary.wafer)):void 0,i=n.title??(t?t.lotId!==void 0?`Lot ${t.lotId} Findings Report`:`Findings Report \u2014 ${h(t)}`:"Wafer Findings Report"),a=new Date().toLocaleString(),l=S(e.findings),r=e.level==="lot"?e.stats.waferCount:void 0,s=f(F(l)??""),u=s?`<p class="findings-narrative">${o(s)}</p>
22
- `:"",m=[c("Summary",T(e,a)),c("Findings",u+L(l,r))].join(`
1
+ import{describeWaferPopulation as w,populationLabel as b}from"./population.js";import{findingsTableHtml as g,buildMetadataRows as d,renderDefinitionList as p,renderSection as f,reportStyles as h}from"./reportHtml.js";import{escHtml as l}from"../core/utils.js";import{buildFindingsNarrative as v}from"./findingsNarrative.js";import{plainBinTerms as R}from"../renderer/fmt.js";import{visibleFindings as y}from"./filterFindings.js";function F(e,o){if(e.level==="lot"){const r=e,a=r.perWafer.map(n=>n.summary.wafer?.wafer??n.summary.wafer?.waferId).filter(n=>n!=null).map(String).join(", ");return p([...d(r.perWafer.map(n=>({metadata:n.summary.wafer}))),...a?[{label:"Wafers",value:a}]:[],{label:"Wafer count",value:String(r.stats.waferCount)},{label:"Generated",value:o}])}const t=e,i=t.stats.yieldPercent!==null?`${t.stats.yieldPercent.toFixed(1)}%`:"N/A";return p([...d([{metadata:t.wafer}]),{label:"Total dies",value:String(t.stats.totalDies)},{label:"Analysed dies",value:String(t.stats.analyzedDies)},{label:"Yield",value:i},{label:"Generated",value:o}])}export function renderFindingsReportHtml(e,o={}){const t=e.level==="lot"?w(e.perWafer.map(m=>m.summary.wafer)):void 0,i=o.title??(t?t.lotId!==void 0?`Lot ${t.lotId} Findings Report`:`Findings Report \u2014 ${b(t)}`:"Wafer Findings Report"),r=new Date().toLocaleString(),a=y(e.findings),n=e.level==="lot"?e.stats.waferCount:void 0,s=R(v(a)??""),c=s?`<p class="findings-narrative">${l(s)}</p>
2
+ `:"",u=[f("Summary",F(e,r)),f("Findings",c+g(a,n))].join(`
23
3
  `);return`<!DOCTYPE html>
24
4
  <html lang="en">
25
5
  <head>
26
6
  <meta charset="UTF-8">
27
- <title>${o(i)}</title>
7
+ <title>${l(i)}</title>
28
8
  <style>
29
- ${$()}
9
+ ${h()}
30
10
  </style>
31
11
  </head>
32
12
  <body>
33
13
  <main class="report">
34
14
  <header class="report-header">
35
- <h1>${o(i)}</h1>
36
- <p class="report-subtitle">Generated ${o(a)}</p>
15
+ <h1>${l(i)}</h1>
16
+ <p class="report-subtitle">Generated ${l(r)}</p>
37
17
  </header>
38
- ${m}
18
+ ${u}
39
19
  </main>
40
20
  </body>
41
- </html>`}export function openHtmlReport(e){if(typeof window.__openHtmlReport=="function"){window.__openHtmlReport(e);return}const n=URL.createObjectURL(new Blob([e],{type:"text/html"})),t=window.open(n,"_blank");setTimeout(()=>URL.revokeObjectURL(n),3e4),t||console.warn("wmap: window.open() returned null, so the report could not be shown. If this host cannot use window.open (e.g. Tauri, or popups are blocked), call setReportOpener() to supply your own opener.")}export function setReportOpener(e){window.__openHtmlReport=e}
21
+ </html>`}export function openHtmlReport(e){if(typeof window.__openHtmlReport=="function"){window.__openHtmlReport(e);return}const o=URL.createObjectURL(new Blob([e],{type:"text/html"})),t=window.open(o,"_blank");setTimeout(()=>URL.revokeObjectURL(o),3e4),t||console.warn("wmap: window.open() returned null, so the report could not be shown. If this host cannot use window.open (e.g. Tauri, or popups are blocked), call setReportOpener() to supply your own opener.")}export function setReportOpener(e){window.__openHtmlReport=e}