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