@wafertools/wafermap 0.30.0 → 0.30.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/AGENTS.md +68 -9
  2. package/CHANGELOG.md +247 -0
  3. package/README.md +3 -3
  4. package/dist/packages/canvas-adapter/charts/barPanel.js +1 -1
  5. package/dist/packages/canvas-adapter/charts/binCluster.js +1 -1
  6. package/dist/packages/canvas-adapter/charts/boxplot.js +1 -1
  7. package/dist/packages/canvas-adapter/charts/capability.js +1 -1
  8. package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
  9. package/dist/packages/canvas-adapter/charts/correlation.js +2 -2
  10. package/dist/packages/canvas-adapter/charts/groupedBarPlot.d.ts +1 -0
  11. package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
  12. package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
  13. package/dist/packages/canvas-adapter/charts/testPassRate.js +1 -1
  14. package/dist/packages/canvas-adapter/charts/trend.js +1 -1
  15. package/dist/packages/canvas-adapter/chunked.d.ts +45 -0
  16. package/dist/packages/canvas-adapter/chunked.js +1 -0
  17. package/dist/packages/canvas-adapter/deprecated.d.ts +0 -3
  18. package/dist/packages/canvas-adapter/deprecated.js +1 -1
  19. package/dist/packages/canvas-adapter/dieList.js +1 -1
  20. package/dist/packages/canvas-adapter/exportName.d.ts +68 -0
  21. package/dist/packages/canvas-adapter/exportName.js +1 -0
  22. package/dist/packages/canvas-adapter/icons.js +2 -2
  23. package/dist/packages/canvas-adapter/index.d.ts +2 -1
  24. package/dist/packages/canvas-adapter/index.js +1 -1
  25. package/dist/packages/canvas-adapter/insightsTab.js +1 -1
  26. package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +80 -3
  27. package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
  28. package/dist/packages/canvas-adapter/renderWaferMap.d.ts +24 -2
  29. package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
  30. package/dist/packages/canvas-adapter/summaryPanel.d.ts +76 -15
  31. package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
  32. package/dist/packages/canvas-adapter/toCanvas.js +1 -1
  33. package/dist/packages/canvas-adapter/toolbar.d.ts +6 -0
  34. package/dist/packages/canvas-adapter/toolbar.js +2 -2
  35. package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
  36. package/dist/packages/canvas-adapter/userGuideHtml.js +15 -8
  37. package/dist/packages/canvas-adapter/version.d.ts +2 -2
  38. package/dist/packages/canvas-adapter/version.js +1 -1
  39. package/dist/packages/canvas-adapter/warnings.js +1 -1
  40. package/dist/packages/core/aggregates.js +1 -1
  41. package/dist/packages/core/deprecated.d.ts +8 -12
  42. package/dist/packages/core/deprecated.js +1 -1
  43. package/dist/packages/core/index.d.ts +1 -1
  44. package/dist/packages/core/index.js +1 -1
  45. package/dist/packages/core/utils.d.ts +67 -0
  46. package/dist/packages/core/utils.js +1 -1
  47. package/dist/packages/core/waferLabel.d.ts +12 -2
  48. package/dist/packages/core/waferLabel.js +1 -1
  49. package/dist/packages/renderer/binColors.d.ts +52 -0
  50. package/dist/packages/renderer/binColors.js +1 -1
  51. package/dist/packages/renderer/buildView.js +1 -1
  52. package/dist/packages/renderer/buildWaferMap.d.ts +30 -5
  53. package/dist/packages/renderer/buildWaferMap.js +1 -1
  54. package/dist/packages/renderer/deprecate.d.ts +7 -0
  55. package/dist/packages/renderer/deprecate.js +1 -1
  56. package/dist/packages/renderer/deprecated.d.ts +2 -2
  57. package/dist/packages/renderer/deprecated.js +1 -1
  58. package/dist/packages/renderer/index.d.ts +2 -1
  59. package/dist/packages/renderer/index.js +1 -1
  60. package/dist/packages/stats/analyzeWaferLot.js +1 -1
  61. package/dist/packages/stats/analyzeWaferMap.js +1 -1
  62. package/dist/packages/stats/capability.d.ts +72 -1
  63. package/dist/packages/stats/capability.js +1 -1
  64. package/dist/packages/stats/correlation.d.ts +30 -0
  65. package/dist/packages/stats/correlation.js +1 -1
  66. package/dist/packages/stats/deprecated.d.ts +30 -35
  67. package/dist/packages/stats/deprecated.js +1 -1
  68. package/dist/packages/stats/findingsNarrative.js +1 -1
  69. package/dist/packages/stats/index.d.ts +4 -2
  70. package/dist/packages/stats/index.js +1 -1
  71. package/dist/packages/stats/math.d.ts +27 -2
  72. package/dist/packages/stats/math.js +1 -1
  73. package/dist/packages/stats/normalizeInput.d.ts +10 -0
  74. package/dist/packages/stats/normalizeInput.js +1 -0
  75. package/dist/packages/stats/regions.d.ts +2 -0
  76. package/dist/packages/stats/regions.js +1 -1
  77. package/dist/packages/stats/renderFindingsReport.js +1 -1
  78. package/dist/packages/stats/renderSummaryReport.d.ts +39 -1
  79. package/dist/packages/stats/renderSummaryReport.js +27 -27
  80. package/dist/packages/stats/reportHtml.d.ts +0 -1
  81. package/dist/packages/stats/reportHtml.js +10 -10
  82. package/dist/packages/stats/summaryFigures.d.ts +47 -0
  83. package/dist/packages/stats/summaryFigures.js +1 -0
  84. package/dist/packages/stats/types.d.ts +118 -0
  85. package/llms.txt +12 -7
  86. package/package.json +2 -2
