@wafertools/wafermap 0.30.3 → 0.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +9 -19
- package/CHANGELOG.md +140 -0
- package/README.md +2 -2
- package/dist/packages/canvas-adapter/charts/boxplot.js +1 -1
- package/dist/packages/canvas-adapter/charts/capability.js +1 -1
- package/dist/packages/canvas-adapter/charts/chartShell.d.ts +89 -6
- package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
- package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
- package/dist/packages/canvas-adapter/charts/scatter.d.ts +16 -2
- package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
- package/dist/packages/canvas-adapter/charts/sweep.js +1 -1
- package/dist/packages/canvas-adapter/charts/testPassRate.js +1 -1
- package/dist/packages/canvas-adapter/charts/trend.js +1 -1
- package/dist/packages/canvas-adapter/dieList.d.ts +1 -1
- package/dist/packages/canvas-adapter/exportName.d.ts +1 -12
- package/dist/packages/canvas-adapter/exportName.js +1 -1
- package/dist/packages/canvas-adapter/index.d.ts +2 -3
- package/dist/packages/canvas-adapter/index.js +1 -1
- package/dist/packages/canvas-adapter/insightsTab.d.ts +11 -1
- package/dist/packages/canvas-adapter/insightsTab.js +1 -1
- package/dist/packages/canvas-adapter/maplessSummary.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +4 -7
- package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferMap.d.ts +15 -30
- package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
- package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
- package/dist/packages/canvas-adapter/toCanvas.js +1 -1
- package/dist/packages/canvas-adapter/toolbar.js +1 -1
- package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
- package/dist/packages/canvas-adapter/userGuideHtml.js +49 -43
- package/dist/packages/canvas-adapter/version.d.ts +2 -2
- package/dist/packages/canvas-adapter/version.js +1 -1
- package/dist/packages/canvas-adapter/warnings.js +1 -1
- package/dist/packages/core/aggregates.d.ts +0 -6
- package/dist/packages/core/aggregates.js +1 -1
- package/dist/packages/core/dies.d.ts +1 -3
- package/dist/packages/core/dies.js +1 -1
- package/dist/packages/core/index.d.ts +8 -10
- package/dist/packages/core/index.js +1 -1
- package/dist/packages/core/stdf.d.ts +9 -0
- package/dist/packages/core/stdf.js +1 -0
- package/dist/packages/core/transforms.d.ts +0 -23
- package/dist/packages/core/transforms.js +1 -1
- package/dist/packages/renderer/binColors.js +1 -1
- package/dist/packages/renderer/buildView.d.ts +1 -9
- package/dist/packages/renderer/buildView.js +1 -1
- package/dist/packages/renderer/buildWaferMap.d.ts +54 -34
- package/dist/packages/renderer/buildWaferMap.js +1 -1
- package/dist/packages/renderer/colorMap.d.ts +0 -2
- package/dist/packages/renderer/colorMap.js +1 -1
- package/dist/packages/renderer/deprecate.d.ts +1 -1
- package/dist/packages/renderer/deprecate.js +1 -1
- package/dist/packages/renderer/derivedTests/apply.js +1 -1
- package/dist/packages/renderer/derivedTests/parser.js +1 -1
- package/dist/packages/renderer/index.d.ts +6 -5
- package/dist/packages/renderer/index.js +1 -1
- package/dist/packages/renderer/spec.d.ts +17 -0
- package/dist/packages/renderer/spec.js +1 -1
- package/dist/packages/stats/analyzeWaferMap.js +1 -1
- package/dist/packages/stats/capability.d.ts +4 -2
- package/dist/packages/stats/capability.js +1 -1
- package/dist/packages/stats/clusterDetection.d.ts +2 -1
- package/dist/packages/stats/correlation.d.ts +1 -1
- package/dist/packages/stats/facets.d.ts +1 -1
- package/dist/packages/stats/index.d.ts +6 -16
- package/dist/packages/stats/index.js +1 -1
- package/dist/packages/stats/mergeTestDefs.d.ts +1 -1
- package/dist/packages/stats/mergeTestDefs.js +1 -1
- package/dist/packages/stats/patternClassification.d.ts +2 -1
- package/dist/packages/stats/regions.d.ts +2 -1
- package/dist/packages/stats/renderFindingsReport.d.ts +0 -4
- package/dist/packages/stats/renderFindingsReport.js +1 -21
- package/dist/packages/stats/renderSummaryReport.js +11 -11
- package/dist/packages/stats/sweep.d.ts +8 -0
- package/dist/packages/stats/sweep.js +1 -1
- package/dist/packages/stats/types.d.ts +9 -3
- package/package.json +2 -2
- package/dist/packages/canvas-adapter/deprecated.d.ts +0 -7
- package/dist/packages/canvas-adapter/deprecated.js +0 -1
- package/dist/packages/core/deprecated.d.ts +0 -60
- package/dist/packages/core/deprecated.js +0 -1
- package/dist/packages/renderer/deprecated.d.ts +0 -45
- package/dist/packages/renderer/deprecated.js +0 -1
- package/dist/packages/stats/deprecated.d.ts +0 -85
- package/dist/packages/stats/deprecated.js +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{normalizeInput as Le}from"./normalizeInput.js";import{computeCapability as Ee,computeTestFlagYield as Ke,computeRegionYield as Te}from"./summaryFigures.js";import{isYieldEligibleDie as ae,getDieKey as T,isPositionedDie as be}from"../core/dies.js";import{getTestPassStatus as ee,isParametricTest as q}from"../renderer/buildWaferMap.js";import{markedTestLabel as x,derivedFields as te}from"../renderer/testLabel.js";import{buildQuadrantRegions as xe,buildReticlePositionRegions as je,buildRingRegions as Ae,buildSectorRegions as Oe,buildTestSiteRegions as He,sectorCompassNames as ye,areQuadrantsAdjacent as qe,parseRegionKey as I}from"./regions.js";import{buildClusterFindings as Ie}from"./clusterDetection.js";import{classifyPattern as Ye}from"./patternClassification.js";import{normalCdf as Se}from"./math.js";import{quantile as re}from"./math.js";import{mean as ne,clamp01 as we}from"../core/utils.js";const _e={significanceLevel:{min:1e-6,max:1},minimumEffectSize:{min:0,max:1},minimumRelativeEffect:{min:0,max:Number.MAX_SAFE_INTEGER}},ve=[4,8,16,32];function We(s){const a=Object.fromEntries(Object.entries(s).filter(([,t])=>t!==void 0)),i={...j,...a},o=[],e=[];for(const t of["significanceLevel","minimumEffectSize","minimumRelativeEffect"]){const{min:f,max:n}=_e[t],u=i[t];if(typeof u!="number"||!Number.isFinite(u)){e.push(`${t}=${String(u)} is not a finite number (using ${j[t]})`),i[t]=j[t];continue}const l=Math.min(n,Math.max(f,u));l!==u&&(e.push(`${t}=${u} is outside ${f}\u2013${n} (using ${l})`),i[t]=l)}if(ve.includes(i.sectorCount)||(e.push(`sectorCount=${i.sectorCount} is not one of ${ve.join(", ")} (using ${j.sectorCount})`),i.sectorCount=j.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 j={ringCount:4,passBins:[1],significanceLevel:.05,minimumEffectSize:.2,minimumRelativeEffect:1,minimumSampleSize:5,enableTestValueAnalysis:!1,computePerTestStats:!1,sectorCount:8,minimumClusterSize:5};function Qe(s,a){const i={...s};return i.minimumClusterSize=Math.max(5,Math.round(a*.003)),i}function Ue(s){return ae(s)?s.hbin!==void 0||s.sbin!==void 0||s.testValues!==void 0&&Object.keys(s.testValues).length>0:!1}function Ge(s,a,i){if(!s||a)return;const o=(i??[]).filter(e=>q(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 Xe(s,a,i){const o=new Set,e=new Set,c=new Set,r=new Map,t=new Map;for(const f of s){if(f.testValues)for(const n of Object.keys(f.testValues))o.add(Number(n));if(f.testPass)for(const n of Object.keys(f.testPass))o.add(Number(n));f.hbin!==void 0&&e.add(f.hbin),f.sbin!==void 0&&c.add(f.sbin),ae(f)&&(f.hbin!==void 0&&r.set(f.hbin,(r.get(f.hbin)??0)+1),f.sbin!==void 0&&t.set(f.sbin,(t.get(f.sbin)??0)+1))}return{totalDies:s.length,analyzedDies:a,excludedDies:s.length-a,yieldPercent:i,testsConsidered:[...o].sort((f,n)=>f-n),hardBinsConsidered:[...e].sort((f,n)=>f-n),softBinsConsidered:[...c].sort((f,n)=>f-n),...r.size?{hardBinCounts:Object.fromEntries(r)}:{},...t.size?{softBinCounts:Object.fromEntries(t)}:{}}}function Je(s,a){if(!a?.length)return;const i=a.filter(e=>q(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,f=0,n=0;for(const u of s){if(u.partial||u.edgeExcluded)continue;const l=u.testValues?.[c];l!==void 0&&(n++,e.limitLow!==void 0&&l<e.limitLow?t++:e.limitHigh!==void 0&&l>e.limitHigh?f++:r++)}o.push({testNumber:c,label:e.name,passDies:r,failLowDies:t,failHighDies:f,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=>!q(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=ee(n,c,e);u!==void 0&&(u?r++:t++)}const f=r+t;o.push({testNumber:c,label:x(e,c),...te(e),passDies:r,failDies:t,totalDies:f,passRatePercent:f>0?r/f*100:null})}return o.length?o:void 0}function Ze(s,a,i,o){const e=[];for(const c of a){const r=[];for(const l of s){if(l.partial||l.edgeExcluded)continue;const h=l.testValues?.[c];h!==void 0&&Number.isFinite(h)&&r.push(h)}if(r.length<o)continue;const t=ne(r),f=Math.sqrt(ge(r,t)),n=Float64Array.from(r);n.sort();const u=i?.find(l=>l.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:f,median:re(n,.5),q1:re(n,.25),q3:re(n,.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 we(2*(1-Se(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,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],f=t.stats.pValue??1,n=Math.min(1,Math.min(c,f*e/(r+1)));t.stats.adjustedPValue=n,c=n}}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 le(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 $e(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 ce(s,a){return a==="quadrant"&&!s.startsWith("Quadrant")?`quadrant ${s}`:s}function De(s){return/^(Rings|Sectors|Quadrants)\b/.test(s)?"have":"has"}function ke(s,a,i,o){const e=ce(s,o),c=(Math.abs(i)*100).toFixed(1);return`${e} ${De(e)} ${a} occurrence ${c} percentage points ${i>0?"higher":"lower"} than ${se(o)}`}function ze(s,a,i,o){const e=ce(s,o),c=(Math.abs(i)*100).toFixed(1);return`${e} ${De(e)} ${a} pass rate ${c} percentage points ${i>0?"higher":"lower"} than ${se(o)}`}function Me(s,a,i,o,e,c){const r=ce(s,e),t=se(e),f=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}% ${f} than ${t}`}const n=c?` ${c}`:"";return`${r} mean ${a} is ${Math.abs(i).toPrecision(3)}${n} ${f} than ${t}`}function Re(s,a,i){const o=a?.find(e=>e.bin===s);return o?.name?`${i} ${s} (${o.name})`:`${i} ${s}`}function et(s,a){const i=a?.find(o=>o.testNumber===s);return{label:x(i,s),unit:i?.unit,...te(i)}}function ue(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 de(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=ue(s,a),r=new Map,t=new Map;for(const[n,u]of c){let l=0,h=0;for(const g of u)g.hbin!==void 0&&(h++,e.has(g.hbin)&&l++);r.set(n,l),t.set(n,h)}const f=[];for(const n of a){const u=t.get(n.key)??0,l=r.get(n.key)??0;let h=0,g=0;for(const[k,m]of t)k!==n.key&&(h+=m,g+=r.get(k)??0);if(u<o.minimumSampleSize||h<o.minimumSampleSize)continue;const p=l/u,b=g/h,y=p-b,M=A(l,u,g,h);f.push({id:`yield:${n.key}`,level:"wafer",severity:"info",variable:{kind:"yield",label:"Yield"},comparison:{family:n.family,left:n.label,right:Y(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:M,sampleSizeLeft:u,sampleSizeRight:h},summary:$e(n.label,y,n.family),highlight:{kind:"region",regionFamily:n.family,regionKeys:[n.key],dieKeys:[...n.dieKeys]}})}return de(f,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),f=ue(s,a),n=new Map,u=new Map;for(const[g,p]of f){const b=new Map;for(const y of p){const M=r(y);M!==void 0&&b.set(M,(b.get(M)??0)+1)}n.set(g,b),u.set(g,p.length)}const l=[],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 M=new Map;for(const[k,m]of n)if(k!==g.key){y+=u.get(k)??0;for(const[S,$]of m)M.set(S,(M.get(S)??0)+$)}if(!(p<c.minimumSampleSize||y<c.minimumSampleSize))for(const k of t){const m=b.get(k)??0,S=M.get(k)??0,$=m/p,w=S/y,v=$-w,D=A(m,p,S,y),z=Re(k,o,h);l.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:v===0?"different":v>0?"higher":"lower",absoluteDelta:v,relativeDelta:w===0?void 0:v/w,effectSize:v},stats:{method:"two-proportion-z",pValue:D,sampleSizeLeft:p,sampleSizeRight:y},summary:ke(g.label,z,v,g.family),highlight:{kind:"bin",bin:k,regionKeys:[g.key],dieKeys:[...g.dieKeys]}})}}return de(l,c)}function Q(s,a,i,o){const e=(i??[]).filter(u=>!q(u));if(!e.length)return[];const c=e.map(u=>u.testNumber),r=ue(s,a),t=new Map,f=new Map;for(const[u,l]of r){const h=new Uint32Array(e.length),g=new Uint32Array(e.length);for(const p of l)for(let b=0;b<e.length;b++){const y=ee(p,c[b],e[b]);y!==void 0&&(g[b]++,y&&h[b]++)}t.set(u,h),f.set(u,g)}const n=[];for(const u of a){const l=t.get(u.key),h=f.get(u.key);for(let g=0;g<e.length;g++){let p=0,b=0;for(const[v,D]of f)v!==u.key&&(b+=D[g],p+=t.get(v)[g]);const y=h[g],M=l[g];if(y<o.minimumSampleSize||b<o.minimumSampleSize)continue;const k=M/y,m=p/b,S=k-m,$=A(M,y,p,b),w=c[g];n.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:m===0?void 0:S/m,effectSize:S},stats:{method:"two-proportion-z",pValue:$,sampleSizeLeft:y,sampleSizeRight:b},summary:ze(u.label,x(e[g],w),S,u.family),highlight:{kind:"region",regionFamily:u.family,regionKeys:[u.key],dieKeys:[...u.dieKeys]}})}}return de(n,o)}function ge(s,a){return s.length<2?0:s.reduce((i,o)=>i+(o-a)**2,0)/(s.length-1)}function Be(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 f=t/r,n=Math.sqrt(Math.max(0,(i+c)/2)),u=n===0?t:t/n;return{pValue:we(2*(1-Se(Math.abs(f)))),effectSize:u,delta:t}}function tt(s,a){const i=ne(s),o=ne(a);return Be(s.length,i,ge(s,i),a.length,o,ge(a,o))}const Fe=250;function Ne(s,a,i){const o=r=>r.filter(t=>q(i?.find(f=>f.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 f=Number(t);if(!e.has(f)&&(e.add(f),e.size>Fe)){c=!0;break e}}if(c){const r=`Test-value analysis was skipped: more than ${Fe} 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=Ne(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,f=[],n=[];for(const p of s){const b=r.get(T(p));b!==void 0&&(f.push(p),n.push(b))}const u=[],l=new Float64Array(t),h=new Float64Array(t),g=new Float64Array(t);for(const p of c){l.fill(0),h.fill(0),g.fill(0);let b=0,y=0,M=0,k;for(let m=0;m<f.length;m++){const $=f[m].testValues?.[p];if($===void 0)continue;k===void 0&&(k=$);const w=$-k,v=n[m];l[v]+=1,h[v]+=w,g[v]+=w*w,b+=1,y+=w,M+=w*w}if(k!==void 0)for(let m=0;m<t;m++){const S=l[m],$=b-S;if(S<o.minimumSampleSize||$<o.minimumSampleSize)continue;const w=h[m]/S,D=(y-h[m])/$,z=w+k,R=D+k,F=S>1?Math.max(0,g[m]-S*w*w)/(S-1):0,d=$>1?Math.max(0,M-g[m]-$*D*D)/($-1):0,{pValue:N,effectSize:B,delta:V}=Be(S,z,F,$,R,d),P=a[m],{label:H,unit:G,...X}=et(p,i),J=R!==0?V/Math.abs(R):void 0;u.push({id:`test:${p}:${P.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:p,label:H,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:B},stats:{method:"welch-z-approx",pValue:N,sampleSizeLeft:S,sampleSizeRight:$},summary:Me(P.label,H,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:le(p.stats.adjustedPValue??p.stats.pValue??1,p.effect.effectSize??0)}))}}function nt(s,a,i,o){if(!i?.length)return[];const e=i.filter(t=>q(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 f=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 l=[];for(const h of n){const g=u.get(h.key),p=[];for(const[z,R]of u)if(z!==h.key)for(const F of R)p.push(F);const b=z=>z.testValues?.[f]!==void 0,y=z=>{const R=z.testValues?.[f];return R===void 0?!1:t.limitLow!==void 0&&R<t.limitLow||t.limitHigh!==void 0&&R>t.limitHigh},M=g.filter(b),k=p.filter(b);if(M.length<o.minimumSampleSize||k.length<o.minimumSampleSize)continue;const m=M.filter(y).length,S=k.filter(y).length,$=m/M.length,w=S/k.length,v=$-w,D=A(m,M.length,S,k.length);l.push({id:`specLimit:${f}:${h.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:f,label:x(t,f),unit:t.unit,...te(t)},comparison:{family:h.family,left:h.label,right:Y(h.family)},effect:{direction:v===0?"different":v>0?"higher":"lower",absoluteDelta:v,relativeDelta:w===0?void 0:v/w,effectSize:v},stats:{method:"two-proportion-z",pValue:D,sampleSizeLeft:M.length,sampleSizeRight:k.length},summary:`${h.label} spec-fail rate for ${x(t,f)} is ${(Math.abs(v)*100).toFixed(1)} pp ${v>0?"higher":"lower"} than the rest of the wafer`,highlight:{kind:"region",regionFamily:h.family,regionKeys:[h.key],dieKeys:[...h.dieKeys]}})}fe(l),c.push(...l.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:le(h.stats.adjustedPValue??h.stats.pValue??1,h.effect.effectSize??0)})))}}return c}function O(s){return s.highlight.regionKeys??[]}function it(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"?ye(a).indexOf(o.sector??""):0}function st(s,a,i){if(a==="quadrant"){const r=new Map;for(const u of s){const l=I(O(u)[0]??"").quadrant;l&&r.set(l,u)}const t=[...r.keys()],f=new Set,n=[];for(const u of t){if(f.has(u))continue;const l=[],h=[u];for(f.add(u);h.length;){const g=h.pop();l.push(g);for(const p of t)!f.has(p)&&qe(g,p)&&(f.add(p),h.push(p))}n.push(l.map(g=>r.get(g)))}return n}const o=[...s].sort((r,t)=>_(r,i)-_(t,i)),e=[];let c=[];for(const r of o){if(c.length===0){c.push(r);continue}const t=_(c[c.length-1],i);_(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=ye(i).length,f=_(e[0][0],i),n=_(e[e.length-1][e[e.length-1].length-1],i);if(f===0&&n===t-1){const u=e.pop();e[0]=[...u,...e[0]]}}return e}function ot(s,a){const i=s.map(r=>I(O(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 at(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 rt(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 ft(s,a){const i=s[0],o=i.comparison.family,e=i.variable.kind,c=me(s.flatMap(m=>m.highlight.dieKeys??[])),r=me(s.flatMap(O)),t=new Set(c),f=o==="ring"?ot(s,a.ringCount):o==="sector"?at(s,a.sectorCount):rt(s),n=e==="softBin"?a.softEligibleDies:e==="test"?a.testDies:a.eligibleDies,u=n.filter(m=>t.has(T(m))),l=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],$=u.map(S).filter(d=>d!==void 0),w=l.map(S).filter(d=>d!==void 0),{pValue:v,effectSize:D,delta:z}=tt($,w),R=ne(w),F=R!==0?z/Math.abs(R):void 0;h={direction:z===0?"different":z>0?"higher":"lower",absoluteDelta:z,relativeDelta:F,effectSize:D},g={method:"welch-z-approx",pValue:v,sampleSizeLeft:$.length,sampleSizeRight:w.length},p=le(v,D),b=Me(f,i.variable.label,z,F,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),$=l.filter(B=>B.hbin!==void 0),w=S.length,v=$.length,D=S.filter(B=>m.has(B.hbin)).length,z=$.filter(B=>m.has(B.hbin)).length,R=w>0?D/w:0,F=v>0?z/v:0,d=R-F,N=A(D,w,z,v);h={direction:d===0?"different":d>0?"higher":"lower",absoluteDelta:d,relativeDelta:F===0?void 0:d/F,effectSize:d},g={method:"two-proportion-z",pValue:N,sampleSizeLeft:w,sampleSizeRight:v},p=ie(N,d,h.relativeDelta),b=$e(f,d,o),y="yield"}else if(e==="functionalTest"){const m=i.variable.index,S=a.testDefs?.find(N=>N.testNumber===m);let $=0,w=0,v=0,D=0;for(const N of u){const B=ee(N,m,S);B!==void 0&&(w++,B&&$++)}for(const N of l){const B=ee(N,m,S);B!==void 0&&(D++,B&&v++)}const z=w>0?$/w:0,R=D>0?v/D:0,F=z-R,d=A($,w,v,D);h={direction:F===0?"different":F>0?"higher":"lower",absoluteDelta:F,relativeDelta:R===0?void 0:F/R,effectSize:F},g={method:"two-proportion-z",pValue:d,sampleSizeLeft:w,sampleSizeRight:D},p=ie(d,F,h.relativeDelta),b=ze(f,x(S,m),F,o),y=`functional:${m}`}else{const m=i.variable.bin,S=d=>e==="softBin"?d.sbin:d.hbin,$=u.filter(d=>S(d)===m).length,w=l.filter(d=>S(d)===m).length,v=$/u.length,D=w/l.length,z=v-D,R=A($,u.length,w,l.length);h={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:R,sampleSizeLeft:u.length,sampleSizeRight:l.length},p=ie(R,z,h.relativeDelta);const F=e==="softBin"?a.sbinDefs:a.hbinDefs;b=ke(f,Re(m,F,e==="softBin"?"SBin":"HBin"),z,o),y=`${e}:${m}`}const M=me(r.map(m=>m.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:f,right:Y(o)},effect:h,stats:g,summary:b,highlight:k,relatedIds:s.map(m=>m.id)}}function lt(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=it(r),f=e.get(t)??[];f.push(r),e.set(t,f)}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 f of st(r,t,a.sectorCount))c.push(f.length===1?f[0]:ft(f,a))}return[...o,...c]}function ct(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 f=!0;for(const n of c)if(!t.has(n)){f=!1;break}f&&o.add(`${e}:${r}`)}return o}function ut(s){return`${s.comparison.family}\0${s.comparison.left}`}function dt(s,a,i){const o=(t,f)=>{t.absorbedIds=[...t.absorbedIds??[],f.id]},e=new Map;for(const t of s){const f=ut(t);(e.get(f)??e.set(f,[]).get(f)).push(t)}const c=ct(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 l=n.variable.label.replace(/^HBin\s+/,""),h=u.variable.label.replace(/^SBin\s+/,""),g=l===h?`HBin and SBin ${l} (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 f=t.find(n=>n.variable.kind==="yield");if(f){for(const n of t)if(!(n===f||r.has(n.id)||!((n.variable.kind==="hardBin"||n.variable.kind==="softBin")&&n.variable.bin===i[0]))){o(f,n),r.add(n.id);for(const l of n.absorbedIds??[])f.absorbedIds=[...f.absorbedIds??[],l]}}}}export function analyzeWaferMap(s,a={}){const{resolved:i,warnings:o}=We(a),e=Le(s),c={...i,passBins:e.passBins??j.passBins,ringCount:e.ringCount??j.ringCount},r=e.isLotStack,t=e.aggrMethod,f=!r||t==="mode"||t==="countBin"||t==="percent",n=e.dies.filter(d=>Ue(d)),u=n.filter(be),l=Qe(c,n.length),h=e.dies.filter(d=>ae(d)),g=h.filter(be),p=Ae(g,e.wafer,l.ringCount),b=xe(g,e.wafer,l.ringCount),y=je(g,e.reticleConfig),M=He(h,!1),k=Oe(g,e.wafer,l.sectorCount),m=[];f&&m.push(...W(n,p,l.passBins,l),...W(n,b,l.passBins,l),...W(n,y,l.passBins,l),...W(n,M,l.passBins,l),...W(n,k,l.passBins,l)),f&&m.push(...L(n,p,"hard",e.hbinDefs,"hardBin",l),...L(n,b,"hard",e.hbinDefs,"hardBin",l),...L(n,y,"hard",e.hbinDefs,"hardBin",l),...L(n,M,"hard",e.hbinDefs,"hardBin",l),...L(n,k,"hard",e.hbinDefs,"hardBin",l));{const d=n.filter(N=>N.sbin!==void 0);m.push(...L(d,p,"soft",e.sbinDefs,"softBin",l),...L(d,b,"soft",e.sbinDefs,"softBin",l),...L(d,y,"soft",e.sbinDefs,"softBin",l),...L(d,M,"soft",e.sbinDefs,"softBin",l),...L(d,k,"soft",e.sbinDefs,"softBin",l))}const S=[...o];let $;if(l.enableTestValueAnalysis){const d=U(n,p,e.testDefs,l),N=U(n,b,e.testDefs,l),B=U(n,y,e.testDefs,l),V=U(n,M,e.testDefs,l),P=U(n,k,e.testDefs,l);m.push(...d.findings,...N.findings,...B.findings,...V.findings,...P.findings),d.warning&&S.push(d.warning),$=d.activeTestNumbers,m.push(...nt(n,[p,b,y,M,k],e.testDefs,l)),m.push(...Q(n,p,e.testDefs,l),...Q(n,b,e.testDefs,l),...Q(n,y,e.testDefs,l),...Q(n,M,e.testDefs,l),...Q(n,k,e.testDefs,l))}{const d=Ge(r,f,e.testDefs);(!r||f||d!==void 0)&&m.push(...Ie(u,e.wafer,{...l,isFailingDie:d}))}const w=lt(m,{eligibleDies:n,softEligibleDies:n.filter(d=>d.sbin!==void 0),testDies:n,passBins:l.passBins,ringCount:l.ringCount,sectorCount:l.sectorCount,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs});m.length=0,m.push(...w);let v;if(f){const d=Ye(u,e.wafer,{passBins:l.passBins,ringCount:l.ringCount});if(v=d??void 0,d!==null&&d.pattern!=="random"&&d.pattern!=="none"){let Ve=function(C){const E=O(C).map(K=>I(K).ring).filter(K=>K!==void 0);if(E.length===0)return!0;const he=E.some(K=>K===Ce),pe=E.some(K=>K===1);return Z==="edge-ring"||Z==="edge-local"?he:Z==="center"?pe:Z==="donut"?!he&&!pe:!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",P=d.features,H=C=>`${(C*100).toFixed(0)}%`,G=d.pattern==="edge-ring"||d.pattern==="edge-local"?`${H(P.edgeRdd)} of edge dies failing`:d.pattern==="center"||d.pattern==="donut"?`centroid at ${H(P.centroidDistNorm)} of wafer radius from centre`:d.pattern==="scratch"?`linear score ${P.linearScore.toFixed(2)}, eccentricity ${P.eccentricity.toFixed(2)}`:`${H(P.globalRdd)} of dies failing`,X=n.filter(C=>{const E=C.hbin??C.sbin;return E!==void 0&&!new Set(l.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"]},Pe=new Set(J[d.pattern]??[]),Z=d.pattern,Ce=l.ringCount,oe=[];for(const C of m){if(!Pe.has(C.comparison.family)||C.comparison.family==="ring"&&!Ve(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")}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:P.globalRdd},stats:{method:"geometry",sampleSizeLeft:X.length,sampleSizeRight:n.length},summary:`Spatial pattern: ${B.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})}}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 D=Xe(e.dies,n.length,e.yield.yieldPercent);r&&(D.isLotStack=!0,t&&(D.aggregationMethod=t));const z=Je(e.dies,e.testDefs);z&&(D.testSpecYield=z);const R=computeFunctionalYield(e.dies,e.testDefs);if(R&&(D.functionalYield=R),v&&(D.spatialPattern=v),$===void 0&&l.computePerTestStats){const d=Ne(n,l.testNumbers,e.testDefs);d.warning&&S.push(d.warning),$=d.testNumbers}if($?.length){const d=Ze(e.dies,$,e.testDefs,l.minimumSampleSize);d&&(D.perTestStats=d);const N=Ee([{dies:e.dies}],e.testDefs,$);N&&(D.capability=N)}Object.assign(D,Ke(e.dies,e.testDefs));const F=Te([{dies:e.dies,wafer:e.wafer,passBins:l.passBins,ringCount:l.ringCount}]);return F&&(D.regionYield=F),S.length>0&&(D.warnings=S),dt(m,n,l.passBins),{level:"wafer",hasNotableFindings:m.some(d=>d.severity==="notable"||d.severity==="unusual"),findings:m,wafer:e.wafer.metadata??void 0,stats:D}}
|
|
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}}
|
|
@@ -21,6 +21,8 @@ export interface CapabilityDatum {
|
|
|
21
21
|
hasSpec: boolean;
|
|
22
22
|
lsl?: number;
|
|
23
23
|
usl?: number;
|
|
24
|
+
/** Which limits `lsl`/`usl` are — see `TestCapability.limitBasis`. */
|
|
25
|
+
limitBasis?: 'spec' | 'test';
|
|
24
26
|
mean: number;
|
|
25
27
|
/** Sample stddev, ddof=1, pooled across every die across all items. */
|
|
26
28
|
stdOverall: number;
|
|
@@ -80,8 +82,8 @@ export interface PooledTestStats {
|
|
|
80
82
|
/**
|
|
81
83
|
* Per test, how many pooled values fell outside the test's own spec limits —
|
|
82
84
|
* the spec-yield tally, counted off the sorted values this pass already held.
|
|
83
|
-
* Only tests carrying at least one limit appear. `fail`
|
|
84
|
-
*
|
|
85
|
+
* Only tests carrying at least one limit appear. `fail` is
|
|
86
|
+
* `countOutOfSpecSorted` — the same rule as the per-die `classifySpec`.
|
|
85
87
|
*
|
|
86
88
|
* This is here rather than the sorted arrays themselves because the result is
|
|
87
89
|
* memoised: a tally is a handful of numbers per test, whereas the values are
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{isYieldEligibleDie as
|
|
1
|
+
import{isYieldEligibleDie as v}from"../core/dies.js";import{drain as O}from"../core/utils.js";import{isParametricTest as B}from"../renderer/buildWaferMap.js";import{countOutOfSpecSorted as D,hasSpecLimits as H}from"../renderer/spec.js";import{testLabel as E,derivedFields as P}from"../renderer/testLabel.js";import{describeSorted as V,quantile as k}from"./math.js";const _=25e4;function z(n){if(n.specLow!==void 0&&n.specHigh!==void 0&&n.specHigh>n.specLow)return{lsl:n.specLow,usl:n.specHigh,basis:"spec"};if(n.limitLow!==void 0&&n.limitHigh!==void 0&&n.limitHigh>n.limitLow)return{lsl:n.limitLow,usl:n.limitHigh,basis:"test"}}function x(n){const s=new Map,t=new Map;for(const p of n){const e=p.testNumber;if(e===void 0||!B(p))continue;t.set(e,p);const i=z(p);i&&s.set(e,i)}return{defByTestNumber:t,specByTest:s}}function*C(n,s,t){const p=new Map,e=[];for(const l of s.keys())p.set(l,e.length),e.push(l);const i=e.length,r=new Float64Array(i),o=new Float64Array(i),m=new Float64Array(i),h=new Float64Array(i),T=new Float64Array(i),b=new Float64Array(i),w=new Float64Array(i),g=new Float64Array(i),d=t?new Array(i):[],S=i<=64,A=Math.max(500,Math.floor(_/Math.max(1,i)));let f=0;for(const l of n){b.fill(0),w.fill(0),g.fill(0);for(const a of l.dies??[]){if(++f>=A&&(f=0,yield),!v(a))continue;const y=a.testValues;if(y){if(S){for(let u=0;u<i;u++){const c=y[e[u]];c===void 0||!Number.isFinite(c)||(r[u]++,o[u]+=c,m[u]+=c*c,b[u]++,w[u]+=c,g[u]+=c*c,t&&(d[u]??=[]).push(c))}continue}for(const u in y){const c=p.get(+u);if(c===void 0)continue;const N=y[+u];N===void 0||!Number.isFinite(N)||(r[c]++,o[c]+=N,m[c]+=N*N,b[c]++,w[c]+=N,g[c]+=N*N,t&&(d[c]??=[]).push(N))}}}for(let a=0;a<i;a++){const y=b[a];if(y<2)continue;const u=w[a]/y,c=(g[a]-y*u*u)/(y-1);h[a]+=(y-1)*Math.max(0,c),T[a]+=y-1}}const M=new Map;for(let l=0;l<i;l++){if(r[l]===0)continue;const a=e[l];M.set(a,{n:r[l],sum:o[l],sumSq:m[l],withinNumerator:h[l],withinDenominator:T[l]}),t&&d[l]&&t.set(a,d[l])}return M}function I(n,s,t){return O(C(n,s,t))}function L(n,s,t,p){const{n:e,sum:i,sumSq:r,withinNumerator:o,withinDenominator:m}=p,h=i/e,T=e>=2?Math.max(0,(r-e*h*h)/(e-1)):0,b=Math.sqrt(T),w=m>0?Math.sqrt(o/m):NaN;let g=null,d=null,S=null,A=null;if(t){const{lsl:f,usl:M}=t,l=M-f;S=b>0?l/(6*b):null,A=b>0?Math.min((M-h)/(3*b),(h-f)/(3*b)):null,g=Number.isFinite(w)&&w>0?l/(6*w):null,d=Number.isFinite(w)&&w>0?Math.min((M-h)/(3*w),(h-f)/(3*w)):null}return{testNumber:n,label:E(s,n),unit:s.unit,...P(s),hasSpec:t!==void 0,lsl:t?.lsl,usl:t?.usl,...t?{limitBasis:t.basis}:{},mean:h,stdOverall:b,stdWithin:w,n:e,cp:g,cpk:d,pp:S,ppk:A}}function q(n){return n.sort((s,t)=>s.hasSpec!==t.hasSpec?s.hasSpec?-1:1:s.hasSpec?s.ppk===null&&t.ppk===null?0:s.ppk===null?1:t.ppk===null?-1:s.ppk-t.ppk:t.stdOverall-s.stdOverall)}export function buildCapabilityData(n,s){return O(buildCapabilityDataSteps(n,s))}export function*buildCapabilityDataSteps(n,s){return(yield*pooledTestStatsSteps(n,s)).capability.slice()}const F=new Map,R=4,$=new WeakMap;let j=1;function W(n,s){const t=[];for(const e of n){const i=e.dies;if(!i){t.push(0);continue}let r=$.get(i);r===void 0&&(r=j++,$.set(i,r)),t.push(r)}const p=s.map(e=>`${e.testNumber}${e.testType??"P"}${e.limitLow??""}${e.limitHigh??""}${e.specLow??""}${e.specHigh??""}${e.name??""}${e.unit??""}`);return`${t.join(",")}${p.join("")}`}export function clearPooledTestStatsCache(){F.clear()}export function*pooledTestStatsSteps(n,s){const t=W(n,s),p=F.get(t);if(p)return F.delete(t),F.set(t,p),p;const e={stats:new Map,specTally:new Map,capability:[]},{defByTestNumber:i,specByTest:r}=x(s);if(i.size===0)return e;const o=new Map,m=yield*C(n,i,o),h=new Map,T=new Map,b=[];for(const g of s){yield;const d=g.testNumber;if(d===void 0)continue;const S=m.get(d);if(!S||S.n===0||!i.has(d))continue;const A=o.get(d);o.delete(d);const f=Float64Array.from(A);f.sort(),h.set(d,V(f)),H(g)&&T.set(d,{n:f.length,fail:D(f,g)});const M=r.get(d),l=L(d,g,M,S);let a;if(M){const y=M.usl-M.lsl;a=u=>(u-M.lsl)/y}else{const y=f[0],u=f[f.length-1]-y;a=u>0?c=>(c-y)/u:()=>.5}b.push({...l,min:a(f[0]),q1:a(k(f,.25)),median:a(k(f,.5)),q3:a(k(f,.75)),max:a(f[f.length-1])})}const w={stats:h,specTally:T,capability:q(b)};return F.set(t,w),F.size>R&&F.delete(F.keys().next().value),w}export function buildCapabilityFigures(n,s){const{defByTestNumber:t,specByTest:p}=x(s);if(t.size===0)return[];const e=I(n,t,null),i=[];for(const[r,o]of e)o.n!==0&&i.push(L(r,t.get(r),p.get(r),o));return q(i)}export function poolCapabilityFigures(n,s){const{defByTestNumber:t,specByTest:p}=x(s),e=new Map;for(const r of n)for(const o of r){if(!t.has(o.testNumber)||o.n===0)continue;let m=e.get(o.testNumber);m||(m={n:0,sum:0,sumSq:0,withinNumerator:0,withinDenominator:0},e.set(o.testNumber,m));const h=o.stdOverall*o.stdOverall;m.n+=o.n,m.sum+=o.n*o.mean,m.sumSq+=(o.n-1)*h+o.n*o.mean*o.mean,o.n>=2&&(m.withinNumerator+=(o.n-1)*h,m.withinDenominator+=o.n-1)}const i=[];for(const[r,o]of e)i.push(L(r,t.get(r),p.get(r),o));return q(i)}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { Die, PositionedDie
|
|
1
|
+
import type { Die, PositionedDie } from '../core/dies.js';
|
|
2
|
+
import type { Wafer } from '../core/wafer.js';
|
|
2
3
|
import type { StatsFinding } from './types.js';
|
|
3
4
|
interface ClusterOptions {
|
|
4
5
|
passBins: number[];
|
|
@@ -64,7 +64,7 @@ export interface CorrelationMatrix {
|
|
|
64
64
|
* Correlation is the only computation here that is quadratic in tests *and*
|
|
65
65
|
* linear in dies: a 400k-die, 50-test lot is 1,225 pairs per die — 490 million
|
|
66
66
|
* pair updates, each touching six accumulators — which in a browser presents as
|
|
67
|
-
* a panel that never renders
|
|
67
|
+
* a panel that never renders.
|
|
68
68
|
*
|
|
69
69
|
* 25,000 is far past the point where more dies change a Pearson coefficient: the
|
|
70
70
|
* standard error of r is about `(1 - r²)/sqrt(n)`, so at n = 25,000 it is under
|
|
@@ -59,7 +59,7 @@ export declare function commonMetadata(items: Array<{
|
|
|
59
59
|
metadata?: WaferMetadata;
|
|
60
60
|
}>): WaferMetadata;
|
|
61
61
|
export interface BuildFacetTableOptions {
|
|
62
|
-
/** Extra/override curation entries layered on top of
|
|
62
|
+
/** Extra/override curation entries layered on top of wmap's default curation — a caller's own app-specific keys (or a relabel of a default one) without losing wmap's built-in defaults. */
|
|
63
63
|
curation?: Record<string, FacetCuration>;
|
|
64
64
|
/** Restrict to fields curated `facet: true` (the default) plus any uncurated key. Pass `false` to include curated-but-`facet:false` fields too. */
|
|
65
65
|
facetableOnly?: boolean;
|
|
@@ -1,26 +1,16 @@
|
|
|
1
1
|
export * from './types.js';
|
|
2
|
-
export
|
|
3
|
-
export
|
|
4
|
-
export
|
|
5
|
-
export
|
|
2
|
+
export { analyzeWaferMap } from './analyzeWaferMap.js';
|
|
3
|
+
export { analyzeWaferLot } from './analyzeWaferLot.js';
|
|
4
|
+
export { filterFindings, visibleFindings } from './filterFindings.js';
|
|
5
|
+
export type { FindingsFilter } from './filterFindings.js';
|
|
6
6
|
export type { PatternLabel, PatternClassification, PatternFeatures } from './patternClassification.js';
|
|
7
7
|
export { setReportOpener } from './renderFindingsReport.js';
|
|
8
8
|
export { renderWaferReportHtml, renderLotReportHtml } from './renderSummaryReport.js';
|
|
9
9
|
export type { ReportMap } from './renderSummaryReport.js';
|
|
10
|
-
export type { SummaryReportParams
|
|
10
|
+
export type { SummaryReportParams } from './renderSummaryReport.js';
|
|
11
11
|
export { buildFacetTable, facetValueOf, FACET_NONE_VALUE } from './facets.js';
|
|
12
12
|
export type { FacetField, FacetValue, FacetCuration, FacetItem, BuildFacetTableOptions } from './facets.js';
|
|
13
|
-
export type {
|
|
13
|
+
export type { MetadataKeySelection } from './metadataColumns.js';
|
|
14
14
|
export { mergeTestDefs } from './mergeTestDefs.js';
|
|
15
15
|
export type { MergedTestDefs, TestDefConflict, TestDefConflictKind } from './mergeTestDefs.js';
|
|
16
|
-
export type { CapabilityDatum, CapabilityItem } from './capability.js';
|
|
17
|
-
export type { CorrelationTestInfo, CorrelationCell, CorrelationMatrix, CorrelationSummary } from './correlation.js';
|
|
18
|
-
export type { BoxplotDatum, BoxplotItem } from './boxplot.js';
|
|
19
|
-
export type { TrendDatum, TrendItem } from './trend.js';
|
|
20
|
-
export type { TestPassKind, TestPassRateData, TestPassRateItem, TestPassRateRow, TestPassRateValue } from './testPassRate.js';
|
|
21
|
-
export type { HistogramBucket, HistogramItem, HistogramSeries, HistogramSeriesData } from './histogram.js';
|
|
22
|
-
export type { ScatterPoint, ScatterItem } from './scatter.js';
|
|
23
|
-
export type { ChartDatum, YieldItem, YieldSortBy } from './yield.js';
|
|
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, renderFindingsReportHtml, } from './deprecated.js';
|
|
26
16
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export*from"./types.js";export
|
|
1
|
+
export*from"./types.js";export{analyzeWaferMap}from"./analyzeWaferMap.js";export{analyzeWaferLot}from"./analyzeWaferLot.js";export{filterFindings,visibleFindings}from"./filterFindings.js";export{setReportOpener}from"./renderFindingsReport.js";export{renderWaferReportHtml,renderLotReportHtml}from"./renderSummaryReport.js";export{buildFacetTable,facetValueOf,FACET_NONE_VALUE}from"./facets.js";export{mergeTestDefs}from"./mergeTestDefs.js";
|
|
@@ -1,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
|
|
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,4 +1,5 @@
|
|
|
1
|
-
import type { PositionedDie
|
|
1
|
+
import type { PositionedDie } from '../core/dies.js';
|
|
2
|
+
import type { Wafer } from '../core/wafer.js';
|
|
2
3
|
export type PatternLabel = 'center' | 'donut' | 'edge-ring' | 'edge-local' | 'scratch' | 'near-full' | 'random' | 'none';
|
|
3
4
|
export interface PatternFeatures {
|
|
4
5
|
/** Ratio of failing dies to total eligible dies (whole-wafer). */
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { Die, PositionedDie
|
|
1
|
+
import type { Die, PositionedDie } from '../core/dies.js';
|
|
2
|
+
import type { Wafer } from '../core/wafer.js';
|
|
2
3
|
import type { ReticleConfig } from '../renderer/buildWaferMap.js';
|
|
3
4
|
export interface StatsRegion {
|
|
4
5
|
family: 'ring' | 'quadrant' | 'reticle-position' | 'test-site' | 'sector';
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
import type { StatsSummary, LotStatsSummary } from './types.js';
|
|
2
|
-
export declare function renderFindingsReportHtml(summary: StatsSummary | LotStatsSummary, options?: {
|
|
3
|
-
title?: string;
|
|
4
|
-
}): string;
|
|
5
1
|
export declare function openHtmlReport(html: string): void;
|
|
6
2
|
export declare function setReportOpener(opener: (html: string) => void): void;
|
|
7
3
|
//# sourceMappingURL=renderFindingsReport.d.ts.map
|
|
@@ -1,21 +1 @@
|
|
|
1
|
-
|
|
2
|
-
`:"",u=[f("Summary",F(e,r)),f("Findings",c+g(a,n))].join(`
|
|
3
|
-
`);return`<!DOCTYPE html>
|
|
4
|
-
<html lang="en">
|
|
5
|
-
<head>
|
|
6
|
-
<meta charset="UTF-8">
|
|
7
|
-
<title>${l(i)}</title>
|
|
8
|
-
<style>
|
|
9
|
-
${h()}
|
|
10
|
-
</style>
|
|
11
|
-
</head>
|
|
12
|
-
<body>
|
|
13
|
-
<main class="report">
|
|
14
|
-
<header class="report-header">
|
|
15
|
-
<h1>${l(i)}</h1>
|
|
16
|
-
<p class="report-subtitle">Generated ${l(r)}</p>
|
|
17
|
-
</header>
|
|
18
|
-
${u}
|
|
19
|
-
</main>
|
|
20
|
-
</body>
|
|
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}
|
|
1
|
+
export function openHtmlReport(e){if(typeof window.__openHtmlReport=="function"){window.__openHtmlReport(e);return}const o=URL.createObjectURL(new Blob([e],{type:"text/html"})),n=window.open(o,"_blank");setTimeout(()=>URL.revokeObjectURL(o),3e4),n||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}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{waferDisplayLabel as H}from"../core/waferLabel.js";import{binPassSets as we,binPassSetsByWafer as $e,mergeBinDefs as I}from"../renderer/binColors.js";import{itemPassBins as R,passBinsLabel as xe}from"../core/passBins.js";import{isParametricTest as De}from"../renderer/buildWaferMap.js";import{buildRingRegions as j,buildQuadrantRegions as z,buildRegionYieldData as Be}from"./regions.js";import{openHtmlReport as ve}from"./renderFindingsReport.js";import{analyzeWaferLot as Te}from"./analyzeWaferLot.js";import{computeFunctionalYield as Pe,analyzeWaferMap as Fe}from"./analyzeWaferMap.js";import{mergeTestDefs as Ye}from"./mergeTestDefs.js";import{sortBinsForDisplay as A}from"./binPareto.js";import{buildFacetTable as We,facetValueOf as Me,FACET_NONE_VALUE as Ce}from"./facets.js";import{visibleFindings as O}from"./filterFindings.js";import{buildYieldDataCombined as Re}from"./yield.js";import{describeWaferPopulation as G,populationLabel as Ne}from"./population.js";import{buildTestPassRateData as Ee,hasJudgeableTests as
|
|
2
|
-
`)),u?Q(n,
|
|
1
|
+
import{waferDisplayLabel as H}from"../core/waferLabel.js";import{binPassSets as we,binPassSetsByWafer as $e,mergeBinDefs as I}from"../renderer/binColors.js";import{itemPassBins as R,passBinsLabel as xe}from"../core/passBins.js";import{isParametricTest as De}from"../renderer/buildWaferMap.js";import{buildRingRegions as j,buildQuadrantRegions as z,buildRegionYieldData as Be}from"./regions.js";import{openHtmlReport as ve}from"./renderFindingsReport.js";import{analyzeWaferLot as Te}from"./analyzeWaferLot.js";import{computeFunctionalYield as Pe,analyzeWaferMap as Fe}from"./analyzeWaferMap.js";import{mergeTestDefs as Ye}from"./mergeTestDefs.js";import{sortBinsForDisplay as A}from"./binPareto.js";import{buildFacetTable as We,facetValueOf as Me,FACET_NONE_VALUE as Ce}from"./facets.js";import{visibleFindings as O}from"./filterFindings.js";import{buildYieldDataCombined as Re}from"./yield.js";import{describeWaferPopulation as G,populationLabel as Ne}from"./population.js";import{buildTestPassRateData as Ee,hasJudgeableTests as Le,poolFunctionalYield as ke}from"./testPassRate.js";import{buildCapabilityData as He}from"./capability.js";import{fmt as T}from"../renderer/fmt.js";import{getDieKey as Ie,isPositionedDie as je,diePassStatus as U}from"../core/dies.js";import{findingsTableHtml as V,renderMetadataSection as _,renderMetricGrid as K,renderSection as x,renderTable as v,reportStyles as N}from"./reportHtml.js";import{escHtml as y}from"../core/utils.js";function C(t,o){return o===0?"\u2014":`${(t/o*100).toFixed(1)}%`}function ze(t){if(!t)return"";const o=t.lot??t.lotId,a=t.wafer??t.waferId,n=[o,a].filter(Boolean).map(String);return n.length?` \u2014 ${n.map(y).join(" \xB7 ")}`:""}function Q(t,o,a,n,r=[1]){const e=new Map;if(n)for(const[s,c]of Object.entries(n))e.set(Number(s),c);else for(const s of t){if(s.partial||s.edgeExcluded)continue;const c=a==="hard"?s.hbin:s.sbin;c!=null&&e.set(c,(e.get(c)??0)+1)}if(!e.size)return"";const i=[...e.values()].reduce((s,c)=>s+c,0),l=o?new Map(o.map(s=>[s.bin,s])):null,d=A(e.entries(),we(t,r)[a]).map(([s,c])=>{const p=l?.get(s)?.name??"\u2014";return[String(s),p,String(c),C(c,i)]});return x(a==="hard"?"Hard Bin Breakdown":"Soft Bin Breakdown",v(["Bin","Name","Count","%"],d,{className:"compact"}))}function q(t,o,a,n){const r=new Set(n),e=new Map(a.map(d=>[Ie(d),d]));if(!a.some(d=>d.hbin!=null||d.sbin!=null)||!o.length)return"";const l=[];for(const d of o){let f=0,s=0;for(const c of d.dieKeys){const h=e.get(c);if(!h||h.partial||h.edgeExcluded)continue;const p=U(h,r);p!==void 0&&(s++,p&&f++)}s&&l.push([d.label,String(f),String(s),C(f,s)])}return l.length?x(t,v(["Region","Pass","Total","Yield"],l,{className:"compact"})):""}function J(t,o,a){if(!o.length)return"";const n=t.filter(i=>!i.partial&&!i.edgeExcluded),r=new Map((a??[]).map(i=>[i.testNumber,i])),e=[];for(const i of o.filter(De)){const l=i.testNumber,d=i.unit||void 0,f=r.get(l);let s,c,h;if(f)({min:s,max:c,mean:h}=f);else{const p=n.map(w=>w.testValues?.[l]).filter(w=>w!==void 0&&isFinite(w));if(!p.length)continue;p.sort((w,b)=>w-b),s=p[0],c=p[p.length-1],h=p.reduce((w,b)=>w+b,0)/p.length}e.push([y(i.name),T(s,d),T(h,d),T(c,d)])}return e.length?x("Test Values",v(["Test","Min","Mean","Max"],e,{className:"compact"})):""}function X(t,o,a){const n=a??Pe(t,o);if(!n?.length)return"";const r=n.map(e=>[y(e.label),String(e.totalDies),String(e.passDies),String(e.failDies),e.passRatePercent!==null?`${e.passRatePercent.toFixed(1)}%`:"\u2014"]);return x("Functional Tests",v(["Test","N","Pass","Fail","Pass Rate"],r,{className:"compact"}))}function Z(t,o){const a=He(t,o);if(!a.length)return"";const n=e=>e===null?"\u2014":e.toFixed(2),r=a.map(e=>[y(e.label),e.hasSpec?e.limitBasis==="spec"?`LSL ${T(e.lsl,e.unit)} \u2013 USL ${T(e.usl,e.unit)}`:`Lo limit ${T(e.lsl,e.unit)} \u2013 Hi limit ${T(e.usl,e.unit)}`:"\u2014",T(e.mean,e.unit),n(e.cp),n(e.cpk),n(e.pp),n(e.ppk)]);return x("Process Capability",v(["Test","Limits","Mean","Cp","Cpk","Pp","Ppk"],r,{className:"compact"}))}function ee(t,o){const a=O(t);return a.length?x("Findings",V(a,o)):""}export function renderSummaryReportHtml(t,o={}){const{wafer:a,dies:n,yieldSummary:r,dataCoverage:e,hbinDefs:i,sbinDefs:l,testDefs:d=[],statsSummary:f,passBins:s=[1],ringCount:c=4}=t,h=a.metadata,p=ze(h),w=o.title??`Wafer Summary${p}`,b=new Date().toLocaleString(),u=n.some(W=>W.hbin!=null),g=n.some(W=>W.sbin!=null),S=n.filter(je),D=j(S,a,c),$=z(S,a,c),B=[{label:"Total dies",value:String(r.totalDies)},{label:"Filled dies",value:String(e.filledDies),hint:`${C(e.filledDies,e.totalDies)} fill`},{label:"Pass dies",value:String(r.passDies)},{label:"Fail dies",value:String(r.failDies)},...r.partialDies>0?[{label:"Partial",value:String(r.partialDies)}]:[],...r.yieldPercent!==null?[{label:`Yield (pass: ${xe([s])})`,value:`${r.yieldPercent.toFixed(1)}%`}]:[],...r.edgeExcludedDies>0?[{label:"Edge excluded (outer zone)",value:String(r.edgeExcludedDies)}]:[]],Y=[_([{metadata:a.metadata}]),x("Summary",[K(B)].filter(Boolean).join(`
|
|
2
|
+
`)),u?Q(n,i,"hard",f?.stats.hardBinCounts,s):g?Q(n,l,"soft",f?.stats.softBinCounts,s):"",q("Ring Yield",D,n,s),q("Quadrant Yield",$,n,s),J(n,d,f?.stats.perTestStats),X(n,d,f?.stats.functionalYield),Z([{dies:n}],d),f?ee(f.findings):""].filter(Boolean).join(`
|
|
3
3
|
`);return`<!DOCTYPE html>
|
|
4
4
|
<html lang="en">
|
|
5
5
|
<head>
|
|
@@ -19,18 +19,18 @@ ${N()}
|
|
|
19
19
|
<p class="footer">Generated ${y(b)}</p>
|
|
20
20
|
</main>
|
|
21
21
|
</body>
|
|
22
|
-
</html>`}function Ae(t,
|
|
23
|
-
`))}const Ge=3;function Ue(t,
|
|
24
|
-
`)}`)}if(!
|
|
25
|
-
`)+l)}function te(t,
|
|
26
|
-
`);return{lotSummary:l,sections:Se}}function ae(t,
|
|
22
|
+
</html>`}function Ae(t,o){const a=t.perWafer.map(n=>{const r=H(o[n.waferIndex],n.waferIndex),e=n.summary.stats.yieldPercent;return[r,e!==null?`${e.toFixed(1)}%`:"N/A"]});return v(["Wafer","Yield"],a,{className:"compact"})}function Oe(t){const o=[];for(const e of t.perWafer){const i=O(e.summary.findings);if(!i.length)continue;const l=e.summary.wafer?.waferId??`Wafer ${e.waferIndex+1}`;o.push(`<h3 class="report-subheading">${y(l)}</h3>`+V(i))}if(!o.length)return"";const a=o.length,n=t.perWafer.length,r=`<p class="report-note">${a} of ${n} wafer${n===1?"":"s"} have per-wafer findings; the rest had none.</p>`;return x("Findings by Wafer",r+o.join(`
|
|
23
|
+
`))}const Ge=3;function Ue(t,o,a){const n=t.map(d=>({metadata:d.wafer?.metadata})),r=We(n,{facetableOnly:!0}).filter(d=>d.splittable);if(!r.length)return"";const e=r.slice(0,Ge),i=[];for(const d of e){const f=new Map;t.forEach((u,g)=>{const S=Me(u.wafer?.metadata,d.key)??Ce;(f.get(S)??f.set(S,[]).get(S)).push(g)});const s=[...f.entries()].map(([u,g])=>({key:u,items:g.map(S=>t[S])}));if(s.length<2)continue;const c=s.map(u=>[y(u.key),y(u.items.map(g=>g.label).join(", "))]),h=Re(s.map(u=>({key:u.key,items:u.items.map(g=>({label:g.label,dies:g.dies,passBins:R(g,a)}))})),a).map(u=>[y(u.label),String(u.itemCount),`${u.percent.toFixed(1)}%`]),p=[v([d.label,"Wafers"],c,{className:"compact"}),v([d.label,"Wafers","Yield"],h,{className:"compact"})],w=t.flatMap(u=>u.dies??[]);let b=0;for(const u of["spec","testFlag","functional"]){if(!Le(o,u,w))continue;const g=Ee(s.map($=>({key:$.key,items:$.items.map(B=>({dies:B.dies,testSpecYield:B.statsSummary?.stats.testSpecYield,functionalYield:B.statsSummary?.stats.functionalYield}))})),o,u);if(!g.rows.length)continue;const S=g.rows.map($=>[y($.label),...$.byGroup.map(B=>B.passRatePercent===null?"\u2014":`${B.passRatePercent.toFixed(1)}%`),$.overall.passRatePercent===null?"\u2014":`${$.overall.passRatePercent.toFixed(1)}%`]),D=u==="spec"?"Pass rate \xB7 test limits":u==="testFlag"?"Pass rate \xB7 tester flag":"Pass rate \xB7 functional";g.disagreementDies&&(b=g.disagreementDies),p.push(`<h4 class="report-subheading">${y(D)}</h4>`+v(["Test",...g.groups,"All"],S,{className:"compact"}))}b&&p.push(`<p class="report-note">${b.toLocaleString()} dies judged differently by limits and tester flag.</p>`),i.push(`<h3 class="report-subheading">${y(d.label)}</h3>${p.join(`
|
|
24
|
+
`)}`)}if(!i.length)return"";const l=r.length>e.length?`<p class="report-note">${r.length-e.length} further splittable field(s) not shown.</p>`:"";return x("Split Comparison",i.join(`
|
|
25
|
+
`)+l)}function te(t,o,a,n,r){const e=new Map,i=a==="hard"?"hardBinCounts":"softBinCounts";if(n?.length&&n.every(s=>s.stats[i]!==void 0))for(const s of n)for(const[c,h]of Object.entries(s.stats[i])){const p=Number(c);e.set(p,(e.get(p)??0)+h)}else for(const s of t){if(s.partial||s.edgeExcluded)continue;const c=a==="hard"?s.hbin:s.sbin;c!=null&&e.set(c,(e.get(c)??0)+1)}if(!e.size)return"";const l=[...e.values()].reduce((s,c)=>s+c,0),d=o?new Map(o.map(s=>[s.bin,s])):null,f=A(e.entries(),$e(r)[a]).map(([s,c])=>{const h=d?.get(s);return[h?.name?`Bin ${s} \xB7 ${h.name} (${c})`:`Bin ${s} (${c})`,C(c,l)]});return x(a==="hard"?"Hard Bin Breakdown (All Wafers)":"Soft Bin Breakdown (All Wafers)",v(["Bin","Yield"],f,{className:"compact"}))}function ne(t,o,a,n,r,e){const i=Be(a,n,r,l=>e[l],o).map(l=>[l.label,`${l.yieldPercent.toFixed(1)}%`]);return i.length?x(t,v(["Region","Yield"],i,{className:"compact"})):""}function Ve(t,o,a){let n;if(a?.length&&a.every(r=>r.stats.perTestStats!==void 0)){const r=new Map;for(const e of a)for(const i of e.stats.perTestStats??[]){const l=r.get(i.testNumber);l?(l.n+=i.count,l.sum+=i.mean*i.count,l.min=Math.min(l.min,i.min),l.max=Math.max(l.max,i.max)):r.set(i.testNumber,{n:i.count,sum:i.mean*i.count,min:i.min,max:i.max})}n=[...r.entries()].map(([e,i])=>({testNumber:e,min:i.min,max:i.max,mean:i.sum/i.n}))}return J(t,o,n)}function _e(t,o,a){const n=ke(a);return X(t,o,n)}const E=["lot","product","testProgram","temperature"];function Ke(t){const o=new Map(E.map(e=>[e,new Set]));for(const e of t)for(const i of E){const l=e.wafer?.metadata?.[i];l!=null&&l!==""&&o.get(i).add(String(l))}const a=E.filter(e=>o.get(e).size>1);if(a.length===0)return[{label:"",items:t,varying:a}];const n=new Map,r=[];for(const e of t){const i=a.map(l=>String(e.wafer?.metadata?.[l]??"(none)")).join(" \xB7 ");n.has(i)||(n.set(i,[]),r.push(i)),n.get(i).push(e)}return r.map(e=>({label:e,items:n.get(e),varying:a}))}function se(t,o,a,n,r,e,i){const l=Te(t.map(m=>m.source??m),{perWaferSummaries:t.map(m=>m.statsSummary),...i}),d=[],f=[],s=[],c=[];for(const m of t){const F=m.dies??[];m.wafer&&(d.push(m.wafer),s.push(F),c.push(R(m,r)));for(const P of F)f.push(P)}const h=t.map(m=>({dies:m.dies??[],passBins:R(m,r)})),p=f.some(m=>m.hbin!=null),w=f.some(m=>m.sbin!=null),b=p||w,u=l.perWafer.map(m=>m.summary.stats.yieldPercent).filter(m=>m!==null),g=u.length?u.reduce((m,F)=>m+F,0)/u.length:null;let S=0,D=0,$=0,B=0,Y=0;for(const m of h){const F=new Set(m.passBins);for(const P of m.dies){if(S++,P.edgeExcluded&&B++,P.partial&&Y++,P.partial||P.edgeExcluded)continue;const k=U(P,F);k!==void 0&&(D++,k&&$++)}}const W=D-$,L=D>0?$/D*100:null,ie=[{label:"Wafers",value:String(l.stats.waferCount)},{label:"Total dies",value:String(S)},...b?[{label:"Good dies",value:String($)},{label:"Bad dies",value:String(W)}]:[],...B>0?[{label:"Edge excluded",value:String(B)}]:[],...Y>0?[{label:"Partial dies",value:String(Y)}]:[],...g!==null?[{label:"Mean wafer yield",value:`${g.toFixed(1)}%`}]:[],...L!==null?[{label:"Total yield",value:`${L.toFixed(1)}%`}]:[]],oe=G(l.perWafer.map(m=>m.summary.wafer)),re=x(oe.lotId!==void 0?"Lot Summary":"Summary",K(ie)),le=_(t.map(m=>({metadata:m.wafer?.metadata}))),ce=x("Per-Wafer Yield",Ae(l,t)),de=Ue(t,n,r),M=l.perWafer.map(m=>m.summary),ue=b?p?te(f,o,"hard",M,h):te(f,a,"soft",M,h):"",fe=b&&d.length?ne("Ring Yield (All Wafers)",j,s,d,e,c):"",me=b&&d.length?ne("Quadrant Yield (All Wafers)",z,s,d,e,c):"",pe=n.length?Ve(f,n,M):"",ge=n.length?_e(f,n,M):"",he=n.length?Z(t,n):"",be=l.findings.length?ee(l.findings,l.stats.waferCount):"",ye=Oe(l),Se=[re,le,ce,de,ue,fe,me,pe,ge,he,be,ye].filter(Boolean).join(`
|
|
26
|
+
`);return{lotSummary:l,sections:Se}}function ae(t,o,a){return`<main class="report">
|
|
27
27
|
<header class="report-header">
|
|
28
28
|
<h1>${y(t)}</h1>
|
|
29
29
|
<p class="report-subtitle">Generated ${y(a)}</p>
|
|
30
30
|
</header>
|
|
31
|
-
${
|
|
31
|
+
${o}
|
|
32
32
|
<p class="footer">Generated ${y(a)}</p>
|
|
33
|
-
</main>`}export function renderLotSummaryReportHtml(t,
|
|
33
|
+
</main>`}export function renderLotSummaryReportHtml(t,o={}){const{items:a,hbinDefs:n,sbinDefs:r,testDefs:e=[],passBins:i=[1],ringCount:l=4,analyzeOptions:d}=t,f=new Date().toLocaleString(),s=Ke(a);if(s.length===1){const{lotSummary:b,sections:u}=se(s[0].items,n,r,e,i,l,d),g=G(b.perWafer.map(D=>D.summary.wafer)),S=o.title??(g.lotId!==void 0?`Lot Summary \u2014 ${g.lotId}`:`Summary \u2014 ${Ne(g)}`);return`<!DOCTYPE html>
|
|
34
34
|
<html lang="en">
|
|
35
35
|
<head>
|
|
36
36
|
<meta charset="UTF-8">
|
|
@@ -42,7 +42,7 @@ ${N()}
|
|
|
42
42
|
<body>
|
|
43
43
|
${ae(S,u,f)}
|
|
44
44
|
</body>
|
|
45
|
-
</html>`}const c=
|
|
45
|
+
</html>`}const c=o.title??"Summary",h=s.map(b=>{const{lotSummary:u,sections:g}=se(b.items,n,r,e,i,l,d),S=b.varying.includes("lot")?void 0:u.lot?.lot??u.lot?.lotId,D=S?`${b.label} \xB7 Lot ${String(S)}`:b.label;return ae(`${c} \u2014 ${D}`,g,f)}),p=`<p style="max-width:1080px;margin:0 auto 8px;padding:8px 12px;background:var(--report-surface);border:1px solid var(--report-line);border-radius:4px;font-size:12px;color:var(--report-muted);">This load spans ${s.length} groups by identity \u2014 shown separately below so stats are never pooled across them.</p>`,w=h.map((b,u)=>`${u===0?"":'<hr style="max-width:1080px;margin:32px auto 0;border:none;border-top:3px solid var(--report-line-strong);">'}
|
|
46
46
|
${b}`).join(`
|
|
47
47
|
`);return`<!DOCTYPE html>
|
|
48
48
|
<html lang="en">
|
|
@@ -59,4 +59,4 @@ ${p}
|
|
|
59
59
|
</div>
|
|
60
60
|
${w}
|
|
61
61
|
</body>
|
|
62
|
-
</html>`}export{ve as openHtmlReport};export function renderWaferReportHtml(t,
|
|
62
|
+
</html>`}export{ve as openHtmlReport};export function renderWaferReportHtml(t,o,a={}){return renderSummaryReportHtml({wafer:t.wafer,dies:t.dies,yieldSummary:t.yield,dataCoverage:t.dataCoverage,hbinDefs:t.hbinDefs,sbinDefs:t.sbinDefs,testDefs:t.testDefs,statsSummary:o??t.statsSummary??("view"in t?Fe(t):void 0),passBins:[...t.passBins],ringCount:t.ringCount},a)}export function renderLotReportHtml(t,o={}){const a=I(t.map(e=>e.hbinDefs)),n=I(t.map(e=>e.sbinDefs)),r=t.some(e=>e.testDefs?.length)?Ye([...t]).defs:void 0;return renderLotSummaryReportHtml({items:t.map((e,i)=>({label:H(e,i),wafer:e.wafer,dies:e.dies,passBins:[...e.passBins],statsSummary:e.statsSummary,source:"view"in e?e:void 0})),hbinDefs:a.length?a:void 0,sbinDefs:n.length?n:void 0,testDefs:r,ringCount:t[0]?.ringCount,analyzeOptions:o.analyzeOptions},{title:o.title})}
|
|
@@ -165,6 +165,14 @@ export interface SweepData {
|
|
|
165
165
|
*/
|
|
166
166
|
notMeasured?: string;
|
|
167
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* Whether `spec` names at least one parametric test in `testDefs` — i.e.
|
|
170
|
+
* whether it can draw anything for data with these tests. False means none of
|
|
171
|
+
* its tests exist here: typically a sweep from another test program, still
|
|
172
|
+
* defined after a different lot was loaded. Internal: the Sweeps tab and the
|
|
173
|
+
* sweep card say so themselves.
|
|
174
|
+
*/
|
|
175
|
+
export declare function sweepAppliesTo(spec: SweepSpec, testDefs: readonly TestDef[] | undefined): boolean;
|
|
168
176
|
/**
|
|
169
177
|
* Build the plottable data for one sweep over a die population.
|
|
170
178
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{isYieldEligibleDie as
|
|
1
|
+
import{isYieldEligibleDie as G}from"../core/dies.js";import{isParametricTest as M}from"../renderer/buildWaferMap.js";import{markedTestLabel as H,derivedFields as J}from"../renderer/testLabel.js";import{parseTestReference as K}from"../renderer/derivedTests/parser.js";import{quantile as V}from"./math.js";import{compileXNamePattern as Q,readXFromName as W}from"./sweepXFromName.js";function j(o,a,s){const e=[];let t=!1;for(const r of o.tests){if(typeof r=="number"){Number.isInteger(r)?e.push(r):s.push(`"${o.label}": ${r} is not a test number.`);continue}const i=K(String(r),a);if(!i.ok){s.push(`"${o.label}": "${r}" \u2014 ${i.message}.`);continue}i.range&&(t=!0),e.push(...i.tests)}return{spec:o,tests:e,usedRange:t}}export function sweepAppliesTo(o,a){const s=new Map((a??[]).map(e=>[e.testNumber,e]));return o.series.some(e=>j(e,s,[]).tests.some(t=>{const r=s.get(t);return r!==void 0&&M(r)}))}function B(o,a=16){return o.length<=a?o.join(", "):`${o.slice(0,a).join(", ")}, \u2026 (${o.length-a} more)`}function Z(o,a){const s=o?.[a];return s!==void 0&&Number.isFinite(s)?s:a}function _(o,a,s){const e=o.spec;if(e.xValues!==void 0&&e.xFromName!==void 0)return s.push(`"${e.label}": gives both xValues and xFromName \u2014 use one.`),{asked:!0};if(e.xValues!==void 0){const t=e.xValues.length;return t===o.tests.length?{xs:e.xValues,asked:!0}:(s.push(o.usedRange?`"${e.label}": ${t} x values for ${o.tests.length} tests \u2014 the ranges matched ${B(o.tests)}.`:`"${e.label}": ${t} x values for ${o.tests.length} tests.`),{asked:!0})}if(e.xFromName!==void 0){const t=Q(e.xFromName);if("error"in t)return s.push(`"${e.label}": xFromName "${e.xFromName}" \u2014 ${t.error}.`),{asked:!0};const r=[],i=[],u=[];for(const l of o.tests){const c=a.get(l)?.name,f=c===void 0?null:W(t,c);typeof f=="number"?r.push(f):(i.push(l),f!==null&&u.push(f.ambiguous))}return i.length>0?(s.push(`"${e.label}": xFromName "${e.xFromName}" found no x value in the name of ${i.length===1?"test":"tests"} ${B(i)}`+(u.length>0?` \u2014 "${u[0]}" is written in capitals, where M could be milli or mega; put the letter in the pattern ("\u2026{x}M\u2026") and the unit in xLabel.`:".")),{asked:!0}):{xs:r,asked:!0}}return{asked:!1}}function ee(o,a,s){const e=new Map;for(let t=0;t<s.length;t++){const r=e.get(s[t]);if(r!==void 0)return`"${o}": tests ${a[r]} and ${a[t]} have the same x value (${s[t]}) \u2014 a curve cannot be measured where it revisits an x.`;e.set(s[t],t)}return null}function D(o,a){const s=o.filter(e=>e.count>0);for(let e=1;e<s.length;e++){const t=s[e-1],r=s[e],i=Math.min(t.median,r.median),u=Math.max(t.median,r.median);if(a<i||a>u)continue;if(t.median===r.median)return t.x;const l=(a-t.median)/(r.median-t.median);return t.x+l*(r.x-t.x)}return null}function ne(o,a){const s=new Map(a.filter(i=>i.count>0).map(i=>[i.x,i])),e=[];for(const i of o){if(i.count===0)continue;const u=s.get(i.x);u!==void 0&&e.push({x:i.x,d:i.median-u.median,y:i.median})}if(e.length<2)return null;const t=[];for(let i=0;i<e.length;i++){const u=e[i];if(u.d===0){t.push({x:u.x,y:u.y});continue}if(i===0)continue;const l=e[i-1];if(l.d===0||l.d<0==u.d<0)continue;const c=l.d/(l.d-u.d);t.push({x:l.x+c*(u.x-l.x),y:l.y+c*(u.y-l.y)})}const r=t[0];return r===void 0?null:{x:r.x,y:r.y,multiple:t.length>1}}export function buildSweepData(o,a,s){const e=[],t=new Map((a??[]).map(n=>[n.testNumber,n])),r=o.filter(n=>G(n)),i=s.series.map(n=>j(n,t,e)),u=i.map(n=>_(n,t,e)),l=i.length>0&&u.every(n=>n.xs!==void 0),c=!l&&u.some(n=>n.asked);c&&u.some(n=>!n.asked)&&e.push("x values are given for some series but not all.");const f=l?i.map((n,d)=>ee(n.spec.label,n.tests,u[d].xs)).find(n=>n!==null)??null:null;f!==null&&e.push(f);const S=s.xScale==="log",T=l&&u.every(n=>n.xs.every(d=>d>0)),b=S&&T?"log":"linear";S&&!T&&e.push(l?"A log x axis needs every x value to be positive \u2014 drawn on a linear axis.":"A log x axis needs x values (xValues or xFromName) \u2014 drawn in test order.");const L=i.map(({spec:n,tests:d},p)=>{const v=l?u[p].xs:void 0,N=d.map((x,z)=>{const h=t.get(x);h===void 0?e.push(`"${n.label}": test ${x} is not in this program's test definitions.`):M(h)||e.push(`"${n.label}": test ${x} is functional and has no measured value.`);const m=[];if(h!==void 0&&M(h))for(const F of r){const g=F.testValues?.[x];g!==void 0&&Number.isFinite(g)&&m.push(g)}return m.sort((F,g)=>F-g),{testNumber:x,label:H(h,x),...J(h),x:Z(v,z),median:m.length?V(m,.5):NaN,p10:m.length?V(m,.1):NaN,p90:m.length?V(m,.9):NaN,count:m.length}});return{label:n.label,color:n.color,points:N}}),[X,C]=L,$=X!==void 0&&C!==void 0&&!c&&f===null,I=s.crossing??!0,Y=n=>b==="log"?Math.log10(n):n,k=n=>b==="log"?10**n:n,P=n=>n.map(d=>({...d,x:Y(d.x)})),q=$?P(X.points):[],R=$?P(C.points):[],E=(c||f!==null?[]:s.separationAt??[]).map(n=>{if(!$)return{y:n,distance:null};const d=D(q,n),p=D(R,n);if(d===null)return{y:n,distance:null,reason:"first-series-never-reaches"};if(p===null)return{y:n,distance:null,reason:"second-series-never-reaches"};const v=k(d),N=k(p);return{y:n,distance:Math.abs(N-v),from:v,to:N,...b==="log"?{ratio:10**Math.abs(d-p)}:{}}}),w=$&&I?ne(q,R):null,O=w&&{...w,x:k(w.x)},U=n=>n===void 0?void 0:t.get(n.tests[0]??-1)?.unit,y=U(i[0]),A=U(i[1]);return y!==void 0&&A!==void 0&&y!==A&&e.push(`Series measure different units (${y} and ${A}) \u2014 crossing and separation compare unlike quantities.`),{title:s.title,xLabel:c?"Test":s.xLabel??(l?"Sweep value":"Test"),yLabel:s.yLabel??"Value",series:L,xIsPhysical:l,...l&&s.xUnit?{xUnit:s.xUnit}:{},xScale:b,crossing:O,separations:E,dieCount:r.length,warnings:e,...c&&s.series.length>=2?{notMeasured:"x values do not match the tests, so the sweep is drawn in test order and the crossing and widths are not measured"}:f!==null&&s.series.length>=2?{notMeasured:"a series revisits an x value, so its points cannot be paired with the other series"}:{}}}
|
|
@@ -233,7 +233,7 @@ export interface StatsSummary {
|
|
|
233
233
|
/** Number of wafers in the lot stack. Present only when isLotStack is true. */
|
|
234
234
|
lotSize?: number;
|
|
235
235
|
/**
|
|
236
|
-
* Per-test
|
|
236
|
+
* Per-test yield against the test limits, for each test that has at least one limit defined.
|
|
237
237
|
* Only populated when testDefs with limitLow/limitHigh are provided.
|
|
238
238
|
*/
|
|
239
239
|
testSpecYield?: Array<{
|
|
@@ -293,7 +293,7 @@ export interface StatsSummary {
|
|
|
293
293
|
capability?: TestCapability[];
|
|
294
294
|
/**
|
|
295
295
|
* Per-test pass rate by the tester's recorded verdict, for parametric tests
|
|
296
|
-
* with recorded verdicts — the counterpart of `testSpecYield` (
|
|
296
|
+
* with recorded verdicts — the counterpart of `testSpecYield` (test limits)
|
|
297
297
|
* and `functionalYield` (functional tests). Worst first.
|
|
298
298
|
*/
|
|
299
299
|
testFlagYield?: TestVerdictYield[];
|
|
@@ -339,10 +339,16 @@ export interface TestCapability {
|
|
|
339
339
|
derived?: true;
|
|
340
340
|
/** The expression a `derived` test was computed from, for display. */
|
|
341
341
|
expression?: string;
|
|
342
|
-
/**
|
|
342
|
+
/**
|
|
343
|
+
* Limits to judge capability against exist: both spec limits (`specLow`/
|
|
344
|
+
* `specHigh`), or failing that both test limits (`limitLow`/`limitHigh`). When
|
|
345
|
+
* false, `lsl`/`usl` are absent and every index is null.
|
|
346
|
+
*/
|
|
343
347
|
hasSpec: boolean;
|
|
344
348
|
lsl?: number;
|
|
345
349
|
usl?: number;
|
|
350
|
+
/** `'spec'` when `lsl`/`usl` are the spec limits, `'test'` when they are the test limits. */
|
|
351
|
+
limitBasis?: 'spec' | 'test';
|
|
346
352
|
mean: number;
|
|
347
353
|
/** Sample stddev (ddof = 1) over every value. */
|
|
348
354
|
stdOverall: number;
|