@wafertools/wafermap 0.28.0 → 0.30.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 +13 -14
- package/CHANGELOG.md +291 -0
- package/README.md +5 -5
- package/dist/packages/canvas-adapter/deprecated.d.ts +10 -0
- package/dist/packages/canvas-adapter/deprecated.js +1 -0
- package/dist/packages/canvas-adapter/dieList.d.ts +1 -3
- package/dist/packages/canvas-adapter/dieList.js +7 -7
- package/dist/packages/canvas-adapter/index.d.ts +1 -3
- package/dist/packages/canvas-adapter/index.js +1 -1
- package/dist/packages/canvas-adapter/insightsTab.d.ts +0 -1
- package/dist/packages/canvas-adapter/insightsTab.js +1 -1
- package/dist/packages/canvas-adapter/maplessSummary.d.ts +3 -0
- package/dist/packages/canvas-adapter/maplessSummary.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +1 -40
- package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferMap.d.ts +81 -104
- package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
- package/dist/packages/canvas-adapter/summaryPanel.d.ts +11 -8
- package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
- package/dist/packages/canvas-adapter/toCanvas.d.ts +20 -12
- package/dist/packages/canvas-adapter/toCanvas.js +1 -1
- package/dist/packages/canvas-adapter/toolbar.d.ts +2 -0
- 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 +14 -10
- package/dist/packages/canvas-adapter/version.d.ts +2 -2
- package/dist/packages/canvas-adapter/version.js +1 -1
- package/dist/packages/canvas-adapter/warnings.js +1 -1
- package/dist/packages/core/deprecated.d.ts +64 -0
- package/dist/packages/core/deprecated.js +1 -0
- package/dist/packages/core/index.d.ts +1 -3
- package/dist/packages/core/index.js +1 -1
- package/dist/packages/core/passBins.d.ts +33 -0
- package/dist/packages/core/passBins.js +1 -0
- package/dist/packages/core/waferLabel.d.ts +25 -0
- package/dist/packages/core/waferLabel.js +1 -0
- package/dist/packages/renderer/binColors.d.ts +67 -13
- package/dist/packages/renderer/binColors.js +1 -1
- package/dist/packages/renderer/buildView.d.ts +23 -9
- package/dist/packages/renderer/buildView.js +1 -1
- package/dist/packages/renderer/buildWaferMap.d.ts +47 -13
- package/dist/packages/renderer/buildWaferMap.js +1 -1
- package/dist/packages/renderer/colorMap.d.ts +4 -3
- package/dist/packages/renderer/colorSchemes.d.ts +21 -6
- package/dist/packages/renderer/colorSchemes.js +1 -1
- package/dist/packages/renderer/deprecate.d.ts +24 -0
- package/dist/packages/renderer/deprecate.js +1 -0
- package/dist/packages/renderer/deprecated.d.ts +45 -0
- package/dist/packages/renderer/deprecated.js +1 -0
- package/dist/packages/renderer/index.d.ts +2 -3
- package/dist/packages/renderer/index.js +1 -1
- package/dist/packages/stats/analyzeWaferMap.js +1 -1
- package/dist/packages/stats/binPareto.d.ts +6 -1
- package/dist/packages/stats/clusterDetection.d.ts +0 -2
- package/dist/packages/stats/deprecated.d.ts +87 -0
- package/dist/packages/stats/deprecated.js +1 -0
- package/dist/packages/stats/index.d.ts +3 -14
- package/dist/packages/stats/index.js +1 -1
- package/dist/packages/stats/regions.d.ts +7 -2
- package/dist/packages/stats/regions.js +1 -1
- package/dist/packages/stats/renderSummaryReport.d.ts +3 -0
- package/dist/packages/stats/renderSummaryReport.js +34 -34
- package/dist/packages/stats/types.d.ts +0 -43
- package/dist/packages/stats/yield.d.ts +12 -6
- package/dist/packages/stats/yield.js +1 -1
- package/package.json +1 -1
|
@@ -301,6 +301,18 @@ export interface WaferMapInputBase {
|
|
|
301
301
|
* Set to an empty array to suppress yield calculation.
|
|
302
302
|
*/
|
|
303
303
|
passBins?: number[];
|
|
304
|
+
/**
|
|
305
|
+
* Equal-radius rings the wafer is divided into for ring overlays, ring yield and
|
|
306
|
+
* ring findings. Default 4. Set it here, once: the result carries it
|
|
307
|
+
* (`WaferMapResult.ringCount`) and the renderers, analysis, panels and reports all
|
|
308
|
+
* read it, so "Ring 2" names the same dies everywhere. A whole number of at least
|
|
309
|
+
* 1; anything else is corrected and reported as an `analysis-option-corrected`
|
|
310
|
+
* warning. No upper bound, deliberately: a fine banding is still gated by the
|
|
311
|
+
* analysis's minimum region size — at 40 rings on a 561-die wafer the ring
|
|
312
|
+
* findings still carry 16–148 dies each, and a die-count-derived cap rejected
|
|
313
|
+
* 3 rings on 28-die wafers that produce correct findings.
|
|
314
|
+
*/
|
|
315
|
+
ringCount?: number;
|
|
304
316
|
/**
|
|
305
317
|
* Wafer diameters (mm) treated as standard when sanity-checking an *inferred*
|
|
306
318
|
* diameter — see {@link STANDARD_WAFER_DIAMETERS_MM} for the default
|
|
@@ -337,7 +349,7 @@ export interface WaferMapInputBase {
|
|
|
337
349
|
*/
|
|
338
350
|
retestPolicy?: 'last' | 'first' | 'best' | 'worst';
|
|
339
351
|
/**
|
|
340
|
-
* Named test definitions — one per
|
|
352
|
+
* Named test definitions — one per test number in `die.testValues`.
|
|
341
353
|
* When provided, tooltips show `"Idsat: 1.23 A"` instead of `"Values: 1.23"`,
|
|
342
354
|
* and the mode selector offers a per-test dropdown entry.
|
|
343
355
|
*/
|
|
@@ -460,11 +472,11 @@ export interface WaferWarning {
|
|
|
460
472
|
* - `'test-count-capped'` — raised by `analyzeWaferMap`: more tests were found in
|
|
461
473
|
* the die data than the analysis cap allows, so test-value analysis was skipped
|
|
462
474
|
* entirely and NO test findings were produced. Pass `testNumbers` to scope it.
|
|
463
|
-
* - `'analysis-option-corrected'` — raised by `analyzeWaferMap
|
|
475
|
+
* - `'analysis-option-corrected'` — raised by `analyzeWaferMap` (or `buildWaferMap`, for `ringCount`): a numeric option
|
|
464
476
|
* was outside the range that can produce a meaningful analysis and was clamped.
|
|
465
477
|
* The message names each correction. Only reachable from untyped callers now
|
|
466
|
-
* that the statistical thresholds are internal, but a wrong `
|
|
467
|
-
* `
|
|
478
|
+
* that the statistical thresholds are internal, but a wrong `sectorCount`, or a
|
|
479
|
+
* wrong `ringCount` on `buildWaferMap`, still gets here from TypeScript.
|
|
468
480
|
* - `'edge-exclusion-exceeds-radius'` — `waferConfig.edgeExclusion` is larger than
|
|
469
481
|
* the resolved wafer radius (most likely when the diameter was itself inferred
|
|
470
482
|
* from sparse/partial data). The excluded band is clamped to the whole wafer
|
|
@@ -473,11 +485,26 @@ export interface WaferWarning {
|
|
|
473
485
|
* some bins are drawn in the same colour as another bin (more bins than the
|
|
474
486
|
* bin colour scheme has distinct colours, or a `BinDef.color` repeats one).
|
|
475
487
|
* Every die is drawn correctly; colour alone just cannot separate those bins.
|
|
488
|
+
* - `'pass-bins-mixed'` — raised by `renderWaferGallery`: the wafers shown were
|
|
489
|
+
* built with different pass bins, and some hard bins pass on one wafer and fail
|
|
490
|
+
* on another. Each wafer's verdicts and yield are its own and correct; a bin
|
|
491
|
+
* has one colour and one legend row, so those bins show as failing there.
|
|
492
|
+
* - `'ring-count-mixed'` — raised by `renderWaferGallery`: its wafers were built with
|
|
493
|
+
* different `ringCount`s. Each card and each wafer's findings use their own; the
|
|
494
|
+
* lot-level ring figures (Summary panel, report, Insights) use the count named in
|
|
495
|
+
* the message.
|
|
496
|
+
*
|
|
497
|
+
* - `'input-field-removed'` — raised by `buildWaferMap`: the input used a name
|
|
498
|
+
* removed in an earlier release (`data`, `die`, `stack`, `values`,
|
|
499
|
+
* `TestDef.index`, `dieConfig.origin`, `waferConfig.flat`,
|
|
500
|
+
* `reticleConfig.anchor`, `lotStack.aggr`). It was not honoured, so what it
|
|
501
|
+
* described — for `data` and `values`, the data itself — is missing from the
|
|
502
|
+
* map. The message names each one and its replacement.
|
|
476
503
|
*
|
|
477
504
|
* The union is intentionally open to string so future advisory codes can be
|
|
478
505
|
* added without a breaking change; switch with a `default` branch.
|
|
479
506
|
*/
|
|
480
|
-
code: 'partial-coverage' | 'geometry-conflict' | 'non-standard-diameter' | 'diameter-exceeds-die-extent' | 'test-count-capped' | 'edge-exclusion-exceeds-radius' | 'analysis-option-corrected' | (string & {});
|
|
507
|
+
code: 'partial-coverage' | 'geometry-conflict' | 'non-standard-diameter' | 'diameter-exceeds-die-extent' | 'test-count-capped' | 'edge-exclusion-exceeds-radius' | 'analysis-option-corrected' | 'bin-colors-shared' | 'pass-bins-mixed' | 'ring-count-mixed' | 'input-field-removed' | (string & {});
|
|
481
508
|
/** Human-readable explanation, suitable for direct display. */
|
|
482
509
|
message: string;
|
|
483
510
|
/**
|
|
@@ -562,14 +589,6 @@ export interface WaferMapResult {
|
|
|
562
589
|
grid: {
|
|
563
590
|
confidence: number;
|
|
564
591
|
};
|
|
565
|
-
/**
|
|
566
|
-
* @deprecated Use the promoted top-level `WaferMapResult.warnings` instead —
|
|
567
|
-
* it is always present (empty when none) and carries structured
|
|
568
|
-
* `{ code, message, confidence? }` entries you can branch on. This raw
|
|
569
|
-
* string array is retained for backward compatibility and mirrors the
|
|
570
|
-
* `message` of each structured warning.
|
|
571
|
-
*/
|
|
572
|
-
warnings?: string[];
|
|
573
592
|
};
|
|
574
593
|
/**
|
|
575
594
|
* Structured non-fatal advisories raised while inferring geometry from data.
|
|
@@ -599,6 +618,21 @@ export interface WaferMapResult {
|
|
|
599
618
|
/** `filledDies / totalDies` in [0, 1] — `0` when there are no positioned dies, even if `unpositionedDies` is large. */
|
|
600
619
|
ratio: number;
|
|
601
620
|
};
|
|
621
|
+
/**
|
|
622
|
+
* The bins that pass, as given to `buildWaferMap` (default `[1]`) — the ones
|
|
623
|
+
* `yield` was computed with. Carried so everything downstream judges pass/fail
|
|
624
|
+
* the same way: `renderWaferMap`, `renderWaferGallery` (per wafer) and
|
|
625
|
+
* `analyzeWaferMap` all read it, and none of them has a `passBins` option: this
|
|
626
|
+
* is the one place pass bins are set. A map not built by `buildWaferMap`
|
|
627
|
+
* states them in this same field.
|
|
628
|
+
*/
|
|
629
|
+
passBins: number[];
|
|
630
|
+
/**
|
|
631
|
+
* Rings the wafer is divided into (`WaferMapInput.ringCount`, default 4). The
|
|
632
|
+
* renderers, `analyzeWaferMap`, panels and reports read it and none has a ring
|
|
633
|
+
* count option of its own, so ring boundaries and ring findings always agree.
|
|
634
|
+
*/
|
|
635
|
+
ringCount: number;
|
|
602
636
|
/** Yield statistics computed against `passBins`. */
|
|
603
637
|
yield: YieldSummary;
|
|
604
638
|
/** Generated reticle geometry — pass as `viewOptions.reticles` to `renderWaferMap` to show the reticle overlay. */
|
|
@@ -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 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}}
|
|
1
|
+
import{createWafer as q}from"../core/wafer.js";import{isYieldEligibleDie as fe,getDieKey as B,hasPosition as v}from"../core/dies.js";import{applyOrientation as H,transformDies as ce}from"../core/transforms.js";import{affineRotation as le,affineMirror as ue,affineCompose as he,affinePoint as me}from"../core/transforms.js";import{inferWaferFromXY as ge}from"../core/inference/wafer.js";import{resolveGridPitch as pe}from"../core/inference/pitch.js";import{assignGridIndices as ye}from"../core/inference/grid.js";import{generateReticleGrid as xe}from"../core/reticle.js";import{buildView as U}from"./buildView.js";import{modeOf as we}from"../core/utils.js";import{aggregateValues as J,aggregateBinCounts as be}from"../core/aggregates.js";export function isParametricTest(t){return t?.testType!=="F"}function De(t){const n=(l,o)=>({ringCount:l,ringCountWarning:{code:"analysis-option-corrected",severity:"warning",message:`ringCount=${String(t)} ${o} (using ${l}).`}});if(t===void 0)return{ringCount:4,ringCountWarning:void 0};if(typeof t!="number"||!Number.isFinite(t))return n(4,"is not a finite number");const e=Math.max(1,Math.round(t));return e===t?{ringCount:t,ringCountWarning:void 0}:n(e,"is not a whole number of at least 1")}function Q(t){const n=(u,i)=>u!==null&&typeof u=="object"&&u[i]!==void 0,e=Array.isArray(t)?{}:t,l=[],o=(u,i,f)=>{u&&l.push(`\`${i}\` (now \`${f}\`)`)};o(n(e,"data"),"data","results"),o(n(e,"die"),"die","dieConfig"),o(n(e,"stack"),"stack","lotStack"),o(n(e.dieConfig,"origin"),"dieConfig.origin","dieConfig.coordinateOrigin"),o(n(e.waferConfig,"flat"),"waferConfig.flat","waferConfig.notch"),o(n(e.reticleConfig,"anchor"),"reticleConfig.anchor","reticleConfig.anchorDie"),o(n(e.lotStack,"aggr"),"lotStack.aggr","lotStack.method"),o(Array.isArray(e.testDefs)&&e.testDefs.some(u=>n(u,"index")&&!n(u,"testNumber")),"TestDef.index","TestDef.testNumber");const r=n(e.lotStack,"results")?e.lotStack.results:[],h=(Array.isArray(t)?[t]:[e.results,...Array.isArray(r)?r:[]]).filter(u=>Array.isArray(u));if(o(h.some(u=>u.slice(0,100).some(i=>n(i,"values"))),"values","testValues"),!l.length)return;const s=l.length>1,m=`buildWaferMap ignored ${l.join(", ")}: ${s?"these names were":"that name was"} removed in an earlier release, so what ${s?"they":"it"} described is not on this map. Rename and rebuild.`;return console.warn(`[wafermap] ${m}`),{code:"input-field-removed",message:m,severity:"error"}}function Me(t){if(!Array.isArray(t)&&"results"in t&&t.results!==void 0&&"lotStack"in t&&t.lotStack!==void 0)throw new Error("buildWaferMap: pass either `results` or `lotStack`, not both.");return Array.isArray(t)?{results:t,waferOpts:void 0,dieOpts:void 0,explicitDies:void 0,reticleOpts:void 0,lotStackOpts:void 0,passBins:[1],ringCount:4,ringCountWarning:void 0,removedFieldWarning:Q(t),standardDiameters:void 0,testDefs:void 0,hbinDefs:void 0,sbinDefs:void 0,metadataFields:void 0,retestPolicy:"last",edgeDieYieldMode:"exclude"}:{results:t.results??[],waferOpts:t.waferConfig,dieOpts:t.dieConfig,explicitDies:t.dies,reticleOpts:t.reticleConfig,lotStackOpts:t.lotStack,passBins:t.passBins??[1],...De(t.ringCount),removedFieldWarning:Q(t),standardDiameters:t.standardDiameters,testDefs:t.testDefs?.length?t.testDefs:void 0,hbinDefs:t.hbinDefs,sbinDefs:t.sbinDefs,metadataFields:t.metadataFields,retestPolicy:t.retestPolicy??"last",edgeDieYieldMode:t.edgeDieYieldMode??"exclude"}}function Ce(t,n){return n?.coordinateOrigin?n.coordinateOrigin:{type:"center"}}function Oe(t,n,e){if(n.type==="custom"&&n.offset)return{offsetX:n.offset.x,offsetY:n.offset.y};if(n.type!=="center"){let l=1/0,o=-1/0,r=1/0,h=-1/0;for(const s of t)s.x<l&&(l=s.x),s.x>o&&(o=s.x),s.y<r&&(r=s.y),s.y>h&&(h=s.y);return{offsetX:Math.round((o+l)/2),offsetY:Math.round((h+r)/2)}}return{offsetX:e.offsetX,offsetY:e.offsetY}}function Se(t,n,e,l,o,r){if(r)return{colMidX:(Math.round(r.x)-n)*l,colMidY:(Math.round(r.y)-e)*o,anchored:!0};if(t.length===0)return{colMidX:0,colMidY:0,anchored:!1};let h=1/0,s=-1/0,m=1/0,u=-1/0;for(const i of t){const f=Math.round(i.x)-n,c=Math.round(i.y)-e;f<h&&(h=f),f>s&&(s=f),c<m&&(m=c),c>u&&(u=c)}return{colMidX:(h+s)/2*l,colMidY:(m+u)/2*o,anchored:!1}}function ve(t,n,e){let l=1/0,o=-1/0,r=1/0,h=-1/0,s=0,m=0;for(const f of t){const c=Math.round(f.x)-n,d=Math.round(f.y)-e;s+=c,m+=d,c<l&&(l=c),c>o&&(o=c),d<r&&(r=d),d>h&&(h=d)}const u=t.length,i=(f,c,d)=>{const g=(d-c)/2;return g<=0?0:Math.abs(f/u-(c+d)/2)/g};return i(s,l,o)>.11||i(m,r,h)>.11}function Re(t,n){let e=t?.xAxisDirection==="left",l=t?.yAxisDirection==="down";return(n.type==="UL"||n.type==="UR")&&(l=!0),(n.type==="LR"||n.type==="UR")&&(e=!0),{flipX:e,flipY:l}}function ke(t,n){const{method:e,targetBin:l}=t,o=t.results.map(r=>r.filter(v));if(e==="mean"||e==="median"||e==="stddev"||e==="min"||e==="max"||e==="count"){const r=new Set((n??[]).filter(i=>!isParametricTest(i)).map(i=>i.testNumber)),h=new Set;for(const i of o)for(const f of i)if(f.testValues)for(const c of Object.keys(f.testValues)){const d=Number(c);r.has(d)||h.add(d)}if(h.size===0)return J(o,e);const s=[...h],m=s.map(i=>J(o,e,i)),u=new Map;for(let i=0;i<s.length;i++){const f=s[i];for(const c of m[i]){const d=B(c);u.has(d)||u.set(d,{template:c,testValues:{}});const g=u.get(d),p=c.testValues?.[0];p!==void 0&&(g.testValues[f]=p)}}return[...u.values()].map(({template:i,testValues:f})=>({...i,testValues:Object.keys(f).length>0?f:void 0}))}if(e==="countBin"||e==="percent"){if(l===void 0)return[];const r=be(o,l,"hard");if(e==="countBin")return r;const h=o.length;return r.map(s=>({...s,testValues:{0:h>0?(s.testValues?.[0]??0)/h*100:0}}))}if(e==="mode"){const r=new Map;for(const s of o)for(const m of s){const u=B(m);r.has(u)||r.set(u,[]),r.get(u).push(m)}const h=[];for(const[s,m]of r){const u=s.split(","),i=Number(u[0]),f=Number(u[1]),c=m.map(g=>g.hbin).filter(g=>g!==void 0),d=we(c);d!==null&&h.push({x:i,y:f,hbin:d})}return h}return[]}function Z(t){const n=t.filter(v),e=n.length,l=n.filter(r=>r.edgeExcluded).length,o=n.filter(r=>dieHasTestData(r)||r.hbin!==void 0||r.sbin!==void 0).length;return{filledDies:o,totalDies:e,edgeExcludedDies:l,unpositionedDies:t.length-n.length,ratio:e>0?o/e:0}}function ee(t,n,e="exclude"){const l=new Set(n),r=t.filter(d=>!d.partial).filter(d=>d.edgeExcluded).length,h=t.filter(d=>d.partial).length;let s=0,m=0,u=!1;for(const d of t){if(!fe(d))continue;const g=d.hbin??d.sbin;g!==void 0&&(u=!0,l.has(g)?s++:m++)}const i=s+m,f=u&&i>0?s/i*100:null;let c=null;if(e==="denominator-only"){const d=i+r;c=u&&d>0?s/d*100:null}return{passDies:s,failDies:m,edgeExcludedDies:r,partialDies:h,totalDies:i,yieldPercent:f,yieldPercentGross:c}}function te(t,n,e,l,o,r=0,h=0,s=0,m=0,u=0,i=!1,f=!1){if(!t)return[];const c=t.anchorDie??{x:0,y:0},d=xe(n,{width:t.width,height:t.height,diePitchX:l,diePitchY:o,anchorDie:{x:c.x-r,y:c.y-h},gridOrigin:{x:-s,y:-m}}),g=he(ue(i,f,n.center.x,n.center.y),le(u,n.center.x,n.center.y));return d.filter(p=>{const R=p.width/2,k=p.height/2,w=[[p.x-R,p.y-k],[p.x+R,p.y-k],[p.x+R,p.y+k],[p.x-R,p.y+k]].map(([y,N])=>me(g,y,N)),M=Math.min(...w.map(y=>y.x)),A=Math.max(...w.map(y=>y.x)),E=Math.min(...w.map(y=>y.y)),$=Math.max(...w.map(y=>y.y));return e.some(y=>y.physX>=M&&y.physX<A&&y.physY>=E&&y.physY<$)})}function Ae(t,n,e){const o=Math.max(0,n.radius-e)**2;return t.map(r=>{const h=r.physX-n.center.x,s=r.physY-n.center.y;return h*h+s*s>o?{...r,edgeExcluded:!0}:r})}function Ee(t,n,e){const l=t.filter(v),o=t.filter(i=>!v(i)),r=new Map;for(const i of l){let f=r.get(i.x);f||(f=new Map,r.set(i.x,f)),f.set(i.y,(f.get(i.y)??0)+1)}const h=new Set(e);function s(i,f){const c=i.hbin,d=f.hbin;if(c===void 0||d===void 0)return!1;const g=h.has(c),p=h.has(d);return n==="best"?p!==g?p:d<c:p!==g?g:d>c}const m=new Map;for(const i of l){const f=B(i),c=m.get(f);n==="first"&&c||(n==="best"||n==="worst")&&c&&!s(c,i)||m.set(f,i)}return[...Array.from(m.values()).map(i=>{const c=r.get(i.x)?.get(i.y)??1;return c>1?{...i,retestCount:c}:i}),...o]}export function getDieTestValue(t,n){return t.testValues?.[n]}export function getTestPassStatus(t,n,e){const l=t.testPass?.[n];if(l!==void 0)return l;if(e!==void 0&&!isParametricTest(e)){const o=t.testValues?.[n];if(o===0||o===1)return o===1}}export function dieHasTestData(t){return t.testValues!==void 0&&Object.keys(t.testValues).length>0||t.testPass!==void 0&&Object.keys(t.testPass).length>0}function Y(t,n){const e={};return n.hbin!==void 0&&(e.hbin=n.hbin),n.sbin!==void 0&&(e.sbin=n.sbin),n.retestCount!==void 0&&(e.retestCount=n.retestCount),n.siteNum!==void 0&&(e.siteNum=n.siteNum),n.partId!==void 0&&(e.partId=n.partId),n.metadata!==void 0&&(e.metadata=n.metadata),n.testPass!==void 0&&(e.testPass=n.testPass),n.testValues!==void 0&&(e.testValues=n.testValues),{...t,...e}}function ne(t,n){return n.plotMode?n.plotMode:t.some(dieHasTestData)?"value":"hardBin"}function ie(t,n){return t.map(({code:e,message:l})=>({code:e,message:l,severity:e==="non-standard-diameter"||e==="diameter-exceeds-die-extent"?"warning":"error",confidence:n.wafer.confidence}))}const Fe="do not fit inside the supplied",We="Inferred wafer diameter",$e="The probed dies reach only",Ie=.75,Pe=20;export const STANDARD_WAFER_DIAMETERS_MM=[100,125,150,200,300];const Te=.02;function se(t,n){return n.reduce((e,l)=>Math.abs(l-t)<Math.abs(e-t)?l:e)}function _e(t,n){if(!n.length)return!0;const e=se(t,n);return Math.abs(e-t)/e<=Te}function Be(t){return t===void 0?STANDARD_WAFER_DIAMETERS_MM:t.filter(n=>Number.isFinite(n)&&n>0)}export function buildWaferMap(t,n){const e=Me(t),{debug:l,...o}=n??{},r=e.lotStackOpts?ke(e.lotStackOpts,e.testDefs):e.results;for(let a=0;a<Math.min(5,r.length);a++){const x=r[a];if(typeof x.x=="string"||typeof x.y=="string")throw new TypeError("buildWaferMap: x and y must be numbers, received strings. Did you forget to cast CSV values? e.g. { x: +row.x, y: +row.y }")}for(const a of r)if(a.x==null!=(a.y==null))throw new TypeError(`buildWaferMap: die has only one of x/y set (x=${String(a.x)}, y=${String(a.y)}). A die must be either fully positioned (both x and y) or fully unpositioned (neither) \u2014 omit both to mark a die as having no reported position.`);const h=Ee(r,e.retestPolicy,e.passBins),s={wafer:{confidence:1,method:"provided"},diePitch:{confidence:1,units:"mm"},grid:{confidence:1}},m=[];if(e.explicitDies){let a=e.explicitDies;const x=h.filter(v),O=h.filter(_=>!v(_));if(x.length>0){const _=new Map(x.map(S=>[B(S),S]));a=a.map(S=>{const G=_.get(B(S));return G?Y(S,G):S})}const T=e.waferOpts?.diameter??300,W=q({diameter:T,notch:e.waferOpts?.notch,orientation:e.waferOpts?.orientation??0,metadata:e.waferOpts?.metadata});W.orientation!==0&&(a=H(a,W));const z=te(e.reticleOpts,W,a,1,1,0,0,0,0,W.orientation),ae=o.showReticle??e.reticleOpts!==void 0,L=U(W,a,{...o,reticles:z,showReticle:ae,plotMode:ne(h,o),testDefs:e.testDefs,passBins:e.passBins,ringCount:e.ringCount,isLotStack:!1},{hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,metadataFields:e.metadataFields}),de=O.map((_,S)=>Y({id:`unpositioned_${S}`,width:a[0]?.width??1,height:a[0]?.height??1},_)),X=[...a,...de];return{wafer:W,dies:X,view:L,reticleConfig:e.reticleOpts,units:"mm",inference:s,warnings:[...e.removedFieldWarning?[e.removedFieldWarning]:[],...ie(m,s),...e.ringCountWarning?[e.ringCountWarning]:[]],plotMode:L.plotMode,metadata:L.metadata,isLotStack:!1,dataCoverage:Z(X),passBins:e.passBins,ringCount:e.ringCount,yield:ee(X,e.passBins,e.edgeDieYieldMode),reticles:z,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs,metadataFields:e.metadataFields}}const u=h.filter(v),i=h.filter(a=>!v(a)),f=u.map(a=>({x:a.x,y:a.y})),c=pe(f,e.dieOpts,e.waferOpts?.diameter);s.diePitch={confidence:c.confidence,units:c.units};const{pitchX:d,pitchY:g}=c,p=c.units,R=Ce(u,e.dieOpts),k=ye(f);s.grid={confidence:k.confidence};const{offsetX:w,offsetY:M}=Oe(f,R,k),{flipX:A,flipY:E}=Re(e.dieOpts,R),{colMidX:$,colMidY:y,anchored:N}=Se(f,w,M,d,g,e.waferOpts?.center);let b=e.waferOpts?.diameter;const I=f.map(a=>({x:(Math.round(a.x)-w)*d-$,y:(Math.round(a.y)-M)*g-y})),F=I.length>0?Math.max(...I.map(({x:a,y:x})=>Math.hypot(Math.abs(a)+d/2,Math.abs(x)+g/2))):0;if(b===void 0)if(f.length>0)if(p==="mm"){const a=ge(I,{minRadius:F});b=a.diameter,s.wafer={confidence:a.confidence,method:a.method};const x=Be(e.standardDiameters);if(!_e(b,x)){const O=se(b,x);m.push({code:"non-standard-diameter",message:`${We} ${b.toFixed(1)} mm is not a standard wafer size (nearest is ${O} mm). The diameter was derived from the die extent, which assumes the probed grid reaches the wafer edge \u2014 a non-standard result usually means it does not, because the outer dies were never probed. Every die is then placed against a wafer that is too small, which moves dies between rings and changes ring and edge findings. Supply waferConfig.diameter.`})}}else b=F*2,s.wafer={confidence:c.confidence*.8,method:"extent"};else b=p==="mm"?300:30,s.wafer={confidence:0,method:"default"};if(!N&&f.length>0&&ve(f,w,M)&&(s.wafer.method="inferred-partial",m.push({code:"partial-coverage",message:"Wafer geometry was inferred from die positions alone. The data does not span a full symmetric wafer, so the inferred diameter and centre may be wrong and dies may be mis-positioned relative to the true wafer boundary. Supply waferConfig.diameter and waferConfig.center (the prober coordinate of the wafer centre) to position partial data correctly."})),e.waferOpts?.diameter!==void 0&&e.waferOpts.center===void 0&&F>0&&f.length>=Pe){const a=F/(b/2);a<Ie&&m.push({code:"diameter-exceeds-die-extent",message:`${$e} ${(a*100).toFixed(0)}% of the ${b} mm wafer's radius. Either the diameter is larger than the real one, or this is a partial map covering only part of the wafer. Ring bands are equal-radius, so the outer rings are mostly or entirely empty and ring, quadrant and edge findings describe the assumed wafer rather than the probed area. Check waferConfig.diameter, or drop it and let the diameter be inferred from the die extent.`})}if(e.waferOpts?.diameter!==void 0){const a=e.dieOpts?.width!==void 0&&e.dieOpts?.height!==void 0;if(a&&F>b/2+1e-9){const x=I.filter(({x:O,y:T})=>Math.hypot(Math.abs(O)+d/2,Math.abs(T)+g/2)>b/2+1e-9).length;m.push({code:"geometry-conflict",message:`${x} of ${I.length} probed die positions ${Fe} ${b} mm wafer at the supplied die pitch of ${d} \xD7 ${g} mm. A die with test results is a real prober position and is always fully on the wafer, so one of the two supplied values must be wrong: containing these dies at this pitch would need a diameter of at least ${(F*2).toFixed(1)} mm. Check waferConfig.diameter and dieConfig.width/height against the real device.`})}}const C=q({diameter:b,notch:e.waferOpts?.notch,orientation:e.waferOpts?.orientation??0,metadata:e.waferOpts?.metadata});let D=u.map(a=>{const x=Math.round(a.x)-w,O=Math.round(a.y)-M,T={id:`${x}_${O}`,x,y:O,physX:x*d-$,physY:O*g-y,width:d,height:g,insideWafer:!0,partial:!1};return Y(T,a)});(w!==0||M!==0)&&(D=D.map(a=>({...a,x:a.x+w,y:a.y+M,id:`${a.x+w}_${a.y+M}`}))),D=H(D,C),(A||E)&&(D=ce(D,{flipX:A,flipY:E},C.center));const K=[];e.waferOpts?.edgeExclusion&&e.waferOpts.edgeExclusion>0&&(D=Ae(D,C,e.waferOpts.edgeExclusion),e.waferOpts.edgeExclusion>C.radius&&K.push({code:"edge-exclusion-exceeds-radius",message:`edgeExclusion (${e.waferOpts.edgeExclusion}mm) exceeds the resolved wafer radius (${C.radius}mm) \u2014 the entire wafer is excluded.`,severity:"warning"}));const j=te(e.reticleOpts,C,D,d,g,w,M,$,y,C.orientation,A,E),re=o.showReticle??e.reticleOpts!==void 0,P=U(C,D,{...o,reticles:j,showReticle:re,plotMode:ne(h,o),testDefs:e.testDefs,passBins:e.passBins,ringCount:e.ringCount,dataAxisFlip:{x:A,y:E},isLotStack:e.lotStackOpts!==void 0,aggregationMethod:e.lotStackOpts?.method,lotSize:e.lotStackOpts?.results.length},{hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,metadataFields:e.metadataFields}),oe=i.map((a,x)=>Y({id:`unpositioned_${x}`,width:d,height:g},a)),V=[...D,...oe];return{wafer:C,dies:V,view:P,reticleConfig:e.reticleOpts,units:p,inference:s,warnings:[...e.removedFieldWarning?[e.removedFieldWarning]:[],...ie(m,s),...K,...e.ringCountWarning?[e.ringCountWarning]:[]],plotMode:P.plotMode,metadata:P.metadata,isLotStack:e.lotStackOpts!==void 0,dataCoverage:Z(V),passBins:e.passBins,ringCount:e.ringCount,yield:ee(V,e.passBins,e.edgeDieYieldMode),reticles:j,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs,metadataFields:e.metadataFields,aggrMethod:P.aggrMethod,lotSize:P.lotSize}}
|
|
@@ -17,9 +17,10 @@ export declare const NO_DATA_FILL = "#d6d9dd";
|
|
|
17
17
|
* Indices 1–63 are perceptually spread colours generated via golden-angle
|
|
18
18
|
* HSL stepping.
|
|
19
19
|
*
|
|
20
|
-
* No longer used for bins: bin colours
|
|
21
|
-
*
|
|
22
|
-
* list is what made two different bins share a
|
|
20
|
+
* No longer used for bins: bin colours come from a registered bin palette,
|
|
21
|
+
* indexed by bin number and pass/fail, in `resolveBinColors` (binColors.ts).
|
|
22
|
+
* Hashing a bin number into this list is what made two different bins share a
|
|
23
|
+
* colour.
|
|
23
24
|
*/
|
|
24
25
|
export declare const BIN_PALETTE: readonly string[];
|
|
25
26
|
/** Wang hash — maps any integer to a well-distributed unsigned 32-bit value. Shared with colorSchemes. */
|
|
@@ -23,19 +23,34 @@ export declare function listValueColorSchemes(): Array<{
|
|
|
23
23
|
name: string;
|
|
24
24
|
label: string;
|
|
25
25
|
}>;
|
|
26
|
+
/**
|
|
27
|
+
* The colour function a value surface must actually use: the named gradient,
|
|
28
|
+
* flipped when `reversed`.
|
|
29
|
+
*
|
|
30
|
+
* **Every surface that colours by value resolves through this** — die fills
|
|
31
|
+
* (`buildView`), the colorbar (`toCanvas`) and the mapless summary's bars, which
|
|
32
|
+
* each look the gradient up independently. Applying the flip at only some of
|
|
33
|
+
* them would show one reading as two different colours on the same screen, with
|
|
34
|
+
* the colorbar — the thing an engineer reads the map against — the most likely
|
|
35
|
+
* to be missed. Reversing is a flag rather than a second registered entry per
|
|
36
|
+
* ramp so the menu stays one list and a host's own gradient gets it for free.
|
|
37
|
+
*/
|
|
38
|
+
export declare function resolveValueColorFn(name?: string, reversed?: boolean): (t: number) => string;
|
|
26
39
|
export interface BinColorScheme {
|
|
27
40
|
/** Human-readable display name. */
|
|
28
41
|
label: string;
|
|
29
42
|
/**
|
|
30
|
-
* Colours for passing bins,
|
|
31
|
-
*
|
|
43
|
+
* Colours for passing bins, indexed by bin number: pass bin 1 takes the first,
|
|
44
|
+
* pass bin 2 the second, wrapping round. Convention is greens: an engineer
|
|
45
|
+
* reads green as pass before reading the legend.
|
|
32
46
|
*/
|
|
33
47
|
pass: readonly string[];
|
|
34
48
|
/**
|
|
35
|
-
* Colours for failing bins,
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
49
|
+
* Colours for failing bins, indexed by bin number: fail bin 2 takes the
|
|
50
|
+
* first, bin 3 the second, wrapping round. Put the most distinct colours at
|
|
51
|
+
* the front — the low bin numbers most programs use land there. Must not
|
|
52
|
+
* resemble the pass colours — a fail bin that reads as a pass is the most
|
|
53
|
+
* expensive misreading a wafer map can produce.
|
|
39
54
|
*/
|
|
40
55
|
fail: readonly string[];
|
|
41
56
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{VIRIDIS as
|
|
1
|
+
import{VIRIDIS as f,lerpKp as a,valueToGreyscale as i}from"./colorMap.js";const o=new Map;export function registerValueColorScheme(e,l){o.set(e,l)}export function getValueColorScheme(e){return o.get(e??"default")??o.get("default")}export function listValueColorSchemes(){return[...o.entries()].map(([e,l])=>({name:e,label:l.label}))}export function resolveValueColorFn(e,l){const{forValue:t}=getValueColorScheme(e);return l?n=>t(1-n):t}const r=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`);r.set(e,l)}export function getBinColorScheme(e){return r.get(e??"default")??r.get("default")}export function listBinColorSchemes(){return[...r.entries()].map(([e,l])=>({name:e,label:l.label}))}registerValueColorScheme("default",{label:"Default (Viridis)",forValue:e=>a(f,e)});const s=[[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(s,e)}),registerValueColorScheme("greyscale",{label:"Greyscale",forValue:e=>i(e)});const c=[[13,8,135],[126,3,168],[204,71,120],[248,149,64],[240,249,33]];registerValueColorScheme("plasma",{label:"Plasma",forValue:e=>a(c,e)});const u=[[0,0,4],[87,16,110],[188,55,84],[249,142,9],[252,255,164]];registerValueColorScheme("inferno",{label:"Inferno",forValue:e=>a(u,e)});const b=[[11,4,5],[43,28,53],[62,53,107],[59,86,152],[53,123,163],[53,159,171],[75,194,173],[153,221,182],[222,245,229]];registerValueColorScheme("mako",{label:"Mako",forValue:e=>a(b,e)});const d=[[46,204,113],[241,196,15],[231,76,60]];registerValueColorScheme("traffic",{label:"Traffic (Green\u2013Yellow\u2013Red)",forValue:e=>a(d,e)});const p=[[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(p,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"]});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The release that deletes every export deprecated through this helper. Named in
|
|
3
|
+
* each notice and each `@deprecated` tag, and held to by tests/deprecations.test.mjs,
|
|
4
|
+
* which fails once the changelog or package.json reaches it with them still here.
|
|
5
|
+
*/
|
|
6
|
+
export declare const DEPRECATED_REMOVAL_VERSION = "0.31.0";
|
|
7
|
+
/**
|
|
8
|
+
* @internal Every name deprecated through this module, and whether it is a function
|
|
9
|
+
* (which logs a notice) or a value (which cannot). tests/deprecations.test.mjs pins
|
|
10
|
+
* it to the announced list, so a deprecation cannot be added or dropped silently.
|
|
11
|
+
*/
|
|
12
|
+
export declare const DEPRECATED_EXPORTS: Map<string, "function" | "value">;
|
|
13
|
+
/**
|
|
14
|
+
* @internal Wrap `fn` so its first call logs one notice naming `name` and `advice`.
|
|
15
|
+
* The wrapper has exactly `fn`'s type — generics and overloads included — so a
|
|
16
|
+
* deprecated export's signature does not change until it is removed.
|
|
17
|
+
*/
|
|
18
|
+
export declare function deprecated<F extends (...args: never[]) => unknown>(fn: F, name: string, advice: string): F;
|
|
19
|
+
/**
|
|
20
|
+
* @internal Register a deprecated constant. A value cannot log on use, so its
|
|
21
|
+
* `@deprecated` tag and the changelog are the only announcement.
|
|
22
|
+
*/
|
|
23
|
+
export declare function deprecatedValue<T>(value: T, name: string): T;
|
|
24
|
+
//# sourceMappingURL=deprecate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const DEPRECATED_REMOVAL_VERSION="0.31.0",DEPRECATED_EXPORTS=new Map;const n=new Set;export function deprecated(t,e,r){DEPRECATED_EXPORTS.set(e,"function");const o=t;return((...c)=>(n.has(e)||(n.add(e),console.warn(`[wafermap] ${e} is deprecated and will be removed in ${DEPRECATED_REMOVAL_VERSION}. ${r}`)),o(...c)))}export function deprecatedValue(t,e){return DEPRECATED_EXPORTS.set(e,"value"),t}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { valueToViridis as valueToViridisImpl, valueToGreyscale as valueToGreyscaleImpl } from './colorMap.js';
|
|
2
|
+
import { getValueColorScheme as getValueColorSchemeImpl } from './colorSchemes.js';
|
|
3
|
+
/**
|
|
4
|
+
* @deprecated Removed in 0.31.0. Use `resolveValueColorFn('default')`, which returns the same
|
|
5
|
+
* colours through the read-path that also applies `reverseValueScheme`.
|
|
6
|
+
*/
|
|
7
|
+
export declare const valueToViridis: typeof valueToViridisImpl;
|
|
8
|
+
/**
|
|
9
|
+
* @deprecated Removed in 0.31.0. Use `resolveValueColorFn('greyscale')`, which returns the same
|
|
10
|
+
* colours through the read-path that also applies `reverseValueScheme`.
|
|
11
|
+
*/
|
|
12
|
+
export declare const valueToGreyscale: typeof valueToGreyscaleImpl;
|
|
13
|
+
/**
|
|
14
|
+
* @deprecated Removed in 0.31.0. Colour by value with `resolveValueColorFn(name, reversed)`, and list
|
|
15
|
+
* names and labels with `listValueColorSchemes()`. `forValue` ignores
|
|
16
|
+
* `reverseValueScheme`, so a surface built on it can show a reading in a
|
|
17
|
+
* different colour from the map.
|
|
18
|
+
*/
|
|
19
|
+
export declare const getValueColorScheme: typeof getValueColorSchemeImpl;
|
|
20
|
+
import { buildView as buildViewImpl, buildHoverText as buildHoverTextImpl, buildMapTitle as buildMapTitleImpl } from './buildView.js';
|
|
21
|
+
import { resolveBinColors as resolveBinColorsImpl } from './binColors.js';
|
|
22
|
+
import { getBinColorScheme as getBinColorSchemeImpl } from './colorSchemes.js';
|
|
23
|
+
import { contrastTextColor as contrastTextColorImpl } from './colorMap.js';
|
|
24
|
+
import { dieHasTestData as dieHasTestDataImpl, isParametricTest as isParametricTestImpl, getDieTestValue as getDieTestValueImpl } from './buildWaferMap.js';
|
|
25
|
+
/** @deprecated Removed in 0.31.0. It belongs to the low-level drawing pipeline, which is being withdrawn: build with `buildWaferMap` and draw with `renderWaferMap` or `renderWaferGallery`. */
|
|
26
|
+
export declare const buildView: typeof buildViewImpl;
|
|
27
|
+
/** @deprecated Removed in 0.31.0. It belongs to the low-level drawing pipeline, which is being withdrawn: build with `buildWaferMap` and draw with `renderWaferMap` or `renderWaferGallery`. */
|
|
28
|
+
export declare const buildHoverText: typeof buildHoverTextImpl;
|
|
29
|
+
/** @deprecated Removed in 0.31.0. It belongs to the low-level drawing pipeline, which is being withdrawn: build with `buildWaferMap` and draw with `renderWaferMap` or `renderWaferGallery`. */
|
|
30
|
+
export declare const buildMapTitle: typeof buildMapTitleImpl;
|
|
31
|
+
/** @deprecated Removed in 0.31.0. The maps resolve bin colours themselves: set a colour with `BinDef.color`, or register a palette with `registerBinColorScheme`. */
|
|
32
|
+
export declare const resolveBinColors: typeof resolveBinColorsImpl;
|
|
33
|
+
/** @deprecated Removed in 0.31.0. The maps resolve bin colours themselves: set a colour with `BinDef.color`, or register a palette with `registerBinColorScheme`. */
|
|
34
|
+
export declare const getBinColorScheme: typeof getBinColorSchemeImpl;
|
|
35
|
+
/** @deprecated Removed in 0.31.0. It is an internal helper that was exported by accident; `buildWaferMap`, `analyzeWaferMap` and the renderers already apply it. */
|
|
36
|
+
export declare const contrastTextColor: typeof contrastTextColorImpl;
|
|
37
|
+
/** @deprecated Removed in 0.31.0. It is an internal helper that was exported by accident; `buildWaferMap`, `analyzeWaferMap` and the renderers already apply it. */
|
|
38
|
+
export declare const dieHasTestData: typeof dieHasTestDataImpl;
|
|
39
|
+
/** @deprecated Removed in 0.31.0. It is an internal helper that was exported by accident; `buildWaferMap`, `analyzeWaferMap` and the renderers already apply it. */
|
|
40
|
+
export declare const isParametricTest: typeof isParametricTestImpl;
|
|
41
|
+
/** @deprecated Removed in 0.31.0. Read die.testValues[testNumber]. */
|
|
42
|
+
export declare const getDieTestValue: typeof getDieTestValueImpl;
|
|
43
|
+
/** @deprecated Removed in 0.31.0. Pass the sizes you want as buildWaferMap's `standardDiameters`; the default ladder is [100, 125, 150, 200, 300]. */
|
|
44
|
+
export declare const STANDARD_WAFER_DIAMETERS_MM: readonly number[];
|
|
45
|
+
//# sourceMappingURL=deprecated.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{valueToViridis as a,valueToGreyscale as s}from"./colorMap.js";import{getValueColorScheme as i}from"./colorSchemes.js";import{deprecated as e}from"./deprecate.js";export const valueToViridis=e(a,"valueToViridis","Use resolveValueColorFn('default'), which returns the same colours."),valueToGreyscale=e(s,"valueToGreyscale","Use resolveValueColorFn('greyscale'), which returns the same colours."),getValueColorScheme=e(i,"getValueColorScheme","Colour by value with resolveValueColorFn(name, reversed) \u2014 forValue ignores reverseValueScheme \u2014 and list names and labels with listValueColorSchemes().");import{deprecatedValue as n}from"./deprecate.js";import{buildView as p,buildHoverText as c,buildMapTitle as m}from"./buildView.js";import{resolveBinColors as u}from"./binColors.js";import{getBinColorScheme as d}from"./colorSchemes.js";import{contrastTextColor as T}from"./colorMap.js";import{dieHasTestData as h,isParametricTest as I,getDieTestValue as V,STANDARD_WAFER_DIAMETERS_MM as C}from"./buildWaferMap.js";const o="If you depend on it, say so at https://github.com/wafertools/wafermap/issues.",t="It belongs to the low-level drawing pipeline, which is being withdrawn: build with buildWaferMap and draw with renderWaferMap or renderWaferGallery.",l="The maps resolve bin colours themselves: set a colour with BinDef.color, or register a palette with registerBinColorScheme.",r="It is an internal helper that was exported by accident; buildWaferMap, analyzeWaferMap and the renderers already apply it.",D="Read die.testValues[testNumber].";export const buildView=e(p,"buildView",`${t} ${o}`),buildHoverText=e(c,"buildHoverText",`${t} ${o}`),buildMapTitle=e(m,"buildMapTitle",`${t} ${o}`),resolveBinColors=e(u,"resolveBinColors",`${l} ${o}`),getBinColorScheme=e(d,"getBinColorScheme",`${l} ${o}`),contrastTextColor=e(T,"contrastTextColor",`${r} ${o}`),dieHasTestData=e(h,"dieHasTestData",`${r} ${o}`),isParametricTest=e(I,"isParametricTest",`${r} ${o}`),getDieTestValue=e(V,"getDieTestValue",`${D} ${o}`),STANDARD_WAFER_DIAMETERS_MM=n(C,"STANDARD_WAFER_DIAMETERS_MM");
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { getDieKey } from './buildView.js';
|
|
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 { valueToViridis, valueToGreyscale, contrastTextColor, } from './colorMap.js';
|
|
6
5
|
export * from './colorSchemes.js';
|
|
7
|
-
export { resolveBinColors } from './binColors.js';
|
|
8
6
|
export type { BinColors, BinColorOptions } from './binColors.js';
|
|
7
|
+
export { valueToViridis, valueToGreyscale, getValueColorScheme, buildView, buildHoverText, buildMapTitle, resolveBinColors, getBinColorScheme, contrastTextColor, dieHasTestData, isParametricTest, getDieTestValue, STANDARD_WAFER_DIAMETERS_MM, } from './deprecated.js';
|
|
9
8
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export{
|
|
1
|
+
export{getDieKey}from"./buildView.js";export*from"./buildWaferMap.js";export*from"./colorSchemes.js";export{valueToViridis,valueToGreyscale,getValueColorScheme,buildView,buildHoverText,buildMapTitle,resolveBinColors,getBinColorScheme,contrastTextColor,dieHasTestData,isParametricTest,getDieTestValue,STANDARD_WAFER_DIAMETERS_MM}from"./deprecated.js";
|
|
@@ -1 +1 @@
|
|
|
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}}
|
|
1
|
+
import{isYieldEligibleDie as te,getDieKey as x,isPositionedDie as ge}from"../core/dies.js";import{buildWaferMap as Be,getTestPassStatus as he,isParametricTest as A}from"../renderer/buildWaferMap.js";import{buildQuadrantRegions as Fe,buildReticlePositionRegions as Ne,buildRingRegions as Ve,buildSectorRegions as Ce,buildTestSiteRegions as Pe,sectorCompassNames as me,areQuadrantsAdjacent as Le,parseRegionKey as O}from"./regions.js";import{buildClusterFindings as Ee}from"./clusterDetection.js";import{classifyPattern as Ke}from"./patternClassification.js";import{normalCdf as pe}from"./math.js";import{quantile as ne}from"./math.js";import{mean as J,clamp01 as be}from"../core/utils.js";const xe={significanceLevel:{min:1e-6,max:1},minimumEffectSize:{min:0,max:1},minimumRelativeEffect:{min:0,max:Number.MAX_SAFE_INTEGER}},ye=[4,8,16,32];function Te(n){const a=Object.fromEntries(Object.entries(n).filter(([,t])=>t!==void 0)),s={...T,...a},o=[],e=[];for(const t of["significanceLevel","minimumEffectSize","minimumRelativeEffect"]){const{min:l,max:i}=xe[t],u=s[t];if(typeof u!="number"||!Number.isFinite(u)){e.push(`${t}=${String(u)} is not a finite number (using ${T[t]})`),s[t]=T[t];continue}const f=Math.min(i,Math.max(l,u));f!==u&&(e.push(`${t}=${u} is outside ${l}\u2013${i} (using ${f})`),s[t]=f)}if(ye.includes(s.sectorCount)||(e.push(`sectorCount=${s.sectorCount} is not one of ${ye.join(", ")} (using ${T.sectorCount})`),s.sectorCount=T.sectorCount),e.length){const t=`analyzeWaferMap: ${e.join("; ")}. The analysis ran with the corrected values.`;console.warn(`[wafermap] ${t}`),o.push({code:"analysis-option-corrected",message:t,severity:"warning"})}const c=["passBins","ringCount"].filter(t=>a[t]!==void 0);if(c.length){const t=`analyzeWaferMap: ${c.join(" and ")} ${c.length>1?"are":"is"} no longer an analysis option and was ignored. Set ${c.length>1?"them":"it"} on buildWaferMap; the analysis used the value the map was built with.`;console.warn(`[wafermap] ${t}`),o.push({code:"analysis-option-corrected",message:t,severity:"warning"})}const r=["enableYieldAnalysis","enableHardBinAnalysis","enableSoftBinAnalysis","enableReticlePositionAnalysis","enableTestSiteAnalysis","enableClusterAnalysis","enableAngularAnalysis","enablePatternClassification"].filter(t=>a[t]!==void 0);if(r.length){const t=`analyzeWaferMap: ${r.join(", ")} ${r.length>1?"are":"is"} no longer an option and was ignored: every analysis now runs. Filter the findings (filterFindings) to show fewer.`;console.warn(`[wafermap] ${t}`),o.push({code:"analysis-option-corrected",message:t,severity:"warning"})}return{resolved:s,warnings:o}}const T={ringCount:4,passBins:[1],significanceLevel:.05,minimumEffectSize:.2,minimumRelativeEffect:1,minimumSampleSize:5,enableTestValueAnalysis:!1,computePerTestStats:!1,sectorCount:8,minimumClusterSize:5};function je(n,a){const s={...n};return s.minimumClusterSize=Math.max(5,Math.round(a*.003)),s}function Ae(n){return"wafer"in n&&"dies"in n&&"view"in n?n:Be(n)}function Oe(n){return te(n)?n.hbin!==void 0||n.sbin!==void 0||n.testValues!==void 0&&Object.keys(n.testValues).length>0:!1}function qe(n,a,s){if(!n||a)return;const o=(s??[]).filter(e=>A(e)&&(e.limitLow!==void 0||e.limitHigh!==void 0));if(o.length!==0)return e=>{for(const c of o){const r=c.testNumber,t=e.testValues?.[r];if(t!==void 0&&(c.limitLow!==void 0&&t<c.limitLow||c.limitHigh!==void 0&&t>c.limitHigh))return!0}return!1}}function He(n,a,s){const o=new Set,e=new Set,c=new Set,r=new Map,t=new Map;for(const l of n){if(l.testValues)for(const i of Object.keys(l.testValues))o.add(Number(i));if(l.testPass)for(const i of Object.keys(l.testPass))o.add(Number(i));l.hbin!==void 0&&e.add(l.hbin),l.sbin!==void 0&&c.add(l.sbin),te(l)&&(l.hbin!==void 0&&r.set(l.hbin,(r.get(l.hbin)??0)+1),l.sbin!==void 0&&t.set(l.sbin,(t.get(l.sbin)??0)+1))}return{totalDies:n.length,analyzedDies:a,excludedDies:n.length-a,yieldPercent:s,testsConsidered:[...o].sort((l,i)=>l-i),hardBinsConsidered:[...e].sort((l,i)=>l-i),softBinsConsidered:[...c].sort((l,i)=>l-i),...r.size?{hardBinCounts:Object.fromEntries(r)}:{},...t.size?{softBinCounts:Object.fromEntries(t)}:{}}}function Ie(n,a){if(!a?.length)return;const s=a.filter(e=>A(e)&&(e.limitLow!==void 0||e.limitHigh!==void 0));if(!s.length)return;const o=[];for(const e of s){const c=e.testNumber;let r=0,t=0,l=0,i=0;for(const u of n){if(u.partial||u.edgeExcluded)continue;const f=u.testValues?.[c];f!==void 0&&(i++,e.limitLow!==void 0&&f<e.limitLow?t++:e.limitHigh!==void 0&&f>e.limitHigh?l++:r++)}o.push({testNumber:c,label:e.name,passDies:r,failLowDies:t,failHighDies:l,totalDies:i,yieldPercent:i>0?r/i*100:null})}return o.length?o:void 0}export function computeFunctionalYield(n,a){if(!a?.length)return;const s=a.filter(e=>!A(e));if(!s.length)return;const o=[];for(const e of s){const c=e.testNumber;let r=0,t=0;for(const i of n){if(i.partial||i.edgeExcluded)continue;const u=he(i,c,e);u!==void 0&&(u?r++:t++)}const l=r+t;o.push({testNumber:c,label:e.name,passDies:r,failDies:t,totalDies:l,passRatePercent:l>0?r/l*100:null})}return o.length?o:void 0}function _e(n,a,s,o){const e=[];for(const c of a){const r=[];for(const f of n){if(f.partial||f.edgeExcluded)continue;const h=f.testValues?.[c];h!==void 0&&r.push(h)}if(r.length<o)continue;const t=J(r),l=Math.sqrt(fe(r,t)),i=r.slice().sort((f,h)=>f-h),u=s?.find(f=>f.testNumber===c)?.name??String(c);e.push({testNumber:c,label:u,count:r.length,min:i[0],max:i[i.length-1],mean:t,stddev:l,median:ne(i,.5),q1:ne(i,.25),q3:ne(i,.75)})}return e.length?e:void 0}function q(n,a,s,o){const e=(n+s)/(a+o),c=e*(1-e)*(1/a+1/o);if(!Number.isFinite(c)||c<=0)return 1;const r=(n/a-s/o)/Math.sqrt(c);return be(2*(1-pe(Math.abs(r))))}function ie(n){const a=new Map;for(const s of n){const o=`${s.variable.kind}:${s.comparison.family}`,e=a.get(o)??[];e.push(s),a.set(o,e)}for(const s of a.values()){const o=[...s].filter(r=>r.stats.pValue!==void 0).sort((r,t)=>(r.stats.pValue??1)-(t.stats.pValue??1)),e=o.length;let c=1;for(let r=e-1;r>=0;r--){const t=o[r],l=t.stats.pValue??1,i=Math.min(1,Math.min(c,l*e/(r+1)));t.stats.adjustedPValue=i,c=i}}return n}function se(n,a,s){const o=Math.abs(a),e=s!==void 0?Math.abs(s):0;return n<=.01&&(o>=.3||e>=2.5)?"unusual":n<=.05&&(o>=.2||e>=1.5)?"notable":"info"}function oe(n,a){return n<=.01&&Math.abs(a)>=.5?"unusual":n<=.05&&Math.abs(a)>=.15?"notable":"info"}function Z(n){return n==="reticle-position"?"other reticle positions":n==="test-site"?"other test sites":"the rest of the map"}function H(n){return n==="reticle-position"?"Other reticle positions":n==="test-site"?"Other test sites":"Rest of map"}function Se(n,a,s){const o=(Math.abs(a)*100).toFixed(1);return`${n} yield is ${o} percentage points ${a>0?"higher":"lower"} than ${Z(s)}`}function ae(n,a){return a==="quadrant"&&!n.startsWith("Quadrant")?`quadrant ${n}`:n}function we(n,a,s,o){const e=ae(n,o),c=(Math.abs(s)*100).toFixed(1);return`${e} has ${a} occurrence ${c} percentage points ${s>0?"higher":"lower"} than ${Z(o)}`}function We(n,a,s,o){const e=ae(n,o),c=(Math.abs(s)*100).toFixed(1);return`${e} has ${a} pass rate ${c} percentage points ${s>0?"higher":"lower"} than ${Z(o)}`}function $e(n,a,s,o,e,c){const r=ae(n,e),t=Z(e),l=s>0?"higher":"lower";if(o!==void 0&&Number.isFinite(o)){const u=(Math.abs(o)*100).toFixed(1);return`${r} mean ${a} is ${u}% ${l} than ${t}`}const i=c?` ${c}`:"";return`${r} mean ${a} is ${Math.abs(s).toPrecision(3)}${i} ${l} than ${t}`}function ve(n,a,s){const o=a?.find(e=>e.bin===n);return o?.name?`${s} ${n} (${o.name})`:`${s} ${n}`}function Ye(n,a){const s=a?.find(o=>o.testNumber===n);return{label:s?.name??`Test ${n}`,unit:s?.unit}}function re(n,a){const s=new Map(n.map(e=>[x(e),e])),o=new Map;for(const e of a){const c=[];for(const r of e.dieKeys){const t=s.get(r);t&&c.push(t)}o.set(e.key,c)}return o}function le(n,a){return ie(n),n.filter(s=>{const o=s.stats.adjustedPValue??s.stats.pValue??1,e=Math.abs(s.effect.absoluteDelta??0),c=Math.abs(s.effect.relativeDelta??0);return o<=a.significanceLevel&&(e>=a.minimumEffectSize||c>=a.minimumRelativeEffect)}).map(s=>({...s,severity:se(s.stats.adjustedPValue??s.stats.pValue??1,s.effect.absoluteDelta??0,s.effect.relativeDelta)}))}function W(n,a,s,o){const e=new Set(s),c=re(n,a),r=new Map,t=new Map;for(const[i,u]of c){let f=0,h=0;for(const d of u)d.hbin!==void 0&&(h++,e.has(d.hbin)&&f++);r.set(i,f),t.set(i,h)}const l=[];for(const i of a){const u=t.get(i.key)??0,f=r.get(i.key)??0;let h=0,d=0;for(const[$,m]of t)$!==i.key&&(h+=m,d+=r.get($)??0);if(u<o.minimumSampleSize||h<o.minimumSampleSize)continue;const p=f/u,b=d/h,y=p-b,z=q(f,u,d,h);l.push({id:`yield:${i.key}`,level:"wafer",severity:"info",variable:{kind:"yield",label:"Yield"},comparison:{family:i.family,left:i.label,right:H(i.family)},effect:{direction:y===0?"different":y>0?"higher":"lower",absoluteDelta:y,relativeDelta:b===0?void 0:y/b,effectSize:y},stats:{method:"two-proportion-z",pValue:z,sampleSizeLeft:u,sampleSizeRight:h},summary:Se(i.label,y,i.family),highlight:{kind:"region",regionFamily:i.family,regionKeys:[i.key],dieKeys:[...i.dieKeys]}})}return le(l,o)}function P(n,a,s,o,e,c){const r=d=>s==="soft"?d.sbin:d.hbin,t=[...new Set(n.map(r).filter(d=>d!==void 0))].sort((d,p)=>d-p),l=re(n,a),i=new Map,u=new Map;for(const[d,p]of l){const b=new Map;for(const y of p){const z=r(y);z!==void 0&&b.set(z,(b.get(z)??0)+1)}i.set(d,b),u.set(d,p.length)}const f=[],h=e==="hardBin"?"HBin":"SBin";for(const d of a){const p=u.get(d.key)??0,b=i.get(d.key);let y=0;const z=new Map;for(const[$,m]of i)if($!==d.key){y+=u.get($)??0;for(const[w,D]of m)z.set(w,(z.get(w)??0)+D)}if(!(p<c.minimumSampleSize||y<c.minimumSampleSize))for(const $ of t){const m=b.get($)??0,w=z.get($)??0,D=m/p,v=w/y,S=D-v,R=q(m,p,w,y),M=ve($,o,h);f.push({id:`${e}:${$}:${d.key}`,level:"wafer",severity:"info",variable:{kind:e,bin:$,label:M},comparison:{family:d.family,left:d.label,right:H(d.family)},effect:{direction:S===0?"different":S>0?"higher":"lower",absoluteDelta:S,relativeDelta:v===0?void 0:S/v,effectSize:S},stats:{method:"two-proportion-z",pValue:R,sampleSizeLeft:p,sampleSizeRight:y},summary:we(d.label,M,S,d.family),highlight:{kind:"bin",bin:$,regionKeys:[d.key],dieKeys:[...d.dieKeys]}})}}return le(f,c)}function Y(n,a,s,o){const e=(s??[]).filter(u=>!A(u));if(!e.length)return[];const c=e.map(u=>u.testNumber),r=re(n,a),t=new Map,l=new Map;for(const[u,f]of r){const h=new Uint32Array(e.length),d=new Uint32Array(e.length);for(const p of f)for(let b=0;b<e.length;b++){const y=he(p,c[b],e[b]);y!==void 0&&(d[b]++,y&&h[b]++)}t.set(u,h),l.set(u,d)}const i=[];for(const u of a){const f=t.get(u.key),h=l.get(u.key);for(let d=0;d<e.length;d++){let p=0,b=0;for(const[S,R]of l)S!==u.key&&(b+=R[d],p+=t.get(S)[d]);const y=h[d],z=f[d];if(y<o.minimumSampleSize||b<o.minimumSampleSize)continue;const $=z/y,m=p/b,w=$-m,D=q(z,y,p,b),v=c[d];i.push({id:`functional:${v}:${u.key}`,level:"wafer",severity:"info",variable:{kind:"functionalTest",index:v,label:`${e[d].name} pass rate`},comparison:{family:u.family,left:u.label,right:H(u.family)},effect:{direction:w===0?"different":w>0?"higher":"lower",absoluteDelta:w,relativeDelta:m===0?void 0:w/m,effectSize:w},stats:{method:"two-proportion-z",pValue:D,sampleSizeLeft:y,sampleSizeRight:b},summary:We(u.label,e[d].name,w,u.family),highlight:{kind:"region",regionFamily:u.family,regionKeys:[u.key],dieKeys:[...u.dieKeys]}})}}return le(i,o)}function fe(n,a){return n.length<2?0:n.reduce((s,o)=>s+(o-a)**2,0)/(n.length-1)}function De(n,a,s,o,e,c){const r=Math.sqrt(s/n+c/o),t=a-e;if(!Number.isFinite(r)||r===0)return{pValue:1,effectSize:0,delta:t};const l=t/r,i=Math.sqrt(Math.max(0,(s+c)/2)),u=i===0?t:t/i;return{pValue:be(2*(1-pe(Math.abs(l)))),effectSize:u,delta:t}}function Qe(n,a){const s=J(n),o=J(a);return De(n.length,s,fe(n,s),a.length,o,fe(a,o))}const ke=250;function ze(n,a,s){const o=r=>r.filter(t=>A(s?.find(l=>l.testNumber===t)));if(a)return{testNumbers:o(a.slice().sort((r,t)=>r-t))};const e=new Set;let c=!1;e:for(const r of n)for(const t of Object.keys(r.testValues??{})){const l=Number(t);if(!e.has(l)&&(e.add(l),e.size>ke)){c=!0;break e}}if(c){const r=`Test-value analysis was skipped: more than ${ke} tests were found in the die data, so no test findings were computed. Pass testNumbers: [...] to analyse specific tests.`;return console.warn(`[wafermap] analyzeWaferMap: ${r}`),{testNumbers:[],warning:{code:"test-count-capped",message:r,severity:"warning"}}}return{testNumbers:o([...e].sort((r,t)=>r-t))}}function Q(n,a,s,o){const e=ze(n,o.testNumbers,s);if(e.warning)return{findings:[],warning:e.warning};const c=e.testNumbers,r=new Map;for(let p=0;p<a.length;p++)for(const b of a[p].dieKeys)r.set(b,p);const t=a.length,l=[],i=[];for(const p of n){const b=r.get(x(p));b!==void 0&&(l.push(p),i.push(b))}const u=[],f=new Float64Array(t),h=new Float64Array(t),d=new Float64Array(t);for(const p of c){f.fill(0),h.fill(0),d.fill(0);let b=0,y=0,z=0,$;for(let m=0;m<l.length;m++){const D=l[m].testValues?.[p];if(D===void 0)continue;$===void 0&&($=D);const v=D-$,S=i[m];f[S]+=1,h[S]+=v,d[S]+=v*v,b+=1,y+=v,z+=v*v}if($!==void 0)for(let m=0;m<t;m++){const w=f[m],D=b-w;if(w<o.minimumSampleSize||D<o.minimumSampleSize)continue;const v=h[m]/w,R=(y-h[m])/D,M=v+$,g=R+$,B=w>1?Math.max(0,d[m]-w*v*v)/(w-1):0,k=D>1?Math.max(0,z-d[m]-D*R*R)/(D-1):0,{pValue:V,effectSize:F,delta:L}=De(w,M,B,D,g,k),C=a[m],{label:_,unit:U}=Ye(p,s),G=g!==0?L/Math.abs(g):void 0;u.push({id:`test:${p}:${C.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:p,label:_,unit:U},comparison:{family:C.family,left:C.label,right:H(C.family)},effect:{direction:L===0?"different":L>0?"higher":"lower",absoluteDelta:L,relativeDelta:G,effectSize:F},stats:{method:"welch-z-approx",pValue:V,sampleSizeLeft:w,sampleSizeRight:D},summary:$e(C.label,_,L,G,C.family,U),highlight:{kind:"region",regionFamily:C.family,regionKeys:[C.key],dieKeys:[...C.dieKeys]}})}}return ie(u),{activeTestNumbers:c,findings:u.filter(p=>{const b=p.stats.adjustedPValue??p.stats.pValue??1,y=Math.abs(p.effect.effectSize??0);return b<=o.significanceLevel&&y>=o.minimumEffectSize}).map(p=>({...p,severity:oe(p.stats.adjustedPValue??p.stats.pValue??1,p.effect.effectSize??0)}))}}function Ue(n,a,s,o){if(!s?.length)return[];const e=s.filter(t=>A(t)&&(t.limitLow!==void 0||t.limitHigh!==void 0));if(!e.length)return[];const c=[],r=new Map(n.map(t=>[x(t),t]));for(const t of e){const l=t.testNumber;for(const i of a){const u=new Map;for(const h of i){const d=[];for(const p of h.dieKeys){const b=r.get(p);b&&d.push(b)}u.set(h.key,d)}const f=[];for(const h of i){const d=u.get(h.key),p=[];for(const[M,g]of u)if(M!==h.key)for(const B of g)p.push(B);const b=M=>M.testValues?.[l]!==void 0,y=M=>{const g=M.testValues?.[l];return g===void 0?!1:t.limitLow!==void 0&&g<t.limitLow||t.limitHigh!==void 0&&g>t.limitHigh},z=d.filter(b),$=p.filter(b);if(z.length<o.minimumSampleSize||$.length<o.minimumSampleSize)continue;const m=z.filter(y).length,w=$.filter(y).length,D=m/z.length,v=w/$.length,S=D-v,R=q(m,z.length,w,$.length);f.push({id:`specLimit:${l}:${h.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:l,label:t.name,unit:t.unit},comparison:{family:h.family,left:h.label,right:H(h.family)},effect:{direction:S===0?"different":S>0?"higher":"lower",absoluteDelta:S,relativeDelta:v===0?void 0:S/v,effectSize:S},stats:{method:"two-proportion-z",pValue:R,sampleSizeLeft:z.length,sampleSizeRight:$.length},summary:`${h.label} spec-fail rate for ${t.name} is ${(Math.abs(S)*100).toFixed(1)} pp ${S>0?"higher":"lower"} than the rest of the wafer`,highlight:{kind:"region",regionFamily:h.family,regionKeys:[h.key],dieKeys:[...h.dieKeys]}})}ie(f),c.push(...f.filter(h=>{const d=h.stats.adjustedPValue??h.stats.pValue??1,p=Math.abs(h.effect.absoluteDelta??0),b=Math.abs(h.effect.relativeDelta??0);return d<=o.significanceLevel&&(p>=o.minimumEffectSize||b>=o.minimumRelativeEffect)}).map(h=>({...h,severity:oe(h.stats.adjustedPValue??h.stats.pValue??1,h.effect.effectSize??0)})))}}return c}function j(n){return n.highlight.regionKeys??[]}function Ge(n){const a=n.variable.bin??n.variable.index??"";return`${n.comparison.family}\0${n.variable.kind}\0${a}\0${n.effect.direction}`}function I(n,a){const s=j(n)[0]??"",o=O(s);return o.family==="ring"?o.ring??0:o.family==="sector"?me(a).indexOf(o.sector??""):0}function Xe(n,a,s){if(a==="quadrant"){const r=new Map;for(const u of n){const f=O(j(u)[0]??"").quadrant;f&&r.set(f,u)}const t=[...r.keys()],l=new Set,i=[];for(const u of t){if(l.has(u))continue;const f=[],h=[u];for(l.add(u);h.length;){const d=h.pop();f.push(d);for(const p of t)!l.has(p)&&Le(d,p)&&(l.add(p),h.push(p))}i.push(f.map(d=>r.get(d)))}return i}const o=[...n].sort((r,t)=>I(r,s)-I(t,s)),e=[];let c=[];for(const r of o){if(c.length===0){c.push(r);continue}const t=I(c[c.length-1],s);I(r,s)-t===1?c.push(r):(e.push(c),c=[r])}if(c.length&&e.push(c),a==="sector"&&e.length>1){const t=me(s).length,l=I(e[0][0],s),i=I(e[e.length-1][e[e.length-1].length-1],s);if(l===0&&i===t-1){const u=e.pop();e[0]=[...u,...e[0]]}}return e}function Je(n,a){const s=n.map(r=>O(j(r)[0]??"").ring).filter(r=>r!==void 0).sort((r,t)=>r-t),o=s[0],e=s[s.length-1];let c="";return o===e&&o===1&&a>1?c=" (core)":o===e&&o===a&&a>1&&(c=" (edge)"),o===e?`Ring ${o}${c}`:`Rings ${o}\u2013${e}`}function Ze(n,a){const s=n.map(o=>O(j(o)[0]??"").sector??"");return s.length===1?`Sector ${s[0]}`:`Sectors ${s[0]}\u2013${s[s.length-1]}`}function et(n){const a=n.map(s=>O(j(s)[0]??"").quadrant??"");return a.length===1?a[0]:a.length===2?`Quadrants ${a[0]} & ${a[1]}`:`Quadrants ${a.slice(0,-1).join(", ")} & ${a[a.length-1]}`}function ce(n){return[...new Set(n)]}function tt(n,a){const s=n[0],o=s.comparison.family,e=s.variable.kind,c=ce(n.flatMap(m=>m.highlight.dieKeys??[])),r=ce(n.flatMap(j)),t=new Set(c),l=o==="ring"?Je(n,a.ringCount):o==="sector"?Ze(n,a.sectorCount):et(n),i=e==="softBin"?a.softEligibleDies:e==="test"?a.testDies:a.eligibleDies,u=i.filter(m=>t.has(x(m))),f=i.filter(m=>!t.has(x(m)));let h,d,p,b,y;if(e==="test"){const m=s.variable.index,w=k=>k.testValues?.[m],D=u.map(w).filter(k=>k!==void 0),v=f.map(w).filter(k=>k!==void 0),{pValue:S,effectSize:R,delta:M}=Qe(D,v),g=J(v),B=g!==0?M/Math.abs(g):void 0;h={direction:M===0?"different":M>0?"higher":"lower",absoluteDelta:M,relativeDelta:B,effectSize:R},d={method:"welch-z-approx",pValue:S,sampleSizeLeft:D.length,sampleSizeRight:v.length},p=oe(S,R),b=$e(l,s.variable.label,M,B,o,s.variable.unit),y=`test:${m}`}else if(e==="yield"){const m=new Set(a.passBins),w=u.filter(F=>F.hbin!==void 0),D=f.filter(F=>F.hbin!==void 0),v=w.length,S=D.length,R=w.filter(F=>m.has(F.hbin)).length,M=D.filter(F=>m.has(F.hbin)).length,g=v>0?R/v:0,B=S>0?M/S:0,k=g-B,V=q(R,v,M,S);h={direction:k===0?"different":k>0?"higher":"lower",absoluteDelta:k,relativeDelta:B===0?void 0:k/B,effectSize:k},d={method:"two-proportion-z",pValue:V,sampleSizeLeft:v,sampleSizeRight:S},p=se(V,k,h.relativeDelta),b=Se(l,k,o),y="yield"}else{const m=s.variable.bin,w=k=>e==="softBin"?k.sbin:k.hbin,D=u.filter(k=>w(k)===m).length,v=f.filter(k=>w(k)===m).length,S=D/u.length,R=v/f.length,M=S-R,g=q(D,u.length,v,f.length);h={direction:M===0?"different":M>0?"higher":"lower",absoluteDelta:M,relativeDelta:R===0?void 0:M/R,effectSize:M},d={method:"two-proportion-z",pValue:g,sampleSizeLeft:u.length,sampleSizeRight:f.length},p=se(g,M,h.relativeDelta);const B=e==="softBin"?a.sbinDefs:a.hbinDefs;b=we(l,ve(m,B,e==="softBin"?"SBin":"HBin"),M,o),y=`${e}:${m}`}const z=ce(r.map(m=>m.replace(`${o}:`,""))).join("-"),$=e==="hardBin"||e==="softBin"?{kind:"bin",bin:s.variable.bin,regionKeys:r,dieKeys:c}:{kind:"region",regionFamily:o,regionKeys:r,dieKeys:c};return{...s,id:`${y}:${o}:${z}`,severity:p,comparison:{family:o,left:l,right:H(o)},effect:h,stats:d,summary:b,highlight:$,relatedIds:n.map(m=>m.id)}}function nt(n,a){const s=new Set(["ring","quadrant","sector"]),o=[],e=new Map;for(const r of n){if(!s.has(r.comparison.family)){o.push(r);continue}const t=Ge(r),l=e.get(t)??[];l.push(r),e.set(t,l)}const c=[];for(const r of e.values()){if(r.length===1){c.push(r[0]);continue}const t=r[0].comparison.family;for(const l of Xe(r,t,a.sectorCount))c.push(l.length===1?l[0]:tt(l,a))}return[...o,...c]}function it(n){const a=new Map,s=new Map;for(const e of n){const c=x(e);e.hbin!==void 0&&e.hbin!==null&&(a.get(e.hbin)??a.set(e.hbin,new Set).get(e.hbin)).add(c),e.sbin!==void 0&&e.sbin!==null&&(s.get(e.sbin)??s.set(e.sbin,new Set).get(e.sbin)).add(c)}const o=new Set;for(const[e,c]of a)for(const[r,t]of s){if(c.size!==t.size)continue;let l=!0;for(const i of c)if(!t.has(i)){l=!1;break}l&&o.add(`${e}:${r}`)}return o}function st(n){return`${n.comparison.family}\0${n.comparison.left}`}function ot(n,a,s){const o=(t,l)=>{t.absorbedIds=[...t.absorbedIds??[],l.id]},e=new Map;for(const t of n){const l=st(t);(e.get(l)??e.set(l,[]).get(l)).push(t)}const c=it(a),r=new Set;for(const t of e.values()){for(const i of t)if(!(i.variable.kind!=="hardBin"||i.variable.bin===void 0))for(const u of t){if(u.variable.kind!=="softBin"||u.variable.bin===void 0||r.has(u.id)||!c.has(`${i.variable.bin}:${u.variable.bin}`)||u.effect.direction!==i.effect.direction)continue;o(i,u),r.add(u.id);const f=i.variable.label.replace(/^HBin\s+/,""),h=u.variable.label.replace(/^SBin\s+/,""),d=f===h?`HBin and SBin ${f} (same dies)`:`${i.variable.label} and ${u.variable.label} (same dies)`;i.summary=i.summary.replace(i.variable.label,d),i.variable.label=d;break}if(s.length!==1)continue;const l=t.find(i=>i.variable.kind==="yield");if(l){for(const i of t)if(!(i===l||r.has(i.id)||!((i.variable.kind==="hardBin"||i.variable.kind==="softBin")&&i.variable.bin===s[0]))){o(l,i),r.add(i.id);for(const f of i.absorbedIds??[])l.absorbedIds=[...l.absorbedIds??[],f]}}}}export function analyzeWaferMap(n,a={}){const{resolved:s,warnings:o}=Te(a),e=Ae(n),c={...s,passBins:e.passBins??T.passBins,ringCount:e.ringCount??T.ringCount},r=e.isLotStack,t=e.aggrMethod,l=!r||t==="mode"||t==="countBin"||t==="percent",i=e.dies.filter(g=>Oe(g)),u=i.filter(ge),f=je(c,i.length),h=e.dies.filter(g=>te(g)),d=h.filter(ge),p=Ve(d,e.wafer,f.ringCount),b=Fe(d,e.wafer,f.ringCount),y=Ne(d,e.reticleConfig),z=Pe(h,!1),$=Ce(d,e.wafer,f.sectorCount),m=[];l&&m.push(...W(i,p,f.passBins,f),...W(i,b,f.passBins,f),...W(i,y,f.passBins,f),...W(i,z,f.passBins,f),...W(i,$,f.passBins,f)),l&&m.push(...P(i,p,"hard",e.hbinDefs,"hardBin",f),...P(i,b,"hard",e.hbinDefs,"hardBin",f),...P(i,y,"hard",e.hbinDefs,"hardBin",f),...P(i,z,"hard",e.hbinDefs,"hardBin",f),...P(i,$,"hard",e.hbinDefs,"hardBin",f));{const g=i.filter(B=>B.sbin!==void 0);m.push(...P(g,p,"soft",e.sbinDefs,"softBin",f),...P(g,b,"soft",e.sbinDefs,"softBin",f),...P(g,y,"soft",e.sbinDefs,"softBin",f),...P(g,z,"soft",e.sbinDefs,"softBin",f),...P(g,$,"soft",e.sbinDefs,"softBin",f))}const w=[...o];let D;if(f.enableTestValueAnalysis){const g=Q(i,p,e.testDefs,f),B=Q(i,b,e.testDefs,f),k=Q(i,y,e.testDefs,f),V=Q(i,z,e.testDefs,f),F=Q(i,$,e.testDefs,f);m.push(...g.findings,...B.findings,...k.findings,...V.findings,...F.findings),g.warning&&w.push(g.warning),D=g.activeTestNumbers,m.push(...Ue(i,[p,b,y,z,$],e.testDefs,f)),m.push(...Y(i,p,e.testDefs,f),...Y(i,b,e.testDefs,f),...Y(i,y,e.testDefs,f),...Y(i,z,e.testDefs,f),...Y(i,$,e.testDefs,f))}{const g=qe(r,l,e.testDefs);(!r||l||g!==void 0)&&m.push(...Ee(u,e.wafer,{...f,isFailingDie:g}))}const v=nt(m,{eligibleDies:i,softEligibleDies:i.filter(g=>g.sbin!==void 0),testDies:i,passBins:f.passBins,ringCount:f.ringCount,sectorCount:f.sectorCount,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs});if(m.length=0,m.push(...v),l){const g=Ke(u,e.wafer,{passBins:f.passBins,ringCount:f.ringCount});if(g!==null&&g.pattern!=="random"&&g.pattern!=="none"){let Re=function(N){const E=j(N).map(K=>O(K).ring).filter(K=>K!==void 0);if(E.length===0)return!0;const ue=E.some(K=>K===Me),de=E.some(K=>K===1);return X==="edge-ring"||X==="edge-local"?ue:X==="center"?de:X==="donut"?!ue&&!de:!0};const k={center:"Center cluster",donut:"Donut","edge-ring":"Edge-ring","edge-local":"Edge-local",scratch:"Scratch","near-full":"Near-full"}[g.pattern]??g.pattern,V=g.confidence==="high"?"unusual":g.confidence==="medium"?"notable":"info",F=g.features,L=N=>`${(N*100).toFixed(0)}%`,C=g.pattern==="edge-ring"||g.pattern==="edge-local"?`${L(F.edgeRdd)} of edge dies failing`:g.pattern==="center"||g.pattern==="donut"?`centroid at ${L(F.centroidDistNorm)} of wafer radius from centre`:g.pattern==="scratch"?`linear score ${F.linearScore.toFixed(2)}, eccentricity ${F.eccentricity.toFixed(2)}`:`${L(F.globalRdd)} of dies failing`,_=i.filter(N=>{const E=N.hbin??N.sbin;return E!==void 0&&!new Set(f.passBins).has(E)}),U={"edge-ring":["ring","edge-arc"],"edge-local":["edge-arc","sector","quadrant"],center:["ring","cluster"],donut:["ring"],scratch:["cluster","sector","quadrant"],"near-full":["ring"]},G=new Set(U[g.pattern]??[]),X=g.pattern,Me=f.ringCount,ee=[];for(const N of m){if(!G.has(N.comparison.family)||N.comparison.family==="ring"&&!Re(N))continue;ee.push(N.id);const E=N.comparison.family==="ring"||N.comparison.family==="quadrant"||N.comparison.family==="sector";V!=="info"&&E&&(N.severity="info")}m.push({id:`spatial-pattern:${g.pattern}`,level:"wafer",severity:V,variable:{kind:"spatialPattern",label:k},comparison:{family:"spatial-pattern",left:k,right:"Wafer"},effect:{direction:"different",effectSize:F.globalRdd},stats:{method:"geometry",sampleSizeLeft:_.length,sampleSizeRight:i.length},summary:`Spatial pattern: ${k.toLowerCase()} (${g.confidence} confidence) \u2014 ${C}${g.note?` [${g.note}]`:""}`,highlight:{kind:"dies",dieKeys:_.map(N=>x(N))},relatedIds:ee.length>0?ee:void 0})}}m.sort((g,B)=>{const k=g.stats.adjustedPValue??g.stats.pValue??1,V=B.stats.adjustedPValue??B.stats.pValue??1;return k!==V?k-V:Math.abs(B.effect.absoluteDelta??0)-Math.abs(g.effect.absoluteDelta??0)});const S=He(e.dies,i.length,e.yield.yieldPercent);r&&(S.isLotStack=!0,t&&(S.aggregationMethod=t));const R=Ie(e.dies,e.testDefs);R&&(S.testSpecYield=R);const M=computeFunctionalYield(e.dies,e.testDefs);if(M&&(S.functionalYield=M),D===void 0&&f.computePerTestStats){const g=ze(i,f.testNumbers,e.testDefs);g.warning&&w.push(g.warning),D=g.testNumbers}if(D?.length){const g=_e(e.dies,D,e.testDefs,f.minimumSampleSize);g&&(S.perTestStats=g)}return w.length>0&&(S.warnings=w),ot(m,i,f.passBins),{level:"wafer",hasNotableFindings:m.some(g=>g.severity==="notable"||g.severity==="unusual"),findings:m,wafer:e.wafer.metadata??void 0,stats:S}}
|
|
@@ -59,6 +59,11 @@ export declare function buildBinClusterData(groups: {
|
|
|
59
59
|
*
|
|
60
60
|
* Pass bins are pinned to the top rather than competing on count: they are the
|
|
61
61
|
* reference the failures are read against, not themselves a failure mode.
|
|
62
|
+
*
|
|
63
|
+
* `passBins` must be the passing bins OF THE TYPE BEING SORTED. For hard bins
|
|
64
|
+
* that is `passBins`; for soft bins it is `BinColors.pass.soft` (or
|
|
65
|
+
* `binPassSets(dies, passBins).soft`) — hard pass numbers applied to a soft
|
|
66
|
+
* tally pin the wrong rows.
|
|
62
67
|
*/
|
|
63
|
-
export declare function sortBinsForDisplay(entries: Iterable<[bin: number, count: number]>, passBins?: number
|
|
68
|
+
export declare function sortBinsForDisplay(entries: Iterable<[bin: number, count: number]>, passBins?: Iterable<number>): Array<[bin: number, count: number]>;
|
|
64
69
|
//# sourceMappingURL=binPareto.d.ts.map
|
|
@@ -8,8 +8,6 @@ interface ClusterOptions {
|
|
|
8
8
|
minimumSampleSize: number;
|
|
9
9
|
minimumClusterSize: number;
|
|
10
10
|
ringCount: number;
|
|
11
|
-
includePartial: boolean;
|
|
12
|
-
includeEdgeExcluded: boolean;
|
|
13
11
|
/** Optional override for the failure predicate. Default: hard bin, then soft bin, not in passBins. */
|
|
14
12
|
isFailingDie?: (die: Die) => boolean;
|
|
15
13
|
}
|