@@ -1 +1 @@
1
- import{createWafer as q}from"../core/wafer.js";import{isYieldEligibleDie as fe,getDieKey as B,hasPosition as v}from"../core/dies.js";import{applyOrientation as H,transformDies as ce}from"../core/transforms.js";import{affineRotation as le,affineMirror as ue,affineCompose as he,affinePoint as me}from"../core/transforms.js";import{inferWaferFromXY as ge}from"../core/inference/wafer.js";import{resolveGridPitch as pe}from"../core/inference/pitch.js";import{assignGridIndices as ye}from"../core/inference/grid.js";import{generateReticleGrid as xe}from"../core/reticle.js";import{buildView as U}from"./buildView.js";import{modeOf as we}from"../core/utils.js";import{aggregateValues as J,aggregateBinCounts as be}from"../core/aggregates.js";export function isParametricTest(t){return t?.testType!=="F"}function De(t){const n=(l,o)=>({ringCount:l,ringCountWarning:{code:"analysis-option-corrected",severity:"warning",message:`ringCount=${String(t)} ${o} (using ${l}).`}});if(t===void 0)return{ringCount:4,ringCountWarning:void 0};if(typeof t!="number"||!Number.isFinite(t))return n(4,"is not a finite number");const e=Math.max(1,Math.round(t));return e===t?{ringCount:t,ringCountWarning:void 0}:n(e,"is not a whole number of at least 1")}function Q(t){const n=(u,i)=>u!==null&&typeof u=="object"&&u[i]!==void 0,e=Array.isArray(t)?{}:t,l=[],o=(u,i,f)=>{u&&l.push(`\`${i}\` (now \`${f}\`)`)};o(n(e,"data"),"data","results"),o(n(e,"die"),"die","dieConfig"),o(n(e,"stack"),"stack","lotStack"),o(n(e.dieConfig,"origin"),"dieConfig.origin","dieConfig.coordinateOrigin"),o(n(e.waferConfig,"flat"),"waferConfig.flat","waferConfig.notch"),o(n(e.reticleConfig,"anchor"),"reticleConfig.anchor","reticleConfig.anchorDie"),o(n(e.lotStack,"aggr"),"lotStack.aggr","lotStack.method"),o(Array.isArray(e.testDefs)&&e.testDefs.some(u=>n(u,"index")&&!n(u,"testNumber")),"TestDef.index","TestDef.testNumber");const r=n(e.lotStack,"results")?e.lotStack.results:[],h=(Array.isArray(t)?[t]:[e.results,...Array.isArray(r)?r:[]]).filter(u=>Array.isArray(u));if(o(h.some(u=>u.slice(0,100).some(i=>n(i,"values"))),"values","testValues"),!l.length)return;const s=l.length>1,m=`buildWaferMap ignored ${l.join(", ")}: ${s?"these names were":"that name was"} removed in an earlier release, so what ${s?"they":"it"} described is not on this map. Rename and rebuild.`;return console.warn(`[wafermap] ${m}`),{code:"input-field-removed",message:m,severity:"error"}}function Me(t){if(!Array.isArray(t)&&"results"in t&&t.results!==void 0&&"lotStack"in t&&t.lotStack!==void 0)throw new Error("buildWaferMap: pass either `results` or `lotStack`, not both.");return Array.isArray(t)?{results:t,waferOpts:void 0,dieOpts:void 0,explicitDies:void 0,reticleOpts:void 0,lotStackOpts:void 0,passBins:[1],ringCount:4,ringCountWarning:void 0,removedFieldWarning:Q(t),standardDiameters:void 0,testDefs:void 0,hbinDefs:void 0,sbinDefs:void 0,metadataFields:void 0,retestPolicy:"last",edgeDieYieldMode:"exclude"}:{results:t.results??[],waferOpts:t.waferConfig,dieOpts:t.dieConfig,explicitDies:t.dies,reticleOpts:t.reticleConfig,lotStackOpts:t.lotStack,passBins:t.passBins??[1],...De(t.ringCount),removedFieldWarning:Q(t),standardDiameters:t.standardDiameters,testDefs:t.testDefs?.length?t.testDefs:void 0,hbinDefs:t.hbinDefs,sbinDefs:t.sbinDefs,metadataFields:t.metadataFields,retestPolicy:t.retestPolicy??"last",edgeDieYieldMode:t.edgeDieYieldMode??"exclude"}}function Ce(t,n){return n?.coordinateOrigin?n.coordinateOrigin:{type:"center"}}function Oe(t,n,e){if(n.type==="custom"&&n.offset)return{offsetX:n.offset.x,offsetY:n.offset.y};if(n.type!=="center"){let l=1/0,o=-1/0,r=1/0,h=-1/0;for(const s of t)s.x<l&&(l=s.x),s.x>o&&(o=s.x),s.y<r&&(r=s.y),s.y>h&&(h=s.y);return{offsetX:Math.round((o+l)/2),offsetY:Math.round((h+r)/2)}}return{offsetX:e.offsetX,offsetY:e.offsetY}}function Se(t,n,e,l,o,r){if(r)return{colMidX:(Math.round(r.x)-n)*l,colMidY:(Math.round(r.y)-e)*o,anchored:!0};if(t.length===0)return{colMidX:0,colMidY:0,anchored:!1};let h=1/0,s=-1/0,m=1/0,u=-1/0;for(const i of t){const f=Math.round(i.x)-n,c=Math.round(i.y)-e;f<h&&(h=f),f>s&&(s=f),c<m&&(m=c),c>u&&(u=c)}return{colMidX:(h+s)/2*l,colMidY:(m+u)/2*o,anchored:!1}}function ve(t,n,e){let l=1/0,o=-1/0,r=1/0,h=-1/0,s=0,m=0;for(const f of t){const c=Math.round(f.x)-n,d=Math.round(f.y)-e;s+=c,m+=d,c<l&&(l=c),c>o&&(o=c),d<r&&(r=d),d>h&&(h=d)}const u=t.length,i=(f,c,d)=>{const g=(d-c)/2;return g<=0?0:Math.abs(f/u-(c+d)/2)/g};return i(s,l,o)>.11||i(m,r,h)>.11}function Re(t,n){let e=t?.xAxisDirection==="left",l=t?.yAxisDirection==="down";return(n.type==="UL"||n.type==="UR")&&(l=!0),(n.type==="LR"||n.type==="UR")&&(e=!0),{flipX:e,flipY:l}}function ke(t,n){const{method:e,targetBin:l}=t,o=t.results.map(r=>r.filter(v));if(e==="mean"||e==="median"||e==="stddev"||e==="min"||e==="max"||e==="count"){const r=new Set((n??[]).filter(i=>!isParametricTest(i)).map(i=>i.testNumber)),h=new Set;for(const i of o)for(const f of i)if(f.testValues)for(const c of Object.keys(f.testValues)){const d=Number(c);r.has(d)||h.add(d)}if(h.size===0)return J(o,e);const s=[...h],m=s.map(i=>J(o,e,i)),u=new Map;for(let i=0;i<s.length;i++){const f=s[i];for(const c of m[i]){const d=B(c);u.has(d)||u.set(d,{template:c,testValues:{}});const g=u.get(d),p=c.testValues?.[0];p!==void 0&&(g.testValues[f]=p)}}return[...u.values()].map(({template:i,testValues:f})=>({...i,testValues:Object.keys(f).length>0?f:void 0}))}if(e==="countBin"||e==="percent"){if(l===void 0)return[];const r=be(o,l,"hard");if(e==="countBin")return r;const h=o.length;return r.map(s=>({...s,testValues:{0:h>0?(s.testValues?.[0]??0)/h*100:0}}))}if(e==="mode"){const r=new Map;for(const s of o)for(const m of s){const u=B(m);r.has(u)||r.set(u,[]),r.get(u).push(m)}const h=[];for(const[s,m]of r){const u=s.split(","),i=Number(u[0]),f=Number(u[1]),c=m.map(g=>g.hbin).filter(g=>g!==void 0),d=we(c);d!==null&&h.push({x:i,y:f,hbin:d})}return h}return[]}function Z(t){const n=t.filter(v),e=n.length,l=n.filter(r=>r.edgeExcluded).length,o=n.filter(r=>dieHasTestData(r)||r.hbin!==void 0||r.sbin!==void 0).length;return{filledDies:o,totalDies:e,edgeExcludedDies:l,unpositionedDies:t.length-n.length,ratio:e>0?o/e:0}}function ee(t,n,e="exclude"){const l=new Set(n),r=t.filter(d=>!d.partial).filter(d=>d.edgeExcluded).length,h=t.filter(d=>d.partial).length;let s=0,m=0,u=!1;for(const d of t){if(!fe(d))continue;const g=d.hbin??d.sbin;g!==void 0&&(u=!0,l.has(g)?s++:m++)}const i=s+m,f=u&&i>0?s/i*100:null;let c=null;if(e==="denominator-only"){const d=i+r;c=u&&d>0?s/d*100:null}return{passDies:s,failDies:m,edgeExcludedDies:r,partialDies:h,totalDies:i,yieldPercent:f,yieldPercentGross:c}}function te(t,n,e,l,o,r=0,h=0,s=0,m=0,u=0,i=!1,f=!1){if(!t)return[];const c=t.anchorDie??{x:0,y:0},d=xe(n,{width:t.width,height:t.height,diePitchX:l,diePitchY:o,anchorDie:{x:c.x-r,y:c.y-h},gridOrigin:{x:-s,y:-m}}),g=he(ue(i,f,n.center.x,n.center.y),le(u,n.center.x,n.center.y));return d.filter(p=>{const R=p.width/2,k=p.height/2,w=[[p.x-R,p.y-k],[p.x+R,p.y-k],[p.x+R,p.y+k],[p.x-R,p.y+k]].map(([y,N])=>me(g,y,N)),M=Math.min(...w.map(y=>y.x)),A=Math.max(...w.map(y=>y.x)),E=Math.min(...w.map(y=>y.y)),$=Math.max(...w.map(y=>y.y));return e.some(y=>y.physX>=M&&y.physX<A&&y.physY>=E&&y.physY<$)})}function Ae(t,n,e){const o=Math.max(0,n.radius-e)**2;return t.map(r=>{const h=r.physX-n.center.x,s=r.physY-n.center.y;return h*h+s*s>o?{...r,edgeExcluded:!0}:r})}function Ee(t,n,e){const l=t.filter(v),o=t.filter(i=>!v(i)),r=new Map;for(const i of l){let f=r.get(i.x);f||(f=new Map,r.set(i.x,f)),f.set(i.y,(f.get(i.y)??0)+1)}const h=new Set(e);function s(i,f){const c=i.hbin,d=f.hbin;if(c===void 0||d===void 0)return!1;const g=h.has(c),p=h.has(d);return n==="best"?p!==g?p:d<c:p!==g?g:d>c}const m=new Map;for(const i of l){const f=B(i),c=m.get(f);n==="first"&&c||(n==="best"||n==="worst")&&c&&!s(c,i)||m.set(f,i)}return[...Array.from(m.values()).map(i=>{const c=r.get(i.x)?.get(i.y)??1;return c>1?{...i,retestCount:c}:i}),...o]}export function getDieTestValue(t,n){return t.testValues?.[n]}export function getTestPassStatus(t,n,e){const l=t.testPass?.[n];if(l!==void 0)return l;if(e!==void 0&&!isParametricTest(e)){const o=t.testValues?.[n];if(o===0||o===1)return o===1}}export function dieHasTestData(t){return t.testValues!==void 0&&Object.keys(t.testValues).length>0||t.testPass!==void 0&&Object.keys(t.testPass).length>0}function Y(t,n){const e={};return n.hbin!==void 0&&(e.hbin=n.hbin),n.sbin!==void 0&&(e.sbin=n.sbin),n.retestCount!==void 0&&(e.retestCount=n.retestCount),n.siteNum!==void 0&&(e.siteNum=n.siteNum),n.partId!==void 0&&(e.partId=n.partId),n.metadata!==void 0&&(e.metadata=n.metadata),n.testPass!==void 0&&(e.testPass=n.testPass),n.testValues!==void 0&&(e.testValues=n.testValues),{...t,...e}}function ne(t,n){return n.plotMode?n.plotMode:t.some(dieHasTestData)?"value":"hardBin"}function ie(t,n){return t.map(({code:e,message:l})=>({code:e,message:l,severity:e==="non-standard-diameter"||e==="diameter-exceeds-die-extent"?"warning":"error",confidence:n.wafer.confidence}))}const Fe="do not fit inside the supplied",We="Inferred wafer diameter",$e="The probed dies reach only",Ie=.75,Pe=20;export const STANDARD_WAFER_DIAMETERS_MM=[100,125,150,200,300];const Te=.02;function se(t,n){return n.reduce((e,l)=>Math.abs(l-t)<Math.abs(e-t)?l:e)}function _e(t,n){if(!n.length)return!0;const e=se(t,n);return Math.abs(e-t)/e<=Te}function Be(t){return t===void 0?STANDARD_WAFER_DIAMETERS_MM:t.filter(n=>Number.isFinite(n)&&n>0)}export function buildWaferMap(t,n){const e=Me(t),{debug:l,...o}=n??{},r=e.lotStackOpts?ke(e.lotStackOpts,e.testDefs):e.results;for(let a=0;a<Math.min(5,r.length);a++){const x=r[a];if(typeof x.x=="string"||typeof x.y=="string")throw new TypeError("buildWaferMap: x and y must be numbers, received strings. Did you forget to cast CSV values? e.g. { x: +row.x, y: +row.y }")}for(const a of r)if(a.x==null!=(a.y==null))throw new TypeError(`buildWaferMap: die has only one of x/y set (x=${String(a.x)}, y=${String(a.y)}). A die must be either fully positioned (both x and y) or fully unpositioned (neither) \u2014 omit both to mark a die as having no reported position.`);const h=Ee(r,e.retestPolicy,e.passBins),s={wafer:{confidence:1,method:"provided"},diePitch:{confidence:1,units:"mm"},grid:{confidence:1}},m=[];if(e.explicitDies){let a=e.explicitDies;const x=h.filter(v),O=h.filter(_=>!v(_));if(x.length>0){const _=new Map(x.map(S=>[B(S),S]));a=a.map(S=>{const G=_.get(B(S));return G?Y(S,G):S})}const T=e.waferOpts?.diameter??300,W=q({diameter:T,notch:e.waferOpts?.notch,orientation:e.waferOpts?.orientation??0,metadata:e.waferOpts?.metadata});W.orientation!==0&&(a=H(a,W));const z=te(e.reticleOpts,W,a,1,1,0,0,0,0,W.orientation),ae=o.showReticle??e.reticleOpts!==void 0,L=U(W,a,{...o,reticles:z,showReticle:ae,plotMode:ne(h,o),testDefs:e.testDefs,passBins:e.passBins,ringCount:e.ringCount,isLotStack:!1},{hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,metadataFields:e.metadataFields}),de=O.map((_,S)=>Y({id:`unpositioned_${S}`,width:a[0]?.width??1,height:a[0]?.height??1},_)),X=[...a,...de];return{wafer:W,dies:X,view:L,reticleConfig:e.reticleOpts,units:"mm",inference:s,warnings:[...e.removedFieldWarning?[e.removedFieldWarning]:[],...ie(m,s),...e.ringCountWarning?[e.ringCountWarning]:[]],plotMode:L.plotMode,metadata:L.metadata,isLotStack:!1,dataCoverage:Z(X),passBins:e.passBins,ringCount:e.ringCount,yield:ee(X,e.passBins,e.edgeDieYieldMode),reticles:z,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs,metadataFields:e.metadataFields}}const u=h.filter(v),i=h.filter(a=>!v(a)),f=u.map(a=>({x:a.x,y:a.y})),c=pe(f,e.dieOpts,e.waferOpts?.diameter);s.diePitch={confidence:c.confidence,units:c.units};const{pitchX:d,pitchY:g}=c,p=c.units,R=Ce(u,e.dieOpts),k=ye(f);s.grid={confidence:k.confidence};const{offsetX:w,offsetY:M}=Oe(f,R,k),{flipX:A,flipY:E}=Re(e.dieOpts,R),{colMidX:$,colMidY:y,anchored:N}=Se(f,w,M,d,g,e.waferOpts?.center);let b=e.waferOpts?.diameter;const I=f.map(a=>({x:(Math.round(a.x)-w)*d-$,y:(Math.round(a.y)-M)*g-y})),F=I.length>0?Math.max(...I.map(({x:a,y:x})=>Math.hypot(Math.abs(a)+d/2,Math.abs(x)+g/2))):0;if(b===void 0)if(f.length>0)if(p==="mm"){const a=ge(I,{minRadius:F});b=a.diameter,s.wafer={confidence:a.confidence,method:a.method};const x=Be(e.standardDiameters);if(!_e(b,x)){const O=se(b,x);m.push({code:"non-standard-diameter",message:`${We} ${b.toFixed(1)} mm is not a standard wafer size (nearest is ${O} mm). The diameter was derived from the die extent, which assumes the probed grid reaches the wafer edge \u2014 a non-standard result usually means it does not, because the outer dies were never probed. Every die is then placed against a wafer that is too small, which moves dies between rings and changes ring and edge findings. Supply waferConfig.diameter.`})}}else b=F*2,s.wafer={confidence:c.confidence*.8,method:"extent"};else b=p==="mm"?300:30,s.wafer={confidence:0,method:"default"};if(!N&&f.length>0&&ve(f,w,M)&&(s.wafer.method="inferred-partial",m.push({code:"partial-coverage",message:"Wafer geometry was inferred from die positions alone. The data does not span a full symmetric wafer, so the inferred diameter and centre may be wrong and dies may be mis-positioned relative to the true wafer boundary. Supply waferConfig.diameter and waferConfig.center (the prober coordinate of the wafer centre) to position partial data correctly."})),e.waferOpts?.diameter!==void 0&&e.waferOpts.center===void 0&&F>0&&f.length>=Pe){const a=F/(b/2);a<Ie&&m.push({code:"diameter-exceeds-die-extent",message:`${$e} ${(a*100).toFixed(0)}% of the ${b} mm wafer's radius. Either the diameter is larger than the real one, or this is a partial map covering only part of the wafer. Ring bands are equal-radius, so the outer rings are mostly or entirely empty and ring, quadrant and edge findings describe the assumed wafer rather than the probed area. Check waferConfig.diameter, or drop it and let the diameter be inferred from the die extent.`})}if(e.waferOpts?.diameter!==void 0){const a=e.dieOpts?.width!==void 0&&e.dieOpts?.height!==void 0;if(a&&F>b/2+1e-9){const x=I.filter(({x:O,y:T})=>Math.hypot(Math.abs(O)+d/2,Math.abs(T)+g/2)>b/2+1e-9).length;m.push({code:"geometry-conflict",message:`${x} of ${I.length} probed die positions ${Fe} ${b} mm wafer at the supplied die pitch of ${d} \xD7 ${g} mm. A die with test results is a real prober position and is always fully on the wafer, so one of the two supplied values must be wrong: containing these dies at this pitch would need a diameter of at least ${(F*2).toFixed(1)} mm. Check waferConfig.diameter and dieConfig.width/height against the real device.`})}}const C=q({diameter:b,notch:e.waferOpts?.notch,orientation:e.waferOpts?.orientation??0,metadata:e.waferOpts?.metadata});let D=u.map(a=>{const x=Math.round(a.x)-w,O=Math.round(a.y)-M,T={id:`${x}_${O}`,x,y:O,physX:x*d-$,physY:O*g-y,width:d,height:g,insideWafer:!0,partial:!1};return Y(T,a)});(w!==0||M!==0)&&(D=D.map(a=>({...a,x:a.x+w,y:a.y+M,id:`${a.x+w}_${a.y+M}`}))),D=H(D,C),(A||E)&&(D=ce(D,{flipX:A,flipY:E},C.center));const K=[];e.waferOpts?.edgeExclusion&&e.waferOpts.edgeExclusion>0&&(D=Ae(D,C,e.waferOpts.edgeExclusion),e.waferOpts.edgeExclusion>C.radius&&K.push({code:"edge-exclusion-exceeds-radius",message:`edgeExclusion (${e.waferOpts.edgeExclusion}mm) exceeds the resolved wafer radius (${C.radius}mm) \u2014 the entire wafer is excluded.`,severity:"warning"}));const j=te(e.reticleOpts,C,D,d,g,w,M,$,y,C.orientation,A,E),re=o.showReticle??e.reticleOpts!==void 0,P=U(C,D,{...o,reticles:j,showReticle:re,plotMode:ne(h,o),testDefs:e.testDefs,passBins:e.passBins,ringCount:e.ringCount,dataAxisFlip:{x:A,y:E},isLotStack:e.lotStackOpts!==void 0,aggregationMethod:e.lotStackOpts?.method,lotSize:e.lotStackOpts?.results.length},{hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,metadataFields:e.metadataFields}),oe=i.map((a,x)=>Y({id:`unpositioned_${x}`,width:d,height:g},a)),V=[...D,...oe];return{wafer:C,dies:V,view:P,reticleConfig:e.reticleOpts,units:p,inference:s,warnings:[...e.removedFieldWarning?[e.removedFieldWarning]:[],...ie(m,s),...K,...e.ringCountWarning?[e.ringCountWarning]:[]],plotMode:P.plotMode,metadata:P.metadata,isLotStack:e.lotStackOpts!==void 0,dataCoverage:Z(V),passBins:e.passBins,ringCount:e.ringCount,yield:ee(V,e.passBins,e.edgeDieYieldMode),reticles:j,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs,metadataFields:e.metadataFields,aggrMethod:P.aggrMethod,lotSize:P.lotSize}}
1
+ import{createWafer as j}from"../core/wafer.js";import{isYieldEligibleDie as he,getDieKey as V,hasPosition as A}from"../core/dies.js";import{applyOrientation as U,transformDies as me,clipDiesToWafer as ge}from"../core/transforms.js";import{generateDies as pe}from"../core/dies.js";import{affineRotation as ye,affineMirror as be,affineCompose as we,affinePoint as xe}from"../core/transforms.js";import{inferWaferFromXY as De}from"../core/inference/wafer.js";import{resolveGridPitch as Me}from"../core/inference/pitch.js";import{assignGridIndices as Ce}from"../core/inference/grid.js";import{generateReticleGrid as ve}from"../core/reticle.js";import{buildView as J}from"./buildView.js";import{maxOf as K,minOf as Q,modeOf as Oe}from"../core/utils.js";import{aggregateValues as Z,aggregateBinCounts as Se}from"../core/aggregates.js";export function isParametricTest(t){return t?.testType!=="F"}function ke(t){const n=(l,s)=>({ringCount:l,ringCountWarning:{code:"analysis-option-corrected",severity:"warning",message:`ringCount=${String(t)} ${s} (using ${l}).`}});if(t===void 0)return{ringCount:4,ringCountWarning:void 0};if(typeof t!="number"||!Number.isFinite(t))return n(4,"is not a finite number");const e=Math.max(1,Math.round(t));return e===t?{ringCount:t,ringCountWarning:void 0}:n(e,"is not a whole number of at least 1")}function ee(t){const n=Array.isArray(t)?{}:t,e=Array.isArray(n.lotStack?.results)?n.lotStack.results:[],l=(Array.isArray(t)?[t]:[n.results,...e]).filter(a=>Array.isArray(a)),s={hbin:0,sbin:0,testValues:0,testPass:0};let r;const h=(a,i)=>{s[a]++,r??=`${a} ${JSON.stringify(i)}`};for(const a of l){const i=new Set,f=new Set;for(let p=0;p<a.length;p+=p<50?1:100){const m=a[p];if(!(m===null||typeof m!="object")){if(m.testValues&&typeof m.testValues=="object")for(const D of Object.keys(m.testValues))i.add(D);if(m.testPass&&typeof m.testPass=="object")for(const D of Object.keys(m.testPass))f.add(D)}}const u=[...i],d=[...f];for(const p of a){if(p===null||typeof p!="object")continue;const m=p;m.hbin!=null&&typeof m.hbin!="number"&&h("hbin",m.hbin),m.sbin!=null&&typeof m.sbin!="number"&&h("sbin",m.sbin);const D=m.testValues;if(D&&typeof D=="object")for(let y=0;y<u.length;y++){const b=D[u[y]];b!=null&&typeof b!="number"&&h("testValues",b)}const C=m.testPass;if(C&&typeof C=="object")for(let y=0;y<d.length;y++){const b=C[d[y]];b!=null&&typeof b!="boolean"&&h("testPass",b)}}}const o=[];if(s.hbin&&o.push(`${s.hbin} hard bin${s.hbin===1?"":"s"}`),s.sbin&&o.push(`${s.sbin} soft bin${s.sbin===1?"":"s"}`),s.testValues&&o.push(`${s.testValues} test value${s.testValues===1?"":"s"}`),s.testPass&&o.push(`${s.testPass} pass/fail verdict${s.testPass===1?"":"s"}`),!o.length)return;const g=`buildWaferMap received ${o.join(", ")} of the wrong type (for example ${r}). Bins and test values must be numbers and verdicts true/false \u2014 a bin of "1" is not pass bin 1, so those dies count as fails and yield is wrong, and a test value given as text is not plotted or analysed correctly. Convert them with Number() (and verdicts to booleans) and rebuild.`;return console.warn(`[wafermap] ${g}`),{code:"input-values-not-numbers",message:g,severity:"error"}}function te(t){const n=(a,i)=>a!==null&&typeof a=="object"&&a[i]!==void 0,e=Array.isArray(t)?{}:t,l=[],s=(a,i,f)=>{a&&l.push(`\`${i}\` (now \`${f}\`)`)};s(n(e,"data"),"data","results"),s(n(e,"die"),"die","dieConfig"),s(n(e,"stack"),"stack","lotStack"),s(n(e.dieConfig,"origin"),"dieConfig.origin","dieConfig.coordinateOrigin"),s(n(e.waferConfig,"flat"),"waferConfig.flat","waferConfig.notch"),s(n(e.reticleConfig,"anchor"),"reticleConfig.anchor","reticleConfig.anchorDie"),s(n(e.lotStack,"aggr"),"lotStack.aggr","lotStack.method"),s(Array.isArray(e.testDefs)&&e.testDefs.some(a=>n(a,"index")&&!n(a,"testNumber")),"TestDef.index","TestDef.testNumber");const r=n(e.lotStack,"results")?e.lotStack.results:[],h=(Array.isArray(t)?[t]:[e.results,...Array.isArray(r)?r:[]]).filter(a=>Array.isArray(a));if(s(h.some(a=>a.slice(0,100).some(i=>n(i,"values"))),"values","testValues"),!l.length)return;const o=l.length>1,g=`buildWaferMap ignored ${l.join(", ")}: ${o?"these names were":"that name was"} removed in an earlier release, so what ${o?"they":"it"} described is not on this map. Rename and rebuild.`;return console.warn(`[wafermap] ${g}`),{code:"input-field-removed",message:g,severity:"error"}}function Ae(t){if(t.results!==void 0||t.lotStack!==void 0)throw new Error("buildWaferMap: pass `layout`, `results` or `lotStack` \u2014 only one.");const n=t.waferConfig?.diameter,e=t.dieConfig?.width,l=t.dieConfig?.height;if(!(n>0&&e>0&&l>0))throw new Error("buildWaferMap: `layout` needs waferConfig.diameter and dieConfig.width and height, all positive.");const s=j({diameter:n,notch:t.waferConfig?.notch,orientation:0}),r=ge(pe(s,{width:e,height:l}),s,{width:e,height:l}).filter(i=>!i.partial),{flipX:h,flipY:o}=ne(t.dieConfig,{type:"center"}),{layout:g,...a}=t;return{...a,results:r.map(i=>({x:h?-i.x:i.x,y:o?-i.y:i.y})),waferConfig:{...t.waferConfig,center:{x:0,y:0}},dieConfig:{...t.dieConfig,coordinateOrigin:{type:"center"}}}}function Re(t){if(!Array.isArray(t)&&"results"in t&&t.results!==void 0&&"lotStack"in t&&t.lotStack!==void 0)throw new Error("buildWaferMap: pass either `results` or `lotStack`, not both.");return Array.isArray(t)?{results:t,waferOpts:void 0,dieOpts:void 0,explicitDies:void 0,reticleOpts:void 0,lotStackOpts:void 0,passBins:[1],ringCount:4,ringCountWarning:void 0,removedFieldWarning:te(t),nonNumericWarning:ee(t),standardDiameters:void 0,testDefs:void 0,hbinDefs:void 0,sbinDefs:void 0,metadataFields:void 0,retestPolicy:"last",edgeDieYieldMode:"exclude"}:{results:t.results??[],waferOpts:t.waferConfig,dieOpts:t.dieConfig,explicitDies:t.dies,reticleOpts:t.reticleConfig,lotStackOpts:t.lotStack,passBins:t.passBins??[1],...ke(t.ringCount),removedFieldWarning:te(t),nonNumericWarning:ee(t),standardDiameters:t.standardDiameters,testDefs:t.testDefs?.length?t.testDefs:void 0,hbinDefs:t.hbinDefs,sbinDefs:t.sbinDefs,metadataFields:t.metadataFields,retestPolicy:t.retestPolicy??"last",edgeDieYieldMode:t.edgeDieYieldMode??"exclude"}}function $e(t,n){return n?.coordinateOrigin?n.coordinateOrigin:{type:"center"}}function We(t,n,e){if(n.type==="custom"&&n.offset)return{offsetX:n.offset.x,offsetY:n.offset.y};if(n.type!=="center"){let l=1/0,s=-1/0,r=1/0,h=-1/0;for(const o of t)o.x<l&&(l=o.x),o.x>s&&(s=o.x),o.y<r&&(r=o.y),o.y>h&&(h=o.y);return{offsetX:Math.round((s+l)/2),offsetY:Math.round((h+r)/2)}}return{offsetX:e.offsetX,offsetY:e.offsetY}}function Ee(t,n,e,l,s,r){if(r)return{colMidX:(Math.round(r.x)-n)*l,colMidY:(Math.round(r.y)-e)*s,anchored:!0};if(t.length===0)return{colMidX:0,colMidY:0,anchored:!1};let h=1/0,o=-1/0,g=1/0,a=-1/0;for(const i of t){const f=Math.round(i.x)-n,u=Math.round(i.y)-e;f<h&&(h=f),f>o&&(o=f),u<g&&(g=u),u>a&&(a=u)}return{colMidX:(h+o)/2*l,colMidY:(g+a)/2*s,anchored:!1}}function Fe(t,n,e){let l=1/0,s=-1/0,r=1/0,h=-1/0,o=0,g=0;for(const f of t){const u=Math.round(f.x)-n,d=Math.round(f.y)-e;o+=u,g+=d,u<l&&(l=u),u>s&&(s=u),d<r&&(r=d),d>h&&(h=d)}const a=t.length,i=(f,u,d)=>{const p=(d-u)/2;return p<=0?0:Math.abs(f/a-(u+d)/2)/p};return i(o,l,s)>.11||i(g,r,h)>.11}function ne(t,n){let e=t?.xAxisDirection==="left",l=t?.yAxisDirection==="down";return(n.type==="UL"||n.type==="UR")&&(l=!0),(n.type==="LR"||n.type==="UR")&&(e=!0),{flipX:e,flipY:l}}function Pe(t,n){const{method:e,targetBin:l}=t,s=t.results.map(r=>r.filter(A));if(e==="mean"||e==="median"||e==="stddev"||e==="min"||e==="max"||e==="count"){const r=new Set((n??[]).filter(i=>!isParametricTest(i)).map(i=>i.testNumber)),h=new Set;for(const i of s)for(const f of i)if(f.testValues)for(const u of Object.keys(f.testValues)){const d=Number(u);r.has(d)||h.add(d)}if(h.size===0)return Z(s,e);const o=[...h],g=o.map(i=>Z(s,e,i)),a=new Map;for(let i=0;i<o.length;i++){const f=o[i];for(const u of g[i]){const d=V(u);a.has(d)||a.set(d,{template:u,testValues:{}});const p=a.get(d),m=u.testValues?.[0];m!==void 0&&(p.testValues[f]=m)}}return[...a.values()].map(({template:i,testValues:f})=>({...i,testValues:Object.keys(f).length>0?f:void 0}))}if(e==="countBin"||e==="percent"){if(l===void 0)return[];const r=Se(s,l,"hard");if(e==="countBin")return r;const h=s.length;return r.map(o=>({...o,testValues:{0:h>0?(o.testValues?.[0]??0)/h*100:0}}))}if(e==="mode"){const r=new Map;for(const o of s)for(const g of o){const a=V(g);r.has(a)||r.set(a,[]),r.get(a).push(g)}const h=[];for(const[o,g]of r){const a=o.split(","),i=Number(a[0]),f=Number(a[1]),u=g.map(p=>p.hbin).filter(p=>p!==void 0),d=Oe(u);d!==null&&h.push({x:i,y:f,hbin:d})}return h}return[]}function se(t){const n=t.filter(A),e=n.length,l=n.filter(r=>r.edgeExcluded).length,s=n.filter(r=>dieHasTestData(r)||r.hbin!==void 0||r.sbin!==void 0).length;return{filledDies:s,totalDies:e,edgeExcludedDies:l,unpositionedDies:t.length-n.length,ratio:e>0?s/e:0}}function ie(t,n,e="exclude"){const l=new Set(n),r=t.filter(d=>!d.partial).filter(d=>d.edgeExcluded).length,h=t.filter(d=>d.partial).length;let o=0,g=0,a=!1;for(const d of t){if(!he(d))continue;const p=d.hbin??d.sbin;p!==void 0&&(a=!0,l.has(p)?o++:g++)}const i=o+g,f=a&&i>0?o/i*100:null;let u=null;if(e==="denominator-only"){const d=i+r;u=a&&d>0?o/d*100:null}return{passDies:o,failDies:g,edgeExcludedDies:r,partialDies:h,totalDies:i,yieldPercent:f,yieldPercentGross:u}}function oe(t,n,e,l,s,r=0,h=0,o=0,g=0,a=0,i=!1,f=!1){if(!t)return[];const u=t.anchorDie??{x:0,y:0},d=ve(n,{width:t.width,height:t.height,diePitchX:l,diePitchY:s,anchorDie:{x:u.x-r,y:u.y-h},gridOrigin:{x:-o,y:-g}}),p=we(be(i,f,n.center.x,n.center.y),ye(a,n.center.x,n.center.y));return d.filter(m=>{const D=m.width/2,C=m.height/2,y=[[m.x-D,m.y-C],[m.x+D,m.y-C],[m.x+D,m.y+C],[m.x-D,m.y+C]].map(([w,B])=>xe(p,w,B)),b=Q(y.map(w=>w.x)),R=K(y.map(w=>w.x)),$=Q(y.map(w=>w.y)),F=K(y.map(w=>w.y));return e.some(w=>w.physX>=b&&w.physX<R&&w.physY>=$&&w.physY<F)})}function Ie(t,n,e){const s=Math.max(0,n.radius-e)**2;return t.map(r=>{const h=r.physX-n.center.x,o=r.physY-n.center.y;return h*h+o*o>s?{...r,edgeExcluded:!0}:r})}function Ne(t,n,e){const l=t.filter(A),s=t.filter(i=>!A(i)),r=new Map;for(const i of l){let f=r.get(i.x);f||(f=new Map,r.set(i.x,f)),f.set(i.y,(f.get(i.y)??0)+1)}const h=new Set(e);function o(i,f){const u=i.hbin,d=f.hbin;if(u===void 0||d===void 0)return!1;const p=h.has(u),m=h.has(d);return n==="best"?m!==p?m:d<u:m!==p?p:d>u}const g=new Map;for(const i of l){const f=V(i),u=g.get(f);n==="first"&&u||(n==="best"||n==="worst")&&u&&!o(u,i)||g.set(f,i)}return[...Array.from(g.values()).map(i=>{const u=r.get(i.x)?.get(i.y)??1;return u>1?{...i,retestCount:u}:i}),...s]}export function getDieTestValue(t,n){return t.testValues?.[n]}export function getTestPassStatus(t,n,e){const l=t.testPass?.[n];if(l!==void 0)return l;if(e!==void 0&&!isParametricTest(e)){const s=t.testValues?.[n];if(s===0||s===1)return s===1}}export function dieHasTestData(t){return t.testValues!==void 0&&Object.keys(t.testValues).length>0||t.testPass!==void 0&&Object.keys(t.testPass).length>0}function _(t,n){const e={};return n.hbin!==void 0&&(e.hbin=n.hbin),n.sbin!==void 0&&(e.sbin=n.sbin),n.retestCount!==void 0&&(e.retestCount=n.retestCount),n.siteNum!==void 0&&(e.siteNum=n.siteNum),n.partId!==void 0&&(e.partId=n.partId),n.metadata!==void 0&&(e.metadata=n.metadata),n.testPass!==void 0&&(e.testPass=n.testPass),n.testValues!==void 0&&(e.testValues=n.testValues),{...t,...e}}function re(t,n){return n.plotMode?n.plotMode:t.some(dieHasTestData)?"value":"hardBin"}function ae(t,n){return t.map(({code:e,message:l})=>({code:e,message:l,severity:e==="non-standard-diameter"||e==="diameter-exceeds-die-extent"?"warning":"error",confidence:n.wafer.confidence}))}const Ve="do not fit inside the supplied",Te="Inferred wafer diameter",_e="The probed dies reach only",Be=.75,Ye=20;export const STANDARD_WAFER_DIAMETERS_MM=[100,125,150,200,300];const Le=.02;function de(t,n){return n.reduce((e,l)=>Math.abs(l-t)<Math.abs(e-t)?l:e)}function Xe(t,n){if(!n.length)return!0;const e=de(t,n);return Math.abs(e-t)/e<=Le}function je(t){return t===void 0?STANDARD_WAFER_DIAMETERS_MM:t.filter(n=>Number.isFinite(n)&&n>0)}export function buildWaferMap(t,n){const e=Re(!Array.isArray(t)&&t.layout?Ae(t):t),{debug:l,...s}=n??{},r=e.lotStackOpts?Pe(e.lotStackOpts,e.testDefs):e.results;for(let c=0;c<Math.min(5,r.length);c++){const x=r[c];if(typeof x.x=="string"||typeof x.y=="string")throw new TypeError("buildWaferMap: x and y must be numbers, received strings. Did you forget to cast CSV values? e.g. { x: +row.x, y: +row.y }")}for(const c of r)if(c.x==null!=(c.y==null))throw new TypeError(`buildWaferMap: die has only one of x/y set (x=${String(c.x)}, y=${String(c.y)}). A die must be either fully positioned (both x and y) or fully unpositioned (neither) \u2014 omit both to mark a die as having no reported position.`);const h=Ne(r,e.retestPolicy,e.passBins),o={wafer:{confidence:1,method:"provided"},diePitch:{confidence:1,units:"mm"},grid:{confidence:1}},g=[];if(e.explicitDies){let c=e.explicitDies;const x=h.filter(A),S=h.filter(N=>!A(N));if(x.length>0){const N=new Map(x.map(k=>[V(k),k]));c=c.map(k=>{const H=N.get(V(k));return H?_(k,H):k})}const I=e.waferOpts?.diameter??300,E=j({diameter:I,notch:e.waferOpts?.notch,orientation:e.waferOpts?.orientation??0,metadata:e.waferOpts?.metadata});E.orientation!==0&&(c=U(c,E));const q=oe(e.reticleOpts,E,c,1,1,0,0,0,0,E.orientation),le=s.showReticle??e.reticleOpts!==void 0,L=J(E,c,{...s,reticles:q,showReticle:le,plotMode:re(h,s),testDefs:e.testDefs,passBins:e.passBins,ringCount:e.ringCount,isLotStack:!1},{hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,metadataFields:e.metadataFields}),ue=S.map((N,k)=>_({id:`unpositioned_${k}`,width:c[0]?.width??1,height:c[0]?.height??1},N)),X=[...c,...ue];return{wafer:E,dies:X,view:L,reticleConfig:e.reticleOpts,units:"mm",inference:o,warnings:[...e.removedFieldWarning?[e.removedFieldWarning]:[],...e.nonNumericWarning?[e.nonNumericWarning]:[],...ae(g,o),...e.ringCountWarning?[e.ringCountWarning]:[]],plotMode:L.plotMode,metadata:L.metadata,isLotStack:!1,dataCoverage:se(X),passBins:e.passBins,ringCount:e.ringCount,yield:ie(X,e.passBins,e.edgeDieYieldMode),reticles:q,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs,metadataFields:e.metadataFields}}const a=h.filter(A),i=h.filter(c=>!A(c)),f=a.map(c=>({x:c.x,y:c.y})),u=Me(f,e.dieOpts,e.waferOpts?.diameter);o.diePitch={confidence:u.confidence,units:u.units};const{pitchX:d,pitchY:p}=u,m=u.units,D=$e(a,e.dieOpts),C=Ce(f);o.grid={confidence:C.confidence};const{offsetX:y,offsetY:b}=We(f,D,C),{flipX:R,flipY:$}=ne(e.dieOpts,D),{colMidX:F,colMidY:w,anchored:B}=Ee(f,y,b,d,p,e.waferOpts?.center);let M=e.waferOpts?.diameter;const T=f.map(c=>({x:(Math.round(c.x)-y)*d-F,y:(Math.round(c.y)-b)*p-w})),W=K(T.map(({x:c,y:x})=>Math.hypot(Math.abs(c)+d/2,Math.abs(x)+p/2)));if(M===void 0)if(f.length>0)if(m==="mm"){const c=De(T,{minRadius:W});M=c.diameter,o.wafer={confidence:c.confidence,method:c.method};const x=je(e.standardDiameters);if(!Xe(M,x)){const S=de(M,x);g.push({code:"non-standard-diameter",message:`${Te} ${M.toFixed(1)} mm is not a standard wafer size (nearest is ${S} mm). The diameter was derived from the die extent, which assumes the probed grid reaches the wafer edge \u2014 a non-standard result usually means it does not, because the outer dies were never probed. Every die is then placed against a wafer that is too small, which moves dies between rings and changes ring and edge findings. Supply waferConfig.diameter.`})}}else M=W*2,o.wafer={confidence:u.confidence*.8,method:"extent"};else M=m==="mm"?300:30,o.wafer={confidence:0,method:"default"};if(!B&&f.length>0&&Fe(f,y,b)&&(o.wafer.method="inferred-partial",g.push({code:"partial-coverage",message:"Wafer geometry was inferred from die positions alone. The data does not span a full symmetric wafer, so the inferred diameter and centre may be wrong and dies may be mis-positioned relative to the true wafer boundary. Supply waferConfig.diameter and waferConfig.center (the prober coordinate of the wafer centre) to position partial data correctly."})),e.waferOpts?.diameter!==void 0&&e.waferOpts.center===void 0&&W>0&&f.length>=Ye){const c=W/(M/2);c<Be&&g.push({code:"diameter-exceeds-die-extent",message:`${_e} ${(c*100).toFixed(0)}% of the ${M} mm wafer's radius. Either the diameter is larger than the real one, or this is a partial map covering only part of the wafer. Ring bands are equal-radius, so the outer rings are mostly or entirely empty and ring, quadrant and edge findings describe the assumed wafer rather than the probed area. Check waferConfig.diameter, or drop it and let the diameter be inferred from the die extent.`})}if(e.waferOpts?.diameter!==void 0){const c=e.dieOpts?.width!==void 0&&e.dieOpts?.height!==void 0;if(c&&W>M/2+1e-9){const x=T.filter(({x:S,y:I})=>Math.hypot(Math.abs(S)+d/2,Math.abs(I)+p/2)>M/2+1e-9).length;g.push({code:"geometry-conflict",message:`${x} of ${T.length} probed die positions ${Ve} ${M} mm wafer at the supplied die pitch of ${d} \xD7 ${p} mm. A die with test results is a real prober position and is always fully on the wafer, so one of the two supplied values must be wrong: containing these dies at this pitch would need a diameter of at least ${(W*2).toFixed(1)} mm. Check waferConfig.diameter and dieConfig.width/height against the real device.`})}}const O=j({diameter:M,notch:e.waferOpts?.notch,orientation:e.waferOpts?.orientation??0,metadata:e.waferOpts?.metadata});let v=a.map(c=>{const x=Math.round(c.x)-y,S=Math.round(c.y)-b,I={id:`${x}_${S}`,x,y:S,physX:x*d-F,physY:S*p-w,width:d,height:p,insideWafer:!0,partial:!1};return _(I,c)});(y!==0||b!==0)&&(v=v.map(c=>({...c,x:c.x+y,y:c.y+b,id:`${c.x+y}_${c.y+b}`}))),v=U(v,O),(R||$)&&(v=me(v,{flipX:R,flipY:$},O.center));const z=[];e.waferOpts?.edgeExclusion&&e.waferOpts.edgeExclusion>0&&(v=Ie(v,O,e.waferOpts.edgeExclusion),e.waferOpts.edgeExclusion>O.radius&&z.push({code:"edge-exclusion-exceeds-radius",message:`edgeExclusion (${e.waferOpts.edgeExclusion}mm) exceeds the resolved wafer radius (${O.radius}mm) \u2014 the entire wafer is excluded.`,severity:"warning"}));const G=oe(e.reticleOpts,O,v,d,p,y,b,F,w,O.orientation,R,$),fe=s.showReticle??e.reticleOpts!==void 0,P=J(O,v,{...s,reticles:G,showReticle:fe,plotMode:re(h,s),testDefs:e.testDefs,passBins:e.passBins,ringCount:e.ringCount,dataAxisFlip:{x:R,y:$},isLotStack:e.lotStackOpts!==void 0,aggregationMethod:e.lotStackOpts?.method,lotSize:e.lotStackOpts?.results.length},{hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,metadataFields:e.metadataFields}),ce=i.map((c,x)=>_({id:`unpositioned_${x}`,width:d,height:p},c)),Y=[...v,...ce];return{wafer:O,dies:Y,view:P,reticleConfig:e.reticleOpts,units:m,inference:o,warnings:[...e.removedFieldWarning?[e.removedFieldWarning]:[],...e.nonNumericWarning?[e.nonNumericWarning]:[],...ae(g,o),...z,...e.ringCountWarning?[e.ringCountWarning]:[]],plotMode:P.plotMode,metadata:P.metadata,isLotStack:e.lotStackOpts!==void 0,dataCoverage:se(Y),passBins:e.passBins,ringCount:e.ringCount,yield:ie(Y,e.passBins,e.edgeDieYieldMode),reticles:G,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs,metadataFields:e.metadataFields,aggrMethod:P.aggrMethod,lotSize:P.lotSize}}
@@ -10,6 +10,13 @@ export declare const DEPRECATED_REMOVAL_VERSION = "0.31.0";
10
10
  * it to the announced list, so a deprecation cannot be added or dropped silently.
