@wafertools/wafermap 0.32.0 → 0.33.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 +1 -1
- package/CHANGELOG.md +162 -1
- package/README.md +2 -2
- package/dist/packages/canvas-adapter/charts/chartShell.d.ts +2 -12
- package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
- package/dist/packages/canvas-adapter/insightsTab.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferMap.d.ts +30 -0
- package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
- package/dist/packages/canvas-adapter/summaryPanel.d.ts +22 -3
- package/dist/packages/canvas-adapter/summaryPanel.js +4 -3
- package/dist/packages/canvas-adapter/toCanvas.js +1 -1
- package/dist/packages/canvas-adapter/toolbar.d.ts +54 -1
- package/dist/packages/canvas-adapter/toolbar.js +2 -2
- package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
- package/dist/packages/canvas-adapter/userGuideHtml.js +76 -13
- package/dist/packages/canvas-adapter/version.d.ts +2 -2
- package/dist/packages/canvas-adapter/version.js +1 -1
- package/dist/packages/core/aggregates.d.ts +6 -0
- package/dist/packages/core/aggregates.js +1 -1
- package/dist/packages/core/compact.d.ts +205 -0
- package/dist/packages/core/compact.js +3 -0
- package/dist/packages/core/passBins.d.ts +6 -0
- package/dist/packages/core/passBins.js +1 -1
- package/dist/packages/renderer/axisTicks.d.ts +14 -0
- package/dist/packages/renderer/axisTicks.js +1 -1
- package/dist/packages/renderer/buildView.d.ts +26 -1
- package/dist/packages/renderer/buildView.js +1 -1
- package/dist/packages/renderer/buildWaferMap.js +1 -1
- package/dist/packages/renderer/compactView.d.ts +22 -0
- package/dist/packages/renderer/compactView.js +1 -0
- package/dist/packages/renderer/xyIndicator.d.ts +27 -0
- package/dist/packages/renderer/xyIndicator.js +1 -0
- package/dist/packages/stats/analyzeWaferLot.d.ts +28 -0
- package/dist/packages/stats/analyzeWaferLot.js +1 -1
- package/dist/packages/stats/analyzeWaferMap.d.ts +36 -1
- package/dist/packages/stats/analyzeWaferMap.js +1 -1
- package/dist/packages/stats/binRows.d.ts +38 -0
- package/dist/packages/stats/binRows.js +1 -0
- package/dist/packages/stats/filterFindings.d.ts +25 -0
- package/dist/packages/stats/filterFindings.js +1 -1
- package/dist/packages/stats/findingText.d.ts +9 -0
- package/dist/packages/stats/findingText.js +2 -0
- package/dist/packages/stats/lotDrift.d.ts +16 -0
- package/dist/packages/stats/lotDrift.js +1 -0
- package/dist/packages/stats/lotPattern.d.ts +33 -0
- package/dist/packages/stats/lotPattern.js +1 -0
- package/dist/packages/stats/math.d.ts +51 -0
- package/dist/packages/stats/math.js +1 -1
- package/dist/packages/stats/patternClassification.d.ts +31 -0
- package/dist/packages/stats/patternClassification.js +1 -1
- package/dist/packages/stats/presentation.d.ts +42 -0
- package/dist/packages/stats/presentation.js +1 -0
- package/dist/packages/stats/regions.d.ts +9 -0
- package/dist/packages/stats/regions.js +1 -1
- package/dist/packages/stats/renderSummaryReport.d.ts +15 -2
- package/dist/packages/stats/renderSummaryReport.js +32 -27
- package/dist/packages/stats/reportHtml.d.ts +54 -9
- package/dist/packages/stats/reportHtml.js +130 -47
- package/dist/packages/stats/synthesis.d.ts +66 -0
- package/dist/packages/stats/synthesis.js +2 -0
- package/dist/packages/stats/types.d.ts +1 -1
- package/dist/packages/stats/yieldRows.d.ts +31 -0
- package/dist/packages/stats/yieldRows.js +1 -0
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/dist/packages/stats/findingsNarrative.d.ts +0 -3
- package/dist/packages/stats/findingsNarrative.js +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{normalizeInput as et}from"./normalizeInput.js";import{computeCapability as tt,computeTestFlagYield as nt,computeRegionYield as it}from"./summaryFigures.js";import{binPassSets as Fe}from"../renderer/binColors.js";import{diePassStatus as Pe,isYieldEligibleDie as ge,getDieKey as Ce,isPositionedDie as xe,positionKey as pe}from"../core/dies.js";import{dieHasValues as st,testsPresent as Le,testValue as I}from"../core/dieTable.js";import{getTestPassStatus as ae,isParametricTest as W}from"../renderer/buildWaferMap.js";import{markedTestLabel as q,derivedFields as re}from"../renderer/testLabel.js";import{buildQuadrantRegions as ot,buildReticlePositionRegions as at,buildRingRegions as rt,buildSectorRegions as ft,buildTestSiteRegions as lt,sectorCompassNames as Ve,areQuadrantsAdjacent as ct,parseRegionKey as Y}from"./regions.js";import{buildClusterFindings as ut}from"./clusterDetection.js";import{classifyPattern as dt}from"./patternClassification.js";import{fiveNumberSummary as mt,normalCdf as Ee}from"./math.js";import{mean as fe,clamp01 as Ke}from"../core/utils.js";import{classifySpec as be,isOutOfSpec as Oe}from"../renderer/spec.js";const ht={significanceLevel:{min:1e-6,max:1},minimumEffectSize:{min:0,max:1},minimumRelativeEffect:{min:0,max:Number.MAX_SAFE_INTEGER}},Te=[4,8,16,32];export function resolveOptions(t){const i=Object.fromEntries(Object.entries(t).filter(([,r])=>r!==void 0)),s={...A,...i},n=[],e=[];for(const r of["significanceLevel","minimumEffectSize","minimumRelativeEffect"]){const{min:u,max:a}=ht[r],c=s[r];if(typeof c!="number"||!Number.isFinite(c)){e.push(`${r}=${String(c)} is not a finite number (using ${A[r]})`),s[r]=A[r];continue}const f=Math.min(a,Math.max(u,c));f!==c&&(e.push(`${r}=${c} is outside ${u}\u2013${a} (using ${f})`),s[r]=f)}if(Te.includes(s.sectorCount)||(e.push(`sectorCount=${s.sectorCount} is not one of ${Te.join(", ")} (using ${A.sectorCount})`),s.sectorCount=A.sectorCount),e.length){const r=`analyzeWaferMap: ${e.join("; ")}. The analysis ran with the corrected values.`;console.warn(`[wafermap] ${r}`),n.push({code:"analysis-option-corrected",message:r,severity:"warning"})}const l=["passBins","ringCount"].filter(r=>i[r]!==void 0);if(l.length){const r=`analyzeWaferMap: ${l.join(" and ")} ${l.length>1?"are":"is"} no longer an analysis option and was ignored. Set ${l.length>1?"them":"it"} on buildWaferMap; the analysis used the value the map was built with.`;console.warn(`[wafermap] ${r}`),n.push({code:"analysis-option-corrected",message:r,severity:"warning"})}const o=["enableYieldAnalysis","enableHardBinAnalysis","enableSoftBinAnalysis","enableReticlePositionAnalysis","enableTestSiteAnalysis","enableClusterAnalysis","enableAngularAnalysis","enablePatternClassification"].filter(r=>i[r]!==void 0);if(o.length){const r=`analyzeWaferMap: ${o.join(", ")} ${o.length>1?"are":"is"} no longer an option and was ignored: every analysis now runs. Filter the findings (filterFindings) to show fewer.`;console.warn(`[wafermap] ${r}`),n.push({code:"analysis-option-corrected",message:r,severity:"warning"})}return{resolved:s,warnings:n}}const A={ringCount:4,passBins:[1],significanceLevel:.05,minimumEffectSize:.2,minimumRelativeEffect:1,minimumSampleSize:5,enableTestValueAnalysis:!1,computePerTestStats:!1,sectorCount:8,minimumClusterSize:5};function gt(t,i){const s={...t};return s.minimumClusterSize=Math.max(5,Math.round(i*.003)),s}function pt(t){return ge(t)?t.hbin!==void 0||t.sbin!==void 0||st(t):!1}function bt(t,i,s){if(!t||i)return;const n=(s??[]).filter(e=>W(e)&&(e.limitLow!==void 0||e.limitHigh!==void 0));if(n.length!==0)return e=>{for(const l of n)if(Oe(be(I(e,l.testNumber),l)))return!0;return!1}}function yt(t,i,s){const n=new Set,e=new Set,l=new Set,o=new Map,r=new Map;for(const u of Le(t))n.add(u);for(const u of t)u.hbin!==void 0&&e.add(u.hbin),u.sbin!==void 0&&l.add(u.sbin),ge(u)&&(u.hbin!==void 0&&o.set(u.hbin,(o.get(u.hbin)??0)+1),u.sbin!==void 0&&r.set(u.sbin,(r.get(u.sbin)??0)+1));return{totalDies:t.length,analyzedDies:i,excludedDies:t.length-i,yieldPercent:s,testsConsidered:[...n].sort((u,a)=>u-a),hardBinsConsidered:[...e].sort((u,a)=>u-a),softBinsConsidered:[...l].sort((u,a)=>u-a),...o.size?{hardBinCounts:Object.fromEntries(o)}:{},...r.size?{softBinCounts:Object.fromEntries(r)}:{}}}function St(t,i){if(!i?.length)return;const s=i.filter(e=>W(e)&&(e.limitLow!==void 0||e.limitHigh!==void 0));if(!s.length)return;const n=[];for(const e of s){const l=e.testNumber;let o=0,r=0,u=0,a=0;for(const c of t){if(c.partial||c.edgeExcluded)continue;const f=be(I(c,l),e);f!==null&&(a++,f==="failLow"?r++:f==="failHigh"?u++:o++)}n.push({testNumber:l,label:e.name,passDies:o,failLowDies:r,failHighDies:u,totalDies:a,yieldPercent:a>0?o/a*100:null})}return n.length?n:void 0}export function computeFunctionalYield(t,i){if(!i?.length)return;const s=i.filter(e=>!W(e));if(!s.length)return;const n=[];for(const e of s){const l=e.testNumber;let o=0,r=0;for(const a of t){if(a.partial||a.edgeExcluded)continue;const c=ae(a,l,e);c!==void 0&&(c?o++:r++)}const u=o+r;n.push({testNumber:l,label:q(e,l),...re(e),passDies:o,failDies:r,totalDies:u,passRatePercent:u>0?o/u*100:null})}return n.length?n:void 0}function wt(t,i,s,n){const e=[];for(const l of i){const o=[];for(const f of t){if(f.partial||f.edgeExcluded)continue;const b=I(f,l);b!==void 0&&Number.isFinite(b)&&o.push(b)}if(o.length<n)continue;const r=fe(o),u=Math.sqrt(De(o,r)),a=mt(Float64Array.from(o)),c=s?.find(f=>f.testNumber===l)?.name??String(l);e.push({testNumber:l,label:c,count:o.length,min:a.min,max:a.max,mean:r,stddev:u,median:a.median,q1:a.q1,q3:a.q3})}return e.length?e:void 0}export function twoProportionZ(t,i,s,n){const e=(t+s)/(i+n),l=e*(1-e)*(1/i+1/n);return!Number.isFinite(l)||l<=0?0:(t/i-s/n)/Math.sqrt(l)}export function zPValue(t){return Ke(2*(1-Ee(Math.abs(t))))}function T(t,i,s,n){const e=twoProportionZ(t,i,s,n);return e===0?1:zPValue(e)}export function adjustPValues(t){const i=new Map;for(const s of t){const n=`${s.variable.kind}:${s.comparison.family}`,e=i.get(n)??[];e.push(s),i.set(n,e)}for(const s of i.values()){const n=[...s].filter(o=>o.stats.pValue!==void 0).sort((o,r)=>(o.stats.pValue??1)-(r.stats.pValue??1)),e=n.length;let l=1;for(let o=e-1;o>=0;o--){const r=n[o],u=r.stats.pValue??1,a=Math.min(1,Math.min(l,u*e/(o+1)));r.stats.adjustedPValue=a,l=a}}return t}export function rateRelativeEffect(t){const i=(t.passRate?t.n-t.hits:t.hits)/t.n,s=(t.passRate?t.restN-t.restHits:t.restHits)/t.restN;return s===0?i===0?0:1/0:Math.abs(i-s)/s}export function severityForFinding(t,i,s){const n=Math.abs(i),e=s;return t<=.01&&(n>=.3||e>=2.5)?"unusual":t<=.05&&(n>=.2||e>=1.5)?"notable":"info"}export function severityForScore(t,i){return t<=.01&&Math.abs(i)>=.5?"unusual":t<=.05&&Math.abs(i)>=.15?"notable":"info"}function le(t){return t==="reticle-position"?"other reticle positions":t==="test-site"?"other test sites":"the rest of the map"}function _(t){return t==="reticle-position"?"Other reticle positions":t==="test-site"?"Other test sites":"Rest of map"}function ye(t,i,s){const n=(Math.abs(i)*100).toFixed(1);return`${t} yield is ${n} percentage points ${i>0?"higher":"lower"} than ${le(s)}`}function Se(t,i){return i==="quadrant"&&!t.startsWith("Quadrant")?`quadrant ${t}`:t}function He(t){return/^(Rings|Sectors|Quadrants)\b/.test(t)?"have":"has"}function we(t,i,s,n){const e=Se(t,n),l=(Math.abs(s)*100).toFixed(1);return`${e} ${He(e)} ${i} occurrence ${l} percentage points ${s>0?"higher":"lower"} than ${le(n)}`}function ve(t,i,s,n){const e=Se(t,n),l=(Math.abs(s)*100).toFixed(1);return`${e} ${He(e)} ${i} pass rate ${l} percentage points ${s>0?"higher":"lower"} than ${le(n)}`}function $e(t,i,s,n,e,l){const o=Se(t,e),r=le(e),u=s>0?"higher":"lower";if(n!==void 0&&Number.isFinite(n)){const c=(Math.abs(n)*100).toFixed(1);return`${o} mean ${i} is ${c}% ${u} than ${r}`}const a=l?` ${l}`:"";return`${o} mean ${i} is ${Math.abs(s).toPrecision(3)}${a} ${u} than ${r}`}function qe(t,i,s){const n=i?.find(e=>e.bin===t);return n?.name?`${s} ${t} (${n.name})`:`${s} ${t}`}function vt(t,i){const s=i?.find(n=>n.testNumber===t);return{label:q(s,t),unit:s?.unit,...re(s)}}const Ae=new WeakMap,je=new WeakMap,Ie=new WeakMap;function Z(t,i){const s=i.highlight.dieKeys;return s&&Ie.set(s,t),i}let Q=null,J;const We=new WeakMap;export function candidatesOf(t){return We.get(t)}function Ye(t,i,s,n){if(!Q)return;const e=Qe(t);if(!e)return;const l=t.id.slice(0,t.id.indexOf(":")),o=l==="specLimit"?"limitFail":l,{kind:r,bin:u,index:a}=t.variable;Q.push({key:[o,r,u??"",a??"",e.family,e.key].join("|"),source:o,variable:t.variable,label:t.variable.label,comparison:t.comparison,region:e,z:i,delta:t.effect.absoluteDelta??0,effectSize:t.effect.effectSize??0,relativeDelta:t.effect.relativeDelta,rate:s,mean:n})}export function describeRegional(t,i,s){const n=t.comparison.left,e=t.region.family;switch(t.source){case"yield":return ye(n,i,e);case"hardBin":case"softBin":return we(n,t.label,i,e);case"functional":return ve(n,t.label.replace(/ pass rate$/,""),i,e);case"limitFail":return _e(n,t.label,i);case"test":return $e(n,t.label,i,s,e,t.variable.unit)}}function _e(t,i,s){return`${t} limit fail rate for ${i} is ${(Math.abs(s)*100).toFixed(1)} pp ${s>0?"higher":"lower"} than the rest of the wafer`}const U=new WeakMap;function ce(t,i){return U.set(t,i),t}function Qe(t){const i=t.highlight.dieKeys;return i?Ie.get(i):void 0}const Ue=new WeakMap;function ke(t){let i=Ue.get(t);return i||Ue.set(t,i=t.map(pe)),i}function $t(t){let i=Ae.get(t);return i||Ae.set(t,i=t.map(Ce)),i}function ue(t,i){let s=je.get(t);s||je.set(t,s={members:new Set(t),byFamily:new WeakMap});const n=s.byFamily.get(i);if(n)return n;const{members:e}=s,l=new Map;for(const o of i)l.set(o.key,o.dies.filter(r=>e.has(r)));return s.byFamily.set(i,l),l}function de(t,i){adjustPValues(t);for(const n of t){const e=U.get(n);Ye(n,twoProportionZ(e.hits,e.n,e.restHits,e.restN),e)}const s=t.filter(n=>passesRateGate(n.stats.adjustedPValue??n.stats.pValue??1,n.effect.absoluteDelta??0,rateRelativeEffect(U.get(n)),i));return s.filter(n=>!kt(n,s,i)).map(n=>({...n,severity:severityForFinding(n.stats.adjustedPValue??n.stats.pValue??1,n.effect.absoluteDelta??0,rateRelativeEffect(U.get(n)))}))}export function passesRateGate(t,i,s,n){return t<=n.significanceLevel&&(Math.abs(i)>=n.minimumEffectSize||s>=n.minimumRelativeEffect)}function kt(t,i,s){const n=U.get(t);if(!n)return!1;const e=t.stats.pValue??1;let{restHits:l,restN:o}=n;for(const c of Ge(t,i)){const f=U.get(c);f&&(l-=f.hits,o-=f.n)}if(o===n.restN)return!1;if(o<s.minimumSampleSize)return!0;const r=n.hits/n.n-l/o;if(Math.sign(r)!==Math.sign(t.effect.absoluteDelta??0))return!0;const u=e>0?(t.stats.adjustedPValue??e)/e:1,a=Math.min(1,T(n.hits,n.n,l,o)*u);return!passesRateGate(a,r,rateRelativeEffect({...n,restHits:l,restN:o}),s)}function Ge(t,i){const s=t.stats.pValue??1;return i.filter(n=>n!==t&&n.effect.direction!==t.effect.direction&&n.variable.kind===t.variable.kind&&n.variable.bin===t.variable.bin&&n.variable.index===t.variable.index&&n.comparison.family===t.comparison.family&&(n.stats.pValue??1)<s)}const ze=new WeakMap;export function welchOfSums(t,i,s,n){const e=(l,o,r)=>r>1?Math.max(0,l-o*o/r)/(r-1):0;return welchFromStats(t.n,t.sum/t.n,e(t.sq,t.sum,t.n),i,s/i,e(n,s,i))}function zt(t,i,s){const n=ze.get(t);if(!n)return!1;let{restN:e,restSum:l,restSq:o}=n;for(const b of Ge(t,i)){const p=ze.get(b);p&&(e-=p.n,l-=p.sum,o-=p.sq)}if(e===n.restN)return!1;if(e<s.minimumSampleSize)return!0;const{pValue:r,effectSize:u,delta:a}=welchOfSums(n,e,l,o);if(Math.sign(a)!==Math.sign(t.effect.absoluteDelta??0))return!0;const c=t.stats.pValue??1,f=c>0?(t.stats.adjustedPValue??c)/c:1;return!(Math.min(1,r*f)<=s.significanceLevel&&Math.abs(u)>=s.minimumEffectSize)}function ee(t,i,s,n){const e=new Set(s),l=ue(t,i),o=new Map,r=new Map;for(const[a,c]of l){let f=0,b=0;for(const p of c)p.hbin!==void 0&&(b++,e.has(p.hbin)&&f++);o.set(a,f),r.set(a,b)}const u=[];for(const a of i){const c=r.get(a.key)??0,f=o.get(a.key)??0;let b=0,p=0;for(const[D,w]of r)D!==a.key&&(b+=w,p+=o.get(D)??0);if(c<n.minimumSampleSize||b<n.minimumSampleSize)continue;const g=f/c,m=p/b,y=g-m,z=T(f,c,p,b);u.push(ce(Z(a,{id:`yield:${a.key}`,level:"wafer",severity:"info",variable:{kind:"yield",label:"Yield"},comparison:{family:a.family,left:a.label,right:_(a.family)},effect:{direction:y===0?"different":y>0?"higher":"lower",absoluteDelta:y,relativeDelta:m===0?void 0:y/m,effectSize:y},stats:{method:"two-proportion-z",pValue:z,sampleSizeLeft:c,sampleSizeRight:b},summary:ye(a.label,y,a.family),highlight:{kind:"region",regionFamily:a.family,regionKeys:[a.key],dieKeys:[...a.dieKeys]}}),{hits:f,n:c,restHits:p,restN:b,passRate:!0}))}return de(u,n)}function E(t,i,s,n,e,l){const o=g=>s==="soft"?g.sbin:g.hbin,r=[...new Set(t.map(o).filter(g=>g!==void 0))].sort((g,m)=>g-m),u=ue(t,i),a=new Map,c=new Map;for(const[g,m]of u){const y=new Map;for(const z of m){const D=o(z);D!==void 0&&y.set(D,(y.get(D)??0)+1)}a.set(g,y),c.set(g,m.length)}const f=[],b=e==="hardBin"?"HBin":"SBin",p=s==="hard"?new Set(l.passBins):l.softPassBins??Fe(t,l.passBins).soft;for(const g of i){const m=c.get(g.key)??0,y=a.get(g.key);let z=0;const D=new Map;for(const[w,h]of a)if(w!==g.key){z+=c.get(w)??0;for(const[v,S]of h)D.set(v,(D.get(v)??0)+S)}if(!(m<l.minimumSampleSize||z<l.minimumSampleSize))for(const w of r){const h=y.get(w)??0,v=D.get(w)??0,S=h/m,$=v/z,k=S-$,M=T(h,m,v,z),R=qe(w,n,b);f.push(ce(Z(g,{id:`${e}:${w}:${g.key}`,level:"wafer",severity:"info",variable:{kind:e,bin:w,label:R},comparison:{family:g.family,left:g.label,right:_(g.family)},effect:{direction:k===0?"different":k>0?"higher":"lower",absoluteDelta:k,relativeDelta:$===0?void 0:k/$,effectSize:k},stats:{method:"two-proportion-z",pValue:M,sampleSizeLeft:m,sampleSizeRight:z},summary:we(g.label,R,k,g.family),highlight:{kind:"bin",bin:w,regionKeys:[g.key],dieKeys:[...g.dieKeys]}}),{hits:h,n:m,restHits:v,restN:z,passRate:p.has(w)}))}}return de(f,l)}function te(t,i,s,n){const e=(s??[]).filter(c=>!W(c));if(!e.length)return[];const l=e.map(c=>c.testNumber),o=ue(t,i),r=new Map,u=new Map;for(const[c,f]of o){const b=new Uint32Array(e.length),p=new Uint32Array(e.length);for(const g of f)for(let m=0;m<e.length;m++){const y=ae(g,l[m],e[m]);y!==void 0&&(p[m]++,y&&b[m]++)}r.set(c,b),u.set(c,p)}const a=[];for(const c of i){const f=r.get(c.key),b=u.get(c.key);for(let p=0;p<e.length;p++){let g=0,m=0;for(const[$,k]of u)$!==c.key&&(m+=k[p],g+=r.get($)[p]);const y=b[p],z=f[p];if(y<n.minimumSampleSize||m<n.minimumSampleSize)continue;const D=z/y,w=g/m,h=D-w,v=T(z,y,g,m),S=l[p];a.push(ce(Z(c,{id:`functional:${S}:${c.key}`,level:"wafer",severity:"info",variable:{kind:"functionalTest",index:S,label:`${q(e[p],S)} pass rate`,...re(e[p])},comparison:{family:c.family,left:c.label,right:_(c.family)},effect:{direction:h===0?"different":h>0?"higher":"lower",absoluteDelta:h,relativeDelta:w===0?void 0:h/w,effectSize:h},stats:{method:"two-proportion-z",pValue:v,sampleSizeLeft:y,sampleSizeRight:m},summary:ve(c.label,q(e[p],S),h,c.family),highlight:{kind:"region",regionFamily:c.family,regionKeys:[c.key],dieKeys:[...c.dieKeys]}}),{hits:z,n:y,restHits:g,restN:m,passRate:!0}))}}return de(a,n)}function De(t,i){return t.length<2?0:t.reduce((s,n)=>s+(n-i)**2,0)/(t.length-1)}export function welchFromStats(t,i,s,n,e,l){const o=Math.sqrt(s/t+l/n),r=i-e;if(!Number.isFinite(o)||o===0)return{pValue:1,effectSize:0,delta:r,z:0};const u=r/o,a=Math.sqrt(Math.max(0,(s+l)/2)),c=a===0?r:r/a;return{pValue:Ke(2*(1-Ee(Math.abs(u)))),effectSize:c,delta:r,z:u}}function Dt(t,i){const s=fe(t),n=fe(i);return welchFromStats(t.length,s,De(t,s),i.length,n,De(i,n))}const Xe=250;function Ze(t,i,s){const n=o=>o.filter(r=>W(s?.find(u=>u.testNumber===r)));if(i)return{testNumbers:n(i.slice().sort((o,r)=>o-r))};const e=Le(t,"values");if(e.length>Xe){const o=`Test-value analysis was skipped: more than ${Xe} tests were found in the die data, so no test findings were computed. Pass testNumbers: [...] to analyse specific tests.`;return console.warn(`[wafermap] analyzeWaferMap: ${o}`),{testNumbers:[],warning:{code:"test-count-capped",message:o,severity:"warning"}}}return{testNumbers:n(e)}}function ne(t,i,s,n){const e=Ze(t,n.testNumbers,s);if(e.warning)return{findings:[],warning:e.warning};const l=e.testNumbers,o=new Map;for(let m=0;m<i.length;m++)for(const y of ke(i[m].dies))o.set(y,m);const r=i.length,u=[],a=[];for(const m of t){const y=o.get(pe(m));y!==void 0&&(u.push(m),a.push(y))}const c=[],f=new Float64Array(r),b=new Float64Array(r),p=new Float64Array(r);for(const m of l){f.fill(0),b.fill(0),p.fill(0);let y=0,z=0,D=0,w;for(let h=0;h<u.length;h++){const v=u[h],S=I(v,m);if(S===void 0)continue;w===void 0&&(w=S);const $=S-w,k=a[h];f[k]+=1,b[k]+=$,p[k]+=$*$,y+=1,z+=$,D+=$*$}if(w!==void 0)for(let h=0;h<r;h++){const v=f[h],S=y-v;if(v<n.minimumSampleSize||S<n.minimumSampleSize)continue;const $=b[h]/v,M=(z-b[h])/S,R=$+w,P=M+w,F=v>1?Math.max(0,p[h]-v*$*$)/(v-1):0,d=S>1?Math.max(0,D-p[h]-S*M*M)/(S-1):0,{pValue:N,effectSize:B,delta:L,z:V}=welchFromStats(v,R,F,S,P,d),x=i[h],{label:ie,unit:X,...me}=vt(m,s),se=P!==0?L/Math.abs(P):void 0,K=Z(x,{id:`test:${m}:${x.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:m,label:ie,unit:X,...me},comparison:{family:x.family,left:x.label,right:_(x.family)},effect:{direction:L===0?"different":L>0?"higher":"lower",absoluteDelta:L,relativeDelta:se,effectSize:B},stats:{method:"welch-z-approx",pValue:N,sampleSizeLeft:v,sampleSizeRight:S},summary:$e(x.label,ie,L,se,x.family,X),highlight:{kind:"region",regionFamily:x.family,regionKeys:[x.key],dieKeys:[...x.dieKeys]}}),oe={n:v,sum:b[h],sq:p[h],restN:S,restSum:z-b[h],restSq:D-p[h]};ze.set(K,oe),Ye(K,V,void 0,oe),c.push(K)}}adjustPValues(c);const g=c.filter(m=>{const y=m.stats.adjustedPValue??m.stats.pValue??1,z=Math.abs(m.effect.effectSize??0);return y<=n.significanceLevel&&z>=n.minimumEffectSize});return{activeTestNumbers:l,findings:g.filter(m=>!zt(m,g,n)).map(m=>({...m,severity:severityForScore(m.stats.adjustedPValue??m.stats.pValue??1,m.effect.effectSize??0)}))}}function Rt(t,i,s,n){if(!s?.length)return[];const e=s.filter(o=>W(o)&&(o.limitLow!==void 0||o.limitHigh!==void 0));if(!e.length)return[];const l=[];for(const o of e){const r=o.testNumber;for(const u of i){const a=ue(t,u),c=[],f=new Map;let b=0,p=0;for(const[g,m]of a){let y=0,z=0;for(const D of m){const w=I(D,r);w!==void 0&&(y++,Oe(be(w,o))&&z++)}f.set(g,{valid:y,fail:z}),b+=y,p+=z}for(const g of u){const m=f.get(g.key),y=m.valid,z=b-m.valid;if(y<n.minimumSampleSize||z<n.minimumSampleSize)continue;const D=m.fail,w=p-m.fail,h=D/y,v=w/z,S=h-v,$=T(D,y,w,z);c.push(ce(Z(g,{id:`specLimit:${r}:${g.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:r,label:q(o,r),unit:o.unit,...re(o)},comparison:{family:g.family,left:g.label,right:_(g.family)},effect:{direction:S===0?"different":S>0?"higher":"lower",absoluteDelta:S,relativeDelta:v===0?void 0:S/v,effectSize:S},stats:{method:"two-proportion-z",pValue:$,sampleSizeLeft:y,sampleSizeRight:z},summary:_e(g.label,q(o,r),S),highlight:{kind:"region",regionFamily:g.family,regionKeys:[g.key],dieKeys:[...g.dieKeys]}}),{hits:D,n:y,restHits:w,restN:z,passRate:!1}))}l.push(...de(c,n))}}return l}function Re(t,i){return t==="softBin"?i.softEligibleDies:t==="test"?i.testDies:i.eligibleDies}function j(t){return t.highlight.regionKeys??[]}function Mt(t){const i=t.variable.bin??t.variable.index??"";return`${t.comparison.family}\0${t.variable.kind}\0${i}\0${t.effect.direction}`}function G(t,i){const s=j(t)[0]??"",n=Y(s);return n.family==="ring"?n.ring??0:n.family==="sector"?Ve(i).indexOf(n.sector??""):0}function Bt(t,i,s){if(i==="quadrant"){const o=new Map;for(const c of t){const f=Y(j(c)[0]??"").quadrant;f&&o.set(f,c)}const r=[...o.keys()],u=new Set,a=[];for(const c of r){if(u.has(c))continue;const f=[],b=[c];for(u.add(c);b.length;){const p=b.pop();f.push(p);for(const g of r)!u.has(g)&&ct(p,g)&&(u.add(g),b.push(g))}a.push(f.map(p=>o.get(p)))}return a}const n=[...t].sort((o,r)=>G(o,s)-G(r,s)),e=[];let l=[];for(const o of n){if(l.length===0){l.push(o);continue}const r=G(l[l.length-1],s);G(o,s)-r===1?l.push(o):(e.push(l),l=[o])}if(l.length&&e.push(l),i==="sector"&&e.length>1){const r=Ve(s).length,u=G(e[0][0],s),a=G(e[e.length-1][e[e.length-1].length-1],s);if(u===0&&a===r-1){const c=e.pop();e[0]=[...c,...e[0]]}}return e}function Nt(t,i){const s=t.map(o=>Y(j(o)[0]??"").ring).filter(o=>o!==void 0).sort((o,r)=>o-r),n=s[0],e=s[s.length-1];let l="";return n===e&&n===1&&i>1?l=" (core)":n===e&&n===i&&i>1&&(l=" (edge)"),n===e?`Ring ${n}${l}`:`Rings ${n}\u2013${e}`}function Ft(t,i){const s=t.map(n=>Y(j(n)[0]??"").sector??"");return s.length===1?`Sector ${s[0]}`:`Sectors ${s[0]}\u2013${s[s.length-1]}`}function Pt(t){const i=t.map(s=>Y(j(s)[0]??"").quadrant??"");return i.length===1?i[0]:i.length===2?`Quadrants ${i[0]} & ${i[1]}`:`Quadrants ${i.slice(0,-1).join(", ")} & ${i[i.length-1]}`}function Me(t){return[...new Set(t)]}function Ct(t,i){const s=t[0],n=s.comparison.family,e=s.variable.kind,l=Me(t.flatMap(j));let o,r;const u=t.map(Qe);if(u.every(h=>h!==void 0)){const h=new Set;o=[];for(const S of u){const $=ke(S.dies);for(let k=0;k<$.length;k++)h.has($[k])||(h.add($[k]),o.push(S.dieKeys[k]))}const v=ke(Re(s.variable.kind,i));r=S=>h.has(v[S])}else{o=Me(t.flatMap(S=>S.highlight.dieKeys??[]));const h=new Set(o),v=$t(Re(s.variable.kind,i));r=S=>h.has(v[S])}const a=n==="ring"?Nt(t,i.ringCount):n==="sector"?Ft(t,i.sectorCount):Pt(t),c=Re(e,i),f=[],b=[];for(let h=0;h<c.length;h++)(r(h)?f:b).push(c[h]);let p,g,m,y,z;if(e==="test"){const h=s.variable.index,v=d=>I(d,h),S=f.map(v).filter(d=>d!==void 0),$=b.map(v).filter(d=>d!==void 0),{pValue:k,effectSize:M,delta:R}=Dt(S,$),P=fe($),F=P!==0?R/Math.abs(P):void 0;p={direction:R===0?"different":R>0?"higher":"lower",absoluteDelta:R,relativeDelta:F,effectSize:M},g={method:"welch-z-approx",pValue:k,sampleSizeLeft:S.length,sampleSizeRight:$.length},m=severityForScore(k,M),y=$e(a,s.variable.label,R,F,n,s.variable.unit),z=`test:${h}`}else if(e==="yield"){const h=new Set(i.passBins),v=f.filter(B=>B.hbin!==void 0),S=b.filter(B=>B.hbin!==void 0),$=v.length,k=S.length,M=v.filter(B=>h.has(B.hbin)).length,R=S.filter(B=>h.has(B.hbin)).length,P=$>0?M/$:0,F=k>0?R/k:0,d=P-F,N=T(M,$,R,k);p={direction:d===0?"different":d>0?"higher":"lower",absoluteDelta:d,relativeDelta:F===0?void 0:d/F,effectSize:d},g={method:"two-proportion-z",pValue:N,sampleSizeLeft:$,sampleSizeRight:k},m=severityForFinding(N,d,rateRelativeEffect({hits:M,n:$,restHits:R,restN:k,passRate:!0})),y=ye(a,d,n),z="yield"}else if(e==="functionalTest"){const h=s.variable.index,v=i.testDefs?.find(N=>N.testNumber===h);let S=0,$=0,k=0,M=0;for(const N of f){const B=ae(N,h,v);B!==void 0&&($++,B&&S++)}for(const N of b){const B=ae(N,h,v);B!==void 0&&(M++,B&&k++)}const R=$>0?S/$:0,P=M>0?k/M:0,F=R-P,d=T(S,$,k,M);p={direction:F===0?"different":F>0?"higher":"lower",absoluteDelta:F,relativeDelta:P===0?void 0:F/P,effectSize:F},g={method:"two-proportion-z",pValue:d,sampleSizeLeft:$,sampleSizeRight:M},m=severityForFinding(d,F,rateRelativeEffect({hits:S,n:$,restHits:k,restN:M,passRate:!0})),y=ve(a,q(v,h),F,n),z=`functional:${h}`}else{const h=s.variable.bin,v=d=>e==="softBin"?d.sbin:d.hbin,S=f.filter(d=>v(d)===h).length,$=b.filter(d=>v(d)===h).length,k=S/f.length,M=$/b.length,R=k-M,P=T(S,f.length,$,b.length);p={direction:R===0?"different":R>0?"higher":"lower",absoluteDelta:R,relativeDelta:M===0?void 0:R/M,effectSize:R},g={method:"two-proportion-z",pValue:P,sampleSizeLeft:f.length,sampleSizeRight:b.length},m=severityForFinding(P,R,rateRelativeEffect({hits:S,n:f.length,restHits:$,restN:b.length,passRate:!1}));const F=e==="softBin"?i.sbinDefs:i.hbinDefs;y=we(a,qe(h,F,e==="softBin"?"SBin":"HBin"),R,n),z=`${e}:${h}`}const D=Me(l.map(h=>h.replace(`${n}:`,""))).join("-"),w=e==="hardBin"||e==="softBin"?{kind:"bin",bin:s.variable.bin,regionKeys:l,dieKeys:o}:{kind:"region",regionFamily:n,regionKeys:l,dieKeys:o};return{...s,id:`${z}:${n}:${D}`,severity:m,comparison:{family:n,left:a,right:_(n)},effect:p,stats:g,summary:y,highlight:w,relatedIds:t.map(h=>h.id)}}function xt(t,i){const s=new Set(["ring","quadrant","sector"]),n=[],e=new Map;for(const o of t){if(!s.has(o.comparison.family)){n.push(o);continue}const r=Mt(o),u=e.get(r)??[];u.push(o),e.set(r,u)}const l=[];for(const o of e.values()){if(o.length===1){l.push(o[0]);continue}const r=o[0].comparison.family;for(const u of Bt(o,r,i.sectorCount))l.push(u.length===1?u[0]:Ct(u,i))}return[...n,...l]}function Lt(t){const i=new Map,s=new Map;for(const e of t){const l=pe(e);e.hbin!==void 0&&e.hbin!==null&&(i.get(e.hbin)??i.set(e.hbin,new Set).get(e.hbin)).add(l),e.sbin!==void 0&&e.sbin!==null&&(s.get(e.sbin)??s.set(e.sbin,new Set).get(e.sbin)).add(l)}const n=new Set;for(const[e,l]of i)for(const[o,r]of s){if(l.size!==r.size)continue;let u=!0;for(const a of l)if(!r.has(a)){u=!1;break}u&&n.add(`${e}:${o}`)}return n}function Vt(t,i){const s=new Set(i);let n;for(const e of t){const l=Pe(e,s);if(l!==void 0&&l){if(e.sbin==null||n!==void 0&&e.sbin!==n)return;n=e.sbin}}if(n!==void 0){for(const e of t)if(e.sbin===n&&Pe(e,s)!==!0)return;return n}}function Et(t){return`${t.comparison.family}\0${t.comparison.left}`}export function redundancyFacts(t,i){return{coincident:Lt(t),yieldSoftBin:Vt(t,i),passBins:i}}export function collapseRedundantFindings(t,i){const{coincident:s,yieldSoftBin:n,passBins:e}=i,l=(u,a)=>{u.absorbedIds=[...u.absorbedIds??[],a.id]},o=new Map;for(const u of t){const a=Et(u);(o.get(a)??o.set(a,[]).get(a)).push(u)}const r=new Set;for(const u of o.values()){for(const c of u)if(!(c.variable.kind!=="hardBin"||c.variable.bin===void 0))for(const f of u){if(f.variable.kind!=="softBin"||f.variable.bin===void 0||r.has(f.id)||!s.has(`${c.variable.bin}:${f.variable.bin}`)||f.effect.direction!==c.effect.direction)continue;l(c,f),r.add(f.id);const b=c.variable.label.replace(/^HBin\s+/,""),p=f.variable.label.replace(/^SBin\s+/,""),g=b===p?`HBin and SBin ${b} (same dies)`:`${c.variable.label} and ${f.variable.label} (same dies)`;c.summary=c.summary.replace(c.variable.label,g),c.variable.label=g;break}const a=u.find(c=>c.variable.kind==="yield");if(a){for(const c of u)if(!(c===a||r.has(c.id)||!(c.variable.kind==="hardBin"&&e.length===1&&c.variable.bin===e[0]||c.variable.kind==="softBin"&&c.variable.bin===n))){l(a,c),r.add(c.id);for(const b of c.absorbedIds??[])a.absorbedIds=[...a.absorbedIds??[],b]}}}}export function analyzeWaferMap(t,i={}){const s=Q,n=J,e=[];Q=e,J=void 0;try{const l=Kt(t,i);return We.set(l,{candidates:e,facts:J}),l}finally{Q=s,J=n}}function Kt(t,i){const{resolved:s,warnings:n}=resolveOptions(i),e=et(t),l={...s,passBins:e.passBins??A.passBins,ringCount:e.ringCount??A.ringCount},o=e.isLotStack,r=e.aggrMethod,u=!o||r==="mode"||r==="countBin"||r==="percent",a=e.dies.filter(d=>pt(d)),c=a.filter(xe),f=gt(l,a.length);f.softPassBins=Fe(a,f.passBins).soft;const b=e.dies.filter(d=>ge(d)),p=b.filter(xe),g=rt(p,e.wafer,f.ringCount),m=ot(p,e.wafer,f.ringCount),y=at(p,e.reticleConfig),z=lt(b,!1),D=ft(p,e.wafer,f.sectorCount),w=[];u&&w.push(...ee(a,g,f.passBins,f),...ee(a,m,f.passBins,f),...ee(a,y,f.passBins,f),...ee(a,z,f.passBins,f),...ee(a,D,f.passBins,f)),u&&w.push(...E(a,g,"hard",e.hbinDefs,"hardBin",f),...E(a,m,"hard",e.hbinDefs,"hardBin",f),...E(a,y,"hard",e.hbinDefs,"hardBin",f),...E(a,z,"hard",e.hbinDefs,"hardBin",f),...E(a,D,"hard",e.hbinDefs,"hardBin",f));{const d=a.filter(N=>N.sbin!==void 0);w.push(...E(d,g,"soft",e.sbinDefs,"softBin",f),...E(d,m,"soft",e.sbinDefs,"softBin",f),...E(d,y,"soft",e.sbinDefs,"softBin",f),...E(d,z,"soft",e.sbinDefs,"softBin",f),...E(d,D,"soft",e.sbinDefs,"softBin",f))}const h=[...n];let v;if(f.enableTestValueAnalysis){const d=ne(a,g,e.testDefs,f),N=ne(a,m,e.testDefs,f),B=ne(a,y,e.testDefs,f),L=ne(a,z,e.testDefs,f),V=ne(a,D,e.testDefs,f);w.push(...d.findings,...N.findings,...B.findings,...L.findings,...V.findings),d.warning&&h.push(d.warning),v=d.activeTestNumbers,w.push(...Rt(a,[g,m,y,z,D],e.testDefs,f)),w.push(...te(a,g,e.testDefs,f),...te(a,m,e.testDefs,f),...te(a,y,e.testDefs,f),...te(a,z,e.testDefs,f),...te(a,D,e.testDefs,f))}{const d=bt(o,u,e.testDefs);(!o||u||d!==void 0)&&w.push(...ut(c,e.wafer,{...f,isFailingDie:d}))}const S=xt(w,{eligibleDies:a,softEligibleDies:a.filter(d=>d.sbin!==void 0),testDies:a,passBins:f.passBins,ringCount:f.ringCount,sectorCount:f.sectorCount,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs});w.length=0,w.push(...S);let $;if(u){const d=dt(c,e.wafer,{passBins:f.passBins,ringCount:f.ringCount});if($=d??void 0,d!==null&&d.pattern!=="random"&&d.pattern!=="none"){let Je=function(C){const O=j(C).map(H=>Y(H).ring).filter(H=>H!==void 0);if(O.length===0)return!0;const Be=O.some(H=>H===oe),Ne=O.some(H=>H===1);return K==="edge-ring"||K==="edge-local"?Be:K==="center"?Ne:K==="donut"?!Be&&!Ne:!0};const B={center:"Center cluster",donut:"Donut","edge-ring":"Edge-ring","edge-local":"Edge-local",scratch:"Scratch","near-full":"Near-full"}[d.pattern]??d.pattern,L=d.confidence==="high"?"unusual":d.confidence==="medium"?"notable":"info",V=d.features,x=C=>`${(C*100).toFixed(0)}%`,ie=d.pattern==="edge-ring"||d.pattern==="edge-local"?`${x(V.edgeRdd)} of edge dies failing`:d.pattern==="center"||d.pattern==="donut"?`centroid at ${x(V.centroidDistNorm)} of wafer radius from centre`:d.pattern==="scratch"?`linear score ${V.linearScore.toFixed(2)}, eccentricity ${V.eccentricity.toFixed(2)}`:`${x(V.globalRdd)} of dies failing`,X=a.filter(C=>{const O=C.hbin??C.sbin;return O!==void 0&&!new Set(f.passBins).has(O)}),me={"edge-ring":["ring","edge-arc"],"edge-local":["edge-arc","sector","quadrant"],center:["ring","cluster"],donut:["ring"],scratch:["cluster","sector","quadrant"],"near-full":["ring"]},se=new Set(me[d.pattern]??[]),K=d.pattern,oe=f.ringCount,he=[];for(const C of w){if(!se.has(C.comparison.family)||C.comparison.family==="ring"&&!Je(C))continue;he.push(C.id);const O=C.comparison.family==="ring"||C.comparison.family==="quadrant"||C.comparison.family==="sector";L!=="info"&&O&&(C.severity="info")}w.push({id:`spatial-pattern:${d.pattern}`,level:"wafer",severity:L,variable:{kind:"spatialPattern",label:B},comparison:{family:"spatial-pattern",left:B,right:"Wafer"},effect:{direction:"different",effectSize:V.globalRdd},stats:{method:"geometry",sampleSizeLeft:X.length,sampleSizeRight:a.length},summary:`Spatial pattern: ${B.toLowerCase()} (${d.confidence} confidence) \u2014 ${ie}${d.note?` [${d.note}]`:""}`,highlight:{kind:"dies",dieKeys:X.map(C=>Ce(C))},relatedIds:he.length>0?he:void 0})}}w.sort((d,N)=>{const B=d.stats.adjustedPValue??d.stats.pValue??1,L=N.stats.adjustedPValue??N.stats.pValue??1;return B!==L?B-L:Math.abs(N.effect.absoluteDelta??0)-Math.abs(d.effect.absoluteDelta??0)});const k=yt(e.dies,a.length,e.yield.yieldPercent);o&&(k.isLotStack=!0,r&&(k.aggregationMethod=r));const M=St(e.dies,e.testDefs);M&&(k.testSpecYield=M);const R=computeFunctionalYield(e.dies,e.testDefs);if(R&&(k.functionalYield=R),$&&(k.spatialPattern=$),v===void 0&&f.computePerTestStats){const d=Ze(a,f.testNumbers,e.testDefs);d.warning&&h.push(d.warning),v=d.testNumbers}if(v?.length){const d=wt(e.dies,v,e.testDefs,f.minimumSampleSize);d&&(k.perTestStats=d);const N=tt([{dies:e.dies}],e.testDefs,v);N&&(k.capability=N)}Object.assign(k,nt(e.dies,e.testDefs));const P=it([{dies:e.dies,wafer:e.wafer,passBins:f.passBins,ringCount:f.ringCount}]);P&&(k.regionYield=P),h.length>0&&(k.warnings=h);const F=redundancyFacts(a,f.passBins);return collapseRedundantFindings(w,F),Q&&(J=F),{level:"wafer",hasNotableFindings:w.some(d=>d.severity==="notable"||d.severity==="unusual"),findings:w,wafer:e.wafer.metadata??void 0,stats:k}}
|
|
1
|
+
import{normalizeInput as Ge}from"./normalizeInput.js";import{computeCapability as Xe,computeTestFlagYield as Ze,computeRegionYield as Je}from"./summaryFigures.js";import{binPassSets as Me}from"../renderer/binColors.js";import{diePassStatus as Be,isYieldEligibleDie as de,getDieKey as Ne,isPositionedDie as Fe,positionKey as me}from"../core/dies.js";import{dieHasValues as et,testsPresent as Pe,testValue as j}from"../core/dieTable.js";import{getTestPassStatus as se,isParametricTest as Y}from"../renderer/buildWaferMap.js";import{markedTestLabel as T,derivedFields as oe}from"../renderer/testLabel.js";import{buildQuadrantRegions as tt,buildReticlePositionRegions as nt,buildRingRegions as it,buildSectorRegions as st,buildTestSiteRegions as ot,sectorCompassNames as xe,areQuadrantsAdjacent as at,parseRegionKey as _,QUADRANT_CYCLE as pe,regionAngleBins as rt}from"./regions.js";import{buildClusterFindings as ft}from"./clusterDetection.js";import{classifyPattern as ct,patternExplains as lt,patternFailVerdict as ut,PATTERN_LABELS as dt}from"./patternClassification.js";import{benjaminiHochberg as mt,fiveNumberSummary as pt,normalCdf as Ce}from"./math.js";import{mean as ae,clamp01 as Ve,maxOf as ht}from"../core/utils.js";import{classifySpec as re,isOutOfSpec as he}from"../renderer/spec.js";const gt={significanceLevel:{min:1e-6,max:1},minimumEffectSize:{min:0,max:1},minimumRelativeEffect:{min:0,max:Number.MAX_SAFE_INTEGER}},Le=[4,8,16];export function resolveOptions(e){const i=Object.fromEntries(Object.entries(e).filter(([,f])=>f!==void 0)),s={...I,...i},n=[],t=[];for(const f of["significanceLevel","minimumEffectSize","minimumRelativeEffect"]){const{min:l,max:o}=gt[f],u=s[f];if(typeof u!="number"||!Number.isFinite(u)){t.push(`${f}=${String(u)} is not a finite number (using ${I[f]})`),s[f]=I[f];continue}const a=Math.min(o,Math.max(l,u));a!==u&&(t.push(`${f}=${u} is outside ${l}\u2013${o} (using ${a})`),s[f]=a)}if(Le.includes(s.sectorCount)||(t.push(`sectorCount=${s.sectorCount} is not one of ${Le.join(", ")} (using ${I.sectorCount})`),s.sectorCount=I.sectorCount),t.length){const f=`analyzeWaferMap: ${t.join("; ")}. The analysis ran with the corrected values.`;console.warn(`[wafermap] ${f}`),n.push({code:"analysis-option-corrected",message:f,severity:"warning"})}const c=["passBins","ringCount"].filter(f=>i[f]!==void 0);if(c.length){const f=`analyzeWaferMap: ${c.join(" and ")} ${c.length>1?"are":"is"} no longer an analysis option and was ignored. Set ${c.length>1?"them":"it"} on buildWaferMap; the analysis used the value the map was built with.`;console.warn(`[wafermap] ${f}`),n.push({code:"analysis-option-corrected",message:f,severity:"warning"})}const r=["enableYieldAnalysis","enableHardBinAnalysis","enableSoftBinAnalysis","enableReticlePositionAnalysis","enableTestSiteAnalysis","enableClusterAnalysis","enableAngularAnalysis","enablePatternClassification"].filter(f=>i[f]!==void 0);if(r.length){const f=`analyzeWaferMap: ${r.join(", ")} ${r.length>1?"are":"is"} no longer an option and was ignored: every analysis now runs. Filter the findings (filterFindings) to show fewer.`;console.warn(`[wafermap] ${f}`),n.push({code:"analysis-option-corrected",message:f,severity:"warning"})}return{resolved:s,warnings:n}}const I={ringCount:4,passBins:[1],significanceLevel:.05,minimumEffectSize:.2,minimumRelativeEffect:1,minimumSampleSize:5,enableTestValueAnalysis:!1,computePerTestStats:!1,sectorCount:8,minimumClusterSize:5};function bt(e,i){const s={...e};return s.minimumClusterSize=Math.max(5,Math.round(i*.003)),s}function yt(e){return de(e)?e.hbin!==void 0||e.sbin!==void 0||et(e):!1}function St(e,i,s){if(!e||i)return;const n=(s??[]).filter(t=>Y(t)&&(t.limitLow!==void 0||t.limitHigh!==void 0));if(n.length!==0)return t=>{for(const c of n)if(he(re(j(t,c.testNumber),c)))return!0;return!1}}function wt(e,i,s){const n=new Set,t=new Set,c=new Set,r=new Map,f=new Map;for(const l of Pe(e))n.add(l);for(const l of e)l.hbin!==void 0&&t.add(l.hbin),l.sbin!==void 0&&c.add(l.sbin),de(l)&&(l.hbin!==void 0&&r.set(l.hbin,(r.get(l.hbin)??0)+1),l.sbin!==void 0&&f.set(l.sbin,(f.get(l.sbin)??0)+1));return{totalDies:e.length,analyzedDies:i,excludedDies:e.length-i,yieldPercent:s,testsConsidered:[...n].sort((l,o)=>l-o),hardBinsConsidered:[...t].sort((l,o)=>l-o),softBinsConsidered:[...c].sort((l,o)=>l-o),...r.size?{hardBinCounts:Object.fromEntries(r)}:{},...f.size?{softBinCounts:Object.fromEntries(f)}:{}}}function vt(e,i){if(!i?.length)return;const s=i.filter(t=>Y(t)&&(t.limitLow!==void 0||t.limitHigh!==void 0));if(!s.length)return;const n=[];for(const t of s){const c=t.testNumber;let r=0,f=0,l=0,o=0;for(const u of e){if(u.partial||u.edgeExcluded)continue;const a=re(j(u,c),t);a!==null&&(o++,a==="failLow"?f++:a==="failHigh"?l++:r++)}n.push({testNumber:c,label:t.name,passDies:r,failLowDies:f,failHighDies:l,totalDies:o,yieldPercent:o>0?r/o*100:null})}return n.length?n:void 0}export function computeFunctionalYield(e,i){if(!i?.length)return;const s=i.filter(t=>!Y(t));if(!s.length)return;const n=[];for(const t of s){const c=t.testNumber;let r=0,f=0;for(const o of e){if(o.partial||o.edgeExcluded)continue;const u=se(o,c,t);u!==void 0&&(u?r++:f++)}const l=r+f;n.push({testNumber:c,label:T(t,c),...oe(t),passDies:r,failDies:f,totalDies:l,passRatePercent:l>0?r/l*100:null})}return n.length?n:void 0}function $t(e,i,s,n){const t=[];for(const c of i){const r=[];for(const a of e){if(a.partial||a.edgeExcluded)continue;const h=j(a,c);h!==void 0&&Number.isFinite(h)&&r.push(h)}if(r.length<n)continue;const f=ae(r),l=Math.sqrt(ze(r,f)),o=pt(Float64Array.from(r)),u=s?.find(a=>a.testNumber===c)?.name??String(c);t.push({testNumber:c,label:u,count:r.length,min:o.min,max:o.max,mean:f,stddev:l,median:o.median,q1:o.q1,q3:o.q3})}return t.length?t:void 0}export function twoProportionZ(e,i,s,n){const t=(e+s)/(i+n),c=t*(1-t)*(1/i+1/n);return!Number.isFinite(c)||c<=0?0:(e/i-s/n)/Math.sqrt(c)}export function zPValue(e){return Ve(2*(1-Ce(Math.abs(e))))}function O(e,i,s,n){const t=twoProportionZ(e,i,s,n);return t===0?1:zPValue(t)}export function adjustPValues(e){const i=new Map;for(const s of e){const n=`${s.variable.kind}:${s.comparison.family}`,t=i.get(n)??[];t.push(s),i.set(n,t)}for(const s of i.values()){const n=s.filter(c=>c.stats.pValue!==void 0),t=mt(n.map(c=>c.stats.pValue));n.forEach((c,r)=>{c.stats.adjustedPValue=t[r]})}return e}export function rateRelativeEffect(e){const i=(e.passRate?e.n-e.hits:e.hits)/e.n,s=(e.passRate?e.restN-e.restHits:e.restHits)/e.restN;return s===0?i===0?0:1/0:Math.abs(i-s)/s}export function severityForFinding(e,i,s){const n=Math.abs(i),t=s;return e<=.01&&(n>=.3||t>=2.5)?"unusual":e<=.05&&(n>=.2||t>=1.5)?"notable":"info"}export function severityForScore(e,i){return e<=.01&&Math.abs(i)>=.5?"unusual":e<=.05&&Math.abs(i)>=.15?"notable":"info"}function fe(e){return e==="reticle-position"?"other reticle positions":e==="test-site"?"other test sites":"the rest of the map"}function Q(e){return e==="reticle-position"?"Other reticle positions":e==="test-site"?"Other test sites":"Rest of map"}function ge(e,i,s){const n=(Math.abs(i)*100).toFixed(1);return`${e} yield is ${n} percentage points ${i>0?"higher":"lower"} than ${fe(s)}`}function be(e,i){return i==="quadrant"&&!e.startsWith("Quadrant")?`quadrant ${e}`:e}function Ke(e){return/^(Rings|Sectors|Quadrants)\b/.test(e)?"have":"has"}function ye(e,i,s,n){const t=be(e,n),c=(Math.abs(s)*100).toFixed(1);return`${t} ${Ke(t)} ${i} occurrence ${c} percentage points ${s>0?"higher":"lower"} than ${fe(n)}`}function Se(e,i,s,n){const t=be(e,n),c=(Math.abs(s)*100).toFixed(1);return`${t} ${Ke(t)} ${i} pass rate ${c} percentage points ${s>0?"higher":"lower"} than ${fe(n)}`}function we(e,i,s,n,t,c){const r=be(e,t),f=fe(t),l=s>0?"higher":"lower";if(n!==void 0&&Number.isFinite(n)){const u=(Math.abs(n)*100).toFixed(1);return`${r} mean ${i} is ${u}% ${l} than ${f}`}const o=c?` ${c}`:"";return`${r} mean ${i} is ${Math.abs(s).toPrecision(3)}${o} ${l} than ${f}`}function Ee(e,i,s){const n=i?.find(t=>t.bin===e);return n?.name?`${s} ${e} (${n.name})`:`${s} ${e}`}function zt(e,i){const s=i?.find(n=>n.testNumber===e);return{label:T(s,e),unit:s?.unit,...oe(s)}}const Oe=new WeakMap,Te=new WeakMap,qe=new WeakMap;function Z(e,i){const s=i.highlight.dieKeys;return s&&qe.set(s,e),i}let U=null,J;const He=new WeakMap;export function candidatesOf(e){return He.get(e)}function Ae(e,i,s,n){if(!U)return;const t=je(e);if(!t)return;const c=e.id.slice(0,e.id.indexOf(":")),r=c==="specLimit"?"limitFail":c,{kind:f,bin:l,index:o}=e.variable;U.push({key:[r,f,l??"",o??"",t.family,t.key].join("|"),source:r,variable:e.variable,label:e.variable.label,comparison:e.comparison,region:t,z:i,delta:e.effect.absoluteDelta??0,effectSize:e.effect.effectSize??0,relativeDelta:e.effect.relativeDelta,rate:s,mean:n})}export function describeRegional(e,i,s){const n=e.comparison.left,t=e.region.family;switch(e.source){case"yield":return ge(n,i,t);case"hardBin":case"softBin":return ye(n,e.label,i,t);case"functional":return Se(n,e.label.replace(/ pass rate$/,""),i,t);case"limitFail":return ve(n,e.label,i);case"test":return we(n,e.label,i,s,t,e.variable.unit)}}function ve(e,i,s){return`${e} limit fail rate for ${i} is ${(Math.abs(s)*100).toFixed(1)} pp ${s>0?"higher":"lower"} than the rest of the wafer`}const G=new WeakMap;function ce(e,i){return G.set(e,i),e}function je(e){const i=e.highlight.dieKeys;return i?qe.get(i):void 0}const Ie=new WeakMap;function W(e){let i=Ie.get(e);return i||Ie.set(e,i=e.map(me)),i}function kt(e){let i=Oe.get(e);return i||Oe.set(e,i=e.map(Ne)),i}function le(e,i){let s=Te.get(e);s||Te.set(e,s={members:new Set(e),byFamily:new WeakMap});const n=s.byFamily.get(i);if(n)return n;const{members:t}=s,c=new Map;for(const r of i)c.set(r.key,r.dies.filter(f=>t.has(f)));return s.byFamily.set(i,c),c}function ue(e,i){adjustPValues(e);for(const n of e){const t=G.get(n);Ae(n,twoProportionZ(t.hits,t.n,t.restHits,t.restN),t)}const s=e.filter(n=>passesRateGate(n.stats.adjustedPValue??n.stats.pValue??1,n.effect.absoluteDelta??0,rateRelativeEffect(G.get(n)),i));return s.filter(n=>!Dt(n,s,i)).map(n=>({...n,severity:severityForFinding(n.stats.adjustedPValue??n.stats.pValue??1,n.effect.absoluteDelta??0,rateRelativeEffect(G.get(n)))}))}export function passesRateGate(e,i,s,n){return e<=n.significanceLevel&&(Math.abs(i)>=n.minimumEffectSize||s>=n.minimumRelativeEffect)}export function claimForPattern(e,i,s,n,t){const c=[];for(const r of s){const f=r.comparison.family,l=f==="ring"?n(r):[];e.some(o=>lt(o,f,l,t))&&(c.push(r.id),i!=="info"&&(f==="ring"||f==="quadrant"||f==="sector")&&(r.severity="info"))}return c}function Dt(e,i,s){const n=G.get(e);if(!n)return!1;const t=e.stats.pValue??1;let{restHits:c,restN:r}=n;for(const u of We(e,i)){const a=G.get(u);a&&(c-=a.hits,r-=a.n)}if(r===n.restN)return!1;if(r<s.minimumSampleSize)return!0;const f=n.hits/n.n-c/r;if(Math.sign(f)!==Math.sign(e.effect.absoluteDelta??0))return!0;const l=t>0?(e.stats.adjustedPValue??t)/t:1,o=Math.min(1,O(n.hits,n.n,c,r)*l);return!passesRateGate(o,f,rateRelativeEffect({...n,restHits:c,restN:r}),s)}function We(e,i){const s=e.stats.pValue??1;return i.filter(n=>n!==e&&n.effect.direction!==e.effect.direction&&n.variable.kind===e.variable.kind&&n.variable.bin===e.variable.bin&&n.variable.index===e.variable.index&&n.comparison.family===e.comparison.family&&(n.stats.pValue??1)<s)}const $e=new WeakMap;export function welchOfSums(e,i,s,n){const t=(c,r,f)=>f>1?Math.max(0,c-r*r/f)/(f-1):0;return welchFromStats(e.n,e.sum/e.n,t(e.sq,e.sum,e.n),i,s/i,t(n,s,i))}function Rt(e,i,s){const n=$e.get(e);if(!n)return!1;let{restN:t,restSum:c,restSq:r}=n;for(const h of We(e,i)){const g=$e.get(h);g&&(t-=g.n,c-=g.sum,r-=g.sq)}if(t===n.restN)return!1;if(t<s.minimumSampleSize)return!0;const{pValue:f,effectSize:l,delta:o}=welchOfSums(n,t,c,r);if(Math.sign(o)!==Math.sign(e.effect.absoluteDelta??0))return!0;const u=e.stats.pValue??1,a=u>0?(e.stats.adjustedPValue??u)/u:1;return!(Math.min(1,f*a)<=s.significanceLevel&&Math.abs(l)>=s.minimumEffectSize)}function ee(e,i,s,n){const t=new Set(s),c=le(e,i),r=new Map,f=new Map;for(const[o,u]of c){let a=0,h=0;for(const g of u)g.hbin!==void 0&&(h++,t.has(g.hbin)&&a++);r.set(o,a),f.set(o,h)}const l=[];for(const o of i){const u=f.get(o.key)??0,a=r.get(o.key)??0;let h=0,g=0;for(const[k,S]of f)k!==o.key&&(h+=S,g+=r.get(k)??0);if(u<n.minimumSampleSize||h<n.minimumSampleSize)continue;const p=a/u,d=g/h,y=p-d,w=O(a,u,g,h);l.push(ce(Z(o,{id:`yield:${o.key}`,level:"wafer",severity:"info",variable:{kind:"yield",label:"Yield"},comparison:{family:o.family,left:o.label,right:Q(o.family)},effect:{direction:y===0?"different":y>0?"higher":"lower",absoluteDelta:y,relativeDelta:d===0?void 0:y/d,effectSize:y},stats:{method:"two-proportion-z",pValue:w,sampleSizeLeft:u,sampleSizeRight:h},summary:ge(o.label,y,o.family),highlight:{kind:"region",regionFamily:o.family,regionKeys:[o.key],dieKeys:[...o.dieKeys]}}),{hits:a,n:u,restHits:g,restN:h,passRate:!0}))}return ue(l,n)}function E(e,i,s,n,t,c){const r=p=>s==="soft"?p.sbin:p.hbin,f=[...new Set(e.map(r).filter(p=>p!==void 0))].sort((p,d)=>p-d),l=le(e,i),o=new Map,u=new Map;for(const[p,d]of l){const y=new Map;for(const w of d){const k=r(w);k!==void 0&&y.set(k,(y.get(k)??0)+1)}o.set(p,y),u.set(p,d.length)}const a=[],h=t==="hardBin"?"HBin":"SBin",g=s==="hard"?new Set(c.passBins):c.softPassBins??Me(e,c.passBins).soft;for(const p of i){const d=u.get(p.key)??0,y=o.get(p.key);let w=0;const k=new Map;for(const[S,$]of o)if(S!==p.key){w+=u.get(S)??0;for(const[N,F]of $)k.set(N,(k.get(N)??0)+F)}if(!(d<c.minimumSampleSize||w<c.minimumSampleSize))for(const S of f){const $=y.get(S)??0,N=k.get(S)??0,F=$/d,b=N/w,v=F-b,M=O($,d,N,w),z=Ee(S,n,h);a.push(ce(Z(p,{id:`${t}:${S}:${p.key}`,level:"wafer",severity:"info",variable:{kind:t,bin:S,label:z},comparison:{family:p.family,left:p.label,right:Q(p.family)},effect:{direction:v===0?"different":v>0?"higher":"lower",absoluteDelta:v,relativeDelta:b===0?void 0:v/b,effectSize:v},stats:{method:"two-proportion-z",pValue:M,sampleSizeLeft:d,sampleSizeRight:w},summary:ye(p.label,z,v,p.family),highlight:{kind:"bin",bin:S,regionKeys:[p.key],dieKeys:[...p.dieKeys]}}),{hits:$,n:d,restHits:N,restN:w,passRate:g.has(S)}))}}return ue(a,c)}function te(e,i,s,n){const t=(s??[]).filter(u=>!Y(u));if(!t.length)return[];const c=t.map(u=>u.testNumber),r=le(e,i),f=new Map,l=new Map;for(const[u,a]of r){const h=new Uint32Array(t.length),g=new Uint32Array(t.length);for(const p of a)for(let d=0;d<t.length;d++){const y=se(p,c[d],t[d]);y!==void 0&&(g[d]++,y&&h[d]++)}f.set(u,h),l.set(u,g)}const o=[];for(const u of i){const a=f.get(u.key),h=l.get(u.key);for(let g=0;g<t.length;g++){let p=0,d=0;for(const[b,v]of l)b!==u.key&&(d+=v[g],p+=f.get(b)[g]);const y=h[g],w=a[g];if(y<n.minimumSampleSize||d<n.minimumSampleSize)continue;const k=w/y,S=p/d,$=k-S,N=O(w,y,p,d),F=c[g];o.push(ce(Z(u,{id:`functional:${F}:${u.key}`,level:"wafer",severity:"info",variable:{kind:"functionalTest",index:F,label:`${T(t[g],F)} pass rate`,...oe(t[g])},comparison:{family:u.family,left:u.label,right:Q(u.family)},effect:{direction:$===0?"different":$>0?"higher":"lower",absoluteDelta:$,relativeDelta:S===0?void 0:$/S,effectSize:$},stats:{method:"two-proportion-z",pValue:N,sampleSizeLeft:y,sampleSizeRight:d},summary:Se(u.label,T(t[g],F),$,u.family),highlight:{kind:"region",regionFamily:u.family,regionKeys:[u.key],dieKeys:[...u.dieKeys]}}),{hits:w,n:y,restHits:p,restN:d,passRate:!0}))}}return ue(o,n)}function ze(e,i){return e.length<2?0:e.reduce((s,n)=>s+(n-i)**2,0)/(e.length-1)}export function welchFromStats(e,i,s,n,t,c){const r=Math.sqrt(s/e+c/n),f=i-t;if(!Number.isFinite(r)||r===0)return{pValue:1,effectSize:0,delta:f,z:0};const l=f/r,o=Math.sqrt(Math.max(0,(s+c)/2)),u=o===0?f:f/o;return{pValue:Ve(2*(1-Ce(Math.abs(l)))),effectSize:u,delta:f,z:l}}function Mt(e,i){const s=ae(e),n=ae(i);return welchFromStats(e.length,s,ze(e,s),i.length,n,ze(i,n))}const Ye=250;function _e(e,i,s){const n=r=>r.filter(f=>Y(s?.find(l=>l.testNumber===f)));if(i)return{testNumbers:n(i.slice().sort((r,f)=>r-f))};const t=Pe(e,"values");if(t.length>Ye){const r=`Test-value analysis was skipped: more than ${Ye} tests were found in the die data, so no test findings were computed. Pass testNumbers: [...] to analyse specific tests.`;return console.warn(`[wafermap] analyzeWaferMap: ${r}`),{testNumbers:[],warning:{code:"test-count-capped",message:r,severity:"warning"}}}return{testNumbers:n(t)}}function ne(e,i,s,n){const t=_e(e,n.testNumbers,s);if(t.warning)return{findings:[],warning:t.warning};const c=t.testNumbers,r=new Map;for(let d=0;d<i.length;d++)for(const y of W(i[d].dies))r.set(y,d);const f=i.length,l=[],o=[];for(const d of e){const y=r.get(me(d));y!==void 0&&(l.push(d),o.push(y))}const u=[],a=new Float64Array(f),h=new Float64Array(f),g=new Float64Array(f);for(const d of c){a.fill(0),h.fill(0),g.fill(0);let y=0,w=0,k=0,S;for(let $=0;$<l.length;$++){const N=l[$],F=j(N,d);if(F===void 0)continue;S===void 0&&(S=F);const b=F-S,v=o[$];a[v]+=1,h[v]+=b,g[v]+=b*b,y+=1,w+=b,k+=b*b}if(S!==void 0)for(let $=0;$<f;$++){const N=a[$],F=y-N;if(N<n.minimumSampleSize||F<n.minimumSampleSize)continue;const b=h[$]/N,M=(w-h[$])/F,z=b+S,D=M+S,C=N>1?Math.max(0,g[$]-N*b*b)/(N-1):0,m=F>1?Math.max(0,k-g[$]-F*M*M)/(F-1):0,{pValue:R,effectSize:P,delta:B,z:x}=welchFromStats(N,z,C,F,D,m),V=i[$],{label:L,unit:q,...H}=zt(d,s),K=D!==0?B/Math.abs(D):void 0,A=Z(V,{id:`test:${d}:${V.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:d,label:L,unit:q,...H},comparison:{family:V.family,left:V.label,right:Q(V.family)},effect:{direction:B===0?"different":B>0?"higher":"lower",absoluteDelta:B,relativeDelta:K,effectSize:P},stats:{method:"welch-z-approx",pValue:R,sampleSizeLeft:N,sampleSizeRight:F},summary:we(V.label,L,B,K,V.family,q),highlight:{kind:"region",regionFamily:V.family,regionKeys:[V.key],dieKeys:[...V.dieKeys]}}),Re={n:N,sum:h[$],sq:g[$],restN:F,restSum:w-h[$],restSq:k-g[$],shift:S};$e.set(A,Re),Ae(A,x,void 0,Re),u.push(A)}}adjustPValues(u);const p=u.filter(d=>{const y=d.stats.adjustedPValue??d.stats.pValue??1,w=Math.abs(d.effect.effectSize??0);return y<=n.significanceLevel&&w>=n.minimumEffectSize});return{activeTestNumbers:c,findings:p.filter(d=>!Rt(d,p,n)).map(d=>({...d,severity:severityForScore(d.stats.adjustedPValue??d.stats.pValue??1,d.effect.effectSize??0)}))}}function Bt(e,i,s,n){if(!s?.length)return[];const t=s.filter(r=>Y(r)&&(r.limitLow!==void 0||r.limitHigh!==void 0));if(!t.length)return[];const c=[];for(const r of t){const f=r.testNumber;for(const l of i){const o=le(e,l),u=[],a=new Map;let h=0,g=0;for(const[p,d]of o){let y=0,w=0;for(const k of d){const S=j(k,f);S!==void 0&&(y++,he(re(S,r))&&w++)}a.set(p,{valid:y,fail:w}),h+=y,g+=w}for(const p of l){const d=a.get(p.key),y=d.valid,w=h-d.valid;if(y<n.minimumSampleSize||w<n.minimumSampleSize)continue;const k=d.fail,S=g-d.fail,$=k/y,N=S/w,F=$-N,b=O(k,y,S,w);u.push(ce(Z(p,{id:`specLimit:${f}:${p.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:f,label:T(r,f),unit:r.unit,...oe(r)},comparison:{family:p.family,left:p.label,right:Q(p.family)},effect:{direction:F===0?"different":F>0?"higher":"lower",absoluteDelta:F,relativeDelta:N===0?void 0:F/N,effectSize:F},stats:{method:"two-proportion-z",pValue:b,sampleSizeLeft:y,sampleSizeRight:w},summary:ve(p.label,T(r,f),F),highlight:{kind:"region",regionFamily:p.family,regionKeys:[p.key],dieKeys:[...p.dieKeys]}}),{hits:k,n:y,restHits:S,restN:w,passRate:!1}))}c.push(...ue(u,n))}}return c}function ke(e,i){return e==="softBin"?i.softEligibleDies:e==="test"?i.testDies:i.eligibleDies}function Nt(e){return e.id.startsWith("specLimit:")}function ie(e){return e.highlight.regionKeys??[]}function Ft(e){const i=e.variable.bin??e.variable.index??"";return`${e.comparison.family}\0${Qe(e)}\0${e.variable.kind}\0${i}\0${e.effect.direction}`}function Pt(e,i){const s=_(e);return s.family==="ring"?s.ring??0:s.family==="sector"?xe(i).indexOf(s.sector??""):0}export function inQuadrantOrder(e){const i=new Set(e),s=pe.findIndex((t,c)=>i.has(t)&&!i.has(pe[(c+3)%4])),n=s<0?0:s;return Array.from({length:4},(t,c)=>pe[(n+c)%4]).filter(t=>i.has(t))}export function findContiguousRuns(e,i,s,n){if(i==="quadrant"){const l=new Map;for(const h of e){const g=_(n(h)).quadrant;g&&l.set(g,h)}const o=[...l.keys()],u=new Set,a=[];for(const h of o){if(u.has(h))continue;const g=[],p=[h];for(u.add(h);p.length;){const d=p.pop();g.push(d);for(const y of o)!u.has(y)&&at(d,y)&&(u.add(y),p.push(y))}a.push(inQuadrantOrder(g).map(d=>l.get(d)))}return a}const t=l=>Pt(n(l),s),c=[...e].sort((l,o)=>t(l)-t(o)),r=[];let f=[];for(const l of c){if(f.length===0){f.push(l);continue}const o=t(f[f.length-1]);t(l)-o===1?f.push(l):(r.push(f),f=[l])}if(f.length&&r.push(f),i==="sector"&&r.length>1){const o=xe(s).length,u=t(r[0][0]),a=t(r[r.length-1][r[r.length-1].length-1]);if(u===0&&a===o-1){const h=r.pop();r[0]=[...h,...r[0]]}}return r}export function mergedRegionLabel(e,i,s){if(e==="ring"){const t=i.map(l=>_(l).ring).filter(l=>l!==void 0).sort((l,o)=>l-o),c=t[0],r=t[t.length-1];let f="";return c===r&&c===1&&s>1?f=" (core)":c===r&&c===s&&s>1&&(f=" (edge)"),c===r?`Ring ${c}${f}`:`Rings ${c}\u2013${r}`}if(e==="sector"){const t=i.map(c=>_(c).sector??"");return t.length===1?`Sector ${t[0]}`:`Sectors ${t[0]}\u2013${t[t.length-1]}`}const n=i.map(t=>_(t).quadrant??"");return n.length===1?n[0]:n.length===2?`Quadrants ${n[0]} & ${n[1]}`:`Quadrants ${n.slice(0,-1).join(", ")} & ${n[n.length-1]}`}function De(e){return[...new Set(e)]}export function weakestMultiplier(e){return ht(e.map(i=>{const s=i.stats.pValue??1;return s>0?(i.stats.adjustedPValue??s)/s:1}))}function X(e,i){return Math.min(1,e*i)}function xt(e,i){const s=e[0],n=s.comparison.family,t=s.variable.kind,c=De(e.flatMap(ie));let r,f;const l=e.map(je);if(l.every(b=>b!==void 0)){const b=new Set;r=[];for(const M of l){const z=W(M.dies);for(let D=0;D<z.length;D++)b.has(z[D])||(b.add(z[D]),r.push(M.dieKeys[D]))}const v=W(ke(s.variable.kind,i));f=M=>b.has(v[M])}else{r=De(e.flatMap(M=>M.highlight.dieKeys??[]));const b=new Set(r),v=kt(ke(s.variable.kind,i));f=M=>b.has(v[M])}const o=mergedRegionLabel(n,e.map(b=>ie(b)[0]??""),i.ringCount),u=ke(t,i),a=i.regionDies[n],h=W(u),g=[],p=[];for(let b=0;b<u.length;b++)a.has(h[b])&&(f(b)?g:p).push(u[b]);const d=weakestMultiplier(e);let y,w,k,S,$;if(Nt(s)){const b=s.variable.index,v=i.testDefs?.find(x=>x.testNumber===b),M=x=>{let V=0,L=0;for(const q of x){const H=j(q,b);H!==void 0&&(V++,v&&he(re(H,v))&&L++)}return{valid:V,fail:L}},z=M(g),D=M(p),C=z.valid>0?z.fail/z.valid:0,m=D.valid>0?D.fail/D.valid:0,R=C-m,P=O(z.fail,z.valid,D.fail,D.valid);y={direction:R===0?"different":R>0?"higher":"lower",absoluteDelta:R,relativeDelta:m===0?void 0:R/m,effectSize:R},w={method:"two-proportion-z",pValue:P,sampleSizeLeft:z.valid,sampleSizeRight:D.valid};const B=X(P,d);k=severityForFinding(B,R,rateRelativeEffect({hits:z.fail,n:z.valid,restHits:D.fail,restN:D.valid,passRate:!1})),S=ve(o,T(v,b),R),$=`specLimit:${b}`}else if(t==="test"){const b=s.variable.index,v=x=>j(x,b),M=g.map(v).filter(x=>x!==void 0),z=p.map(v).filter(x=>x!==void 0),{pValue:D,effectSize:C,delta:m}=Mt(M,z),R=ae(z),P=R!==0?m/Math.abs(R):void 0;y={direction:m===0?"different":m>0?"higher":"lower",absoluteDelta:m,relativeDelta:P,effectSize:C},w={method:"welch-z-approx",pValue:D,sampleSizeLeft:M.length,sampleSizeRight:z.length};const B=X(D,d);k=severityForScore(B,C),S=we(o,s.variable.label,m,P,n,s.variable.unit),$=`test:${b}`}else if(t==="yield"){const b=new Set(i.passBins),v=g.filter(L=>L.hbin!==void 0),M=p.filter(L=>L.hbin!==void 0),z=v.length,D=M.length,C=v.filter(L=>b.has(L.hbin)).length,m=M.filter(L=>b.has(L.hbin)).length,R=z>0?C/z:0,P=D>0?m/D:0,B=R-P,x=O(C,z,m,D);y={direction:B===0?"different":B>0?"higher":"lower",absoluteDelta:B,relativeDelta:P===0?void 0:B/P,effectSize:B},w={method:"two-proportion-z",pValue:x,sampleSizeLeft:z,sampleSizeRight:D};const V=X(x,d);k=severityForFinding(V,B,rateRelativeEffect({hits:C,n:z,restHits:m,restN:D,passRate:!0})),S=ge(o,B,n),$="yield"}else if(t==="functionalTest"){const b=s.variable.index,v=i.testDefs?.find(V=>V.testNumber===b);let M=0,z=0,D=0,C=0;for(const V of g){const L=se(V,b,v);L!==void 0&&(z++,L&&M++)}for(const V of p){const L=se(V,b,v);L!==void 0&&(C++,L&&D++)}const m=z>0?M/z:0,R=C>0?D/C:0,P=m-R,B=O(M,z,D,C);y={direction:P===0?"different":P>0?"higher":"lower",absoluteDelta:P,relativeDelta:R===0?void 0:P/R,effectSize:P},w={method:"two-proportion-z",pValue:B,sampleSizeLeft:z,sampleSizeRight:C};const x=X(B,d);k=severityForFinding(x,P,rateRelativeEffect({hits:M,n:z,restHits:D,restN:C,passRate:!0})),S=Se(o,T(v,b),P,n),$=`functional:${b}`}else{const b=s.variable.bin,v=x=>t==="softBin"?x.sbin:x.hbin,M=g.filter(x=>v(x)===b).length,z=p.filter(x=>v(x)===b).length,D=M/g.length,C=z/p.length,m=D-C,R=O(M,g.length,z,p.length);y={direction:m===0?"different":m>0?"higher":"lower",absoluteDelta:m,relativeDelta:C===0?void 0:m/C,effectSize:m},w={method:"two-proportion-z",pValue:R,sampleSizeLeft:g.length,sampleSizeRight:p.length};const P=X(R,d);k=severityForFinding(P,m,rateRelativeEffect({hits:M,n:g.length,restHits:z,restN:p.length,passRate:!1}));const B=t==="softBin"?i.sbinDefs:i.hbinDefs;S=ye(o,Ee(b,B,t==="softBin"?"SBin":"HBin"),m,n),$=`${t}:${b}`}w={...w,adjustedPValue:X(w.pValue??1,d)};const N=De(c.map(b=>b.replace(`${n}:`,""))).join("-"),F=t==="hardBin"||t==="softBin"?{kind:"bin",bin:s.variable.bin,regionKeys:c,dieKeys:r}:{kind:"region",regionFamily:n,regionKeys:c,dieKeys:r};return{...s,id:`${$}:${n}:${N}`,severity:k,comparison:{family:n,left:o,right:Q(n)},effect:y,stats:w,summary:S,highlight:F,relatedIds:e.map(b=>b.id)}}function Ct(e,i){const s=new Set(["ring","quadrant","sector"]),n=[],t=new Map;for(const r of e){if(!s.has(r.comparison.family)){n.push(r);continue}const f=Ft(r),l=t.get(f)??[];l.push(r),t.set(f,l)}const c=[];for(const r of t.values()){if(r.length===1){c.push(r[0]);continue}const f=r[0].comparison.family;for(const l of findContiguousRuns(r,f,i.sectorCount,o=>ie(o)[0]??""))c.push(l.length===1?l[0]:xt(l,i))}return[...n,...c]}function Vt(e){const i=new Map,s=new Map;for(const t of e){const c=me(t);t.hbin!==void 0&&t.hbin!==null&&(i.get(t.hbin)??i.set(t.hbin,new Set).get(t.hbin)).add(c),t.sbin!==void 0&&t.sbin!==null&&(s.get(t.sbin)??s.set(t.sbin,new Set).get(t.sbin)).add(c)}const n=new Set;for(const[t,c]of i)for(const[r,f]of s){if(c.size!==f.size)continue;let l=!0;for(const o of c)if(!f.has(o)){l=!1;break}l&&n.add(`${t}:${r}`)}return n}function Lt(e,i){const s=new Set(i);let n;for(const t of e){const c=Be(t,s);if(c!==void 0&&c){if(t.sbin==null||n!==void 0&&t.sbin!==n)return;n=t.sbin}}if(n!==void 0){for(const t of e)if(t.sbin===n&&Be(t,s)!==!0)return;return n}}function Kt(e){return`${e.comparison.family}\0${e.comparison.left}`}export function redundancyFacts(e,i,s){return{coincident:Vt(e),yieldSoftBin:Lt(e,i),passBins:i,sectorCount:s}}const Et=3/5;function Qe(e){return/^(?:lot-region:)?([^:|]+)/.exec(e.id)?.[1]??e.id}function Ot(e){const i=ie(e);if(i.length)return i;const s=/(ring|sector|quadrant):([^:|]+)$/.exec(e.id);return s?s[2].split("-").map(n=>`${s[1]}:${n}`):[]}function Ue(e,i){const s=new Set;for(const n of Ot(e)){const t=rt(n,i);if(!t)return;for(const c of t)s.add(c)}return s.size?s:void 0}export function collapseRedundantFindings(e,i){const{coincident:s,yieldSoftBin:n,passBins:t}=i,c=(o,u)=>{o.absorbedIds=[...o.absorbedIds??[],u.id]},r=new Map;for(const o of e){const u=Kt(o);(r.get(u)??r.set(u,[]).get(u)).push(o)}const f=new Set;for(const o of r.values()){for(const a of o)if(!(a.variable.kind!=="hardBin"||a.variable.bin===void 0))for(const h of o){if(h.variable.kind!=="softBin"||h.variable.bin===void 0||f.has(h.id)||!s.has(`${a.variable.bin}:${h.variable.bin}`)||h.effect.direction!==a.effect.direction)continue;c(a,h),f.add(h.id);const g=a.variable.label.replace(/^HBin\s+/,""),p=h.variable.label.replace(/^SBin\s+/,""),d=g===p?`HBin and SBin ${g} (same dies)`:`${a.variable.label} and ${h.variable.label} (same dies)`;a.summary=a.summary.replace(a.variable.label,d),a.variable.label=d;break}const u=o.find(a=>a.variable.kind==="yield");if(u){for(const a of o)if(!(a===u||f.has(a.id)||!(a.variable.kind==="hardBin"&&t.length===1&&a.variable.bin===t[0]||a.variable.kind==="softBin"&&a.variable.bin===n))){c(u,a),f.add(a.id);for(const g of a.absorbedIds??[])u.absorbedIds=[...u.absorbedIds??[],g]}}}const l=new Map;for(const o of e){if(f.has(o.id)||o.comparison.family!=="sector"&&o.comparison.family!=="quadrant")continue;const u=[Qe(o),o.variable.kind,o.variable.bin??"",o.variable.index??"",o.effect.direction].join("\0");(l.get(u)??l.set(u,[]).get(u)).push(o)}for(const o of l.values()){const u=[];for(const a of o.filter(h=>h.comparison.family==="sector")){const h=Ue(a,i.sectorCount);if(h)for(const g of o.filter(p=>p.comparison.family==="quadrant")){const p=Ue(g,i.sectorCount);if(!p)continue;let d=0;for(const w of h)p.has(w)&&d++;const y=h.size+p.size-d;d/y>=Et-1e-9&&u.push({sector:a,quadrant:g,overlap:d/y})}}u.sort((a,h)=>h.overlap-a.overlap);for(const{sector:a,quadrant:h}of u){if(f.has(a.id)||f.has(h.id))continue;const g=(h.stats.pValue??1)<(a.stats.pValue??1),[p,d]=g?[h,a]:[a,h];c(p,d),f.add(d.id),p.absorbedIds=[...p.absorbedIds??[],...d.absorbedIds??[]]}}}export function analyzeWaferMap(e,i={}){const s=U,n=J,t=[];U=t,J=void 0;try{const c=Tt(e,i);return He.set(c,{candidates:t,facts:J}),c}finally{U=s,J=n}}function Tt(e,i){const{resolved:s,warnings:n}=resolveOptions(i),t=Ge(e),c={...s,passBins:t.passBins??I.passBins,ringCount:t.ringCount??I.ringCount},r=t.isLotStack,f=t.aggrMethod,l=!r||f==="mode"||f==="countBin"||f==="percent",o=t.dies.filter(m=>yt(m)),u=o.filter(Fe),a=bt(c,o.length);a.softPassBins=Me(o,a.passBins).soft;const h=t.dies.filter(m=>de(m)),g=h.filter(Fe),p=it(g,t.wafer,a.ringCount),d=tt(g,t.wafer,a.ringCount),y=nt(g,t.reticleConfig),w=ot(h,!1),k=st(g,t.wafer,a.sectorCount),S=[];l&&S.push(...ee(o,p,a.passBins,a),...ee(o,d,a.passBins,a),...ee(o,y,a.passBins,a),...ee(o,w,a.passBins,a),...ee(o,k,a.passBins,a)),l&&S.push(...E(o,p,"hard",t.hbinDefs,"hardBin",a),...E(o,d,"hard",t.hbinDefs,"hardBin",a),...E(o,y,"hard",t.hbinDefs,"hardBin",a),...E(o,w,"hard",t.hbinDefs,"hardBin",a),...E(o,k,"hard",t.hbinDefs,"hardBin",a));{const m=o.filter(R=>R.sbin!==void 0);S.push(...E(m,p,"soft",t.sbinDefs,"softBin",a),...E(m,d,"soft",t.sbinDefs,"softBin",a),...E(m,y,"soft",t.sbinDefs,"softBin",a),...E(m,w,"soft",t.sbinDefs,"softBin",a),...E(m,k,"soft",t.sbinDefs,"softBin",a))}const $=[...n];let N;if(a.enableTestValueAnalysis){const m=ne(o,p,t.testDefs,a),R=ne(o,d,t.testDefs,a),P=ne(o,y,t.testDefs,a),B=ne(o,w,t.testDefs,a),x=ne(o,k,t.testDefs,a);S.push(...m.findings,...R.findings,...P.findings,...B.findings,...x.findings),m.warning&&$.push(m.warning),N=m.activeTestNumbers,S.push(...Bt(o,[p,d,y,w,k],t.testDefs,a)),S.push(...te(o,p,t.testDefs,a),...te(o,d,t.testDefs,a),...te(o,y,t.testDefs,a),...te(o,w,t.testDefs,a),...te(o,k,t.testDefs,a))}{const m=St(r,l,t.testDefs);(!r||l||m!==void 0)&&S.push(...ft(u,t.wafer,{...a,isFailingDie:m}))}const F=Ct(S,{eligibleDies:o,softEligibleDies:o.filter(m=>m.sbin!==void 0),testDies:o,passBins:a.passBins,ringCount:a.ringCount,sectorCount:a.sectorCount,regionDies:{ring:new Set(p.flatMap(m=>W(m.dies))),quadrant:new Set(d.flatMap(m=>W(m.dies))),sector:new Set(k.flatMap(m=>W(m.dies)))},hbinDefs:t.hbinDefs,sbinDefs:t.sbinDefs,testDefs:t.testDefs});S.length=0,S.push(...F);let b;if(l){const m=ct(u,t.wafer,{passBins:a.passBins,ringCount:a.ringCount});if(b=m??void 0,m!==null&&m.pattern!=="random"&&m.pattern!=="none"){const R=dt[m.pattern],P=m.confidence==="high"?"unusual":m.confidence==="medium"?"notable":"info",B=m.features,x=K=>`${(K*100).toFixed(0)}%`,V=m.pattern==="edge-ring"||m.pattern==="edge-local"?`${x(B.edgeRdd)} of edge dies failing`:m.pattern==="center"||m.pattern==="donut"?`centroid at ${x(B.centroidDistNorm)} of wafer radius from centre`:m.pattern==="scratch"?`linear score ${B.linearScore.toFixed(2)}, eccentricity ${B.eccentricity.toFixed(2)}`:`${x(B.globalRdd)} of dies failing`,L=new Set(a.passBins),q=o.filter(K=>ut(K,L)===!0),H=claimForPattern([m.pattern],P,S,K=>ie(K).map(A=>_(A).ring).filter(A=>A!==void 0),a.ringCount);S.push({id:`spatial-pattern:${m.pattern}`,level:"wafer",severity:P,variable:{kind:"spatialPattern",label:R},comparison:{family:"spatial-pattern",left:R,right:"Wafer"},effect:{direction:"different",effectSize:B.globalRdd},stats:{method:"geometry",sampleSizeLeft:q.length,sampleSizeRight:o.length},summary:`Spatial pattern: ${R.toLowerCase()} (${m.confidence} confidence) \u2014 ${V}${m.note?` [${m.note}]`:""}`,highlight:{kind:"dies",dieKeys:q.map(K=>Ne(K))},relatedIds:H.length>0?H:void 0})}}S.sort((m,R)=>{const P=m.stats.adjustedPValue??m.stats.pValue??1,B=R.stats.adjustedPValue??R.stats.pValue??1;return P!==B?P-B:Math.abs(R.effect.absoluteDelta??0)-Math.abs(m.effect.absoluteDelta??0)});const v=wt(t.dies,o.length,t.yield.yieldPercent);r&&(v.isLotStack=!0,f&&(v.aggregationMethod=f));const M=vt(t.dies,t.testDefs);M&&(v.testSpecYield=M);const z=computeFunctionalYield(t.dies,t.testDefs);if(z&&(v.functionalYield=z),b&&(v.spatialPattern=b),N===void 0&&a.computePerTestStats){const m=_e(o,a.testNumbers,t.testDefs);m.warning&&$.push(m.warning),N=m.testNumbers}if(N?.length){const m=$t(t.dies,N,t.testDefs,a.minimumSampleSize);m&&(v.perTestStats=m);const R=Xe([{dies:t.dies}],t.testDefs,N);R&&(v.capability=R)}Object.assign(v,Ze(t.dies,t.testDefs));const D=Je([{dies:t.dies,wafer:t.wafer,passBins:a.passBins,ringCount:a.ringCount}]);D&&(v.regionYield=D),$.length>0&&(v.warnings=$);const C=redundancyFacts(o,a.passBins,a.sectorCount);return collapseRedundantFindings(S,C),U&&(J=C),{level:"wafer",hasNotableFindings:S.some(m=>m.severity==="notable"||m.severity==="unusual"),findings:S,wafer:t.wafer.metadata??void 0,stats:v}}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Die } from '../core/dies.js';
|
|
2
|
+
import type { BinDef } from '../renderer/buildWaferMap.js';
|
|
3
|
+
export type BinMode = 'hard' | 'soft';
|
|
4
|
+
type Counts = Readonly<Record<number, number>>;
|
|
5
|
+
/**
|
|
6
|
+
* Dies per bin. `precomputed` (`StatsSummary.stats.hardBinCounts`/`softBinCounts`, already scoped to the
|
|
7
|
+
* yield-eligible population) is used directly when supplied; otherwise the dies are walked, skipping
|
|
8
|
+
* partial and edge-excluded ones, the same population the yield uses.
|
|
9
|
+
*/
|
|
10
|
+
export declare function binCountsFrom(dies: Iterable<Die>, mode: BinMode, precomputed?: Counts): Map<number, number>;
|
|
11
|
+
/** The same for several wafers: each wafer's own counts summed when every one has them, the pooled dies walked otherwise. */
|
|
12
|
+
export declare function pooledBinCounts(summaries: ReadonlyArray<{
|
|
13
|
+
stats: {
|
|
14
|
+
hardBinCounts?: Counts;
|
|
15
|
+
softBinCounts?: Counts;
|
|
16
|
+
};
|
|
17
|
+
}> | undefined, dies: Iterable<Die>, mode: BinMode): Map<number, number>;
|
|
18
|
+
export interface BinRow {
|
|
19
|
+
bin: number;
|
|
20
|
+
/** The definition's name, when there is one. */
|
|
21
|
+
name?: string;
|
|
22
|
+
/** `Bin 5 · IDDQ`, or `Bin 5` without a name. */
|
|
23
|
+
label: string;
|
|
24
|
+
count: number;
|
|
25
|
+
/** Share of the dies counted, 0–100. */
|
|
26
|
+
percent: number;
|
|
27
|
+
/** Whether this bin passes: pass bins come first, in grey. */
|
|
28
|
+
passing: boolean;
|
|
29
|
+
}
|
|
30
|
+
/** One row per bin in the shared display order: pass bins first, then fail bins by descending count. */
|
|
31
|
+
export declare function binBreakdownRows(counts: ReadonlyMap<number, number>, defs: readonly BinDef[] | undefined,
|
|
32
|
+
/** Bins of THIS type that pass — `BinColors.pass.hard`/`.soft`, never `passBins` for soft bins. */
|
|
33
|
+
passing: Iterable<number>): BinRow[];
|
|
34
|
+
/** The title carries the population, so the percentages below are never a bare number: they are % of dies, not a mean of wafers. */
|
|
35
|
+
export declare const binBreakdownTitle: (mode: BinMode, total: number) => string;
|
|
36
|
+
export declare const totalOf: (counts: ReadonlyMap<number, number>) => number;
|
|
37
|
+
export {};
|
|
38
|
+
//# sourceMappingURL=binRows.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{sortBinsForDisplay as u}from"./binPareto.js";export function binCountsFrom(e,r,s){const o=new Map;if(s){for(const[n,i]of Object.entries(s))o.set(Number(n),i);return o}for(const n of e){if(n.partial||n.edgeExcluded)continue;const i=r==="hard"?n.hbin:n.sbin;i!=null&&o.set(i,(o.get(i)??0)+1)}return o}export function pooledBinCounts(e,r,s){const o=s==="hard"?"hardBinCounts":"softBinCounts";if(e?.length&&e.every(n=>n.stats[o]!==void 0)){const n=new Map;for(const i of e)for(const[t,a]of Object.entries(i.stats[o]))n.set(Number(t),(n.get(Number(t))??0)+a);return n}return binCountsFrom(r,s)}export function binBreakdownRows(e,r,s){const o=new Set(s),n=[...e.values()].reduce((t,a)=>t+a,0),i=r?new Map(r.map(t=>[t.bin,t.name])):void 0;return u(e.entries(),o).map(([t,a])=>{const c=i?.get(t);return{bin:t,...c?{name:c}:{},label:c?`Bin ${t} \xB7 ${c}`:`Bin ${t}`,count:a,percent:n?a/n*100:0,passing:o.has(t)}})}export const binBreakdownTitle=(e,r)=>`${e==="hard"?"Hard":"Soft"} Bin Breakdown \u2014 % of dies (N=${r.toLocaleString("en-GB")})`,totalOf=e=>[...e.values()].reduce((r,s)=>r+s,0);
|
|
@@ -29,4 +29,29 @@ export declare function visibleFindings<T extends {
|
|
|
29
29
|
* the lot's wafer count and the gallery's per-card highlight of a lot finding.
|
|
30
30
|
*/
|
|
31
31
|
export declare function findingPatternKey(f: StatsFinding): string;
|
|
32
|
+
/** A findings list as the reader sees it: patterns with the findings they explain, then the rest grouped by region. */
|
|
33
|
+
export interface ArrangedFindings {
|
|
34
|
+
/** Spatial-pattern findings, each with the findings in the list it names as supporting detail. */
|
|
35
|
+
patterns: Array<{
|
|
36
|
+
finding: StatsFinding;
|
|
37
|
+
children: StatsFinding[];
|
|
38
|
+
}>;
|
|
39
|
+
/** Everything else grouped by region (`family` and `left`), the most severe group first and within it too. */
|
|
40
|
+
groups: Array<{
|
|
41
|
+
family: string;
|
|
42
|
+
left: string;
|
|
43
|
+
worst: StatsSeverity;
|
|
44
|
+
findings: StatsFinding[];
|
|
45
|
+
}>;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The one arrangement of a findings list, read by the Summary panel and both HTML reports, so a
|
|
49
|
+
* finding sits under the same heading and in the same order on every surface.
|
|
50
|
+
*
|
|
51
|
+
* A finding a pattern lists as supporting detail, or that another finding absorbed as an exact
|
|
52
|
+
* restatement, is not repeated among the groups: the first is shown under its pattern, the second
|
|
53
|
+
* is not shown (it stays in `summary.findings` for a host reading them programmatically). Not every
|
|
54
|
+
* finding's `relatedIds`: a run-merge records the constituents it replaced there, and those are gone.
|
|
55
|
+
*/
|
|
56
|
+
export declare function arrangeFindings(findings: readonly StatsFinding[]): ArrangedFindings;
|
|
32
57
|
//# sourceMappingURL=filterFindings.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
function
|
|
1
|
+
function s(e){return Array.isArray(e)?e:[e]}export function filterFindings(e,i){let t=e.findings;if(i.severity!==void 0){const r=new Set(s(i.severity));t=t.filter(a=>r.has(a.severity))}if(i.kind!==void 0){const r=new Set(s(i.kind));t=t.filter(a=>r.has(a.variable.kind))}if(i.family!==void 0){const r=new Set(s(i.family));t=t.filter(a=>r.has(a.comparison.family))}if(i.level!==void 0){const r=new Set(s(i.level));t=t.filter(a=>r.has(a.level))}return t}export function visibleFindings(e){const i=new Set(e.flatMap(t=>t.absorbedIds??[]));return e.filter(t=>!i.has(t.id))}export function findingPatternKey(e){return[e.variable.kind,e.variable.index??"",e.variable.bin??"",e.comparison.family,e.comparison.left,e.effect.direction].join("|")}const l={unusual:0,notable:1,info:2};export function arrangeFindings(e){const i=e.filter(n=>n.comparison.family==="spatial-pattern"),t=new Set([...i.flatMap(n=>n.relatedIds??[]),...e.flatMap(n=>n.absorbedIds??[])]),r=i.map(n=>({finding:n,children:e.filter(o=>n.relatedIds?.includes(o.id))})),a=new Map;for(const n of e){if(n.comparison.family==="spatial-pattern"||t.has(n.id))continue;const o=`${n.comparison.family}\0${n.comparison.left}`;a.set(o,[...a.get(o)??[],n])}const d=[...a.entries()].map(([n,o])=>{const c=[...o].sort((y,m)=>l[y.severity]-l[m.severity]),[f,p]=n.split("\0");return{family:f,left:p,worst:c[0].severity,findings:c}});return d.sort((n,o)=>l[n.worst]-l[o.worst]),{patterns:r,groups:d}}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { StatsFinding } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* A finding's hover text, for the Summary panel and both HTML reports alike —
|
|
4
|
+
* the one rule for it. The sentence already carries the `†` for a finding about
|
|
5
|
+
* a derived test; the tooltip adds the words for it and the expression, on a
|
|
6
|
+
* line of their own.
|
|
7
|
+
*/
|
|
8
|
+
export declare function formatFindingTooltip(finding: StatsFinding): string;
|
|
9
|
+
//# sourceMappingURL=findingText.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { StatsFinding, StatsSummary } from './types.js';
|
|
2
|
+
/** A trend needs this many wafers: with fewer, even a perfect run of steps cannot clear the significance level. */
|
|
3
|
+
export declare const DRIFT_MIN_WAFERS = 5;
|
|
4
|
+
type PerWafer = ReadonlyArray<{
|
|
5
|
+
waferIndex: number;
|
|
6
|
+
summary: StatsSummary;
|
|
7
|
+
}>;
|
|
8
|
+
/**
|
|
9
|
+
* Trend findings for a lot: yield across the wafers, and each test's mean across them. The p-values go
|
|
10
|
+
* through one Benjamini–Hochberg correction together (the tests are many), then a finding must also move
|
|
11
|
+
* far enough to matter (a point or two of yield, half a σ of a test): a long run of tiny steps is
|
|
12
|
+
* significant and not worth saying.
|
|
13
|
+
*/
|
|
14
|
+
export declare function buildDriftFindings(perWafer: PerWafer, significanceLevel: number): StatsFinding[];
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=lotDrift.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{benjaminiHochberg as v,mannKendall as b}from"./math.js";import{fmt as y}from"../renderer/fmt.js";export const DRIFT_MIN_WAFERS=5;const N=2,M=.5,S=5,x=1;function _(u){const d=[],a=u.map(n=>({waferIndex:n.waferIndex,y:n.summary.stats.yieldPercent})).filter(n=>n.y!==null&&n.y!==void 0);if(a.length>=DRIFT_MIN_WAFERS){const n=b(a.map(e=>e.y));n&&d.push({kind:"yield",label:"Yield",values:a.map(e=>e.y),waferIndices:a.map(e=>e.waferIndex),mk:n})}const r=new Map;for(const{waferIndex:n,summary:e}of u)for(const t of e.stats.perTestStats??[]){if(!Number.isFinite(t.mean)||!(t.count>0))continue;const l=r.get(t.testNumber)??{label:t.label,rows:[]};l.rows.push({waferIndex:n,mean:t.mean,n:t.count,sd:t.stddev}),r.set(t.testNumber,l)}for(const[n,{label:e,rows:t}]of r){if(t.length<DRIFT_MIN_WAFERS)continue;const l=b(t.map(s=>s.mean));if(!l)continue;const m=t.reduce((s,f)=>s+f.n,0),o=Math.sqrt(t.reduce((s,f)=>s+f.n*f.sd**2,0)/m);d.push({kind:"test",testNumber:n,label:e,values:t.map(s=>s.mean),waferIndices:t.map(s=>s.waferIndex),sigma:o,mk:l})}return d}export function buildDriftFindings(u,d){const a=_(u);if(!a.length)return[];const r=v(a.map(e=>e.mk.pValue)),n=[];return a.forEach((e,t)=>{if(r[t]>d)return;const{slope:l,intercept:m,n:o}=e.mk,s=m,i=m+l*(o-1)-s,p=i>0,h=e.kind==="test"&&e.sigma&&e.sigma>0?Math.abs(i)/e.sigma:void 0;if(e.kind==="yield"?Math.abs(i)<N:h===void 0||h<M)return;const g=(e.kind==="yield"?Math.abs(i)>=S:(h??0)>=x)&&r[t]<=.01?"unusual":"notable",k=p?"rises":"falls",c={first:e.values[0],last:e.values[e.values.length-1]},I=e.kind==="yield"?`${c.first.toFixed(1)}% \u2192 ${c.last.toFixed(1)}%`:`${y(c.first)} \u2192 ${y(c.last)}`,w=e.kind==="yield"?"on successive wafers":"across the wafers";n.push({id:e.kind==="yield"?"drift:yield":`drift:test:${e.testNumber}`,level:"inter-wafer",severity:g,variable:e.kind==="yield"?{kind:"yield",label:"Yield"}:{kind:"test",index:e.testNumber,label:e.label},comparison:{family:"wafer",left:"Wafers in input order",right:"a constant level"},effect:{direction:p?"higher":"lower",absoluteDelta:e.kind==="yield"?i/100:i,...e.kind==="yield"?{}:{relativeDelta:s!==0?i/Math.abs(s):void 0},effectSize:e.kind==="yield"?i/100:i/(e.sigma||1)},stats:{method:"mann-kendall",pValue:e.mk.pValue,adjustedPValue:r[t],sampleSizeLeft:o,sampleSizeRight:0},summary:`${e.label} ${k} ${w}: ${I} over ${o} wafers (input order)`,highlight:{kind:"wafer",waferIndices:e.waferIndices}})}),n}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { WaferMapResult } from '../renderer/buildWaferMap.js';
|
|
2
|
+
import { type PatternClassification } from './patternClassification.js';
|
|
3
|
+
import { type ResolvedOptions } from './analyzeWaferMap.js';
|
|
4
|
+
/** A lot's spatial pattern: where its failures recur, what shape that is, and which wafers show it. */
|
|
5
|
+
export interface LotPattern {
|
|
6
|
+
/** The classifier's reading of the recurring positions — never 'random' or 'none'. */
|
|
7
|
+
classification: PatternClassification;
|
|
8
|
+
/** Each wafer that shows the pattern → its failing dies inside the recurring area. */
|
|
9
|
+
carriers: Map<number, string[]>;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The lot's spatial pattern, decided from the lot rather than by counting each
|
|
13
|
+
* wafer's single best label — so one pattern that the classifier reads two ways
|
|
14
|
+
* on different wafers (an elongated centre cluster called a scratch, a faint
|
|
15
|
+
* edge ring called random) is still one pattern.
|
|
16
|
+
*
|
|
17
|
+
* 1. Stack the wafers by die position. A position is *recurring* when more wafers
|
|
18
|
+
* fail in its 3×3 neighbourhood than the lot's overall fail rate explains
|
|
19
|
+
* (an exact binomial test, Benjamini–Hochberg across positions at the
|
|
20
|
+
* analysis's significance level) and it fails above that rate itself. A systematic cause fails the same dies wafer after
|
|
21
|
+
* wafer; random defects and patterns that move (scratches, particles) do not.
|
|
22
|
+
* 2. Classify the recurring positions as a map of their own — the same
|
|
23
|
+
* classifier and thresholds as a single wafer.
|
|
24
|
+
* 3. A wafer shows the pattern when its own failures are concentrated in that
|
|
25
|
+
* area: its fail rate inside against outside, by the same two-proportion test
|
|
26
|
+
* and effect gate as any regional finding, Benjamini–Hochberg across wafers.
|
|
27
|
+
*
|
|
28
|
+
* Wafers stack only with wafers on the same grid as the first (a lot stack, or a
|
|
29
|
+
* wafer of another geometry, is left out and keeps its own classification).
|
|
30
|
+
* Returns null when nothing recurs, or what recurs has no shape.
|
|
31
|
+
*/
|
|
32
|
+
export declare function findLotPattern(results: readonly WaferMapResult[], options: ResolvedOptions): LotPattern | null;
|
|
33
|
+
//# sourceMappingURL=lotPattern.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{getDieKey as R,isPositionedDie as v,isYieldEligibleDie as L,positionKey as h}from"../core/dies.js";import{itemPassBins as j}from"../core/passBins.js";import{classifyFailingDies as C,patternFailVerdict as K}from"./patternClassification.js";import{benjaminiHochberg as D,binomialUpperTail as F}from"./math.js";import{passesRateGate as G,rateRelativeEffect as T,twoProportionZ as V,zPValue as W}from"./analyzeWaferMap.js";const q=.01;export function findLotPattern(H,c){const l=[];let f,k=0,w=0;if(H.forEach((e,n)=>{if(e.isLotStack)return;const s=new Set(j(e)),i=[],t=[];for(const a of e.dies){if(!L(a)||!v(a))continue;const o=K(a,s);o!==void 0&&(i.push(a),t.push(o))}if(!i.length)return;const r=(a,o)=>Math.abs(a-o)<=q*Math.max(Math.abs(a),Math.abs(o));if(!f)f=e,k=i[0].width,w=i[0].height;else if(!r(e.wafer.radius,f.wafer.radius)||!r(i[0].width,k)||!r(i[0].height,w))return;l.push({index:n,dies:i,fails:t})}),!f||l.length<2)return null;const d=new Map;let z=0,m=0;for(const e of l)e.dies.forEach((n,s)=>{const i=h(n);let t=d.get(i);t||d.set(i,t={die:n,n:0,k:0}),t.n++,e.fails[s]&&(t.k++,m++),z++});if(m===0)return null;const E=m/z,p=[...d.values()],M=p.map(e=>{let n=0,s=0;for(let i=-1;i<=1;i++)for(let t=-1;t<=1;t++){const r=i===0&&t===0?e:d.get(h({x:e.die.x+i,y:e.die.y+t}));r&&(n+=r.n,s+=r.k)}return{n,k:s}}),N=D(M.map(e=>F(e.k,e.n,E))),x=new Set,S=[];p.forEach((e,n)=>{N[n]>c.significanceLevel||e.k/e.n<=E||(x.add(h(e.die)),S.push(e.die))});const u=C(S,p.map(e=>e.die),f.wafer,f.ringCount??c.ringCount);if(!u||u.pattern==="random"||u.pattern==="none")return null;const b=l.map(e=>{let n=0,s=0,i=0,t=0;const r=[];e.dies.forEach((o,y)=>{x.has(h(o))?(s++,e.fails[y]&&(n++,r.push(R(o)))):(t++,e.fails[y]&&i++)});const a=s>=c.minimumSampleSize&&t>=c.minimumSampleSize?V(n,s,i,t):0;return{index:e.index,z:a,hits:n,n:s,restHits:i,restN:t,inside:r}}),P=D(b.map(e=>e.z>0?W(e.z):1)),g=new Map;return b.forEach((e,n)=>{if(e.z<=0)return;const s=e.hits/e.n-e.restHits/e.restN,i=T({hits:e.hits,n:e.n,restHits:e.restHits,restN:e.restN,passRate:!1});G(P[n],s,i,c)&&g.set(e.index,e.inside)}),g.size?{classification:u,carriers:g}:null}
|
|
@@ -5,12 +5,42 @@
|
|
|
5
5
|
export declare function errorFunction(value: number): number;
|
|
6
6
|
/** Standard normal cumulative distribution function Φ(value). */
|
|
7
7
|
export declare function normalCdf(value: number): number;
|
|
8
|
+
/**
|
|
9
|
+
* Exact one-sided binomial tail P(X ≥ k) for X ~ Binomial(n, p). Exact rather
|
|
10
|
+
* than a normal approximation because it is asked about small n — how many of a
|
|
11
|
+
* lot's few wafers fail at one die position — where the approximation is poor.
|
|
12
|
+
*/
|
|
13
|
+
export declare function binomialUpperTail(k: number, n: number, p: number): number;
|
|
14
|
+
/**
|
|
15
|
+
* Benjamini–Hochberg adjusted p-values, in the order given. The one
|
|
16
|
+
* false-discovery-rate rule in the library: findings (`adjustPValues`) and the
|
|
17
|
+
* die positions a lot's failures recur at (`findLotPattern`) both go through it.
|
|
18
|
+
*/
|
|
19
|
+
export declare function benjaminiHochberg(pValues: readonly number[]): number[];
|
|
8
20
|
/** Linear-interpolation quantile of a pre-sorted array (`q` in [0, 1]).
|
|
9
21
|
*
|
|
10
22
|
* `ArrayLike<number>`, not `number[]`, so a caller holding its values in a
|
|
11
23
|
* `Float64Array` does not have to copy them back into a plain array to be
|
|
12
24
|
* read. */
|
|
13
25
|
export declare function quantile(sorted: ArrayLike<number>, q: number): number;
|
|
26
|
+
/** Robust outlier fence over a value list: Tukey's `Q1 − k·IQR … Q3 + k·IQR`.
|
|
27
|
+
*
|
|
28
|
+
* Deliberately NOT mean ± 3σ. σ is computed FROM the data including the
|
|
29
|
+
* outlier, so a single reading of 1e30 inflates σ far enough that the fence no
|
|
30
|
+
* longer excludes it — the classic masking failure, and it fails hardest exactly
|
|
31
|
+
* when the outlier is worst. Quartiles are unmoved by the extreme tail.
|
|
32
|
+
*
|
|
33
|
+
* The one outlier rule in the library: the histogram's outlier clip (per-die
|
|
34
|
+
* values) and outlier wafers (per-wafer yields — `waferYieldFences` in
|
|
35
|
+
* analyzeWaferLot.ts, read by the lot findings and the Summary panel's yield
|
|
36
|
+
* bars) both go through it.
|
|
37
|
+
*
|
|
38
|
+
* Returns null when there are fewer than `minCount` values — too few for
|
|
39
|
+
* quartiles to mean anything — or when the IQR is zero. */
|
|
40
|
+
export declare function robustFence(values: ArrayLike<number>, k?: number, minCount?: number): {
|
|
41
|
+
lo: number;
|
|
42
|
+
hi: number;
|
|
43
|
+
} | null;
|
|
14
44
|
/** Descriptive statistics of one population of values — see {@link describeValues}. */
|
|
15
45
|
export interface DescriptiveStats {
|
|
16
46
|
min: number;
|
|
@@ -48,4 +78,25 @@ export declare function fiveNumberSummary(values: Float64Array): {
|
|
|
48
78
|
q3: number;
|
|
49
79
|
max: number;
|
|
50
80
|
};
|
|
81
|
+
export interface MannKendall {
|
|
82
|
+
/** Points in the series. */
|
|
83
|
+
n: number;
|
|
84
|
+
/** The Mann–Kendall statistic: concordant minus discordant pairs. */
|
|
85
|
+
s: number;
|
|
86
|
+
/** S with continuity correction over its tie-corrected standard deviation. */
|
|
87
|
+
z: number;
|
|
88
|
+
/** Two-sided p-value of `z` against no monotonic trend (normal approximation). */
|
|
89
|
+
pValue: number;
|
|
90
|
+
/** The Theil–Sen slope: the median of every pair's slope, per step. Robust to a few wild points. */
|
|
91
|
+
slope: number;
|
|
92
|
+
/** The Theil–Sen line's value at the first point, so `intercept + slope × i` is the fitted series. */
|
|
93
|
+
intercept: number;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* The Mann–Kendall test for a monotonic trend, with the Theil–Sen slope that sizes it. Rank-based, so
|
|
97
|
+
* a trend need not be linear and one wild point cannot make or break it. The normal approximation is
|
|
98
|
+
* conservative at small n (n = 5, a perfect trend, p ≈ 0.028 where the exact value is ≈ 0.017), which is
|
|
99
|
+
* the right side to err on. `null` below three points or when every value is the same.
|
|
100
|
+
*/
|
|
101
|
+
export declare function mannKendall(values: readonly number[]): MannKendall | null;
|
|
51
102
|
//# sourceMappingURL=math.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export function errorFunction(t){const o=t<0?-1:1,n=Math.abs(t),e=.254829592,
|
|
1
|
+
export function errorFunction(t){const o=t<0?-1:1,n=Math.abs(t),e=.254829592,r=-.284496736,c=1.421413741,i=-1.453152027,s=1.061405429,l=1/(1+.3275911*n),u=1-((((s*l+i)*l+c)*l+r)*l+e)*l*Math.exp(-n*n);return o*u}export function normalCdf(t){return .5*(1+errorFunction(t/Math.sqrt(2)))}export function binomialUpperTail(t,o,n){if(t<=0)return 1;if(t>o||n<=0)return 0;if(n>=1)return 1;let e=t*Math.log(n)+(o-t)*Math.log1p(-n);for(let s=0;s<t;s++)e+=Math.log(o-s)-Math.log(s+1);let r=Math.exp(e),c=0;const i=n/(1-n);for(let s=t;s<=o;s++)c+=r,r*=(o-s)/(s+1)*i;return Math.min(1,c)}export function benjaminiHochberg(t){const o=t.length,n=t.map((c,i)=>i).sort((c,i)=>t[c]-t[i]),e=new Array(o);let r=1;for(let c=o-1;c>=0;c--){const i=n[c];r=Math.min(r,t[i]*o/(c+1),1),e[i]=r}return e}export function quantile(t,o){if(t.length===0)return NaN;const n=o*(t.length-1),e=Math.floor(n),r=Math.ceil(n);return t[e]+(t[r]-t[e])*(n-e)}export function robustFence(t,o=1.5,n=8){const e=Float64Array.from(Array.from(t).filter(s=>Number.isFinite(s)));if(e.length<n)return null;const{q1:r,q3:c}=fiveNumberSummary(e),i=c-r;return i===0?null:{lo:r-o*i,hi:c+o*i}}export function describeValues(t){const o=t.length;let n=0;for(let u=0;u<o;u++)n+=t[u];const e=n/o;let r=0;for(let u=0;u<o;u++)r+=(t[u]-e)**2;const{min:c,q1:i,median:s,q3:h,max:l}=fiveNumberSummary(t);return{min:c,max:l,mean:e,count:o,stddev:Math.sqrt(r/o),median:s,q1:i,q3:h}}export function fiveNumberSummary(t){let o=1/0,n=-1/0;for(let i=0;i<t.length;i++){const s=t[i];s<o&&(o=s),s>n&&(n=s)}t.length===0&&(o=NaN,n=NaN);const{q1:e,median:r,q3:c}=b(t);return{min:o,q1:e,median:r,q3:c,max:n}}function b(t){const o=t.length;if(o===0)return{q1:NaN,median:NaN,q3:NaN};const n=o-1,e=(p,g,f)=>{const a=p*n,m=Math.floor(a),q=N(t,m,g,f);if(a===m)return q;let M=1/0;for(let x=m+1;x<=f;x++)t[x]<M&&(M=t[x]);return q+(M-q)*(a-m)},r=Math.floor(.5*n),c=e(.5,0,n),i=Math.floor(.25*n),s=i===r?e(.25,r,n):u(.25,i,r),l=Math.floor(.75*n)===r?e(.75,r,n):e(.75,r+1,n);return{q1:s,median:c,q3:l};function u(p,g,f){const a=p*n,m=N(t,g,0,f-1);if(a===g)return m;let q=t[f];for(let M=g+1;M<f;M++)t[M]<q&&(q=t[M]);return m+(q-m)*(a-g)}}function N(t,o,n,e){for(;n<e;){const r=t[n],c=t[n+e>>>1],i=t[e],s=r<c?c<i?c:r<i?i:r:r<i?r:c<i?i:c;let h=n,l=e;for(;h<=l;){for(;t[h]<s;)h++;for(;t[l]>s;)l--;if(h<=l){const u=t[h];t[h]=t[l],t[l]=u,h++,l--}}if(o<=l)e=l;else if(o>=h)n=h;else break}return t[o]}export function mannKendall(t){const o=t.length;if(o<3)return null;let n=0;const e=[];for(let f=0;f<o-1;f++)for(let a=f+1;a<o;a++){const m=t[a]-t[f];n+=m>0?1:m<0?-1:0,e.push(m/(a-f))}const r=new Map;for(const f of t)r.set(f,(r.get(f)??0)+1);let c=o*(o-1)*(2*o+5)/18;for(const f of r.values())f>1&&(c-=f*(f-1)*(2*f+5)/18);if(c<=0)return null;const i=n===0?0:(n-Math.sign(n))/Math.sqrt(c),s=Math.min(1,2*(1-normalCdf(Math.abs(i))));e.sort((f,a)=>f-a);const h=e.length>>1,l=e.length%2?e[h]:(e[h-1]+e[h])/2,u=t.map((f,a)=>f-l*a).sort((f,a)=>f-a),p=u.length>>1,g=u.length%2?u[p]:(u[p-1]+u[p])/2;return{n:o,s:n,z:i,pValue:s,slope:l,intercept:g}}
|
|
@@ -70,6 +70,10 @@ export interface PatternThresholds {
|
|
|
70
70
|
scratchLinearScore: number;
|
|
71
71
|
scratchLinearScoreHigh: number;
|
|
72
72
|
scratchEccentricity: number;
|
|
73
|
+
/** A scratch's salient region must reach at least this far out (maxDistNorm): a
|
|
74
|
+
* scratch is long, so an elongated blob wholly inside the central zone is a
|
|
75
|
+
* centre cluster, not a scratch. */
|
|
76
|
+
scratchMinReach: number;
|
|
73
77
|
minimumFailingDies: number;
|
|
74
78
|
}
|
|
75
79
|
/**
|
|
@@ -91,6 +95,28 @@ export interface PatternThresholds {
|
|
|
91
95
|
* Overall accuracy: 64% exact match, 86.4% detection rate (any pattern flagged).
|
|
92
96
|
*/
|
|
93
97
|
export declare const DEFAULT_PATTERN_THRESHOLDS: PatternThresholds;
|
|
98
|
+
/** How each pattern is named to an engineer — the wafer's finding and the lot's. */
|
|
99
|
+
export declare const PATTERN_LABELS: Record<PatternLabel, string>;
|
|
100
|
+
/**
|
|
101
|
+
* Whether a die counts as failing for pattern classification: its hard bin, or
|
|
102
|
+
* failing that its soft bin, is not a pass bin. `undefined` for a die with no
|
|
103
|
+
* bin — no data, neither passing nor failing.
|
|
104
|
+
*/
|
|
105
|
+
export declare function patternFailVerdict(die: {
|
|
106
|
+
hbin?: number;
|
|
107
|
+
sbin?: number;
|
|
108
|
+
}, passSet: ReadonlySet<number>): boolean | undefined;
|
|
109
|
+
/**
|
|
110
|
+
* The finding families a spatial pattern explains, and so lists under itself:
|
|
111
|
+
* the ring an edge or centre pattern lies in, the edge arcs of an edge pattern,
|
|
112
|
+
* the clusters of a centre cluster or a scratch. One rule for the wafer's
|
|
113
|
+
* pattern and the lot's.
|
|
114
|
+
*
|
|
115
|
+
* `rings` are the ring numbers a ring finding covers (none for other families);
|
|
116
|
+
* edge patterns explain the outer ring, a centre cluster the core ring, a donut
|
|
117
|
+
* the rings between.
|
|
118
|
+
*/
|
|
119
|
+
export declare function patternExplains(pattern: PatternLabel, family: string, rings: number[], ringCount: number): boolean;
|
|
94
120
|
/**
|
|
95
121
|
* Classify the spatial failure pattern of a wafer from its die data.
|
|
96
122
|
*
|
|
@@ -105,4 +131,9 @@ export declare function classifyPattern(dies: PositionedDie[], wafer: Wafer, opt
|
|
|
105
131
|
passBins: number[];
|
|
106
132
|
ringCount?: number;
|
|
107
133
|
}): PatternClassification | null;
|
|
134
|
+
/**
|
|
135
|
+
* {@link classifyPattern} over a failing set already chosen — a wafer's failing
|
|
136
|
+
* dies, or the positions at which a lot's failures recur (`findLotPattern`).
|
|
137
|
+
*/
|
|
138
|
+
export declare function classifyFailingDies(failing: PositionedDie[], dies: PositionedDie[], wafer: Wafer, ringCount: number): PatternClassification | null;
|
|
108
139
|
//# sourceMappingURL=patternClassification.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{findConnectedComponents as
|
|
1
|
+
import{findConnectedComponents as j}from"./connectedComponents.js";import{clamp01 as J}from"../core/utils.js";export const DEFAULT_PATTERN_THRESHOLDS={nearFullGlobalRdd:.6,edgeRingEdgeRdd:.18,edgeRingEdgeRddHigh:.3,edgeRingRadialSpread:.45,edgeRingAngularSpread:.6,edgeLocalEdgeRdd:.1,edgeLocalCentroidDist:.76,donutP25Dist:.4,centerCentroidDist:.22,centerCentroidDistHigh:.1,centerEdgeRdd:.35,donutEdgeRdd:.22,scratchLinearScore:.25,scratchLinearScoreHigh:.45,scratchEccentricity:.75,scratchMinReach:.35,minimumFailingDies:5};function q(e,t,r){const d=e.physX-t.center.x,i=e.physY-t.center.y,s=Math.sqrt(d*d+i*i)/t.radius;return Math.min(r,Math.max(1,Math.floor(s*r)+1))===r}function K(e,t,r){if(e.length<3)return 0;let d=0,i=0,s=0;for(const f of e){const g=f.physX-t,R=f.physY-r;d+=g*g,i+=R*R,s+=g*R}d/=e.length,i/=e.length,s/=e.length;const c=d+i,n=d*i-s*s,a=Math.max(0,(c/2)**2-n),h=c/2+Math.sqrt(a),y=c/2-Math.sqrt(a);if(h<=0)return 0;const D=J(y/h);return Math.sqrt(1-D)}function Q(e,t){if(t===0)return 0;const r=new Map,d=new Map,i=new Map,s=new Map;for(const n of e)r.set(n.y,(r.get(n.y)??0)+1),d.set(n.x,(d.get(n.x)??0)+1),i.set(n.x-n.y,(i.get(n.x-n.y)??0)+1),s.set(n.x+n.y,(s.get(n.x+n.y)??0)+1);let c=0;for(const n of r.values())n>c&&(c=n);for(const n of d.values())n>c&&(c=n);for(const n of i.values())n>c&&(c=n);for(const n of s.values())n>c&&(c=n);return c/t}function W(e,t,r,d){const i=t.length,s=e.length,c=i>0?s/i:0,n=t.filter(o=>q(o,r,d)),a=e.filter(o=>q(o,r,d)),h=n.length>0?a.length/n.length:0,y=j(e),D=y.length>0?[...y].sort((o,l)=>l.length-o.length):[e],f=D[0],g=D.slice(0,5).flat(),R=f.length,T=s>0?R/s:0,u=r.center.x,m=r.center.y,p=r.radius;let S=0,N=0,M=1/0,x=0;for(const o of f){S+=o.physX,N+=o.physY;const l=Math.sqrt((o.physX-u)**2+(o.physY-m)**2);l<M&&(M=l),l>x&&(x=l)}const H=S/f.length,O=N/f.length,P=Math.sqrt((H-u)**2+(O-m)**2)/p,A=M===1/0?0:M/p,I=Math.min(x/p,1),C=e.map(o=>Math.sqrt((o.physX-u)**2+(o.physY-m)**2)/p).sort((o,l)=>o-l),v=C[Math.floor(C.length*.25)]??0,b=t.filter(o=>Math.sqrt((o.physX-u)**2+(o.physY-m)**2)/p<.5).length,L=t.filter(o=>Math.sqrt((o.physX-u)**2+(o.physY-m)**2)/p>=.5).length,_=e.filter(o=>Math.sqrt((o.physX-u)**2+(o.physY-m)**2)/p<.5).length,w=e.filter(o=>Math.sqrt((o.physX-u)**2+(o.physY-m)**2)/p>=.5).length,F=b>0?_/b:0,X=L>0?w/L:0,z=X>0?F/X:F>0?2:1,G=g.reduce((o,l)=>o+l.physX,0)/g.length,k=g.reduce((o,l)=>o+l.physY,0)/g.length,B=K(g,G,k),U=Q(g,g.length),E=16,Y=new Set;for(const o of a){const l=(Math.atan2(o.physY-m,o.physX-u)+2*Math.PI)%(2*Math.PI);Y.add(Math.floor(l/(2*Math.PI)*E)%E)}const V=a.length>0?Y.size/E:0;return{globalRdd:c,edgeRdd:h,centroidDistNorm:P,minDistNorm:A,maxDistNorm:I,p25DistNorm:v,eccentricity:B,linearScore:U,salienceSize:R,salienceFraction:T,edgeAngularSpread:V,innerOuterRatio:z}}function Z(e,t,r){const d=Math.max(5,Math.round(r*.003)),i=e.salienceSize>=d;if(e.globalRdd<t.nearFullGlobalRdd&&e.p25DistNorm>=t.edgeLocalCentroidDist&&e.edgeAngularSpread>=t.edgeRingAngularSpread&&e.edgeRdd>=t.edgeLocalEdgeRdd)return{pattern:"edge-ring",confidence:e.edgeRdd>=t.edgeRingEdgeRddHigh?"high":"medium"};if(e.globalRdd<t.nearFullGlobalRdd&&e.salienceFraction<.1)return{pattern:"random",confidence:"low"};if(e.globalRdd>=t.nearFullGlobalRdd)return{pattern:"near-full",confidence:e.globalRdd>=.8?"high":"medium"};if(i&&e.linearScore>=t.scratchLinearScore&&e.eccentricity>=t.scratchEccentricity&&e.maxDistNorm>=t.scratchMinReach){const a=e.linearScore>=t.scratchLinearScoreHigh?"high":"medium";return{pattern:"scratch",confidence:a,note:a==="medium"?"Scratch detection is less reliable for fragmented or diagonal patterns":void 0}}const s=i&&e.edgeRdd>=t.edgeLocalEdgeRdd&&e.maxDistNorm>=.7;if(s&&e.p25DistNorm>=t.edgeLocalCentroidDist&&e.maxDistNorm-e.minDistNorm<=t.edgeRingRadialSpread)return{pattern:"edge-ring",confidence:e.edgeRdd>=t.edgeRingEdgeRddHigh?"high":"medium"};if(s&&e.p25DistNorm>=.55&&e.p25DistNorm<t.edgeLocalCentroidDist)return{pattern:"edge-local",confidence:e.edgeRdd>=t.edgeRingEdgeRdd?"medium":"low"};const c=i&&e.centroidDistNorm<=t.centerCentroidDist&&e.edgeRdd<=t.centerEdgeRdd,n="Center and donut patterns have similar geometry; classification may be imprecise";if(c&&e.p25DistNorm<t.donutP25Dist){const a=e.centroidDistNorm<=t.centerCentroidDistHigh?"high":"medium";return{pattern:"center",confidence:a,note:a!=="high"?n:void 0}}return c&&e.p25DistNorm>=t.donutP25Dist&&e.p25DistNorm<.55?{pattern:"donut",confidence:"medium",note:n}:{pattern:"random",confidence:"low"}}export const PATTERN_LABELS={center:"Center cluster",donut:"Donut","edge-ring":"Edge-ring","edge-local":"Edge-local",scratch:"Scratch","near-full":"Near-full",random:"Random",none:"None"};export function patternFailVerdict(e,t){const r=e.hbin??e.sbin;return r===void 0?void 0:!t.has(r)}export function patternExplains(e,t,r,d){if(!{"edge-ring":["ring","edge-arc"],"edge-local":["edge-arc","sector","quadrant"],center:["ring","cluster"],donut:["ring"],scratch:["cluster","sector","quadrant"],"near-full":["ring"]}[e]?.includes(t))return!1;if(t!=="ring"||r.length===0)return!0;const s=r.includes(d),c=r.includes(1);return e==="edge-ring"||e==="edge-local"?s:e==="center"?c:e==="donut"?!s&&!c:!0}export function classifyPattern(e,t,r){const d=new Set(r.passBins),i=e.filter(s=>patternFailVerdict(s,d)===!0);return classifyFailingDies(i,e,t,r.ringCount??4)}export function classifyFailingDies(e,t,r,d){const i={...DEFAULT_PATTERN_THRESHOLDS},s=Math.max(i.minimumFailingDies,Math.round(t.length*.003));if(e.length<s)return null;const c=W(e,t,r,d),{pattern:n,confidence:a,note:h}=Z(c,i,t.length);return{pattern:n,confidence:a,features:c,...h?{note:h}:{}}}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { StatsSeverity } from './types.js';
|
|
2
|
+
import type { ImpactTier } from './synthesis.js';
|
|
3
|
+
/** The three steps severity and impact are drawn in. */
|
|
4
|
+
export type MeterTier = ImpactTier;
|
|
5
|
+
export declare const METER_DOTS = 3;
|
|
6
|
+
/** Dots filled for a tier: high 3, medium 2, low 1. Never zero, so the lowest tier is still marked. */
|
|
7
|
+
export declare const filledDots: (tier: MeterTier) => number;
|
|
8
|
+
/** A finding's severity as a tier and the word it is shown by. `'info'` reads "Minor". */
|
|
9
|
+
export declare const SEVERITY_MARK: Readonly<Record<StatsSeverity, {
|
|
10
|
+
tier: MeterTier;
|
|
11
|
+
word: string;
|
|
12
|
+
}>>;
|
|
13
|
+
/** An item's impact (share of dies lost) as the word it is shown by. */
|
|
14
|
+
export declare const impactWord: (tier: MeterTier) => string;
|
|
15
|
+
/**
|
|
16
|
+
* How strongly a yield difference is tinted: 0 for none, 1–3 light to strong. Only a shortfall of a
|
|
17
|
+
* point or more is tinted — a lot with nothing wrong has no tint at all, which is how a good lot is
|
|
18
|
+
* recognised. `points` is signed: yield minus its reference.
|
|
19
|
+
*/
|
|
20
|
+
export declare function shortfallStep(points: number): 0 | 1 | 2 | 3;
|
|
21
|
+
/**
|
|
22
|
+
* The tint of a shortfall, one colour at three strengths, as an alpha over whatever the surface is
|
|
23
|
+
* (a translucent wash reads on the report's white and on any panel theme alike). Full ink stays on
|
|
24
|
+
* every step: the strongest is still under a third opaque.
|
|
25
|
+
*/
|
|
26
|
+
export declare const SHORTFALL_RGB = "190, 60, 60";
|
|
27
|
+
export declare const SHORTFALL_ALPHA: Readonly<Record<1 | 2 | 3, number>>;
|
|
28
|
+
/** The CSS colour for a shortfall step. */
|
|
29
|
+
export declare const shortfallColor: (step: 1 | 2 | 3) => string;
|
|
30
|
+
/** A signed difference in yield points: `+1.2`, `−1.2` (a true minus sign), `0.0`. */
|
|
31
|
+
export declare function formatPoints(points: number): string;
|
|
32
|
+
/** A bar's fill as a percentage of a 0–100 scale, kept in range. */
|
|
33
|
+
export declare const barPercent: (percent: number) => number;
|
|
34
|
+
/** The legend sentence for a column of shortfall tints, naming its reference ("the lot", "the wafer"). */
|
|
35
|
+
export declare const shortfallLegend: (reference: string) => string;
|
|
36
|
+
/**
|
|
37
|
+
* The label for the unweighted mean of each wafer's own yield. It is not the die-weighted yield of the
|
|
38
|
+
* lot (which the bin breakdown's shares are), and the name says which statistic it is: the panel, the
|
|
39
|
+
* Insights population line and the reports all use this one.
|
|
40
|
+
*/
|
|
41
|
+
export declare const MEAN_WAFER_YIELD_LABEL = "Mean per-wafer yield";
|
|
42
|
+
//# sourceMappingURL=presentation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const METER_DOTS=3,filledDots=t=>t==="high"?3:t==="medium"?2:1,SEVERITY_MARK={unusual:{tier:"high",word:"Unusual"},notable:{tier:"medium",word:"Notable"},info:{tier:"low",word:"Minor"}},impactWord=t=>`${t==="medium"?"Moderate":t==="high"?"High":"Low"} impact`;const e=[{points:4,step:3},{points:2,step:2},{points:1,step:1}];export function shortfallStep(t){return e.find(o=>-t>=o.points)?.step??0}export const SHORTFALL_RGB="190, 60, 60",SHORTFALL_ALPHA={1:.1,2:.2,3:.32},shortfallColor=t=>`rgba(${SHORTFALL_RGB}, ${SHORTFALL_ALPHA[t]})`;export function formatPoints(t){return`${t>0?"+":t<0?"\u2212":""}${Math.abs(t).toFixed(1)}`}export const barPercent=t=>Math.max(0,Math.min(100,t)),shortfallLegend=t=>`Bars run 0\u2013100%. A figure is tinted when it is 1, 2 or 4 or more points below ${t}; nothing above it is.`,MEAN_WAFER_YIELD_LABEL="Mean per-wafer yield";
|
|
@@ -40,8 +40,17 @@ export interface RegionYieldDatum {
|
|
|
40
40
|
export declare function buildRegionYieldData(diesByWafer: Die[][], allWafers: Wafer[], ringCount: number, passBins: readonly number[] | ((waferIndex: number) => readonly number[]), regionBuilder: (dies: PositionedDie[], wafer: Wafer, ringCount: number) => StatsRegion[]): RegionYieldDatum[];
|
|
41
41
|
/** Compass bearing names for a given sector count, ordered CCW from East. */
|
|
42
42
|
export declare function sectorCompassNames(sectorCount: number): string[];
|
|
43
|
+
/** Quadrants going round the wafer, counter-clockwise from the north-east. */
|
|
44
|
+
export declare const QUADRANT_CYCLE: readonly ["NE", "NW", "SW", "SE"];
|
|
43
45
|
/** True when two quadrants share an edge (NE–NW, NE–SE, NW–SW, SE–SW); diagonals are not adjacent. */
|
|
44
46
|
export declare function areQuadrantsAdjacent(a: string, b: string): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* The angular extent of a sector or quadrant region as the set of bins it covers
|
|
49
|
+
* (counter-clockwise from East), for asking how much two regions overlap. Both
|
|
50
|
+
* are cuts of the same wafer by angle alone, so their dies overlap as their
|
|
51
|
+
* angles do. `undefined` for any other region, or a name outside the compass.
|
|
52
|
+
*/
|
|
53
|
+
export declare function regionAngleBins(key: string, sectorCount: number): Set<number> | undefined;
|
|
45
54
|
export interface ParsedRegionKey {
|
|
46
55
|
family: StatsRegion['family'] | 'unknown';
|
|
47
56
|
ring?: number;
|