@wafertools/wafermap 0.27.0 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +10 -2
- package/CHANGELOG.md +310 -0
- package/README.md +1 -1
- package/dist/packages/canvas-adapter/charts/binCluster.d.ts +0 -1
- package/dist/packages/canvas-adapter/charts/boxplot.d.ts +8 -1
- package/dist/packages/canvas-adapter/charts/boxplot.js +1 -1
- package/dist/packages/canvas-adapter/charts/capability.d.ts +0 -1
- package/dist/packages/canvas-adapter/charts/correlation.d.ts +0 -1
- package/dist/packages/canvas-adapter/charts/histogram.d.ts +0 -1
- package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
- package/dist/packages/canvas-adapter/charts/regionYieldDiagram.d.ts +0 -1
- package/dist/packages/canvas-adapter/charts/scatter.d.ts +6 -1
- package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
- package/dist/packages/canvas-adapter/charts/trend.d.ts +7 -0
- package/dist/packages/canvas-adapter/charts/trend.js +1 -1
- package/dist/packages/canvas-adapter/hitGrid.d.ts +22 -0
- package/dist/packages/canvas-adapter/hitGrid.js +1 -0
- package/dist/packages/canvas-adapter/index.d.ts +1 -0
- package/dist/packages/canvas-adapter/index.js +1 -1
- package/dist/packages/canvas-adapter/insightsTab.d.ts +24 -2
- package/dist/packages/canvas-adapter/insightsTab.js +1 -1
- package/dist/packages/canvas-adapter/maplessSummary.d.ts +5 -1
- package/dist/packages/canvas-adapter/maplessSummary.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferMap.d.ts +38 -1
- package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
- package/dist/packages/canvas-adapter/summaryPanel.d.ts +15 -5
- package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
- package/dist/packages/canvas-adapter/toCanvas.js +1 -1
- package/dist/packages/canvas-adapter/toolbar.d.ts +42 -9
- package/dist/packages/canvas-adapter/toolbar.js +2 -2
- package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
- package/dist/packages/canvas-adapter/userGuideHtml.js +27 -16
- package/dist/packages/canvas-adapter/version.d.ts +2 -2
- package/dist/packages/canvas-adapter/version.js +1 -1
- package/dist/packages/canvas-adapter/warnings.d.ts +25 -0
- package/dist/packages/canvas-adapter/warnings.js +1 -1
- package/dist/packages/core/dies.d.ts +11 -0
- package/dist/packages/core/dies.js +1 -1
- package/dist/packages/renderer/binColors.d.ts +80 -0
- package/dist/packages/renderer/binColors.js +1 -0
- package/dist/packages/renderer/buildView.d.ts +67 -6
- package/dist/packages/renderer/buildView.js +1 -1
- package/dist/packages/renderer/buildWaferMap.d.ts +8 -2
- package/dist/packages/renderer/buildWaferMap.js +1 -1
- package/dist/packages/renderer/colorMap.d.ts +16 -15
- package/dist/packages/renderer/colorMap.js +1 -1
- package/dist/packages/renderer/colorSchemes.d.ts +45 -26
- package/dist/packages/renderer/colorSchemes.js +1 -1
- package/dist/packages/renderer/index.d.ts +3 -1
- package/dist/packages/renderer/index.js +1 -1
- package/dist/packages/stats/analyzeWaferLot.js +1 -1
- package/dist/packages/stats/analyzeWaferMap.js +1 -1
- package/dist/packages/stats/histogram.d.ts +28 -1
- package/dist/packages/stats/histogram.js +1 -1
- package/dist/packages/stats/population.d.ts +23 -0
- package/dist/packages/stats/population.js +1 -0
- package/dist/packages/stats/renderFindingsReport.js +10 -10
- package/dist/packages/stats/renderSummaryReport.js +22 -22
- package/dist/packages/stats/yield.js +1 -1
- package/package.json +5 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createWafer as j}from"../core/wafer.js";import{isYieldEligibleDie as ce,getDieKey as V,hasPosition as E}from"../core/dies.js";import{applyOrientation as q,transformDies as le}from"../core/transforms.js";import{affineRotation as ue,affineMirror as he,affineCompose as pe,affinePoint as me}from"../core/transforms.js";import{inferWaferFromXY as ge}from"../core/inference/wafer.js";import{resolveGridPitch as ye}from"../core/inference/pitch.js";import{assignGridIndices as we}from"../core/inference/grid.js";import{generateReticleGrid as xe}from"../core/reticle.js";import{buildView as H}from"./buildView.js";import{modeOf as be}from"../core/utils.js";import{aggregateValues as U,aggregateBinCounts as De}from"../core/aggregates.js";export function isParametricTest(t){return t?.testType!=="F"}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],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],standardDiameters:t.standardDiameters,testDefs:t.testDefs,hbinDefs:t.hbinDefs,sbinDefs:t.sbinDefs,metadataFields:t.metadataFields,retestPolicy:t.retestPolicy??"last",edgeDieYieldMode:t.edgeDieYieldMode??"exclude"}}function Oe(t,n){return n?.coordinateOrigin?n.coordinateOrigin:{type:"center"}}function Re(t,n,e){if(n.type==="custom"&&n.offset)return{offsetX:n.offset.x,offsetY:n.offset.y};if(n.type!=="center"){let u=1/0,c=-1/0,a=1/0,l=-1/0;for(const s of t)s.x<u&&(u=s.x),s.x>c&&(c=s.x),s.y<a&&(a=s.y),s.y>l&&(l=s.y);return{offsetX:Math.round((c+u)/2),offsetY:Math.round((l+a)/2)}}return{offsetX:e.offsetX,offsetY:e.offsetY}}function Se(t,n,e,u,c,a){if(a)return{colMidX:(Math.round(a.x)-n)*u,colMidY:(Math.round(a.y)-e)*c,anchored:!0};if(t.length===0)return{colMidX:0,colMidY:0,anchored:!1};let l=1/0,s=-1/0,h=1/0,p=-1/0;for(const i of t){const f=Math.round(i.x)-n,r=Math.round(i.y)-e;f<l&&(l=f),f>s&&(s=f),r<h&&(h=r),r>p&&(p=r)}return{colMidX:(l+s)/2*u,colMidY:(h+p)/2*c,anchored:!1}}function Ee(t,n,e){let u=1/0,c=-1/0,a=1/0,l=-1/0,s=0,h=0;for(const f of t){const r=Math.round(f.x)-n,d=Math.round(f.y)-e;s+=r,h+=d,r<u&&(u=r),r>c&&(c=r),d<a&&(a=d),d>l&&(l=d)}const p=t.length,i=(f,r,d)=>{const m=(d-r)/2;return m<=0?0:Math.abs(f/p-(r+d)/2)/m};return i(s,u,c)>.11||i(h,a,l)>.11}function ve(t,n){let e=t?.xAxisDirection==="left",u=t?.yAxisDirection==="down";return(n.type==="UL"||n.type==="UR")&&(u=!0),(n.type==="LR"||n.type==="UR")&&(e=!0),{flipX:e,flipY:u}}function ke(t,n){const{method:e,targetBin:u}=t,c=t.results.map(a=>a.filter(E));if(e==="mean"||e==="median"||e==="stddev"||e==="min"||e==="max"||e==="count"){const a=new Set((n??[]).filter(i=>!isParametricTest(i)).map(i=>i.testNumber)),l=new Set;for(const i of c)for(const f of i)if(f.testValues)for(const r of Object.keys(f.testValues)){const d=Number(r);a.has(d)||l.add(d)}if(l.size===0)return U(c,e);const s=[...l],h=s.map(i=>U(c,e,i)),p=new Map;for(let i=0;i<s.length;i++){const f=s[i];for(const r of h[i]){const d=V(r);p.has(d)||p.set(d,{template:r,testValues:{}});const m=p.get(d),g=r.testValues?.[0];g!==void 0&&(m.testValues[f]=g)}}return[...p.values()].map(({template:i,testValues:f})=>({...i,testValues:Object.keys(f).length>0?f:void 0}))}if(e==="countBin"||e==="percent"){if(u===void 0)return[];const a=De(c,u,"hard");if(e==="countBin")return a;const l=c.length;return a.map(s=>({...s,testValues:{0:l>0?(s.testValues?.[0]??0)/l*100:0}}))}if(e==="mode"){const a=new Map;for(const s of c)for(const h of s){const p=V(h);a.has(p)||a.set(p,[]),a.get(p).push(h)}const l=[];for(const[s,h]of a){const p=s.split(","),i=Number(p[0]),f=Number(p[1]),r=h.map(m=>m.hbin).filter(m=>m!==void 0),d=be(r);d!==null&&l.push({x:i,y:f,hbin:d})}return l}return[]}function J(t){const n=t.filter(E),e=n.length,u=n.filter(a=>a.edgeExcluded).length,c=n.filter(a=>dieHasTestData(a)||a.hbin!==void 0||a.sbin!==void 0).length;return{filledDies:c,totalDies:e,edgeExcludedDies:u,unpositionedDies:t.length-n.length,ratio:e>0?c/e:0}}function Q(t,n,e="exclude"){const u=new Set(n),a=t.filter(d=>!d.partial).filter(d=>d.edgeExcluded).length,l=t.filter(d=>d.partial).length;let s=0,h=0,p=!1;for(const d of t){if(!ce(d))continue;const m=d.hbin??d.sbin;m!==void 0&&(p=!0,u.has(m)?s++:h++)}const i=s+h,f=p&&i>0?s/i*100:null;let r=null;if(e==="denominator-only"){const d=i+a;r=p&&d>0?s/d*100:null}return{passDies:s,failDies:h,edgeExcludedDies:a,partialDies:l,totalDies:i,yieldPercent:f,yieldPercentGross:r}}function Z(t,n,e,u,c,a=0,l=0,s=0,h=0,p=0,i=!1,f=!1){if(!t)return[];const r=t.anchorDie??{x:0,y:0},d=xe(n,{width:t.width,height:t.height,diePitchX:u,diePitchY:c,anchorDie:{x:r.x-a,y:r.y-l},gridOrigin:{x:-s,y:-h}}),m=pe(he(i,f,n.center.x,n.center.y),ue(p,n.center.x,n.center.y));return d.filter(g=>{const v=g.width/2,b=g.height/2,D=[[g.x-v,g.y-b],[g.x+v,g.y-b],[g.x+v,g.y+b],[g.x-v,g.y+b]].map(([w,x])=>me(m,w,x)),k=Math.min(...D.map(w=>w.x)),A=Math.max(...D.map(w=>w.x)),F=Math.min(...D.map(w=>w.y)),P=Math.max(...D.map(w=>w.y));return e.some(w=>w.physX>=k&&w.physX<A&&w.physY>=F&&w.physY<P)})}function Ae(t,n,e){const c=Math.max(0,n.radius-e)**2;return t.map(a=>{const l=a.physX-n.center.x,s=a.physY-n.center.y;return l*l+s*s>c?{...a,edgeExcluded:!0}:a})}function Ce(t,n,e){const u=t.filter(E),c=t.filter(i=>!E(i)),a=new Map;for(const i of u){let f=a.get(i.x);f||(f=new Map,a.set(i.x,f)),f.set(i.y,(f.get(i.y)??0)+1)}const l=new Set(e);function s(i,f){const r=i.hbin,d=f.hbin;if(r===void 0||d===void 0)return!1;const m=l.has(r),g=l.has(d);return n==="best"?g!==m?g:d<r:g!==m?m:d>r}const h=new Map;for(const i of u){const f=V(i),r=h.get(f);n==="first"&&r||(n==="best"||n==="worst")&&r&&!s(r,i)||h.set(f,i)}return[...Array.from(h.values()).map(i=>{const r=a.get(i.x)?.get(i.y)??1;return r>1?{...i,retestCount:r}:i}),...c]}export function getDieTestValue(t,n){return t.testValues?.[n]}export function getTestPassStatus(t,n,e){const u=t.testPass?.[n];if(u!==void 0)return u;if(e!==void 0&&!isParametricTest(e)){const c=t.testValues?.[n];if(c===0||c===1)return c===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 L(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 ee(t,n){return n.plotMode?n.plotMode:t.some(dieHasTestData)?"value":"hardBin"}function te(t){return(t.warnings??[]).map(n=>{const e=Ie(n);return{code:e,message:n,severity:e==="non-standard-diameter"||e==="diameter-exceeds-die-extent"?"warning":"error",confidence:t.wafer.confidence}})}function Ie(t){return t.includes(ne)?"geometry-conflict":t.includes(ie)?"non-standard-diameter":t.includes(se)?"diameter-exceeds-die-extent":"partial-coverage"}const ne="do not fit inside the supplied",ie="Inferred wafer diameter",se="The probed dies reach only",Fe=.75,Pe=20;export const STANDARD_WAFER_DIAMETERS_MM=[100,125,150,200,300];const _e=.02;function oe(t,n){return n.reduce((e,u)=>Math.abs(u-t)<Math.abs(e-t)?u:e)}function Ye(t,n){if(!n.length)return!0;const e=oe(t,n);return Math.abs(e-t)/e<=_e}function $e(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:u,...c}=n??{},a=e.lotStackOpts?ke(e.lotStackOpts,e.testDefs):e.results;for(let o=0;o<Math.min(5,a.length);o++){const y=a[o];if(typeof y.x=="string"||typeof y.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 o of a)if(o.x==null!=(o.y==null))throw new TypeError(`buildWaferMap: die has only one of x/y set (x=${String(o.x)}, y=${String(o.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 l=Ce(a,e.retestPolicy,e.passBins),s={wafer:{confidence:1,method:"provided"},diePitch:{confidence:1,units:"mm"},grid:{confidence:1}};if(e.explicitDies){let o=e.explicitDies;const y=l.filter(E),R=l.filter(T=>!E(T));if(y.length>0){const T=new Map(y.map(S=>[V(S),S]));o=o.map(S=>{const G=T.get(V(S));return G?L(S,G):S})}const $=e.waferOpts?.diameter??300,I=j({diameter:$,notch:e.waferOpts?.notch,orientation:e.waferOpts?.orientation??0,metadata:e.waferOpts?.metadata});I.orientation!==0&&(o=q(o,I));const z=Z(e.reticleOpts,I,o,1,1,0,0,0,0,I.orientation),de=c.showReticle??e.reticleOpts!==void 0,B=H(I,o,{...c,reticles:z,showReticle:de,plotMode:ee(l,c),testDefs:e.testDefs,isLotStack:!1},{hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,metadataFields:e.metadataFields}),fe=R.map((T,S)=>L({id:`unpositioned_${S}`,width:o[0]?.width??1,height:o[0]?.height??1},T)),W=[...o,...fe];return{wafer:I,dies:W,view:B,reticleConfig:e.reticleOpts,units:"mm",inference:s,warnings:te(s),plotMode:B.plotMode,metadata:B.metadata,isLotStack:!1,dataCoverage:J(W),yield:Q(W,e.passBins,e.edgeDieYieldMode),reticles:z,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs,metadataFields:e.metadataFields}}const h=l.filter(E),p=l.filter(o=>!E(o)),i=h.map(o=>({x:o.x,y:o.y})),f=ye(i,e.dieOpts,e.waferOpts?.diameter);s.diePitch={confidence:f.confidence,units:f.units};const{pitchX:r,pitchY:d}=f,m=f.units,g=Oe(h,e.dieOpts),v=we(i);s.grid={confidence:v.confidence};const{offsetX:b,offsetY:D}=Re(i,g,v),{flipX:k,flipY:A}=ve(e.dieOpts,g),{colMidX:F,colMidY:P,anchored:w}=Se(i,b,D,r,d,e.waferOpts?.center);let x=e.waferOpts?.diameter;const _=i.map(o=>({x:(Math.round(o.x)-b)*r-F,y:(Math.round(o.y)-D)*d-P})),C=_.length>0?Math.max(..._.map(({x:o,y})=>Math.hypot(Math.abs(o)+r/2,Math.abs(y)+d/2))):0;if(x===void 0)if(i.length>0)if(m==="mm"){const o=ge(_,{minRadius:C});x=o.diameter,s.wafer={confidence:o.confidence,method:o.method};const y=$e(e.standardDiameters);if(!Ye(x,y)){const R=oe(x,y);(s.warnings??=[]).push(`${ie} ${x.toFixed(1)} mm is not a standard wafer size (nearest is ${R} 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 x=C*2,s.wafer={confidence:f.confidence*.8,method:"extent"};else x=m==="mm"?300:30,s.wafer={confidence:0,method:"default"};if(!w&&i.length>0&&Ee(i,b,D)&&(s.wafer.method="inferred-partial",(s.warnings??=[]).push("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&&C>0&&i.length>=Pe){const o=C/(x/2);o<Fe&&(s.warnings??=[]).push(`${se} ${(o*100).toFixed(0)}% of the ${x} 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 o=e.dieOpts?.width!==void 0&&e.dieOpts?.height!==void 0;if(o&&C>x/2+1e-9){const y=_.filter(({x:R,y:$})=>Math.hypot(Math.abs(R)+r/2,Math.abs($)+d/2)>x/2+1e-9).length;(s.warnings??=[]).push(`${y} of ${_.length} probed die positions ${ne} ${x} mm wafer at the supplied die pitch of ${r} \xD7 ${d} 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 ${(C*2).toFixed(1)} mm. Check waferConfig.diameter and dieConfig.width/height against the real device.`)}}const O=j({diameter:x,notch:e.waferOpts?.notch,orientation:e.waferOpts?.orientation??0,metadata:e.waferOpts?.metadata});let M=h.map(o=>{const y=Math.round(o.x)-b,R=Math.round(o.y)-D,$={id:`${y}_${R}`,x:y,y:R,physX:y*r-F,physY:R*d-P,width:r,height:d,insideWafer:!0,partial:!1};return L($,o)});(b!==0||D!==0)&&(M=M.map(o=>({...o,x:o.x+b,y:o.y+D,id:`${o.x+b}_${o.y+D}`}))),M=q(M,O),(k||A)&&(M=le(M,{flipX:k,flipY:A},O.center));const X=[];e.waferOpts?.edgeExclusion&&e.waferOpts.edgeExclusion>0&&(M=Ae(M,O,e.waferOpts.edgeExclusion),e.waferOpts.edgeExclusion>O.radius&&X.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 K=Z(e.reticleOpts,O,M,r,d,b,D,F,P,O.orientation,k,A),re=c.showReticle??e.reticleOpts!==void 0,Y=H(O,M,{...c,reticles:K,showReticle:re,plotMode:ee(l,c),testDefs:e.testDefs,dataAxisFlip:{x:k,y:A},isLotStack:e.lotStackOpts!==void 0,aggregationMethod:e.lotStackOpts?.method,lotSize:e.lotStackOpts?.results.length},{hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,metadataFields:e.metadataFields}),ae=p.map((o,y)=>L({id:`unpositioned_${y}`,width:r,height:d},o)),N=[...M,...ae];return{wafer:O,dies:N,view:Y,reticleConfig:e.reticleOpts,units:m,inference:s,warnings:[...te(s),...X],plotMode:Y.plotMode,metadata:Y.metadata,isLotStack:e.lotStackOpts!==void 0,dataCoverage:J(N),yield:Q(N,e.passBins,e.edgeDieYieldMode),reticles:K,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs,metadataFields:e.metadataFields,aggrMethod:Y.aggrMethod,lotSize:Y.lotSize}}
|
|
1
|
+
import{createWafer as j}from"../core/wafer.js";import{isYieldEligibleDie as ce,getDieKey as V,hasPosition as E}from"../core/dies.js";import{applyOrientation as q,transformDies as le}from"../core/transforms.js";import{affineRotation as ue,affineMirror as he,affineCompose as pe,affinePoint as me}from"../core/transforms.js";import{inferWaferFromXY as ge}from"../core/inference/wafer.js";import{resolveGridPitch as ye}from"../core/inference/pitch.js";import{assignGridIndices as we}from"../core/inference/grid.js";import{generateReticleGrid as xe}from"../core/reticle.js";import{buildView as H}from"./buildView.js";import{modeOf as De}from"../core/utils.js";import{aggregateValues as U,aggregateBinCounts as be}from"../core/aggregates.js";export function isParametricTest(t){return t?.testType!=="F"}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],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],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 Oe(t,n){return n?.coordinateOrigin?n.coordinateOrigin:{type:"center"}}function Re(t,n,e){if(n.type==="custom"&&n.offset)return{offsetX:n.offset.x,offsetY:n.offset.y};if(n.type!=="center"){let u=1/0,c=-1/0,a=1/0,l=-1/0;for(const s of t)s.x<u&&(u=s.x),s.x>c&&(c=s.x),s.y<a&&(a=s.y),s.y>l&&(l=s.y);return{offsetX:Math.round((c+u)/2),offsetY:Math.round((l+a)/2)}}return{offsetX:e.offsetX,offsetY:e.offsetY}}function Se(t,n,e,u,c,a){if(a)return{colMidX:(Math.round(a.x)-n)*u,colMidY:(Math.round(a.y)-e)*c,anchored:!0};if(t.length===0)return{colMidX:0,colMidY:0,anchored:!1};let l=1/0,s=-1/0,h=1/0,p=-1/0;for(const i of t){const f=Math.round(i.x)-n,r=Math.round(i.y)-e;f<l&&(l=f),f>s&&(s=f),r<h&&(h=r),r>p&&(p=r)}return{colMidX:(l+s)/2*u,colMidY:(h+p)/2*c,anchored:!1}}function Ee(t,n,e){let u=1/0,c=-1/0,a=1/0,l=-1/0,s=0,h=0;for(const f of t){const r=Math.round(f.x)-n,d=Math.round(f.y)-e;s+=r,h+=d,r<u&&(u=r),r>c&&(c=r),d<a&&(a=d),d>l&&(l=d)}const p=t.length,i=(f,r,d)=>{const m=(d-r)/2;return m<=0?0:Math.abs(f/p-(r+d)/2)/m};return i(s,u,c)>.11||i(h,a,l)>.11}function ve(t,n){let e=t?.xAxisDirection==="left",u=t?.yAxisDirection==="down";return(n.type==="UL"||n.type==="UR")&&(u=!0),(n.type==="LR"||n.type==="UR")&&(e=!0),{flipX:e,flipY:u}}function ke(t,n){const{method:e,targetBin:u}=t,c=t.results.map(a=>a.filter(E));if(e==="mean"||e==="median"||e==="stddev"||e==="min"||e==="max"||e==="count"){const a=new Set((n??[]).filter(i=>!isParametricTest(i)).map(i=>i.testNumber)),l=new Set;for(const i of c)for(const f of i)if(f.testValues)for(const r of Object.keys(f.testValues)){const d=Number(r);a.has(d)||l.add(d)}if(l.size===0)return U(c,e);const s=[...l],h=s.map(i=>U(c,e,i)),p=new Map;for(let i=0;i<s.length;i++){const f=s[i];for(const r of h[i]){const d=V(r);p.has(d)||p.set(d,{template:r,testValues:{}});const m=p.get(d),g=r.testValues?.[0];g!==void 0&&(m.testValues[f]=g)}}return[...p.values()].map(({template:i,testValues:f})=>({...i,testValues:Object.keys(f).length>0?f:void 0}))}if(e==="countBin"||e==="percent"){if(u===void 0)return[];const a=be(c,u,"hard");if(e==="countBin")return a;const l=c.length;return a.map(s=>({...s,testValues:{0:l>0?(s.testValues?.[0]??0)/l*100:0}}))}if(e==="mode"){const a=new Map;for(const s of c)for(const h of s){const p=V(h);a.has(p)||a.set(p,[]),a.get(p).push(h)}const l=[];for(const[s,h]of a){const p=s.split(","),i=Number(p[0]),f=Number(p[1]),r=h.map(m=>m.hbin).filter(m=>m!==void 0),d=De(r);d!==null&&l.push({x:i,y:f,hbin:d})}return l}return[]}function J(t){const n=t.filter(E),e=n.length,u=n.filter(a=>a.edgeExcluded).length,c=n.filter(a=>dieHasTestData(a)||a.hbin!==void 0||a.sbin!==void 0).length;return{filledDies:c,totalDies:e,edgeExcludedDies:u,unpositionedDies:t.length-n.length,ratio:e>0?c/e:0}}function Q(t,n,e="exclude"){const u=new Set(n),a=t.filter(d=>!d.partial).filter(d=>d.edgeExcluded).length,l=t.filter(d=>d.partial).length;let s=0,h=0,p=!1;for(const d of t){if(!ce(d))continue;const m=d.hbin??d.sbin;m!==void 0&&(p=!0,u.has(m)?s++:h++)}const i=s+h,f=p&&i>0?s/i*100:null;let r=null;if(e==="denominator-only"){const d=i+a;r=p&&d>0?s/d*100:null}return{passDies:s,failDies:h,edgeExcludedDies:a,partialDies:l,totalDies:i,yieldPercent:f,yieldPercentGross:r}}function Z(t,n,e,u,c,a=0,l=0,s=0,h=0,p=0,i=!1,f=!1){if(!t)return[];const r=t.anchorDie??{x:0,y:0},d=xe(n,{width:t.width,height:t.height,diePitchX:u,diePitchY:c,anchorDie:{x:r.x-a,y:r.y-l},gridOrigin:{x:-s,y:-h}}),m=pe(he(i,f,n.center.x,n.center.y),ue(p,n.center.x,n.center.y));return d.filter(g=>{const v=g.width/2,D=g.height/2,b=[[g.x-v,g.y-D],[g.x+v,g.y-D],[g.x+v,g.y+D],[g.x-v,g.y+D]].map(([w,x])=>me(m,w,x)),k=Math.min(...b.map(w=>w.x)),A=Math.max(...b.map(w=>w.x)),F=Math.min(...b.map(w=>w.y)),P=Math.max(...b.map(w=>w.y));return e.some(w=>w.physX>=k&&w.physX<A&&w.physY>=F&&w.physY<P)})}function Ae(t,n,e){const c=Math.max(0,n.radius-e)**2;return t.map(a=>{const l=a.physX-n.center.x,s=a.physY-n.center.y;return l*l+s*s>c?{...a,edgeExcluded:!0}:a})}function Ce(t,n,e){const u=t.filter(E),c=t.filter(i=>!E(i)),a=new Map;for(const i of u){let f=a.get(i.x);f||(f=new Map,a.set(i.x,f)),f.set(i.y,(f.get(i.y)??0)+1)}const l=new Set(e);function s(i,f){const r=i.hbin,d=f.hbin;if(r===void 0||d===void 0)return!1;const m=l.has(r),g=l.has(d);return n==="best"?g!==m?g:d<r:g!==m?m:d>r}const h=new Map;for(const i of u){const f=V(i),r=h.get(f);n==="first"&&r||(n==="best"||n==="worst")&&r&&!s(r,i)||h.set(f,i)}return[...Array.from(h.values()).map(i=>{const r=a.get(i.x)?.get(i.y)??1;return r>1?{...i,retestCount:r}:i}),...c]}export function getDieTestValue(t,n){return t.testValues?.[n]}export function getTestPassStatus(t,n,e){const u=t.testPass?.[n];if(u!==void 0)return u;if(e!==void 0&&!isParametricTest(e)){const c=t.testValues?.[n];if(c===0||c===1)return c===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 L(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 ee(t,n){return n.plotMode?n.plotMode:t.some(dieHasTestData)?"value":"hardBin"}function te(t){return(t.warnings??[]).map(n=>{const e=Ie(n);return{code:e,message:n,severity:e==="non-standard-diameter"||e==="diameter-exceeds-die-extent"?"warning":"error",confidence:t.wafer.confidence}})}function Ie(t){return t.includes(ne)?"geometry-conflict":t.includes(ie)?"non-standard-diameter":t.includes(se)?"diameter-exceeds-die-extent":"partial-coverage"}const ne="do not fit inside the supplied",ie="Inferred wafer diameter",se="The probed dies reach only",Fe=.75,Pe=20;export const STANDARD_WAFER_DIAMETERS_MM=[100,125,150,200,300];const _e=.02;function oe(t,n){return n.reduce((e,u)=>Math.abs(u-t)<Math.abs(e-t)?u:e)}function Ye(t,n){if(!n.length)return!0;const e=oe(t,n);return Math.abs(e-t)/e<=_e}function $e(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:u,...c}=n??{},a=e.lotStackOpts?ke(e.lotStackOpts,e.testDefs):e.results;for(let o=0;o<Math.min(5,a.length);o++){const y=a[o];if(typeof y.x=="string"||typeof y.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 o of a)if(o.x==null!=(o.y==null))throw new TypeError(`buildWaferMap: die has only one of x/y set (x=${String(o.x)}, y=${String(o.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 l=Ce(a,e.retestPolicy,e.passBins),s={wafer:{confidence:1,method:"provided"},diePitch:{confidence:1,units:"mm"},grid:{confidence:1}};if(e.explicitDies){let o=e.explicitDies;const y=l.filter(E),R=l.filter(T=>!E(T));if(y.length>0){const T=new Map(y.map(S=>[V(S),S]));o=o.map(S=>{const G=T.get(V(S));return G?L(S,G):S})}const $=e.waferOpts?.diameter??300,I=j({diameter:$,notch:e.waferOpts?.notch,orientation:e.waferOpts?.orientation??0,metadata:e.waferOpts?.metadata});I.orientation!==0&&(o=q(o,I));const z=Z(e.reticleOpts,I,o,1,1,0,0,0,0,I.orientation),de=c.showReticle??e.reticleOpts!==void 0,B=H(I,o,{...c,reticles:z,showReticle:de,plotMode:ee(l,c),testDefs:e.testDefs,isLotStack:!1},{hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,metadataFields:e.metadataFields}),fe=R.map((T,S)=>L({id:`unpositioned_${S}`,width:o[0]?.width??1,height:o[0]?.height??1},T)),W=[...o,...fe];return{wafer:I,dies:W,view:B,reticleConfig:e.reticleOpts,units:"mm",inference:s,warnings:te(s),plotMode:B.plotMode,metadata:B.metadata,isLotStack:!1,dataCoverage:J(W),yield:Q(W,e.passBins,e.edgeDieYieldMode),reticles:z,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs,metadataFields:e.metadataFields}}const h=l.filter(E),p=l.filter(o=>!E(o)),i=h.map(o=>({x:o.x,y:o.y})),f=ye(i,e.dieOpts,e.waferOpts?.diameter);s.diePitch={confidence:f.confidence,units:f.units};const{pitchX:r,pitchY:d}=f,m=f.units,g=Oe(h,e.dieOpts),v=we(i);s.grid={confidence:v.confidence};const{offsetX:D,offsetY:b}=Re(i,g,v),{flipX:k,flipY:A}=ve(e.dieOpts,g),{colMidX:F,colMidY:P,anchored:w}=Se(i,D,b,r,d,e.waferOpts?.center);let x=e.waferOpts?.diameter;const _=i.map(o=>({x:(Math.round(o.x)-D)*r-F,y:(Math.round(o.y)-b)*d-P})),C=_.length>0?Math.max(..._.map(({x:o,y})=>Math.hypot(Math.abs(o)+r/2,Math.abs(y)+d/2))):0;if(x===void 0)if(i.length>0)if(m==="mm"){const o=ge(_,{minRadius:C});x=o.diameter,s.wafer={confidence:o.confidence,method:o.method};const y=$e(e.standardDiameters);if(!Ye(x,y)){const R=oe(x,y);(s.warnings??=[]).push(`${ie} ${x.toFixed(1)} mm is not a standard wafer size (nearest is ${R} 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 x=C*2,s.wafer={confidence:f.confidence*.8,method:"extent"};else x=m==="mm"?300:30,s.wafer={confidence:0,method:"default"};if(!w&&i.length>0&&Ee(i,D,b)&&(s.wafer.method="inferred-partial",(s.warnings??=[]).push("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&&C>0&&i.length>=Pe){const o=C/(x/2);o<Fe&&(s.warnings??=[]).push(`${se} ${(o*100).toFixed(0)}% of the ${x} 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 o=e.dieOpts?.width!==void 0&&e.dieOpts?.height!==void 0;if(o&&C>x/2+1e-9){const y=_.filter(({x:R,y:$})=>Math.hypot(Math.abs(R)+r/2,Math.abs($)+d/2)>x/2+1e-9).length;(s.warnings??=[]).push(`${y} of ${_.length} probed die positions ${ne} ${x} mm wafer at the supplied die pitch of ${r} \xD7 ${d} 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 ${(C*2).toFixed(1)} mm. Check waferConfig.diameter and dieConfig.width/height against the real device.`)}}const O=j({diameter:x,notch:e.waferOpts?.notch,orientation:e.waferOpts?.orientation??0,metadata:e.waferOpts?.metadata});let M=h.map(o=>{const y=Math.round(o.x)-D,R=Math.round(o.y)-b,$={id:`${y}_${R}`,x:y,y:R,physX:y*r-F,physY:R*d-P,width:r,height:d,insideWafer:!0,partial:!1};return L($,o)});(D!==0||b!==0)&&(M=M.map(o=>({...o,x:o.x+D,y:o.y+b,id:`${o.x+D}_${o.y+b}`}))),M=q(M,O),(k||A)&&(M=le(M,{flipX:k,flipY:A},O.center));const X=[];e.waferOpts?.edgeExclusion&&e.waferOpts.edgeExclusion>0&&(M=Ae(M,O,e.waferOpts.edgeExclusion),e.waferOpts.edgeExclusion>O.radius&&X.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 K=Z(e.reticleOpts,O,M,r,d,D,b,F,P,O.orientation,k,A),re=c.showReticle??e.reticleOpts!==void 0,Y=H(O,M,{...c,reticles:K,showReticle:re,plotMode:ee(l,c),testDefs:e.testDefs,dataAxisFlip:{x:k,y:A},isLotStack:e.lotStackOpts!==void 0,aggregationMethod:e.lotStackOpts?.method,lotSize:e.lotStackOpts?.results.length},{hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,metadataFields:e.metadataFields}),ae=p.map((o,y)=>L({id:`unpositioned_${y}`,width:r,height:d},o)),N=[...M,...ae];return{wafer:O,dies:N,view:Y,reticleConfig:e.reticleOpts,units:m,inference:s,warnings:[...te(s),...X],plotMode:Y.plotMode,metadata:Y.metadata,isLotStack:e.lotStackOpts!==void 0,dataCoverage:J(N),yield:Q(N,e.passBins,e.edgeDieYieldMode),reticles:K,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs,metadataFields:e.metadataFields,aggrMethod:Y.aggrMethod,lotSize:Y.lotSize}}
|
|
@@ -6,24 +6,29 @@ export declare const SPEC_PASS_FILL = "#2ecc71";
|
|
|
6
6
|
export declare const SPEC_FAIL_LOW = "#3498db";
|
|
7
7
|
export declare const SPEC_FAIL_HIGH = "#e74c3c";
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
* Fill for a die with no value for what is being plotted — no bin in a bin
|
|
10
|
+
* mode, no reading in a value mode. Shared by the map and every surface that
|
|
11
|
+
* draws a "no data" swatch, so the two can never disagree.
|
|
12
|
+
*/
|
|
13
|
+
export declare const NO_DATA_FILL = "#d6d9dd";
|
|
14
|
+
/**
|
|
15
|
+
* Categorical fallback palette for `'metadata'` mode values beyond
|
|
16
|
+
* METADATA_PALETTE's fixed slots. Index 0 is a no-data grey sentinel.
|
|
17
|
+
* Indices 1–63 are perceptually spread colours generated via golden-angle
|
|
18
|
+
* HSL stepping.
|
|
19
|
+
*
|
|
20
|
+
* No longer used for bins: bin colours are rank-assigned from a registered bin
|
|
21
|
+
* palette by `resolveBinColors` (binColors.ts). Hashing a bin number into this
|
|
22
|
+
* list is what made two different bins share a colour.
|
|
14
23
|
*/
|
|
15
24
|
export declare const BIN_PALETTE: readonly string[];
|
|
16
25
|
/** Wang hash — maps any integer to a well-distributed unsigned 32-bit value. Shared with colorSchemes. */
|
|
17
26
|
export declare function wangHash(n: number): number;
|
|
18
|
-
/** Categorical colour for a hard bin. No-data handling is the caller's responsibility. */
|
|
19
|
-
export declare function hardBinColor(bin: number): string;
|
|
20
27
|
/** Linear interpolation across RGB keypoints for t ∈ [0, 1]. */
|
|
21
28
|
export declare function lerpKp(kp: readonly [number, number, number][], t: number): string;
|
|
22
29
|
export declare const VIRIDIS: readonly [number, number, number][];
|
|
23
30
|
/** Map t ∈ [0, 1] to a Viridis RGB colour string. */
|
|
24
31
|
export declare function valueToViridis(t: number): string;
|
|
25
|
-
/** Categorical colour for a soft bin. No-data handling is the caller's responsibility. */
|
|
26
|
-
export declare function softBinColor(bin: number): string;
|
|
27
32
|
/**
|
|
28
33
|
* Categorical colour for the `index`-th distinct value of an active
|
|
29
34
|
* `'metadata'` field, where `index` comes from sorting the field's distinct
|
|
@@ -31,14 +36,10 @@ export declare function softBinColor(bin: number): string;
|
|
|
31
36
|
* iteration order). Ordered assignment, not hashing, for the first
|
|
32
37
|
* `METADATA_PALETTE.length` slots — this maximizes distinctness for the
|
|
33
38
|
* common case of a handful of categories, unlike a hash which doesn't
|
|
34
|
-
* optimize for a *known* small set. Falls back to
|
|
35
|
-
*
|
|
36
|
-
* salt) for wafers with an unusually large category count.
|
|
39
|
+
* optimize for a *known* small set. Falls back to hashing into `BIN_PALETTE`
|
|
40
|
+
* for wafers with an unusually large category count.
|
|
37
41
|
*/
|
|
38
42
|
export declare function metadataValueColor(index: number): string;
|
|
39
|
-
/** Categorical greyscale shades for hard bins. Index 0 = no data. */
|
|
40
|
-
export declare const HARD_BIN_GREY: readonly string[];
|
|
41
|
-
export declare function hardBinGreyscale(bin: number): string;
|
|
42
43
|
/** Map t ∈ [0, 1] to a greyscale rgb string (range 30–230 to avoid pure black/white). */
|
|
43
44
|
export declare function valueToGreyscale(t: number): string;
|
|
44
45
|
/** Return '#000000' or '#ffffff' for maximum contrast against the given colour. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{clamp01 as
|
|
1
|
+
import{clamp01 as s}from"../core/utils.js";export const SPEC_PASS_FILL="#2ecc71",SPEC_FAIL_LOW="#3498db",SPEC_FAIL_HIGH="#e74c3c",NO_DATA_FILL="#d6d9dd",BIN_PALETTE=["#95a5a6","#1eb84b","#932cdd","#b8a51e","#2cbfdd","#b81e71","#58dd2c","#251eb8","#dd672c","#1eb87f","#ce2cdd","#98b81e","#2c84dd","#b81e3e","#2cdd3b","#581eb8","#dda22c","#1eb8b2","#dd2cb0","#64b81e","#2c49dd","#b8321e","#2cdd76","#8c1eb8","#dcdd2c","#1e8bb8","#dd2c75","#31b81e","#4b2cdd","#b8651e","#2cddb1","#b81eb1","#a1dd2c","#1e57b8","#dd2c3a","#1eb83f","#852cdd","#b8991e","#2ccddd","#b81e7e","#66dd2c","#1e24b8","#dd5a2c","#1eb872","#c02cdd","#a4b81e","#2c92dd","#b81e4a","#2cdd2e","#4c1eb8","#dd942c","#1eb8a6","#dd2cbe","#70b81e","#2c57dd","#b8261e","#2cdd69","#801eb8","#ddcf2c","#1e97b8","#dd2c83","#3db81e","#3d2cdd","#b8591e"];export function wangHash(e){let c=e|0;return c=Math.imul(c^c>>>16,73244475),c=Math.imul(c^c>>>16,73244475),(c^c>>>16)>>>0}const u=BIN_PALETTE.length-1;export function lerpKp(e,c){const n=s(c)*(e.length-1),d=Math.floor(n),t=Math.min(d+1,e.length-1),a=n-d,b=Math.round(e[d][0]+a*(e[t][0]-e[d][0])),o=Math.round(e[d][1]+a*(e[t][1]-e[d][1])),l=Math.round(e[d][2]+a*(e[t][2]-e[d][2]));return`rgb(${b},${o},${l})`}export const VIRIDIS=[[68,1,84],[59,82,139],[33,145,140],[94,201,98],[253,231,37]];export function valueToViridis(e){return lerpKp(VIRIDIS,e)}const f=["#4e79a7","#f28e2b","#e15759","#76b7b2","#59a14f","#edc948","#b07aa1","#ff9da7","#9c755f","#bab0ac"],i=668265263;export function metadataValueColor(e){return e<f.length?f[e]:BIN_PALETTE[wangHash(e^i)%u+1]}export function valueToGreyscale(e){const c=Math.round(s(e)*200+30);return`rgb(${c},${c},${c})`}export function contrastTextColor(e){let c=0,r=0,n=0;const d=e.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);if(d)c=+d[1],r=+d[2],n=+d[3];else{const b=e.replace("#","");c=parseInt(b.slice(0,2),16),r=parseInt(b.slice(2,4),16),n=parseInt(b.slice(4,6),16)}const t=b=>{const o=b/255;return o<=.03928?o/12.92:((o+.055)/1.055)**2.4};return .2126*t(c)+.7152*t(r)+.0722*t(n)>.179?"#000000":"#ffffff"}
|
|
@@ -1,39 +1,58 @@
|
|
|
1
|
-
export interface
|
|
2
|
-
/** Human-readable display name */
|
|
1
|
+
export interface ValueColorScheme {
|
|
2
|
+
/** Human-readable display name. */
|
|
3
3
|
label: string;
|
|
4
4
|
/**
|
|
5
|
-
* Return a CSS colour string for a
|
|
6
|
-
*
|
|
7
|
-
*/
|
|
8
|
-
forBin: (bin: number) => string;
|
|
9
|
-
/**
|
|
10
|
-
* Return a CSS colour string for a continuous value t ∈ [0, 1].
|
|
11
|
-
* Values are pre-normalized by buildView before this is called.
|
|
5
|
+
* Return a CSS colour string for a continuous value t ∈ [0, 1]. Values are
|
|
6
|
+
* pre-normalized by buildView before this is called.
|
|
12
7
|
*/
|
|
13
8
|
forValue: (t: number) => string;
|
|
14
9
|
}
|
|
15
10
|
/**
|
|
16
|
-
* Register a named
|
|
17
|
-
*
|
|
11
|
+
* Register a named value gradient, making it selectable via the
|
|
12
|
+
* `valueColorScheme` option and listed in the toolbar for value and stacked
|
|
13
|
+
* modes. Call once at startup, before rendering.
|
|
18
14
|
*
|
|
19
|
-
* `
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* @example
|
|
23
|
-
* registerColorScheme('my-brand', {
|
|
24
|
-
* label: 'My Brand',
|
|
25
|
-
* forBin: (bin) => MY_BRAND_BINS[bin] ?? '#ccc',
|
|
26
|
-
* forValue: (t) => `hsl(${200 + t * 60}, 70%, ${30 + t * 40}%)`,
|
|
27
|
-
* });
|
|
15
|
+
* `forValue` must return a valid CSS colour — an invalid string renders as a
|
|
16
|
+
* silent blank or black die.
|
|
28
17
|
*/
|
|
29
|
-
export declare function
|
|
18
|
+
export declare function registerValueColorScheme(name: string, scheme: ValueColorScheme): void;
|
|
19
|
+
/** Retrieve a value gradient by name. Falls back to `'default'`, never undefined. */
|
|
20
|
+
export declare function getValueColorScheme(name?: string): ValueColorScheme;
|
|
21
|
+
/** All registered value gradients as `{ name, label }`, in registration order. */
|
|
22
|
+
export declare function listValueColorSchemes(): Array<{
|
|
23
|
+
name: string;
|
|
24
|
+
label: string;
|
|
25
|
+
}>;
|
|
26
|
+
export interface BinColorScheme {
|
|
27
|
+
/** Human-readable display name. */
|
|
28
|
+
label: string;
|
|
29
|
+
/**
|
|
30
|
+
* Colours for passing bins, handed out most-populous bin first. Convention is
|
|
31
|
+
* greens: an engineer reads green as pass before reading the legend.
|
|
32
|
+
*/
|
|
33
|
+
pass: readonly string[];
|
|
34
|
+
/**
|
|
35
|
+
* Colours for failing bins, handed out most-populous bin first, so put the
|
|
36
|
+
* most distinct colours at the front. Must not resemble the pass colours —
|
|
37
|
+
* a fail bin that reads as a pass is the most expensive misreading a wafer
|
|
38
|
+
* map can produce.
|
|
39
|
+
*/
|
|
40
|
+
fail: readonly string[];
|
|
41
|
+
}
|
|
30
42
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
43
|
+
* Register a named bin palette, making it selectable via the `binColorScheme`
|
|
44
|
+
* option and listed in the toolbar for hard/soft-bin modes. Call once at
|
|
45
|
+
* startup, before rendering.
|
|
46
|
+
*
|
|
47
|
+
* Throws on an empty `pass` or `fail` list: there would be nothing to colour
|
|
48
|
+
* that category with, and the failure is better at registration than as
|
|
49
|
+
* uncoloured dies later.
|
|
33
50
|
*/
|
|
34
|
-
export declare function
|
|
35
|
-
/**
|
|
36
|
-
export declare function
|
|
51
|
+
export declare function registerBinColorScheme(name: string, scheme: BinColorScheme): void;
|
|
52
|
+
/** Retrieve a bin palette by name. Falls back to `'default'`, never undefined. */
|
|
53
|
+
export declare function getBinColorScheme(name?: string): BinColorScheme;
|
|
54
|
+
/** All registered bin palettes as `{ name, label }`, in registration order. */
|
|
55
|
+
export declare function listBinColorSchemes(): Array<{
|
|
37
56
|
name: string;
|
|
38
57
|
label: string;
|
|
39
58
|
}>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{VIRIDIS as o,lerpKp as a,valueToGreyscale as i}from"./colorMap.js";const r=new Map;export function registerValueColorScheme(e,l){r.set(e,l)}export function getValueColorScheme(e){return r.get(e??"default")??r.get("default")}export function listValueColorSchemes(){return[...r.entries()].map(([e,l])=>({name:e,label:l.label}))}const t=new Map;export function registerBinColorScheme(e,l){if(!l.pass.length||!l.fail.length)throw new Error(`registerBinColorScheme('${e}'): 'pass' and 'fail' must each list at least one colour`);t.set(e,l)}export function getBinColorScheme(e){return t.get(e??"default")??t.get("default")}export function listBinColorSchemes(){return[...t.entries()].map(([e,l])=>({name:e,label:l.label}))}const n=[[0,0,255],[0,255,255],[255,255,0],[255,0,0]];registerValueColorScheme("default",{label:"Default (Blue\u2013Cyan\u2013Yellow\u2013Red)",forValue:e=>a(n,e)}),registerValueColorScheme("viridis",{label:"Viridis",forValue:e=>a(o,1-e)});const f=[[0,32,77],[54,68,130],[107,107,145],[180,154,108],[253,228,32]];registerValueColorScheme("cividis",{label:"Cividis (colour-blind safe)",forValue:e=>a(f,1-e)}),registerValueColorScheme("greyscale",{label:"Greyscale",forValue:e=>i(1-e)});const s=[[13,8,135],[126,3,168],[204,71,120],[248,149,64],[240,249,33]];registerValueColorScheme("plasma",{label:"Plasma",forValue:e=>a(s,1-e)});const c=[[0,0,4],[87,16,110],[188,55,84],[249,142,9],[252,255,164]];registerValueColorScheme("inferno",{label:"Inferno",forValue:e=>a(c,1-e)});const u=[[46,204,113],[241,196,15],[231,76,60]];registerValueColorScheme("traffic",{label:"Traffic (Green\u2013Yellow\u2013Red)",forValue:e=>a(u,e)});const b=[[0,0,128],[0,0,255],[0,255,255],[255,255,0],[255,0,0],[128,0,0]];registerValueColorScheme("jet",{label:"Jet (MATLAB rainbow)",forValue:e=>a(b,e)}),registerBinColorScheme("default",{label:"Default",pass:["#2ca02c","#98df8a","#1b5e20"],fail:["#d62728","#332288","#e69f00","#000000","#0072b2","#654522","#ff9d9a","#aa3377","#499894","#e377c2","#f0e442","#225555","#79706e","#33bbee","#ee7733","#999933","#9467bd","#663333","#cc6677"]}),registerBinColorScheme("accessible",{label:"Colour-blind safe",pass:["#009e73","#44bb99"],fail:["#d55e00","#000000","#604e97","#882d17","#f3c300","#222255","#225555","#a1caf1","#9467bd","#8c564b","#b07aa1","#eedd88","#f38400","#663333"]});
|
|
@@ -2,6 +2,8 @@ export { buildView, getDieKey, buildHoverText, buildMapTitle, } from './buildVie
|
|
|
2
2
|
export type { View, MapTitleParts, PlotMode, ViewRect, ViewText, ViewHoverPoint, ViewOverlay, ViewOptions, } from './buildView.js';
|
|
3
3
|
export * from './buildWaferMap.js';
|
|
4
4
|
export type { WaferMetadata, DieMetadata } from '../core/metadata.js';
|
|
5
|
-
export {
|
|
5
|
+
export { valueToViridis, valueToGreyscale, contrastTextColor, } from './colorMap.js';
|
|
6
6
|
export * from './colorSchemes.js';
|
|
7
|
+
export { resolveBinColors } from './binColors.js';
|
|
8
|
+
export type { BinColors, BinColorOptions } from './binColors.js';
|
|
7
9
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export{buildView,getDieKey,buildHoverText,buildMapTitle}from"./buildView.js";export*from"./buildWaferMap.js";export{
|
|
1
|
+
export{buildView,getDieKey,buildHoverText,buildMapTitle}from"./buildView.js";export*from"./buildWaferMap.js";export{valueToViridis,valueToGreyscale,contrastTextColor}from"./colorMap.js";export*from"./colorSchemes.js";export{resolveBinColors}from"./binColors.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{analyzeWaferMap as h}from"./analyzeWaferMap.js";import{median as y}from"../core/utils.js";const
|
|
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 +1 @@
|
|
|
1
|
-
import{isYieldEligibleDie as ee,getDieKey as x,isPositionedDie as de}from"../core/dies.js";import{buildWaferMap as Re,getTestPassStatus as ge,isParametricTest as j}from"../renderer/buildWaferMap.js";import{buildQuadrantRegions as Be,buildReticlePositionRegions as Ce,buildRingRegions as Fe,buildSectorRegions as Ne,buildTestSiteRegions as Pe,sectorCompassNames as me,areQuadrantsAdjacent as Ve,parseRegionKey as H}from"./regions.js";import{buildClusterFindings as Le}from"./clusterDetection.js";import{classifyPattern as Ee}from"./patternClassification.js";import{normalCdf as he}from"./math.js";import{quantile as te}from"./math.js";import{mean as X,clamp01 as be}from"../core/utils.js";const Ae={significanceLevel:{min:1e-6,max:1},minimumEffectSize:{min:0,max:1},minimumRelativeEffect:{min:0,max:Number.MAX_SAFE_INTEGER},ringCount:{min:1,max:Number.MAX_SAFE_INTEGER}},pe=[4,8,16,32];function Ke(n){const s={..._,...n},a=[],o=[];for(const e of["significanceLevel","minimumEffectSize","minimumRelativeEffect","ringCount"]){const{min:c,max:l}=Ae[e],r=s[e];if(typeof r!="number"||!Number.isFinite(r)){o.push(`${e}=${String(r)} is not a finite number (using ${_[e]})`),s[e]=_[e];continue}const i=Math.min(l,Math.max(c,r));i!==r&&(o.push(`${e}=${r} is outside ${c}\u2013${l} (using ${i})`),s[e]=i)}if(!Number.isInteger(s.ringCount)){const e=Math.max(1,Math.round(s.ringCount));o.push(`ringCount=${s.ringCount} is not a whole number (using ${e})`),s.ringCount=e}if(pe.includes(s.sectorCount)||(o.push(`sectorCount=${s.sectorCount} is not one of ${pe.join(", ")} (using ${_.sectorCount})`),s.sectorCount=_.sectorCount),o.length){const e=`analyzeWaferMap: ${o.join("; ")}. The analysis ran with the corrected values.`;console.warn(`[wafermap] ${e}`),a.push({code:"analysis-option-corrected",message:e,severity:"warning"})}return{resolved:s,warnings:a}}const _={ringCount:4,passBins:[1],significanceLevel:.05,minimumEffectSize:.2,minimumRelativeEffect:1,minimumSampleSize:5,includePartial:!1,includeEdgeExcluded:!1,enableYieldAnalysis:!0,enableHardBinAnalysis:!0,enableSoftBinAnalysis:!0,enableTestValueAnalysis:!1,computePerTestStats:!1,enableReticlePositionAnalysis:!0,enableClusterAnalysis:!0,enableAngularAnalysis:!0,sectorCount:8,enablePatternClassification:!0,minimumClusterSize:5};function xe(n,s){const a={...n};return a.minimumClusterSize=Math.max(5,Math.round(s*.003)),a}function Te(n){return"wafer"in n&&"dies"in n&&"view"in n?n:Re(n)}function je(n,s){return ee(n,s)?n.hbin!==void 0||n.sbin!==void 0||n.testValues!==void 0&&Object.keys(n.testValues).length>0:!1}function He(n,s,a){if(!n||s)return;const o=(a??[]).filter(e=>j(e)&&(e.limitLow!==void 0||e.limitHigh!==void 0));if(o.length!==0)return e=>{for(const c of o){const l=c.testNumber,r=e.testValues?.[l];if(r!==void 0&&(c.limitLow!==void 0&&r<c.limitLow||c.limitHigh!==void 0&&r>c.limitHigh))return!0}return!1}}function qe(n,s,a){const o=new Set,e=new Set,c=new Set,l=new Map,r=new Map;for(const i of n){if(i.testValues)for(const f of Object.keys(i.testValues))o.add(Number(f));if(i.testPass)for(const f of Object.keys(i.testPass))o.add(Number(f));i.hbin!==void 0&&e.add(i.hbin),i.sbin!==void 0&&c.add(i.sbin),ee(i)&&(i.hbin!==void 0&&l.set(i.hbin,(l.get(i.hbin)??0)+1),i.sbin!==void 0&&r.set(i.sbin,(r.get(i.sbin)??0)+1))}return{totalDies:n.length,analyzedDies:s,excludedDies:n.length-s,yieldPercent:a,testsConsidered:[...o].sort((i,f)=>i-f),hardBinsConsidered:[...e].sort((i,f)=>i-f),softBinsConsidered:[...c].sort((i,f)=>i-f),...l.size?{hardBinCounts:Object.fromEntries(l)}:{},...r.size?{softBinCounts:Object.fromEntries(r)}:{}}}function Oe(n,s){if(!s?.length)return;const a=s.filter(e=>j(e)&&(e.limitLow!==void 0||e.limitHigh!==void 0));if(!a.length)return;const o=[];for(const e of a){const c=e.testNumber;let l=0,r=0,i=0,f=0;for(const t of n){if(t.partial||t.edgeExcluded)continue;const h=t.testValues?.[c];h!==void 0&&(f++,e.limitLow!==void 0&&h<e.limitLow?r++:e.limitHigh!==void 0&&h>e.limitHigh?i++:l++)}o.push({testNumber:c,label:e.name,passDies:l,failLowDies:r,failHighDies:i,totalDies:f,yieldPercent:f>0?l/f*100:null})}return o.length?o:void 0}export function computeFunctionalYield(n,s){if(!s?.length)return;const a=s.filter(e=>!j(e));if(!a.length)return;const o=[];for(const e of a){const c=e.testNumber;let l=0,r=0;for(const f of n){if(f.partial||f.edgeExcluded)continue;const t=ge(f,c,e);t!==void 0&&(t?l++:r++)}const i=l+r;o.push({testNumber:c,label:e.name,passDies:l,failDies:r,totalDies:i,passRatePercent:i>0?l/i*100:null})}return o.length?o:void 0}function Ie(n,s,a,o){const e=[];for(const c of s){const l=[];for(const h of n){if(h.partial||h.edgeExcluded)continue;const g=h.testValues?.[c];g!==void 0&&l.push(g)}if(l.length<o)continue;const r=X(l),i=Math.sqrt(le(l,r)),f=l.slice().sort((h,g)=>h-g),t=a?.find(h=>h.testNumber===c)?.name??String(c);e.push({testNumber:c,label:t,count:l.length,min:f[0],max:f[f.length-1],mean:r,stddev:i,median:te(f,.5),q1:te(f,.25),q3:te(f,.75)})}return e.length?e:void 0}function q(n,s,a,o){const e=(n+a)/(s+o),c=e*(1-e)*(1/s+1/o);if(!Number.isFinite(c)||c<=0)return 1;const l=(n/s-a/o)/Math.sqrt(c);return be(2*(1-he(Math.abs(l))))}function ne(n){const s=new Map;for(const a of n){const o=`${a.variable.kind}:${a.comparison.family}`,e=s.get(o)??[];e.push(a),s.set(o,e)}for(const a of s.values()){const o=[...a].filter(l=>l.stats.pValue!==void 0).sort((l,r)=>(l.stats.pValue??1)-(r.stats.pValue??1)),e=o.length;let c=1;for(let l=e-1;l>=0;l--){const r=o[l],i=r.stats.pValue??1,f=Math.min(1,Math.min(c,i*e/(l+1)));r.stats.adjustedPValue=f,c=f}}return n}function ie(n,s,a){const o=Math.abs(s),e=a!==void 0?Math.abs(a):0;return n<=.01&&(o>=.3||e>=2.5)?"unusual":n<=.05&&(o>=.2||e>=1.5)?"notable":"info"}function se(n,s){return n<=.01&&Math.abs(s)>=.5?"unusual":n<=.05&&Math.abs(s)>=.15?"notable":"info"}function J(n){return n==="reticle-position"?"other reticle positions":n==="test-site"?"other test sites":"the rest of the map"}function O(n){return n==="reticle-position"?"Other reticle positions":n==="test-site"?"Other test sites":"Rest of map"}function ye(n,s,a){const o=(Math.abs(s)*100).toFixed(1);return`${n} yield is ${o} percentage points ${s>0?"higher":"lower"} than ${J(a)}`}function oe(n,s){return s==="quadrant"&&!n.startsWith("Quadrant")?`quadrant ${n}`:n}function Se(n,s,a,o){const e=oe(n,o),c=(Math.abs(a)*100).toFixed(1);return`${e} has ${s} occurrence ${c} percentage points ${a>0?"higher":"lower"} than ${J(o)}`}function _e(n,s,a,o){const e=oe(n,o),c=(Math.abs(a)*100).toFixed(1);return`${e} has ${s} pass rate ${c} percentage points ${a>0?"higher":"lower"} than ${J(o)}`}function we(n,s,a,o,e,c){const l=oe(n,e),r=J(e),i=a>0?"higher":"lower";if(o!==void 0&&Number.isFinite(o)){const t=(Math.abs(o)*100).toFixed(1);return`${l} mean ${s} is ${t}% ${i} than ${r}`}const f=c?` ${c}`:"";return`${l} mean ${s} is ${Math.abs(a).toPrecision(3)}${f} ${i} than ${r}`}function $e(n,s,a){const o=s?.find(e=>e.bin===n);return o?.name?`${a} ${n} (${o.name})`:`${a} ${n}`}function Ye(n,s){const a=s?.find(o=>o.testNumber===n);return{label:a?.name??`Test ${n}`,unit:a?.unit}}function ae(n,s){const a=new Map(n.map(e=>[x(e),e])),o=new Map;for(const e of s){const c=[];for(const l of e.dieKeys){const r=a.get(l);r&&c.push(r)}o.set(e.key,c)}return o}function re(n,s){return ne(n),n.filter(a=>{const o=a.stats.adjustedPValue??a.stats.pValue??1,e=Math.abs(a.effect.absoluteDelta??0),c=Math.abs(a.effect.relativeDelta??0);return o<=s.significanceLevel&&(e>=s.minimumEffectSize||c>=s.minimumRelativeEffect)}).map(a=>({...a,severity:ie(a.stats.adjustedPValue??a.stats.pValue??1,a.effect.absoluteDelta??0,a.effect.relativeDelta)}))}function Y(n,s,a,o){const e=new Set(a),c=ae(n,s),l=new Map,r=new Map;for(const[f,t]of c){let h=0,g=0;for(const d of t)d.hbin!==void 0&&(g++,e.has(d.hbin)&&h++);l.set(f,h),r.set(f,g)}const i=[];for(const f of s){const t=r.get(f.key)??0,h=l.get(f.key)??0;let g=0,d=0;for(const[y,b]of r)y!==f.key&&(g+=b,d+=l.get(y)??0);if(t<o.minimumSampleSize||g<o.minimumSampleSize)continue;const m=h/t,p=d/g,S=m-p,D=q(h,t,d,g);i.push({id:`yield:${f.key}`,level:"wafer",severity:"info",variable:{kind:"yield",label:"Yield"},comparison:{family:f.family,left:f.label,right:O(f.family)},effect:{direction:S===0?"different":S>0?"higher":"lower",absoluteDelta:S,relativeDelta:p===0?void 0:S/p,effectSize:S},stats:{method:"two-proportion-z",pValue:D,sampleSizeLeft:t,sampleSizeRight:g},summary:ye(f.label,S,f.family),highlight:{kind:"region",regionFamily:f.family,regionKeys:[f.key],dieKeys:[...f.dieKeys]}})}return re(i,o)}function V(n,s,a,o,e,c){const l=d=>a==="soft"?d.sbin:d.hbin,r=[...new Set(n.map(l).filter(d=>d!==void 0))].sort((d,m)=>d-m),i=ae(n,s),f=new Map,t=new Map;for(const[d,m]of i){const p=new Map;for(const S of m){const D=l(S);D!==void 0&&p.set(D,(p.get(D)??0)+1)}f.set(d,p),t.set(d,m.length)}const h=[],g=e==="hardBin"?"HBin":"SBin";for(const d of s){const m=t.get(d.key)??0,p=f.get(d.key);let S=0;const D=new Map;for(const[y,b]of f)if(y!==d.key){S+=t.get(y)??0;for(const[$,k]of b)D.set($,(D.get($)??0)+k)}if(!(m<c.minimumSampleSize||S<c.minimumSampleSize))for(const y of r){const b=p.get(y)??0,$=D.get(y)??0,k=b/m,w=$/S,v=k-w,R=q(b,m,$,S),u=$e(y,o,g);h.push({id:`${e}:${y}:${d.key}`,level:"wafer",severity:"info",variable:{kind:e,bin:y,label:u},comparison:{family:d.family,left:d.label,right:O(d.family)},effect:{direction:v===0?"different":v>0?"higher":"lower",absoluteDelta:v,relativeDelta:w===0?void 0:v/w,effectSize:v},stats:{method:"two-proportion-z",pValue:R,sampleSizeLeft:m,sampleSizeRight:S},summary:Se(d.label,u,v,d.family),highlight:{kind:"bin",bin:y,regionKeys:[d.key],dieKeys:[...d.dieKeys]}})}}return re(h,c)}function W(n,s,a,o){const e=(a??[]).filter(t=>!j(t));if(!e.length)return[];const c=e.map(t=>t.testNumber),l=ae(n,s),r=new Map,i=new Map;for(const[t,h]of l){const g=new Uint32Array(e.length),d=new Uint32Array(e.length);for(const m of h)for(let p=0;p<e.length;p++){const S=ge(m,c[p],e[p]);S!==void 0&&(d[p]++,S&&g[p]++)}r.set(t,g),i.set(t,d)}const f=[];for(const t of s){const h=r.get(t.key),g=i.get(t.key);for(let d=0;d<e.length;d++){let m=0,p=0;for(const[v,R]of i)v!==t.key&&(p+=R[d],m+=r.get(v)[d]);const S=g[d],D=h[d];if(S<o.minimumSampleSize||p<o.minimumSampleSize)continue;const y=D/S,b=m/p,$=y-b,k=q(D,S,m,p),w=c[d];f.push({id:`functional:${w}:${t.key}`,level:"wafer",severity:"info",variable:{kind:"functionalTest",index:w,label:`${e[d].name} pass rate`},comparison:{family:t.family,left:t.label,right:O(t.family)},effect:{direction:$===0?"different":$>0?"higher":"lower",absoluteDelta:$,relativeDelta:b===0?void 0:$/b,effectSize:$},stats:{method:"two-proportion-z",pValue:k,sampleSizeLeft:S,sampleSizeRight:p},summary:_e(t.label,e[d].name,$,t.family),highlight:{kind:"region",regionFamily:t.family,regionKeys:[t.key],dieKeys:[...t.dieKeys]}})}}return re(f,o)}function le(n,s){return n.length<2?0:n.reduce((a,o)=>a+(o-s)**2,0)/(n.length-1)}function ve(n,s,a,o,e,c){const l=Math.sqrt(a/n+c/o),r=s-e;if(!Number.isFinite(l)||l===0)return{pValue:1,effectSize:0,delta:r};const i=r/l,f=Math.sqrt(Math.max(0,(a+c)/2)),t=f===0?r:r/f;return{pValue:be(2*(1-he(Math.abs(i)))),effectSize:t,delta:r}}function We(n,s){const a=X(n),o=X(s);return ve(n.length,a,le(n,a),s.length,o,le(s,o))}const De=250;function ke(n,s,a){const o=l=>l.filter(r=>j(a?.find(i=>i.testNumber===r)));if(s)return{testNumbers:o(s.slice().sort((l,r)=>l-r))};const e=new Set;let c=!1;e:for(const l of n)for(const r of Object.keys(l.testValues??{})){const i=Number(r);if(!e.has(i)&&(e.add(i),e.size>De)){c=!0;break e}}if(c){const l=`Test-value analysis was skipped: more than ${De} tests were found in the die data, so no test findings were computed. Pass testNumbers: [...] to analyse specific tests.`;return console.warn(`[wafermap] analyzeWaferMap: ${l}`),{testNumbers:[],warning:{code:"test-count-capped",message:l,severity:"warning"}}}return{testNumbers:o([...e].sort((l,r)=>l-r))}}function Q(n,s,a,o){const e=ke(n,o.testNumbers,a);if(e.warning)return{findings:[],warning:e.warning};const c=e.testNumbers,l=new Map;for(let m=0;m<s.length;m++)for(const p of s[m].dieKeys)l.set(p,m);const r=s.length,i=[],f=[];for(const m of n){const p=l.get(x(m));p!==void 0&&(i.push(m),f.push(p))}const t=[],h=new Float64Array(r),g=new Float64Array(r),d=new Float64Array(r);for(const m of c){h.fill(0),g.fill(0),d.fill(0);let p=0,S=0,D=0,y;for(let b=0;b<i.length;b++){const k=i[b].testValues?.[m];if(k===void 0)continue;y===void 0&&(y=k);const w=k-y,v=f[b];h[v]+=1,g[v]+=w,d[v]+=w*w,p+=1,S+=w,D+=w*w}if(y!==void 0)for(let b=0;b<r;b++){const $=h[b],k=p-$;if($<o.minimumSampleSize||k<o.minimumSampleSize)continue;const w=g[b]/$,R=(S-g[b])/k,u=w+y,M=R+y,B=$>1?Math.max(0,d[b]-$*w*w)/($-1):0,z=k>1?Math.max(0,D-d[b]-k*R*R)/(k-1):0,{pValue:F,effectSize:N,delta:E}=ve($,u,B,k,M,z),P=s[b],{label:U,unit:G}=Ye(m,a),A=M!==0?E/Math.abs(M):void 0;t.push({id:`test:${m}:${P.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:m,label:U,unit:G},comparison:{family:P.family,left:P.label,right:O(P.family)},effect:{direction:E===0?"different":E>0?"higher":"lower",absoluteDelta:E,relativeDelta:A,effectSize:N},stats:{method:"welch-z-approx",pValue:F,sampleSizeLeft:$,sampleSizeRight:k},summary:we(P.label,U,E,A,P.family,G),highlight:{kind:"region",regionFamily:P.family,regionKeys:[P.key],dieKeys:[...P.dieKeys]}})}}return ne(t),{activeTestNumbers:c,findings:t.filter(m=>{const p=m.stats.adjustedPValue??m.stats.pValue??1,S=Math.abs(m.effect.effectSize??0);return p<=o.significanceLevel&&S>=o.minimumEffectSize}).map(m=>({...m,severity:se(m.stats.adjustedPValue??m.stats.pValue??1,m.effect.effectSize??0)}))}}function Qe(n,s,a,o){if(!a?.length)return[];const e=a.filter(r=>j(r)&&(r.limitLow!==void 0||r.limitHigh!==void 0));if(!e.length)return[];const c=[],l=new Map(n.map(r=>[x(r),r]));for(const r of e){const i=r.testNumber;for(const f of s){const t=new Map;for(const g of f){const d=[];for(const m of g.dieKeys){const p=l.get(m);p&&d.push(p)}t.set(g.key,d)}const h=[];for(const g of f){const d=t.get(g.key),m=[];for(const[u,M]of t)if(u!==g.key)for(const B of M)m.push(B);const p=u=>u.testValues?.[i]!==void 0,S=u=>{const M=u.testValues?.[i];return M===void 0?!1:r.limitLow!==void 0&&M<r.limitLow||r.limitHigh!==void 0&&M>r.limitHigh},D=d.filter(p),y=m.filter(p);if(D.length<o.minimumSampleSize||y.length<o.minimumSampleSize)continue;const b=D.filter(S).length,$=y.filter(S).length,k=b/D.length,w=$/y.length,v=k-w,R=q(b,D.length,$,y.length);h.push({id:`specLimit:${i}:${g.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:i,label:r.name,unit:r.unit},comparison:{family:g.family,left:g.label,right:O(g.family)},effect:{direction:v===0?"different":v>0?"higher":"lower",absoluteDelta:v,relativeDelta:w===0?void 0:v/w,effectSize:v},stats:{method:"two-proportion-z",pValue:R,sampleSizeLeft:D.length,sampleSizeRight:y.length},summary:`${g.label} spec-fail rate for ${r.name} is ${(Math.abs(v)*100).toFixed(1)} pp ${v>0?"higher":"lower"} than the rest of the wafer`,highlight:{kind:"region",regionFamily:g.family,regionKeys:[g.key],dieKeys:[...g.dieKeys]}})}ne(h),c.push(...h.filter(g=>{const d=g.stats.adjustedPValue??g.stats.pValue??1,m=Math.abs(g.effect.absoluteDelta??0),p=Math.abs(g.effect.relativeDelta??0);return d<=o.significanceLevel&&(m>=o.minimumEffectSize||p>=o.minimumRelativeEffect)}).map(g=>({...g,severity:se(g.stats.adjustedPValue??g.stats.pValue??1,g.effect.effectSize??0)})))}}return c}function T(n){return n.highlight.regionKeys??[]}function Ue(n){const s=n.variable.bin??n.variable.index??"";return`${n.comparison.family}\0${n.variable.kind}\0${s}\0${n.effect.direction}`}function I(n,s){const a=T(n)[0]??"",o=H(a);return o.family==="ring"?o.ring??0:o.family==="sector"?me(s).indexOf(o.sector??""):0}function Ge(n,s,a){if(s==="quadrant"){const l=new Map;for(const t of n){const h=H(T(t)[0]??"").quadrant;h&&l.set(h,t)}const r=[...l.keys()],i=new Set,f=[];for(const t of r){if(i.has(t))continue;const h=[],g=[t];for(i.add(t);g.length;){const d=g.pop();h.push(d);for(const m of r)!i.has(m)&&Ve(d,m)&&(i.add(m),g.push(m))}f.push(h.map(d=>l.get(d)))}return f}const o=[...n].sort((l,r)=>I(l,a)-I(r,a)),e=[];let c=[];for(const l of o){if(c.length===0){c.push(l);continue}const r=I(c[c.length-1],a);I(l,a)-r===1?c.push(l):(e.push(c),c=[l])}if(c.length&&e.push(c),s==="sector"&&e.length>1){const r=me(a).length,i=I(e[0][0],a),f=I(e[e.length-1][e[e.length-1].length-1],a);if(i===0&&f===r-1){const t=e.pop();e[0]=[...t,...e[0]]}}return e}function Xe(n,s){const a=n.map(l=>H(T(l)[0]??"").ring).filter(l=>l!==void 0).sort((l,r)=>l-r),o=a[0],e=a[a.length-1];let c="";return o===e&&o===1&&s>1?c=" (core)":o===e&&o===s&&s>1&&(c=" (edge)"),o===e?`Ring ${o}${c}`:`Rings ${o}\u2013${e}`}function Je(n,s){const a=n.map(o=>H(T(o)[0]??"").sector??"");return a.length===1?`Sector ${a[0]}`:`Sectors ${a[0]}\u2013${a[a.length-1]}`}function Ze(n){const s=n.map(a=>H(T(a)[0]??"").quadrant??"");return s.length===1?s[0]:s.length===2?`Quadrants ${s[0]} & ${s[1]}`:`Quadrants ${s.slice(0,-1).join(", ")} & ${s[s.length-1]}`}function fe(n){return[...new Set(n)]}function et(n,s){const a=n[0],o=a.comparison.family,e=a.variable.kind,c=fe(n.flatMap(b=>b.highlight.dieKeys??[])),l=fe(n.flatMap(T)),r=new Set(c),i=o==="ring"?Xe(n,s.ringCount):o==="sector"?Je(n,s.sectorCount):Ze(n),f=e==="softBin"?s.softEligibleDies:e==="test"?s.testDies:s.eligibleDies,t=f.filter(b=>r.has(x(b))),h=f.filter(b=>!r.has(x(b)));let g,d,m,p,S;if(e==="test"){const b=a.variable.index,$=z=>z.testValues?.[b],k=t.map($).filter(z=>z!==void 0),w=h.map($).filter(z=>z!==void 0),{pValue:v,effectSize:R,delta:u}=We(k,w),M=X(w),B=M!==0?u/Math.abs(M):void 0;g={direction:u===0?"different":u>0?"higher":"lower",absoluteDelta:u,relativeDelta:B,effectSize:R},d={method:"welch-z-approx",pValue:v,sampleSizeLeft:k.length,sampleSizeRight:w.length},m=se(v,R),p=we(i,a.variable.label,u,B,o,a.variable.unit),S=`test:${b}`}else if(e==="yield"){const b=new Set(s.passBins),$=t.filter(N=>N.hbin!==void 0),k=h.filter(N=>N.hbin!==void 0),w=$.length,v=k.length,R=$.filter(N=>b.has(N.hbin)).length,u=k.filter(N=>b.has(N.hbin)).length,M=w>0?R/w:0,B=v>0?u/v:0,z=M-B,F=q(R,w,u,v);g={direction:z===0?"different":z>0?"higher":"lower",absoluteDelta:z,relativeDelta:B===0?void 0:z/B,effectSize:z},d={method:"two-proportion-z",pValue:F,sampleSizeLeft:w,sampleSizeRight:v},m=ie(F,z,g.relativeDelta),p=ye(i,z,o),S="yield"}else{const b=a.variable.bin,$=z=>e==="softBin"?z.sbin:z.hbin,k=t.filter(z=>$(z)===b).length,w=h.filter(z=>$(z)===b).length,v=k/t.length,R=w/h.length,u=v-R,M=q(k,t.length,w,h.length);g={direction:u===0?"different":u>0?"higher":"lower",absoluteDelta:u,relativeDelta:R===0?void 0:u/R,effectSize:u},d={method:"two-proportion-z",pValue:M,sampleSizeLeft:t.length,sampleSizeRight:h.length},m=ie(M,u,g.relativeDelta);const B=e==="softBin"?s.sbinDefs:s.hbinDefs;p=Se(i,$e(b,B,e==="softBin"?"SBin":"HBin"),u,o),S=`${e}:${b}`}const D=fe(l.map(b=>b.replace(`${o}:`,""))).join("-"),y=e==="hardBin"||e==="softBin"?{kind:"bin",bin:a.variable.bin,regionKeys:l,dieKeys:c}:{kind:"region",regionFamily:o,regionKeys:l,dieKeys:c};return{...a,id:`${S}:${o}:${D}`,severity:m,comparison:{family:o,left:i,right:O(o)},effect:g,stats:d,summary:p,highlight:y,relatedIds:n.map(b=>b.id)}}function tt(n,s){const a=new Set(["ring","quadrant","sector"]),o=[],e=new Map;for(const l of n){if(!a.has(l.comparison.family)){o.push(l);continue}const r=Ue(l),i=e.get(r)??[];i.push(l),e.set(r,i)}const c=[];for(const l of e.values()){if(l.length===1){c.push(l[0]);continue}const r=l[0].comparison.family;for(const i of Ge(l,r,s.sectorCount))c.push(i.length===1?i[0]:et(i,s))}return[...o,...c]}function nt(n){const s=new Map,a=new Map;for(const e of n){const c=x(e);e.hbin!==void 0&&e.hbin!==null&&(s.get(e.hbin)??s.set(e.hbin,new Set).get(e.hbin)).add(c),e.sbin!==void 0&&e.sbin!==null&&(a.get(e.sbin)??a.set(e.sbin,new Set).get(e.sbin)).add(c)}const o=new Set;for(const[e,c]of s)for(const[l,r]of a){if(c.size!==r.size)continue;let i=!0;for(const f of c)if(!r.has(f)){i=!1;break}i&&o.add(`${e}:${l}`)}return o}function it(n){return`${n.comparison.family}\0${n.comparison.left}`}function st(n,s,a){const o=(r,i)=>{r.absorbedIds=[...r.absorbedIds??[],i.id]},e=new Map;for(const r of n){const i=it(r);(e.get(i)??e.set(i,[]).get(i)).push(r)}const c=nt(s),l=new Set;for(const r of e.values()){for(const f of r)if(!(f.variable.kind!=="hardBin"||f.variable.bin===void 0))for(const t of r){if(t.variable.kind!=="softBin"||t.variable.bin===void 0||l.has(t.id)||!c.has(`${f.variable.bin}:${t.variable.bin}`)||t.effect.direction!==f.effect.direction)continue;o(f,t),l.add(t.id);const h=f.variable.label.replace(/^HBin\s+/,""),g=t.variable.label.replace(/^SBin\s+/,""),d=h===g?`HBin and SBin ${h} (same dies)`:`${f.variable.label} and ${t.variable.label} (same dies)`;f.summary=f.summary.replace(f.variable.label,d),f.variable.label=d;break}if(a.length!==1)continue;const i=r.find(f=>f.variable.kind==="yield");if(i){for(const f of r)if(!(f===i||l.has(f.id)||!((f.variable.kind==="hardBin"||f.variable.kind==="softBin")&&f.variable.bin===a[0]))){o(i,f),l.add(f.id);for(const h of f.absorbedIds??[])i.absorbedIds=[...i.absorbedIds??[],h]}}}}export function analyzeWaferMap(n,s={}){const{resolved:a,warnings:o}=Ke(s),e=Te(n),c=e.isLotStack,l=e.aggrMethod,r=!c||l==="mode"||l==="countBin"||l==="percent",i=e.dies.filter(u=>je(u,a)),f=i.filter(de),t=xe(a,i.length),h=e.dies.filter(u=>ee(u,t)),g=h.filter(de),d=Fe(g,e.wafer,t.ringCount),m=Be(g,e.wafer,t.ringCount),p=t.enableReticlePositionAnalysis?Ce(g,e.reticleConfig):[],S=t.enableTestSiteAnalysis===!1?[]:Pe(h,t.enableTestSiteAnalysis===!0),D=t.enableAngularAnalysis?Ne(g,e.wafer,t.sectorCount):[],y=[];if(t.enableYieldAnalysis&&r&&y.push(...Y(i,d,t.passBins,t),...Y(i,m,t.passBins,t),...Y(i,p,t.passBins,t),...Y(i,S,t.passBins,t),...Y(i,D,t.passBins,t)),t.enableHardBinAnalysis&&r&&y.push(...V(i,d,"hard",e.hbinDefs,"hardBin",t),...V(i,m,"hard",e.hbinDefs,"hardBin",t),...V(i,p,"hard",e.hbinDefs,"hardBin",t),...V(i,S,"hard",e.hbinDefs,"hardBin",t),...V(i,D,"hard",e.hbinDefs,"hardBin",t)),t.enableSoftBinAnalysis){const u=i.filter(M=>M.sbin!==void 0);y.push(...V(u,d,"soft",e.sbinDefs,"softBin",t),...V(u,m,"soft",e.sbinDefs,"softBin",t),...V(u,p,"soft",e.sbinDefs,"softBin",t),...V(u,S,"soft",e.sbinDefs,"softBin",t),...V(u,D,"soft",e.sbinDefs,"softBin",t))}const b=[...o];let $;if(t.enableTestValueAnalysis){const u=Q(i,d,e.testDefs,t),M=Q(i,m,e.testDefs,t),B=Q(i,p,e.testDefs,t),z=Q(i,S,e.testDefs,t),F=Q(i,D,e.testDefs,t);y.push(...u.findings,...M.findings,...B.findings,...z.findings,...F.findings),u.warning&&b.push(u.warning),$=u.activeTestNumbers,y.push(...Qe(i,[d,m,p,S,D],e.testDefs,t)),y.push(...W(i,d,e.testDefs,t),...W(i,m,e.testDefs,t),...W(i,p,e.testDefs,t),...W(i,S,e.testDefs,t),...W(i,D,e.testDefs,t))}if(t.enableClusterAnalysis){const u=He(c,r,e.testDefs);(!c||r||u!==void 0)&&y.push(...Le(f,e.wafer,{...t,isFailingDie:u}))}const k=tt(y,{eligibleDies:i,softEligibleDies:i.filter(u=>u.sbin!==void 0),testDies:i,passBins:t.passBins,ringCount:t.ringCount,sectorCount:t.sectorCount,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs});if(y.length=0,y.push(...k),t.enablePatternClassification&&r){const u=Ee(f,e.wafer,{passBins:t.passBins,ringCount:t.ringCount});if(u!==null&&u.pattern!=="random"&&u.pattern!=="none"){let Me=function(C){const L=T(C).map(K=>H(K).ring).filter(K=>K!==void 0);if(L.length===0)return!0;const ce=L.some(K=>K===ze),ue=L.some(K=>K===1);return A==="edge-ring"||A==="edge-local"?ce:A==="center"?ue:A==="donut"?!ce&&!ue:!0};const B={center:"Center cluster",donut:"Donut","edge-ring":"Edge-ring","edge-local":"Edge-local",scratch:"Scratch","near-full":"Near-full"}[u.pattern]??u.pattern,z=u.confidence==="high"?"unusual":u.confidence==="medium"?"notable":"info",F=u.features,N=C=>`${(C*100).toFixed(0)}%`,E=u.pattern==="edge-ring"||u.pattern==="edge-local"?`${N(F.edgeRdd)} of edge dies failing`:u.pattern==="center"||u.pattern==="donut"?`centroid at ${N(F.centroidDistNorm)} of wafer radius from centre`:u.pattern==="scratch"?`linear score ${F.linearScore.toFixed(2)}, eccentricity ${F.eccentricity.toFixed(2)}`:`${N(F.globalRdd)} of dies failing`,P=i.filter(C=>{const L=C.hbin??C.sbin;return L!==void 0&&!new Set(t.passBins).has(L)}),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[u.pattern]??[]),A=u.pattern,ze=t.ringCount,Z=[];for(const C of y){if(!G.has(C.comparison.family)||C.comparison.family==="ring"&&!Me(C))continue;Z.push(C.id);const L=C.comparison.family==="ring"||C.comparison.family==="quadrant"||C.comparison.family==="sector";z!=="info"&&L&&(C.severity="info")}y.push({id:`spatial-pattern:${u.pattern}`,level:"wafer",severity:z,variable:{kind:"spatialPattern",label:B},comparison:{family:"spatial-pattern",left:B,right:"Wafer"},effect:{direction:"different",effectSize:F.globalRdd},stats:{method:"geometry",sampleSizeLeft:P.length,sampleSizeRight:i.length},summary:`Spatial pattern: ${B.toLowerCase()} (${u.confidence} confidence) \u2014 ${E}${u.note?` [${u.note}]`:""}`,highlight:{kind:"dies",dieKeys:P.map(C=>x(C))},relatedIds:Z.length>0?Z:void 0})}}y.sort((u,M)=>{const B=u.stats.adjustedPValue??u.stats.pValue??1,z=M.stats.adjustedPValue??M.stats.pValue??1;return B!==z?B-z:Math.abs(M.effect.absoluteDelta??0)-Math.abs(u.effect.absoluteDelta??0)});const w=qe(e.dies,i.length,e.yield.yieldPercent);c&&(w.isLotStack=!0,l&&(w.aggregationMethod=l));const v=Oe(e.dies,e.testDefs);v&&(w.testSpecYield=v);const R=computeFunctionalYield(e.dies,e.testDefs);if(R&&(w.functionalYield=R),$===void 0&&t.computePerTestStats){const u=ke(i,t.testNumbers,e.testDefs);u.warning&&b.push(u.warning),$=u.testNumbers}if($?.length){const u=Ie(e.dies,$,e.testDefs,t.minimumSampleSize);u&&(w.perTestStats=u)}return b.length>0&&(w.warnings=b),st(y,i,t.passBins),{level:"wafer",hasNotableFindings:y.some(u=>u.severity==="notable"||u.severity==="unusual"),findings:y,wafer:e.wafer.metadata??void 0,stats:w}}
|
|
1
|
+
import{isYieldEligibleDie as ee,getDieKey as x,isPositionedDie as de}from"../core/dies.js";import{buildWaferMap as Re,getTestPassStatus as me,isParametricTest as j}from"../renderer/buildWaferMap.js";import{buildQuadrantRegions as Be,buildReticlePositionRegions as Ce,buildRingRegions as Fe,buildSectorRegions as Ne,buildTestSiteRegions as Pe,sectorCompassNames as ge,areQuadrantsAdjacent as Ve,parseRegionKey as O}from"./regions.js";import{buildClusterFindings as Le}from"./clusterDetection.js";import{classifyPattern as Ee}from"./patternClassification.js";import{normalCdf as he}from"./math.js";import{quantile as te}from"./math.js";import{mean as X,clamp01 as be}from"../core/utils.js";const Ae={significanceLevel:{min:1e-6,max:1},minimumEffectSize:{min:0,max:1},minimumRelativeEffect:{min:0,max:Number.MAX_SAFE_INTEGER},ringCount:{min:1,max:Number.MAX_SAFE_INTEGER}},pe=[4,8,16,32];function Ke(i){const a=Object.fromEntries(Object.entries(i).filter(([,f])=>f!==void 0)),s={..._,...a},o=[],e=[];for(const f of["significanceLevel","minimumEffectSize","minimumRelativeEffect","ringCount"]){const{min:l,max:r}=Ae[f],n=s[f];if(typeof n!="number"||!Number.isFinite(n)){e.push(`${f}=${String(n)} is not a finite number (using ${_[f]})`),s[f]=_[f];continue}const c=Math.min(r,Math.max(l,n));c!==n&&(e.push(`${f}=${n} is outside ${l}\u2013${r} (using ${c})`),s[f]=c)}if(!Number.isInteger(s.ringCount)){const f=Math.max(1,Math.round(s.ringCount));e.push(`ringCount=${s.ringCount} is not a whole number (using ${f})`),s.ringCount=f}if(pe.includes(s.sectorCount)||(e.push(`sectorCount=${s.sectorCount} is not one of ${pe.join(", ")} (using ${_.sectorCount})`),s.sectorCount=_.sectorCount),e.length){const f=`analyzeWaferMap: ${e.join("; ")}. The analysis ran with the corrected values.`;console.warn(`[wafermap] ${f}`),o.push({code:"analysis-option-corrected",message:f,severity:"warning"})}return{resolved:s,warnings:o}}const _={ringCount:4,passBins:[1],significanceLevel:.05,minimumEffectSize:.2,minimumRelativeEffect:1,minimumSampleSize:5,includePartial:!1,includeEdgeExcluded:!1,enableYieldAnalysis:!0,enableHardBinAnalysis:!0,enableSoftBinAnalysis:!0,enableTestValueAnalysis:!1,computePerTestStats:!1,enableReticlePositionAnalysis:!0,enableClusterAnalysis:!0,enableAngularAnalysis:!0,sectorCount:8,enablePatternClassification:!0,minimumClusterSize:5};function xe(i,a){const s={...i};return s.minimumClusterSize=Math.max(5,Math.round(a*.003)),s}function Te(i){return"wafer"in i&&"dies"in i&&"view"in i?i:Re(i)}function je(i,a){return ee(i,a)?i.hbin!==void 0||i.sbin!==void 0||i.testValues!==void 0&&Object.keys(i.testValues).length>0:!1}function Oe(i,a,s){if(!i||a)return;const o=(s??[]).filter(e=>j(e)&&(e.limitLow!==void 0||e.limitHigh!==void 0));if(o.length!==0)return e=>{for(const f of o){const l=f.testNumber,r=e.testValues?.[l];if(r!==void 0&&(f.limitLow!==void 0&&r<f.limitLow||f.limitHigh!==void 0&&r>f.limitHigh))return!0}return!1}}function He(i,a,s){const o=new Set,e=new Set,f=new Set,l=new Map,r=new Map;for(const n of i){if(n.testValues)for(const c of Object.keys(n.testValues))o.add(Number(c));if(n.testPass)for(const c of Object.keys(n.testPass))o.add(Number(c));n.hbin!==void 0&&e.add(n.hbin),n.sbin!==void 0&&f.add(n.sbin),ee(n)&&(n.hbin!==void 0&&l.set(n.hbin,(l.get(n.hbin)??0)+1),n.sbin!==void 0&&r.set(n.sbin,(r.get(n.sbin)??0)+1))}return{totalDies:i.length,analyzedDies:a,excludedDies:i.length-a,yieldPercent:s,testsConsidered:[...o].sort((n,c)=>n-c),hardBinsConsidered:[...e].sort((n,c)=>n-c),softBinsConsidered:[...f].sort((n,c)=>n-c),...l.size?{hardBinCounts:Object.fromEntries(l)}:{},...r.size?{softBinCounts:Object.fromEntries(r)}:{}}}function qe(i,a){if(!a?.length)return;const s=a.filter(e=>j(e)&&(e.limitLow!==void 0||e.limitHigh!==void 0));if(!s.length)return;const o=[];for(const e of s){const f=e.testNumber;let l=0,r=0,n=0,c=0;for(const t of i){if(t.partial||t.edgeExcluded)continue;const h=t.testValues?.[f];h!==void 0&&(c++,e.limitLow!==void 0&&h<e.limitLow?r++:e.limitHigh!==void 0&&h>e.limitHigh?n++:l++)}o.push({testNumber:f,label:e.name,passDies:l,failLowDies:r,failHighDies:n,totalDies:c,yieldPercent:c>0?l/c*100:null})}return o.length?o:void 0}export function computeFunctionalYield(i,a){if(!a?.length)return;const s=a.filter(e=>!j(e));if(!s.length)return;const o=[];for(const e of s){const f=e.testNumber;let l=0,r=0;for(const c of i){if(c.partial||c.edgeExcluded)continue;const t=me(c,f,e);t!==void 0&&(t?l++:r++)}const n=l+r;o.push({testNumber:f,label:e.name,passDies:l,failDies:r,totalDies:n,passRatePercent:n>0?l/n*100:null})}return o.length?o:void 0}function Ie(i,a,s,o){const e=[];for(const f of a){const l=[];for(const h of i){if(h.partial||h.edgeExcluded)continue;const m=h.testValues?.[f];m!==void 0&&l.push(m)}if(l.length<o)continue;const r=X(l),n=Math.sqrt(le(l,r)),c=l.slice().sort((h,m)=>h-m),t=s?.find(h=>h.testNumber===f)?.name??String(f);e.push({testNumber:f,label:t,count:l.length,min:c[0],max:c[c.length-1],mean:r,stddev:n,median:te(c,.5),q1:te(c,.25),q3:te(c,.75)})}return e.length?e:void 0}function H(i,a,s,o){const e=(i+s)/(a+o),f=e*(1-e)*(1/a+1/o);if(!Number.isFinite(f)||f<=0)return 1;const l=(i/a-s/o)/Math.sqrt(f);return be(2*(1-he(Math.abs(l))))}function ne(i){const a=new Map;for(const s of i){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(l=>l.stats.pValue!==void 0).sort((l,r)=>(l.stats.pValue??1)-(r.stats.pValue??1)),e=o.length;let f=1;for(let l=e-1;l>=0;l--){const r=o[l],n=r.stats.pValue??1,c=Math.min(1,Math.min(f,n*e/(l+1)));r.stats.adjustedPValue=c,f=c}}return i}function ie(i,a,s){const o=Math.abs(a),e=s!==void 0?Math.abs(s):0;return i<=.01&&(o>=.3||e>=2.5)?"unusual":i<=.05&&(o>=.2||e>=1.5)?"notable":"info"}function se(i,a){return i<=.01&&Math.abs(a)>=.5?"unusual":i<=.05&&Math.abs(a)>=.15?"notable":"info"}function J(i){return i==="reticle-position"?"other reticle positions":i==="test-site"?"other test sites":"the rest of the map"}function q(i){return i==="reticle-position"?"Other reticle positions":i==="test-site"?"Other test sites":"Rest of map"}function ye(i,a,s){const o=(Math.abs(a)*100).toFixed(1);return`${i} yield is ${o} percentage points ${a>0?"higher":"lower"} than ${J(s)}`}function oe(i,a){return a==="quadrant"&&!i.startsWith("Quadrant")?`quadrant ${i}`:i}function Se(i,a,s,o){const e=oe(i,o),f=(Math.abs(s)*100).toFixed(1);return`${e} has ${a} occurrence ${f} percentage points ${s>0?"higher":"lower"} than ${J(o)}`}function _e(i,a,s,o){const e=oe(i,o),f=(Math.abs(s)*100).toFixed(1);return`${e} has ${a} pass rate ${f} percentage points ${s>0?"higher":"lower"} than ${J(o)}`}function we(i,a,s,o,e,f){const l=oe(i,e),r=J(e),n=s>0?"higher":"lower";if(o!==void 0&&Number.isFinite(o)){const t=(Math.abs(o)*100).toFixed(1);return`${l} mean ${a} is ${t}% ${n} than ${r}`}const c=f?` ${f}`:"";return`${l} mean ${a} is ${Math.abs(s).toPrecision(3)}${c} ${n} than ${r}`}function $e(i,a,s){const o=a?.find(e=>e.bin===i);return o?.name?`${s} ${i} (${o.name})`:`${s} ${i}`}function Ye(i,a){const s=a?.find(o=>o.testNumber===i);return{label:s?.name??`Test ${i}`,unit:s?.unit}}function ae(i,a){const s=new Map(i.map(e=>[x(e),e])),o=new Map;for(const e of a){const f=[];for(const l of e.dieKeys){const r=s.get(l);r&&f.push(r)}o.set(e.key,f)}return o}function re(i,a){return ne(i),i.filter(s=>{const o=s.stats.adjustedPValue??s.stats.pValue??1,e=Math.abs(s.effect.absoluteDelta??0),f=Math.abs(s.effect.relativeDelta??0);return o<=a.significanceLevel&&(e>=a.minimumEffectSize||f>=a.minimumRelativeEffect)}).map(s=>({...s,severity:ie(s.stats.adjustedPValue??s.stats.pValue??1,s.effect.absoluteDelta??0,s.effect.relativeDelta)}))}function Y(i,a,s,o){const e=new Set(s),f=ae(i,a),l=new Map,r=new Map;for(const[c,t]of f){let h=0,m=0;for(const d of t)d.hbin!==void 0&&(m++,e.has(d.hbin)&&h++);l.set(c,h),r.set(c,m)}const n=[];for(const c of a){const t=r.get(c.key)??0,h=l.get(c.key)??0;let m=0,d=0;for(const[y,b]of r)y!==c.key&&(m+=b,d+=l.get(y)??0);if(t<o.minimumSampleSize||m<o.minimumSampleSize)continue;const g=h/t,p=d/m,S=g-p,D=H(h,t,d,m);n.push({id:`yield:${c.key}`,level:"wafer",severity:"info",variable:{kind:"yield",label:"Yield"},comparison:{family:c.family,left:c.label,right:q(c.family)},effect:{direction:S===0?"different":S>0?"higher":"lower",absoluteDelta:S,relativeDelta:p===0?void 0:S/p,effectSize:S},stats:{method:"two-proportion-z",pValue:D,sampleSizeLeft:t,sampleSizeRight:m},summary:ye(c.label,S,c.family),highlight:{kind:"region",regionFamily:c.family,regionKeys:[c.key],dieKeys:[...c.dieKeys]}})}return re(n,o)}function V(i,a,s,o,e,f){const l=d=>s==="soft"?d.sbin:d.hbin,r=[...new Set(i.map(l).filter(d=>d!==void 0))].sort((d,g)=>d-g),n=ae(i,a),c=new Map,t=new Map;for(const[d,g]of n){const p=new Map;for(const S of g){const D=l(S);D!==void 0&&p.set(D,(p.get(D)??0)+1)}c.set(d,p),t.set(d,g.length)}const h=[],m=e==="hardBin"?"HBin":"SBin";for(const d of a){const g=t.get(d.key)??0,p=c.get(d.key);let S=0;const D=new Map;for(const[y,b]of c)if(y!==d.key){S+=t.get(y)??0;for(const[$,k]of b)D.set($,(D.get($)??0)+k)}if(!(g<f.minimumSampleSize||S<f.minimumSampleSize))for(const y of r){const b=p.get(y)??0,$=D.get(y)??0,k=b/g,w=$/S,v=k-w,R=H(b,g,$,S),u=$e(y,o,m);h.push({id:`${e}:${y}:${d.key}`,level:"wafer",severity:"info",variable:{kind:e,bin:y,label:u},comparison:{family:d.family,left:d.label,right:q(d.family)},effect:{direction:v===0?"different":v>0?"higher":"lower",absoluteDelta:v,relativeDelta:w===0?void 0:v/w,effectSize:v},stats:{method:"two-proportion-z",pValue:R,sampleSizeLeft:g,sampleSizeRight:S},summary:Se(d.label,u,v,d.family),highlight:{kind:"bin",bin:y,regionKeys:[d.key],dieKeys:[...d.dieKeys]}})}}return re(h,f)}function W(i,a,s,o){const e=(s??[]).filter(t=>!j(t));if(!e.length)return[];const f=e.map(t=>t.testNumber),l=ae(i,a),r=new Map,n=new Map;for(const[t,h]of l){const m=new Uint32Array(e.length),d=new Uint32Array(e.length);for(const g of h)for(let p=0;p<e.length;p++){const S=me(g,f[p],e[p]);S!==void 0&&(d[p]++,S&&m[p]++)}r.set(t,m),n.set(t,d)}const c=[];for(const t of a){const h=r.get(t.key),m=n.get(t.key);for(let d=0;d<e.length;d++){let g=0,p=0;for(const[v,R]of n)v!==t.key&&(p+=R[d],g+=r.get(v)[d]);const S=m[d],D=h[d];if(S<o.minimumSampleSize||p<o.minimumSampleSize)continue;const y=D/S,b=g/p,$=y-b,k=H(D,S,g,p),w=f[d];c.push({id:`functional:${w}:${t.key}`,level:"wafer",severity:"info",variable:{kind:"functionalTest",index:w,label:`${e[d].name} pass rate`},comparison:{family:t.family,left:t.label,right:q(t.family)},effect:{direction:$===0?"different":$>0?"higher":"lower",absoluteDelta:$,relativeDelta:b===0?void 0:$/b,effectSize:$},stats:{method:"two-proportion-z",pValue:k,sampleSizeLeft:S,sampleSizeRight:p},summary:_e(t.label,e[d].name,$,t.family),highlight:{kind:"region",regionFamily:t.family,regionKeys:[t.key],dieKeys:[...t.dieKeys]}})}}return re(c,o)}function le(i,a){return i.length<2?0:i.reduce((s,o)=>s+(o-a)**2,0)/(i.length-1)}function ve(i,a,s,o,e,f){const l=Math.sqrt(s/i+f/o),r=a-e;if(!Number.isFinite(l)||l===0)return{pValue:1,effectSize:0,delta:r};const n=r/l,c=Math.sqrt(Math.max(0,(s+f)/2)),t=c===0?r:r/c;return{pValue:be(2*(1-he(Math.abs(n)))),effectSize:t,delta:r}}function We(i,a){const s=X(i),o=X(a);return ve(i.length,s,le(i,s),a.length,o,le(a,o))}const De=250;function ke(i,a,s){const o=l=>l.filter(r=>j(s?.find(n=>n.testNumber===r)));if(a)return{testNumbers:o(a.slice().sort((l,r)=>l-r))};const e=new Set;let f=!1;e:for(const l of i)for(const r of Object.keys(l.testValues??{})){const n=Number(r);if(!e.has(n)&&(e.add(n),e.size>De)){f=!0;break e}}if(f){const l=`Test-value analysis was skipped: more than ${De} tests were found in the die data, so no test findings were computed. Pass testNumbers: [...] to analyse specific tests.`;return console.warn(`[wafermap] analyzeWaferMap: ${l}`),{testNumbers:[],warning:{code:"test-count-capped",message:l,severity:"warning"}}}return{testNumbers:o([...e].sort((l,r)=>l-r))}}function Q(i,a,s,o){const e=ke(i,o.testNumbers,s);if(e.warning)return{findings:[],warning:e.warning};const f=e.testNumbers,l=new Map;for(let g=0;g<a.length;g++)for(const p of a[g].dieKeys)l.set(p,g);const r=a.length,n=[],c=[];for(const g of i){const p=l.get(x(g));p!==void 0&&(n.push(g),c.push(p))}const t=[],h=new Float64Array(r),m=new Float64Array(r),d=new Float64Array(r);for(const g of f){h.fill(0),m.fill(0),d.fill(0);let p=0,S=0,D=0,y;for(let b=0;b<n.length;b++){const k=n[b].testValues?.[g];if(k===void 0)continue;y===void 0&&(y=k);const w=k-y,v=c[b];h[v]+=1,m[v]+=w,d[v]+=w*w,p+=1,S+=w,D+=w*w}if(y!==void 0)for(let b=0;b<r;b++){const $=h[b],k=p-$;if($<o.minimumSampleSize||k<o.minimumSampleSize)continue;const w=m[b]/$,R=(S-m[b])/k,u=w+y,M=R+y,B=$>1?Math.max(0,d[b]-$*w*w)/($-1):0,z=k>1?Math.max(0,D-d[b]-k*R*R)/(k-1):0,{pValue:F,effectSize:N,delta:E}=ve($,u,B,k,M,z),P=a[b],{label:U,unit:G}=Ye(g,s),A=M!==0?E/Math.abs(M):void 0;t.push({id:`test:${g}:${P.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:g,label:U,unit:G},comparison:{family:P.family,left:P.label,right:q(P.family)},effect:{direction:E===0?"different":E>0?"higher":"lower",absoluteDelta:E,relativeDelta:A,effectSize:N},stats:{method:"welch-z-approx",pValue:F,sampleSizeLeft:$,sampleSizeRight:k},summary:we(P.label,U,E,A,P.family,G),highlight:{kind:"region",regionFamily:P.family,regionKeys:[P.key],dieKeys:[...P.dieKeys]}})}}return ne(t),{activeTestNumbers:f,findings:t.filter(g=>{const p=g.stats.adjustedPValue??g.stats.pValue??1,S=Math.abs(g.effect.effectSize??0);return p<=o.significanceLevel&&S>=o.minimumEffectSize}).map(g=>({...g,severity:se(g.stats.adjustedPValue??g.stats.pValue??1,g.effect.effectSize??0)}))}}function Qe(i,a,s,o){if(!s?.length)return[];const e=s.filter(r=>j(r)&&(r.limitLow!==void 0||r.limitHigh!==void 0));if(!e.length)return[];const f=[],l=new Map(i.map(r=>[x(r),r]));for(const r of e){const n=r.testNumber;for(const c of a){const t=new Map;for(const m of c){const d=[];for(const g of m.dieKeys){const p=l.get(g);p&&d.push(p)}t.set(m.key,d)}const h=[];for(const m of c){const d=t.get(m.key),g=[];for(const[u,M]of t)if(u!==m.key)for(const B of M)g.push(B);const p=u=>u.testValues?.[n]!==void 0,S=u=>{const M=u.testValues?.[n];return M===void 0?!1:r.limitLow!==void 0&&M<r.limitLow||r.limitHigh!==void 0&&M>r.limitHigh},D=d.filter(p),y=g.filter(p);if(D.length<o.minimumSampleSize||y.length<o.minimumSampleSize)continue;const b=D.filter(S).length,$=y.filter(S).length,k=b/D.length,w=$/y.length,v=k-w,R=H(b,D.length,$,y.length);h.push({id:`specLimit:${n}:${m.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:n,label:r.name,unit:r.unit},comparison:{family:m.family,left:m.label,right:q(m.family)},effect:{direction:v===0?"different":v>0?"higher":"lower",absoluteDelta:v,relativeDelta:w===0?void 0:v/w,effectSize:v},stats:{method:"two-proportion-z",pValue:R,sampleSizeLeft:D.length,sampleSizeRight:y.length},summary:`${m.label} spec-fail rate for ${r.name} is ${(Math.abs(v)*100).toFixed(1)} pp ${v>0?"higher":"lower"} than the rest of the wafer`,highlight:{kind:"region",regionFamily:m.family,regionKeys:[m.key],dieKeys:[...m.dieKeys]}})}ne(h),f.push(...h.filter(m=>{const d=m.stats.adjustedPValue??m.stats.pValue??1,g=Math.abs(m.effect.absoluteDelta??0),p=Math.abs(m.effect.relativeDelta??0);return d<=o.significanceLevel&&(g>=o.minimumEffectSize||p>=o.minimumRelativeEffect)}).map(m=>({...m,severity:se(m.stats.adjustedPValue??m.stats.pValue??1,m.effect.effectSize??0)})))}}return f}function T(i){return i.highlight.regionKeys??[]}function Ue(i){const a=i.variable.bin??i.variable.index??"";return`${i.comparison.family}\0${i.variable.kind}\0${a}\0${i.effect.direction}`}function I(i,a){const s=T(i)[0]??"",o=O(s);return o.family==="ring"?o.ring??0:o.family==="sector"?ge(a).indexOf(o.sector??""):0}function Ge(i,a,s){if(a==="quadrant"){const l=new Map;for(const t of i){const h=O(T(t)[0]??"").quadrant;h&&l.set(h,t)}const r=[...l.keys()],n=new Set,c=[];for(const t of r){if(n.has(t))continue;const h=[],m=[t];for(n.add(t);m.length;){const d=m.pop();h.push(d);for(const g of r)!n.has(g)&&Ve(d,g)&&(n.add(g),m.push(g))}c.push(h.map(d=>l.get(d)))}return c}const o=[...i].sort((l,r)=>I(l,s)-I(r,s)),e=[];let f=[];for(const l of o){if(f.length===0){f.push(l);continue}const r=I(f[f.length-1],s);I(l,s)-r===1?f.push(l):(e.push(f),f=[l])}if(f.length&&e.push(f),a==="sector"&&e.length>1){const r=ge(s).length,n=I(e[0][0],s),c=I(e[e.length-1][e[e.length-1].length-1],s);if(n===0&&c===r-1){const t=e.pop();e[0]=[...t,...e[0]]}}return e}function Xe(i,a){const s=i.map(l=>O(T(l)[0]??"").ring).filter(l=>l!==void 0).sort((l,r)=>l-r),o=s[0],e=s[s.length-1];let f="";return o===e&&o===1&&a>1?f=" (core)":o===e&&o===a&&a>1&&(f=" (edge)"),o===e?`Ring ${o}${f}`:`Rings ${o}\u2013${e}`}function Je(i,a){const s=i.map(o=>O(T(o)[0]??"").sector??"");return s.length===1?`Sector ${s[0]}`:`Sectors ${s[0]}\u2013${s[s.length-1]}`}function Ze(i){const a=i.map(s=>O(T(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 fe(i){return[...new Set(i)]}function et(i,a){const s=i[0],o=s.comparison.family,e=s.variable.kind,f=fe(i.flatMap(b=>b.highlight.dieKeys??[])),l=fe(i.flatMap(T)),r=new Set(f),n=o==="ring"?Xe(i,a.ringCount):o==="sector"?Je(i,a.sectorCount):Ze(i),c=e==="softBin"?a.softEligibleDies:e==="test"?a.testDies:a.eligibleDies,t=c.filter(b=>r.has(x(b))),h=c.filter(b=>!r.has(x(b)));let m,d,g,p,S;if(e==="test"){const b=s.variable.index,$=z=>z.testValues?.[b],k=t.map($).filter(z=>z!==void 0),w=h.map($).filter(z=>z!==void 0),{pValue:v,effectSize:R,delta:u}=We(k,w),M=X(w),B=M!==0?u/Math.abs(M):void 0;m={direction:u===0?"different":u>0?"higher":"lower",absoluteDelta:u,relativeDelta:B,effectSize:R},d={method:"welch-z-approx",pValue:v,sampleSizeLeft:k.length,sampleSizeRight:w.length},g=se(v,R),p=we(n,s.variable.label,u,B,o,s.variable.unit),S=`test:${b}`}else if(e==="yield"){const b=new Set(a.passBins),$=t.filter(N=>N.hbin!==void 0),k=h.filter(N=>N.hbin!==void 0),w=$.length,v=k.length,R=$.filter(N=>b.has(N.hbin)).length,u=k.filter(N=>b.has(N.hbin)).length,M=w>0?R/w:0,B=v>0?u/v:0,z=M-B,F=H(R,w,u,v);m={direction:z===0?"different":z>0?"higher":"lower",absoluteDelta:z,relativeDelta:B===0?void 0:z/B,effectSize:z},d={method:"two-proportion-z",pValue:F,sampleSizeLeft:w,sampleSizeRight:v},g=ie(F,z,m.relativeDelta),p=ye(n,z,o),S="yield"}else{const b=s.variable.bin,$=z=>e==="softBin"?z.sbin:z.hbin,k=t.filter(z=>$(z)===b).length,w=h.filter(z=>$(z)===b).length,v=k/t.length,R=w/h.length,u=v-R,M=H(k,t.length,w,h.length);m={direction:u===0?"different":u>0?"higher":"lower",absoluteDelta:u,relativeDelta:R===0?void 0:u/R,effectSize:u},d={method:"two-proportion-z",pValue:M,sampleSizeLeft:t.length,sampleSizeRight:h.length},g=ie(M,u,m.relativeDelta);const B=e==="softBin"?a.sbinDefs:a.hbinDefs;p=Se(n,$e(b,B,e==="softBin"?"SBin":"HBin"),u,o),S=`${e}:${b}`}const D=fe(l.map(b=>b.replace(`${o}:`,""))).join("-"),y=e==="hardBin"||e==="softBin"?{kind:"bin",bin:s.variable.bin,regionKeys:l,dieKeys:f}:{kind:"region",regionFamily:o,regionKeys:l,dieKeys:f};return{...s,id:`${S}:${o}:${D}`,severity:g,comparison:{family:o,left:n,right:q(o)},effect:m,stats:d,summary:p,highlight:y,relatedIds:i.map(b=>b.id)}}function tt(i,a){const s=new Set(["ring","quadrant","sector"]),o=[],e=new Map;for(const l of i){if(!s.has(l.comparison.family)){o.push(l);continue}const r=Ue(l),n=e.get(r)??[];n.push(l),e.set(r,n)}const f=[];for(const l of e.values()){if(l.length===1){f.push(l[0]);continue}const r=l[0].comparison.family;for(const n of Ge(l,r,a.sectorCount))f.push(n.length===1?n[0]:et(n,a))}return[...o,...f]}function nt(i){const a=new Map,s=new Map;for(const e of i){const f=x(e);e.hbin!==void 0&&e.hbin!==null&&(a.get(e.hbin)??a.set(e.hbin,new Set).get(e.hbin)).add(f),e.sbin!==void 0&&e.sbin!==null&&(s.get(e.sbin)??s.set(e.sbin,new Set).get(e.sbin)).add(f)}const o=new Set;for(const[e,f]of a)for(const[l,r]of s){if(f.size!==r.size)continue;let n=!0;for(const c of f)if(!r.has(c)){n=!1;break}n&&o.add(`${e}:${l}`)}return o}function it(i){return`${i.comparison.family}\0${i.comparison.left}`}function st(i,a,s){const o=(r,n)=>{r.absorbedIds=[...r.absorbedIds??[],n.id]},e=new Map;for(const r of i){const n=it(r);(e.get(n)??e.set(n,[]).get(n)).push(r)}const f=nt(a),l=new Set;for(const r of e.values()){for(const c of r)if(!(c.variable.kind!=="hardBin"||c.variable.bin===void 0))for(const t of r){if(t.variable.kind!=="softBin"||t.variable.bin===void 0||l.has(t.id)||!f.has(`${c.variable.bin}:${t.variable.bin}`)||t.effect.direction!==c.effect.direction)continue;o(c,t),l.add(t.id);const h=c.variable.label.replace(/^HBin\s+/,""),m=t.variable.label.replace(/^SBin\s+/,""),d=h===m?`HBin and SBin ${h} (same dies)`:`${c.variable.label} and ${t.variable.label} (same dies)`;c.summary=c.summary.replace(c.variable.label,d),c.variable.label=d;break}if(s.length!==1)continue;const n=r.find(c=>c.variable.kind==="yield");if(n){for(const c of r)if(!(c===n||l.has(c.id)||!((c.variable.kind==="hardBin"||c.variable.kind==="softBin")&&c.variable.bin===s[0]))){o(n,c),l.add(c.id);for(const h of c.absorbedIds??[])n.absorbedIds=[...n.absorbedIds??[],h]}}}}export function analyzeWaferMap(i,a={}){const{resolved:s,warnings:o}=Ke(a),e=Te(i),f=e.isLotStack,l=e.aggrMethod,r=!f||l==="mode"||l==="countBin"||l==="percent",n=e.dies.filter(u=>je(u,s)),c=n.filter(de),t=xe(s,n.length),h=e.dies.filter(u=>ee(u,t)),m=h.filter(de),d=Fe(m,e.wafer,t.ringCount),g=Be(m,e.wafer,t.ringCount),p=t.enableReticlePositionAnalysis?Ce(m,e.reticleConfig):[],S=t.enableTestSiteAnalysis===!1?[]:Pe(h,t.enableTestSiteAnalysis===!0),D=t.enableAngularAnalysis?Ne(m,e.wafer,t.sectorCount):[],y=[];if(t.enableYieldAnalysis&&r&&y.push(...Y(n,d,t.passBins,t),...Y(n,g,t.passBins,t),...Y(n,p,t.passBins,t),...Y(n,S,t.passBins,t),...Y(n,D,t.passBins,t)),t.enableHardBinAnalysis&&r&&y.push(...V(n,d,"hard",e.hbinDefs,"hardBin",t),...V(n,g,"hard",e.hbinDefs,"hardBin",t),...V(n,p,"hard",e.hbinDefs,"hardBin",t),...V(n,S,"hard",e.hbinDefs,"hardBin",t),...V(n,D,"hard",e.hbinDefs,"hardBin",t)),t.enableSoftBinAnalysis){const u=n.filter(M=>M.sbin!==void 0);y.push(...V(u,d,"soft",e.sbinDefs,"softBin",t),...V(u,g,"soft",e.sbinDefs,"softBin",t),...V(u,p,"soft",e.sbinDefs,"softBin",t),...V(u,S,"soft",e.sbinDefs,"softBin",t),...V(u,D,"soft",e.sbinDefs,"softBin",t))}const b=[...o];let $;if(t.enableTestValueAnalysis){const u=Q(n,d,e.testDefs,t),M=Q(n,g,e.testDefs,t),B=Q(n,p,e.testDefs,t),z=Q(n,S,e.testDefs,t),F=Q(n,D,e.testDefs,t);y.push(...u.findings,...M.findings,...B.findings,...z.findings,...F.findings),u.warning&&b.push(u.warning),$=u.activeTestNumbers,y.push(...Qe(n,[d,g,p,S,D],e.testDefs,t)),y.push(...W(n,d,e.testDefs,t),...W(n,g,e.testDefs,t),...W(n,p,e.testDefs,t),...W(n,S,e.testDefs,t),...W(n,D,e.testDefs,t))}if(t.enableClusterAnalysis){const u=Oe(f,r,e.testDefs);(!f||r||u!==void 0)&&y.push(...Le(c,e.wafer,{...t,isFailingDie:u}))}const k=tt(y,{eligibleDies:n,softEligibleDies:n.filter(u=>u.sbin!==void 0),testDies:n,passBins:t.passBins,ringCount:t.ringCount,sectorCount:t.sectorCount,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs});if(y.length=0,y.push(...k),t.enablePatternClassification&&r){const u=Ee(c,e.wafer,{passBins:t.passBins,ringCount:t.ringCount});if(u!==null&&u.pattern!=="random"&&u.pattern!=="none"){let Me=function(C){const L=T(C).map(K=>O(K).ring).filter(K=>K!==void 0);if(L.length===0)return!0;const ce=L.some(K=>K===ze),ue=L.some(K=>K===1);return A==="edge-ring"||A==="edge-local"?ce:A==="center"?ue:A==="donut"?!ce&&!ue:!0};const B={center:"Center cluster",donut:"Donut","edge-ring":"Edge-ring","edge-local":"Edge-local",scratch:"Scratch","near-full":"Near-full"}[u.pattern]??u.pattern,z=u.confidence==="high"?"unusual":u.confidence==="medium"?"notable":"info",F=u.features,N=C=>`${(C*100).toFixed(0)}%`,E=u.pattern==="edge-ring"||u.pattern==="edge-local"?`${N(F.edgeRdd)} of edge dies failing`:u.pattern==="center"||u.pattern==="donut"?`centroid at ${N(F.centroidDistNorm)} of wafer radius from centre`:u.pattern==="scratch"?`linear score ${F.linearScore.toFixed(2)}, eccentricity ${F.eccentricity.toFixed(2)}`:`${N(F.globalRdd)} of dies failing`,P=n.filter(C=>{const L=C.hbin??C.sbin;return L!==void 0&&!new Set(t.passBins).has(L)}),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[u.pattern]??[]),A=u.pattern,ze=t.ringCount,Z=[];for(const C of y){if(!G.has(C.comparison.family)||C.comparison.family==="ring"&&!Me(C))continue;Z.push(C.id);const L=C.comparison.family==="ring"||C.comparison.family==="quadrant"||C.comparison.family==="sector";z!=="info"&&L&&(C.severity="info")}y.push({id:`spatial-pattern:${u.pattern}`,level:"wafer",severity:z,variable:{kind:"spatialPattern",label:B},comparison:{family:"spatial-pattern",left:B,right:"Wafer"},effect:{direction:"different",effectSize:F.globalRdd},stats:{method:"geometry",sampleSizeLeft:P.length,sampleSizeRight:n.length},summary:`Spatial pattern: ${B.toLowerCase()} (${u.confidence} confidence) \u2014 ${E}${u.note?` [${u.note}]`:""}`,highlight:{kind:"dies",dieKeys:P.map(C=>x(C))},relatedIds:Z.length>0?Z:void 0})}}y.sort((u,M)=>{const B=u.stats.adjustedPValue??u.stats.pValue??1,z=M.stats.adjustedPValue??M.stats.pValue??1;return B!==z?B-z:Math.abs(M.effect.absoluteDelta??0)-Math.abs(u.effect.absoluteDelta??0)});const w=He(e.dies,n.length,e.yield.yieldPercent);f&&(w.isLotStack=!0,l&&(w.aggregationMethod=l));const v=qe(e.dies,e.testDefs);v&&(w.testSpecYield=v);const R=computeFunctionalYield(e.dies,e.testDefs);if(R&&(w.functionalYield=R),$===void 0&&t.computePerTestStats){const u=ke(n,t.testNumbers,e.testDefs);u.warning&&b.push(u.warning),$=u.testNumbers}if($?.length){const u=Ie(e.dies,$,e.testDefs,t.minimumSampleSize);u&&(w.perTestStats=u)}return b.length>0&&(w.warnings=b),st(y,n,t.passBins),{level:"wafer",hasNotableFindings:y.some(u=>u.severity==="notable"||u.severity==="unusual"),findings:y,wafer:e.wafer.metadata??void 0,stats:w}}
|
|
@@ -31,6 +31,19 @@ export interface HistogramSeriesData {
|
|
|
31
31
|
* panel that wants to derive a robust fence needs the raw population, and
|
|
32
32
|
* re-walking dies in the chart layer would be a second copy of this loop. */
|
|
33
33
|
export declare function collectTestValues(items: HistogramItem[], testNumber: number): number[];
|
|
34
|
+
/**
|
|
35
|
+
* Min/max of one test's values across `items`, without building an array of
|
|
36
|
+
* them. Both are `NaN` when nothing was measured.
|
|
37
|
+
*
|
|
38
|
+
* A loop rather than `Math.min(...values)`: the spread passes every value as a
|
|
39
|
+
* separate ARGUMENT, and a lot of 25 wafers × ~10k dies overflows the argument
|
|
40
|
+
* limit and throws `RangeError: Maximum call stack size exceeded` — which in the
|
|
41
|
+
* chart layer surfaces as the whole Insights rebuild dying, not as a bad number.
|
|
42
|
+
*/
|
|
43
|
+
export declare function testValueExtent(items: HistogramItem[], testNumber: number): {
|
|
44
|
+
min: number;
|
|
45
|
+
max: number;
|
|
46
|
+
};
|
|
34
47
|
export declare function buildTestHistogramData(items: HistogramItem[], testNumber: number, bucketCount?: number, limitLow?: number, limitHigh?: number,
|
|
35
48
|
/**
|
|
36
49
|
* Bound the bucket range and DROP values outside it. Unlike `limitLow`/
|
|
@@ -56,5 +69,19 @@ clip?: {
|
|
|
56
69
|
export declare function buildTestHistogramSeries(groups: {
|
|
57
70
|
key: string;
|
|
58
71
|
items: HistogramItem[];
|
|
59
|
-
}[], testNumber: number, bucketCount?: number, limitLow?: number, limitHigh?: number
|
|
72
|
+
}[], testNumber: number, bucketCount?: number, limitLow?: number, limitHigh?: number,
|
|
73
|
+
/**
|
|
74
|
+
* Bound the bucket range and DROP values outside it — the faceted twin of
|
|
75
|
+
* `buildTestHistogramData`'s own `clip`, with the same contract: it narrows
|
|
76
|
+
* where the limits only widen, it is this chart's axis control and nothing
|
|
77
|
+
* else, and no statistic anywhere is computed from a clipped population.
|
|
78
|
+
*
|
|
79
|
+
* It exists because the panel's "Clip outliers" checkbox was rendered in the
|
|
80
|
+
* grouped view too and did nothing there — the series builder had no way to
|
|
81
|
+
* take a clip range, so the control silently applied to one branch only.
|
|
82
|
+
*/
|
|
83
|
+
clip?: {
|
|
84
|
+
lo: number;
|
|
85
|
+
hi: number;
|
|
86
|
+
}): HistogramSeriesData;
|
|
60
87
|
//# sourceMappingURL=histogram.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export function collectTestValues(
|
|
1
|
+
export function collectTestValues(d,u){const t=[];for(const i of d)for(const a of i.dies??[]){const s=a.testValues?.[u];s!==void 0&&Number.isFinite(s)&&t.push(s)}return t}export function testValueExtent(d,u){let t=1/0,i=-1/0;for(const a of d)for(const s of a.dies??[]){const n=s.testValues?.[u];n!==void 0&&Number.isFinite(n)&&(n<t&&(t=n),n>i&&(i=n))}return t===1/0?{min:NaN,max:NaN}:{min:t,max:i}}export function buildTestHistogramData(d,u,t=16,i,a,s){let n=collectTestValues(d,u);if(s&&(n=n.filter(e=>e>=s.lo&&e<=s.hi)),n.length===0)return[];let f=n[0],r=n[0];for(let e=1;e<n.length;e++)n[e]<f&&(f=n[e]),n[e]>r&&(r=n[e]);const l=i!==void 0?Math.min(f,i):f,M=((a!==void 0?Math.max(r,a):r)-l||1)/t,h=Array.from({length:t},(e,x)=>({rangeLow:l+x*M,rangeHigh:l+(x+1)*M,count:0}));for(const e of n){const x=Math.min(t-1,Math.floor((e-l)/M));h[x].count++}return h}export function buildTestHistogramSeries(d,u,t=16,i,a,s){const n=new Map;let f=1/0,r=-1/0;for(const o of d){const m=[];for(const y of o.items)for(const p of y.dies??[]){const c=p.testValues?.[u];if(c!==void 0&&Number.isFinite(c)){if(s&&(c<s.lo||c>s.hi))continue;m.push(c),c<f&&(f=c),c>r&&(r=c)}}n.set(o.key,m)}const l=d.map(o=>o.key).filter(o=>n.get(o).length>0);if(l.length===0)return{ranges:[],series:[]};const g=i!==void 0?Math.min(f,i):f,h=((a!==void 0?Math.max(r,a):r)-g||1)/t,e=Array.from({length:t},(o,m)=>({rangeLow:g+m*h,rangeHigh:g+(m+1)*h})),x=l.map(o=>{const m=new Array(t).fill(0);for(const y of n.get(o)){const p=Math.min(t-1,Math.floor((y-g)/h));m[p]++}return{groupKey:o,counts:m}});return{ranges:e,series:x}}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface WaferPopulation {
|
|
2
|
+
waferCount: number;
|
|
3
|
+
/** The lot ID every wafer records, when they all record the same one. */
|
|
4
|
+
lotId?: string;
|
|
5
|
+
/** Distinct lot IDs recorded across the wafers — 0 when none records one. */
|
|
6
|
+
lotCount: number;
|
|
7
|
+
}
|
|
8
|
+
type Metadata = Record<string, unknown> | null | undefined;
|
|
9
|
+
/** One entry per wafer — its `wafer.metadata` (or a summary's `wafer` identity). */
|
|
10
|
+
export declare function describeWaferPopulation(metadatas: readonly Metadata[]): WaferPopulation;
|
|
11
|
+
/**
|
|
12
|
+
* What the set is, for a title: "Lot LOT123 · 13 wafers", "26 wafers from
|
|
13
|
+
* 2 lots", or "13 wafers" (no lot ID recorded).
|
|
14
|
+
*/
|
|
15
|
+
export declare function populationLabel(p: WaferPopulation): string;
|
|
16
|
+
/**
|
|
17
|
+
* A statistic taken across the set, as a noun phrase: "lot median" for one
|
|
18
|
+
* lot, otherwise "median of all wafers". Reads correctly after "the" and
|
|
19
|
+
* "vs".
|
|
20
|
+
*/
|
|
21
|
+
export declare function populationStat(p: WaferPopulation, stat: 'mean' | 'median'): string;
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=population.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function f(n){const t=n?.lot??n?.lotId;return t==null||t===""?void 0:String(t)}export function describeWaferPopulation(n){const t=n.map(f),o=new Set(t.filter(e=>e!==void 0)),u=o.size===1&&t.every(e=>e!==void 0);return{waferCount:n.length,lotCount:o.size,...u?{lotId:[...o][0]}:{}}}const r=n=>`${n} wafer${n===1?"":"s"}`;export function populationLabel(n){return n.lotId!==void 0?`Lot ${n.lotId} \xB7 ${r(n.waferCount)}`:n.lotCount>1?`${r(n.waferCount)} from ${n.lotCount} lots`:r(n.waferCount)}export function populationStat(n,t){return n.lotId!==void 0?`lot ${t}`:`${t} of all wafers`}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import{formatFindingDelta as
|
|
2
|
-
<td class="tight">${
|
|
1
|
+
import{describeWaferPopulation as b,populationLabel as h}from"./population.js";import{formatFindingDelta as g,formatFindingCoverage as v,formatFindingTooltip as y,escHtml as o,buildMetadataRows as d,renderDefinitionList as p,renderSection as c,renderSeverityBadge as R,reportStyles as $}from"./reportHtml.js";import{buildFindingsNarrative as F}from"./findingsNarrative.js";import{plainBinTerms as f}from"../renderer/fmt.js";import{visibleFindings as S}from"./filterFindings.js";function T(e,n){if(e.level==="lot"){const a=e,l=a.perWafer.map(r=>r.summary.wafer?.wafer??r.summary.wafer?.waferId).filter(r=>r!=null).map(String).join(", ");return p([...d(a.perWafer.map(r=>({metadata:r.summary.wafer}))),...l?[{label:"Wafers",value:l}]:[],{label:"Wafer count",value:String(a.stats.waferCount)},{label:"Generated",value:n}])}const t=e,i=t.stats.yieldPercent!==null?`${t.stats.yieldPercent.toFixed(1)}%`:"N/A";return p([...d([{metadata:t.wafer}]),{label:"Total dies",value:String(t.stats.totalDies)},{label:"Analysed dies",value:String(t.stats.analyzedDies)},{label:"Yield",value:i},{label:"Generated",value:n}])}function H(e,n){return e.length?e.map(t=>`<tr title="${o(y(t))}">
|
|
2
|
+
<td class="tight">${R(t.severity)}</td>
|
|
3
3
|
<td class="tight">${o(t.comparison.left)}</td>
|
|
4
|
-
<td>${o(
|
|
5
|
-
<td class="numeric">${o(
|
|
6
|
-
<td class="numeric">${o(
|
|
4
|
+
<td>${o(f(t.variable.label))}</td>
|
|
5
|
+
<td class="numeric">${o(g(t))}</td>
|
|
6
|
+
<td class="numeric">${o(v(t,n))}</td>
|
|
7
7
|
</tr>`).join(`
|
|
8
|
-
`):'<tr><td colspan="5" class="no-data">No significant findings</td></tr>'}function
|
|
8
|
+
`):'<tr><td colspan="5" class="no-data">No significant findings</td></tr>'}function L(e,n){return`<table class="report-table findings-table compact">
|
|
9
9
|
<thead>
|
|
10
10
|
<tr>
|
|
11
11
|
<th>Severity</th>
|
|
@@ -16,17 +16,17 @@ import{formatFindingDelta as h,formatFindingCoverage as w,formatFindingTooltip a
|
|
|
16
16
|
</tr>
|
|
17
17
|
</thead>
|
|
18
18
|
<tbody>
|
|
19
|
-
${
|
|
19
|
+
${H(e,n)}
|
|
20
20
|
</tbody>
|
|
21
|
-
</table>`}export function renderFindingsReportHtml(e,n={}){const t=e.level==="lot",i=n.title??(t?
|
|
22
|
-
`:"",m=[
|
|
21
|
+
</table>`}export function renderFindingsReportHtml(e,n={}){const t=e.level==="lot"?b(e.perWafer.map(w=>w.summary.wafer)):void 0,i=n.title??(t?t.lotId!==void 0?`Lot ${t.lotId} Findings Report`:`Findings Report \u2014 ${h(t)}`:"Wafer Findings Report"),a=new Date().toLocaleString(),l=S(e.findings),r=e.level==="lot"?e.stats.waferCount:void 0,s=f(F(l)??""),u=s?`<p class="findings-narrative">${o(s)}</p>
|
|
22
|
+
`:"",m=[c("Summary",T(e,a)),c("Findings",u+L(l,r))].join(`
|
|
23
23
|
`);return`<!DOCTYPE html>
|
|
24
24
|
<html lang="en">
|
|
25
25
|
<head>
|
|
26
26
|
<meta charset="UTF-8">
|
|
27
27
|
<title>${o(i)}</title>
|
|
28
28
|
<style>
|
|
29
|
-
${
|
|
29
|
+
${$()}
|
|
30
30
|
</style>
|
|
31
31
|
</head>
|
|
32
32
|
<body>
|