11
11
  */
12
12
  export declare const DEPRECATED_EXPORTS: Map<string, "function" | "value">;
13
+ /**
14
+ * @internal Log `message` once per `key` for the life of the page — the one
15
+ * copy of the once-only rule, for export deprecations (`deprecated`) and for
16
+ * notices about an option whose behaviour is changing, which is not an export
17
+ * and so is not registered in DEPRECATED_EXPORTS or held to its removal.
18
+ */
19
+ export declare function noticeOnce(key: string, message: string): void;
13
20
  /**
14
21
  * @internal Wrap `fn` so its first call logs one notice naming `name` and `advice`.
15
22
  * The wrapper has exactly `fn`'s type — generics and overloads included — so a
@@ -1 +1 @@
1
- export const DEPRECATED_REMOVAL_VERSION="0.31.0",DEPRECATED_EXPORTS=new Map;const n=new Set;export function deprecated(t,e,r){DEPRECATED_EXPORTS.set(e,"function");const o=t;return((...c)=>(n.has(e)||(n.add(e),console.warn(`[wafermap] ${e} is deprecated and will be removed in ${DEPRECATED_REMOVAL_VERSION}. ${r}`)),o(...c)))}export function deprecatedValue(t,e){return DEPRECATED_EXPORTS.set(e,"value"),t}
1
+ export const DEPRECATED_REMOVAL_VERSION="0.31.0",DEPRECATED_EXPORTS=new Map;const n=new Set;export function noticeOnce(t,e){n.has(t)||(n.add(t),console.warn(`[wafermap] ${e}`))}export function deprecated(t,e,r){DEPRECATED_EXPORTS.set(e,"function");const o=t;return((...c)=>(noticeOnce(e,`${e} is deprecated and will be removed in ${DEPRECATED_REMOVAL_VERSION}. ${r}`),o(...c)))}export function deprecatedValue(t,e){return DEPRECATED_EXPORTS.set(e,"value"),t}
@@ -28,9 +28,9 @@ export declare const buildView: typeof buildViewImpl;
28
28
  export declare const buildHoverText: typeof buildHoverTextImpl;
29
29
  /** @deprecated Removed in 0.31.0. It belongs to the low-level drawing pipeline, which is being withdrawn: build with `buildWaferMap` and draw with `renderWaferMap` or `renderWaferGallery`. */
30
30
  export declare const buildMapTitle: typeof buildMapTitleImpl;
31
- /** @deprecated Removed in 0.31.0. The maps resolve bin colours themselves: set a colour with `BinDef.color`, or register a palette with `registerBinColorScheme`. */
31
+ /** @deprecated Removed in 0.31.0. Read the colours a map draws with `binColorsForMaps(results)`, which takes each map's own pass bins, or with `getBinColors()` on a WaferMapController or GalleryController. */
32
32
  export declare const resolveBinColors: typeof resolveBinColorsImpl;
33
- /** @deprecated Removed in 0.31.0. The maps resolve bin colours themselves: set a colour with `BinDef.color`, or register a palette with `registerBinColorScheme`. */
33
+ /** @deprecated Removed in 0.31.0. A raw palette lookup bypasses the bin colour rule. Read the colours a map draws with `binColorsForMaps(results)`, or `getBinColors()` on a map or gallery controller. */
34
34
  export declare const getBinColorScheme: typeof getBinColorSchemeImpl;
35
35
  /** @deprecated Removed in 0.31.0. It is an internal helper that was exported by accident; `buildWaferMap`, `analyzeWaferMap` and the renderers already apply it. */
36
36
  export declare const contrastTextColor: typeof contrastTextColorImpl;
