@wafertools/wafermap 0.26.0 → 0.27.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/CHANGELOG.md +367 -0
- package/README.md +46 -3
- package/dist/packages/canvas-adapter/canvasTheme.d.ts +4 -0
- package/dist/packages/canvas-adapter/canvasTheme.js +1 -1
- package/dist/packages/canvas-adapter/charts/barPanel.d.ts +16 -0
- package/dist/packages/canvas-adapter/charts/barPanel.js +1 -1
- package/dist/packages/canvas-adapter/charts/binCluster.js +1 -1
- package/dist/packages/canvas-adapter/charts/boxplot.d.ts +13 -1
- package/dist/packages/canvas-adapter/charts/boxplot.js +1 -1
- package/dist/packages/canvas-adapter/charts/capability.d.ts +10 -10
- package/dist/packages/canvas-adapter/charts/capability.js +1 -1
- package/dist/packages/canvas-adapter/charts/chartShell.d.ts +337 -3
- package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
- package/dist/packages/canvas-adapter/charts/correlation.d.ts +8 -9
- package/dist/packages/canvas-adapter/charts/correlation.js +2 -1
- package/dist/packages/canvas-adapter/charts/groupedBarPlot.d.ts +50 -0
- package/dist/packages/canvas-adapter/charts/groupedBarPlot.js +1 -0
- package/dist/packages/canvas-adapter/charts/histogram.d.ts +12 -1
- package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
- package/dist/packages/canvas-adapter/charts/regionYieldDiagram.js +1 -1
- package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
- package/dist/packages/canvas-adapter/charts/testPassRate.d.ts +20 -0
- package/dist/packages/canvas-adapter/charts/testPassRate.js +1 -0
- package/dist/packages/canvas-adapter/charts/trend.d.ts +30 -0
- package/dist/packages/canvas-adapter/charts/trend.js +1 -0
- package/dist/packages/canvas-adapter/dieList.js +7 -7
- package/dist/packages/canvas-adapter/identityHeader.d.ts +46 -0
- package/dist/packages/canvas-adapter/identityHeader.js +1 -0
- package/dist/packages/canvas-adapter/index.d.ts +1 -1
- package/dist/packages/canvas-adapter/insightsTab.d.ts +27 -3
- package/dist/packages/canvas-adapter/insightsTab.js +1 -1
- package/dist/packages/canvas-adapter/maplessSummary.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +15 -2
- package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -20
- package/dist/packages/canvas-adapter/renderWaferMap.d.ts +72 -17
- package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
- package/dist/packages/canvas-adapter/summaryPanel.d.ts +219 -33
- package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
- package/dist/packages/canvas-adapter/toCanvas.d.ts +6 -0
- package/dist/packages/canvas-adapter/toCanvas.js +1 -1
- package/dist/packages/canvas-adapter/toolbar.d.ts +310 -6
- 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 +98 -35
- package/dist/packages/canvas-adapter/version.d.ts +2 -2
- package/dist/packages/canvas-adapter/version.js +1 -1
- package/dist/packages/canvas-adapter/warnings.d.ts +0 -4
- package/dist/packages/canvas-adapter/warnings.js +1 -1
- package/dist/packages/core/utils.d.ts +11 -0
- package/dist/packages/core/utils.js +1 -1
- package/dist/packages/renderer/buildView.d.ts +34 -1
- package/dist/packages/renderer/buildView.js +1 -1
- package/dist/packages/renderer/buildWaferMap.d.ts +104 -5
- package/dist/packages/renderer/buildWaferMap.js +1 -1
- package/dist/packages/renderer/colorSchemes.js +1 -1
- package/dist/packages/stats/analyzeWaferMap.js +1 -1
- package/dist/packages/stats/binPareto.d.ts +15 -0
- package/dist/packages/stats/binPareto.js +1 -1
- package/dist/packages/stats/clusterDetection.js +1 -1
- package/dist/packages/stats/connectedComponents.d.ts +14 -0
- package/dist/packages/stats/connectedComponents.js +1 -0
- package/dist/packages/stats/correlation.d.ts +26 -0
- package/dist/packages/stats/correlation.js +1 -1
- package/dist/packages/stats/facets.js +1 -1
- package/dist/packages/stats/filterFindings.d.ts +18 -0
- package/dist/packages/stats/filterFindings.js +1 -1
- package/dist/packages/stats/findingsNarrative.js +1 -1
- package/dist/packages/stats/histogram.d.ts +19 -1
- package/dist/packages/stats/histogram.js +1 -1
- package/dist/packages/stats/index.d.ts +6 -0
- package/dist/packages/stats/index.js +1 -1
- package/dist/packages/stats/mergeTestDefs.d.ts +44 -0
- package/dist/packages/stats/mergeTestDefs.js +1 -0
- package/dist/packages/stats/metadataColumns.d.ts +20 -4
- package/dist/packages/stats/metadataColumns.js +1 -1
- package/dist/packages/stats/patternClassification.js +1 -1
- package/dist/packages/stats/renderFindingsReport.js +14 -14
- package/dist/packages/stats/renderSummaryReport.js +32 -29
- package/dist/packages/stats/reportHtml.js +38 -13
- package/dist/packages/stats/testPassRate.d.ts +85 -0
- package/dist/packages/stats/testPassRate.js +1 -0
- package/dist/packages/stats/trend.d.ts +40 -0
- package/dist/packages/stats/trend.js +1 -0
- package/dist/packages/stats/types.d.ts +117 -6
- package/llms.txt +6 -0
- package/package.json +7 -5
- package/dist/packages/canvas-adapter/metadataBadge.d.ts +0 -30
- package/dist/packages/canvas-adapter/metadataBadge.js +0 -1
|
@@ -299,6 +299,21 @@ export interface WaferMapInputBase {
|
|
|
299
299
|
* Set to an empty array to suppress yield calculation.
|
|
300
300
|
*/
|
|
301
301
|
passBins?: number[];
|
|
302
|
+
/**
|
|
303
|
+
* Wafer diameters (mm) treated as standard when sanity-checking an *inferred*
|
|
304
|
+
* diameter — see {@link STANDARD_WAFER_DIAMETERS_MM} for the default
|
|
305
|
+
* (100/125/150/200/300) and the measurements behind it.
|
|
306
|
+
*
|
|
307
|
+
* **Replaces** the default rather than adding to it, so spread it to extend:
|
|
308
|
+
* `standardDiameters: [...STANDARD_WAFER_DIAMETERS_MM, 76.2]` for a line that
|
|
309
|
+
* also runs 3-inch. Pass `[]` to disable the check entirely, for genuinely
|
|
310
|
+
* non-standard substrates (panels, reclaim, odd R&D shapes) — that is the
|
|
311
|
+
* intended opt-out, rather than suppressing every geometry advisory.
|
|
312
|
+
*
|
|
313
|
+
* Only consulted when the diameter was inferred; a diameter you supply is
|
|
314
|
+
* never second-guessed.
|
|
315
|
+
*/
|
|
316
|
+
standardDiameters?: number[];
|
|
302
317
|
/**
|
|
303
318
|
* How to handle multiple `DieResult` entries for the same die position (retests).
|
|
304
319
|
*
|
|
@@ -420,13 +435,34 @@ export interface WaferWarning {
|
|
|
420
435
|
* you asserted — it reports this so you can correct one of them. Only raised
|
|
421
436
|
* when the pitch was supplied: pitch is a free scaling parameter, so with an
|
|
422
437
|
* inferred pitch "the dies don't fit" is not a statement about your data.
|
|
423
|
-
* - `'
|
|
424
|
-
*
|
|
425
|
-
*
|
|
426
|
-
*
|
|
438
|
+
* - `'non-standard-diameter'` — the wafer diameter was INFERRED from the die
|
|
439
|
+
* extent (a pitch was supplied, a diameter was not) and came out off the
|
|
440
|
+
* standard wafer-size ladder (SEMI M1: 100/150/200/300 mm and the smaller
|
|
441
|
+
* legacy sizes). Silicon only comes in those sizes, so a result like 210 mm
|
|
442
|
+
* is evidence the inference went wrong — almost always because the outer
|
|
443
|
+
* dies were never probed, so the die extent understates the wafer. Every die
|
|
444
|
+
* is then placed against a wafer that is too small, which moves dies between
|
|
445
|
+
* rings and changes ring and edge findings. Supply `waferConfig.diameter`.
|
|
446
|
+
*
|
|
447
|
+
* There is deliberately no matching advisory for an inferred *pitch*: that
|
|
448
|
+
* is derived to fit the supplied diameter, so it is always self-consistent
|
|
449
|
+
* and there is nothing to check it against.
|
|
450
|
+
* - `'diameter-exceeds-die-extent'` — a SUPPLIED `waferConfig.diameter` that the
|
|
451
|
+
* probed dies fill less than 75% of. The mirror of `geometry-conflict`, which
|
|
452
|
+
* asks whether the dies *fit*; this asks whether they *fill*. An over-large
|
|
453
|
+
* wafer is not harmless: ring bands are equal-radius, so it crushes dies into
|
|
454
|
+
* the inner rings and empties the outer ones — at a 10× diameter every die
|
|
455
|
+
* lands in ring 1 — and ring/quadrant/edge findings then describe the assumed
|
|
456
|
+
* wafer, not the probed area. A genuinely partial map looks identical and is
|
|
457
|
+
* equally worth reporting, so the message names both causes.
|
|
427
458
|
* - `'test-count-capped'` — raised by `analyzeWaferMap`: more tests were found in
|
|
428
459
|
* the die data than the analysis cap allows, so test-value analysis was skipped
|
|
429
460
|
* entirely and NO test findings were produced. Pass `testNumbers` to scope it.
|
|
461
|
+
* - `'analysis-option-corrected'` — raised by `analyzeWaferMap`: a numeric option
|
|
462
|
+
* was outside the range that can produce a meaningful analysis and was clamped.
|
|
463
|
+
* The message names each correction. Only reachable from untyped callers now
|
|
464
|
+
* that the statistical thresholds are internal, but a wrong `ringCount` or
|
|
465
|
+
* `sectorCount` still gets here from TypeScript.
|
|
430
466
|
* - `'edge-exclusion-exceeds-radius'` — `waferConfig.edgeExclusion` is larger than
|
|
431
467
|
* the resolved wafer radius (most likely when the diameter was itself inferred
|
|
432
468
|
* from sparse/partial data). The excluded band is clamped to the whole wafer
|
|
@@ -435,7 +471,7 @@ export interface WaferWarning {
|
|
|
435
471
|
* The union is intentionally open to string so future advisory codes can be
|
|
436
472
|
* added without a breaking change; switch with a `default` branch.
|
|
437
473
|
*/
|
|
438
|
-
code: 'partial-coverage' | 'geometry-conflict' | '
|
|
474
|
+
code: 'partial-coverage' | 'geometry-conflict' | 'non-standard-diameter' | 'diameter-exceeds-die-extent' | 'test-count-capped' | 'edge-exclusion-exceeds-radius' | 'analysis-option-corrected' | (string & {});
|
|
439
475
|
/** Human-readable explanation, suitable for direct display. */
|
|
440
476
|
message: string;
|
|
441
477
|
/**
|
|
@@ -453,7 +489,14 @@ export interface WaferWarning {
|
|
|
453
489
|
confidence?: number;
|
|
454
490
|
}
|
|
455
491
|
export interface WaferMapResult {
|
|
492
|
+
/** Resolved wafer geometry — diameter, radius, centre, notch, orientation. */
|
|
456
493
|
wafer: Wafer;
|
|
494
|
+
/**
|
|
495
|
+
* Every die, carrying its original grid coordinates (`x`/`y`), physical
|
|
496
|
+
* position, bin/test data and eligibility flags. Dies with no reported
|
|
497
|
+
* position are included here — guard with `hasPosition` before using
|
|
498
|
+
* coordinates.
|
|
499
|
+
*/
|
|
457
500
|
dies: Die[];
|
|
458
501
|
/**
|
|
459
502
|
* The initial plot mode selected by `buildWaferMap` — `'value'` when test values are
|
|
@@ -468,6 +511,17 @@ export interface WaferMapResult {
|
|
|
468
511
|
* @internal Renderer-agnostic draw list consumed by `renderWaferMap` and `toCanvas`.
|
|
469
512
|
* Not part of the public API — access the named fields on `WaferMapResult` instead.
|
|
470
513
|
*/
|
|
514
|
+
/**
|
|
515
|
+
* @internal The initial draw list. Kept on the result because `analyzeWaferMap`
|
|
516
|
+
* discriminates its input on `'view' in input`, and because `renderWaferMap`
|
|
517
|
+
* reads `dataAxisFlip` from it — not because a host should.
|
|
518
|
+
*
|
|
519
|
+
* Read the promoted top-level fields instead (`plotMode`, `metadata`,
|
|
520
|
+
* `isLotStack`, `hbinDefs`, `sbinDefs`, `testDefs`): this one is rebuilt on
|
|
521
|
+
* every option change, so anything you cache from it goes stale immediately.
|
|
522
|
+
* Hosts driving the low-level pipeline should call `buildView` themselves,
|
|
523
|
+
* which is the supported way to hold a {@link View}.
|
|
524
|
+
*/
|
|
471
525
|
view: View;
|
|
472
526
|
/** Reticle configuration used to generate the overlay and reticle-local groupings. */
|
|
473
527
|
reticleConfig?: ReticleConfig;
|
|
@@ -479,15 +533,26 @@ export interface WaferMapResult {
|
|
|
479
533
|
* proportionally correct but not in physical mm.
|
|
480
534
|
*/
|
|
481
535
|
units: 'mm' | 'normalized';
|
|
536
|
+
/**
|
|
537
|
+
* How much of the geometry had to be guessed, and how confident each guess is.
|
|
538
|
+
* `confidence` runs 0–1, where 1 means the value was supplied rather than
|
|
539
|
+
* inferred. Worth surfacing when a map looks wrong: a low `wafer` or `diePitch`
|
|
540
|
+
* confidence usually means sparse or partial data, and is the first thing to
|
|
541
|
+
* check before suspecting the data itself. See `warnings` for the advisories
|
|
542
|
+
* these confidences drive.
|
|
543
|
+
*/
|
|
482
544
|
inference: {
|
|
545
|
+
/** Wafer diameter/centre. `method` names how it was derived, e.g. `'inferred-from-xy'`. */
|
|
483
546
|
wafer: {
|
|
484
547
|
confidence: number;
|
|
485
548
|
method: string;
|
|
486
549
|
};
|
|
550
|
+
/** Die pitch. `units` is `'mm'` when a real physical scale was established, `'normalized'` when not. */
|
|
487
551
|
diePitch: {
|
|
488
552
|
confidence: number;
|
|
489
553
|
units: 'mm' | 'normalized';
|
|
490
554
|
};
|
|
555
|
+
/** The die grid's origin and step. */
|
|
491
556
|
grid: {
|
|
492
557
|
confidence: number;
|
|
493
558
|
};
|
|
@@ -574,5 +639,39 @@ export declare function getTestPassStatus(die: Pick<Die, 'testValues' | 'testPas
|
|
|
574
639
|
* availability checks.
|
|
575
640
|
*/
|
|
576
641
|
export declare function dieHasTestData(die: Pick<Die, 'testValues' | 'testPass'>): boolean;
|
|
642
|
+
/**
|
|
643
|
+
* Nominal wafer diameters in mm, used to sanity-check an *inferred* diameter.
|
|
644
|
+
* Override per build with {@link WaferMapInputBase.standardDiameters}.
|
|
645
|
+
*
|
|
646
|
+
* This is what makes an inferred diameter checkable at all. When a die pitch is
|
|
647
|
+
* supplied but no diameter, the wafer is sized from the die extent — right only
|
|
648
|
+
* if the probed grid reaches the wafer edge, and silently wrong whenever the
|
|
649
|
+
* outer dies were never probed. Silicon comes in standard sizes, so landing off
|
|
650
|
+
* the ladder is evidence the assumption failed. (The library already leans on
|
|
651
|
+
* SEMI M1 for notch chord lengths — see `standardNotchLength` in core/wafer.ts.)
|
|
652
|
+
*
|
|
653
|
+
* **The size of this list is a real trade-off, measured, not guessed.** Every
|
|
654
|
+
* entry is one more value a wrong inference can hide behind; every omission
|
|
655
|
+
* wrongly accuses someone actually running that size. Across 63 fixtures
|
|
656
|
+
* (76.2–300 mm, square and rectangular die, 50–94% coverage):
|
|
657
|
+
*
|
|
658
|
+
* | table | caught | missed | false alarms |
|
|
659
|
+
* |---|---|---|---|
|
|
660
|
+
* | 100/150/200/300 | 31 | 8 | 3 |
|
|
661
|
+
* | **100/125/150/200/300 (this default)** | 31 | 8 | 3 |
|
|
662
|
+
* | + 76.2 | 28 | 11 | 0 |
|
|
663
|
+
* | + 25.4/50.8/450 | 26 | 13 | 0 |
|
|
664
|
+
*
|
|
665
|
+
* A miss is silent, which is what every one of these cases did before this check
|
|
666
|
+
* existed — so it costs nothing against the status quo. A false alarm is a new
|
|
667
|
+
* harm: it tells someone whose diameter is correct that it is suspect, with no
|
|
668
|
+
* way to clear it. Hosts running 3-inch or other legacy sizes should add them
|
|
669
|
+
* via `standardDiameters` rather than live with that.
|
|
670
|
+
*
|
|
671
|
+
* 25.4/50.8 (1"/2") and 450 mm are excluded: none are in current production
|
|
672
|
+
* flows, and including 50.8 demonstrably created a miss — a 76.2 mm wafer
|
|
673
|
+
* mis-inferring to 50 mm went undetected.
|
|
674
|
+
*/
|
|
675
|
+
export declare const STANDARD_WAFER_DIAMETERS_MM: readonly number[];
|
|
577
676
|
export declare function buildWaferMap(input: DieResult[] | WaferMapInput, options?: WaferMapOptions): WaferMapResult;
|
|
578
677
|
//# sourceMappingURL=buildWaferMap.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createWafer as j}from"../core/wafer.js";import{isYieldEligibleDie as fe,getDieKey as B,hasPosition as k}from"../core/dies.js";import{applyOrientation as H,transformDies as de}from"../core/transforms.js";import{affineRotation as ce,affineMirror as le,affineCompose as ue,affinePoint as he}from"../core/transforms.js";import{inferWaferFromXY as pe}from"../core/inference/wafer.js";import{resolveGridPitch as me}from"../core/inference/pitch.js";import{assignGridIndices as ye}from"../core/inference/grid.js";import{generateReticleGrid as ge}from"../core/reticle.js";import{buildView as U}from"./buildView.js";import{modeOf as xe}from"../core/utils.js";import{aggregateValues as q,aggregateBinCounts as we}from"../core/aggregates.js";export function isParametricTest(t){return t?.testType!=="F"}function be(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],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],testDefs:t.testDefs,hbinDefs:t.hbinDefs,sbinDefs:t.sbinDefs,metadataFields:t.metadataFields,retestPolicy:t.retestPolicy??"last",edgeDieYieldMode:t.edgeDieYieldMode??"exclude"}}function Me(t,n){return n?.coordinateOrigin?n.coordinateOrigin:{type:"center"}}function De(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 Oe(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 d=Math.round(i.x)-n,o=Math.round(i.y)-e;d<l&&(l=d),d>s&&(s=d),o<h&&(h=o),o>p&&(p=o)}return{colMidX:(l+s)/2*u,colMidY:(h+p)/2*c,anchored:!1}}function Se(t,n,e){let u=1/0,c=-1/0,a=1/0,l=-1/0,s=0,h=0;for(const d of t){const o=Math.round(d.x)-n,r=Math.round(d.y)-e;s+=o,h+=r,o<u&&(u=o),o>c&&(c=o),r<a&&(a=r),r>l&&(l=r)}const p=t.length,i=(d,o,r)=>{const m=(r-o)/2;return m<=0?0:Math.abs(d/p-(o+r)/2)/m};return i(s,u,c)>.11||i(h,a,l)>.11}function ke(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 ve(t,n){const{method:e,targetBin:u}=t,c=t.results.map(a=>a.filter(k));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 d of i)if(d.testValues)for(const o of Object.keys(d.testValues)){const r=Number(o);a.has(r)||l.add(r)}if(l.size===0)return q(c,e);const s=[...l],h=s.map(i=>q(c,e,i)),p=new Map;for(let i=0;i<s.length;i++){const d=s[i];for(const o of h[i]){const r=B(o);p.has(r)||p.set(r,{template:o,testValues:{}});const m=p.get(r),y=o.testValues?.[0];y!==void 0&&(m.testValues[d]=y)}}return[...p.values()].map(({template:i,testValues:d})=>({...i,testValues:Object.keys(d).length>0?d:void 0}))}if(e==="countBin"||e==="percent"){if(u===void 0)return[];const a=we(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=B(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]),d=Number(p[1]),o=h.map(m=>m.hbin).filter(m=>m!==void 0),r=xe(o);r!==null&&l.push({x:i,y:d,hbin:r})}return l}return[]}function J(t){const n=t.filter(k),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(r=>!r.partial).filter(r=>r.edgeExcluded).length,l=t.filter(r=>r.partial).length;let s=0,h=0,p=!1;for(const r of t){if(!fe(r))continue;const m=r.hbin??r.sbin;m!==void 0&&(p=!0,u.has(m)?s++:h++)}const i=s+h,d=p&&i>0?s/i*100:null;let o=null;if(e==="denominator-only"){const r=i+a;o=p&&r>0?s/r*100:null}return{passDies:s,failDies:h,edgeExcludedDies:a,partialDies:l,totalDies:i,yieldPercent:d,yieldPercentGross:o}}function Z(t,n,e,u,c,a=0,l=0,s=0,h=0,p=0,i=!1,d=!1){if(!t)return[];const o=t.anchorDie??{x:0,y:0},r=ge(n,{width:t.width,height:t.height,diePitchX:u,diePitchY:c,anchorDie:{x:o.x-a,y:o.y-l},gridOrigin:{x:-s,y:-h}}),m=ue(le(i,d,n.center.x,n.center.y),ce(p,n.center.x,n.center.y));return r.filter(y=>{const v=y.width/2,w=y.height/2,b=[[y.x-v,y.y-w],[y.x+v,y.y-w],[y.x+v,y.y+w],[y.x-v,y.y+w]].map(([g,M])=>he(m,g,M)),C=Math.min(...b.map(g=>g.x)),P=Math.max(...b.map(g=>g.x)),Y=Math.min(...b.map(g=>g.y)),F=Math.max(...b.map(g=>g.y));return e.some(g=>g.physX>=C&&g.physX<P&&g.physY>=Y&&g.physY<F)})}function Re(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(k),c=t.filter(i=>!k(i)),a=new Map;for(const i of u){let d=a.get(i.x);d||(d=new Map,a.set(i.x,d)),d.set(i.y,(d.get(i.y)??0)+1)}const l=new Set(e);function s(i,d){const o=i.hbin,r=d.hbin;if(o===void 0||r===void 0)return!1;const m=l.has(o),y=l.has(r);return n==="best"?y!==m?y:r<o:y!==m?m:r>o}const h=new Map;for(const i of u){const d=B(i),o=h.get(d);n==="first"&&o||(n==="best"||n==="worst")&&o&&!s(o,i)||h.set(d,i)}return[...Array.from(h.values()).map(i=>{const o=a.get(i.x)?.get(i.y)??1;return o>1?{...i,retestCount:o}: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 T(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=>({code:Pe(n),message:n,severity:"error",confidence:t.wafer.confidence}))}function Pe(t){return t.includes(ne)?"geometry-conflict":t.includes(ie)?"inferred-pitch":"partial-coverage"}const ne="do not fit inside the supplied",ie="The die pitch was inferred as";export function buildWaferMap(t,n){const e=be(t),{debug:u,...c}=n??{},a=e.lotStackOpts?ve(e.lotStackOpts,e.testDefs):e.results;for(let f=0;f<Math.min(5,a.length);f++){const x=a[f];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 f of a)if(f.x==null!=(f.y==null))throw new TypeError(`buildWaferMap: die has only one of x/y set (x=${String(f.x)}, y=${String(f.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 f=e.explicitDies;const x=l.filter(k),R=l.filter(A=>!k(A));if(x.length>0){const A=new Map(x.map(S=>[B(S),S]));f=f.map(S=>{const z=A.get(B(S));return z?T(S,z):S})}const V=e.waferOpts?.diameter??300,E=j({diameter:V,notch:e.waferOpts?.notch,orientation:e.waferOpts?.orientation??0,metadata:e.waferOpts?.metadata});E.orientation!==0&&(f=H(f,E));const K=Z(e.reticleOpts,E,f,1,1,0,0,0,0,E.orientation),re=c.showReticle??e.reticleOpts!==void 0,L=U(E,f,{...c,reticles:K,showReticle:re,plotMode:ee(l,c),testDefs:e.testDefs,isLotStack:!1},{hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,metadataFields:e.metadataFields}),ae=R.map((A,S)=>T({id:`unpositioned_${S}`,width:f[0]?.width??1,height:f[0]?.height??1},A)),_=[...f,...ae];return{wafer:E,dies:_,view:L,reticleConfig:e.reticleOpts,units:"mm",inference:s,warnings:te(s),plotMode:L.plotMode,metadata:L.metadata,isLotStack:!1,dataCoverage:J(_),yield:Q(_,e.passBins,e.edgeDieYieldMode),reticles:K,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs,metadataFields:e.metadataFields}}const h=l.filter(k),p=l.filter(f=>!k(f)),i=h.map(f=>({x:f.x,y:f.y})),d=me(i,e.dieOpts,e.waferOpts?.diameter);s.diePitch={confidence:d.confidence,units:d.units};const{pitchX:o,pitchY:r}=d,m=d.units,y=Me(h,e.dieOpts),v=ye(i);s.grid={confidence:v.confidence};const{offsetX:w,offsetY:b}=De(i,y,v),{flipX:C,flipY:P}=ke(e.dieOpts,y),{colMidX:Y,colMidY:F,anchored:g}=Oe(i,w,b,o,r,e.waferOpts?.center);let M=e.waferOpts?.diameter;const $=i.map(f=>({x:(Math.round(f.x)-w)*o-Y,y:(Math.round(f.y)-b)*r-F})),X=$.length>0?Math.max(...$.map(({x:f,y:x})=>Math.hypot(Math.abs(f)+o/2,Math.abs(x)+r/2))):0;if(M===void 0)if(i.length>0)if(m==="mm"){const f=pe($,{minRadius:X});M=f.diameter,s.wafer={confidence:f.confidence,method:f.method}}else M=X*2,s.wafer={confidence:d.confidence*.8,method:"extent"};else M=m==="mm"?300:30,s.wafer={confidence:0,method:"default"};if(!g&&i.length>0&&Se(i,w,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){const f=e.dieOpts?.width!==void 0&&e.dieOpts?.height!==void 0;if(f&&X>M/2+1e-9){const x=$.filter(({x:R,y:V})=>Math.hypot(Math.abs(R)+o/2,Math.abs(V)+r/2)>M/2+1e-9).length;(s.warnings??=[]).push(`${x} of ${$.length} probed die positions ${ne} ${M} mm wafer at the supplied die pitch of ${o} \xD7 ${r} 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 ${(X*2).toFixed(1)} mm. Check waferConfig.diameter and dieConfig.width/height against the real device.`)}else f||(s.warnings??=[]).push(`${ie} ${o.toFixed(3)} \xD7 ${r.toFixed(3)} mm by assuming the die grid fits within the ${M} mm wafer. The assumed aspect ratio may not match the true die shape whenever edge dies are absent from the data (a reticle-complete map, or partial dies filtered out upstream). Supply dieConfig.width and dieConfig.height \u2014 the die pitch, not the diameter, is what fixes placement.`)}const O=j({diameter:M,notch:e.waferOpts?.notch,orientation:e.waferOpts?.orientation??0,metadata:e.waferOpts?.metadata}),Ee={width:o,height:r};let D=h.map(f=>{const x=Math.round(f.x)-w,R=Math.round(f.y)-b,V={id:`${x}_${R}`,x,y:R,physX:x*o-Y,physY:R*r-F,width:o,height:r,insideWafer:!0,partial:!1};return T(V,f)});(w!==0||b!==0)&&(D=D.map(f=>({...f,x:f.x+w,y:f.y+b,id:`${f.x+w}_${f.y+b}`}))),D=H(D,O),(C||P)&&(D=de(D,{flipX:C,flipY:P},O.center));const N=[];e.waferOpts?.edgeExclusion&&e.waferOpts.edgeExclusion>0&&(D=Re(D,O,e.waferOpts.edgeExclusion),e.waferOpts.edgeExclusion>O.radius&&N.push({code:"edge-exclusion-exceeds-radius",message:`edgeExclusion (${e.waferOpts.edgeExclusion}mm) exceeds the resolved wafer radius (${O.radius}mm) \u2014 the entire wafer is excluded.`,severity:"warning"}));const G=Z(e.reticleOpts,O,D,o,r,w,b,Y,F,O.orientation,C,P),se=c.showReticle??e.reticleOpts!==void 0,I=U(O,D,{...c,reticles:G,showReticle:se,plotMode:ee(l,c),testDefs:e.testDefs,dataAxisFlip:{x:C,y:P},isLotStack:e.lotStackOpts!==void 0,aggregationMethod:e.lotStackOpts?.method,lotSize:e.lotStackOpts?.results.length},{hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,metadataFields:e.metadataFields}),oe=p.map((f,x)=>T({id:`unpositioned_${x}`,width:o,height:r},f)),W=[...D,...oe];return{wafer:O,dies:W,view:I,reticleConfig:e.reticleOpts,units:m,inference:s,warnings:[...te(s),...N],plotMode:I.plotMode,metadata:I.metadata,isLotStack:e.lotStackOpts!==void 0,dataCoverage:J(W),yield:Q(W,e.passBins,e.edgeDieYieldMode),reticles:G,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs,metadataFields:e.metadataFields,aggrMethod:I.aggrMethod,lotSize:I.lotSize}}
|
|
1
|
+
import{createWafer as j}from"../core/wafer.js";import{isYieldEligibleDie as ce,getDieKey as V,hasPosition as E}from"../core/dies.js";import{applyOrientation as q,transformDies as le}from"../core/transforms.js";import{affineRotation as ue,affineMirror as he,affineCompose as pe,affinePoint as me}from"../core/transforms.js";import{inferWaferFromXY as ge}from"../core/inference/wafer.js";import{resolveGridPitch as ye}from"../core/inference/pitch.js";import{assignGridIndices as we}from"../core/inference/grid.js";import{generateReticleGrid as xe}from"../core/reticle.js";import{buildView as H}from"./buildView.js";import{modeOf as be}from"../core/utils.js";import{aggregateValues as U,aggregateBinCounts as De}from"../core/aggregates.js";export function isParametricTest(t){return t?.testType!=="F"}function Me(t){if(!Array.isArray(t)&&"results"in t&&t.results!==void 0&&"lotStack"in t&&t.lotStack!==void 0)throw new Error("buildWaferMap: pass either `results` or `lotStack`, not both.");return Array.isArray(t)?{results:t,waferOpts:void 0,dieOpts:void 0,explicitDies:void 0,reticleOpts:void 0,lotStackOpts:void 0,passBins:[1],standardDiameters:void 0,testDefs:void 0,hbinDefs:void 0,sbinDefs:void 0,metadataFields:void 0,retestPolicy:"last",edgeDieYieldMode:"exclude"}:{results:t.results??[],waferOpts:t.waferConfig,dieOpts:t.dieConfig,explicitDies:t.dies,reticleOpts:t.reticleConfig,lotStackOpts:t.lotStack,passBins:t.passBins??[1],standardDiameters:t.standardDiameters,testDefs:t.testDefs,hbinDefs:t.hbinDefs,sbinDefs:t.sbinDefs,metadataFields:t.metadataFields,retestPolicy:t.retestPolicy??"last",edgeDieYieldMode:t.edgeDieYieldMode??"exclude"}}function Oe(t,n){return n?.coordinateOrigin?n.coordinateOrigin:{type:"center"}}function Re(t,n,e){if(n.type==="custom"&&n.offset)return{offsetX:n.offset.x,offsetY:n.offset.y};if(n.type!=="center"){let u=1/0,c=-1/0,a=1/0,l=-1/0;for(const s of t)s.x<u&&(u=s.x),s.x>c&&(c=s.x),s.y<a&&(a=s.y),s.y>l&&(l=s.y);return{offsetX:Math.round((c+u)/2),offsetY:Math.round((l+a)/2)}}return{offsetX:e.offsetX,offsetY:e.offsetY}}function Se(t,n,e,u,c,a){if(a)return{colMidX:(Math.round(a.x)-n)*u,colMidY:(Math.round(a.y)-e)*c,anchored:!0};if(t.length===0)return{colMidX:0,colMidY:0,anchored:!1};let l=1/0,s=-1/0,h=1/0,p=-1/0;for(const i of t){const f=Math.round(i.x)-n,r=Math.round(i.y)-e;f<l&&(l=f),f>s&&(s=f),r<h&&(h=r),r>p&&(p=r)}return{colMidX:(l+s)/2*u,colMidY:(h+p)/2*c,anchored:!1}}function Ee(t,n,e){let u=1/0,c=-1/0,a=1/0,l=-1/0,s=0,h=0;for(const f of t){const r=Math.round(f.x)-n,d=Math.round(f.y)-e;s+=r,h+=d,r<u&&(u=r),r>c&&(c=r),d<a&&(a=d),d>l&&(l=d)}const p=t.length,i=(f,r,d)=>{const m=(d-r)/2;return m<=0?0:Math.abs(f/p-(r+d)/2)/m};return i(s,u,c)>.11||i(h,a,l)>.11}function ve(t,n){let e=t?.xAxisDirection==="left",u=t?.yAxisDirection==="down";return(n.type==="UL"||n.type==="UR")&&(u=!0),(n.type==="LR"||n.type==="UR")&&(e=!0),{flipX:e,flipY:u}}function ke(t,n){const{method:e,targetBin:u}=t,c=t.results.map(a=>a.filter(E));if(e==="mean"||e==="median"||e==="stddev"||e==="min"||e==="max"||e==="count"){const a=new Set((n??[]).filter(i=>!isParametricTest(i)).map(i=>i.testNumber)),l=new Set;for(const i of c)for(const f of i)if(f.testValues)for(const r of Object.keys(f.testValues)){const d=Number(r);a.has(d)||l.add(d)}if(l.size===0)return U(c,e);const s=[...l],h=s.map(i=>U(c,e,i)),p=new Map;for(let i=0;i<s.length;i++){const f=s[i];for(const r of h[i]){const d=V(r);p.has(d)||p.set(d,{template:r,testValues:{}});const m=p.get(d),g=r.testValues?.[0];g!==void 0&&(m.testValues[f]=g)}}return[...p.values()].map(({template:i,testValues:f})=>({...i,testValues:Object.keys(f).length>0?f:void 0}))}if(e==="countBin"||e==="percent"){if(u===void 0)return[];const a=De(c,u,"hard");if(e==="countBin")return a;const l=c.length;return a.map(s=>({...s,testValues:{0:l>0?(s.testValues?.[0]??0)/l*100:0}}))}if(e==="mode"){const a=new Map;for(const s of c)for(const h of s){const p=V(h);a.has(p)||a.set(p,[]),a.get(p).push(h)}const l=[];for(const[s,h]of a){const p=s.split(","),i=Number(p[0]),f=Number(p[1]),r=h.map(m=>m.hbin).filter(m=>m!==void 0),d=be(r);d!==null&&l.push({x:i,y:f,hbin:d})}return l}return[]}function J(t){const n=t.filter(E),e=n.length,u=n.filter(a=>a.edgeExcluded).length,c=n.filter(a=>dieHasTestData(a)||a.hbin!==void 0||a.sbin!==void 0).length;return{filledDies:c,totalDies:e,edgeExcludedDies:u,unpositionedDies:t.length-n.length,ratio:e>0?c/e:0}}function Q(t,n,e="exclude"){const u=new Set(n),a=t.filter(d=>!d.partial).filter(d=>d.edgeExcluded).length,l=t.filter(d=>d.partial).length;let s=0,h=0,p=!1;for(const d of t){if(!ce(d))continue;const m=d.hbin??d.sbin;m!==void 0&&(p=!0,u.has(m)?s++:h++)}const i=s+h,f=p&&i>0?s/i*100:null;let r=null;if(e==="denominator-only"){const d=i+a;r=p&&d>0?s/d*100:null}return{passDies:s,failDies:h,edgeExcludedDies:a,partialDies:l,totalDies:i,yieldPercent:f,yieldPercentGross:r}}function Z(t,n,e,u,c,a=0,l=0,s=0,h=0,p=0,i=!1,f=!1){if(!t)return[];const r=t.anchorDie??{x:0,y:0},d=xe(n,{width:t.width,height:t.height,diePitchX:u,diePitchY:c,anchorDie:{x:r.x-a,y:r.y-l},gridOrigin:{x:-s,y:-h}}),m=pe(he(i,f,n.center.x,n.center.y),ue(p,n.center.x,n.center.y));return d.filter(g=>{const v=g.width/2,b=g.height/2,D=[[g.x-v,g.y-b],[g.x+v,g.y-b],[g.x+v,g.y+b],[g.x-v,g.y+b]].map(([w,x])=>me(m,w,x)),k=Math.min(...D.map(w=>w.x)),A=Math.max(...D.map(w=>w.x)),F=Math.min(...D.map(w=>w.y)),P=Math.max(...D.map(w=>w.y));return e.some(w=>w.physX>=k&&w.physX<A&&w.physY>=F&&w.physY<P)})}function Ae(t,n,e){const c=Math.max(0,n.radius-e)**2;return t.map(a=>{const l=a.physX-n.center.x,s=a.physY-n.center.y;return l*l+s*s>c?{...a,edgeExcluded:!0}:a})}function Ce(t,n,e){const u=t.filter(E),c=t.filter(i=>!E(i)),a=new Map;for(const i of u){let f=a.get(i.x);f||(f=new Map,a.set(i.x,f)),f.set(i.y,(f.get(i.y)??0)+1)}const l=new Set(e);function s(i,f){const r=i.hbin,d=f.hbin;if(r===void 0||d===void 0)return!1;const m=l.has(r),g=l.has(d);return n==="best"?g!==m?g:d<r:g!==m?m:d>r}const h=new Map;for(const i of u){const f=V(i),r=h.get(f);n==="first"&&r||(n==="best"||n==="worst")&&r&&!s(r,i)||h.set(f,i)}return[...Array.from(h.values()).map(i=>{const r=a.get(i.x)?.get(i.y)??1;return r>1?{...i,retestCount:r}:i}),...c]}export function getDieTestValue(t,n){return t.testValues?.[n]}export function getTestPassStatus(t,n,e){const u=t.testPass?.[n];if(u!==void 0)return u;if(e!==void 0&&!isParametricTest(e)){const c=t.testValues?.[n];if(c===0||c===1)return c===1}}export function dieHasTestData(t){return t.testValues!==void 0&&Object.keys(t.testValues).length>0||t.testPass!==void 0&&Object.keys(t.testPass).length>0}function L(t,n){const e={};return n.hbin!==void 0&&(e.hbin=n.hbin),n.sbin!==void 0&&(e.sbin=n.sbin),n.retestCount!==void 0&&(e.retestCount=n.retestCount),n.siteNum!==void 0&&(e.siteNum=n.siteNum),n.partId!==void 0&&(e.partId=n.partId),n.metadata!==void 0&&(e.metadata=n.metadata),n.testPass!==void 0&&(e.testPass=n.testPass),n.testValues!==void 0&&(e.testValues=n.testValues),{...t,...e}}function ee(t,n){return n.plotMode?n.plotMode:t.some(dieHasTestData)?"value":"hardBin"}function te(t){return(t.warnings??[]).map(n=>{const e=Ie(n);return{code:e,message:n,severity:e==="non-standard-diameter"||e==="diameter-exceeds-die-extent"?"warning":"error",confidence:t.wafer.confidence}})}function Ie(t){return t.includes(ne)?"geometry-conflict":t.includes(ie)?"non-standard-diameter":t.includes(se)?"diameter-exceeds-die-extent":"partial-coverage"}const ne="do not fit inside the supplied",ie="Inferred wafer diameter",se="The probed dies reach only",Fe=.75,Pe=20;export const STANDARD_WAFER_DIAMETERS_MM=[100,125,150,200,300];const _e=.02;function oe(t,n){return n.reduce((e,u)=>Math.abs(u-t)<Math.abs(e-t)?u:e)}function Ye(t,n){if(!n.length)return!0;const e=oe(t,n);return Math.abs(e-t)/e<=_e}function $e(t){return t===void 0?STANDARD_WAFER_DIAMETERS_MM:t.filter(n=>Number.isFinite(n)&&n>0)}export function buildWaferMap(t,n){const e=Me(t),{debug:u,...c}=n??{},a=e.lotStackOpts?ke(e.lotStackOpts,e.testDefs):e.results;for(let o=0;o<Math.min(5,a.length);o++){const y=a[o];if(typeof y.x=="string"||typeof y.y=="string")throw new TypeError("buildWaferMap: x and y must be numbers, received strings. Did you forget to cast CSV values? e.g. { x: +row.x, y: +row.y }")}for(const o of a)if(o.x==null!=(o.y==null))throw new TypeError(`buildWaferMap: die has only one of x/y set (x=${String(o.x)}, y=${String(o.y)}). A die must be either fully positioned (both x and y) or fully unpositioned (neither) \u2014 omit both to mark a die as having no reported position.`);const l=Ce(a,e.retestPolicy,e.passBins),s={wafer:{confidence:1,method:"provided"},diePitch:{confidence:1,units:"mm"},grid:{confidence:1}};if(e.explicitDies){let o=e.explicitDies;const y=l.filter(E),R=l.filter(T=>!E(T));if(y.length>0){const T=new Map(y.map(S=>[V(S),S]));o=o.map(S=>{const G=T.get(V(S));return G?L(S,G):S})}const $=e.waferOpts?.diameter??300,I=j({diameter:$,notch:e.waferOpts?.notch,orientation:e.waferOpts?.orientation??0,metadata:e.waferOpts?.metadata});I.orientation!==0&&(o=q(o,I));const z=Z(e.reticleOpts,I,o,1,1,0,0,0,0,I.orientation),de=c.showReticle??e.reticleOpts!==void 0,B=H(I,o,{...c,reticles:z,showReticle:de,plotMode:ee(l,c),testDefs:e.testDefs,isLotStack:!1},{hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,metadataFields:e.metadataFields}),fe=R.map((T,S)=>L({id:`unpositioned_${S}`,width:o[0]?.width??1,height:o[0]?.height??1},T)),W=[...o,...fe];return{wafer:I,dies:W,view:B,reticleConfig:e.reticleOpts,units:"mm",inference:s,warnings:te(s),plotMode:B.plotMode,metadata:B.metadata,isLotStack:!1,dataCoverage:J(W),yield:Q(W,e.passBins,e.edgeDieYieldMode),reticles:z,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs,metadataFields:e.metadataFields}}const h=l.filter(E),p=l.filter(o=>!E(o)),i=h.map(o=>({x:o.x,y:o.y})),f=ye(i,e.dieOpts,e.waferOpts?.diameter);s.diePitch={confidence:f.confidence,units:f.units};const{pitchX:r,pitchY:d}=f,m=f.units,g=Oe(h,e.dieOpts),v=we(i);s.grid={confidence:v.confidence};const{offsetX:b,offsetY:D}=Re(i,g,v),{flipX:k,flipY:A}=ve(e.dieOpts,g),{colMidX:F,colMidY:P,anchored:w}=Se(i,b,D,r,d,e.waferOpts?.center);let x=e.waferOpts?.diameter;const _=i.map(o=>({x:(Math.round(o.x)-b)*r-F,y:(Math.round(o.y)-D)*d-P})),C=_.length>0?Math.max(..._.map(({x:o,y})=>Math.hypot(Math.abs(o)+r/2,Math.abs(y)+d/2))):0;if(x===void 0)if(i.length>0)if(m==="mm"){const o=ge(_,{minRadius:C});x=o.diameter,s.wafer={confidence:o.confidence,method:o.method};const y=$e(e.standardDiameters);if(!Ye(x,y)){const R=oe(x,y);(s.warnings??=[]).push(`${ie} ${x.toFixed(1)} mm is not a standard wafer size (nearest is ${R} mm). The diameter was derived from the die extent, which assumes the probed grid reaches the wafer edge \u2014 a non-standard result usually means it does not, because the outer dies were never probed. Every die is then placed against a wafer that is too small, which moves dies between rings and changes ring and edge findings. Supply waferConfig.diameter.`)}}else x=C*2,s.wafer={confidence:f.confidence*.8,method:"extent"};else x=m==="mm"?300:30,s.wafer={confidence:0,method:"default"};if(!w&&i.length>0&&Ee(i,b,D)&&(s.wafer.method="inferred-partial",(s.warnings??=[]).push("Wafer geometry was inferred from die positions alone. The data does not span a full symmetric wafer, so the inferred diameter and centre may be wrong and dies may be mis-positioned relative to the true wafer boundary. Supply waferConfig.diameter and waferConfig.center (the prober coordinate of the wafer centre) to position partial data correctly.")),e.waferOpts?.diameter!==void 0&&e.waferOpts.center===void 0&&C>0&&i.length>=Pe){const o=C/(x/2);o<Fe&&(s.warnings??=[]).push(`${se} ${(o*100).toFixed(0)}% of the ${x} mm wafer's radius. Either the diameter is larger than the real one, or this is a partial map covering only part of the wafer. Ring bands are equal-radius, so the outer rings are mostly or entirely empty and ring, quadrant and edge findings describe the assumed wafer rather than the probed area. Check waferConfig.diameter, or drop it and let the diameter be inferred from the die extent.`)}if(e.waferOpts?.diameter!==void 0){const o=e.dieOpts?.width!==void 0&&e.dieOpts?.height!==void 0;if(o&&C>x/2+1e-9){const y=_.filter(({x:R,y:$})=>Math.hypot(Math.abs(R)+r/2,Math.abs($)+d/2)>x/2+1e-9).length;(s.warnings??=[]).push(`${y} of ${_.length} probed die positions ${ne} ${x} mm wafer at the supplied die pitch of ${r} \xD7 ${d} mm. A die with test results is a real prober position and is always fully on the wafer, so one of the two supplied values must be wrong: containing these dies at this pitch would need a diameter of at least ${(C*2).toFixed(1)} mm. Check waferConfig.diameter and dieConfig.width/height against the real device.`)}}const O=j({diameter:x,notch:e.waferOpts?.notch,orientation:e.waferOpts?.orientation??0,metadata:e.waferOpts?.metadata});let M=h.map(o=>{const y=Math.round(o.x)-b,R=Math.round(o.y)-D,$={id:`${y}_${R}`,x:y,y:R,physX:y*r-F,physY:R*d-P,width:r,height:d,insideWafer:!0,partial:!1};return L($,o)});(b!==0||D!==0)&&(M=M.map(o=>({...o,x:o.x+b,y:o.y+D,id:`${o.x+b}_${o.y+D}`}))),M=q(M,O),(k||A)&&(M=le(M,{flipX:k,flipY:A},O.center));const X=[];e.waferOpts?.edgeExclusion&&e.waferOpts.edgeExclusion>0&&(M=Ae(M,O,e.waferOpts.edgeExclusion),e.waferOpts.edgeExclusion>O.radius&&X.push({code:"edge-exclusion-exceeds-radius",message:`edgeExclusion (${e.waferOpts.edgeExclusion}mm) exceeds the resolved wafer radius (${O.radius}mm) \u2014 the entire wafer is excluded.`,severity:"warning"}));const K=Z(e.reticleOpts,O,M,r,d,b,D,F,P,O.orientation,k,A),re=c.showReticle??e.reticleOpts!==void 0,Y=H(O,M,{...c,reticles:K,showReticle:re,plotMode:ee(l,c),testDefs:e.testDefs,dataAxisFlip:{x:k,y:A},isLotStack:e.lotStackOpts!==void 0,aggregationMethod:e.lotStackOpts?.method,lotSize:e.lotStackOpts?.results.length},{hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,metadataFields:e.metadataFields}),ae=p.map((o,y)=>L({id:`unpositioned_${y}`,width:r,height:d},o)),N=[...M,...ae];return{wafer:O,dies:N,view:Y,reticleConfig:e.reticleOpts,units:m,inference:s,warnings:[...te(s),...X],plotMode:Y.plotMode,metadata:Y.metadata,isLotStack:e.lotStackOpts!==void 0,dataCoverage:J(N),yield:Q(N,e.passBins,e.edgeDieYieldMode),reticles:K,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs,metadataFields:e.metadataFields,aggrMethod:Y.aggrMethod,lotSize:Y.lotSize}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{HARD_BIN_GREY as t,VIRIDIS as l,lerpKp as a,valueToGreyscale as i,hardBinColor as s,wangHash as u}from"./colorMap.js";const b=new Map;export function registerColorScheme(e,f){b.set(e,f)}export function getColorScheme(e){return b.get(e??"default")??b.get("default")}export function listColorSchemes(){return[...b.entries()].map(([e,f])=>({name:e,label:f.label}))}function c(e,f){const r=e.length-1;return n=>e[u(n^f)%r+1]}const d=[[0,0,255],[0,255,255],[255,255,0],[255,0,0]];registerColorScheme("default",{label:"Default",forBin:s,forValue:e=>a(d,e)}),registerColorScheme("viridis",{label:"Viridis",forBin:e=>a(l,Math.min(e,16)/16),forValue:e=>a(l,1-e)}),registerColorScheme("greyscale",{label:"Greyscale",forBin:c(t,2654435769),forValue:e=>i(1-e)});const B=["#aaaaaa","#51b3ec","#5199ec","#517fec","#5165ec","#5b51ec","#7a51ec","#1891d8","#1871d8","#1851d8","#1832d8","#2518d8","#4b18d8","#116697","#115097","#113997","#112397","#1a1197","#351197","#f39349","#f3a749","#f3bb49","#f3d149","#f3eb49","#e16a0e","#e1820e","#e19b0e","#e1b70e","#e1d70e","#9e4a0a","#9e5c0a","#9e6d0a","#9e810a","#9e970a","#54e8b7","#54e8ca","#54e8de","#54dee8","#54cae8","#1dd396","#1dd3af","#1dd3c7","#1dc7d3","#1dafd3","#149469","#14947b","#14948c","#148c94","#147b94","#bb5ce0","#d35ce0","#e05cd5","#e05cbf","#e05cab","#9b26c9","#b926c9","#c926bc","#c926a1","#c92688","#6d1b8d","#821b8d","#8d1b84","#8d1b71","#8d1b60"],I=[[0,32,77],[54,68,130],[107,107,145],[180,154,108],[253,228,32]];registerColorScheme("accessible",{label:"Accessible (CVD-safe / Cividis)",forBin:c(B,2654435769),forValue:e=>a(I,1-e)});const A=["#888888","#0d0887","#5302a3","#8b0aa5","#b83289","#db5c68","#f48849","#febc2a","#f0f921","#2c0594","#6a00a8","#a62098","#d0456d","#ec7958","#fad44c"],S=[[13,8,135],[126,3,168],[204,71,120],[248,149,64],[240,249,33]];registerColorScheme("plasma",{label:"Plasma",forBin:c(A,2654435769),forValue:e=>a(S,1-e)});const
|
|
1
|
+
import{HARD_BIN_GREY as t,VIRIDIS as l,lerpKp as a,valueToGreyscale as i,hardBinColor as s,wangHash as u}from"./colorMap.js";const b=new Map;export function registerColorScheme(e,f){b.set(e,f)}export function getColorScheme(e){return b.get(e??"default")??b.get("default")}export function listColorSchemes(){return[...b.entries()].map(([e,f])=>({name:e,label:f.label}))}function c(e,f){const r=e.length-1;return n=>e[u(n^f)%r+1]}const d=[[0,0,255],[0,255,255],[255,255,0],[255,0,0]];registerColorScheme("default",{label:"Default",forBin:s,forValue:e=>a(d,e)}),registerColorScheme("viridis",{label:"Viridis",forBin:e=>a(l,Math.min(e,16)/16),forValue:e=>a(l,1-e)}),registerColorScheme("greyscale",{label:"Greyscale",forBin:c(t,2654435769),forValue:e=>i(1-e)});const B=["#aaaaaa","#51b3ec","#5199ec","#517fec","#5165ec","#5b51ec","#7a51ec","#1891d8","#1871d8","#1851d8","#1832d8","#2518d8","#4b18d8","#116697","#115097","#113997","#112397","#1a1197","#351197","#f39349","#f3a749","#f3bb49","#f3d149","#f3eb49","#e16a0e","#e1820e","#e19b0e","#e1b70e","#e1d70e","#9e4a0a","#9e5c0a","#9e6d0a","#9e810a","#9e970a","#54e8b7","#54e8ca","#54e8de","#54dee8","#54cae8","#1dd396","#1dd3af","#1dd3c7","#1dc7d3","#1dafd3","#149469","#14947b","#14948c","#148c94","#147b94","#bb5ce0","#d35ce0","#e05cd5","#e05cbf","#e05cab","#9b26c9","#b926c9","#c926bc","#c926a1","#c92688","#6d1b8d","#821b8d","#8d1b84","#8d1b71","#8d1b60"],I=[[0,32,77],[54,68,130],[107,107,145],[180,154,108],[253,228,32]];registerColorScheme("accessible",{label:"Accessible (CVD-safe / Cividis)",forBin:c(B,2654435769),forValue:e=>a(I,1-e)});const A=["#888888","#0d0887","#5302a3","#8b0aa5","#b83289","#db5c68","#f48849","#febc2a","#f0f921","#2c0594","#6a00a8","#a62098","#d0456d","#ec7958","#fad44c"],S=[[13,8,135],[126,3,168],[204,71,120],[248,149,64],[240,249,33]];registerColorScheme("plasma",{label:"Plasma",forBin:c(A,2654435769),forValue:e=>a(S,1-e)});const V=["#aaaaaa","#000004","#1b0c41","#4a0c6b","#781c6d","#a52c60","#cf4446","#ed6925","#fb9b06","#f7d13d","#fcffa4","#2e0a47","#8e1e6e","#c8424b","#f5a623"],m=[[0,0,4],[87,16,110],[188,55,84],[249,142,9],[252,255,164]];registerColorScheme("inferno",{label:"Inferno",forBin:c(V,2654435769),forValue:e=>a(m,1-e)});const C=["#aaaaaa","#27ae60","#e74c3c","#f39c12","#2ecc71","#c0392b","#f1c40f","#1e8449","#e67e22","#a9cce3","#922b21","#82e0aa","#d4e6f1","#fdebd0","#d5f5e3"],_=[[46,204,113],[241,196,15],[231,76,60]];registerColorScheme("traffic",{label:"Traffic (Green\u2013Yellow\u2013Red)",forBin:c(C,2654435769),forValue:e=>a(_,e)});const h=["#aaaaaa","#2980b9","#e74c3c","#1abc9c","#f39c12","#8e44ad","#e67e22","#3498db","#c0392b","#16a085","#f1c40f","#154360","#922b21","#d6eaf8","#fdedec"];registerColorScheme("thermal",{label:"Thermal (Blue\u2013Cyan\u2013Yellow\u2013Red)",forBin:c(h,2654435769),forValue:e=>a(d,e)});const o=[[0,0,128],[0,0,255],[0,255,255],[255,255,0],[255,0,0],[128,0,0]];registerColorScheme("jet",{label:"Jet (MATLAB rainbow)",forBin:e=>a(o,Math.min(e,16)/16),forValue:e=>a(o,e)});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{isYieldEligibleDie as Z,getDieKey as x,isPositionedDie as ue}from"../core/dies.js";import{buildWaferMap as ke,getTestPassStatus as de,isParametricTest as q}from"../renderer/buildWaferMap.js";import{buildQuadrantRegions as ze,buildReticlePositionRegions as Me,buildRingRegions as Re,buildSectorRegions as Be,buildTestSiteRegions as Fe,sectorCompassNames as ge,areQuadrantsAdjacent as Pe,parseRegionKey as j}from"./regions.js";import{buildClusterFindings as Ve}from"./clusterDetection.js";import{classifyPattern as Le}from"./patternClassification.js";import{normalCdf as he}from"./math.js";import{quantile as ee}from"./math.js";import{mean as G,clamp01 as me}from"../core/utils.js";const Ne={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 Ce(s,a){const o={...s};return o.minimumClusterSize=Math.max(5,Math.round(a*.003)),o}function Ke(s){return"wafer"in s&&"dies"in s&&"view"in s?s:ke(s)}function Ee(s,a){return Z(s,a)?s.hbin!==void 0||s.sbin!==void 0||s.testValues!==void 0&&Object.keys(s.testValues).length>0:!1}function Ae(s,a,o){if(!s||a)return;const e=(o??[]).filter(n=>q(n)&&(n.limitLow!==void 0||n.limitHigh!==void 0));if(e.length!==0)return n=>{for(const f of e){const r=f.testNumber,i=n.testValues?.[r];if(i!==void 0&&(f.limitLow!==void 0&&i<f.limitLow||f.limitHigh!==void 0&&i>f.limitHigh))return!0}return!1}}function xe(s,a,o){const e=new Set,n=new Set,f=new Set,r=new Map,i=new Map;for(const l of s){if(l.testValues)for(const t of Object.keys(l.testValues))e.add(Number(t));if(l.testPass)for(const t of Object.keys(l.testPass))e.add(Number(t));l.hbin!==void 0&&n.add(l.hbin),l.sbin!==void 0&&f.add(l.sbin),Z(l)&&(l.hbin!==void 0&&r.set(l.hbin,(r.get(l.hbin)??0)+1),l.sbin!==void 0&&i.set(l.sbin,(i.get(l.sbin)??0)+1))}return{totalDies:s.length,analyzedDies:a,excludedDies:s.length-a,yieldPercent:o,testsConsidered:[...e].sort((l,t)=>l-t),hardBinsConsidered:[...n].sort((l,t)=>l-t),softBinsConsidered:[...f].sort((l,t)=>l-t),...r.size?{hardBinCounts:Object.fromEntries(r)}:{},...i.size?{softBinCounts:Object.fromEntries(i)}:{}}}function Te(s,a){if(!a?.length)return;const o=a.filter(n=>q(n)&&(n.limitLow!==void 0||n.limitHigh!==void 0));if(!o.length)return;const e=[];for(const n of o){const f=n.testNumber;let r=0,i=0,l=0,t=0;for(const c of s){if(c.partial||c.edgeExcluded)continue;const m=c.testValues?.[f];m!==void 0&&(t++,n.limitLow!==void 0&&m<n.limitLow?i++:n.limitHigh!==void 0&&m>n.limitHigh?l++:r++)}e.push({testNumber:f,label:n.name,passDies:r,failLowDies:i,failHighDies:l,totalDies:t,yieldPercent:t>0?r/t*100:null})}return e.length?e:void 0}export function computeFunctionalYield(s,a){if(!a?.length)return;const o=a.filter(n=>!q(n));if(!o.length)return;const e=[];for(const n of o){const f=n.testNumber;let r=0,i=0;for(const t of s){if(t.partial||t.edgeExcluded)continue;const c=de(t,f,n);c!==void 0&&(c?r++:i++)}const l=r+i;e.push({testNumber:f,label:n.name,passDies:r,failDies:i,totalDies:l,passRatePercent:l>0?r/l*100:null})}return e.length?e:void 0}function qe(s,a,o,e){const n=[];for(const f of a){const r=[];for(const m of s){if(m.partial||m.edgeExcluded)continue;const g=m.testValues?.[f];g!==void 0&&r.push(g)}if(r.length<e)continue;const i=G(r),l=Math.sqrt(re(r,i)),t=r.slice().sort((m,g)=>m-g),c=o?.find(m=>m.testNumber===f)?.name??String(f);n.push({testNumber:f,label:c,count:r.length,min:t[0],max:t[t.length-1],mean:i,stddev:l,median:ee(t,.5),q1:ee(t,.25),q3:ee(t,.75)})}return n.length?n:void 0}function H(s,a,o,e){const n=(s+o)/(a+e),f=n*(1-n)*(1/a+1/e);if(!Number.isFinite(f)||f<=0)return 1;const r=(s/a-o/e)/Math.sqrt(f);return me(2*(1-he(Math.abs(r))))}function te(s){const a=new Map;for(const o of s){const e=`${o.variable.kind}:${o.comparison.family}`,n=a.get(e)??[];n.push(o),a.set(e,n)}for(const o of a.values()){const e=[...o].filter(r=>r.stats.pValue!==void 0).sort((r,i)=>(r.stats.pValue??1)-(i.stats.pValue??1)),n=e.length;let f=1;for(let r=n-1;r>=0;r--){const i=e[r],l=i.stats.pValue??1,t=Math.min(1,Math.min(f,l*n/(r+1)));i.stats.adjustedPValue=t,f=t}}return s}function ne(s,a,o){const e=Math.abs(a),n=o!==void 0?Math.abs(o):0;return s<=.01&&(e>=.3||n>=2.5)?"unusual":s<=.05&&(e>=.2||n>=1.5)?"notable":"info"}function ie(s,a){return s<=.01&&Math.abs(a)>=.5?"unusual":s<=.05&&Math.abs(a)>=.15?"notable":"info"}function J(s){return s==="reticle-position"?"other reticle positions":s==="test-site"?"other test sites":"the rest of the map"}function O(s){return s==="reticle-position"?"Other reticle positions":s==="test-site"?"Other test sites":"Rest of map"}function be(s,a,o){const e=(Math.abs(a)*100).toFixed(1);return`${s} yield is ${e} percentage points ${a>0?"higher":"lower"} than ${J(o)}`}function se(s,a){return a==="quadrant"&&!s.startsWith("Quadrant")?`quadrant ${s}`:s}function pe(s,a,o,e){const n=se(s,e),f=(Math.abs(o)*100).toFixed(1);return`${n} has ${a} occurrence ${f} percentage points ${o>0?"higher":"lower"} than ${J(e)}`}function je(s,a,o,e){const n=se(s,e),f=(Math.abs(o)*100).toFixed(1);return`${n} has ${a} pass rate ${f} percentage points ${o>0?"higher":"lower"} than ${J(e)}`}function ye(s,a,o,e,n,f){const r=se(s,n),i=J(n),l=o>0?"higher":"lower";if(e!==void 0&&Number.isFinite(e)){const c=(Math.abs(e)*100).toFixed(1);return`${r} mean ${a} is ${c}% ${l} than ${i}`}const t=f?` ${f}`:"";return`${r} mean ${a} is ${Math.abs(o).toPrecision(3)}${t} ${l} than ${i}`}function Se(s,a,o){const e=a?.find(n=>n.bin===s);return e?.name?`${o} ${s} (${e.name})`:`${o} ${s}`}function He(s,a){const o=a?.find(e=>e.testNumber===s);return{label:o?.name??`Test ${s}`,unit:o?.unit}}function oe(s,a){const o=new Map(s.map(n=>[x(n),n])),e=new Map;for(const n of a){const f=[];for(const r of n.dieKeys){const i=o.get(r);i&&f.push(i)}e.set(n.key,f)}return e}function ae(s,a){return te(s),s.filter(o=>{const e=o.stats.adjustedPValue??o.stats.pValue??1,n=Math.abs(o.effect.absoluteDelta??0),f=Math.abs(o.effect.relativeDelta??0);return e<=a.significanceLevel&&(n>=a.minimumEffectSize||f>=a.minimumRelativeEffect)}).map(o=>({...o,severity:ne(o.stats.adjustedPValue??o.stats.pValue??1,o.effect.absoluteDelta??0,o.effect.relativeDelta)}))}function Y(s,a,o,e){const n=new Set(o),f=oe(s,a),r=new Map,i=new Map;for(const[t,c]of f){let m=0,g=0;for(const d of c)d.hbin!==void 0&&(g++,n.has(d.hbin)&&m++);r.set(t,m),i.set(t,g)}const l=[];for(const t of a){const c=i.get(t.key)??0,m=r.get(t.key)??0;let g=0,d=0;for(const[z,b]of i)z!==t.key&&(g+=b,d+=r.get(z)??0);if(c<e.minimumSampleSize||g<e.minimumSampleSize)continue;const h=m/c,p=d/g,y=h-p,S=H(m,c,d,g);l.push({id:`yield:${t.key}`,level:"wafer",severity:"info",variable:{kind:"yield",label:"Yield"},comparison:{family:t.family,left:t.label,right:O(t.family)},effect:{direction:y===0?"different":y>0?"higher":"lower",absoluteDelta:y,relativeDelta:p===0?void 0:y/p,effectSize:y},stats:{method:"two-proportion-z",pValue:S,sampleSizeLeft:c,sampleSizeRight:g},summary:be(t.label,y,t.family),highlight:{kind:"region",regionFamily:t.family,regionKeys:[t.key],dieKeys:[...t.dieKeys]}})}return ae(l,e)}function L(s,a,o,e,n,f){const r=d=>o==="soft"?d.sbin:d.hbin,i=[...new Set(s.map(r).filter(d=>d!==void 0))].sort((d,h)=>d-h),l=oe(s,a),t=new Map,c=new Map;for(const[d,h]of l){const p=new Map;for(const y of h){const S=r(y);S!==void 0&&p.set(S,(p.get(S)??0)+1)}t.set(d,p),c.set(d,h.length)}const m=[],g=n==="hardBin"?"HBin":"SBin";for(const d of a){const h=c.get(d.key)??0,p=t.get(d.key);let y=0;const S=new Map;for(const[z,b]of t)if(z!==d.key){y+=c.get(z)??0;for(const[w,D]of b)S.set(w,(S.get(w)??0)+D)}if(!(h<f.minimumSampleSize||y<f.minimumSampleSize))for(const z of i){const b=p.get(z)??0,w=S.get(z)??0,D=b/h,v=w/y,k=D-v,u=H(b,h,w,y),$=Se(z,e,g);m.push({id:`${n}:${z}:${d.key}`,level:"wafer",severity:"info",variable:{kind:n,bin:z,label:$},comparison:{family:d.family,left:d.label,right:O(d.family)},effect:{direction:k===0?"different":k>0?"higher":"lower",absoluteDelta:k,relativeDelta:v===0?void 0:k/v,effectSize:k},stats:{method:"two-proportion-z",pValue:u,sampleSizeLeft:h,sampleSizeRight:y},summary:pe(d.label,$,k,d.family),highlight:{kind:"bin",bin:z,regionKeys:[d.key],dieKeys:[...d.dieKeys]}})}}return ae(m,f)}function Q(s,a,o,e){const n=(o??[]).filter(c=>!q(c));if(!n.length)return[];const f=n.map(c=>c.testNumber),r=oe(s,a),i=new Map,l=new Map;for(const[c,m]of r){const g=new Uint32Array(n.length),d=new Uint32Array(n.length);for(const h of m)for(let p=0;p<n.length;p++){const y=de(h,f[p],n[p]);y!==void 0&&(d[p]++,y&&g[p]++)}i.set(c,g),l.set(c,d)}const t=[];for(const c of a){const m=i.get(c.key),g=l.get(c.key);for(let d=0;d<n.length;d++){let h=0,p=0;for(const[k,u]of l)k!==c.key&&(p+=u[d],h+=i.get(k)[d]);const y=g[d],S=m[d];if(y<e.minimumSampleSize||p<e.minimumSampleSize)continue;const z=S/y,b=h/p,w=z-b,D=H(S,y,h,p),v=f[d];t.push({id:`functional:${v}:${c.key}`,level:"wafer",severity:"info",variable:{kind:"functionalTest",index:v,label:`${n[d].name} pass rate`},comparison:{family:c.family,left:c.label,right:O(c.family)},effect:{direction:w===0?"different":w>0?"higher":"lower",absoluteDelta:w,relativeDelta:b===0?void 0:w/b,effectSize:w},stats:{method:"two-proportion-z",pValue:D,sampleSizeLeft:y,sampleSizeRight:p},summary:je(c.label,n[d].name,w,c.family),highlight:{kind:"region",regionFamily:c.family,regionKeys:[c.key],dieKeys:[...c.dieKeys]}})}}return ae(t,e)}function re(s,a){return s.length<2?0:s.reduce((o,e)=>o+(e-a)**2,0)/(s.length-1)}function we(s,a,o,e,n,f){const r=Math.sqrt(o/s+f/e),i=a-n;if(!Number.isFinite(r)||r===0)return{pValue:1,effectSize:0,delta:i};const l=i/r,t=Math.sqrt(Math.max(0,(o+f)/2)),c=t===0?i:i/t;return{pValue:me(2*(1-he(Math.abs(l)))),effectSize:c,delta:i}}function Oe(s,a){const o=G(s),e=G(a);return we(s.length,o,re(s,o),a.length,e,re(a,e))}const De=250;function ve(s,a,o){const e=r=>r.filter(i=>q(o?.find(l=>l.testNumber===i)));if(a)return{testNumbers:e(a.slice().sort((r,i)=>r-i))};const n=new Set;let f=!1;e:for(const r of s)for(const i of Object.keys(r.testValues??{})){const l=Number(i);if(!n.has(l)&&(n.add(l),n.size>De)){f=!0;break e}}if(f){const r=`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: ${r}`),{testNumbers:[],warning:{code:"test-count-capped",message:r,severity:"warning"}}}return{testNumbers:e([...n].sort((r,i)=>r-i))}}function _(s,a,o,e){const n=ve(s,e.testNumbers,o);if(n.warning)return{findings:[],warning:n.warning};const f=n.testNumbers,r=new Map;for(let h=0;h<a.length;h++)for(const p of a[h].dieKeys)r.set(p,h);const i=a.length,l=[],t=[];for(const h of s){const p=r.get(x(h));p!==void 0&&(l.push(h),t.push(p))}const c=[],m=new Float64Array(i),g=new Float64Array(i),d=new Float64Array(i);for(const h of f){m.fill(0),g.fill(0),d.fill(0);let p=0,y=0,S=0,z;for(let b=0;b<l.length;b++){const D=l[b].testValues?.[h];if(D===void 0)continue;z===void 0&&(z=D);const v=D-z,k=t[b];m[k]+=1,g[k]+=v,d[k]+=v*v,p+=1,y+=v,S+=v*v}if(z!==void 0)for(let b=0;b<i;b++){const w=m[b],D=p-w;if(w<e.minimumSampleSize||D<e.minimumSampleSize)continue;const v=g[b]/w,u=(y-g[b])/D,$=v+z,R=u+z,B=w>1?Math.max(0,d[b]-w*v*v)/(w-1):0,M=D>1?Math.max(0,S-d[b]-D*u*u)/(D-1):0,{pValue:N,effectSize:P,delta:C}=we(w,$,B,D,R,M),V=a[b],{label:W,unit:E}=He(h,o),U=R!==0?C/Math.abs(R):void 0;c.push({id:`test:${h}:${V.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:h,label:W,unit:E},comparison:{family:V.family,left:V.label,right:O(V.family)},effect:{direction:C===0?"different":C>0?"higher":"lower",absoluteDelta:C,relativeDelta:U,effectSize:P},stats:{method:"welch-z-approx",pValue:N,sampleSizeLeft:w,sampleSizeRight:D},summary:ye(V.label,W,C,U,V.family,E),highlight:{kind:"region",regionFamily:V.family,regionKeys:[V.key],dieKeys:[...V.dieKeys]}})}}return te(c),{activeTestNumbers:f,findings:c.filter(h=>{const p=h.stats.adjustedPValue??h.stats.pValue??1,y=Math.abs(h.effect.effectSize??0);return p<=e.significanceLevel&&y>=e.minimumEffectSize}).map(h=>({...h,severity:ie(h.stats.adjustedPValue??h.stats.pValue??1,h.effect.effectSize??0)}))}}function Ie(s,a,o,e){if(!o?.length)return[];const n=o.filter(i=>q(i)&&(i.limitLow!==void 0||i.limitHigh!==void 0));if(!n.length)return[];const f=[],r=new Map(s.map(i=>[x(i),i]));for(const i of n){const l=i.testNumber;for(const t of a){const c=new Map;for(const g of t){const d=[];for(const h of g.dieKeys){const p=r.get(h);p&&d.push(p)}c.set(g.key,d)}const m=[];for(const g of t){const d=c.get(g.key),h=[];for(const[$,R]of c)if($!==g.key)for(const B of R)h.push(B);const p=$=>$.testValues?.[l]!==void 0,y=$=>{const R=$.testValues?.[l];return R===void 0?!1:i.limitLow!==void 0&&R<i.limitLow||i.limitHigh!==void 0&&R>i.limitHigh},S=d.filter(p),z=h.filter(p);if(S.length<e.minimumSampleSize||z.length<e.minimumSampleSize)continue;const b=S.filter(y).length,w=z.filter(y).length,D=b/S.length,v=w/z.length,k=D-v,u=H(b,S.length,w,z.length);m.push({id:`specLimit:${l}:${g.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:l,label:i.name,unit:i.unit},comparison:{family:g.family,left:g.label,right:O(g.family)},effect:{direction:k===0?"different":k>0?"higher":"lower",absoluteDelta:k,relativeDelta:v===0?void 0:k/v,effectSize:k},stats:{method:"two-proportion-z",pValue:u,sampleSizeLeft:S.length,sampleSizeRight:z.length},summary:`${g.label} spec-fail rate for ${i.name} is ${(Math.abs(k)*100).toFixed(1)} pp ${k>0?"higher":"lower"} than the rest of the wafer`,highlight:{kind:"region",regionFamily:g.family,regionKeys:[g.key],dieKeys:[...g.dieKeys]}})}te(m),f.push(...m.filter(g=>{const d=g.stats.adjustedPValue??g.stats.pValue??1,h=Math.abs(g.effect.absoluteDelta??0),p=Math.abs(g.effect.relativeDelta??0);return d<=e.significanceLevel&&(h>=e.minimumEffectSize||p>=e.minimumRelativeEffect)}).map(g=>({...g,severity:ie(g.stats.adjustedPValue??g.stats.pValue??1,g.effect.effectSize??0)})))}}return f}function T(s){return s.highlight.regionKeys??[]}function Ye(s){const a=s.variable.bin??s.variable.index??"";return`${s.comparison.family}\0${s.variable.kind}\0${a}\0${s.effect.direction}`}function I(s,a){const o=T(s)[0]??"",e=j(o);return e.family==="ring"?e.ring??0:e.family==="sector"?ge(a).indexOf(e.sector??""):0}function Qe(s,a,o){if(a==="quadrant"){const r=new Map;for(const c of s){const m=j(T(c)[0]??"").quadrant;m&&r.set(m,c)}const i=[...r.keys()],l=new Set,t=[];for(const c of i){if(l.has(c))continue;const m=[],g=[c];for(l.add(c);g.length;){const d=g.pop();m.push(d);for(const h of i)!l.has(h)&&Pe(d,h)&&(l.add(h),g.push(h))}t.push(m.map(d=>r.get(d)))}return t}const e=[...s].sort((r,i)=>I(r,o)-I(i,o)),n=[];let f=[];for(const r of e){if(f.length===0){f.push(r);continue}const i=I(f[f.length-1],o);I(r,o)-i===1?f.push(r):(n.push(f),f=[r])}if(f.length&&n.push(f),a==="sector"&&n.length>1){const i=ge(o).length,l=I(n[0][0],o),t=I(n[n.length-1][n[n.length-1].length-1],o);if(l===0&&t===i-1){const c=n.pop();n[0]=[...c,...n[0]]}}return n}function _e(s,a){const o=s.map(r=>j(T(r)[0]??"").ring).filter(r=>r!==void 0).sort((r,i)=>r-i),e=o[0],n=o[o.length-1];let f="";return e===n&&e===1&&a>1?f=" (core)":e===n&&e===a&&a>1&&(f=" (edge)"),e===n?`Ring ${e}${f}`:`Rings ${e}\u2013${n}`}function We(s,a){const o=s.map(e=>j(T(e)[0]??"").sector??"");return o.length===1?`Sector ${o[0]}`:`Sectors ${o[0]}\u2013${o[o.length-1]}`}function Ue(s){const a=s.map(o=>j(T(o)[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 le(s){return[...new Set(s)]}function Ge(s,a){const o=s[0],e=o.comparison.family,n=o.variable.kind,f=le(s.flatMap(b=>b.highlight.dieKeys??[])),r=le(s.flatMap(T)),i=new Set(f),l=e==="ring"?_e(s,a.ringCount):e==="sector"?We(s,a.sectorCount):Ue(s),t=n==="softBin"?a.softEligibleDies:n==="test"?a.testDies:a.eligibleDies,c=t.filter(b=>i.has(x(b))),m=t.filter(b=>!i.has(x(b)));let g,d,h,p,y;if(n==="test"){const b=o.variable.index,w=M=>M.testValues?.[b],D=c.map(w).filter(M=>M!==void 0),v=m.map(w).filter(M=>M!==void 0),{pValue:k,effectSize:u,delta:$}=Oe(D,v),R=G(v),B=R!==0?$/Math.abs(R):void 0;g={direction:$===0?"different":$>0?"higher":"lower",absoluteDelta:$,relativeDelta:B,effectSize:u},d={method:"welch-z-approx",pValue:k,sampleSizeLeft:D.length,sampleSizeRight:v.length},h=ie(k,u),p=ye(l,o.variable.label,$,B,e,o.variable.unit),y=`test:${b}`}else if(n==="yield"){const b=new Set(a.passBins),w=c.filter(P=>P.hbin!==void 0),D=m.filter(P=>P.hbin!==void 0),v=w.length,k=D.length,u=w.filter(P=>b.has(P.hbin)).length,$=D.filter(P=>b.has(P.hbin)).length,R=v>0?u/v:0,B=k>0?$/k:0,M=R-B,N=H(u,v,$,k);g={direction:M===0?"different":M>0?"higher":"lower",absoluteDelta:M,relativeDelta:B===0?void 0:M/B,effectSize:M},d={method:"two-proportion-z",pValue:N,sampleSizeLeft:v,sampleSizeRight:k},h=ne(N,M,g.relativeDelta),p=be(l,M,e),y="yield"}else{const b=o.variable.bin,w=M=>n==="softBin"?M.sbin:M.hbin,D=c.filter(M=>w(M)===b).length,v=m.filter(M=>w(M)===b).length,k=D/c.length,u=v/m.length,$=k-u,R=H(D,c.length,v,m.length);g={direction:$===0?"different":$>0?"higher":"lower",absoluteDelta:$,relativeDelta:u===0?void 0:$/u,effectSize:$},d={method:"two-proportion-z",pValue:R,sampleSizeLeft:c.length,sampleSizeRight:m.length},h=ne(R,$,g.relativeDelta);const B=n==="softBin"?a.sbinDefs:a.hbinDefs;p=pe(l,Se(b,B,n==="softBin"?"SBin":"HBin"),$,e),y=`${n}:${b}`}const S=le(r.map(b=>b.replace(`${e}:`,""))).join("-"),z=n==="hardBin"||n==="softBin"?{kind:"bin",bin:o.variable.bin,regionKeys:r,dieKeys:f}:{kind:"region",regionFamily:e,regionKeys:r,dieKeys:f};return{...o,id:`${y}:${e}:${S}`,severity:h,comparison:{family:e,left:l,right:O(e)},effect:g,stats:d,summary:p,highlight:z,relatedIds:s.map(b=>b.id)}}function Je(s,a){const o=new Set(["ring","quadrant","sector"]),e=[],n=new Map;for(const r of s){if(!o.has(r.comparison.family)){e.push(r);continue}const i=Ye(r),l=n.get(i)??[];l.push(r),n.set(i,l)}const f=[];for(const r of n.values()){if(r.length===1){f.push(r[0]);continue}const i=r[0].comparison.family;for(const l of Qe(r,i,a.sectorCount))f.push(l.length===1?l[0]:Ge(l,a))}return[...e,...f]}function Xe(s){const a=new Map,o=new Map;for(const n of s){const f=x(n);n.hbin!==void 0&&n.hbin!==null&&(a.get(n.hbin)??a.set(n.hbin,new Set).get(n.hbin)).add(f),n.sbin!==void 0&&n.sbin!==null&&(o.get(n.sbin)??o.set(n.sbin,new Set).get(n.sbin)).add(f)}const e=new Set;for(const[n,f]of a)for(const[r,i]of o){if(f.size!==i.size)continue;let l=!0;for(const t of f)if(!i.has(t)){l=!1;break}l&&e.add(`${n}:${r}`)}return e}function Ze(s){return`${s.comparison.family}\0${s.comparison.left}`}function et(s,a,o){const e=(i,l)=>{i.absorbedIds=[...i.absorbedIds??[],l.id]},n=new Map;for(const i of s){const l=Ze(i);(n.get(l)??n.set(l,[]).get(l)).push(i)}const f=Xe(a),r=new Set;for(const i of n.values()){for(const t of i)if(!(t.variable.kind!=="hardBin"||t.variable.bin===void 0))for(const c of i){if(c.variable.kind!=="softBin"||c.variable.bin===void 0||r.has(c.id)||!f.has(`${t.variable.bin}:${c.variable.bin}`)||c.effect.direction!==t.effect.direction)continue;e(t,c),r.add(c.id);const m=`${t.variable.label} and ${c.variable.label} (same dies)`;t.summary=t.summary.replace(t.variable.label,m),t.variable.label=m;break}if(o.length!==1)continue;const l=i.find(t=>t.variable.kind==="yield");if(l){for(const t of i)if(!(t===l||r.has(t.id)||!((t.variable.kind==="hardBin"||t.variable.kind==="softBin")&&t.variable.bin===o[0]))){e(l,t),r.add(t.id);for(const m of t.absorbedIds??[])l.absorbedIds=[...l.absorbedIds??[],m]}}}}export function analyzeWaferMap(s,a={}){const o={...Ne,...a},e=Ke(s),n=e.isLotStack,f=e.aggrMethod,r=!n||f==="mode"||f==="countBin"||f==="percent",i=e.dies.filter(u=>Ee(u,o)),l=i.filter(ue),t=Ce(o,i.length),c=e.dies.filter(u=>Z(u,t)),m=c.filter(ue),g=Re(m,e.wafer,t.ringCount),d=ze(m,e.wafer,t.ringCount),h=t.enableReticlePositionAnalysis?Me(m,e.reticleConfig):[],p=t.enableTestSiteAnalysis===!1?[]:Fe(c,t.enableTestSiteAnalysis===!0),y=t.enableAngularAnalysis?Be(m,e.wafer,t.sectorCount):[],S=[];if(t.enableYieldAnalysis&&r&&S.push(...Y(i,g,t.passBins,t),...Y(i,d,t.passBins,t),...Y(i,h,t.passBins,t),...Y(i,p,t.passBins,t),...Y(i,y,t.passBins,t)),t.enableHardBinAnalysis&&r&&S.push(...L(i,g,"hard",e.hbinDefs,"hardBin",t),...L(i,d,"hard",e.hbinDefs,"hardBin",t),...L(i,h,"hard",e.hbinDefs,"hardBin",t),...L(i,p,"hard",e.hbinDefs,"hardBin",t),...L(i,y,"hard",e.hbinDefs,"hardBin",t)),t.enableSoftBinAnalysis){const u=i.filter($=>$.sbin!==void 0);S.push(...L(u,g,"soft",e.sbinDefs,"softBin",t),...L(u,d,"soft",e.sbinDefs,"softBin",t),...L(u,h,"soft",e.sbinDefs,"softBin",t),...L(u,p,"soft",e.sbinDefs,"softBin",t),...L(u,y,"soft",e.sbinDefs,"softBin",t))}const z=[];let b;if(t.enableTestValueAnalysis){const u=_(i,g,e.testDefs,t),$=_(i,d,e.testDefs,t),R=_(i,h,e.testDefs,t),B=_(i,p,e.testDefs,t),M=_(i,y,e.testDefs,t);S.push(...u.findings,...$.findings,...R.findings,...B.findings,...M.findings),u.warning&&z.push(u.warning),b=u.activeTestNumbers,S.push(...Ie(i,[g,d,h,p,y],e.testDefs,t)),S.push(...Q(i,g,e.testDefs,t),...Q(i,d,e.testDefs,t),...Q(i,h,e.testDefs,t),...Q(i,p,e.testDefs,t),...Q(i,y,e.testDefs,t))}if(t.enableClusterAnalysis){const u=Ae(n,r,e.testDefs);(!n||r||u!==void 0)&&S.push(...Ve(l,e.wafer,{...t,isFailingDie:u}))}const w=Je(S,{eligibleDies:i,softEligibleDies:i.filter(u=>u.sbin!==void 0),testDies:i,passBins:t.passBins,ringCount:t.ringCount,sectorCount:t.sectorCount,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs});if(S.length=0,S.push(...w),t.enablePatternClassification&&r){const u=Le(l,e.wafer,{passBins:t.passBins,ringCount:t.ringCount});if(u!==null&&u.pattern!=="random"&&u.pattern!=="none"){let $e=function(F){const K=T(F).map(A=>j(A).ring).filter(A=>A!==void 0);if(K.length===0)return!0;const fe=K.some(A=>A===U),ce=K.some(A=>A===1);return E==="edge-ring"||E==="edge-local"?fe:E==="center"?ce:E==="donut"?!fe&&!ce:!0};const R={center:"Center cluster",donut:"Donut","edge-ring":"Edge-ring","edge-local":"Edge-local",scratch:"Scratch","near-full":"Near-full"}[u.pattern]??u.pattern,B=u.confidence==="high"?"unusual":u.confidence==="medium"?"notable":"info",M=u.features,N=F=>`${(F*100).toFixed(0)}%`,P=u.pattern==="edge-ring"||u.pattern==="edge-local"?`${N(M.edgeRdd)} of edge dies failing`:u.pattern==="center"||u.pattern==="donut"?`centroid at ${N(M.centroidDistNorm)} of wafer radius from centre`:u.pattern==="scratch"?`linear score ${M.linearScore.toFixed(2)}, eccentricity ${M.eccentricity.toFixed(2)}`:`${N(M.globalRdd)} of dies failing`,C=i.filter(F=>{const K=F.hbin??F.sbin;return K!==void 0&&!new Set(t.passBins).has(K)}),V={"edge-ring":["ring","edge-arc"],"edge-local":["edge-arc","sector","quadrant"],center:["ring","cluster"],donut:["ring"],scratch:["cluster","sector","quadrant"],"near-full":["ring"]},W=new Set(V[u.pattern]??[]),E=u.pattern,U=t.ringCount,X=[];for(const F of S){if(!W.has(F.comparison.family)||F.comparison.family==="ring"&&!$e(F))continue;X.push(F.id);const K=F.comparison.family==="ring"||F.comparison.family==="quadrant"||F.comparison.family==="sector";B!=="info"&&K&&(F.severity="info")}S.push({id:`spatial-pattern:${u.pattern}`,level:"wafer",severity:B,variable:{kind:"spatialPattern",label:R},comparison:{family:"spatial-pattern",left:R,right:"Wafer"},effect:{direction:"different",effectSize:M.globalRdd},stats:{method:"geometry",sampleSizeLeft:C.length,sampleSizeRight:i.length},summary:`Spatial pattern: ${R.toLowerCase()} (${u.confidence} confidence) \u2014 ${P}${u.note?` [${u.note}]`:""}`,highlight:{kind:"dies",dieKeys:C.map(F=>x(F))},relatedIds:X.length>0?X:void 0})}}S.sort((u,$)=>{const R=u.stats.adjustedPValue??u.stats.pValue??1,B=$.stats.adjustedPValue??$.stats.pValue??1;return R!==B?R-B:Math.abs($.effect.absoluteDelta??0)-Math.abs(u.effect.absoluteDelta??0)});const D=xe(e.dies,i.length,e.yield.yieldPercent);n&&(D.isLotStack=!0,f&&(D.aggregationMethod=f));const v=Te(e.dies,e.testDefs);v&&(D.testSpecYield=v);const k=computeFunctionalYield(e.dies,e.testDefs);if(k&&(D.functionalYield=k),b===void 0&&t.computePerTestStats){const u=ve(i,t.testNumbers,e.testDefs);u.warning&&z.push(u.warning),b=u.testNumbers}if(b?.length){const u=qe(e.dies,b,e.testDefs,t.minimumSampleSize);u&&(D.perTestStats=u)}return z.length>0&&(D.warnings=z),et(S,i,t.passBins),{level:"wafer",hasNotableFindings:S.some(u=>u.severity==="notable"||u.severity==="unusual"),findings:S,wafer:e.wafer.metadata??void 0,stats:D}}
|
|
1
|
+
import{isYieldEligibleDie as ee,getDieKey as x,isPositionedDie as de}from"../core/dies.js";import{buildWaferMap as Re,getTestPassStatus as ge,isParametricTest as j}from"../renderer/buildWaferMap.js";import{buildQuadrantRegions as Be,buildReticlePositionRegions as Ce,buildRingRegions as Fe,buildSectorRegions as Ne,buildTestSiteRegions as Pe,sectorCompassNames as me,areQuadrantsAdjacent as Ve,parseRegionKey as H}from"./regions.js";import{buildClusterFindings as Le}from"./clusterDetection.js";import{classifyPattern as Ee}from"./patternClassification.js";import{normalCdf as he}from"./math.js";import{quantile as te}from"./math.js";import{mean as X,clamp01 as be}from"../core/utils.js";const Ae={significanceLevel:{min:1e-6,max:1},minimumEffectSize:{min:0,max:1},minimumRelativeEffect:{min:0,max:Number.MAX_SAFE_INTEGER},ringCount:{min:1,max:Number.MAX_SAFE_INTEGER}},pe=[4,8,16,32];function Ke(n){const s={..._,...n},a=[],o=[];for(const e of["significanceLevel","minimumEffectSize","minimumRelativeEffect","ringCount"]){const{min:c,max:l}=Ae[e],r=s[e];if(typeof r!="number"||!Number.isFinite(r)){o.push(`${e}=${String(r)} is not a finite number (using ${_[e]})`),s[e]=_[e];continue}const i=Math.min(l,Math.max(c,r));i!==r&&(o.push(`${e}=${r} is outside ${c}\u2013${l} (using ${i})`),s[e]=i)}if(!Number.isInteger(s.ringCount)){const e=Math.max(1,Math.round(s.ringCount));o.push(`ringCount=${s.ringCount} is not a whole number (using ${e})`),s.ringCount=e}if(pe.includes(s.sectorCount)||(o.push(`sectorCount=${s.sectorCount} is not one of ${pe.join(", ")} (using ${_.sectorCount})`),s.sectorCount=_.sectorCount),o.length){const e=`analyzeWaferMap: ${o.join("; ")}. The analysis ran with the corrected values.`;console.warn(`[wafermap] ${e}`),a.push({code:"analysis-option-corrected",message:e,severity:"warning"})}return{resolved:s,warnings:a}}const _={ringCount:4,passBins:[1],significanceLevel:.05,minimumEffectSize:.2,minimumRelativeEffect:1,minimumSampleSize:5,includePartial:!1,includeEdgeExcluded:!1,enableYieldAnalysis:!0,enableHardBinAnalysis:!0,enableSoftBinAnalysis:!0,enableTestValueAnalysis:!1,computePerTestStats:!1,enableReticlePositionAnalysis:!0,enableClusterAnalysis:!0,enableAngularAnalysis:!0,sectorCount:8,enablePatternClassification:!0,minimumClusterSize:5};function xe(n,s){const a={...n};return a.minimumClusterSize=Math.max(5,Math.round(s*.003)),a}function Te(n){return"wafer"in n&&"dies"in n&&"view"in n?n:Re(n)}function je(n,s){return ee(n,s)?n.hbin!==void 0||n.sbin!==void 0||n.testValues!==void 0&&Object.keys(n.testValues).length>0:!1}function He(n,s,a){if(!n||s)return;const o=(a??[]).filter(e=>j(e)&&(e.limitLow!==void 0||e.limitHigh!==void 0));if(o.length!==0)return e=>{for(const c of o){const l=c.testNumber,r=e.testValues?.[l];if(r!==void 0&&(c.limitLow!==void 0&&r<c.limitLow||c.limitHigh!==void 0&&r>c.limitHigh))return!0}return!1}}function qe(n,s,a){const o=new Set,e=new Set,c=new Set,l=new Map,r=new Map;for(const i of n){if(i.testValues)for(const f of Object.keys(i.testValues))o.add(Number(f));if(i.testPass)for(const f of Object.keys(i.testPass))o.add(Number(f));i.hbin!==void 0&&e.add(i.hbin),i.sbin!==void 0&&c.add(i.sbin),ee(i)&&(i.hbin!==void 0&&l.set(i.hbin,(l.get(i.hbin)??0)+1),i.sbin!==void 0&&r.set(i.sbin,(r.get(i.sbin)??0)+1))}return{totalDies:n.length,analyzedDies:s,excludedDies:n.length-s,yieldPercent:a,testsConsidered:[...o].sort((i,f)=>i-f),hardBinsConsidered:[...e].sort((i,f)=>i-f),softBinsConsidered:[...c].sort((i,f)=>i-f),...l.size?{hardBinCounts:Object.fromEntries(l)}:{},...r.size?{softBinCounts:Object.fromEntries(r)}:{}}}function Oe(n,s){if(!s?.length)return;const a=s.filter(e=>j(e)&&(e.limitLow!==void 0||e.limitHigh!==void 0));if(!a.length)return;const o=[];for(const e of a){const c=e.testNumber;let l=0,r=0,i=0,f=0;for(const t of n){if(t.partial||t.edgeExcluded)continue;const h=t.testValues?.[c];h!==void 0&&(f++,e.limitLow!==void 0&&h<e.limitLow?r++:e.limitHigh!==void 0&&h>e.limitHigh?i++:l++)}o.push({testNumber:c,label:e.name,passDies:l,failLowDies:r,failHighDies:i,totalDies:f,yieldPercent:f>0?l/f*100:null})}return o.length?o:void 0}export function computeFunctionalYield(n,s){if(!s?.length)return;const a=s.filter(e=>!j(e));if(!a.length)return;const o=[];for(const e of a){const c=e.testNumber;let l=0,r=0;for(const f of n){if(f.partial||f.edgeExcluded)continue;const t=ge(f,c,e);t!==void 0&&(t?l++:r++)}const i=l+r;o.push({testNumber:c,label:e.name,passDies:l,failDies:r,totalDies:i,passRatePercent:i>0?l/i*100:null})}return o.length?o:void 0}function Ie(n,s,a,o){const e=[];for(const c of s){const l=[];for(const h of n){if(h.partial||h.edgeExcluded)continue;const g=h.testValues?.[c];g!==void 0&&l.push(g)}if(l.length<o)continue;const r=X(l),i=Math.sqrt(le(l,r)),f=l.slice().sort((h,g)=>h-g),t=a?.find(h=>h.testNumber===c)?.name??String(c);e.push({testNumber:c,label:t,count:l.length,min:f[0],max:f[f.length-1],mean:r,stddev:i,median:te(f,.5),q1:te(f,.25),q3:te(f,.75)})}return e.length?e:void 0}function q(n,s,a,o){const e=(n+a)/(s+o),c=e*(1-e)*(1/s+1/o);if(!Number.isFinite(c)||c<=0)return 1;const l=(n/s-a/o)/Math.sqrt(c);return be(2*(1-he(Math.abs(l))))}function ne(n){const s=new Map;for(const a of n){const o=`${a.variable.kind}:${a.comparison.family}`,e=s.get(o)??[];e.push(a),s.set(o,e)}for(const a of s.values()){const o=[...a].filter(l=>l.stats.pValue!==void 0).sort((l,r)=>(l.stats.pValue??1)-(r.stats.pValue??1)),e=o.length;let c=1;for(let l=e-1;l>=0;l--){const r=o[l],i=r.stats.pValue??1,f=Math.min(1,Math.min(c,i*e/(l+1)));r.stats.adjustedPValue=f,c=f}}return n}function ie(n,s,a){const o=Math.abs(s),e=a!==void 0?Math.abs(a):0;return n<=.01&&(o>=.3||e>=2.5)?"unusual":n<=.05&&(o>=.2||e>=1.5)?"notable":"info"}function se(n,s){return n<=.01&&Math.abs(s)>=.5?"unusual":n<=.05&&Math.abs(s)>=.15?"notable":"info"}function J(n){return n==="reticle-position"?"other reticle positions":n==="test-site"?"other test sites":"the rest of the map"}function O(n){return n==="reticle-position"?"Other reticle positions":n==="test-site"?"Other test sites":"Rest of map"}function ye(n,s,a){const o=(Math.abs(s)*100).toFixed(1);return`${n} yield is ${o} percentage points ${s>0?"higher":"lower"} than ${J(a)}`}function oe(n,s){return s==="quadrant"&&!n.startsWith("Quadrant")?`quadrant ${n}`:n}function Se(n,s,a,o){const e=oe(n,o),c=(Math.abs(a)*100).toFixed(1);return`${e} has ${s} occurrence ${c} percentage points ${a>0?"higher":"lower"} than ${J(o)}`}function _e(n,s,a,o){const e=oe(n,o),c=(Math.abs(a)*100).toFixed(1);return`${e} has ${s} pass rate ${c} percentage points ${a>0?"higher":"lower"} than ${J(o)}`}function we(n,s,a,o,e,c){const l=oe(n,e),r=J(e),i=a>0?"higher":"lower";if(o!==void 0&&Number.isFinite(o)){const t=(Math.abs(o)*100).toFixed(1);return`${l} mean ${s} is ${t}% ${i} than ${r}`}const f=c?` ${c}`:"";return`${l} mean ${s} is ${Math.abs(a).toPrecision(3)}${f} ${i} than ${r}`}function $e(n,s,a){const o=s?.find(e=>e.bin===n);return o?.name?`${a} ${n} (${o.name})`:`${a} ${n}`}function Ye(n,s){const a=s?.find(o=>o.testNumber===n);return{label:a?.name??`Test ${n}`,unit:a?.unit}}function ae(n,s){const a=new Map(n.map(e=>[x(e),e])),o=new Map;for(const e of s){const c=[];for(const l of e.dieKeys){const r=a.get(l);r&&c.push(r)}o.set(e.key,c)}return o}function re(n,s){return ne(n),n.filter(a=>{const o=a.stats.adjustedPValue??a.stats.pValue??1,e=Math.abs(a.effect.absoluteDelta??0),c=Math.abs(a.effect.relativeDelta??0);return o<=s.significanceLevel&&(e>=s.minimumEffectSize||c>=s.minimumRelativeEffect)}).map(a=>({...a,severity:ie(a.stats.adjustedPValue??a.stats.pValue??1,a.effect.absoluteDelta??0,a.effect.relativeDelta)}))}function Y(n,s,a,o){const e=new Set(a),c=ae(n,s),l=new Map,r=new Map;for(const[f,t]of c){let h=0,g=0;for(const d of t)d.hbin!==void 0&&(g++,e.has(d.hbin)&&h++);l.set(f,h),r.set(f,g)}const i=[];for(const f of s){const t=r.get(f.key)??0,h=l.get(f.key)??0;let g=0,d=0;for(const[y,b]of r)y!==f.key&&(g+=b,d+=l.get(y)??0);if(t<o.minimumSampleSize||g<o.minimumSampleSize)continue;const m=h/t,p=d/g,S=m-p,D=q(h,t,d,g);i.push({id:`yield:${f.key}`,level:"wafer",severity:"info",variable:{kind:"yield",label:"Yield"},comparison:{family:f.family,left:f.label,right:O(f.family)},effect:{direction:S===0?"different":S>0?"higher":"lower",absoluteDelta:S,relativeDelta:p===0?void 0:S/p,effectSize:S},stats:{method:"two-proportion-z",pValue:D,sampleSizeLeft:t,sampleSizeRight:g},summary:ye(f.label,S,f.family),highlight:{kind:"region",regionFamily:f.family,regionKeys:[f.key],dieKeys:[...f.dieKeys]}})}return re(i,o)}function V(n,s,a,o,e,c){const l=d=>a==="soft"?d.sbin:d.hbin,r=[...new Set(n.map(l).filter(d=>d!==void 0))].sort((d,m)=>d-m),i=ae(n,s),f=new Map,t=new Map;for(const[d,m]of i){const p=new Map;for(const S of m){const D=l(S);D!==void 0&&p.set(D,(p.get(D)??0)+1)}f.set(d,p),t.set(d,m.length)}const h=[],g=e==="hardBin"?"HBin":"SBin";for(const d of s){const m=t.get(d.key)??0,p=f.get(d.key);let S=0;const D=new Map;for(const[y,b]of f)if(y!==d.key){S+=t.get(y)??0;for(const[$,k]of b)D.set($,(D.get($)??0)+k)}if(!(m<c.minimumSampleSize||S<c.minimumSampleSize))for(const y of r){const b=p.get(y)??0,$=D.get(y)??0,k=b/m,w=$/S,v=k-w,R=q(b,m,$,S),u=$e(y,o,g);h.push({id:`${e}:${y}:${d.key}`,level:"wafer",severity:"info",variable:{kind:e,bin:y,label:u},comparison:{family:d.family,left:d.label,right:O(d.family)},effect:{direction:v===0?"different":v>0?"higher":"lower",absoluteDelta:v,relativeDelta:w===0?void 0:v/w,effectSize:v},stats:{method:"two-proportion-z",pValue:R,sampleSizeLeft:m,sampleSizeRight:S},summary:Se(d.label,u,v,d.family),highlight:{kind:"bin",bin:y,regionKeys:[d.key],dieKeys:[...d.dieKeys]}})}}return re(h,c)}function W(n,s,a,o){const e=(a??[]).filter(t=>!j(t));if(!e.length)return[];const c=e.map(t=>t.testNumber),l=ae(n,s),r=new Map,i=new Map;for(const[t,h]of l){const g=new Uint32Array(e.length),d=new Uint32Array(e.length);for(const m of h)for(let p=0;p<e.length;p++){const S=ge(m,c[p],e[p]);S!==void 0&&(d[p]++,S&&g[p]++)}r.set(t,g),i.set(t,d)}const f=[];for(const t of s){const h=r.get(t.key),g=i.get(t.key);for(let d=0;d<e.length;d++){let m=0,p=0;for(const[v,R]of i)v!==t.key&&(p+=R[d],m+=r.get(v)[d]);const S=g[d],D=h[d];if(S<o.minimumSampleSize||p<o.minimumSampleSize)continue;const y=D/S,b=m/p,$=y-b,k=q(D,S,m,p),w=c[d];f.push({id:`functional:${w}:${t.key}`,level:"wafer",severity:"info",variable:{kind:"functionalTest",index:w,label:`${e[d].name} pass rate`},comparison:{family:t.family,left:t.label,right:O(t.family)},effect:{direction:$===0?"different":$>0?"higher":"lower",absoluteDelta:$,relativeDelta:b===0?void 0:$/b,effectSize:$},stats:{method:"two-proportion-z",pValue:k,sampleSizeLeft:S,sampleSizeRight:p},summary:_e(t.label,e[d].name,$,t.family),highlight:{kind:"region",regionFamily:t.family,regionKeys:[t.key],dieKeys:[...t.dieKeys]}})}}return re(f,o)}function le(n,s){return n.length<2?0:n.reduce((a,o)=>a+(o-s)**2,0)/(n.length-1)}function ve(n,s,a,o,e,c){const l=Math.sqrt(a/n+c/o),r=s-e;if(!Number.isFinite(l)||l===0)return{pValue:1,effectSize:0,delta:r};const i=r/l,f=Math.sqrt(Math.max(0,(a+c)/2)),t=f===0?r:r/f;return{pValue:be(2*(1-he(Math.abs(i)))),effectSize:t,delta:r}}function We(n,s){const a=X(n),o=X(s);return ve(n.length,a,le(n,a),s.length,o,le(s,o))}const De=250;function ke(n,s,a){const o=l=>l.filter(r=>j(a?.find(i=>i.testNumber===r)));if(s)return{testNumbers:o(s.slice().sort((l,r)=>l-r))};const e=new Set;let c=!1;e:for(const l of n)for(const r of Object.keys(l.testValues??{})){const i=Number(r);if(!e.has(i)&&(e.add(i),e.size>De)){c=!0;break e}}if(c){const l=`Test-value analysis was skipped: more than ${De} tests were found in the die data, so no test findings were computed. Pass testNumbers: [...] to analyse specific tests.`;return console.warn(`[wafermap] analyzeWaferMap: ${l}`),{testNumbers:[],warning:{code:"test-count-capped",message:l,severity:"warning"}}}return{testNumbers:o([...e].sort((l,r)=>l-r))}}function Q(n,s,a,o){const e=ke(n,o.testNumbers,a);if(e.warning)return{findings:[],warning:e.warning};const c=e.testNumbers,l=new Map;for(let m=0;m<s.length;m++)for(const p of s[m].dieKeys)l.set(p,m);const r=s.length,i=[],f=[];for(const m of n){const p=l.get(x(m));p!==void 0&&(i.push(m),f.push(p))}const t=[],h=new Float64Array(r),g=new Float64Array(r),d=new Float64Array(r);for(const m of c){h.fill(0),g.fill(0),d.fill(0);let p=0,S=0,D=0,y;for(let b=0;b<i.length;b++){const k=i[b].testValues?.[m];if(k===void 0)continue;y===void 0&&(y=k);const w=k-y,v=f[b];h[v]+=1,g[v]+=w,d[v]+=w*w,p+=1,S+=w,D+=w*w}if(y!==void 0)for(let b=0;b<r;b++){const $=h[b],k=p-$;if($<o.minimumSampleSize||k<o.minimumSampleSize)continue;const w=g[b]/$,R=(S-g[b])/k,u=w+y,M=R+y,B=$>1?Math.max(0,d[b]-$*w*w)/($-1):0,z=k>1?Math.max(0,D-d[b]-k*R*R)/(k-1):0,{pValue:F,effectSize:N,delta:E}=ve($,u,B,k,M,z),P=s[b],{label:U,unit:G}=Ye(m,a),A=M!==0?E/Math.abs(M):void 0;t.push({id:`test:${m}:${P.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:m,label:U,unit:G},comparison:{family:P.family,left:P.label,right:O(P.family)},effect:{direction:E===0?"different":E>0?"higher":"lower",absoluteDelta:E,relativeDelta:A,effectSize:N},stats:{method:"welch-z-approx",pValue:F,sampleSizeLeft:$,sampleSizeRight:k},summary:we(P.label,U,E,A,P.family,G),highlight:{kind:"region",regionFamily:P.family,regionKeys:[P.key],dieKeys:[...P.dieKeys]}})}}return ne(t),{activeTestNumbers:c,findings:t.filter(m=>{const p=m.stats.adjustedPValue??m.stats.pValue??1,S=Math.abs(m.effect.effectSize??0);return p<=o.significanceLevel&&S>=o.minimumEffectSize}).map(m=>({...m,severity:se(m.stats.adjustedPValue??m.stats.pValue??1,m.effect.effectSize??0)}))}}function Qe(n,s,a,o){if(!a?.length)return[];const e=a.filter(r=>j(r)&&(r.limitLow!==void 0||r.limitHigh!==void 0));if(!e.length)return[];const c=[],l=new Map(n.map(r=>[x(r),r]));for(const r of e){const i=r.testNumber;for(const f of s){const t=new Map;for(const g of f){const d=[];for(const m of g.dieKeys){const p=l.get(m);p&&d.push(p)}t.set(g.key,d)}const h=[];for(const g of f){const d=t.get(g.key),m=[];for(const[u,M]of t)if(u!==g.key)for(const B of M)m.push(B);const p=u=>u.testValues?.[i]!==void 0,S=u=>{const M=u.testValues?.[i];return M===void 0?!1:r.limitLow!==void 0&&M<r.limitLow||r.limitHigh!==void 0&&M>r.limitHigh},D=d.filter(p),y=m.filter(p);if(D.length<o.minimumSampleSize||y.length<o.minimumSampleSize)continue;const b=D.filter(S).length,$=y.filter(S).length,k=b/D.length,w=$/y.length,v=k-w,R=q(b,D.length,$,y.length);h.push({id:`specLimit:${i}:${g.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:i,label:r.name,unit:r.unit},comparison:{family:g.family,left:g.label,right:O(g.family)},effect:{direction:v===0?"different":v>0?"higher":"lower",absoluteDelta:v,relativeDelta:w===0?void 0:v/w,effectSize:v},stats:{method:"two-proportion-z",pValue:R,sampleSizeLeft:D.length,sampleSizeRight:y.length},summary:`${g.label} spec-fail rate for ${r.name} is ${(Math.abs(v)*100).toFixed(1)} pp ${v>0?"higher":"lower"} than the rest of the wafer`,highlight:{kind:"region",regionFamily:g.family,regionKeys:[g.key],dieKeys:[...g.dieKeys]}})}ne(h),c.push(...h.filter(g=>{const d=g.stats.adjustedPValue??g.stats.pValue??1,m=Math.abs(g.effect.absoluteDelta??0),p=Math.abs(g.effect.relativeDelta??0);return d<=o.significanceLevel&&(m>=o.minimumEffectSize||p>=o.minimumRelativeEffect)}).map(g=>({...g,severity:se(g.stats.adjustedPValue??g.stats.pValue??1,g.effect.effectSize??0)})))}}return c}function T(n){return n.highlight.regionKeys??[]}function Ue(n){const s=n.variable.bin??n.variable.index??"";return`${n.comparison.family}\0${n.variable.kind}\0${s}\0${n.effect.direction}`}function I(n,s){const a=T(n)[0]??"",o=H(a);return o.family==="ring"?o.ring??0:o.family==="sector"?me(s).indexOf(o.sector??""):0}function Ge(n,s,a){if(s==="quadrant"){const l=new Map;for(const t of n){const h=H(T(t)[0]??"").quadrant;h&&l.set(h,t)}const r=[...l.keys()],i=new Set,f=[];for(const t of r){if(i.has(t))continue;const h=[],g=[t];for(i.add(t);g.length;){const d=g.pop();h.push(d);for(const m of r)!i.has(m)&&Ve(d,m)&&(i.add(m),g.push(m))}f.push(h.map(d=>l.get(d)))}return f}const o=[...n].sort((l,r)=>I(l,a)-I(r,a)),e=[];let c=[];for(const l of o){if(c.length===0){c.push(l);continue}const r=I(c[c.length-1],a);I(l,a)-r===1?c.push(l):(e.push(c),c=[l])}if(c.length&&e.push(c),s==="sector"&&e.length>1){const r=me(a).length,i=I(e[0][0],a),f=I(e[e.length-1][e[e.length-1].length-1],a);if(i===0&&f===r-1){const t=e.pop();e[0]=[...t,...e[0]]}}return e}function Xe(n,s){const a=n.map(l=>H(T(l)[0]??"").ring).filter(l=>l!==void 0).sort((l,r)=>l-r),o=a[0],e=a[a.length-1];let c="";return o===e&&o===1&&s>1?c=" (core)":o===e&&o===s&&s>1&&(c=" (edge)"),o===e?`Ring ${o}${c}`:`Rings ${o}\u2013${e}`}function Je(n,s){const a=n.map(o=>H(T(o)[0]??"").sector??"");return a.length===1?`Sector ${a[0]}`:`Sectors ${a[0]}\u2013${a[a.length-1]}`}function Ze(n){const s=n.map(a=>H(T(a)[0]??"").quadrant??"");return s.length===1?s[0]:s.length===2?`Quadrants ${s[0]} & ${s[1]}`:`Quadrants ${s.slice(0,-1).join(", ")} & ${s[s.length-1]}`}function fe(n){return[...new Set(n)]}function et(n,s){const a=n[0],o=a.comparison.family,e=a.variable.kind,c=fe(n.flatMap(b=>b.highlight.dieKeys??[])),l=fe(n.flatMap(T)),r=new Set(c),i=o==="ring"?Xe(n,s.ringCount):o==="sector"?Je(n,s.sectorCount):Ze(n),f=e==="softBin"?s.softEligibleDies:e==="test"?s.testDies:s.eligibleDies,t=f.filter(b=>r.has(x(b))),h=f.filter(b=>!r.has(x(b)));let g,d,m,p,S;if(e==="test"){const b=a.variable.index,$=z=>z.testValues?.[b],k=t.map($).filter(z=>z!==void 0),w=h.map($).filter(z=>z!==void 0),{pValue:v,effectSize:R,delta:u}=We(k,w),M=X(w),B=M!==0?u/Math.abs(M):void 0;g={direction:u===0?"different":u>0?"higher":"lower",absoluteDelta:u,relativeDelta:B,effectSize:R},d={method:"welch-z-approx",pValue:v,sampleSizeLeft:k.length,sampleSizeRight:w.length},m=se(v,R),p=we(i,a.variable.label,u,B,o,a.variable.unit),S=`test:${b}`}else if(e==="yield"){const b=new Set(s.passBins),$=t.filter(N=>N.hbin!==void 0),k=h.filter(N=>N.hbin!==void 0),w=$.length,v=k.length,R=$.filter(N=>b.has(N.hbin)).length,u=k.filter(N=>b.has(N.hbin)).length,M=w>0?R/w:0,B=v>0?u/v:0,z=M-B,F=q(R,w,u,v);g={direction:z===0?"different":z>0?"higher":"lower",absoluteDelta:z,relativeDelta:B===0?void 0:z/B,effectSize:z},d={method:"two-proportion-z",pValue:F,sampleSizeLeft:w,sampleSizeRight:v},m=ie(F,z,g.relativeDelta),p=ye(i,z,o),S="yield"}else{const b=a.variable.bin,$=z=>e==="softBin"?z.sbin:z.hbin,k=t.filter(z=>$(z)===b).length,w=h.filter(z=>$(z)===b).length,v=k/t.length,R=w/h.length,u=v-R,M=q(k,t.length,w,h.length);g={direction:u===0?"different":u>0?"higher":"lower",absoluteDelta:u,relativeDelta:R===0?void 0:u/R,effectSize:u},d={method:"two-proportion-z",pValue:M,sampleSizeLeft:t.length,sampleSizeRight:h.length},m=ie(M,u,g.relativeDelta);const B=e==="softBin"?s.sbinDefs:s.hbinDefs;p=Se(i,$e(b,B,e==="softBin"?"SBin":"HBin"),u,o),S=`${e}:${b}`}const D=fe(l.map(b=>b.replace(`${o}:`,""))).join("-"),y=e==="hardBin"||e==="softBin"?{kind:"bin",bin:a.variable.bin,regionKeys:l,dieKeys:c}:{kind:"region",regionFamily:o,regionKeys:l,dieKeys:c};return{...a,id:`${S}:${o}:${D}`,severity:m,comparison:{family:o,left:i,right:O(o)},effect:g,stats:d,summary:p,highlight:y,relatedIds:n.map(b=>b.id)}}function tt(n,s){const a=new Set(["ring","quadrant","sector"]),o=[],e=new Map;for(const l of n){if(!a.has(l.comparison.family)){o.push(l);continue}const r=Ue(l),i=e.get(r)??[];i.push(l),e.set(r,i)}const c=[];for(const l of e.values()){if(l.length===1){c.push(l[0]);continue}const r=l[0].comparison.family;for(const i of Ge(l,r,s.sectorCount))c.push(i.length===1?i[0]:et(i,s))}return[...o,...c]}function nt(n){const s=new Map,a=new Map;for(const e of n){const c=x(e);e.hbin!==void 0&&e.hbin!==null&&(s.get(e.hbin)??s.set(e.hbin,new Set).get(e.hbin)).add(c),e.sbin!==void 0&&e.sbin!==null&&(a.get(e.sbin)??a.set(e.sbin,new Set).get(e.sbin)).add(c)}const o=new Set;for(const[e,c]of s)for(const[l,r]of a){if(c.size!==r.size)continue;let i=!0;for(const f of c)if(!r.has(f)){i=!1;break}i&&o.add(`${e}:${l}`)}return o}function it(n){return`${n.comparison.family}\0${n.comparison.left}`}function st(n,s,a){const o=(r,i)=>{r.absorbedIds=[...r.absorbedIds??[],i.id]},e=new Map;for(const r of n){const i=it(r);(e.get(i)??e.set(i,[]).get(i)).push(r)}const c=nt(s),l=new Set;for(const r of e.values()){for(const f of r)if(!(f.variable.kind!=="hardBin"||f.variable.bin===void 0))for(const t of r){if(t.variable.kind!=="softBin"||t.variable.bin===void 0||l.has(t.id)||!c.has(`${f.variable.bin}:${t.variable.bin}`)||t.effect.direction!==f.effect.direction)continue;o(f,t),l.add(t.id);const h=f.variable.label.replace(/^HBin\s+/,""),g=t.variable.label.replace(/^SBin\s+/,""),d=h===g?`HBin and SBin ${h} (same dies)`:`${f.variable.label} and ${t.variable.label} (same dies)`;f.summary=f.summary.replace(f.variable.label,d),f.variable.label=d;break}if(a.length!==1)continue;const i=r.find(f=>f.variable.kind==="yield");if(i){for(const f of r)if(!(f===i||l.has(f.id)||!((f.variable.kind==="hardBin"||f.variable.kind==="softBin")&&f.variable.bin===a[0]))){o(i,f),l.add(f.id);for(const h of f.absorbedIds??[])i.absorbedIds=[...i.absorbedIds??[],h]}}}}export function analyzeWaferMap(n,s={}){const{resolved:a,warnings:o}=Ke(s),e=Te(n),c=e.isLotStack,l=e.aggrMethod,r=!c||l==="mode"||l==="countBin"||l==="percent",i=e.dies.filter(u=>je(u,a)),f=i.filter(de),t=xe(a,i.length),h=e.dies.filter(u=>ee(u,t)),g=h.filter(de),d=Fe(g,e.wafer,t.ringCount),m=Be(g,e.wafer,t.ringCount),p=t.enableReticlePositionAnalysis?Ce(g,e.reticleConfig):[],S=t.enableTestSiteAnalysis===!1?[]:Pe(h,t.enableTestSiteAnalysis===!0),D=t.enableAngularAnalysis?Ne(g,e.wafer,t.sectorCount):[],y=[];if(t.enableYieldAnalysis&&r&&y.push(...Y(i,d,t.passBins,t),...Y(i,m,t.passBins,t),...Y(i,p,t.passBins,t),...Y(i,S,t.passBins,t),...Y(i,D,t.passBins,t)),t.enableHardBinAnalysis&&r&&y.push(...V(i,d,"hard",e.hbinDefs,"hardBin",t),...V(i,m,"hard",e.hbinDefs,"hardBin",t),...V(i,p,"hard",e.hbinDefs,"hardBin",t),...V(i,S,"hard",e.hbinDefs,"hardBin",t),...V(i,D,"hard",e.hbinDefs,"hardBin",t)),t.enableSoftBinAnalysis){const u=i.filter(M=>M.sbin!==void 0);y.push(...V(u,d,"soft",e.sbinDefs,"softBin",t),...V(u,m,"soft",e.sbinDefs,"softBin",t),...V(u,p,"soft",e.sbinDefs,"softBin",t),...V(u,S,"soft",e.sbinDefs,"softBin",t),...V(u,D,"soft",e.sbinDefs,"softBin",t))}const b=[...o];let $;if(t.enableTestValueAnalysis){const u=Q(i,d,e.testDefs,t),M=Q(i,m,e.testDefs,t),B=Q(i,p,e.testDefs,t),z=Q(i,S,e.testDefs,t),F=Q(i,D,e.testDefs,t);y.push(...u.findings,...M.findings,...B.findings,...z.findings,...F.findings),u.warning&&b.push(u.warning),$=u.activeTestNumbers,y.push(...Qe(i,[d,m,p,S,D],e.testDefs,t)),y.push(...W(i,d,e.testDefs,t),...W(i,m,e.testDefs,t),...W(i,p,e.testDefs,t),...W(i,S,e.testDefs,t),...W(i,D,e.testDefs,t))}if(t.enableClusterAnalysis){const u=He(c,r,e.testDefs);(!c||r||u!==void 0)&&y.push(...Le(f,e.wafer,{...t,isFailingDie:u}))}const k=tt(y,{eligibleDies:i,softEligibleDies:i.filter(u=>u.sbin!==void 0),testDies:i,passBins:t.passBins,ringCount:t.ringCount,sectorCount:t.sectorCount,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs});if(y.length=0,y.push(...k),t.enablePatternClassification&&r){const u=Ee(f,e.wafer,{passBins:t.passBins,ringCount:t.ringCount});if(u!==null&&u.pattern!=="random"&&u.pattern!=="none"){let Me=function(C){const L=T(C).map(K=>H(K).ring).filter(K=>K!==void 0);if(L.length===0)return!0;const ce=L.some(K=>K===ze),ue=L.some(K=>K===1);return A==="edge-ring"||A==="edge-local"?ce:A==="center"?ue:A==="donut"?!ce&&!ue:!0};const B={center:"Center cluster",donut:"Donut","edge-ring":"Edge-ring","edge-local":"Edge-local",scratch:"Scratch","near-full":"Near-full"}[u.pattern]??u.pattern,z=u.confidence==="high"?"unusual":u.confidence==="medium"?"notable":"info",F=u.features,N=C=>`${(C*100).toFixed(0)}%`,E=u.pattern==="edge-ring"||u.pattern==="edge-local"?`${N(F.edgeRdd)} of edge dies failing`:u.pattern==="center"||u.pattern==="donut"?`centroid at ${N(F.centroidDistNorm)} of wafer radius from centre`:u.pattern==="scratch"?`linear score ${F.linearScore.toFixed(2)}, eccentricity ${F.eccentricity.toFixed(2)}`:`${N(F.globalRdd)} of dies failing`,P=i.filter(C=>{const L=C.hbin??C.sbin;return L!==void 0&&!new Set(t.passBins).has(L)}),U={"edge-ring":["ring","edge-arc"],"edge-local":["edge-arc","sector","quadrant"],center:["ring","cluster"],donut:["ring"],scratch:["cluster","sector","quadrant"],"near-full":["ring"]},G=new Set(U[u.pattern]??[]),A=u.pattern,ze=t.ringCount,Z=[];for(const C of y){if(!G.has(C.comparison.family)||C.comparison.family==="ring"&&!Me(C))continue;Z.push(C.id);const L=C.comparison.family==="ring"||C.comparison.family==="quadrant"||C.comparison.family==="sector";z!=="info"&&L&&(C.severity="info")}y.push({id:`spatial-pattern:${u.pattern}`,level:"wafer",severity:z,variable:{kind:"spatialPattern",label:B},comparison:{family:"spatial-pattern",left:B,right:"Wafer"},effect:{direction:"different",effectSize:F.globalRdd},stats:{method:"geometry",sampleSizeLeft:P.length,sampleSizeRight:i.length},summary:`Spatial pattern: ${B.toLowerCase()} (${u.confidence} confidence) \u2014 ${E}${u.note?` [${u.note}]`:""}`,highlight:{kind:"dies",dieKeys:P.map(C=>x(C))},relatedIds:Z.length>0?Z:void 0})}}y.sort((u,M)=>{const B=u.stats.adjustedPValue??u.stats.pValue??1,z=M.stats.adjustedPValue??M.stats.pValue??1;return B!==z?B-z:Math.abs(M.effect.absoluteDelta??0)-Math.abs(u.effect.absoluteDelta??0)});const w=qe(e.dies,i.length,e.yield.yieldPercent);c&&(w.isLotStack=!0,l&&(w.aggregationMethod=l));const v=Oe(e.dies,e.testDefs);v&&(w.testSpecYield=v);const R=computeFunctionalYield(e.dies,e.testDefs);if(R&&(w.functionalYield=R),$===void 0&&t.computePerTestStats){const u=ke(i,t.testNumbers,e.testDefs);u.warning&&b.push(u.warning),$=u.testNumbers}if($?.length){const u=Ie(e.dies,$,e.testDefs,t.minimumSampleSize);u&&(w.perTestStats=u)}return b.length>0&&(w.warnings=b),st(y,i,t.passBins),{level:"wafer",hasNotableFindings:y.some(u=>u.severity==="notable"||u.severity==="unusual"),findings:y,wafer:e.wafer.metadata??void 0,stats:w}}
|
|
@@ -46,4 +46,19 @@ export declare function buildBinClusterData(groups: {
|
|
|
46
46
|
key: string;
|
|
47
47
|
items: BinItem[];
|
|
48
48
|
}[], binType: BinType): BinClusterData;
|
|
49
|
+
/**
|
|
50
|
+
* Display order for a bin breakdown: pass bins first (in bin order), then failing
|
|
51
|
+
* bins by descending count, ties broken by bin number.
|
|
52
|
+
*
|
|
53
|
+
* The single source of this ordering. It previously existed as three separate
|
|
54
|
+
* ascending-bin-number sorts — the Summary panel's bin section and the summary
|
|
55
|
+
* report's two bin tables (per-wafer and lot) — none of which agreed with this
|
|
56
|
+
* module's own `buildBinParetoData`, which has always been count-descending. The
|
|
57
|
+
* effect was that the same wafer's bins came out in one order in the Insights bin
|
|
58
|
+
* chart and a different one in the panel and the report.
|
|
59
|
+
*
|
|
60
|
+
* Pass bins are pinned to the top rather than competing on count: they are the
|
|
61
|
+
* reference the failures are read against, not themselves a failure mode.
|
|
62
|
+
*/
|
|
63
|
+
export declare function sortBinsForDisplay(entries: Iterable<[bin: number, count: number]>, passBins?: number[]): Array<[bin: number, count: number]>;
|
|
49
64
|
//# sourceMappingURL=binPareto.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{isYieldEligibleDie as
|
|
1
|
+
import{isYieldEligibleDie as p}from"../core/dies.js";export function buildBinParetoData(l,u){const s=new Map;let n=0;for(const o of l){const i=u==="hbin"?o.hardBinCounts:o.softBinCounts;if(i){for(const[a,t]of Object.entries(i)){const e=Number(a);n+=t,s.set(e,(s.get(e)??0)+t)}continue}for(const a of o.dies??[]){if(!p(a))continue;const t=u==="hbin"?a.hbin:a.sbin;t!==void 0&&(n++,s.set(t,(s.get(t)??0)+1))}}const c=Array.from(s.entries()).map(([o,i])=>({label:`Bin ${o}`,value:i,percent:n>0?i/n*100:0,itemCount:1,binCode:o}));return c.sort((o,i)=>i.value-o.value),c}export function buildBinClusterData(l,u){const s=l.map(t=>t.key),n=new Map,c=[];function o(t,e,f){let r=n.get(t);r||(r={total:0,counts:[]},n.set(t,r),c.push(t)),r.total+=f,r.counts[e]=(r.counts[e]??0)+f}l.forEach((t,e)=>{for(const f of t.items){const r=u==="hbin"?f.hardBinCounts:f.softBinCounts;if(r){for(const[d,b]of Object.entries(r))o(Number(d),e,b);continue}for(const d of f.dies??[]){if(!p(d))continue;const b=u==="hbin"?d.hbin:d.sbin;b!==void 0&&o(b,e,1)}}});const a=c.slice().sort((t,e)=>n.get(e).total-n.get(t).total).map(t=>{const e=n.get(t);return{binCode:t,label:`Bin ${t}`,total:e.total,counts:s.map((f,r)=>e.counts[r]??0)}});return{groups:s,bins:a}}export function sortBinsForDisplay(l,u=[1]){const s=new Set(u);return[...l].sort((n,c)=>{const o=s.has(n[0]),i=s.has(c[0]);return o!==i?o?-1:1:o&&i?n[0]-c[0]:c[1]-n[1]||n[0]-c[0]})}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{normalCdf as
|
|
1
|
+
import{normalCdf as ct}from"./math.js";import{getDieKey as q}from"../core/dies.js";import{findConnectedComponents as at}from"./connectedComponents.js";const rt=["E","ENE","NE","NNE","N","NNW","NW","WNW","W","WSW","SW","SSW","S","SSE","SE","ESE"];function ft(n,s){const i=(Math.atan2(s,n)+2*Math.PI)%(2*Math.PI);return rt[Math.round(i/(2*Math.PI)*16)%16]}function lt(n,s,i,r){const f=r!==void 0?Math.abs(r):0;return n<=.01&&(s>=.25||f>=2||i>=.1)?"unusual":n<=.05&&(s>=.15||f>=1||i>=.03)?"notable":"info"}function ut(n,s,i){if(s===0||i<=0)return n>0?0:1;if(i>=1)return n<=s?0:1;const r=s*i,f=Math.sqrt(s*i*(1-i));if(f===0)return n>r?0:1;const S=(n-.5-r)/f;return 1-ct(S)}export function buildClusterFindings(n,s,i){const{passBins:r,significanceLevel:f,minimumEffectSize:S,minimumRelativeEffect:O,minimumSampleSize:W,minimumClusterSize:T,ringCount:_}=i,G=new Set(r);if(n.length<W)return[];const H=e=>{const a=e.hbin??e.sbin;return a===void 0||!G.has(a)},J=i.isFailingDie??H,X=n.filter(J),u=X.length/n.length,m=n[0],I=m?m.width:10,w=m?m.height:10,M=Math.max(I,w)*1.5,b=new Map;for(const e of n)b.set(q(e),e);const B=Math.ceil(M/I),R=Math.ceil(M/w),Q=at(X),A=[],$=s.center.x,x=s.center.y,U=s.radius;for(const e of Q){if(e.length<T)continue;const a=new Set(e.map(t=>q(t))),h=new Set(a);for(const t of e)for(let c=-R;c<=R;c++)for(let z=-B;z<=B;z++){const D=`${t.x+z},${t.y+c}`;if(h.has(D))continue;const N=b.get(D);N&&Math.hypot(N.physX-t.physX,N.physY-t.physY)<=M&&h.add(D)}const p=[...h].map(t=>b.get(t)).filter(Boolean),o=e.length,E=p.length;if(E<W)continue;const C=o/E,d=C-u,y=ut(o,E,u),g=u>0?d/u:void 0,Z=d>=S||g!==void 0&&g>=O;if(y>f||!Z)continue;const K=e.reduce((t,c)=>t+c.physX,0)/o,V=e.reduce((t,c)=>t+c.physY,0)/o;let l=e[0],k=1/0;for(const t of e){const c=Math.hypot(t.physX-K,t.physY-V);c<k&&(k=c,l=t)}const F=e.map(t=>(Math.atan2(t.physY-x,t.physX-$)+2*Math.PI)%(2*Math.PI));let P=F[0],Y=F[0];for(const t of F)P=Math.min(P,t),Y=Math.max(Y,t);const tt=(Y-P)*180/Math.PI,et=1-1/_,v=e.filter(t=>Math.hypot(t.physX-$,t.physY-x)/U>et).length>o/2&&tt<120,L=v?"edge-arc":"cluster",j=ft(K-$,V-x),nt=v?`Edge arc ~${j}`:`Cluster at (${l.x}, ${l.y})`,st=o/n.length,it=lt(y,d,st,g),ot=[...a];A.push({id:`${L}:${l.x},${l.y}`,level:"wafer",severity:it,variable:{kind:"yield",label:"Yield"},comparison:{family:L,left:nt,right:"Rest of wafer"},effect:{direction:"lower",absoluteDelta:-d,relativeDelta:g!==void 0?-g:void 0,effectSize:d},stats:{method:"binomial",pValue:y,adjustedPValue:y,sampleSizeLeft:o,sampleSizeRight:n.length-o},summary:v?`Edge arc near ${j}: ${o} contiguous failing dies (${(C*100).toFixed(0)}% vs ${(u*100).toFixed(0)}% background)`:`Failure cluster at (${l.x}, ${l.y}): ${o} contiguous failing dies (${(C*100).toFixed(0)}% vs ${(u*100).toFixed(0)}% background)`,highlight:{kind:"dies",dieKeys:ot}})}return A.sort((e,a)=>{const h=e.stats.pValue??1,p=a.stats.pValue??1;return h!==p?h-p:Math.abs(a.effect.effectSize??0)-Math.abs(e.effect.effectSize??0)})}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { PositionedDie } from '../core/dies.js';
|
|
2
|
+
/**
|
|
3
|
+
* Group `failing` into 8-connected components on the integer die grid.
|
|
4
|
+
*
|
|
5
|
+
* Adjacency is `|dx| <= 1 && |dy| <= 1` on `die.x`/`die.y` — the ORIGINAL
|
|
6
|
+
* prober coordinates, per this library's rule that those are the only
|
|
7
|
+
* coordinates any consumer sees. Dies not in `failing` are never traversed, so
|
|
8
|
+
* a component is a contiguous run of failures and nothing else.
|
|
9
|
+
*
|
|
10
|
+
* Iterative, not recursive: a large contiguous failure region is exactly the
|
|
11
|
+
* case this is for, and it is also exactly the case that blows a call stack.
|
|
12
|
+
*/
|
|
13
|
+
export declare function findConnectedComponents(failing: readonly PositionedDie[]): PositionedDie[][];
|
|
14
|
+
//# sourceMappingURL=connectedComponents.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{getDieKey as y}from"../core/dies.js";export function findConnectedComponents(s){if(s.length===0)return[];const r=new Map;for(const e of s)r.set(y(e),e);const n=new Set,u=[];for(const e of s){const f=y(e);if(n.has(f))continue;const p=[],c=[e];for(n.add(f);c.length>0;){const d=c.pop();p.push(d);for(let t=-1;t<=1;t++)for(let o=-1;o<=1;o++){if(o===0&&t===0)continue;const i=`${d.x+o},${d.y+t}`;if(n.has(i))continue;const h=r.get(i);h&&(n.add(i),c.push(h))}}u.push(p)}return u}
|
|
@@ -10,7 +10,33 @@ export interface CorrelationCell {
|
|
|
10
10
|
xIndex: number;
|
|
11
11
|
yIndex: number;
|
|
12
12
|
r: number | null;
|
|
13
|
+
/**
|
|
14
|
+
* Dies contributing to this pair — i.e. those carrying a finite value for BOTH
|
|
15
|
+
* tests, which is not the same as the population size when tests have different
|
|
16
|
+
* coverage. An `r` without its `n` is not interpretable: |r| = 0.8 over 6 dies
|
|
17
|
+
* and over 6,000 are very different claims, and the panel's "strong pair" count
|
|
18
|
+
* thresholds on |r| alone.
|
|
19
|
+
*/
|
|
20
|
+
n: number;
|
|
13
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Pearson r from running sums. The single implementation of the formula —
|
|
24
|
+
* `buildCorrelationMatrix`'s per-pair accumulators and `pearsonOfPairs` (used by
|
|
25
|
+
* the scatter panel to report r for the pair it is displaying) both go through
|
|
26
|
+
* here, so the matrix cell and the scatter card can never disagree about the
|
|
27
|
+
* same pair. Returns null below 3 points or with zero variance in either axis.
|
|
28
|
+
*/
|
|
29
|
+
export declare function pearsonFromSums(c: number, sumX: number, sumY: number, sumXX: number, sumYY: number, sumXY: number): number | null;
|
|
30
|
+
/** Pearson r and n for an explicit list of XY pairs — the scatter panel's own
|
|
31
|
+
* displayed points, after any legend filtering. Shares `pearsonFromSums` with
|
|
32
|
+
* the matrix. */
|
|
33
|
+
export declare function pearsonOfPairs(pairs: ArrayLike<{
|
|
34
|
+
x: number;
|
|
35
|
+
y: number;
|
|
36
|
+
}>): {
|
|
37
|
+
r: number | null;
|
|
38
|
+
n: number;
|
|
39
|
+
};
|
|
14
40
|
export interface CorrelationMatrix {
|
|
15
41
|
tests: CorrelationTestInfo[];
|
|
16
42
|
cells: CorrelationCell[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{isParametricTest as A}from"../renderer/buildWaferMap.js";function
|
|
1
|
+
import{isParametricTest as A}from"../renderer/buildWaferMap.js";export function pearsonFromSums(o,l,u,i,y,f){if(o<3)return null;const d=l/o,c=u/o,r=f/o-d*c,m=i/o-d*d,p=y/o-c*c,h=Math.sqrt(m*p);return h===0?null:Math.max(-1,Math.min(1,r/h))}export function pearsonOfPairs(o){let l=0,u=0,i=0,y=0,f=0,d=0;for(let c=0;c<o.length;c++){const{x:r,y:m}=o[c];!Number.isFinite(r)||!Number.isFinite(m)||(l++,u+=r,i+=m,y+=r*r,f+=m*m,d+=r*m)}return{r:pearsonFromSums(l,u,i,y,f,d),n:l}}function N(o){return o.filter(l=>l.testNumber!==void 0&&A(l)).map(l=>({testNumber:l.testNumber,label:l.name,unit:l.unit}))}export function buildCorrelationMatrix(o,l){const u=N(l);if(u.length<2)return{tests:u,cells:[]};const i=u.length,y=u.map(t=>t.testNumber),f=i*(i-1)/2,d=new Float64Array(f),c=new Float64Array(i),r=new Float64Array(f),m=new Float64Array(f),p=new Float64Array(f),h=new Float64Array(f),g=new Float64Array(f);function w(t,n){return t*i-(t*(t+1)>>1)+(n-t-1)}for(const t of o){if(!t.testValues)continue;const n=new Float64Array(i),x=new Uint8Array(i);for(let s=0;s<i;s++){const a=t.testValues[y[s]];a!==void 0&&Number.isFinite(a)&&(n[s]=a,x[s]=1)}for(let s=0;s<i;s++){if(!x[s])continue;c[s]++;const a=n[s];for(let b=s+1;b<i;b++){if(!x[b])continue;const M=n[b],I=w(s,b);d[I]++,r[I]+=a,m[I]+=M,p[I]+=a*a,h[I]+=M*M,g[I]+=a*M}}}const F=t=>pearsonFromSums(d[t],r[t],m[t],p[t],h[t],g[t]),e=[];for(let t=0;t<i;t++)for(let n=0;n<i;n++){if(n===t){e.push({xIndex:n,yIndex:t,r:1,n:c[n]});continue}const x=Math.min(n,t),s=Math.max(n,t),a=w(x,s);e.push({xIndex:n,yIndex:t,r:F(a),n:d[a]})}return{tests:u,cells:e}}export function filterCorrelationMatrix(o,{threshold:l=.3,minTests:u=6,maxTests:i=20}={}){const y=[];let f=0,d=0,c=null;for(const e of o.cells){if(e.xIndex>=e.yIndex||e.r===null)continue;const t=Math.abs(e.r);y.push({xi:e.xIndex,yi:e.yIndex,absR:t}),(c===null||t>Math.abs(c.r))&&(c={xLabel:o.tests[e.xIndex].label,yLabel:o.tests[e.yIndex].label,r:e.r})}y.sort((e,t)=>t.absR-e.absR);const r=new Set;for(const{xi:e,yi:t,absR:n}of y){const x=n<l;r.size>=i||x&&r.size>=u||(r.add(e),r.size<i&&r.add(t))}if(r.size<Math.min(u,o.tests.length))for(let e=0;e<o.tests.length&&r.size<Math.min(i,u,o.tests.length);e++)r.add(e);const m=(()=>{const e=Array.from(r),t=new Map,n=new Map;for(const{xi:s,yi:a,absR:b}of y)!r.has(s)||!r.has(a)||(t.set(s,(t.get(s)??0)+b),t.set(a,(t.get(a)??0)+b),n.set(s,(n.get(s)??0)+1),n.set(a,(n.get(a)??0)+1));const x=s=>(n.get(s)??0)>0?t.get(s)/n.get(s):0;return e.sort((s,a)=>x(a)-x(s))})(),p=m.map(e=>o.tests[e]),h=new Map(m.map((e,t)=>[e,t])),g=new Set(p.map(e=>e.testNumber)),w=o.cells.filter(e=>g.has(o.tests[e.xIndex].testNumber)&&g.has(o.tests[e.yIndex].testNumber)).map(e=>({xIndex:h.get(e.xIndex),yIndex:h.get(e.yIndex),r:e.r,n:e.n}));let F=0;for(const{xi:e,yi:t,absR:n}of y)r.has(e)&&r.has(t)?n>=.7?f++:n>=.4&&d++:n<l&&F++;return{matrix:{tests:p,cells:w},strongPairs:f,moderatePairs:d,hiddenWeakPairs:F,strongestPair:c}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{metadataDisplayValue as b}from"../core/metadata.js";import{compareNatural as C}from"../core/utils.js";export const FACET_NONE_VALUE="(none)";export{prettyKey}from"../core/utils.js";export const DEFAULT_FACET_CURATION={split:{label:"Split"},lot:{label:"Lot"},product:{label:"Product"},testProgram:{label:"Program"},temperature:{label:"Temperature"},testDate:{label:"Test date",date:!0},operator:{label:"Operator"},waferId:{label:"Wafer",facet:!1}};function
|
|
1
|
+
import{metadataDisplayValue as b}from"../core/metadata.js";import{compareNatural as y,prettyKey as C}from"../core/utils.js";export const FACET_NONE_VALUE="(none)";export{prettyKey}from"../core/utils.js";export const DEFAULT_FACET_CURATION={split:{label:"Split"},lot:{label:"Lot"},product:{label:"Product"},testProgram:{label:"Program"},temperature:{label:"Temperature"},testDate:{label:"Test date",date:!0},operator:{label:"Operator"},waferId:{label:"Wafer",facet:!1}};function O(e){const t=e.indexOf("T");return t>0?e.slice(0,t):e}export function facetValueOf(e,t,o=DEFAULT_FACET_CURATION){const l=b(e?.[t]);if(l!==void 0)return o[t]?.date?O(l):l}export function commonMetadata(e){if(!e.length)return{};const t=e[0].metadata??{},o={};for(const[l,r]of Object.entries(t))r==null||r===""||e.every(d=>(d.metadata??{})[l]===r)&&(o[l]=r);return o}export function buildFacetTable(e,t={}){const o={...DEFAULT_FACET_CURATION,...t.curation},l=t.facetableOnly??!0,r=new Set;for(const n of e)for(const c of Object.keys(n.metadata??{}))r.add(c);const m=[...Object.keys(o).filter(n=>r.has(n)),...[...r].filter(n=>!(n in o))],p=[];for(const n of m){const c=o[n];if(l&&c&&c.facet===!1)continue;const u=new Map;for(const f of e){const a=facetValueOf(f.metadata,n,o)??FACET_NONE_VALUE,i=u.get(a)??{waferCount:0,dieCount:0};i.waferCount+=1,i.dieCount+=f.dieCount??0,u.set(a,i)}if(u.size===0)continue;const s=Array.from(u,([f,a])=>({value:f,waferCount:a.waferCount,dieCount:a.dieCount}));s.sort((f,a)=>(f.value===FACET_NONE_VALUE?1:0)-(a.value===FACET_NONE_VALUE?1:0)||a.waferCount-f.waferCount||y(f.value,a.value)),p.push({key:n,label:c?.label??C(n),values:s,splittable:s.length>1})}return p}
|
|
@@ -6,4 +6,22 @@ export interface FindingsFilter {
|
|
|
6
6
|
level?: StatsLevel | StatsLevel[];
|
|
7
7
|
}
|
|
8
8
|
export declare function filterFindings(source: StatsSummary | LotStatsSummary, filter: FindingsFilter): StatsFinding[];
|
|
9
|
+
/**
|
|
10
|
+
* Drop findings another finding has claimed as an exact restatement of itself —
|
|
11
|
+
* a soft-bin twin covering the same dies, or the single pass bin's row against
|
|
12
|
+
* the yield row that says the same thing.
|
|
13
|
+
*
|
|
14
|
+
* The claimer's own label names what it absorbed ("hard bin and soft bin 3 (same
|
|
15
|
+
* dies)"), so listing both prints one fact twice: once merged, once not. The full
|
|
16
|
+
* uncollapsed list stays on `summary.findings` for any host that wants it.
|
|
17
|
+
*
|
|
18
|
+
* Extracted because this rule existed in three places and one of them was wrong:
|
|
19
|
+
* the Summary panel and the findings report both applied it, while the summary
|
|
20
|
+
* report printed every finding — so a wafer with 8 merged twins listed 16 rows,
|
|
21
|
+
* each duplicate contradicting the merge the label had just described.
|
|
22
|
+
*/
|
|
23
|
+
export declare function visibleFindings<T extends {
|
|
24
|
+
id: string;
|
|
25
|
+
absorbedIds?: string[];
|
|
26
|
+
}>(findings: T[]): T[];
|
|
9
27
|
//# sourceMappingURL=filterFindings.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
function
|
|
1
|
+
function s(i){return Array.isArray(i)?i:[i]}export function filterFindings(i,n){let e=i.findings;if(n.severity!==void 0){const t=new Set(s(n.severity));e=e.filter(d=>t.has(d.severity))}if(n.kind!==void 0){const t=new Set(s(n.kind));e=e.filter(d=>t.has(d.variable.kind))}if(n.family!==void 0){const t=new Set(s(n.family));e=e.filter(d=>t.has(d.comparison.family))}if(n.level!==void 0){const t=new Set(s(n.level));e=e.filter(d=>t.has(d.level))}return e}export function visibleFindings(i){const n=new Set(i.flatMap(e=>e.absorbedIds??[]));return i.filter(e=>!n.has(e.id))}
|