@@ -1 +1 @@
1
- import{valueToViridis as a,valueToGreyscale as s}from"./colorMap.js";import{getValueColorScheme as i}from"./colorSchemes.js";import{deprecated as e}from"./deprecate.js";export const valueToViridis=e(a,"valueToViridis","Use resolveValueColorFn('default'), which returns the same colours."),valueToGreyscale=e(s,"valueToGreyscale","Use resolveValueColorFn('greyscale'), which returns the same colours."),getValueColorScheme=e(i,"getValueColorScheme","Colour by value with resolveValueColorFn(name, reversed) \u2014 forValue ignores reverseValueScheme \u2014 and list names and labels with listValueColorSchemes().");import{deprecatedValue as n}from"./deprecate.js";import{buildView as p,buildHoverText as c,buildMapTitle as m}from"./buildView.js";import{resolveBinColors as u}from"./binColors.js";import{getBinColorScheme as d}from"./colorSchemes.js";import{contrastTextColor as T}from"./colorMap.js";import{dieHasTestData as h,isParametricTest as I,getDieTestValue as V,STANDARD_WAFER_DIAMETERS_MM as C}from"./buildWaferMap.js";const o="If you depend on it, say so at https://github.com/wafertools/wafermap/issues.",t="It belongs to the low-level drawing pipeline, which is being withdrawn: build with buildWaferMap and draw with renderWaferMap or renderWaferGallery.",l="The maps resolve bin colours themselves: set a colour with BinDef.color, or register a palette with registerBinColorScheme.",r="It is an internal helper that was exported by accident; buildWaferMap, analyzeWaferMap and the renderers already apply it.",D="Read die.testValues[testNumber].";export const buildView=e(p,"buildView",`${t} ${o}`),buildHoverText=e(c,"buildHoverText",`${t} ${o}`),buildMapTitle=e(m,"buildMapTitle",`${t} ${o}`),resolveBinColors=e(u,"resolveBinColors",`${l} ${o}`),getBinColorScheme=e(d,"getBinColorScheme",`${l} ${o}`),contrastTextColor=e(T,"contrastTextColor",`${r} ${o}`),dieHasTestData=e(h,"dieHasTestData",`${r} ${o}`),isParametricTest=e(I,"isParametricTest",`${r} ${o}`),getDieTestValue=e(V,"getDieTestValue",`${D} ${o}`),STANDARD_WAFER_DIAMETERS_MM=n(C,"STANDARD_WAFER_DIAMETERS_MM");
1
+ import{valueToViridis as a,valueToGreyscale as l}from"./colorMap.js";import{getValueColorScheme as s}from"./colorSchemes.js";import{deprecated as e}from"./deprecate.js";export const valueToViridis=e(a,"valueToViridis","Use resolveValueColorFn('default'), which returns the same colours."),valueToGreyscale=e(l,"valueToGreyscale","Use resolveValueColorFn('greyscale'), which returns the same colours."),getValueColorScheme=e(s,"getValueColorScheme","Colour by value with resolveValueColorFn(name, reversed) \u2014 forValue ignores reverseValueScheme \u2014 and list names and labels with listValueColorSchemes().");import{deprecatedValue as i}from"./deprecate.js";import{buildView as n,buildHoverText as p,buildMapTitle as u}from"./buildView.js";import{resolveBinColors as c}from"./binColors.js";import{getBinColorScheme as m}from"./colorSchemes.js";import{contrastTextColor as d}from"./colorMap.js";import{dieHasTestData as T,isParametricTest as h,getDieTestValue as C,STANDARD_WAFER_DIAMETERS_MM as I}from"./buildWaferMap.js";const o="If you depend on it, say so at https://github.com/wafertools/wafermap/issues.",r="It belongs to the low-level drawing pipeline, which is being withdrawn: build with buildWaferMap and draw with renderWaferMap or renderWaferGallery.",t="It is an internal helper that was exported by accident; buildWaferMap, analyzeWaferMap and the renderers already apply it.",V="Read die.testValues[testNumber].";export const buildView=e(n,"buildView",`${r} ${o}`),buildHoverText=e(p,"buildHoverText",`${r} ${o}`),buildMapTitle=e(u,"buildMapTitle",`${r} ${o}`),resolveBinColors=e(c,"resolveBinColors",`Read the colours a map draws with binColorsForMaps(results), which takes each map's own pass bins, or with getBinColors() on a WaferMapController or GalleryController. ${o}`),getBinColorScheme=e(m,"getBinColorScheme",`A raw palette lookup bypasses the bin colour rule. Read the colours a map draws with binColorsForMaps(results), or getBinColors() on a map or gallery controller. ${o}`),contrastTextColor=e(d,"contrastTextColor",`${t} ${o}`),dieHasTestData=e(T,"dieHasTestData",`${t} ${o}`),isParametricTest=e(h,"isParametricTest",`${t} ${o}`),getDieTestValue=e(C,"getDieTestValue",`${V} ${o}`),STANDARD_WAFER_DIAMETERS_MM=i(I,"STANDARD_WAFER_DIAMETERS_MM");
@@ -3,6 +3,7 @@ export type { View, MapTitleParts, PlotMode, ViewRect, ViewText, ViewHoverPoint,
3
3
  export * from './buildWaferMap.js';
4
4
  export type { WaferMetadata, DieMetadata } from '../core/metadata.js';
5
5
  export * from './colorSchemes.js';
6
- export type { BinColors, BinColorOptions } from './binColors.js';
6
+ export type { BinColors, BinColorOptions, BinColorSource, MapBinColorOptions } from './binColors.js';
7
+ export { binColorsForMaps } from './binColors.js';
7
8
  export { valueToViridis, valueToGreyscale, getValueColorScheme, buildView, buildHoverText, buildMapTitle, resolveBinColors, getBinColorScheme, contrastTextColor, dieHasTestData, isParametricTest, getDieTestValue, STANDARD_WAFER_DIAMETERS_MM, } from './deprecated.js';
8
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- export{getDieKey}from"./buildView.js";export*from"./buildWaferMap.js";export*from"./colorSchemes.js";export{valueToViridis,valueToGreyscale,getValueColorScheme,buildView,buildHoverText,buildMapTitle,resolveBinColors,getBinColorScheme,contrastTextColor,dieHasTestData,isParametricTest,getDieTestValue,STANDARD_WAFER_DIAMETERS_MM}from"./deprecated.js";
1
+ export{getDieKey}from"./buildView.js";export*from"./buildWaferMap.js";export*from"./colorSchemes.js";export{binColorsForMaps}from"./binColors.js";export{valueToViridis,valueToGreyscale,getValueColorScheme,buildView,buildHoverText,buildMapTitle,resolveBinColors,getBinColorScheme,contrastTextColor,dieHasTestData,isParametricTest,getDieTestValue,STANDARD_WAFER_DIAMETERS_MM}from"./deprecated.js";
@@ -1 +1 @@
1
- import{analyzeWaferMap as h}from"./analyzeWaferMap.js";import{median as y}from"../core/utils.js";import{describeWaferPopulation as p,populationStat as g}from"./population.js";const b=1.3,w=2;function v(f,c){const i={info:0,notable:1,unusual:2};return i[f]>=i[c]?f:c}function I(f){const c=new Map;for(const l of f){const s=new Set,o=new Set(l.summary.findings.flatMap(a=>a.absorbedIds??[]));for(const a of l.summary.findings){if(o.has(a.id))continue;const r=[a.variable.kind,a.variable.index??"",a.variable.bin??"",a.comparison.family,a.comparison.left,a.effect.direction].join("|");if(s.has(r))continue;s.add(r);const n=c.get(r)??{finding:a,waferIndices:[],severity:a.severity};n.waferIndices.push(l.waferIndex),n.severity=v(n.severity,a.severity),c.set(r,n)}}const i=[];for(const[l,s]of c){if(s.waferIndices.length<w)continue;const o=s.waferIndices.length/Math.max(1,f.length),a=o>=.6||s.severity==="unusual"?"unusual":"notable",r=s.finding;i.push({id:`lot-repeat:${l}`,level:"lot",severity:a,variable:{...r.variable},comparison:{...r.comparison},effect:{direction:r.effect.direction,absoluteDelta:r.effect.absoluteDelta,relativeDelta:r.effect.relativeDelta,effectSize:r.effect.effectSize},stats:{method:"wafer-finding-frequency",sampleSizeLeft:s.waferIndices.length,sampleSizeRight:f.length-s.waferIndices.length},summary:`${r.summary} \u2014 seen on ${s.waferIndices.length}/${f.length} wafers (${Math.round(o*100)}%)`,highlight:{kind:"wafer",waferIndices:s.waferIndices}})}return i}function S(f,c){const i=g(c,"median"),l=f.map(n=>({waferIndex:n.waferIndex,yieldPercent:n.summary.stats.yieldPercent})).filter(n=>n.yieldPercent!==null);if(l.length<3)return[];const s=l.map(n=>n.yieldPercent),o=y(s),a=y(s.map(n=>Math.abs(n-o)));if(!Number.isFinite(a)||a===0)return[];const r=[];for(const n of l){const e=.6745*(n.yieldPercent-o)/a;if(Math.abs(e)<b)continue;const t=n.yieldPercent-o;r.push({id:`inter-wafer:yield:${n.waferIndex}`,level:"inter-wafer",severity:Math.abs(e)>=2?"unusual":"notable",variable:{kind:"yield",label:"Yield"},comparison:{family:"wafer",left:`Wafer ${n.waferIndex+1}`,right:i.charAt(0).toUpperCase()+i.slice(1)},effect:{direction:t>0?"higher":"lower",absoluteDelta:t,relativeDelta:o===0?void 0:t/o,effectSize:e},stats:{method:"mad-z-score",sampleSizeLeft:1,sampleSizeRight:l.length-1},summary:`Wafer ${n.waferIndex+1} yield is ${Math.abs(t).toFixed(1)} percentage points ${t>0?"higher":"lower"} than the ${i}`,highlight:{kind:"wafer",waferIndices:[n.waferIndex]}})}return r}export function analyzeWaferLot(f,c={}){const i=f.map((e,t)=>({waferIndex:t,summary:c.perWaferSummaries?.[t]??h(e,c)})),l=[...I(i),...S(i,p(i.map(e=>e.summary.wafer)))].sort((e,t)=>{const d=e.severity==="unusual"?2:e.severity==="notable"?1:0,m=t.severity==="unusual"?2:t.severity==="notable"?1:0;return d!==m?m-d:e.summary.localeCompare(t.summary,"en")}),s=i.map(e=>e.summary.wafer).filter(e=>!!e),o={},a=[];if(s.length>0){const e=new Set;for(const t of s)for(const d of Object.keys(t))e.add(d);e.delete("wafer"),e.delete("waferId");for(const t of e){const d=s.filter(u=>t in u).map(u=>u[t]);d.every(u=>u===d[0])?o[t]=d[0]:a.push(t)}}const r=i.map(({waferIndex:e,summary:t})=>({waferIndex:e,yieldPercent:t.stats.yieldPercent})),n=i.map(({waferIndex:e,summary:t})=>t.stats.perTestStats?.length?{waferIndex:e,tests:t.stats.perTestStats}:null).filter(e=>e!==null);return{level:"lot",hasNotableFindings:l.some(e=>e.severity!=="info")||i.some(e=>e.summary.hasNotableFindings),findings:l,lot:Object.keys(o).length>0?o:void 0,...a.length>0?{mixedIdentityFields:a}:{},stats:{waferCount:f.length},lotYieldSeries:r,perWafer:i,...n.length>0?{perWaferTestStats:n}:{}}}
1
+ import{analyzeWaferMap as g}from"./analyzeWaferMap.js";import{normalizeInput as h}from"./normalizeInput.js";import{poolCapability as b,poolRegionYield as w,poolPassRates as v}from"./summaryFigures.js";import{mergeTestDefs as I}from"./mergeTestDefs.js";import{median as p}from"../core/utils.js";import{describeWaferPopulation as S,populationStat as k}from"./population.js";const P=1.3,R=2;function x(f,c){const o={info:0,notable:1,unusual:2};return o[f]>=o[c]?f:c}function z(f){const c=new Map;for(const t of f){const i=new Set,l=new Set(t.summary.findings.flatMap(n=>n.absorbedIds??[]));for(const n of t.summary.findings){if(l.has(n.id))continue;const s=[n.variable.kind,n.variable.index??"",n.variable.bin??"",n.comparison.family,n.comparison.left,n.effect.direction].join("|");if(i.has(s))continue;i.add(s);const a=c.get(s)??{finding:n,waferIndices:[],severity:n.severity};a.waferIndices.push(t.waferIndex),a.severity=x(a.severity,n.severity),c.set(s,a)}}const o=[];for(const[t,i]of c){if(i.waferIndices.length<R)continue;const l=i.waferIndices.length/Math.max(1,f.length),n=l>=.6||i.severity==="unusual"?"unusual":"notable",s=i.finding;o.push({id:`lot-repeat:${t}`,level:"lot",severity:n,variable:{...s.variable},comparison:{...s.comparison},effect:{direction:s.effect.direction,absoluteDelta:s.effect.absoluteDelta,relativeDelta:s.effect.relativeDelta,effectSize:s.effect.effectSize},stats:{method:"wafer-finding-frequency",sampleSizeLeft:i.waferIndices.length,sampleSizeRight:f.length-i.waferIndices.length},summary:`${s.summary} \u2014 seen on ${i.waferIndices.length}/${f.length} wafers (${Math.round(l*100)}%)`,highlight:{kind:"wafer",waferIndices:i.waferIndices}})}return o}function $(f,c){const o=k(c,"median"),t=f.map(a=>({waferIndex:a.waferIndex,yieldPercent:a.summary.stats.yieldPercent})).filter(a=>a.yieldPercent!==null);if(t.length<3)return[];const i=t.map(a=>a.yieldPercent),l=p(i),n=p(i.map(a=>Math.abs(a-l)));if(!Number.isFinite(n)||n===0)return[];const s=[];for(const a of t){const u=.6745*(a.yieldPercent-l)/n;if(Math.abs(u)<P)continue;const e=a.yieldPercent-l;s.push({id:`inter-wafer:yield:${a.waferIndex}`,level:"inter-wafer",severity:Math.abs(u)>=2?"unusual":"notable",variable:{kind:"yield",label:"Yield"},comparison:{family:"wafer",left:`Wafer ${a.waferIndex+1}`,right:o.charAt(0).toUpperCase()+o.slice(1)},effect:{direction:e>0?"higher":"lower",absoluteDelta:e,relativeDelta:l===0?void 0:e/l,effectSize:u},stats:{method:"mad-z-score",sampleSizeLeft:1,sampleSizeRight:t.length-1},summary:`Wafer ${a.waferIndex+1} yield is ${Math.abs(e).toFixed(1)} percentage points ${e>0?"higher":"lower"} than the ${o}`,highlight:{kind:"wafer",waferIndices:[a.waferIndex]}})}return s}export function analyzeWaferLot(f,c={}){const o=f.map(h),t=o.map((e,r)=>({waferIndex:r,summary:c.perWaferSummaries?.[r]??g(e,c)})),i=[...z(t),...$(t,S(t.map(e=>e.summary.wafer)))].sort((e,r)=>{const d=e.severity==="unusual"?2:e.severity==="notable"?1:0,y=r.severity==="unusual"?2:r.severity==="notable"?1:0;return d!==y?y-d:e.summary.localeCompare(r.summary,"en")}),l=t.map(e=>e.summary.wafer).filter(e=>!!e),n={},s=[];if(l.length>0){const e=new Set;for(const r of l)for(const d of Object.keys(r))e.add(d);e.delete("wafer"),e.delete("waferId");for(const r of e){const d=l.filter(m=>r in m).map(m=>m[r]);d.every(m=>m===d[0])?n[r]=d[0]:s.push(r)}}const a=t.map(({waferIndex:e,summary:r})=>({waferIndex:e,yieldPercent:r.stats.yieldPercent})),u=t.map(({waferIndex:e,summary:r})=>r.stats.perTestStats?.length?{waferIndex:e,tests:r.stats.perTestStats}:null).filter(e=>e!==null);return{level:"lot",hasNotableFindings:i.some(e=>e.severity!=="info")||t.some(e=>e.summary.hasNotableFindings),findings:i,lot:Object.keys(n).length>0?n:void 0,...s.length>0?{mixedIdentityFields:s}:{},stats:{waferCount:f.length,...M(o,t.map(e=>e.summary))},lotYieldSeries:a,perWafer:t,...u.length>0?{perWaferTestStats:u}:{}}}function M(f,c){const o={...v(c)},t=b(c,I(f).defs);t&&(o.capability=t);const i=w(c,f.map(l=>l.ringCount));return i&&(o.regionYield=i),o}
@@ -1 +1 @@
1
- import{isYieldEligibleDie as te,getDieKey as x,isPositionedDie as ge}from"../core/dies.js";import{buildWaferMap as Be,getTestPassStatus as he,isParametricTest as A}from"../renderer/buildWaferMap.js";import{buildQuadrantRegions as Fe,buildReticlePositionRegions as Ne,buildRingRegions as Ve,buildSectorRegions as Ce,buildTestSiteRegions as Pe,sectorCompassNames as me,areQuadrantsAdjacent as Le,parseRegionKey as O}from"./regions.js";import{buildClusterFindings as Ee}from"./clusterDetection.js";import{classifyPattern as Ke}from"./patternClassification.js";import{normalCdf as pe}from"./math.js";import{quantile as ne}from"./math.js";import{mean as J,clamp01 as be}from"../core/utils.js";const xe={significanceLevel:{min:1e-6,max:1},minimumEffectSize:{min:0,max:1},minimumRelativeEffect:{min:0,max:Number.MAX_SAFE_INTEGER}},ye=[4,8,16,32];function Te(n){const a=Object.fromEntries(Object.entries(n).filter(([,t])=>t!==void 0)),s={...T,...a},o=[],e=[];for(const t of["significanceLevel","minimumEffectSize","minimumRelativeEffect"]){const{min:l,max:i}=xe[t],u=s[t];if(typeof u!="number"||!Number.isFinite(u)){e.push(`${t}=${String(u)} is not a finite number (using ${T[t]})`),s[t]=T[t];continue}const f=Math.min(i,Math.max(l,u));f!==u&&(e.push(`${t}=${u} is outside ${l}\u2013${i} (using ${f})`),s[t]=f)}if(ye.includes(s.sectorCount)||(e.push(`sectorCount=${s.sectorCount} is not one of ${ye.join(", ")} (using ${T.sectorCount})`),s.sectorCount=T.sectorCount),e.length){const t=`analyzeWaferMap: ${e.join("; ")}. The analysis ran with the corrected values.`;console.warn(`[wafermap] ${t}`),o.push({code:"analysis-option-corrected",message:t,severity:"warning"})}const c=["passBins","ringCount"].filter(t=>a[t]!==void 0);if(c.length){const t=`analyzeWaferMap: ${c.join(" and ")} ${c.length>1?"are":"is"} no longer an analysis option and was ignored. Set ${c.length>1?"them":"it"} on buildWaferMap; the analysis used the value the map was built with.`;console.warn(`[wafermap] ${t}`),o.push({code:"analysis-option-corrected",message:t,severity:"warning"})}const r=["enableYieldAnalysis","enableHardBinAnalysis","enableSoftBinAnalysis","enableReticlePositionAnalysis","enableTestSiteAnalysis","enableClusterAnalysis","enableAngularAnalysis","enablePatternClassification"].filter(t=>a[t]!==void 0);if(r.length){const t=`analyzeWaferMap: ${r.join(", ")} ${r.length>1?"are":"is"} no longer an option and was ignored: every analysis now runs. Filter the findings (filterFindings) to show fewer.`;console.warn(`[wafermap] ${t}`),o.push({code:"analysis-option-corrected",message:t,severity:"warning"})}return{resolved:s,warnings:o}}const T={ringCount:4,passBins:[1],significanceLevel:.05,minimumEffectSize:.2,minimumRelativeEffect:1,minimumSampleSize:5,enableTestValueAnalysis:!1,computePerTestStats:!1,sectorCount:8,minimumClusterSize:5};function je(n,a){const s={...n};return s.minimumClusterSize=Math.max(5,Math.round(a*.003)),s}function Ae(n){return"wafer"in n&&"dies"in n&&"view"in n?n:Be(n)}function Oe(n){return te(n)?n.hbin!==void 0||n.sbin!==void 0||n.testValues!==void 0&&Object.keys(n.testValues).length>0:!1}function qe(n,a,s){if(!n||a)return;const o=(s??[]).filter(e=>A(e)&&(e.limitLow!==void 0||e.limitHigh!==void 0));if(o.length!==0)return e=>{for(const c of o){const r=c.testNumber,t=e.testValues?.[r];if(t!==void 0&&(c.limitLow!==void 0&&t<c.limitLow||c.limitHigh!==void 0&&t>c.limitHigh))return!0}return!1}}function He(n,a,s){const o=new Set,e=new Set,c=new Set,r=new Map,t=new Map;for(const l of n){if(l.testValues)for(const i of Object.keys(l.testValues))o.add(Number(i));if(l.testPass)for(const i of Object.keys(l.testPass))o.add(Number(i));l.hbin!==void 0&&e.add(l.hbin),l.sbin!==void 0&&c.add(l.sbin),te(l)&&(l.hbin!==void 0&&r.set(l.hbin,(r.get(l.hbin)??0)+1),l.sbin!==void 0&&t.set(l.sbin,(t.get(l.sbin)??0)+1))}return{totalDies:n.length,analyzedDies:a,excludedDies:n.length-a,yieldPercent:s,testsConsidered:[...o].sort((l,i)=>l-i),hardBinsConsidered:[...e].sort((l,i)=>l-i),softBinsConsidered:[...c].sort((l,i)=>l-i),...r.size?{hardBinCounts:Object.fromEntries(r)}:{},...t.size?{softBinCounts:Object.fromEntries(t)}:{}}}function Ie(n,a){if(!a?.length)return;const s=a.filter(e=>A(e)&&(e.limitLow!==void 0||e.limitHigh!==void 0));if(!s.length)return;const o=[];for(const e of s){const c=e.testNumber;let r=0,t=0,l=0,i=0;for(const u of n){if(u.partial||u.edgeExcluded)continue;const f=u.testValues?.[c];f!==void 0&&(i++,e.limitLow!==void 0&&f<e.limitLow?t++:e.limitHigh!==void 0&&f>e.limitHigh?l++:r++)}o.push({testNumber:c,label:e.name,passDies:r,failLowDies:t,failHighDies:l,totalDies:i,yieldPercent:i>0?r/i*100:null})}return o.length?o:void 0}export function computeFunctionalYield(n,a){if(!a?.length)return;const s=a.filter(e=>!A(e));if(!s.length)return;const o=[];for(const e of s){const c=e.testNumber;let r=0,t=0;for(const i of n){if(i.partial||i.edgeExcluded)continue;const u=he(i,c,e);u!==void 0&&(u?r++:t++)}const l=r+t;o.push({testNumber:c,label:e.name,passDies:r,failDies:t,totalDies:l,passRatePercent:l>0?r/l*100:null})}return o.length?o:void 0}function _e(n,a,s,o){const e=[];for(const c of a){const r=[];for(const f of n){if(f.partial||f.edgeExcluded)continue;const h=f.testValues?.[c];h!==void 0&&r.push(h)}if(r.length<o)continue;const t=J(r),l=Math.sqrt(fe(r,t)),i=r.slice().sort((f,h)=>f-h),u=s?.find(f=>f.testNumber===c)?.name??String(c);e.push({testNumber:c,label:u,count:r.length,min:i[0],max:i[i.length-1],mean:t,stddev:l,median:ne(i,.5),q1:ne(i,.25),q3:ne(i,.75)})}return e.length?e:void 0}function q(n,a,s,o){const e=(n+s)/(a+o),c=e*(1-e)*(1/a+1/o);if(!Number.isFinite(c)||c<=0)return 1;const r=(n/a-s/o)/Math.sqrt(c);return be(2*(1-pe(Math.abs(r))))}function ie(n){const a=new Map;for(const s of n){const o=`${s.variable.kind}:${s.comparison.family}`,e=a.get(o)??[];e.push(s),a.set(o,e)}for(const s of a.values()){const o=[...s].filter(r=>r.stats.pValue!==void 0).sort((r,t)=>(r.stats.pValue??1)-(t.stats.pValue??1)),e=o.length;let c=1;for(let r=e-1;r>=0;r--){const t=o[r],l=t.stats.pValue??1,i=Math.min(1,Math.min(c,l*e/(r+1)));t.stats.adjustedPValue=i,c=i}}return n}function se(n,a,s){const o=Math.abs(a),e=s!==void 0?Math.abs(s):0;return n<=.01&&(o>=.3||e>=2.5)?"unusual":n<=.05&&(o>=.2||e>=1.5)?"notable":"info"}function oe(n,a){return n<=.01&&Math.abs(a)>=.5?"unusual":n<=.05&&Math.abs(a)>=.15?"notable":"info"}function Z(n){return n==="reticle-position"?"other reticle positions":n==="test-site"?"other test sites":"the rest of the map"}function H(n){return n==="reticle-position"?"Other reticle positions":n==="test-site"?"Other test sites":"Rest of map"}function Se(n,a,s){const o=(Math.abs(a)*100).toFixed(1);return`${n} yield is ${o} percentage points ${a>0?"higher":"lower"} than ${Z(s)}`}function ae(n,a){return a==="quadrant"&&!n.startsWith("Quadrant")?`quadrant ${n}`:n}function we(n,a,s,o){const e=ae(n,o),c=(Math.abs(s)*100).toFixed(1);return`${e} has ${a} occurrence ${c} percentage points ${s>0?"higher":"lower"} than ${Z(o)}`}function We(n,a,s,o){const e=ae(n,o),c=(Math.abs(s)*100).toFixed(1);return`${e} has ${a} pass rate ${c} percentage points ${s>0?"higher":"lower"} than ${Z(o)}`}function $e(n,a,s,o,e,c){const r=ae(n,e),t=Z(e),l=s>0?"higher":"lower";if(o!==void 0&&Number.isFinite(o)){const u=(Math.abs(o)*100).toFixed(1);return`${r} mean ${a} is ${u}% ${l} than ${t}`}const i=c?` ${c}`:"";return`${r} mean ${a} is ${Math.abs(s).toPrecision(3)}${i} ${l} than ${t}`}function ve(n,a,s){const o=a?.find(e=>e.bin===n);return o?.name?`${s} ${n} (${o.name})`:`${s} ${n}`}function Ye(n,a){const s=a?.find(o=>o.testNumber===n);return{label:s?.name??`Test ${n}`,unit:s?.unit}}function re(n,a){const s=new Map(n.map(e=>[x(e),e])),o=new Map;for(const e of a){const c=[];for(const r of e.dieKeys){const t=s.get(r);t&&c.push(t)}o.set(e.key,c)}return o}function le(n,a){return ie(n),n.filter(s=>{const o=s.stats.adjustedPValue??s.stats.pValue??1,e=Math.abs(s.effect.absoluteDelta??0),c=Math.abs(s.effect.relativeDelta??0);return o<=a.significanceLevel&&(e>=a.minimumEffectSize||c>=a.minimumRelativeEffect)}).map(s=>({...s,severity:se(s.stats.adjustedPValue??s.stats.pValue??1,s.effect.absoluteDelta??0,s.effect.relativeDelta)}))}function W(n,a,s,o){const e=new Set(s),c=re(n,a),r=new Map,t=new Map;for(const[i,u]of c){let f=0,h=0;for(const d of u)d.hbin!==void 0&&(h++,e.has(d.hbin)&&f++);r.set(i,f),t.set(i,h)}const l=[];for(const i of a){const u=t.get(i.key)??0,f=r.get(i.key)??0;let h=0,d=0;for(const[$,m]of t)$!==i.key&&(h+=m,d+=r.get($)??0);if(u<o.minimumSampleSize||h<o.minimumSampleSize)continue;const p=f/u,b=d/h,y=p-b,z=q(f,u,d,h);l.push({id:`yield:${i.key}`,level:"wafer",severity:"info",variable:{kind:"yield",label:"Yield"},comparison:{family:i.family,left:i.label,right:H(i.family)},effect:{direction:y===0?"different":y>0?"higher":"lower",absoluteDelta:y,relativeDelta:b===0?void 0:y/b,effectSize:y},stats:{method:"two-proportion-z",pValue:z,sampleSizeLeft:u,sampleSizeRight:h},summary:Se(i.label,y,i.family),highlight:{kind:"region",regionFamily:i.family,regionKeys:[i.key],dieKeys:[...i.dieKeys]}})}return le(l,o)}function P(n,a,s,o,e,c){const r=d=>s==="soft"?d.sbin:d.hbin,t=[...new Set(n.map(r).filter(d=>d!==void 0))].sort((d,p)=>d-p),l=re(n,a),i=new Map,u=new Map;for(const[d,p]of l){const b=new Map;for(const y of p){const z=r(y);z!==void 0&&b.set(z,(b.get(z)??0)+1)}i.set(d,b),u.set(d,p.length)}const f=[],h=e==="hardBin"?"HBin":"SBin";for(const d of a){const p=u.get(d.key)??0,b=i.get(d.key);let y=0;const z=new Map;for(const[$,m]of i)if($!==d.key){y+=u.get($)??0;for(const[w,D]of m)z.set(w,(z.get(w)??0)+D)}if(!(p<c.minimumSampleSize||y<c.minimumSampleSize))for(const $ of t){const m=b.get($)??0,w=z.get($)??0,D=m/p,v=w/y,S=D-v,R=q(m,p,w,y),M=ve($,o,h);f.push({id:`${e}:${$}:${d.key}`,level:"wafer",severity:"info",variable:{kind:e,bin:$,label:M},comparison:{family:d.family,left:d.label,right:H(d.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:R,sampleSizeLeft:p,sampleSizeRight:y},summary:we(d.label,M,S,d.family),highlight:{kind:"bin",bin:$,regionKeys:[d.key],dieKeys:[...d.dieKeys]}})}}return le(f,c)}function Y(n,a,s,o){const e=(s??[]).filter(u=>!A(u));if(!e.length)return[];const c=e.map(u=>u.testNumber),r=re(n,a),t=new Map,l=new Map;for(const[u,f]of r){const h=new Uint32Array(e.length),d=new Uint32Array(e.length);for(const p of f)for(let b=0;b<e.length;b++){const y=he(p,c[b],e[b]);y!==void 0&&(d[b]++,y&&h[b]++)}t.set(u,h),l.set(u,d)}const i=[];for(const u of a){const f=t.get(u.key),h=l.get(u.key);for(let d=0;d<e.length;d++){let p=0,b=0;for(const[S,R]of l)S!==u.key&&(b+=R[d],p+=t.get(S)[d]);const y=h[d],z=f[d];if(y<o.minimumSampleSize||b<o.minimumSampleSize)continue;const $=z/y,m=p/b,w=$-m,D=q(z,y,p,b),v=c[d];i.push({id:`functional:${v}:${u.key}`,level:"wafer",severity:"info",variable:{kind:"functionalTest",index:v,label:`${e[d].name} pass rate`},comparison:{family:u.family,left:u.label,right:H(u.family)},effect:{direction:w===0?"different":w>0?"higher":"lower",absoluteDelta:w,relativeDelta:m===0?void 0:w/m,effectSize:w},stats:{method:"two-proportion-z",pValue:D,sampleSizeLeft:y,sampleSizeRight:b},summary:We(u.label,e[d].name,w,u.family),highlight:{kind:"region",regionFamily:u.family,regionKeys:[u.key],dieKeys:[...u.dieKeys]}})}}return le(i,o)}function fe(n,a){return n.length<2?0:n.reduce((s,o)=>s+(o-a)**2,0)/(n.length-1)}function De(n,a,s,o,e,c){const r=Math.sqrt(s/n+c/o),t=a-e;if(!Number.isFinite(r)||r===0)return{pValue:1,effectSize:0,delta:t};const l=t/r,i=Math.sqrt(Math.max(0,(s+c)/2)),u=i===0?t:t/i;return{pValue:be(2*(1-pe(Math.abs(l)))),effectSize:u,delta:t}}function Qe(n,a){const s=J(n),o=J(a);return De(n.length,s,fe(n,s),a.length,o,fe(a,o))}const ke=250;function ze(n,a,s){const o=r=>r.filter(t=>A(s?.find(l=>l.testNumber===t)));if(a)return{testNumbers:o(a.slice().sort((r,t)=>r-t))};const e=new Set;let c=!1;e:for(const r of n)for(const t of Object.keys(r.testValues??{})){const l=Number(t);if(!e.has(l)&&(e.add(l),e.size>ke)){c=!0;break e}}if(c){const r=`Test-value analysis was skipped: more than ${ke} tests were found in the die data, so no test findings were computed. Pass testNumbers: [...] to analyse specific tests.`;return console.warn(`[wafermap] analyzeWaferMap: ${r}`),{testNumbers:[],warning:{code:"test-count-capped",message:r,severity:"warning"}}}return{testNumbers:o([...e].sort((r,t)=>r-t))}}function Q(n,a,s,o){const e=ze(n,o.testNumbers,s);if(e.warning)return{findings:[],warning:e.warning};const c=e.testNumbers,r=new Map;for(let p=0;p<a.length;p++)for(const b of a[p].dieKeys)r.set(b,p);const t=a.length,l=[],i=[];for(const p of n){const b=r.get(x(p));b!==void 0&&(l.push(p),i.push(b))}const u=[],f=new Float64Array(t),h=new Float64Array(t),d=new Float64Array(t);for(const p of c){f.fill(0),h.fill(0),d.fill(0);let b=0,y=0,z=0,$;for(let m=0;m<l.length;m++){const D=l[m].testValues?.[p];if(D===void 0)continue;$===void 0&&($=D);const v=D-$,S=i[m];f[S]+=1,h[S]+=v,d[S]+=v*v,b+=1,y+=v,z+=v*v}if($!==void 0)for(let m=0;m<t;m++){const w=f[m],D=b-w;if(w<o.minimumSampleSize||D<o.minimumSampleSize)continue;const v=h[m]/w,R=(y-h[m])/D,M=v+$,g=R+$,B=w>1?Math.max(0,d[m]-w*v*v)/(w-1):0,k=D>1?Math.max(0,z-d[m]-D*R*R)/(D-1):0,{pValue:V,effectSize:F,delta:L}=De(w,M,B,D,g,k),C=a[m],{label:_,unit:U}=Ye(p,s),G=g!==0?L/Math.abs(g):void 0;u.push({id:`test:${p}:${C.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:p,label:_,unit:U},comparison:{family:C.family,left:C.label,right:H(C.family)},effect:{direction:L===0?"different":L>0?"higher":"lower",absoluteDelta:L,relativeDelta:G,effectSize:F},stats:{method:"welch-z-approx",pValue:V,sampleSizeLeft:w,sampleSizeRight:D},summary:$e(C.label,_,L,G,C.family,U),highlight:{kind:"region",regionFamily:C.family,regionKeys:[C.key],dieKeys:[...C.dieKeys]}})}}return ie(u),{activeTestNumbers:c,findings:u.filter(p=>{const b=p.stats.adjustedPValue??p.stats.pValue??1,y=Math.abs(p.effect.effectSize??0);return b<=o.significanceLevel&&y>=o.minimumEffectSize}).map(p=>({...p,severity:oe(p.stats.adjustedPValue??p.stats.pValue??1,p.effect.effectSize??0)}))}}function Ue(n,a,s,o){if(!s?.length)return[];const e=s.filter(t=>A(t)&&(t.limitLow!==void 0||t.limitHigh!==void 0));if(!e.length)return[];const c=[],r=new Map(n.map(t=>[x(t),t]));for(const t of e){const l=t.testNumber;for(const i of a){const u=new Map;for(const h of i){const d=[];for(const p of h.dieKeys){const b=r.get(p);b&&d.push(b)}u.set(h.key,d)}const f=[];for(const h of i){const d=u.get(h.key),p=[];for(const[M,g]of u)if(M!==h.key)for(const B of g)p.push(B);const b=M=>M.testValues?.[l]!==void 0,y=M=>{const g=M.testValues?.[l];return g===void 0?!1:t.limitLow!==void 0&&g<t.limitLow||t.limitHigh!==void 0&&g>t.limitHigh},z=d.filter(b),$=p.filter(b);if(z.length<o.minimumSampleSize||$.length<o.minimumSampleSize)continue;const m=z.filter(y).length,w=$.filter(y).length,D=m/z.length,v=w/$.length,S=D-v,R=q(m,z.length,w,$.length);f.push({id:`specLimit:${l}:${h.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:l,label:t.name,unit:t.unit},comparison:{family:h.family,left:h.label,right:H(h.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:R,sampleSizeLeft:z.length,sampleSizeRight:$.length},summary:`${h.label} spec-fail rate for ${t.name} is ${(Math.abs(S)*100).toFixed(1)} pp ${S>0?"higher":"lower"} than the rest of the wafer`,highlight:{kind:"region",regionFamily:h.family,regionKeys:[h.key],dieKeys:[...h.dieKeys]}})}ie(f),c.push(...f.filter(h=>{const d=h.stats.adjustedPValue??h.stats.pValue??1,p=Math.abs(h.effect.absoluteDelta??0),b=Math.abs(h.effect.relativeDelta??0);return d<=o.significanceLevel&&(p>=o.minimumEffectSize||b>=o.minimumRelativeEffect)}).map(h=>({...h,severity:oe(h.stats.adjustedPValue??h.stats.pValue??1,h.effect.effectSize??0)})))}}return c}function j(n){return n.highlight.regionKeys??[]}function Ge(n){const a=n.variable.bin??n.variable.index??"";return`${n.comparison.family}\0${n.variable.kind}\0${a}\0${n.effect.direction}`}function I(n,a){const s=j(n)[0]??"",o=O(s);return o.family==="ring"?o.ring??0:o.family==="sector"?me(a).indexOf(o.sector??""):0}function Xe(n,a,s){if(a==="quadrant"){const r=new Map;for(const u of n){const f=O(j(u)[0]??"").quadrant;f&&r.set(f,u)}const t=[...r.keys()],l=new Set,i=[];for(const u of t){if(l.has(u))continue;const f=[],h=[u];for(l.add(u);h.length;){const d=h.pop();f.push(d);for(const p of t)!l.has(p)&&Le(d,p)&&(l.add(p),h.push(p))}i.push(f.map(d=>r.get(d)))}return i}const o=[...n].sort((r,t)=>I(r,s)-I(t,s)),e=[];let c=[];for(const r of o){if(c.length===0){c.push(r);continue}const t=I(c[c.length-1],s);I(r,s)-t===1?c.push(r):(e.push(c),c=[r])}if(c.length&&e.push(c),a==="sector"&&e.length>1){const t=me(s).length,l=I(e[0][0],s),i=I(e[e.length-1][e[e.length-1].length-1],s);if(l===0&&i===t-1){const u=e.pop();e[0]=[...u,...e[0]]}}return e}function Je(n,a){const s=n.map(r=>O(j(r)[0]??"").ring).filter(r=>r!==void 0).sort((r,t)=>r-t),o=s[0],e=s[s.length-1];let c="";return o===e&&o===1&&a>1?c=" (core)":o===e&&o===a&&a>1&&(c=" (edge)"),o===e?`Ring ${o}${c}`:`Rings ${o}\u2013${e}`}function Ze(n,a){const s=n.map(o=>O(j(o)[0]??"").sector??"");return s.length===1?`Sector ${s[0]}`:`Sectors ${s[0]}\u2013${s[s.length-1]}`}function et(n){const a=n.map(s=>O(j(s)[0]??"").quadrant??"");return a.length===1?a[0]:a.length===2?`Quadrants ${a[0]} & ${a[1]}`:`Quadrants ${a.slice(0,-1).join(", ")} & ${a[a.length-1]}`}function ce(n){return[...new Set(n)]}function tt(n,a){const s=n[0],o=s.comparison.family,e=s.variable.kind,c=ce(n.flatMap(m=>m.highlight.dieKeys??[])),r=ce(n.flatMap(j)),t=new Set(c),l=o==="ring"?Je(n,a.ringCount):o==="sector"?Ze(n,a.sectorCount):et(n),i=e==="softBin"?a.softEligibleDies:e==="test"?a.testDies:a.eligibleDies,u=i.filter(m=>t.has(x(m))),f=i.filter(m=>!t.has(x(m)));let h,d,p,b,y;if(e==="test"){const m=s.variable.index,w=k=>k.testValues?.[m],D=u.map(w).filter(k=>k!==void 0),v=f.map(w).filter(k=>k!==void 0),{pValue:S,effectSize:R,delta:M}=Qe(D,v),g=J(v),B=g!==0?M/Math.abs(g):void 0;h={direction:M===0?"different":M>0?"higher":"lower",absoluteDelta:M,relativeDelta:B,effectSize:R},d={method:"welch-z-approx",pValue:S,sampleSizeLeft:D.length,sampleSizeRight:v.length},p=oe(S,R),b=$e(l,s.variable.label,M,B,o,s.variable.unit),y=`test:${m}`}else if(e==="yield"){const m=new Set(a.passBins),w=u.filter(F=>F.hbin!==void 0),D=f.filter(F=>F.hbin!==void 0),v=w.length,S=D.length,R=w.filter(F=>m.has(F.hbin)).length,M=D.filter(F=>m.has(F.hbin)).length,g=v>0?R/v:0,B=S>0?M/S:0,k=g-B,V=q(R,v,M,S);h={direction:k===0?"different":k>0?"higher":"lower",absoluteDelta:k,relativeDelta:B===0?void 0:k/B,effectSize:k},d={method:"two-proportion-z",pValue:V,sampleSizeLeft:v,sampleSizeRight:S},p=se(V,k,h.relativeDelta),b=Se(l,k,o),y="yield"}else{const m=s.variable.bin,w=k=>e==="softBin"?k.sbin:k.hbin,D=u.filter(k=>w(k)===m).length,v=f.filter(k=>w(k)===m).length,S=D/u.length,R=v/f.length,M=S-R,g=q(D,u.length,v,f.length);h={direction:M===0?"different":M>0?"higher":"lower",absoluteDelta:M,relativeDelta:R===0?void 0:M/R,effectSize:M},d={method:"two-proportion-z",pValue:g,sampleSizeLeft:u.length,sampleSizeRight:f.length},p=se(g,M,h.relativeDelta);const B=e==="softBin"?a.sbinDefs:a.hbinDefs;b=we(l,ve(m,B,e==="softBin"?"SBin":"HBin"),M,o),y=`${e}:${m}`}const z=ce(r.map(m=>m.replace(`${o}:`,""))).join("-"),$=e==="hardBin"||e==="softBin"?{kind:"bin",bin:s.variable.bin,regionKeys:r,dieKeys:c}:{kind:"region",regionFamily:o,regionKeys:r,dieKeys:c};return{...s,id:`${y}:${o}:${z}`,severity:p,comparison:{family:o,left:l,right:H(o)},effect:h,stats:d,summary:b,highlight:$,relatedIds:n.map(m=>m.id)}}function nt(n,a){const s=new Set(["ring","quadrant","sector"]),o=[],e=new Map;for(const r of n){if(!s.has(r.comparison.family)){o.push(r);continue}const t=Ge(r),l=e.get(t)??[];l.push(r),e.set(t,l)}const c=[];for(const r of e.values()){if(r.length===1){c.push(r[0]);continue}const t=r[0].comparison.family;for(const l of Xe(r,t,a.sectorCount))c.push(l.length===1?l[0]:tt(l,a))}return[...o,...c]}function it(n){const a=new Map,s=new Map;for(const e of n){const c=x(e);e.hbin!==void 0&&e.hbin!==null&&(a.get(e.hbin)??a.set(e.hbin,new Set).get(e.hbin)).add(c),e.sbin!==void 0&&e.sbin!==null&&(s.get(e.sbin)??s.set(e.sbin,new Set).get(e.sbin)).add(c)}const o=new Set;for(const[e,c]of a)for(const[r,t]of s){if(c.size!==t.size)continue;let l=!0;for(const i of c)if(!t.has(i)){l=!1;break}l&&o.add(`${e}:${r}`)}return o}function st(n){return`${n.comparison.family}\0${n.comparison.left}`}function ot(n,a,s){const o=(t,l)=>{t.absorbedIds=[...t.absorbedIds??[],l.id]},e=new Map;for(const t of n){const l=st(t);(e.get(l)??e.set(l,[]).get(l)).push(t)}const c=it(a),r=new Set;for(const t of e.values()){for(const i of t)if(!(i.variable.kind!=="hardBin"||i.variable.bin===void 0))for(const u of t){if(u.variable.kind!=="softBin"||u.variable.bin===void 0||r.has(u.id)||!c.has(`${i.variable.bin}:${u.variable.bin}`)||u.effect.direction!==i.effect.direction)continue;o(i,u),r.add(u.id);const f=i.variable.label.replace(/^HBin\s+/,""),h=u.variable.label.replace(/^SBin\s+/,""),d=f===h?`HBin and SBin ${f} (same dies)`:`${i.variable.label} and ${u.variable.label} (same dies)`;i.summary=i.summary.replace(i.variable.label,d),i.variable.label=d;break}if(s.length!==1)continue;const l=t.find(i=>i.variable.kind==="yield");if(l){for(const i of t)if(!(i===l||r.has(i.id)||!((i.variable.kind==="hardBin"||i.variable.kind==="softBin")&&i.variable.bin===s[0]))){o(l,i),r.add(i.id);for(const f of i.absorbedIds??[])l.absorbedIds=[...l.absorbedIds??[],f]}}}}export function analyzeWaferMap(n,a={}){const{resolved:s,warnings:o}=Te(a),e=Ae(n),c={...s,passBins:e.passBins??T.passBins,ringCount:e.ringCount??T.ringCount},r=e.isLotStack,t=e.aggrMethod,l=!r||t==="mode"||t==="countBin"||t==="percent",i=e.dies.filter(g=>Oe(g)),u=i.filter(ge),f=je(c,i.length),h=e.dies.filter(g=>te(g)),d=h.filter(ge),p=Ve(d,e.wafer,f.ringCount),b=Fe(d,e.wafer,f.ringCount),y=Ne(d,e.reticleConfig),z=Pe(h,!1),$=Ce(d,e.wafer,f.sectorCount),m=[];l&&m.push(...W(i,p,f.passBins,f),...W(i,b,f.passBins,f),...W(i,y,f.passBins,f),...W(i,z,f.passBins,f),...W(i,$,f.passBins,f)),l&&m.push(...P(i,p,"hard",e.hbinDefs,"hardBin",f),...P(i,b,"hard",e.hbinDefs,"hardBin",f),...P(i,y,"hard",e.hbinDefs,"hardBin",f),...P(i,z,"hard",e.hbinDefs,"hardBin",f),...P(i,$,"hard",e.hbinDefs,"hardBin",f));{const g=i.filter(B=>B.sbin!==void 0);m.push(...P(g,p,"soft",e.sbinDefs,"softBin",f),...P(g,b,"soft",e.sbinDefs,"softBin",f),...P(g,y,"soft",e.sbinDefs,"softBin",f),...P(g,z,"soft",e.sbinDefs,"softBin",f),...P(g,$,"soft",e.sbinDefs,"softBin",f))}const w=[...o];let D;if(f.enableTestValueAnalysis){const g=Q(i,p,e.testDefs,f),B=Q(i,b,e.testDefs,f),k=Q(i,y,e.testDefs,f),V=Q(i,z,e.testDefs,f),F=Q(i,$,e.testDefs,f);m.push(...g.findings,...B.findings,...k.findings,...V.findings,...F.findings),g.warning&&w.push(g.warning),D=g.activeTestNumbers,m.push(...Ue(i,[p,b,y,z,$],e.testDefs,f)),m.push(...Y(i,p,e.testDefs,f),...Y(i,b,e.testDefs,f),...Y(i,y,e.testDefs,f),...Y(i,z,e.testDefs,f),...Y(i,$,e.testDefs,f))}{const g=qe(r,l,e.testDefs);(!r||l||g!==void 0)&&m.push(...Ee(u,e.wafer,{...f,isFailingDie:g}))}const v=nt(m,{eligibleDies:i,softEligibleDies:i.filter(g=>g.sbin!==void 0),testDies:i,passBins:f.passBins,ringCount:f.ringCount,sectorCount:f.sectorCount,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs});if(m.length=0,m.push(...v),l){const g=Ke(u,e.wafer,{passBins:f.passBins,ringCount:f.ringCount});if(g!==null&&g.pattern!=="random"&&g.pattern!=="none"){let Re=function(N){const E=j(N).map(K=>O(K).ring).filter(K=>K!==void 0);if(E.length===0)return!0;const ue=E.some(K=>K===Me),de=E.some(K=>K===1);return X==="edge-ring"||X==="edge-local"?ue:X==="center"?de:X==="donut"?!ue&&!de:!0};const k={center:"Center cluster",donut:"Donut","edge-ring":"Edge-ring","edge-local":"Edge-local",scratch:"Scratch","near-full":"Near-full"}[g.pattern]??g.pattern,V=g.confidence==="high"?"unusual":g.confidence==="medium"?"notable":"info",F=g.features,L=N=>`${(N*100).toFixed(0)}%`,C=g.pattern==="edge-ring"||g.pattern==="edge-local"?`${L(F.edgeRdd)} of edge dies failing`:g.pattern==="center"||g.pattern==="donut"?`centroid at ${L(F.centroidDistNorm)} of wafer radius from centre`:g.pattern==="scratch"?`linear score ${F.linearScore.toFixed(2)}, eccentricity ${F.eccentricity.toFixed(2)}`:`${L(F.globalRdd)} of dies failing`,_=i.filter(N=>{const E=N.hbin??N.sbin;return E!==void 0&&!new Set(f.passBins).has(E)}),U={"edge-ring":["ring","edge-arc"],"edge-local":["edge-arc","sector","quadrant"],center:["ring","cluster"],donut:["ring"],scratch:["cluster","sector","quadrant"],"near-full":["ring"]},G=new Set(U[g.pattern]??[]),X=g.pattern,Me=f.ringCount,ee=[];for(const N of m){if(!G.has(N.comparison.family)||N.comparison.family==="ring"&&!Re(N))continue;ee.push(N.id);const E=N.comparison.family==="ring"||N.comparison.family==="quadrant"||N.comparison.family==="sector";V!=="info"&&E&&(N.severity="info")}m.push({id:`spatial-pattern:${g.pattern}`,level:"wafer",severity:V,variable:{kind:"spatialPattern",label:k},comparison:{family:"spatial-pattern",left:k,right:"Wafer"},effect:{direction:"different",effectSize:F.globalRdd},stats:{method:"geometry",sampleSizeLeft:_.length,sampleSizeRight:i.length},summary:`Spatial pattern: ${k.toLowerCase()} (${g.confidence} confidence) \u2014 ${C}${g.note?` [${g.note}]`:""}`,highlight:{kind:"dies",dieKeys:_.map(N=>x(N))},relatedIds:ee.length>0?ee:void 0})}}m.sort((g,B)=>{const k=g.stats.adjustedPValue??g.stats.pValue??1,V=B.stats.adjustedPValue??B.stats.pValue??1;return k!==V?k-V:Math.abs(B.effect.absoluteDelta??0)-Math.abs(g.effect.absoluteDelta??0)});const S=He(e.dies,i.length,e.yield.yieldPercent);r&&(S.isLotStack=!0,t&&(S.aggregationMethod=t));const R=Ie(e.dies,e.testDefs);R&&(S.testSpecYield=R);const M=computeFunctionalYield(e.dies,e.testDefs);if(M&&(S.functionalYield=M),D===void 0&&f.computePerTestStats){const g=ze(i,f.testNumbers,e.testDefs);g.warning&&w.push(g.warning),D=g.testNumbers}if(D?.length){const g=_e(e.dies,D,e.testDefs,f.minimumSampleSize);g&&(S.perTestStats=g)}return w.length>0&&(S.warnings=w),ot(m,i,f.passBins),{level:"wafer",hasNotableFindings:m.some(g=>g.severity==="notable"||g.severity==="unusual"),findings:m,wafer:e.wafer.metadata??void 0,stats:S}}
1
+ import{normalizeInput as Ce}from"./normalizeInput.js";import{computeCapability as Ne,computeTestFlagYield as Pe,computeRegionYield as Ve}from"./summaryFigures.js";import{isYieldEligibleDie as te,getDieKey as T,isPositionedDie as ge}from"../core/dies.js";import{getTestPassStatus as me,isParametricTest as O}from"../renderer/buildWaferMap.js";import{buildQuadrantRegions as Le,buildReticlePositionRegions as Ee,buildRingRegions as Ke,buildSectorRegions as Te,buildTestSiteRegions as xe,sectorCompassNames as he,areQuadrantsAdjacent as je,parseRegionKey as q}from"./regions.js";import{buildClusterFindings as Ae}from"./clusterDetection.js";import{classifyPattern as Oe}from"./patternClassification.js";import{normalCdf as pe}from"./math.js";import{quantile as ne}from"./math.js";import{mean as J,clamp01 as be}from"../core/utils.js";const qe={significanceLevel:{min:1e-6,max:1},minimumEffectSize:{min:0,max:1},minimumRelativeEffect:{min:0,max:Number.MAX_SAFE_INTEGER}},ye=[4,8,16,32];function He(s){const a=Object.fromEntries(Object.entries(s).filter(([,t])=>t!==void 0)),i={...x,...a},o=[],e=[];for(const t of["significanceLevel","minimumEffectSize","minimumRelativeEffect"]){const{min:l,max:n}=qe[t],u=i[t];if(typeof u!="number"||!Number.isFinite(u)){e.push(`${t}=${String(u)} is not a finite number (using ${x[t]})`),i[t]=x[t];continue}const f=Math.min(n,Math.max(l,u));f!==u&&(e.push(`${t}=${u} is outside ${l}\u2013${n} (using ${f})`),i[t]=f)}if(ye.includes(i.sectorCount)||(e.push(`sectorCount=${i.sectorCount} is not one of ${ye.join(", ")} (using ${x.sectorCount})`),i.sectorCount=x.sectorCount),e.length){const t=`analyzeWaferMap: ${e.join("; ")}. The analysis ran with the corrected values.`;console.warn(`[wafermap] ${t}`),o.push({code:"analysis-option-corrected",message:t,severity:"warning"})}const c=["passBins","ringCount"].filter(t=>a[t]!==void 0);if(c.length){const t=`analyzeWaferMap: ${c.join(" and ")} ${c.length>1?"are":"is"} no longer an analysis option and was ignored. Set ${c.length>1?"them":"it"} on buildWaferMap; the analysis used the value the map was built with.`;console.warn(`[wafermap] ${t}`),o.push({code:"analysis-option-corrected",message:t,severity:"warning"})}const r=["enableYieldAnalysis","enableHardBinAnalysis","enableSoftBinAnalysis","enableReticlePositionAnalysis","enableTestSiteAnalysis","enableClusterAnalysis","enableAngularAnalysis","enablePatternClassification"].filter(t=>a[t]!==void 0);if(r.length){const t=`analyzeWaferMap: ${r.join(", ")} ${r.length>1?"are":"is"} no longer an option and was ignored: every analysis now runs. Filter the findings (filterFindings) to show fewer.`;console.warn(`[wafermap] ${t}`),o.push({code:"analysis-option-corrected",message:t,severity:"warning"})}return{resolved:i,warnings:o}}const x={ringCount:4,passBins:[1],significanceLevel:.05,minimumEffectSize:.2,minimumRelativeEffect:1,minimumSampleSize:5,enableTestValueAnalysis:!1,computePerTestStats:!1,sectorCount:8,minimumClusterSize:5};function Ie(s,a){const i={...s};return i.minimumClusterSize=Math.max(5,Math.round(a*.003)),i}function Ye(s){return te(s)?s.hbin!==void 0||s.sbin!==void 0||s.testValues!==void 0&&Object.keys(s.testValues).length>0:!1}function _e(s,a,i){if(!s||a)return;const o=(i??[]).filter(e=>O(e)&&(e.limitLow!==void 0||e.limitHigh!==void 0));if(o.length!==0)return e=>{for(const c of o){const r=c.testNumber,t=e.testValues?.[r];if(t!==void 0&&(c.limitLow!==void 0&&t<c.limitLow||c.limitHigh!==void 0&&t>c.limitHigh))return!0}return!1}}function We(s,a,i){const o=new Set,e=new Set,c=new Set,r=new Map,t=new Map;for(const l of s){if(l.testValues)for(const n of Object.keys(l.testValues))o.add(Number(n));if(l.testPass)for(const n of Object.keys(l.testPass))o.add(Number(n));l.hbin!==void 0&&e.add(l.hbin),l.sbin!==void 0&&c.add(l.sbin),te(l)&&(l.hbin!==void 0&&r.set(l.hbin,(r.get(l.hbin)??0)+1),l.sbin!==void 0&&t.set(l.sbin,(t.get(l.sbin)??0)+1))}return{totalDies:s.length,analyzedDies:a,excludedDies:s.length-a,yieldPercent:i,testsConsidered:[...o].sort((l,n)=>l-n),hardBinsConsidered:[...e].sort((l,n)=>l-n),softBinsConsidered:[...c].sort((l,n)=>l-n),...r.size?{hardBinCounts:Object.fromEntries(r)}:{},...t.size?{softBinCounts:Object.fromEntries(t)}:{}}}function Qe(s,a){if(!a?.length)return;const i=a.filter(e=>O(e)&&(e.limitLow!==void 0||e.limitHigh!==void 0));if(!i.length)return;const o=[];for(const e of i){const c=e.testNumber;let r=0,t=0,l=0,n=0;for(const u of s){if(u.partial||u.edgeExcluded)continue;const f=u.testValues?.[c];f!==void 0&&(n++,e.limitLow!==void 0&&f<e.limitLow?t++:e.limitHigh!==void 0&&f>e.limitHigh?l++:r++)}o.push({testNumber:c,label:e.name,passDies:r,failLowDies:t,failHighDies:l,totalDies:n,yieldPercent:n>0?r/n*100:null})}return o.length?o:void 0}export function computeFunctionalYield(s,a){if(!a?.length)return;const i=a.filter(e=>!O(e));if(!i.length)return;const o=[];for(const e of i){const c=e.testNumber;let r=0,t=0;for(const n of s){if(n.partial||n.edgeExcluded)continue;const u=me(n,c,e);u!==void 0&&(u?r++:t++)}const l=r+t;o.push({testNumber:c,label:e.name,passDies:r,failDies:t,totalDies:l,passRatePercent:l>0?r/l*100:null})}return o.length?o:void 0}function Ue(s,a,i,o){const e=[];for(const c of a){const r=[];for(const f of s){if(f.partial||f.edgeExcluded)continue;const h=f.testValues?.[c];h!==void 0&&Number.isFinite(h)&&r.push(h)}if(r.length<o)continue;const t=J(r),l=Math.sqrt(fe(r,t)),n=Float64Array.from(r);n.sort();const u=i?.find(f=>f.testNumber===c)?.name??String(c);e.push({testNumber:c,label:u,count:r.length,min:n[0],max:n[n.length-1],mean:t,stddev:l,median:ne(n,.5),q1:ne(n,.25),q3:ne(n,.75)})}return e.length?e:void 0}function H(s,a,i,o){const e=(s+i)/(a+o),c=e*(1-e)*(1/a+1/o);if(!Number.isFinite(c)||c<=0)return 1;const r=(s/a-i/o)/Math.sqrt(c);return be(2*(1-pe(Math.abs(r))))}function ie(s){const a=new Map;for(const i of s){const o=`${i.variable.kind}:${i.comparison.family}`,e=a.get(o)??[];e.push(i),a.set(o,e)}for(const i of a.values()){const o=[...i].filter(r=>r.stats.pValue!==void 0).sort((r,t)=>(r.stats.pValue??1)-(t.stats.pValue??1)),e=o.length;let c=1;for(let r=e-1;r>=0;r--){const t=o[r],l=t.stats.pValue??1,n=Math.min(1,Math.min(c,l*e/(r+1)));t.stats.adjustedPValue=n,c=n}}return s}function se(s,a,i){const o=Math.abs(a),e=i!==void 0?Math.abs(i):0;return s<=.01&&(o>=.3||e>=2.5)?"unusual":s<=.05&&(o>=.2||e>=1.5)?"notable":"info"}function oe(s,a){return s<=.01&&Math.abs(a)>=.5?"unusual":s<=.05&&Math.abs(a)>=.15?"notable":"info"}function Z(s){return s==="reticle-position"?"other reticle positions":s==="test-site"?"other test sites":"the rest of the map"}function I(s){return s==="reticle-position"?"Other reticle positions":s==="test-site"?"Other test sites":"Rest of map"}function Se(s,a,i){const o=(Math.abs(a)*100).toFixed(1);return`${s} yield is ${o} percentage points ${a>0?"higher":"lower"} than ${Z(i)}`}function ae(s,a){return a==="quadrant"&&!s.startsWith("Quadrant")?`quadrant ${s}`:s}function we(s,a,i,o){const e=ae(s,o),c=(Math.abs(i)*100).toFixed(1);return`${e} has ${a} occurrence ${c} percentage points ${i>0?"higher":"lower"} than ${Z(o)}`}function Ge(s,a,i,o){const e=ae(s,o),c=(Math.abs(i)*100).toFixed(1);return`${e} has ${a} pass rate ${c} percentage points ${i>0?"higher":"lower"} than ${Z(o)}`}function $e(s,a,i,o,e,c){const r=ae(s,e),t=Z(e),l=i>0?"higher":"lower";if(o!==void 0&&Number.isFinite(o)){const u=(Math.abs(o)*100).toFixed(1);return`${r} mean ${a} is ${u}% ${l} than ${t}`}const n=c?` ${c}`:"";return`${r} mean ${a} is ${Math.abs(i).toPrecision(3)}${n} ${l} than ${t}`}function ve(s,a,i){const o=a?.find(e=>e.bin===s);return o?.name?`${i} ${s} (${o.name})`:`${i} ${s}`}function Xe(s,a){const i=a?.find(o=>o.testNumber===s);return{label:i?.name??`Test ${s}`,unit:i?.unit}}function re(s,a){const i=new Map(s.map(e=>[T(e),e])),o=new Map;for(const e of a){const c=[];for(const r of e.dieKeys){const t=i.get(r);t&&c.push(t)}o.set(e.key,c)}return o}function le(s,a){return ie(s),s.filter(i=>{const o=i.stats.adjustedPValue??i.stats.pValue??1,e=Math.abs(i.effect.absoluteDelta??0),c=Math.abs(i.effect.relativeDelta??0);return o<=a.significanceLevel&&(e>=a.minimumEffectSize||c>=a.minimumRelativeEffect)}).map(i=>({...i,severity:se(i.stats.adjustedPValue??i.stats.pValue??1,i.effect.absoluteDelta??0,i.effect.relativeDelta)}))}function W(s,a,i,o){const e=new Set(i),c=re(s,a),r=new Map,t=new Map;for(const[n,u]of c){let f=0,h=0;for(const g of u)g.hbin!==void 0&&(h++,e.has(g.hbin)&&f++);r.set(n,f),t.set(n,h)}const l=[];for(const n of a){const u=t.get(n.key)??0,f=r.get(n.key)??0;let h=0,g=0;for(const[w,m]of t)w!==n.key&&(h+=m,g+=r.get(w)??0);if(u<o.minimumSampleSize||h<o.minimumSampleSize)continue;const p=f/u,b=g/h,y=p-b,z=H(f,u,g,h);l.push({id:`yield:${n.key}`,level:"wafer",severity:"info",variable:{kind:"yield",label:"Yield"},comparison:{family:n.family,left:n.label,right:I(n.family)},effect:{direction:y===0?"different":y>0?"higher":"lower",absoluteDelta:y,relativeDelta:b===0?void 0:y/b,effectSize:y},stats:{method:"two-proportion-z",pValue:z,sampleSizeLeft:u,sampleSizeRight:h},summary:Se(n.label,y,n.family),highlight:{kind:"region",regionFamily:n.family,regionKeys:[n.key],dieKeys:[...n.dieKeys]}})}return le(l,o)}function L(s,a,i,o,e,c){const r=g=>i==="soft"?g.sbin:g.hbin,t=[...new Set(s.map(r).filter(g=>g!==void 0))].sort((g,p)=>g-p),l=re(s,a),n=new Map,u=new Map;for(const[g,p]of l){const b=new Map;for(const y of p){const z=r(y);z!==void 0&&b.set(z,(b.get(z)??0)+1)}n.set(g,b),u.set(g,p.length)}const f=[],h=e==="hardBin"?"HBin":"SBin";for(const g of a){const p=u.get(g.key)??0,b=n.get(g.key);let y=0;const z=new Map;for(const[w,m]of n)if(w!==g.key){y+=u.get(w)??0;for(const[S,v]of m)z.set(S,(z.get(S)??0)+v)}if(!(p<c.minimumSampleSize||y<c.minimumSampleSize))for(const w of t){const m=b.get(w)??0,S=z.get(w)??0,v=m/p,D=S/y,$=v-D,k=H(m,p,S,y),M=ve(w,o,h);f.push({id:`${e}:${w}:${g.key}`,level:"wafer",severity:"info",variable:{kind:e,bin:w,label:M},comparison:{family:g.family,left:g.label,right:I(g.family)},effect:{direction:$===0?"different":$>0?"higher":"lower",absoluteDelta:$,relativeDelta:D===0?void 0:$/D,effectSize:$},stats:{method:"two-proportion-z",pValue:k,sampleSizeLeft:p,sampleSizeRight:y},summary:we(g.label,M,$,g.family),highlight:{kind:"bin",bin:w,regionKeys:[g.key],dieKeys:[...g.dieKeys]}})}}return le(f,c)}function Q(s,a,i,o){const e=(i??[]).filter(u=>!O(u));if(!e.length)return[];const c=e.map(u=>u.testNumber),r=re(s,a),t=new Map,l=new Map;for(const[u,f]of r){const h=new Uint32Array(e.length),g=new Uint32Array(e.length);for(const p of f)for(let b=0;b<e.length;b++){const y=me(p,c[b],e[b]);y!==void 0&&(g[b]++,y&&h[b]++)}t.set(u,h),l.set(u,g)}const n=[];for(const u of a){const f=t.get(u.key),h=l.get(u.key);for(let g=0;g<e.length;g++){let p=0,b=0;for(const[$,k]of l)$!==u.key&&(b+=k[g],p+=t.get($)[g]);const y=h[g],z=f[g];if(y<o.minimumSampleSize||b<o.minimumSampleSize)continue;const w=z/y,m=p/b,S=w-m,v=H(z,y,p,b),D=c[g];n.push({id:`functional:${D}:${u.key}`,level:"wafer",severity:"info",variable:{kind:"functionalTest",index:D,label:`${e[g].name} pass rate`},comparison:{family:u.family,left:u.label,right:I(u.family)},effect:{direction:S===0?"different":S>0?"higher":"lower",absoluteDelta:S,relativeDelta:m===0?void 0:S/m,effectSize:S},stats:{method:"two-proportion-z",pValue:v,sampleSizeLeft:y,sampleSizeRight:b},summary:Ge(u.label,e[g].name,S,u.family),highlight:{kind:"region",regionFamily:u.family,regionKeys:[u.key],dieKeys:[...u.dieKeys]}})}}return le(n,o)}function fe(s,a){return s.length<2?0:s.reduce((i,o)=>i+(o-a)**2,0)/(s.length-1)}function De(s,a,i,o,e,c){const r=Math.sqrt(i/s+c/o),t=a-e;if(!Number.isFinite(r)||r===0)return{pValue:1,effectSize:0,delta:t};const l=t/r,n=Math.sqrt(Math.max(0,(i+c)/2)),u=n===0?t:t/n;return{pValue:be(2*(1-pe(Math.abs(l)))),effectSize:u,delta:t}}function Je(s,a){const i=J(s),o=J(a);return De(s.length,i,fe(s,i),a.length,o,fe(a,o))}const ke=250;function ze(s,a,i){const o=r=>r.filter(t=>O(i?.find(l=>l.testNumber===t)));if(a)return{testNumbers:o(a.slice().sort((r,t)=>r-t))};const e=new Set;let c=!1;e:for(const r of s)for(const t of Object.keys(r.testValues??{})){const l=Number(t);if(!e.has(l)&&(e.add(l),e.size>ke)){c=!0;break e}}if(c){const r=`Test-value analysis was skipped: more than ${ke} tests were found in the die data, so no test findings were computed. Pass testNumbers: [...] to analyse specific tests.`;return console.warn(`[wafermap] analyzeWaferMap: ${r}`),{testNumbers:[],warning:{code:"test-count-capped",message:r,severity:"warning"}}}return{testNumbers:o([...e].sort((r,t)=>r-t))}}function U(s,a,i,o){const e=ze(s,o.testNumbers,i);if(e.warning)return{findings:[],warning:e.warning};const c=e.testNumbers,r=new Map;for(let p=0;p<a.length;p++)for(const b of a[p].dieKeys)r.set(b,p);const t=a.length,l=[],n=[];for(const p of s){const b=r.get(T(p));b!==void 0&&(l.push(p),n.push(b))}const u=[],f=new Float64Array(t),h=new Float64Array(t),g=new Float64Array(t);for(const p of c){f.fill(0),h.fill(0),g.fill(0);let b=0,y=0,z=0,w;for(let m=0;m<l.length;m++){const v=l[m].testValues?.[p];if(v===void 0)continue;w===void 0&&(w=v);const D=v-w,$=n[m];f[$]+=1,h[$]+=D,g[$]+=D*D,b+=1,y+=D,z+=D*D}if(w!==void 0)for(let m=0;m<t;m++){const S=f[m],v=b-S;if(S<o.minimumSampleSize||v<o.minimumSampleSize)continue;const D=h[m]/S,k=(y-h[m])/v,M=D+w,R=k+w,P=S>1?Math.max(0,g[m]-S*D*D)/(S-1):0,d=v>1?Math.max(0,z-g[m]-v*k*k)/(v-1):0,{pValue:N,effectSize:B,delta:V}=De(S,M,P,v,R,d),F=a[m],{label:A,unit:G}=Xe(p,i),_=R!==0?V/Math.abs(R):void 0;u.push({id:`test:${p}:${F.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:p,label:A,unit:G},comparison:{family:F.family,left:F.label,right:I(F.family)},effect:{direction:V===0?"different":V>0?"higher":"lower",absoluteDelta:V,relativeDelta:_,effectSize:B},stats:{method:"welch-z-approx",pValue:N,sampleSizeLeft:S,sampleSizeRight:v},summary:$e(F.label,A,V,_,F.family,G),highlight:{kind:"region",regionFamily:F.family,regionKeys:[F.key],dieKeys:[...F.dieKeys]}})}}return ie(u),{activeTestNumbers:c,findings:u.filter(p=>{const b=p.stats.adjustedPValue??p.stats.pValue??1,y=Math.abs(p.effect.effectSize??0);return b<=o.significanceLevel&&y>=o.minimumEffectSize}).map(p=>({...p,severity:oe(p.stats.adjustedPValue??p.stats.pValue??1,p.effect.effectSize??0)}))}}function Ze(s,a,i,o){if(!i?.length)return[];const e=i.filter(t=>O(t)&&(t.limitLow!==void 0||t.limitHigh!==void 0));if(!e.length)return[];const c=[],r=new Map(s.map(t=>[T(t),t]));for(const t of e){const l=t.testNumber;for(const n of a){const u=new Map;for(const h of n){const g=[];for(const p of h.dieKeys){const b=r.get(p);b&&g.push(b)}u.set(h.key,g)}const f=[];for(const h of n){const g=u.get(h.key),p=[];for(const[M,R]of u)if(M!==h.key)for(const P of R)p.push(P);const b=M=>M.testValues?.[l]!==void 0,y=M=>{const R=M.testValues?.[l];return R===void 0?!1:t.limitLow!==void 0&&R<t.limitLow||t.limitHigh!==void 0&&R>t.limitHigh},z=g.filter(b),w=p.filter(b);if(z.length<o.minimumSampleSize||w.length<o.minimumSampleSize)continue;const m=z.filter(y).length,S=w.filter(y).length,v=m/z.length,D=S/w.length,$=v-D,k=H(m,z.length,S,w.length);f.push({id:`specLimit:${l}:${h.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:l,label:t.name,unit:t.unit},comparison:{family:h.family,left:h.label,right:I(h.family)},effect:{direction:$===0?"different":$>0?"higher":"lower",absoluteDelta:$,relativeDelta:D===0?void 0:$/D,effectSize:$},stats:{method:"two-proportion-z",pValue:k,sampleSizeLeft:z.length,sampleSizeRight:w.length},summary:`${h.label} spec-fail rate for ${t.name} is ${(Math.abs($)*100).toFixed(1)} pp ${$>0?"higher":"lower"} than the rest of the wafer`,highlight:{kind:"region",regionFamily:h.family,regionKeys:[h.key],dieKeys:[...h.dieKeys]}})}ie(f),c.push(...f.filter(h=>{const g=h.stats.adjustedPValue??h.stats.pValue??1,p=Math.abs(h.effect.absoluteDelta??0),b=Math.abs(h.effect.relativeDelta??0);return g<=o.significanceLevel&&(p>=o.minimumEffectSize||b>=o.minimumRelativeEffect)}).map(h=>({...h,severity:oe(h.stats.adjustedPValue??h.stats.pValue??1,h.effect.effectSize??0)})))}}return c}function j(s){return s.highlight.regionKeys??[]}function et(s){const a=s.variable.bin??s.variable.index??"";return`${s.comparison.family}\0${s.variable.kind}\0${a}\0${s.effect.direction}`}function Y(s,a){const i=j(s)[0]??"",o=q(i);return o.family==="ring"?o.ring??0:o.family==="sector"?he(a).indexOf(o.sector??""):0}function tt(s,a,i){if(a==="quadrant"){const r=new Map;for(const u of s){const f=q(j(u)[0]??"").quadrant;f&&r.set(f,u)}const t=[...r.keys()],l=new Set,n=[];for(const u of t){if(l.has(u))continue;const f=[],h=[u];for(l.add(u);h.length;){const g=h.pop();f.push(g);for(const p of t)!l.has(p)&&je(g,p)&&(l.add(p),h.push(p))}n.push(f.map(g=>r.get(g)))}return n}const o=[...s].sort((r,t)=>Y(r,i)-Y(t,i)),e=[];let c=[];for(const r of o){if(c.length===0){c.push(r);continue}const t=Y(c[c.length-1],i);Y(r,i)-t===1?c.push(r):(e.push(c),c=[r])}if(c.length&&e.push(c),a==="sector"&&e.length>1){const t=he(i).length,l=Y(e[0][0],i),n=Y(e[e.length-1][e[e.length-1].length-1],i);if(l===0&&n===t-1){const u=e.pop();e[0]=[...u,...e[0]]}}return e}function nt(s,a){const i=s.map(r=>q(j(r)[0]??"").ring).filter(r=>r!==void 0).sort((r,t)=>r-t),o=i[0],e=i[i.length-1];let c="";return o===e&&o===1&&a>1?c=" (core)":o===e&&o===a&&a>1&&(c=" (edge)"),o===e?`Ring ${o}${c}`:`Rings ${o}\u2013${e}`}function it(s,a){const i=s.map(o=>q(j(o)[0]??"").sector??"");return i.length===1?`Sector ${i[0]}`:`Sectors ${i[0]}\u2013${i[i.length-1]}`}function st(s){const a=s.map(i=>q(j(i)[0]??"").quadrant??"");return a.length===1?a[0]:a.length===2?`Quadrants ${a[0]} & ${a[1]}`:`Quadrants ${a.slice(0,-1).join(", ")} & ${a[a.length-1]}`}function ce(s){return[...new Set(s)]}function ot(s,a){const i=s[0],o=i.comparison.family,e=i.variable.kind,c=ce(s.flatMap(m=>m.highlight.dieKeys??[])),r=ce(s.flatMap(j)),t=new Set(c),l=o==="ring"?nt(s,a.ringCount):o==="sector"?it(s,a.sectorCount):st(s),n=e==="softBin"?a.softEligibleDies:e==="test"?a.testDies:a.eligibleDies,u=n.filter(m=>t.has(T(m))),f=n.filter(m=>!t.has(T(m)));let h,g,p,b,y;if(e==="test"){const m=i.variable.index,S=d=>d.testValues?.[m],v=u.map(S).filter(d=>d!==void 0),D=f.map(S).filter(d=>d!==void 0),{pValue:$,effectSize:k,delta:M}=Je(v,D),R=J(D),P=R!==0?M/Math.abs(R):void 0;h={direction:M===0?"different":M>0?"higher":"lower",absoluteDelta:M,relativeDelta:P,effectSize:k},g={method:"welch-z-approx",pValue:$,sampleSizeLeft:v.length,sampleSizeRight:D.length},p=oe($,k),b=$e(l,i.variable.label,M,P,o,i.variable.unit),y=`test:${m}`}else if(e==="yield"){const m=new Set(a.passBins),S=u.filter(B=>B.hbin!==void 0),v=f.filter(B=>B.hbin!==void 0),D=S.length,$=v.length,k=S.filter(B=>m.has(B.hbin)).length,M=v.filter(B=>m.has(B.hbin)).length,R=D>0?k/D:0,P=$>0?M/$:0,d=R-P,N=H(k,D,M,$);h={direction:d===0?"different":d>0?"higher":"lower",absoluteDelta:d,relativeDelta:P===0?void 0:d/P,effectSize:d},g={method:"two-proportion-z",pValue:N,sampleSizeLeft:D,sampleSizeRight:$},p=se(N,d,h.relativeDelta),b=Se(l,d,o),y="yield"}else{const m=i.variable.bin,S=d=>e==="softBin"?d.sbin:d.hbin,v=u.filter(d=>S(d)===m).length,D=f.filter(d=>S(d)===m).length,$=v/u.length,k=D/f.length,M=$-k,R=H(v,u.length,D,f.length);h={direction:M===0?"different":M>0?"higher":"lower",absoluteDelta:M,relativeDelta:k===0?void 0:M/k,effectSize:M},g={method:"two-proportion-z",pValue:R,sampleSizeLeft:u.length,sampleSizeRight:f.length},p=se(R,M,h.relativeDelta);const P=e==="softBin"?a.sbinDefs:a.hbinDefs;b=we(l,ve(m,P,e==="softBin"?"SBin":"HBin"),M,o),y=`${e}:${m}`}const z=ce(r.map(m=>m.replace(`${o}:`,""))).join("-"),w=e==="hardBin"||e==="softBin"?{kind:"bin",bin:i.variable.bin,regionKeys:r,dieKeys:c}:{kind:"region",regionFamily:o,regionKeys:r,dieKeys:c};return{...i,id:`${y}:${o}:${z}`,severity:p,comparison:{family:o,left:l,right:I(o)},effect:h,stats:g,summary:b,highlight:w,relatedIds:s.map(m=>m.id)}}function at(s,a){const i=new Set(["ring","quadrant","sector"]),o=[],e=new Map;for(const r of s){if(!i.has(r.comparison.family)){o.push(r);continue}const t=et(r),l=e.get(t)??[];l.push(r),e.set(t,l)}const c=[];for(const r of e.values()){if(r.length===1){c.push(r[0]);continue}const t=r[0].comparison.family;for(const l of tt(r,t,a.sectorCount))c.push(l.length===1?l[0]:ot(l,a))}return[...o,...c]}function rt(s){const a=new Map,i=new Map;for(const e of s){const c=T(e);e.hbin!==void 0&&e.hbin!==null&&(a.get(e.hbin)??a.set(e.hbin,new Set).get(e.hbin)).add(c),e.sbin!==void 0&&e.sbin!==null&&(i.get(e.sbin)??i.set(e.sbin,new Set).get(e.sbin)).add(c)}const o=new Set;for(const[e,c]of a)for(const[r,t]of i){if(c.size!==t.size)continue;let l=!0;for(const n of c)if(!t.has(n)){l=!1;break}l&&o.add(`${e}:${r}`)}return o}function lt(s){return`${s.comparison.family}\0${s.comparison.left}`}function ft(s,a,i){const o=(t,l)=>{t.absorbedIds=[...t.absorbedIds??[],l.id]},e=new Map;for(const t of s){const l=lt(t);(e.get(l)??e.set(l,[]).get(l)).push(t)}const c=rt(a),r=new Set;for(const t of e.values()){for(const n of t)if(!(n.variable.kind!=="hardBin"||n.variable.bin===void 0))for(const u of t){if(u.variable.kind!=="softBin"||u.variable.bin===void 0||r.has(u.id)||!c.has(`${n.variable.bin}:${u.variable.bin}`)||u.effect.direction!==n.effect.direction)continue;o(n,u),r.add(u.id);const f=n.variable.label.replace(/^HBin\s+/,""),h=u.variable.label.replace(/^SBin\s+/,""),g=f===h?`HBin and SBin ${f} (same dies)`:`${n.variable.label} and ${u.variable.label} (same dies)`;n.summary=n.summary.replace(n.variable.label,g),n.variable.label=g;break}if(i.length!==1)continue;const l=t.find(n=>n.variable.kind==="yield");if(l){for(const n of t)if(!(n===l||r.has(n.id)||!((n.variable.kind==="hardBin"||n.variable.kind==="softBin")&&n.variable.bin===i[0]))){o(l,n),r.add(n.id);for(const f of n.absorbedIds??[])l.absorbedIds=[...l.absorbedIds??[],f]}}}}export function analyzeWaferMap(s,a={}){const{resolved:i,warnings:o}=He(a),e=Ce(s),c={...i,passBins:e.passBins??x.passBins,ringCount:e.ringCount??x.ringCount},r=e.isLotStack,t=e.aggrMethod,l=!r||t==="mode"||t==="countBin"||t==="percent",n=e.dies.filter(d=>Ye(d)),u=n.filter(ge),f=Ie(c,n.length),h=e.dies.filter(d=>te(d)),g=h.filter(ge),p=Ke(g,e.wafer,f.ringCount),b=Le(g,e.wafer,f.ringCount),y=Ee(g,e.reticleConfig),z=xe(h,!1),w=Te(g,e.wafer,f.sectorCount),m=[];l&&m.push(...W(n,p,f.passBins,f),...W(n,b,f.passBins,f),...W(n,y,f.passBins,f),...W(n,z,f.passBins,f),...W(n,w,f.passBins,f)),l&&m.push(...L(n,p,"hard",e.hbinDefs,"hardBin",f),...L(n,b,"hard",e.hbinDefs,"hardBin",f),...L(n,y,"hard",e.hbinDefs,"hardBin",f),...L(n,z,"hard",e.hbinDefs,"hardBin",f),...L(n,w,"hard",e.hbinDefs,"hardBin",f));{const d=n.filter(N=>N.sbin!==void 0);m.push(...L(d,p,"soft",e.sbinDefs,"softBin",f),...L(d,b,"soft",e.sbinDefs,"softBin",f),...L(d,y,"soft",e.sbinDefs,"softBin",f),...L(d,z,"soft",e.sbinDefs,"softBin",f),...L(d,w,"soft",e.sbinDefs,"softBin",f))}const S=[...o];let v;if(f.enableTestValueAnalysis){const d=U(n,p,e.testDefs,f),N=U(n,b,e.testDefs,f),B=U(n,y,e.testDefs,f),V=U(n,z,e.testDefs,f),F=U(n,w,e.testDefs,f);m.push(...d.findings,...N.findings,...B.findings,...V.findings,...F.findings),d.warning&&S.push(d.warning),v=d.activeTestNumbers,m.push(...Ze(n,[p,b,y,z,w],e.testDefs,f)),m.push(...Q(n,p,e.testDefs,f),...Q(n,b,e.testDefs,f),...Q(n,y,e.testDefs,f),...Q(n,z,e.testDefs,f),...Q(n,w,e.testDefs,f))}{const d=_e(r,l,e.testDefs);(!r||l||d!==void 0)&&m.push(...Ae(u,e.wafer,{...f,isFailingDie:d}))}const D=at(m,{eligibleDies:n,softEligibleDies:n.filter(d=>d.sbin!==void 0),testDies:n,passBins:f.passBins,ringCount:f.ringCount,sectorCount:f.sectorCount,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs});m.length=0,m.push(...D);let $;if(l){const d=Oe(u,e.wafer,{passBins:f.passBins,ringCount:f.ringCount});if($=d??void 0,d!==null&&d.pattern!=="random"&&d.pattern!=="none"){let Fe=function(C){const E=j(C).map(K=>q(K).ring).filter(K=>K!==void 0);if(E.length===0)return!0;const ue=E.some(K=>K===Be),de=E.some(K=>K===1);return X==="edge-ring"||X==="edge-local"?ue:X==="center"?de:X==="donut"?!ue&&!de:!0};const B={center:"Center cluster",donut:"Donut","edge-ring":"Edge-ring","edge-local":"Edge-local",scratch:"Scratch","near-full":"Near-full"}[d.pattern]??d.pattern,V=d.confidence==="high"?"unusual":d.confidence==="medium"?"notable":"info",F=d.features,A=C=>`${(C*100).toFixed(0)}%`,G=d.pattern==="edge-ring"||d.pattern==="edge-local"?`${A(F.edgeRdd)} of edge dies failing`:d.pattern==="center"||d.pattern==="donut"?`centroid at ${A(F.centroidDistNorm)} of wafer radius from centre`:d.pattern==="scratch"?`linear score ${F.linearScore.toFixed(2)}, eccentricity ${F.eccentricity.toFixed(2)}`:`${A(F.globalRdd)} of dies failing`,_=n.filter(C=>{const E=C.hbin??C.sbin;return E!==void 0&&!new Set(f.passBins).has(E)}),Me={"edge-ring":["ring","edge-arc"],"edge-local":["edge-arc","sector","quadrant"],center:["ring","cluster"],donut:["ring"],scratch:["cluster","sector","quadrant"],"near-full":["ring"]},Re=new Set(Me[d.pattern]??[]),X=d.pattern,Be=f.ringCount,ee=[];for(const C of m){if(!Re.has(C.comparison.family)||C.comparison.family==="ring"&&!Fe(C))continue;ee.push(C.id);const E=C.comparison.family==="ring"||C.comparison.family==="quadrant"||C.comparison.family==="sector";V!=="info"&&E&&(C.severity="info")}m.push({id:`spatial-pattern:${d.pattern}`,level:"wafer",severity:V,variable:{kind:"spatialPattern",label:B},comparison:{family:"spatial-pattern",left:B,right:"Wafer"},effect:{direction:"different",effectSize:F.globalRdd},stats:{method:"geometry",sampleSizeLeft:_.length,sampleSizeRight:n.length},summary:`Spatial pattern: ${B.toLowerCase()} (${d.confidence} confidence) \u2014 ${G}${d.note?` [${d.note}]`:""}`,highlight:{kind:"dies",dieKeys:_.map(C=>T(C))},relatedIds:ee.length>0?ee:void 0})}}m.sort((d,N)=>{const B=d.stats.adjustedPValue??d.stats.pValue??1,V=N.stats.adjustedPValue??N.stats.pValue??1;return B!==V?B-V:Math.abs(N.effect.absoluteDelta??0)-Math.abs(d.effect.absoluteDelta??0)});const k=We(e.dies,n.length,e.yield.yieldPercent);r&&(k.isLotStack=!0,t&&(k.aggregationMethod=t));const M=Qe(e.dies,e.testDefs);M&&(k.testSpecYield=M);const R=computeFunctionalYield(e.dies,e.testDefs);if(R&&(k.functionalYield=R),$&&(k.spatialPattern=$),v===void 0&&f.computePerTestStats){const d=ze(n,f.testNumbers,e.testDefs);d.warning&&S.push(d.warning),v=d.testNumbers}if(v?.length){const d=Ue(e.dies,v,e.testDefs,f.minimumSampleSize);d&&(k.perTestStats=d);const N=Ne([{dies:e.dies}],e.testDefs,v);N&&(k.capability=N)}Object.assign(k,Pe(e.dies,e.testDefs));const P=Ve([{dies:e.dies,wafer:e.wafer,passBins:f.passBins,ringCount:f.ringCount}]);return P&&(k.regionYield=P),S.length>0&&(k.warnings=S),ft(m,n,f.passBins),{level:"wafer",hasNotableFindings:m.some(d=>d.severity==="notable"||d.severity==="unusual"),findings:m,wafer:e.wafer.metadata??void 0,stats:k}}
@@ -1,5 +1,8 @@
1
1
  import type { Die } from '../core/dies.js';
2
+ import { type Chunked } from '../core/utils.js';
2
3
  import { type TestDef } from '../renderer/buildWaferMap.js';
4
+ import { type DescriptiveStats } from './math.js';
5
+ import type { TestCapability } from './types.js';
3
6
  export interface CapabilityDatum {
4
7
  testNumber: number;
5
8
  label: string;
@@ -49,7 +52,75 @@ export interface CapabilityItem {
49
52
  * (`hasSpec: false`), normalized onto their own observed range instead, so a
50
53
  * lot where most tests lack full limits doesn't render as an empty chart.
51
54
  * Sorted spec'd-first (worst-Ppk-first within that tier), then unspec'd
52
- * (most-variable-first within that tier) — see the sort at the bottom.
55
+ * (most-variable-first within that tier).
53
56
  */
54
57
  export declare function buildCapabilityData(items: CapabilityItem[], testDefs: TestDef[]): CapabilityDatum[];
58
+ /**
59
+ * @internal {@link buildCapabilityData} as a {@link Chunked} computation, for a
60
+ * caller rendering on the main thread — a lot-sized call walks every die of
61
+ * every wafer once per test and then sorts every value of every test, which is
62
+ * seconds of uninterruptible work at lot scale. Steps are one slice of the die
63
+ * pass, or one test's sort. Same result, same order; the synchronous entry
64
+ * above is this function drained.
65
+ */
66
+ export declare function buildCapabilityDataSteps(items: CapabilityItem[], testDefs: TestDef[]): Chunked<CapabilityDatum[]>;
67
+ /** @internal Everything {@link pooledTestStatsSteps} derives from its one pass. */
68
+ export interface PooledTestStats {
69
+ /**
70
+ * Raw (NOT normalised) descriptive statistics per test number, over the
71
+ * pooled eligible dies — the population every other figure here describes.
72
+ * Only tests that are parametric, carry a `TestDef`, and have at least one
73
+ * finite value appear.
74
+ */
75
+ stats: Map<number, DescriptiveStats>;
76
+ /**
77
+ * Per test, how many pooled values fell outside the test's own spec limits —
78
+ * the spec-yield tally, counted off the sorted values this pass already held.
79
+ * Only tests carrying at least one limit appear. `fail` counts `v < limitLow`
80
+ * or `v > limitHigh`, matching the per-die judgement everywhere else.
81
+ *
82
+ * This is here rather than the sorted arrays themselves because the result is
83
+ * memoised: a tally is a handful of numbers per test, whereas the values are
84
+ * every die-test reading in the lot (~160 MB on a 200k x 100 lot, §5's
85
+ * browser ceiling territory) and must not outlive the pass that built them.
86
+ */
87
+ specTally: Map<number, {
88
+ n: number;
89
+ fail: number;
90
+ }>;
91
+ /** Capability indices plus the chart's normalised five-number summary,
92
+ * worst-Ppk first. */
93
+ capability: CapabilityDatum[];
94
+ }
95
+ /** @internal Test seam — `tests/pooledTestStatsCache.test.mjs`. */
96
+ export declare function clearPooledTestStatsCache(): void;
97
+ /**
98
+ * @internal Per-test descriptive statistics AND capability indices for a pooled
99
+ * population, from **one** walk of the dies.
100
+ *
101
+ * The summary panel used to scan the pooled dies three times over: once per
102
+ * test for min/σ/quartiles, once per test again to count spec failures, and a
103
+ * third time inside `buildCapabilityData` for the Ppk column beside them — all
104
+ * reading the same values off the same dies and all of them O(dies × tests).
105
+ * On a 50-wafer lot of 4,000 dies × 100 tests that was 40 s of a 41 s panel
106
+ * render. One pass collects the values, one sort per test serves the quantiles
107
+ * of both the table and the chart, and the spec tally reads the sorted array.
108
+ *
109
+ * {@link Chunked}: one step per slice of the die pass, one per test thereafter.
110
+ */
111
+ export declare function pooledTestStatsSteps(items: CapabilityItem[], testDefs: TestDef[]): Chunked<PooledTestStats>;
112
+ /**
113
+ * @internal Capability indices without the chart's normalised quantiles — what
114
+ * `stats.capability` carries. Same moments, formulas and order as
115
+ * `buildCapabilityData`, without collecting and sorting every value.
116
+ */
117
+ export declare function buildCapabilityFigures(items: CapabilityItem[], testDefs: TestDef[]): TestCapability[];
118
+ /**
119
+ * @internal Lot capability pooled exactly from each wafer's own capability
120
+ * figures, without revisiting a die: a wafer's (n, mean, stdOverall) give its
121
+ * sum and sum of squares, and — one wafer being one subgroup — its within-wafer
122
+ * variance. So a lot analysis given `perWaferSummaries` stays cheap. Specs come
123
+ * from the lot's merged `testDefs`, as they would for a direct computation.
124
+ */
125
+ export declare function poolCapabilityFigures(perWafer: readonly TestCapability[][], testDefs: TestDef[]): TestCapability[];
55
126
  //# sourceMappingURL=capability.d.ts.map
@@ -1 +1 @@
1
- import{isYieldEligibleDie as z}from"../core/dies.js";import{isParametricTest as A}from"../renderer/buildWaferMap.js";import{quantile as b}from"./math.js";export function buildCapabilityData(T,q){const k=new Map,N=new Map;for(const t of q){const n=t.testNumber;if(n===void 0||!A(t))continue;N.set(n,t);const o=t.limitLow,e=t.limitHigh;o!==void 0&&e!==void 0&&e>o&&k.set(n,{lsl:o,usl:e})}if(N.size===0)return[];const M=new Map,g=t=>{let n=M.get(t);return n||(n={n:0,sum:0,sumSq:0,withinNumerator:0,withinDenominator:0,allValues:[]},M.set(t,n)),n};for(const t of T){const n=new Map;for(const o of t.dies??[]){if(!z(o))continue;const e=o.testValues;if(e)for(const f of Object.keys(e)){const u=Number(f);if(!N.has(u))continue;const i=e[u];if(i===void 0||!Number.isFinite(i))continue;const a=g(u);a.n++,a.sum+=i,a.sumSq+=i*i,a.allValues.push(i);let s=n.get(u);s||(s={wn:0,wsum:0,wsumSq:0},n.set(u,s)),s.wn++,s.wsum+=i,s.wsumSq+=i*i}}for(const[o,e]of n){if(e.wn<2)continue;const f=g(o),u=e.wsum/e.wn,i=(e.wsumSq-e.wn*u*u)/(e.wn-1);f.withinNumerator+=(e.wn-1)*Math.max(0,i),f.withinDenominator+=e.wn-1}}const S=[];for(const t of q){const n=t.testNumber;if(n===void 0)continue;const o=M.get(n);if(!o||o.n===0)continue;const{n:e,sum:f,sumSq:u,withinNumerator:i,withinDenominator:a,allValues:s}=o;s.sort((l,h)=>l-h);const r=f/e,B=e>=2?Math.max(0,(u-e*r*r)/(e-1)):0,p=Math.sqrt(B),c=a>0?Math.sqrt(i/a):NaN,v=k.get(n),x=v!==void 0;let d,w,y=null,O=null,V=null,D=null,m;if(x){({lsl:d,usl:w}=v);const l=w-d;V=p>0?l/(6*p):null,D=p>0?Math.min((w-r)/(3*p),(r-d)/(3*p)):null,y=Number.isFinite(c)&&c>0?l/(6*c):null,O=Number.isFinite(c)&&c>0?Math.min((w-r)/(3*c),(r-d)/(3*c)):null,m=h=>(h-d)/l}else{const l=s[0],F=s[s.length-1]-l;m=F>0?j=>(j-l)/F:()=>.5}S.push({testNumber:n,label:t.name??`Test ${n}`,unit:t.unit,hasSpec:x,lsl:d,usl:w,mean:r,stdOverall:p,stdWithin:c,n:e,cp:y,cpk:O,pp:V,ppk:D,min:m(s[0]),q1:m(b(s,.25)),median:m(b(s,.5)),q3:m(b(s,.75)),max:m(s[s.length-1])})}return S.sort((t,n)=>t.hasSpec!==n.hasSpec?t.hasSpec?-1:1:t.hasSpec?t.ppk===null&&n.ppk===null?0:t.ppk===null?1:n.ppk===null?-1:t.ppk-n.ppk:n.stdOverall-t.stdOverall),S}
1
+ import{isYieldEligibleDie as O}from"../core/dies.js";import{drain as C}from"../core/utils.js";import{isParametricTest as $}from"../renderer/buildWaferMap.js";import{describeSorted as E,quantile as k}from"./math.js";const L=25e4;function x(o){const e=new Map,t=new Map;for(const l of o){const n=l.testNumber;if(n===void 0||!$(l))continue;t.set(n,l);const i=l.limitLow,r=l.limitHigh;i!==void 0&&r!==void 0&&r>i&&e.set(n,{lsl:i,usl:r})}return{defByTestNumber:t,specByTest:e}}function*v(o,e,t){const l=new Map,n=[];for(const f of e.keys())l.set(f,n.length),n.push(f);const i=n.length,r=new Float64Array(i),s=new Float64Array(i),d=new Float64Array(i),w=new Float64Array(i),F=new Float64Array(i),g=new Float64Array(i),y=new Float64Array(i),b=new Float64Array(i),h=t?new Array(i):[],S=i<=64,A=Math.max(500,Math.floor(L/Math.max(1,i)));let u=0;for(const f of o){g.fill(0),y.fill(0),b.fill(0);for(const m of f.dies??[]){if(++u>=A&&(u=0,yield),!O(m))continue;const p=m.testValues;if(p){if(S){for(let c=0;c<i;c++){const a=p[n[c]];a===void 0||!Number.isFinite(a)||(r[c]++,s[c]+=a,d[c]+=a*a,g[c]++,y[c]+=a,b[c]+=a*a,t&&(h[c]??=[]).push(a))}continue}for(const c in p){const a=l.get(+c);if(a===void 0)continue;const N=p[+c];N===void 0||!Number.isFinite(N)||(r[a]++,s[a]+=N,d[a]+=N*N,g[a]++,y[a]+=N,b[a]+=N*N,t&&(h[a]??=[]).push(N))}}}for(let m=0;m<i;m++){const p=g[m];if(p<2)continue;const c=y[m]/p,a=(b[m]-p*c*c)/(p-1);w[m]+=(p-1)*Math.max(0,a),F[m]+=p-1}}const M=new Map;for(let f=0;f<i;f++){if(r[f]===0)continue;const m=n[f];M.set(m,{n:r[f],sum:s[f],sumSq:d[f],withinNumerator:w[f],withinDenominator:F[f]}),t&&h[f]&&t.set(m,h[f])}return M}function P(o,e,t){return C(v(o,e,t))}function q(o,e,t,l){const{n,sum:i,sumSq:r,withinNumerator:s,withinDenominator:d}=l,w=i/n,F=n>=2?Math.max(0,(r-n*w*w)/(n-1)):0,g=Math.sqrt(F),y=d>0?Math.sqrt(s/d):NaN;let b=null,h=null,S=null,A=null;if(t){const{lsl:u,usl:M}=t,f=M-u;S=g>0?f/(6*g):null,A=g>0?Math.min((M-w)/(3*g),(w-u)/(3*g)):null,b=Number.isFinite(y)&&y>0?f/(6*y):null,h=Number.isFinite(y)&&y>0?Math.min((M-w)/(3*y),(w-u)/(3*y)):null}return{testNumber:o,label:e.name??`Test ${o}`,unit:e.unit,hasSpec:t!==void 0,lsl:t?.lsl,usl:t?.usl,mean:w,stdOverall:g,stdWithin:y,n,cp:b,cpk:h,pp:S,ppk:A}}function B(o){return o.sort((e,t)=>e.hasSpec!==t.hasSpec?e.hasSpec?-1:1:e.hasSpec?e.ppk===null&&t.ppk===null?0:e.ppk===null?1:t.ppk===null?-1:e.ppk-t.ppk:t.stdOverall-e.stdOverall)}export function buildCapabilityData(o,e){return C(buildCapabilityDataSteps(o,e))}export function*buildCapabilityDataSteps(o,e){return(yield*pooledTestStatsSteps(o,e)).capability.slice()}const T=new Map,H=4,D=new WeakMap;let V=1;function _(o,e){const t=[];for(const n of o){const i=n.dies;if(!i){t.push(0);continue}let r=D.get(i);r===void 0&&(r=V++,D.set(i,r)),t.push(r)}const l=e.map(n=>`${n.testNumber}${n.testType??"P"}${n.limitLow??""}${n.limitHigh??""}${n.name??""}${n.unit??""}`);return`${t.join(",")}${l.join("")}`}export function clearPooledTestStatsCache(){T.clear()}function z(o,e){let t=0,l=o.length;for(;t<l;){const n=t+l>>1;o[n]<e?t=n+1:l=n}return t}function I(o,e){let t=0,l=o.length;for(;t<l;){const n=t+l>>1;o[n]<=e?t=n+1:l=n}return t}export function*pooledTestStatsSteps(o,e){const t=_(o,e),l=T.get(t);if(l)return T.delete(t),T.set(t,l),l;const n={stats:new Map,specTally:new Map,capability:[]},{defByTestNumber:i,specByTest:r}=x(e);if(i.size===0)return n;const s=new Map,d=yield*v(o,i,s),w=new Map,F=new Map,g=[];for(const b of e){yield;const h=b.testNumber;if(h===void 0)continue;const S=d.get(h);if(!S||S.n===0||!i.has(h))continue;const A=s.get(h);s.delete(h);const u=Float64Array.from(A);if(u.sort(),w.set(h,E(u)),b.limitLow!==void 0||b.limitHigh!==void 0){const p=b.limitLow!==void 0?z(u,b.limitLow):0,c=b.limitHigh!==void 0?u.length-I(u,b.limitHigh):0;F.set(h,{n:u.length,fail:p+c})}const M=r.get(h),f=q(h,b,M,S);let m;if(M){const p=M.usl-M.lsl;m=c=>(c-M.lsl)/p}else{const p=u[0],c=u[u.length-1]-p;m=c>0?a=>(a-p)/c:()=>.5}g.push({...f,min:m(u[0]),q1:m(k(u,.25)),median:m(k(u,.5)),q3:m(k(u,.75)),max:m(u[u.length-1])})}const y={stats:w,specTally:F,capability:B(g)};return T.set(t,y),T.size>H&&T.delete(T.keys().next().value),y}export function buildCapabilityFigures(o,e){const{defByTestNumber:t,specByTest:l}=x(e);if(t.size===0)return[];const n=P(o,t,null),i=[];for(const[r,s]of n)s.n!==0&&i.push(q(r,t.get(r),l.get(r),s));return B(i)}export function poolCapabilityFigures(o,e){const{defByTestNumber:t,specByTest:l}=x(e),n=new Map;for(const r of o)for(const s of r){if(!t.has(s.testNumber)||s.n===0)continue;let d=n.get(s.testNumber);d||(d={n:0,sum:0,sumSq:0,withinNumerator:0,withinDenominator:0},n.set(s.testNumber,d));const w=s.stdOverall*s.stdOverall;d.n+=s.n,d.sum+=s.n*s.mean,d.sumSq+=(s.n-1)*w+s.n*s.mean*s.mean,s.n>=2&&(d.withinNumerator+=(s.n-1)*w,d.withinDenominator+=s.n-1)}const i=[];for(const[r,s]of n)i.push(q(r,t.get(r),l.get(r),s));return B(i)}
@@ -40,7 +40,37 @@ export declare function pearsonOfPairs(pairs: ArrayLike<{
40
40
  export interface CorrelationMatrix {
41
41
  tests: CorrelationTestInfo[];
42
42
  cells: CorrelationCell[];
43
+ /**
44
+ * Present only when the matrix was computed from a sample of the dies rather
45
+ * than all of them — see `CORRELATION_DIE_BUDGET`. `of` is the population that
46
+ * carried test values, `used` how many were read.
47
+ *
48
+ * **Any surface showing this matrix must say so.** An `r` from a sample is a
49
+ * perfectly good estimate, but an unlabelled one is a number the reader will
50
+ * take for the whole population — the exact class of quietly-wrong figure this
51
+ * library exists to prevent. `correlationSampleNote()` is the wording.
52
+ */
53
+ sample?: {
54
+ of: number;
55
+ used: number;
56
+ };
43
57
  }
58
+ /**
59
+ * Dies read before `buildCorrelationMatrix` starts sampling.
60
+ *
61
+ * Correlation is the only computation here that is quadratic in tests *and*
62
+ * linear in dies: a 400k-die, 50-test lot is 1,225 pairs per die — 490 million
63
+ * pair updates, each touching six accumulators — which in a browser presents as
64
+ * a panel that never renders (tsmap WMAP_ISSUES #61).
65
+ *
66
+ * 25,000 is far past the point where more dies change a Pearson coefficient: the
67
+ * standard error of r is about `(1 - r²)/sqrt(n)`, so at n = 25,000 it is under
68
+ * 0.007 even for r = 0. Sixteen times more dies would halve an error that is
69
+ * already invisible at two decimal places.
70
+ */
71
+ export declare const CORRELATION_DIE_BUDGET = 25000;
72
+ /** The sentence a panel puts next to a sampled matrix. */
73
+ export declare function correlationSampleNote(sample: CorrelationMatrix['sample']): string | undefined;
44
74
  /**
45
75
  * Pearson correlation matrix for every test in `testDefs` across every die
46
76
  * in `dies`.
@@ -1 +1 @@
1
- import{isParametricTest as A}from"../renderer/buildWaferMap.js";export function pearsonFromSums(o,l,u,i,y,f){if(o<3)return null;const d=l/o,c=u/o,r=f/o-d*c,m=i/o-d*d,p=y/o-c*c,h=Math.sqrt(m*p);return h===0?null:Math.max(-1,Math.min(1,r/h))}export function pearsonOfPairs(o){let l=0,u=0,i=0,y=0,f=0,d=0;for(let c=0;c<o.length;c++){const{x:r,y:m}=o[c];!Number.isFinite(r)||!Number.isFinite(m)||(l++,u+=r,i+=m,y+=r*r,f+=m*m,d+=r*m)}return{r:pearsonFromSums(l,u,i,y,f,d),n:l}}function N(o){return o.filter(l=>l.testNumber!==void 0&&A(l)).map(l=>({testNumber:l.testNumber,label:l.name,unit:l.unit}))}export function buildCorrelationMatrix(o,l){const u=N(l);if(u.length<2)return{tests:u,cells:[]};const i=u.length,y=u.map(t=>t.testNumber),f=i*(i-1)/2,d=new Float64Array(f),c=new Float64Array(i),r=new Float64Array(f),m=new Float64Array(f),p=new Float64Array(f),h=new Float64Array(f),g=new Float64Array(f);function w(t,n){return t*i-(t*(t+1)>>1)+(n-t-1)}for(const t of o){if(!t.testValues)continue;const n=new Float64Array(i),x=new Uint8Array(i);for(let s=0;s<i;s++){const a=t.testValues[y[s]];a!==void 0&&Number.isFinite(a)&&(n[s]=a,x[s]=1)}for(let s=0;s<i;s++){if(!x[s])continue;c[s]++;const a=n[s];for(let b=s+1;b<i;b++){if(!x[b])continue;const M=n[b],I=w(s,b);d[I]++,r[I]+=a,m[I]+=M,p[I]+=a*a,h[I]+=M*M,g[I]+=a*M}}}const F=t=>pearsonFromSums(d[t],r[t],m[t],p[t],h[t],g[t]),e=[];for(let t=0;t<i;t++)for(let n=0;n<i;n++){if(n===t){e.push({xIndex:n,yIndex:t,r:1,n:c[n]});continue}const x=Math.min(n,t),s=Math.max(n,t),a=w(x,s);e.push({xIndex:n,yIndex:t,r:F(a),n:d[a]})}return{tests:u,cells:e}}export function filterCorrelationMatrix(o,{threshold:l=.3,minTests:u=6,maxTests:i=20}={}){const y=[];let f=0,d=0,c=null;for(const e of o.cells){if(e.xIndex>=e.yIndex||e.r===null)continue;const t=Math.abs(e.r);y.push({xi:e.xIndex,yi:e.yIndex,absR:t}),(c===null||t>Math.abs(c.r))&&(c={xLabel:o.tests[e.xIndex].label,yLabel:o.tests[e.yIndex].label,r:e.r})}y.sort((e,t)=>t.absR-e.absR);const r=new Set;for(const{xi:e,yi:t,absR:n}of y){const x=n<l;r.size>=i||x&&r.size>=u||(r.add(e),r.size<i&&r.add(t))}if(r.size<Math.min(u,o.tests.length))for(let e=0;e<o.tests.length&&r.size<Math.min(i,u,o.tests.length);e++)r.add(e);const m=(()=>{const e=Array.from(r),t=new Map,n=new Map;for(const{xi:s,yi:a,absR:b}of y)!r.has(s)||!r.has(a)||(t.set(s,(t.get(s)??0)+b),t.set(a,(t.get(a)??0)+b),n.set(s,(n.get(s)??0)+1),n.set(a,(n.get(a)??0)+1));const x=s=>(n.get(s)??0)>0?t.get(s)/n.get(s):0;return e.sort((s,a)=>x(a)-x(s))})(),p=m.map(e=>o.tests[e]),h=new Map(m.map((e,t)=>[e,t])),g=new Set(p.map(e=>e.testNumber)),w=o.cells.filter(e=>g.has(o.tests[e.xIndex].testNumber)&&g.has(o.tests[e.yIndex].testNumber)).map(e=>({xIndex:h.get(e.xIndex),yIndex:h.get(e.yIndex),r:e.r,n:e.n}));let F=0;for(const{xi:e,yi:t,absR:n}of y)r.has(e)&&r.has(t)?n>=.7?f++:n>=.4&&d++:n<l&&F++;return{matrix:{tests:p,cells:w},strongPairs:f,moderatePairs:d,hiddenWeakPairs:F,strongestPair:c}}
1
+ import{isParametricTest as X}from"../renderer/buildWaferMap.js";export function pearsonFromSums(e,i,a,r,c,d){if(e<3)return null;const m=i/e,f=a/e,s=d/e-m*f,h=r/e-m*m,w=c/e-f*f,I=Math.sqrt(h*w);return I===0?null:Math.max(-1,Math.min(1,s/I))}export function pearsonOfPairs(e){let i=0,a=0,r=0,c=0,d=0,m=0;for(let f=0;f<e.length;f++){const{x:s,y:h}=e[f];!Number.isFinite(s)||!Number.isFinite(h)||(i++,a+=s,r+=h,c+=s*s,d+=h*h,m+=s*h)}return{r:pearsonFromSums(i,a,r,c,d,m),n:i}}export const CORRELATION_DIE_BUDGET=25e3;function Y(e,i){const a=[],r=e/i;for(let c=0;c<i;c++)a.push(Math.floor(c*r));return a}export function correlationSampleNote(e){if(e)return`From a ${e.used.toLocaleString()}-die sample of ${e.of.toLocaleString()}, spread evenly across the lot`}function v(e){return e.filter(i=>i.testNumber!==void 0&&X(i)).map(i=>({testNumber:i.testNumber,label:i.name,unit:i.unit}))}export function buildCorrelationMatrix(e,i){const a=v(i);if(a.length<2)return{tests:a,cells:[]};const r=a.length,c=a.map(n=>n.testNumber),d=r*(r-1)/2,m=new Float64Array(d),f=new Float64Array(r),s=new Float64Array(d),h=new Float64Array(d),w=new Float64Array(d),I=new Float64Array(d),A=new Float64Array(d);function R(n,o){return n*r-(n*(n+1)>>1)+(o-n-1)}const g=e.filter(n=>n.testValues!==void 0),t=g.length>CORRELATION_DIE_BUDGET,l=t?Y(g.length,CORRELATION_DIE_BUDGET).map(n=>g[n]):g,u=new Float64Array(r),p=new Uint8Array(r);for(const n of l){p.fill(0);for(let o=0;o<r;o++){const b=n.testValues[c[o]];b!==void 0&&Number.isFinite(b)&&(u[o]=b,p[o]=1)}for(let o=0;o<r;o++){if(!p[o])continue;f[o]++;const b=u[o];for(let F=o+1;F<r;F++){if(!p[F])continue;const M=u[F],N=R(o,F);m[N]++,s[N]+=b,h[N]+=M,w[N]+=b*b,I[N]+=M*M,A[N]+=b*M}}}const y=n=>pearsonFromSums(m[n],s[n],h[n],w[n],I[n],A[n]),x=[];for(let n=0;n<r;n++)for(let o=0;o<r;o++){if(o===n){x.push({xIndex:o,yIndex:n,r:1,n:f[o]});continue}const b=Math.min(o,n),F=Math.max(o,n),M=R(b,F);x.push({xIndex:o,yIndex:n,r:y(M),n:m[M]})}return t?{tests:a,cells:x,sample:{of:g.length,used:l.length}}:{tests:a,cells:x}}export function filterCorrelationMatrix(e,{threshold:i=.3,minTests:a=6,maxTests:r=20}={}){const c=[];let d=0,m=0,f=null;for(const t of e.cells){if(t.xIndex>=t.yIndex||t.r===null)continue;const l=Math.abs(t.r);c.push({xi:t.xIndex,yi:t.yIndex,absR:l}),(f===null||l>Math.abs(f.r))&&(f={xLabel:e.tests[t.xIndex].label,yLabel:e.tests[t.yIndex].label,r:t.r})}c.sort((t,l)=>l.absR-t.absR);const s=new Set;for(const{xi:t,yi:l,absR:u}of c){const p=u<i;s.size>=r||p&&s.size>=a||(s.add(t),s.size<r&&s.add(l))}if(s.size<Math.min(a,e.tests.length))for(let t=0;t<e.tests.length&&s.size<Math.min(r,a,e.tests.length);t++)s.add(t);const h=(()=>{const t=Array.from(s),l=new Map,u=new Map;for(const{xi:y,yi:x,absR:n}of c)!s.has(y)||!s.has(x)||(l.set(y,(l.get(y)??0)+n),l.set(x,(l.get(x)??0)+n),u.set(y,(u.get(y)??0)+1),u.set(x,(u.get(x)??0)+1));const p=y=>(u.get(y)??0)>0?l.get(y)/u.get(y):0;return t.sort((y,x)=>p(x)-p(y))})(),w=h.map(t=>e.tests[t]),I=new Map(h.map((t,l)=>[t,l])),A=new Set(w.map(t=>t.testNumber)),R=e.cells.filter(t=>A.has(e.tests[t.xIndex].testNumber)&&A.has(e.tests[t.yIndex].testNumber)).map(t=>({xIndex:I.get(t.xIndex),yIndex:I.get(t.yIndex),r:t.r,n:t.n}));let g=0;for(const{xi:t,yi:l,absR:u}of c)s.has(t)&&s.has(l)?u>=.7?d++:u>=.4&&m++:u<i&&g++;return{matrix:{tests:w,cells:R},strongPairs:d,moderatePairs:m,hiddenWeakPairs:g,strongestPair:f}}