@wafertools/wafermap 0.31.0 → 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +13 -13
- package/CHANGELOG.md +334 -1
- package/README.md +3 -3
- package/dist/packages/canvas-adapter/charts/chartShell.d.ts +2 -12
- package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
- package/dist/packages/canvas-adapter/dieList.js +7 -7
- package/dist/packages/canvas-adapter/drilldown.js +1 -1
- package/dist/packages/canvas-adapter/insightsTab.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferMap.d.ts +47 -4
- package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
- package/dist/packages/canvas-adapter/summaryPanel.d.ts +22 -3
- package/dist/packages/canvas-adapter/summaryPanel.js +4 -3
- package/dist/packages/canvas-adapter/toCanvas.d.ts +23 -2
- package/dist/packages/canvas-adapter/toCanvas.js +1 -1
- package/dist/packages/canvas-adapter/toolbar.d.ts +54 -1
- 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 +100 -27
- package/dist/packages/canvas-adapter/version.d.ts +2 -2
- package/dist/packages/canvas-adapter/version.js +1 -1
- package/dist/packages/core/aggregates.d.ts +6 -0
- package/dist/packages/core/aggregates.js +1 -1
- package/dist/packages/core/compact.d.ts +205 -0
- package/dist/packages/core/compact.js +3 -0
- package/dist/packages/core/dieTable.d.ts +137 -0
- package/dist/packages/core/dieTable.js +1 -0
- package/dist/packages/core/dies.d.ts +22 -0
- package/dist/packages/core/dies.js +1 -1
- package/dist/packages/core/passBins.d.ts +6 -0
- package/dist/packages/core/passBins.js +1 -1
- package/dist/packages/core/utils.d.ts +14 -0
- package/dist/packages/core/utils.js +1 -1
- package/dist/packages/renderer/axisTicks.d.ts +14 -0
- package/dist/packages/renderer/axisTicks.js +1 -1
- package/dist/packages/renderer/buildView.d.ts +29 -4
- package/dist/packages/renderer/buildView.js +1 -1
- package/dist/packages/renderer/buildWaferMap.d.ts +29 -11
- package/dist/packages/renderer/buildWaferMap.js +1 -1
- package/dist/packages/renderer/columnarInput.d.ts +87 -0
- package/dist/packages/renderer/columnarInput.js +1 -0
- package/dist/packages/renderer/compactView.d.ts +22 -0
- package/dist/packages/renderer/compactView.js +1 -0
- package/dist/packages/renderer/derivedTests/apply.d.ts +8 -6
- package/dist/packages/renderer/derivedTests/apply.js +1 -1
- package/dist/packages/renderer/index.d.ts +1 -0
- package/dist/packages/renderer/spec.d.ts +3 -7
- package/dist/packages/renderer/spec.js +1 -1
- package/dist/packages/renderer/xyIndicator.d.ts +27 -0
- package/dist/packages/renderer/xyIndicator.js +1 -0
- package/dist/packages/stats/analyzeWaferLot.d.ts +28 -0
- package/dist/packages/stats/analyzeWaferLot.js +1 -1
- package/dist/packages/stats/analyzeWaferMap.d.ts +169 -2
- package/dist/packages/stats/analyzeWaferMap.js +1 -1
- package/dist/packages/stats/binRows.d.ts +38 -0
- package/dist/packages/stats/binRows.js +1 -0
- package/dist/packages/stats/boxplot.js +1 -1
- package/dist/packages/stats/capability.d.ts +7 -7
- package/dist/packages/stats/capability.js +1 -1
- package/dist/packages/stats/clusterDetection.js +1 -1
- package/dist/packages/stats/connectedComponents.js +1 -1
- package/dist/packages/stats/correlation.js +1 -1
- package/dist/packages/stats/filterFindings.d.ts +30 -0
- package/dist/packages/stats/filterFindings.js +1 -1
- package/dist/packages/stats/findingText.d.ts +9 -0
- package/dist/packages/stats/findingText.js +2 -0
- package/dist/packages/stats/histogram.js +1 -1
- package/dist/packages/stats/lotDrift.d.ts +16 -0
- package/dist/packages/stats/lotDrift.js +1 -0
- package/dist/packages/stats/lotPattern.d.ts +33 -0
- package/dist/packages/stats/lotPattern.js +1 -0
- package/dist/packages/stats/math.d.ts +73 -6
- package/dist/packages/stats/math.js +1 -1
- package/dist/packages/stats/normalizeInput.js +1 -1
- package/dist/packages/stats/patternClassification.d.ts +34 -3
- package/dist/packages/stats/patternClassification.js +1 -1
- package/dist/packages/stats/presentation.d.ts +42 -0
- package/dist/packages/stats/presentation.js +1 -0
- package/dist/packages/stats/regions.d.ts +16 -0
- package/dist/packages/stats/regions.js +1 -1
- package/dist/packages/stats/renderSummaryReport.d.ts +15 -2
- package/dist/packages/stats/renderSummaryReport.js +32 -27
- package/dist/packages/stats/reportHtml.d.ts +54 -9
- package/dist/packages/stats/reportHtml.js +130 -47
- package/dist/packages/stats/scatter.js +1 -1
- package/dist/packages/stats/sweep.js +1 -1
- package/dist/packages/stats/synthesis.d.ts +66 -0
- package/dist/packages/stats/synthesis.js +2 -0
- package/dist/packages/stats/testPassRate.js +1 -1
- package/dist/packages/stats/trend.js +1 -1
- package/dist/packages/stats/types.d.ts +6 -1
- package/dist/packages/stats/yieldRows.d.ts +31 -0
- package/dist/packages/stats/yieldRows.js +1 -0
- package/dist/packages/worker/index.js +1 -1
- package/dist/packages/worker/wafermap.worker.d.ts +3 -0
- package/dist/packages/worker/wafermap.worker.js +1 -1
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/dist/packages/stats/findingsNarrative.d.ts +0 -3
- package/dist/packages/stats/findingsNarrative.js +0 -1
|
@@ -1,6 +1,38 @@
|
|
|
1
1
|
import type { Die } from '../core/dies.js';
|
|
2
|
-
import type { TestDef } from '../renderer/buildWaferMap.js';
|
|
3
|
-
import type { AnalyzeWaferMapInput, AnalyzeWaferMapOptions, StatsSummary } from './types.js';
|
|
2
|
+
import type { TestDef, WaferWarning } from '../renderer/buildWaferMap.js';
|
|
3
|
+
import type { AnalyzeWaferMapInput, AnalyzeWaferMapOptions, StatsFinding, StatsSeverity, StatsSummary } from './types.js';
|
|
4
|
+
import { type StatsRegion } from './regions.js';
|
|
5
|
+
import { type PatternLabel } from './patternClassification.js';
|
|
6
|
+
interface EligibleDie extends Die {
|
|
7
|
+
hbin?: number;
|
|
8
|
+
}
|
|
9
|
+
interface RawFinding extends StatsFinding {
|
|
10
|
+
comparison: StatsFinding['comparison'];
|
|
11
|
+
stats: StatsFinding['stats'];
|
|
12
|
+
effect: StatsFinding['effect'];
|
|
13
|
+
}
|
|
14
|
+
export type ResolvedOptions = Required<Omit<AnalyzeWaferMapOptions, 'testNumbers'>> & {
|
|
15
|
+
testNumbers?: number[];
|
|
16
|
+
passBins: number[];
|
|
17
|
+
ringCount: number;
|
|
18
|
+
significanceLevel: number;
|
|
19
|
+
minimumEffectSize: number;
|
|
20
|
+
minimumRelativeEffect: number;
|
|
21
|
+
minimumSampleSize: number;
|
|
22
|
+
minimumClusterSize: number;
|
|
23
|
+
/** Soft bins every die carrying which passes — computed once per analysis. */
|
|
24
|
+
softPassBins?: ReadonlySet<number>;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Clamp the numeric options into ranges that can produce a meaningful analysis,
|
|
28
|
+
* reporting anything corrected. A caller who passes nonsense previously got a
|
|
29
|
+
* confident, wrong answer with no indication anything was amiss; they now get
|
|
30
|
+
* the nearest sane analysis plus a warning saying what was changed.
|
|
31
|
+
*/
|
|
32
|
+
export declare function resolveOptions(options: AnalyzeWaferMapOptions): {
|
|
33
|
+
resolved: ResolvedOptions;
|
|
34
|
+
warnings: WaferWarning[];
|
|
35
|
+
};
|
|
4
36
|
/**
|
|
5
37
|
* Per-test pass rate for functional (`testType: 'F'`) tests — the functional
|
|
6
38
|
* counterpart of `computeTestSpecYield`. Same population convention: partial and
|
|
@@ -9,5 +41,140 @@ import type { AnalyzeWaferMapInput, AnalyzeWaferMapOptions, StatsSummary } from
|
|
|
9
41
|
* functional data is counted identically); dies never tested count as neither.
|
|
10
42
|
*/
|
|
11
43
|
export declare function computeFunctionalYield(dies: Die[], testDefs: TestDef[] | undefined): StatsSummary['stats']['functionalYield'];
|
|
44
|
+
/** The two-proportion z statistic, signed as left − right; 0 when untestable. */
|
|
45
|
+
export declare function twoProportionZ(leftPass: number, leftTotal: number, rightPass: number, rightTotal: number): number;
|
|
46
|
+
/** Two-sided p-value of a z statistic. */
|
|
47
|
+
export declare function zPValue(z: number): number;
|
|
48
|
+
export declare function adjustPValues(findings: RawFinding[]): RawFinding[];
|
|
49
|
+
/**
|
|
50
|
+
* A rate finding's counts: hits and dies in its region and in the rest of the
|
|
51
|
+
* wafer. `passRate` marks a rate of passes (yield, a functional pass rate)
|
|
52
|
+
* rather than of an outcome such as a bin or a limit fail.
|
|
53
|
+
*/
|
|
54
|
+
export interface RateCounts {
|
|
55
|
+
hits: number;
|
|
56
|
+
n: number;
|
|
57
|
+
restHits: number;
|
|
58
|
+
restN: number;
|
|
59
|
+
passRate: boolean;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The size of a rate's relative change, for the effect gate and severity —
|
|
63
|
+
* always measured on the adverse outcome. A pass rate is judged by its failure
|
|
64
|
+
* rate: yield 98% → 94% is failures 2% → 6%, a tripling, and must reach the same
|
|
65
|
+
* verdict as the bin rate made of the same dies; as a relative change in yield
|
|
66
|
+
* (4%) it never could. A rate that is zero in the rest of the wafer and not in
|
|
67
|
+
* the region — a bin found only at the edge — is the largest relative change
|
|
68
|
+
* there is, not none (its published `relativeDelta` has no finite value).
|
|
69
|
+
*/
|
|
70
|
+
export declare function rateRelativeEffect(c: RateCounts): number;
|
|
71
|
+
export declare function severityForFinding(pValue: number, delta: number, relEffect: number): StatsSeverity;
|
|
72
|
+
export declare function severityForScore(pValue: number, score: number): StatsSeverity;
|
|
73
|
+
export type RegionFamily = 'ring' | 'quadrant' | 'reticle-position' | 'test-site' | 'sector';
|
|
74
|
+
/**
|
|
75
|
+
* One regional comparison a wafer's analysis made, reported or not — what
|
|
76
|
+
* `analyzeWaferLot` combines across wafers. Compact on purpose: counts or sums
|
|
77
|
+
* and the region, never the finding's own copy of its die keys.
|
|
78
|
+
*/
|
|
79
|
+
export interface RegionCandidate {
|
|
80
|
+
/** `source|variable|family|region key` — equal for the same comparison on every wafer. */
|
|
81
|
+
key: string;
|
|
82
|
+
source: 'yield' | 'hardBin' | 'softBin' | 'functional' | 'limitFail' | 'test';
|
|
83
|
+
/** The finding's own objects, shared; the wafer's redundancy collapse later
|
|
84
|
+
* relabels a surviving hard/soft twin, so the label is kept as it was here. */
|
|
85
|
+
variable: StatsFinding['variable'];
|
|
86
|
+
label: string;
|
|
87
|
+
comparison: StatsFinding['comparison'];
|
|
88
|
+
region: StatsRegion;
|
|
89
|
+
/** Signed z of the wafer's own test (region − rest). */
|
|
90
|
+
z: number;
|
|
91
|
+
/** Region − rest, in the finding's units; and its standardised size for test values. */
|
|
92
|
+
delta: number;
|
|
93
|
+
effectSize: number;
|
|
94
|
+
relativeDelta: number | undefined;
|
|
95
|
+
rate?: RateCounts;
|
|
96
|
+
mean?: MeanStats;
|
|
97
|
+
}
|
|
98
|
+
/** A wafer analysis's comparisons and the facts its redundancy collapse used. */
|
|
99
|
+
export interface WaferComparisons {
|
|
100
|
+
candidates: RegionCandidate[];
|
|
101
|
+
facts: RedundancyFacts | undefined;
|
|
102
|
+
}
|
|
103
|
+
/** The comparisons behind a summary `analyzeWaferMap` returned in this realm (not
|
|
104
|
+
* after a structured clone, which drops them). */
|
|
105
|
+
export declare function candidatesOf(summary: StatsSummary): WaferComparisons | undefined;
|
|
106
|
+
/**
|
|
107
|
+
* A combined finding's sentence, in the words the wafer finding of the same
|
|
108
|
+
* comparison uses — one wording per kind, whichever level reports it.
|
|
109
|
+
*/
|
|
110
|
+
export declare function describeRegional(c: RegionCandidate, delta: number, relativeDelta: number | undefined): string;
|
|
111
|
+
export declare function passesRateGate(adjustedP: number, delta: number, relEffect: number, options: ResolvedOptions): boolean;
|
|
112
|
+
/**
|
|
113
|
+
* The ids of the `candidates` a spatial pattern explains (`patternExplains`), for
|
|
114
|
+
* its `relatedIds` — the wafer's pattern over the wafer's findings, the lot's
|
|
115
|
+
* over the lot's. `patterns` holds more than one label for a lot row that
|
|
116
|
+
* counts a family together (edge-ring and edge-local wafers).
|
|
117
|
+
*
|
|
118
|
+
* A ring, quadrant or sector row the pattern claims is downgraded to info so it
|
|
119
|
+
* does not count twice towards the badge and `hasNotableFindings`; cluster and
|
|
120
|
+
* edge-arc rows keep their own severity — they carry their own evidence (a
|
|
121
|
+
* p-value, an exact die count) and are supporting detail, not a restatement.
|
|
122
|
+
*/
|
|
123
|
+
export declare function claimForPattern<F extends StatsFinding>(patterns: PatternLabel[], severity: StatsSeverity, candidates: readonly F[], ringsOf: (f: F) => number[], ringCount: number): string[];
|
|
124
|
+
/** A test-value finding's per-region sums, in the builder's shifted space. */
|
|
125
|
+
export interface MeanStats {
|
|
126
|
+
n: number;
|
|
127
|
+
sum: number;
|
|
128
|
+
sq: number;
|
|
129
|
+
restN: number;
|
|
130
|
+
restSum: number;
|
|
131
|
+
restSq: number;
|
|
132
|
+
shift: number;
|
|
133
|
+
}
|
|
134
|
+
/** Welch's test of a region's sums against a rest given as sums (one wafer's shifted space). */
|
|
135
|
+
export declare function welchOfSums(own: MeanStats, restN: number, restSum: number, restSq: number): ReturnType<typeof welchFromStats>;
|
|
136
|
+
/**
|
|
137
|
+
* Welch two-sample comparison from summary statistics (count, mean, variance).
|
|
138
|
+
* The findings pass accumulates these per region in a single columnar scan, so
|
|
139
|
+
* the comparison never materialises the value arrays — see buildTestValueFindings.
|
|
140
|
+
*/
|
|
141
|
+
export declare function welchFromStats(leftN: number, leftMean: number, leftVar: number, rightN: number, rightMean: number, rightVar: number): {
|
|
142
|
+
pValue: number;
|
|
143
|
+
effectSize: number;
|
|
144
|
+
delta: number;
|
|
145
|
+
z: number;
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* A connected set of quadrants in reading order round the wafer, starting after
|
|
149
|
+
* a gap when there is one ("NW, SW & SE", not whatever order the search met
|
|
150
|
+
* them in). All four quadrants have no gap and start at the north-east.
|
|
151
|
+
*/
|
|
152
|
+
export declare function inQuadrantOrder(quadrants: string[]): string[];
|
|
153
|
+
/**
|
|
154
|
+
* Partition a same-signal group into maximal runs of spatially adjacent regions.
|
|
155
|
+
* Singletons (no adjacent same-signal neighbour) come back as length-1 runs.
|
|
156
|
+
*/
|
|
157
|
+
export declare function findContiguousRuns<T>(group: T[], family: string, sectorCount: number, keyOf: (item: T) => string): T[][];
|
|
158
|
+
/** The label of a run of adjacent regions, from their region keys in run order. */
|
|
159
|
+
export declare function mergedRegionLabel(family: string, keys: string[], ringCount: number): string;
|
|
160
|
+
/**
|
|
161
|
+
* The Benjamini–Hochberg multiplier (adjusted p over raw p) of the weakest finding
|
|
162
|
+
* in a merged run. A merged finding is graded on its own p-value times this, so a
|
|
163
|
+
* run is judged no more leniently than its parts. Wafer and lot merges both use it.
|
|
164
|
+
*/
|
|
165
|
+
export declare function weakestMultiplier(run: readonly RawFinding[]): number;
|
|
166
|
+
/** What {@link collapseRedundantFindings} needs to know about a wafer's dies. */
|
|
167
|
+
export interface RedundancyFacts {
|
|
168
|
+
/** `"hard:soft"` bin pairs carried by exactly the same dies. */
|
|
169
|
+
coincident: Set<string>;
|
|
170
|
+
/** The soft bin whose rate is the yield, if one is. */
|
|
171
|
+
yieldSoftBin: number | undefined;
|
|
172
|
+
passBins: number[];
|
|
173
|
+
/** How the sectors divide the wafer, for comparing a sector run with a quadrant. */
|
|
174
|
+
sectorCount: number;
|
|
175
|
+
}
|
|
176
|
+
export declare function redundancyFacts(eligibleDies: EligibleDie[], passBins: number[], sectorCount: number): RedundancyFacts;
|
|
177
|
+
export declare function collapseRedundantFindings(findings: RawFinding[], facts: RedundancyFacts): void;
|
|
12
178
|
export declare function analyzeWaferMap(input: AnalyzeWaferMapInput, options?: AnalyzeWaferMapOptions): StatsSummary;
|
|
179
|
+
export {};
|
|
13
180
|
//# sourceMappingURL=analyzeWaferMap.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{normalizeInput as Ke}from"./normalizeInput.js";import{computeCapability as Te,computeTestFlagYield as xe,computeRegionYield as je}from"./summaryFigures.js";import{isYieldEligibleDie as ae,getDieKey as T,isPositionedDie as ye}from"../core/dies.js";import{getTestPassStatus as ee,isParametricTest as H}from"../renderer/buildWaferMap.js";import{markedTestLabel as x,derivedFields as te}from"../renderer/testLabel.js";import{buildQuadrantRegions as Ae,buildReticlePositionRegions as Oe,buildRingRegions as qe,buildSectorRegions as He,buildTestSiteRegions as Ie,sectorCompassNames as Se,areQuadrantsAdjacent as Ye,parseRegionKey as I}from"./regions.js";import{buildClusterFindings as _e}from"./clusterDetection.js";import{classifyPattern as We}from"./patternClassification.js";import{normalCdf as we}from"./math.js";import{quantile as re}from"./math.js";import{mean as ne,clamp01 as $e}from"../core/utils.js";import{classifySpec as le,isOutOfSpec as ve}from"../renderer/spec.js";const Qe={significanceLevel:{min:1e-6,max:1},minimumEffectSize:{min:0,max:1},minimumRelativeEffect:{min:0,max:Number.MAX_SAFE_INTEGER}},De=[4,8,16,32];function Ue(s){const a=Object.fromEntries(Object.entries(s).filter(([,n])=>n!==void 0)),i={...j,...a},o=[],e=[];for(const n of["significanceLevel","minimumEffectSize","minimumRelativeEffect"]){const{min:l,max:t}=Qe[n],u=i[n];if(typeof u!="number"||!Number.isFinite(u)){e.push(`${n}=${String(u)} is not a finite number (using ${j[n]})`),i[n]=j[n];continue}const f=Math.min(t,Math.max(l,u));f!==u&&(e.push(`${n}=${u} is outside ${l}\u2013${t} (using ${f})`),i[n]=f)}if(De.includes(i.sectorCount)||(e.push(`sectorCount=${i.sectorCount} is not one of ${De.join(", ")} (using ${j.sectorCount})`),i.sectorCount=j.sectorCount),e.length){const n=`analyzeWaferMap: ${e.join("; ")}. The analysis ran with the corrected values.`;console.warn(`[wafermap] ${n}`),o.push({code:"analysis-option-corrected",message:n,severity:"warning"})}const c=["passBins","ringCount"].filter(n=>a[n]!==void 0);if(c.length){const n=`analyzeWaferMap: ${c.join(" and ")} ${c.length>1?"are":"is"} no longer an analysis option and was ignored. Set ${c.length>1?"them":"it"} on buildWaferMap; the analysis used the value the map was built with.`;console.warn(`[wafermap] ${n}`),o.push({code:"analysis-option-corrected",message:n,severity:"warning"})}const r=["enableYieldAnalysis","enableHardBinAnalysis","enableSoftBinAnalysis","enableReticlePositionAnalysis","enableTestSiteAnalysis","enableClusterAnalysis","enableAngularAnalysis","enablePatternClassification"].filter(n=>a[n]!==void 0);if(r.length){const n=`analyzeWaferMap: ${r.join(", ")} ${r.length>1?"are":"is"} no longer an option and was ignored: every analysis now runs. Filter the findings (filterFindings) to show fewer.`;console.warn(`[wafermap] ${n}`),o.push({code:"analysis-option-corrected",message:n,severity:"warning"})}return{resolved:i,warnings:o}}const j={ringCount:4,passBins:[1],significanceLevel:.05,minimumEffectSize:.2,minimumRelativeEffect:1,minimumSampleSize:5,enableTestValueAnalysis:!1,computePerTestStats:!1,sectorCount:8,minimumClusterSize:5};function Ge(s,a){const i={...s};return i.minimumClusterSize=Math.max(5,Math.round(a*.003)),i}function Xe(s){return ae(s)?s.hbin!==void 0||s.sbin!==void 0||s.testValues!==void 0&&Object.keys(s.testValues).length>0:!1}function Je(s,a,i){if(!s||a)return;const o=(i??[]).filter(e=>H(e)&&(e.limitLow!==void 0||e.limitHigh!==void 0));if(o.length!==0)return e=>{for(const c of o)if(ve(le(e.testValues?.[c.testNumber],c)))return!0;return!1}}function Ze(s,a,i){const o=new Set,e=new Set,c=new Set,r=new Map,n=new Map;for(const l of s){if(l.testValues)for(const t of Object.keys(l.testValues))o.add(Number(t));if(l.testPass)for(const t of Object.keys(l.testPass))o.add(Number(t));l.hbin!==void 0&&e.add(l.hbin),l.sbin!==void 0&&c.add(l.sbin),ae(l)&&(l.hbin!==void 0&&r.set(l.hbin,(r.get(l.hbin)??0)+1),l.sbin!==void 0&&n.set(l.sbin,(n.get(l.sbin)??0)+1))}return{totalDies:s.length,analyzedDies:a,excludedDies:s.length-a,yieldPercent:i,testsConsidered:[...o].sort((l,t)=>l-t),hardBinsConsidered:[...e].sort((l,t)=>l-t),softBinsConsidered:[...c].sort((l,t)=>l-t),...r.size?{hardBinCounts:Object.fromEntries(r)}:{},...n.size?{softBinCounts:Object.fromEntries(n)}:{}}}function et(s,a){if(!a?.length)return;const i=a.filter(e=>H(e)&&(e.limitLow!==void 0||e.limitHigh!==void 0));if(!i.length)return;const o=[];for(const e of i){const c=e.testNumber;let r=0,n=0,l=0,t=0;for(const u of s){if(u.partial||u.edgeExcluded)continue;const f=le(u.testValues?.[c],e);f!==null&&(t++,f==="failLow"?n++:f==="failHigh"?l++:r++)}o.push({testNumber:c,label:e.name,passDies:r,failLowDies:n,failHighDies:l,totalDies:t,yieldPercent:t>0?r/t*100:null})}return o.length?o:void 0}export function computeFunctionalYield(s,a){if(!a?.length)return;const i=a.filter(e=>!H(e));if(!i.length)return;const o=[];for(const e of i){const c=e.testNumber;let r=0,n=0;for(const t of s){if(t.partial||t.edgeExcluded)continue;const u=ee(t,c,e);u!==void 0&&(u?r++:n++)}const l=r+n;o.push({testNumber:c,label:x(e,c),...te(e),passDies:r,failDies:n,totalDies:l,passRatePercent:l>0?r/l*100:null})}return o.length?o:void 0}function tt(s,a,i,o){const e=[];for(const c of a){const r=[];for(const f of s){if(f.partial||f.edgeExcluded)continue;const m=f.testValues?.[c];m!==void 0&&Number.isFinite(m)&&r.push(m)}if(r.length<o)continue;const n=ne(r),l=Math.sqrt(he(r,n)),t=Float64Array.from(r);t.sort();const u=i?.find(f=>f.testNumber===c)?.name??String(c);e.push({testNumber:c,label:u,count:r.length,min:t[0],max:t[t.length-1],mean:n,stddev:l,median:re(t,.5),q1:re(t,.25),q3:re(t,.75)})}return e.length?e:void 0}function A(s,a,i,o){const e=(s+i)/(a+o),c=e*(1-e)*(1/a+1/o);if(!Number.isFinite(c)||c<=0)return 1;const r=(s/a-i/o)/Math.sqrt(c);return $e(2*(1-we(Math.abs(r))))}function fe(s){const a=new Map;for(const i of s){const o=`${i.variable.kind}:${i.comparison.family}`,e=a.get(o)??[];e.push(i),a.set(o,e)}for(const i of a.values()){const o=[...i].filter(r=>r.stats.pValue!==void 0).sort((r,n)=>(r.stats.pValue??1)-(n.stats.pValue??1)),e=o.length;let c=1;for(let r=e-1;r>=0;r--){const n=o[r],l=n.stats.pValue??1,t=Math.min(1,Math.min(c,l*e/(r+1)));n.stats.adjustedPValue=t,c=t}}return s}function ie(s,a,i){const o=Math.abs(a),e=i!==void 0?Math.abs(i):0;return s<=.01&&(o>=.3||e>=2.5)?"unusual":s<=.05&&(o>=.2||e>=1.5)?"notable":"info"}function ce(s,a){return s<=.01&&Math.abs(a)>=.5?"unusual":s<=.05&&Math.abs(a)>=.15?"notable":"info"}function se(s){return s==="reticle-position"?"other reticle positions":s==="test-site"?"other test sites":"the rest of the map"}function Y(s){return s==="reticle-position"?"Other reticle positions":s==="test-site"?"Other test sites":"Rest of map"}function ke(s,a,i){const o=(Math.abs(a)*100).toFixed(1);return`${s} yield is ${o} percentage points ${a>0?"higher":"lower"} than ${se(i)}`}function ue(s,a){return a==="quadrant"&&!s.startsWith("Quadrant")?`quadrant ${s}`:s}function ze(s){return/^(Rings|Sectors|Quadrants)\b/.test(s)?"have":"has"}function Me(s,a,i,o){const e=ue(s,o),c=(Math.abs(i)*100).toFixed(1);return`${e} ${ze(e)} ${a} occurrence ${c} percentage points ${i>0?"higher":"lower"} than ${se(o)}`}function Re(s,a,i,o){const e=ue(s,o),c=(Math.abs(i)*100).toFixed(1);return`${e} ${ze(e)} ${a} pass rate ${c} percentage points ${i>0?"higher":"lower"} than ${se(o)}`}function Be(s,a,i,o,e,c){const r=ue(s,e),n=se(e),l=i>0?"higher":"lower";if(o!==void 0&&Number.isFinite(o)){const u=(Math.abs(o)*100).toFixed(1);return`${r} mean ${a} is ${u}% ${l} than ${n}`}const t=c?` ${c}`:"";return`${r} mean ${a} is ${Math.abs(i).toPrecision(3)}${t} ${l} than ${n}`}function Fe(s,a,i){const o=a?.find(e=>e.bin===s);return o?.name?`${i} ${s} (${o.name})`:`${i} ${s}`}function nt(s,a){const i=a?.find(o=>o.testNumber===s);return{label:x(i,s),unit:i?.unit,...te(i)}}function de(s,a){const i=new Map(s.map(e=>[T(e),e])),o=new Map;for(const e of a){const c=[];for(const r of e.dieKeys){const n=i.get(r);n&&c.push(n)}o.set(e.key,c)}return o}function ge(s,a){return fe(s),s.filter(i=>{const o=i.stats.adjustedPValue??i.stats.pValue??1,e=Math.abs(i.effect.absoluteDelta??0),c=Math.abs(i.effect.relativeDelta??0);return o<=a.significanceLevel&&(e>=a.minimumEffectSize||c>=a.minimumRelativeEffect)}).map(i=>({...i,severity:ie(i.stats.adjustedPValue??i.stats.pValue??1,i.effect.absoluteDelta??0,i.effect.relativeDelta)}))}function W(s,a,i,o){const e=new Set(i),c=de(s,a),r=new Map,n=new Map;for(const[t,u]of c){let f=0,m=0;for(const g of u)g.hbin!==void 0&&(m++,e.has(g.hbin)&&f++);r.set(t,f),n.set(t,m)}const l=[];for(const t of a){const u=n.get(t.key)??0,f=r.get(t.key)??0;let m=0,g=0;for(const[k,h]of n)k!==t.key&&(m+=h,g+=r.get(k)??0);if(u<o.minimumSampleSize||m<o.minimumSampleSize)continue;const p=f/u,b=g/m,y=p-b,M=A(f,u,g,m);l.push({id:`yield:${t.key}`,level:"wafer",severity:"info",variable:{kind:"yield",label:"Yield"},comparison:{family:t.family,left:t.label,right:Y(t.family)},effect:{direction:y===0?"different":y>0?"higher":"lower",absoluteDelta:y,relativeDelta:b===0?void 0:y/b,effectSize:y},stats:{method:"two-proportion-z",pValue:M,sampleSizeLeft:u,sampleSizeRight:m},summary:ke(t.label,y,t.family),highlight:{kind:"region",regionFamily:t.family,regionKeys:[t.key],dieKeys:[...t.dieKeys]}})}return ge(l,o)}function L(s,a,i,o,e,c){const r=g=>i==="soft"?g.sbin:g.hbin,n=[...new Set(s.map(r).filter(g=>g!==void 0))].sort((g,p)=>g-p),l=de(s,a),t=new Map,u=new Map;for(const[g,p]of l){const b=new Map;for(const y of p){const M=r(y);M!==void 0&&b.set(M,(b.get(M)??0)+1)}t.set(g,b),u.set(g,p.length)}const f=[],m=e==="hardBin"?"HBin":"SBin";for(const g of a){const p=u.get(g.key)??0,b=t.get(g.key);let y=0;const M=new Map;for(const[k,h]of t)if(k!==g.key){y+=u.get(k)??0;for(const[S,v]of h)M.set(S,(M.get(S)??0)+v)}if(!(p<c.minimumSampleSize||y<c.minimumSampleSize))for(const k of n){const h=b.get(k)??0,S=M.get(k)??0,v=h/p,w=S/y,$=v-w,D=A(h,p,S,y),z=Fe(k,o,m);f.push({id:`${e}:${k}:${g.key}`,level:"wafer",severity:"info",variable:{kind:e,bin:k,label:z},comparison:{family:g.family,left:g.label,right:Y(g.family)},effect:{direction:$===0?"different":$>0?"higher":"lower",absoluteDelta:$,relativeDelta:w===0?void 0:$/w,effectSize:$},stats:{method:"two-proportion-z",pValue:D,sampleSizeLeft:p,sampleSizeRight:y},summary:Me(g.label,z,$,g.family),highlight:{kind:"bin",bin:k,regionKeys:[g.key],dieKeys:[...g.dieKeys]}})}}return ge(f,c)}function Q(s,a,i,o){const e=(i??[]).filter(u=>!H(u));if(!e.length)return[];const c=e.map(u=>u.testNumber),r=de(s,a),n=new Map,l=new Map;for(const[u,f]of r){const m=new Uint32Array(e.length),g=new Uint32Array(e.length);for(const p of f)for(let b=0;b<e.length;b++){const y=ee(p,c[b],e[b]);y!==void 0&&(g[b]++,y&&m[b]++)}n.set(u,m),l.set(u,g)}const t=[];for(const u of a){const f=n.get(u.key),m=l.get(u.key);for(let g=0;g<e.length;g++){let p=0,b=0;for(const[$,D]of l)$!==u.key&&(b+=D[g],p+=n.get($)[g]);const y=m[g],M=f[g];if(y<o.minimumSampleSize||b<o.minimumSampleSize)continue;const k=M/y,h=p/b,S=k-h,v=A(M,y,p,b),w=c[g];t.push({id:`functional:${w}:${u.key}`,level:"wafer",severity:"info",variable:{kind:"functionalTest",index:w,label:`${x(e[g],w)} pass rate`,...te(e[g])},comparison:{family:u.family,left:u.label,right:Y(u.family)},effect:{direction:S===0?"different":S>0?"higher":"lower",absoluteDelta:S,relativeDelta:h===0?void 0:S/h,effectSize:S},stats:{method:"two-proportion-z",pValue:v,sampleSizeLeft:y,sampleSizeRight:b},summary:Re(u.label,x(e[g],w),S,u.family),highlight:{kind:"region",regionFamily:u.family,regionKeys:[u.key],dieKeys:[...u.dieKeys]}})}}return ge(t,o)}function he(s,a){return s.length<2?0:s.reduce((i,o)=>i+(o-a)**2,0)/(s.length-1)}function Ne(s,a,i,o,e,c){const r=Math.sqrt(i/s+c/o),n=a-e;if(!Number.isFinite(r)||r===0)return{pValue:1,effectSize:0,delta:n};const l=n/r,t=Math.sqrt(Math.max(0,(i+c)/2)),u=t===0?n:n/t;return{pValue:$e(2*(1-we(Math.abs(l)))),effectSize:u,delta:n}}function it(s,a){const i=ne(s),o=ne(a);return Ne(s.length,i,he(s,i),a.length,o,he(a,o))}const Pe=250;function Ce(s,a,i){const o=r=>r.filter(n=>H(i?.find(l=>l.testNumber===n)));if(a)return{testNumbers:o(a.slice().sort((r,n)=>r-n))};const e=new Set;let c=!1;e:for(const r of s)for(const n of Object.keys(r.testValues??{})){const l=Number(n);if(!e.has(l)&&(e.add(l),e.size>Pe)){c=!0;break e}}if(c){const r=`Test-value analysis was skipped: more than ${Pe} tests were found in the die data, so no test findings were computed. Pass testNumbers: [...] to analyse specific tests.`;return console.warn(`[wafermap] analyzeWaferMap: ${r}`),{testNumbers:[],warning:{code:"test-count-capped",message:r,severity:"warning"}}}return{testNumbers:o([...e].sort((r,n)=>r-n))}}function U(s,a,i,o){const e=Ce(s,o.testNumbers,i);if(e.warning)return{findings:[],warning:e.warning};const c=e.testNumbers,r=new Map;for(let p=0;p<a.length;p++)for(const b of a[p].dieKeys)r.set(b,p);const n=a.length,l=[],t=[];for(const p of s){const b=r.get(T(p));b!==void 0&&(l.push(p),t.push(b))}const u=[],f=new Float64Array(n),m=new Float64Array(n),g=new Float64Array(n);for(const p of c){f.fill(0),m.fill(0),g.fill(0);let b=0,y=0,M=0,k;for(let h=0;h<l.length;h++){const v=l[h].testValues?.[p];if(v===void 0)continue;k===void 0&&(k=v);const w=v-k,$=t[h];f[$]+=1,m[$]+=w,g[$]+=w*w,b+=1,y+=w,M+=w*w}if(k!==void 0)for(let h=0;h<n;h++){const S=f[h],v=b-S;if(S<o.minimumSampleSize||v<o.minimumSampleSize)continue;const w=m[h]/S,D=(y-m[h])/v,z=w+k,N=D+k,B=S>1?Math.max(0,g[h]-S*w*w)/(S-1):0,d=v>1?Math.max(0,M-g[h]-v*D*D)/(v-1):0,{pValue:F,effectSize:R,delta:V}=Ne(S,z,B,v,N,d),P=a[h],{label:q,unit:G,...X}=nt(p,i),J=N!==0?V/Math.abs(N):void 0;u.push({id:`test:${p}:${P.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:p,label:q,unit:G,...X},comparison:{family:P.family,left:P.label,right:Y(P.family)},effect:{direction:V===0?"different":V>0?"higher":"lower",absoluteDelta:V,relativeDelta:J,effectSize:R},stats:{method:"welch-z-approx",pValue:F,sampleSizeLeft:S,sampleSizeRight:v},summary:Be(P.label,q,V,J,P.family,G),highlight:{kind:"region",regionFamily:P.family,regionKeys:[P.key],dieKeys:[...P.dieKeys]}})}}return fe(u),{activeTestNumbers:c,findings:u.filter(p=>{const b=p.stats.adjustedPValue??p.stats.pValue??1,y=Math.abs(p.effect.effectSize??0);return b<=o.significanceLevel&&y>=o.minimumEffectSize}).map(p=>({...p,severity:ce(p.stats.adjustedPValue??p.stats.pValue??1,p.effect.effectSize??0)}))}}function st(s,a,i,o){if(!i?.length)return[];const e=i.filter(n=>H(n)&&(n.limitLow!==void 0||n.limitHigh!==void 0));if(!e.length)return[];const c=[],r=new Map(s.map(n=>[T(n),n]));for(const n of e){const l=n.testNumber;for(const t of a){const u=new Map;for(const m of t){const g=[];for(const p of m.dieKeys){const b=r.get(p);b&&g.push(b)}u.set(m.key,g)}const f=[];for(const m of t){const g=u.get(m.key),p=[];for(const[z,N]of u)if(z!==m.key)for(const B of N)p.push(B);const b=z=>z.testValues?.[l]!==void 0,y=z=>ve(le(z.testValues?.[l],n)),M=g.filter(b),k=p.filter(b);if(M.length<o.minimumSampleSize||k.length<o.minimumSampleSize)continue;const h=M.filter(y).length,S=k.filter(y).length,v=h/M.length,w=S/k.length,$=v-w,D=A(h,M.length,S,k.length);f.push({id:`specLimit:${l}:${m.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:l,label:x(n,l),unit:n.unit,...te(n)},comparison:{family:m.family,left:m.label,right:Y(m.family)},effect:{direction:$===0?"different":$>0?"higher":"lower",absoluteDelta:$,relativeDelta:w===0?void 0:$/w,effectSize:$},stats:{method:"two-proportion-z",pValue:D,sampleSizeLeft:M.length,sampleSizeRight:k.length},summary:`${m.label} limit fail rate for ${x(n,l)} is ${(Math.abs($)*100).toFixed(1)} pp ${$>0?"higher":"lower"} than the rest of the wafer`,highlight:{kind:"region",regionFamily:m.family,regionKeys:[m.key],dieKeys:[...m.dieKeys]}})}fe(f),c.push(...f.filter(m=>{const g=m.stats.adjustedPValue??m.stats.pValue??1,p=Math.abs(m.effect.absoluteDelta??0),b=Math.abs(m.effect.relativeDelta??0);return g<=o.significanceLevel&&(p>=o.minimumEffectSize||b>=o.minimumRelativeEffect)}).map(m=>({...m,severity:ce(m.stats.adjustedPValue??m.stats.pValue??1,m.effect.effectSize??0)})))}}return c}function O(s){return s.highlight.regionKeys??[]}function ot(s){const a=s.variable.bin??s.variable.index??"";return`${s.comparison.family}\0${s.variable.kind}\0${a}\0${s.effect.direction}`}function _(s,a){const i=O(s)[0]??"",o=I(i);return o.family==="ring"?o.ring??0:o.family==="sector"?Se(a).indexOf(o.sector??""):0}function at(s,a,i){if(a==="quadrant"){const r=new Map;for(const u of s){const f=I(O(u)[0]??"").quadrant;f&&r.set(f,u)}const n=[...r.keys()],l=new Set,t=[];for(const u of n){if(l.has(u))continue;const f=[],m=[u];for(l.add(u);m.length;){const g=m.pop();f.push(g);for(const p of n)!l.has(p)&&Ye(g,p)&&(l.add(p),m.push(p))}t.push(f.map(g=>r.get(g)))}return t}const o=[...s].sort((r,n)=>_(r,i)-_(n,i)),e=[];let c=[];for(const r of o){if(c.length===0){c.push(r);continue}const n=_(c[c.length-1],i);_(r,i)-n===1?c.push(r):(e.push(c),c=[r])}if(c.length&&e.push(c),a==="sector"&&e.length>1){const n=Se(i).length,l=_(e[0][0],i),t=_(e[e.length-1][e[e.length-1].length-1],i);if(l===0&&t===n-1){const u=e.pop();e[0]=[...u,...e[0]]}}return e}function rt(s,a){const i=s.map(r=>I(O(r)[0]??"").ring).filter(r=>r!==void 0).sort((r,n)=>r-n),o=i[0],e=i[i.length-1];let c="";return o===e&&o===1&&a>1?c=" (core)":o===e&&o===a&&a>1&&(c=" (edge)"),o===e?`Ring ${o}${c}`:`Rings ${o}\u2013${e}`}function lt(s,a){const i=s.map(o=>I(O(o)[0]??"").sector??"");return i.length===1?`Sector ${i[0]}`:`Sectors ${i[0]}\u2013${i[i.length-1]}`}function ft(s){const a=s.map(i=>I(O(i)[0]??"").quadrant??"");return a.length===1?a[0]:a.length===2?`Quadrants ${a[0]} & ${a[1]}`:`Quadrants ${a.slice(0,-1).join(", ")} & ${a[a.length-1]}`}function me(s){return[...new Set(s)]}function ct(s,a){const i=s[0],o=i.comparison.family,e=i.variable.kind,c=me(s.flatMap(h=>h.highlight.dieKeys??[])),r=me(s.flatMap(O)),n=new Set(c),l=o==="ring"?rt(s,a.ringCount):o==="sector"?lt(s,a.sectorCount):ft(s),t=e==="softBin"?a.softEligibleDies:e==="test"?a.testDies:a.eligibleDies,u=t.filter(h=>n.has(T(h))),f=t.filter(h=>!n.has(T(h)));let m,g,p,b,y;if(e==="test"){const h=i.variable.index,S=d=>d.testValues?.[h],v=u.map(S).filter(d=>d!==void 0),w=f.map(S).filter(d=>d!==void 0),{pValue:$,effectSize:D,delta:z}=it(v,w),N=ne(w),B=N!==0?z/Math.abs(N):void 0;m={direction:z===0?"different":z>0?"higher":"lower",absoluteDelta:z,relativeDelta:B,effectSize:D},g={method:"welch-z-approx",pValue:$,sampleSizeLeft:v.length,sampleSizeRight:w.length},p=ce($,D),b=Be(l,i.variable.label,z,B,o,i.variable.unit),y=`test:${h}`}else if(e==="yield"){const h=new Set(a.passBins),S=u.filter(R=>R.hbin!==void 0),v=f.filter(R=>R.hbin!==void 0),w=S.length,$=v.length,D=S.filter(R=>h.has(R.hbin)).length,z=v.filter(R=>h.has(R.hbin)).length,N=w>0?D/w:0,B=$>0?z/$:0,d=N-B,F=A(D,w,z,$);m={direction:d===0?"different":d>0?"higher":"lower",absoluteDelta:d,relativeDelta:B===0?void 0:d/B,effectSize:d},g={method:"two-proportion-z",pValue:F,sampleSizeLeft:w,sampleSizeRight:$},p=ie(F,d,m.relativeDelta),b=ke(l,d,o),y="yield"}else if(e==="functionalTest"){const h=i.variable.index,S=a.testDefs?.find(F=>F.testNumber===h);let v=0,w=0,$=0,D=0;for(const F of u){const R=ee(F,h,S);R!==void 0&&(w++,R&&v++)}for(const F of f){const R=ee(F,h,S);R!==void 0&&(D++,R&&$++)}const z=w>0?v/w:0,N=D>0?$/D:0,B=z-N,d=A(v,w,$,D);m={direction:B===0?"different":B>0?"higher":"lower",absoluteDelta:B,relativeDelta:N===0?void 0:B/N,effectSize:B},g={method:"two-proportion-z",pValue:d,sampleSizeLeft:w,sampleSizeRight:D},p=ie(d,B,m.relativeDelta),b=Re(l,x(S,h),B,o),y=`functional:${h}`}else{const h=i.variable.bin,S=d=>e==="softBin"?d.sbin:d.hbin,v=u.filter(d=>S(d)===h).length,w=f.filter(d=>S(d)===h).length,$=v/u.length,D=w/f.length,z=$-D,N=A(v,u.length,w,f.length);m={direction:z===0?"different":z>0?"higher":"lower",absoluteDelta:z,relativeDelta:D===0?void 0:z/D,effectSize:z},g={method:"two-proportion-z",pValue:N,sampleSizeLeft:u.length,sampleSizeRight:f.length},p=ie(N,z,m.relativeDelta);const B=e==="softBin"?a.sbinDefs:a.hbinDefs;b=Me(l,Fe(h,B,e==="softBin"?"SBin":"HBin"),z,o),y=`${e}:${h}`}const M=me(r.map(h=>h.replace(`${o}:`,""))).join("-"),k=e==="hardBin"||e==="softBin"?{kind:"bin",bin:i.variable.bin,regionKeys:r,dieKeys:c}:{kind:"region",regionFamily:o,regionKeys:r,dieKeys:c};return{...i,id:`${y}:${o}:${M}`,severity:p,comparison:{family:o,left:l,right:Y(o)},effect:m,stats:g,summary:b,highlight:k,relatedIds:s.map(h=>h.id)}}function ut(s,a){const i=new Set(["ring","quadrant","sector"]),o=[],e=new Map;for(const r of s){if(!i.has(r.comparison.family)){o.push(r);continue}const n=ot(r),l=e.get(n)??[];l.push(r),e.set(n,l)}const c=[];for(const r of e.values()){if(r.length===1){c.push(r[0]);continue}const n=r[0].comparison.family;for(const l of at(r,n,a.sectorCount))c.push(l.length===1?l[0]:ct(l,a))}return[...o,...c]}function dt(s){const a=new Map,i=new Map;for(const e of s){const c=T(e);e.hbin!==void 0&&e.hbin!==null&&(a.get(e.hbin)??a.set(e.hbin,new Set).get(e.hbin)).add(c),e.sbin!==void 0&&e.sbin!==null&&(i.get(e.sbin)??i.set(e.sbin,new Set).get(e.sbin)).add(c)}const o=new Set;for(const[e,c]of a)for(const[r,n]of i){if(c.size!==n.size)continue;let l=!0;for(const t of c)if(!n.has(t)){l=!1;break}l&&o.add(`${e}:${r}`)}return o}function gt(s){return`${s.comparison.family}\0${s.comparison.left}`}function ht(s,a,i){const o=(n,l)=>{n.absorbedIds=[...n.absorbedIds??[],l.id]},e=new Map;for(const n of s){const l=gt(n);(e.get(l)??e.set(l,[]).get(l)).push(n)}const c=dt(a),r=new Set;for(const n of e.values()){for(const t of n)if(!(t.variable.kind!=="hardBin"||t.variable.bin===void 0))for(const u of n){if(u.variable.kind!=="softBin"||u.variable.bin===void 0||r.has(u.id)||!c.has(`${t.variable.bin}:${u.variable.bin}`)||u.effect.direction!==t.effect.direction)continue;o(t,u),r.add(u.id);const f=t.variable.label.replace(/^HBin\s+/,""),m=u.variable.label.replace(/^SBin\s+/,""),g=f===m?`HBin and SBin ${f} (same dies)`:`${t.variable.label} and ${u.variable.label} (same dies)`;t.summary=t.summary.replace(t.variable.label,g),t.variable.label=g;break}if(i.length!==1)continue;const l=n.find(t=>t.variable.kind==="yield");if(l){for(const t of n)if(!(t===l||r.has(t.id)||!((t.variable.kind==="hardBin"||t.variable.kind==="softBin")&&t.variable.bin===i[0]))){o(l,t),r.add(t.id);for(const f of t.absorbedIds??[])l.absorbedIds=[...l.absorbedIds??[],f]}}}}export function analyzeWaferMap(s,a={}){const{resolved:i,warnings:o}=Ue(a),e=Ke(s),c={...i,passBins:e.passBins??j.passBins,ringCount:e.ringCount??j.ringCount},r=e.isLotStack,n=e.aggrMethod,l=!r||n==="mode"||n==="countBin"||n==="percent",t=e.dies.filter(d=>Xe(d)),u=t.filter(ye),f=Ge(c,t.length),m=e.dies.filter(d=>ae(d)),g=m.filter(ye),p=qe(g,e.wafer,f.ringCount),b=Ae(g,e.wafer,f.ringCount),y=Oe(g,e.reticleConfig),M=Ie(m,!1),k=He(g,e.wafer,f.sectorCount),h=[];l&&h.push(...W(t,p,f.passBins,f),...W(t,b,f.passBins,f),...W(t,y,f.passBins,f),...W(t,M,f.passBins,f),...W(t,k,f.passBins,f)),l&&h.push(...L(t,p,"hard",e.hbinDefs,"hardBin",f),...L(t,b,"hard",e.hbinDefs,"hardBin",f),...L(t,y,"hard",e.hbinDefs,"hardBin",f),...L(t,M,"hard",e.hbinDefs,"hardBin",f),...L(t,k,"hard",e.hbinDefs,"hardBin",f));{const d=t.filter(F=>F.sbin!==void 0);h.push(...L(d,p,"soft",e.sbinDefs,"softBin",f),...L(d,b,"soft",e.sbinDefs,"softBin",f),...L(d,y,"soft",e.sbinDefs,"softBin",f),...L(d,M,"soft",e.sbinDefs,"softBin",f),...L(d,k,"soft",e.sbinDefs,"softBin",f))}const S=[...o];let v;if(f.enableTestValueAnalysis){const d=U(t,p,e.testDefs,f),F=U(t,b,e.testDefs,f),R=U(t,y,e.testDefs,f),V=U(t,M,e.testDefs,f),P=U(t,k,e.testDefs,f);h.push(...d.findings,...F.findings,...R.findings,...V.findings,...P.findings),d.warning&&S.push(d.warning),v=d.activeTestNumbers,h.push(...st(t,[p,b,y,M,k],e.testDefs,f)),h.push(...Q(t,p,e.testDefs,f),...Q(t,b,e.testDefs,f),...Q(t,y,e.testDefs,f),...Q(t,M,e.testDefs,f),...Q(t,k,e.testDefs,f))}{const d=Je(r,l,e.testDefs);(!r||l||d!==void 0)&&h.push(..._e(u,e.wafer,{...f,isFailingDie:d}))}const w=ut(h,{eligibleDies:t,softEligibleDies:t.filter(d=>d.sbin!==void 0),testDies:t,passBins:f.passBins,ringCount:f.ringCount,sectorCount:f.sectorCount,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs});h.length=0,h.push(...w);let $;if(l){const d=We(u,e.wafer,{passBins:f.passBins,ringCount:f.ringCount});if($=d??void 0,d!==null&&d.pattern!=="random"&&d.pattern!=="none"){let Ee=function(C){const E=O(C).map(K=>I(K).ring).filter(K=>K!==void 0);if(E.length===0)return!0;const pe=E.some(K=>K===Le),be=E.some(K=>K===1);return Z==="edge-ring"||Z==="edge-local"?pe:Z==="center"?be:Z==="donut"?!pe&&!be:!0};const R={center:"Center cluster",donut:"Donut","edge-ring":"Edge-ring","edge-local":"Edge-local",scratch:"Scratch","near-full":"Near-full"}[d.pattern]??d.pattern,V=d.confidence==="high"?"unusual":d.confidence==="medium"?"notable":"info",P=d.features,q=C=>`${(C*100).toFixed(0)}%`,G=d.pattern==="edge-ring"||d.pattern==="edge-local"?`${q(P.edgeRdd)} of edge dies failing`:d.pattern==="center"||d.pattern==="donut"?`centroid at ${q(P.centroidDistNorm)} of wafer radius from centre`:d.pattern==="scratch"?`linear score ${P.linearScore.toFixed(2)}, eccentricity ${P.eccentricity.toFixed(2)}`:`${q(P.globalRdd)} of dies failing`,X=t.filter(C=>{const E=C.hbin??C.sbin;return E!==void 0&&!new Set(f.passBins).has(E)}),J={"edge-ring":["ring","edge-arc"],"edge-local":["edge-arc","sector","quadrant"],center:["ring","cluster"],donut:["ring"],scratch:["cluster","sector","quadrant"],"near-full":["ring"]},Ve=new Set(J[d.pattern]??[]),Z=d.pattern,Le=f.ringCount,oe=[];for(const C of h){if(!Ve.has(C.comparison.family)||C.comparison.family==="ring"&&!Ee(C))continue;oe.push(C.id);const E=C.comparison.family==="ring"||C.comparison.family==="quadrant"||C.comparison.family==="sector";V!=="info"&&E&&(C.severity="info")}h.push({id:`spatial-pattern:${d.pattern}`,level:"wafer",severity:V,variable:{kind:"spatialPattern",label:R},comparison:{family:"spatial-pattern",left:R,right:"Wafer"},effect:{direction:"different",effectSize:P.globalRdd},stats:{method:"geometry",sampleSizeLeft:X.length,sampleSizeRight:t.length},summary:`Spatial pattern: ${R.toLowerCase()} (${d.confidence} confidence) \u2014 ${G}${d.note?` [${d.note}]`:""}`,highlight:{kind:"dies",dieKeys:X.map(C=>T(C))},relatedIds:oe.length>0?oe:void 0})}}h.sort((d,F)=>{const R=d.stats.adjustedPValue??d.stats.pValue??1,V=F.stats.adjustedPValue??F.stats.pValue??1;return R!==V?R-V:Math.abs(F.effect.absoluteDelta??0)-Math.abs(d.effect.absoluteDelta??0)});const D=Ze(e.dies,t.length,e.yield.yieldPercent);r&&(D.isLotStack=!0,n&&(D.aggregationMethod=n));const z=et(e.dies,e.testDefs);z&&(D.testSpecYield=z);const N=computeFunctionalYield(e.dies,e.testDefs);if(N&&(D.functionalYield=N),$&&(D.spatialPattern=$),v===void 0&&f.computePerTestStats){const d=Ce(t,f.testNumbers,e.testDefs);d.warning&&S.push(d.warning),v=d.testNumbers}if(v?.length){const d=tt(e.dies,v,e.testDefs,f.minimumSampleSize);d&&(D.perTestStats=d);const F=Te([{dies:e.dies}],e.testDefs,v);F&&(D.capability=F)}Object.assign(D,xe(e.dies,e.testDefs));const B=je([{dies:e.dies,wafer:e.wafer,passBins:f.passBins,ringCount:f.ringCount}]);return B&&(D.regionYield=B),S.length>0&&(D.warnings=S),ht(h,t,f.passBins),{level:"wafer",hasNotableFindings:h.some(d=>d.severity==="notable"||d.severity==="unusual"),findings:h,wafer:e.wafer.metadata??void 0,stats:D}}
|
|
1
|
+
import{normalizeInput as Ge}from"./normalizeInput.js";import{computeCapability as Xe,computeTestFlagYield as Ze,computeRegionYield as Je}from"./summaryFigures.js";import{binPassSets as Me}from"../renderer/binColors.js";import{diePassStatus as Be,isYieldEligibleDie as de,getDieKey as Ne,isPositionedDie as Fe,positionKey as me}from"../core/dies.js";import{dieHasValues as et,testsPresent as Pe,testValue as j}from"../core/dieTable.js";import{getTestPassStatus as se,isParametricTest as Y}from"../renderer/buildWaferMap.js";import{markedTestLabel as T,derivedFields as oe}from"../renderer/testLabel.js";import{buildQuadrantRegions as tt,buildReticlePositionRegions as nt,buildRingRegions as it,buildSectorRegions as st,buildTestSiteRegions as ot,sectorCompassNames as xe,areQuadrantsAdjacent as at,parseRegionKey as _,QUADRANT_CYCLE as pe,regionAngleBins as rt}from"./regions.js";import{buildClusterFindings as ft}from"./clusterDetection.js";import{classifyPattern as ct,patternExplains as lt,patternFailVerdict as ut,PATTERN_LABELS as dt}from"./patternClassification.js";import{benjaminiHochberg as mt,fiveNumberSummary as pt,normalCdf as Ce}from"./math.js";import{mean as ae,clamp01 as Ve,maxOf as ht}from"../core/utils.js";import{classifySpec as re,isOutOfSpec as he}from"../renderer/spec.js";const gt={significanceLevel:{min:1e-6,max:1},minimumEffectSize:{min:0,max:1},minimumRelativeEffect:{min:0,max:Number.MAX_SAFE_INTEGER}},Le=[4,8,16];export function resolveOptions(e){const i=Object.fromEntries(Object.entries(e).filter(([,f])=>f!==void 0)),s={...I,...i},n=[],t=[];for(const f of["significanceLevel","minimumEffectSize","minimumRelativeEffect"]){const{min:l,max:o}=gt[f],u=s[f];if(typeof u!="number"||!Number.isFinite(u)){t.push(`${f}=${String(u)} is not a finite number (using ${I[f]})`),s[f]=I[f];continue}const a=Math.min(o,Math.max(l,u));a!==u&&(t.push(`${f}=${u} is outside ${l}\u2013${o} (using ${a})`),s[f]=a)}if(Le.includes(s.sectorCount)||(t.push(`sectorCount=${s.sectorCount} is not one of ${Le.join(", ")} (using ${I.sectorCount})`),s.sectorCount=I.sectorCount),t.length){const f=`analyzeWaferMap: ${t.join("; ")}. The analysis ran with the corrected values.`;console.warn(`[wafermap] ${f}`),n.push({code:"analysis-option-corrected",message:f,severity:"warning"})}const c=["passBins","ringCount"].filter(f=>i[f]!==void 0);if(c.length){const f=`analyzeWaferMap: ${c.join(" and ")} ${c.length>1?"are":"is"} no longer an analysis option and was ignored. Set ${c.length>1?"them":"it"} on buildWaferMap; the analysis used the value the map was built with.`;console.warn(`[wafermap] ${f}`),n.push({code:"analysis-option-corrected",message:f,severity:"warning"})}const r=["enableYieldAnalysis","enableHardBinAnalysis","enableSoftBinAnalysis","enableReticlePositionAnalysis","enableTestSiteAnalysis","enableClusterAnalysis","enableAngularAnalysis","enablePatternClassification"].filter(f=>i[f]!==void 0);if(r.length){const f=`analyzeWaferMap: ${r.join(", ")} ${r.length>1?"are":"is"} no longer an option and was ignored: every analysis now runs. Filter the findings (filterFindings) to show fewer.`;console.warn(`[wafermap] ${f}`),n.push({code:"analysis-option-corrected",message:f,severity:"warning"})}return{resolved:s,warnings:n}}const I={ringCount:4,passBins:[1],significanceLevel:.05,minimumEffectSize:.2,minimumRelativeEffect:1,minimumSampleSize:5,enableTestValueAnalysis:!1,computePerTestStats:!1,sectorCount:8,minimumClusterSize:5};function bt(e,i){const s={...e};return s.minimumClusterSize=Math.max(5,Math.round(i*.003)),s}function yt(e){return de(e)?e.hbin!==void 0||e.sbin!==void 0||et(e):!1}function St(e,i,s){if(!e||i)return;const n=(s??[]).filter(t=>Y(t)&&(t.limitLow!==void 0||t.limitHigh!==void 0));if(n.length!==0)return t=>{for(const c of n)if(he(re(j(t,c.testNumber),c)))return!0;return!1}}function wt(e,i,s){const n=new Set,t=new Set,c=new Set,r=new Map,f=new Map;for(const l of Pe(e))n.add(l);for(const l of e)l.hbin!==void 0&&t.add(l.hbin),l.sbin!==void 0&&c.add(l.sbin),de(l)&&(l.hbin!==void 0&&r.set(l.hbin,(r.get(l.hbin)??0)+1),l.sbin!==void 0&&f.set(l.sbin,(f.get(l.sbin)??0)+1));return{totalDies:e.length,analyzedDies:i,excludedDies:e.length-i,yieldPercent:s,testsConsidered:[...n].sort((l,o)=>l-o),hardBinsConsidered:[...t].sort((l,o)=>l-o),softBinsConsidered:[...c].sort((l,o)=>l-o),...r.size?{hardBinCounts:Object.fromEntries(r)}:{},...f.size?{softBinCounts:Object.fromEntries(f)}:{}}}function vt(e,i){if(!i?.length)return;const s=i.filter(t=>Y(t)&&(t.limitLow!==void 0||t.limitHigh!==void 0));if(!s.length)return;const n=[];for(const t of s){const c=t.testNumber;let r=0,f=0,l=0,o=0;for(const u of e){if(u.partial||u.edgeExcluded)continue;const a=re(j(u,c),t);a!==null&&(o++,a==="failLow"?f++:a==="failHigh"?l++:r++)}n.push({testNumber:c,label:t.name,passDies:r,failLowDies:f,failHighDies:l,totalDies:o,yieldPercent:o>0?r/o*100:null})}return n.length?n:void 0}export function computeFunctionalYield(e,i){if(!i?.length)return;const s=i.filter(t=>!Y(t));if(!s.length)return;const n=[];for(const t of s){const c=t.testNumber;let r=0,f=0;for(const o of e){if(o.partial||o.edgeExcluded)continue;const u=se(o,c,t);u!==void 0&&(u?r++:f++)}const l=r+f;n.push({testNumber:c,label:T(t,c),...oe(t),passDies:r,failDies:f,totalDies:l,passRatePercent:l>0?r/l*100:null})}return n.length?n:void 0}function $t(e,i,s,n){const t=[];for(const c of i){const r=[];for(const a of e){if(a.partial||a.edgeExcluded)continue;const h=j(a,c);h!==void 0&&Number.isFinite(h)&&r.push(h)}if(r.length<n)continue;const f=ae(r),l=Math.sqrt(ze(r,f)),o=pt(Float64Array.from(r)),u=s?.find(a=>a.testNumber===c)?.name??String(c);t.push({testNumber:c,label:u,count:r.length,min:o.min,max:o.max,mean:f,stddev:l,median:o.median,q1:o.q1,q3:o.q3})}return t.length?t:void 0}export function twoProportionZ(e,i,s,n){const t=(e+s)/(i+n),c=t*(1-t)*(1/i+1/n);return!Number.isFinite(c)||c<=0?0:(e/i-s/n)/Math.sqrt(c)}export function zPValue(e){return Ve(2*(1-Ce(Math.abs(e))))}function O(e,i,s,n){const t=twoProportionZ(e,i,s,n);return t===0?1:zPValue(t)}export function adjustPValues(e){const i=new Map;for(const s of e){const n=`${s.variable.kind}:${s.comparison.family}`,t=i.get(n)??[];t.push(s),i.set(n,t)}for(const s of i.values()){const n=s.filter(c=>c.stats.pValue!==void 0),t=mt(n.map(c=>c.stats.pValue));n.forEach((c,r)=>{c.stats.adjustedPValue=t[r]})}return e}export function rateRelativeEffect(e){const i=(e.passRate?e.n-e.hits:e.hits)/e.n,s=(e.passRate?e.restN-e.restHits:e.restHits)/e.restN;return s===0?i===0?0:1/0:Math.abs(i-s)/s}export function severityForFinding(e,i,s){const n=Math.abs(i),t=s;return e<=.01&&(n>=.3||t>=2.5)?"unusual":e<=.05&&(n>=.2||t>=1.5)?"notable":"info"}export function severityForScore(e,i){return e<=.01&&Math.abs(i)>=.5?"unusual":e<=.05&&Math.abs(i)>=.15?"notable":"info"}function fe(e){return e==="reticle-position"?"other reticle positions":e==="test-site"?"other test sites":"the rest of the map"}function Q(e){return e==="reticle-position"?"Other reticle positions":e==="test-site"?"Other test sites":"Rest of map"}function ge(e,i,s){const n=(Math.abs(i)*100).toFixed(1);return`${e} yield is ${n} percentage points ${i>0?"higher":"lower"} than ${fe(s)}`}function be(e,i){return i==="quadrant"&&!e.startsWith("Quadrant")?`quadrant ${e}`:e}function Ke(e){return/^(Rings|Sectors|Quadrants)\b/.test(e)?"have":"has"}function ye(e,i,s,n){const t=be(e,n),c=(Math.abs(s)*100).toFixed(1);return`${t} ${Ke(t)} ${i} occurrence ${c} percentage points ${s>0?"higher":"lower"} than ${fe(n)}`}function Se(e,i,s,n){const t=be(e,n),c=(Math.abs(s)*100).toFixed(1);return`${t} ${Ke(t)} ${i} pass rate ${c} percentage points ${s>0?"higher":"lower"} than ${fe(n)}`}function we(e,i,s,n,t,c){const r=be(e,t),f=fe(t),l=s>0?"higher":"lower";if(n!==void 0&&Number.isFinite(n)){const u=(Math.abs(n)*100).toFixed(1);return`${r} mean ${i} is ${u}% ${l} than ${f}`}const o=c?` ${c}`:"";return`${r} mean ${i} is ${Math.abs(s).toPrecision(3)}${o} ${l} than ${f}`}function Ee(e,i,s){const n=i?.find(t=>t.bin===e);return n?.name?`${s} ${e} (${n.name})`:`${s} ${e}`}function zt(e,i){const s=i?.find(n=>n.testNumber===e);return{label:T(s,e),unit:s?.unit,...oe(s)}}const Oe=new WeakMap,Te=new WeakMap,qe=new WeakMap;function Z(e,i){const s=i.highlight.dieKeys;return s&&qe.set(s,e),i}let U=null,J;const He=new WeakMap;export function candidatesOf(e){return He.get(e)}function Ae(e,i,s,n){if(!U)return;const t=je(e);if(!t)return;const c=e.id.slice(0,e.id.indexOf(":")),r=c==="specLimit"?"limitFail":c,{kind:f,bin:l,index:o}=e.variable;U.push({key:[r,f,l??"",o??"",t.family,t.key].join("|"),source:r,variable:e.variable,label:e.variable.label,comparison:e.comparison,region:t,z:i,delta:e.effect.absoluteDelta??0,effectSize:e.effect.effectSize??0,relativeDelta:e.effect.relativeDelta,rate:s,mean:n})}export function describeRegional(e,i,s){const n=e.comparison.left,t=e.region.family;switch(e.source){case"yield":return ge(n,i,t);case"hardBin":case"softBin":return ye(n,e.label,i,t);case"functional":return Se(n,e.label.replace(/ pass rate$/,""),i,t);case"limitFail":return ve(n,e.label,i);case"test":return we(n,e.label,i,s,t,e.variable.unit)}}function ve(e,i,s){return`${e} limit fail rate for ${i} is ${(Math.abs(s)*100).toFixed(1)} pp ${s>0?"higher":"lower"} than the rest of the wafer`}const G=new WeakMap;function ce(e,i){return G.set(e,i),e}function je(e){const i=e.highlight.dieKeys;return i?qe.get(i):void 0}const Ie=new WeakMap;function W(e){let i=Ie.get(e);return i||Ie.set(e,i=e.map(me)),i}function kt(e){let i=Oe.get(e);return i||Oe.set(e,i=e.map(Ne)),i}function le(e,i){let s=Te.get(e);s||Te.set(e,s={members:new Set(e),byFamily:new WeakMap});const n=s.byFamily.get(i);if(n)return n;const{members:t}=s,c=new Map;for(const r of i)c.set(r.key,r.dies.filter(f=>t.has(f)));return s.byFamily.set(i,c),c}function ue(e,i){adjustPValues(e);for(const n of e){const t=G.get(n);Ae(n,twoProportionZ(t.hits,t.n,t.restHits,t.restN),t)}const s=e.filter(n=>passesRateGate(n.stats.adjustedPValue??n.stats.pValue??1,n.effect.absoluteDelta??0,rateRelativeEffect(G.get(n)),i));return s.filter(n=>!Dt(n,s,i)).map(n=>({...n,severity:severityForFinding(n.stats.adjustedPValue??n.stats.pValue??1,n.effect.absoluteDelta??0,rateRelativeEffect(G.get(n)))}))}export function passesRateGate(e,i,s,n){return e<=n.significanceLevel&&(Math.abs(i)>=n.minimumEffectSize||s>=n.minimumRelativeEffect)}export function claimForPattern(e,i,s,n,t){const c=[];for(const r of s){const f=r.comparison.family,l=f==="ring"?n(r):[];e.some(o=>lt(o,f,l,t))&&(c.push(r.id),i!=="info"&&(f==="ring"||f==="quadrant"||f==="sector")&&(r.severity="info"))}return c}function Dt(e,i,s){const n=G.get(e);if(!n)return!1;const t=e.stats.pValue??1;let{restHits:c,restN:r}=n;for(const u of We(e,i)){const a=G.get(u);a&&(c-=a.hits,r-=a.n)}if(r===n.restN)return!1;if(r<s.minimumSampleSize)return!0;const f=n.hits/n.n-c/r;if(Math.sign(f)!==Math.sign(e.effect.absoluteDelta??0))return!0;const l=t>0?(e.stats.adjustedPValue??t)/t:1,o=Math.min(1,O(n.hits,n.n,c,r)*l);return!passesRateGate(o,f,rateRelativeEffect({...n,restHits:c,restN:r}),s)}function We(e,i){const s=e.stats.pValue??1;return i.filter(n=>n!==e&&n.effect.direction!==e.effect.direction&&n.variable.kind===e.variable.kind&&n.variable.bin===e.variable.bin&&n.variable.index===e.variable.index&&n.comparison.family===e.comparison.family&&(n.stats.pValue??1)<s)}const $e=new WeakMap;export function welchOfSums(e,i,s,n){const t=(c,r,f)=>f>1?Math.max(0,c-r*r/f)/(f-1):0;return welchFromStats(e.n,e.sum/e.n,t(e.sq,e.sum,e.n),i,s/i,t(n,s,i))}function Rt(e,i,s){const n=$e.get(e);if(!n)return!1;let{restN:t,restSum:c,restSq:r}=n;for(const h of We(e,i)){const g=$e.get(h);g&&(t-=g.n,c-=g.sum,r-=g.sq)}if(t===n.restN)return!1;if(t<s.minimumSampleSize)return!0;const{pValue:f,effectSize:l,delta:o}=welchOfSums(n,t,c,r);if(Math.sign(o)!==Math.sign(e.effect.absoluteDelta??0))return!0;const u=e.stats.pValue??1,a=u>0?(e.stats.adjustedPValue??u)/u:1;return!(Math.min(1,f*a)<=s.significanceLevel&&Math.abs(l)>=s.minimumEffectSize)}function ee(e,i,s,n){const t=new Set(s),c=le(e,i),r=new Map,f=new Map;for(const[o,u]of c){let a=0,h=0;for(const g of u)g.hbin!==void 0&&(h++,t.has(g.hbin)&&a++);r.set(o,a),f.set(o,h)}const l=[];for(const o of i){const u=f.get(o.key)??0,a=r.get(o.key)??0;let h=0,g=0;for(const[k,S]of f)k!==o.key&&(h+=S,g+=r.get(k)??0);if(u<n.minimumSampleSize||h<n.minimumSampleSize)continue;const p=a/u,d=g/h,y=p-d,w=O(a,u,g,h);l.push(ce(Z(o,{id:`yield:${o.key}`,level:"wafer",severity:"info",variable:{kind:"yield",label:"Yield"},comparison:{family:o.family,left:o.label,right:Q(o.family)},effect:{direction:y===0?"different":y>0?"higher":"lower",absoluteDelta:y,relativeDelta:d===0?void 0:y/d,effectSize:y},stats:{method:"two-proportion-z",pValue:w,sampleSizeLeft:u,sampleSizeRight:h},summary:ge(o.label,y,o.family),highlight:{kind:"region",regionFamily:o.family,regionKeys:[o.key],dieKeys:[...o.dieKeys]}}),{hits:a,n:u,restHits:g,restN:h,passRate:!0}))}return ue(l,n)}function E(e,i,s,n,t,c){const r=p=>s==="soft"?p.sbin:p.hbin,f=[...new Set(e.map(r).filter(p=>p!==void 0))].sort((p,d)=>p-d),l=le(e,i),o=new Map,u=new Map;for(const[p,d]of l){const y=new Map;for(const w of d){const k=r(w);k!==void 0&&y.set(k,(y.get(k)??0)+1)}o.set(p,y),u.set(p,d.length)}const a=[],h=t==="hardBin"?"HBin":"SBin",g=s==="hard"?new Set(c.passBins):c.softPassBins??Me(e,c.passBins).soft;for(const p of i){const d=u.get(p.key)??0,y=o.get(p.key);let w=0;const k=new Map;for(const[S,$]of o)if(S!==p.key){w+=u.get(S)??0;for(const[N,F]of $)k.set(N,(k.get(N)??0)+F)}if(!(d<c.minimumSampleSize||w<c.minimumSampleSize))for(const S of f){const $=y.get(S)??0,N=k.get(S)??0,F=$/d,b=N/w,v=F-b,M=O($,d,N,w),z=Ee(S,n,h);a.push(ce(Z(p,{id:`${t}:${S}:${p.key}`,level:"wafer",severity:"info",variable:{kind:t,bin:S,label:z},comparison:{family:p.family,left:p.label,right:Q(p.family)},effect:{direction:v===0?"different":v>0?"higher":"lower",absoluteDelta:v,relativeDelta:b===0?void 0:v/b,effectSize:v},stats:{method:"two-proportion-z",pValue:M,sampleSizeLeft:d,sampleSizeRight:w},summary:ye(p.label,z,v,p.family),highlight:{kind:"bin",bin:S,regionKeys:[p.key],dieKeys:[...p.dieKeys]}}),{hits:$,n:d,restHits:N,restN:w,passRate:g.has(S)}))}}return ue(a,c)}function te(e,i,s,n){const t=(s??[]).filter(u=>!Y(u));if(!t.length)return[];const c=t.map(u=>u.testNumber),r=le(e,i),f=new Map,l=new Map;for(const[u,a]of r){const h=new Uint32Array(t.length),g=new Uint32Array(t.length);for(const p of a)for(let d=0;d<t.length;d++){const y=se(p,c[d],t[d]);y!==void 0&&(g[d]++,y&&h[d]++)}f.set(u,h),l.set(u,g)}const o=[];for(const u of i){const a=f.get(u.key),h=l.get(u.key);for(let g=0;g<t.length;g++){let p=0,d=0;for(const[b,v]of l)b!==u.key&&(d+=v[g],p+=f.get(b)[g]);const y=h[g],w=a[g];if(y<n.minimumSampleSize||d<n.minimumSampleSize)continue;const k=w/y,S=p/d,$=k-S,N=O(w,y,p,d),F=c[g];o.push(ce(Z(u,{id:`functional:${F}:${u.key}`,level:"wafer",severity:"info",variable:{kind:"functionalTest",index:F,label:`${T(t[g],F)} pass rate`,...oe(t[g])},comparison:{family:u.family,left:u.label,right:Q(u.family)},effect:{direction:$===0?"different":$>0?"higher":"lower",absoluteDelta:$,relativeDelta:S===0?void 0:$/S,effectSize:$},stats:{method:"two-proportion-z",pValue:N,sampleSizeLeft:y,sampleSizeRight:d},summary:Se(u.label,T(t[g],F),$,u.family),highlight:{kind:"region",regionFamily:u.family,regionKeys:[u.key],dieKeys:[...u.dieKeys]}}),{hits:w,n:y,restHits:p,restN:d,passRate:!0}))}}return ue(o,n)}function ze(e,i){return e.length<2?0:e.reduce((s,n)=>s+(n-i)**2,0)/(e.length-1)}export function welchFromStats(e,i,s,n,t,c){const r=Math.sqrt(s/e+c/n),f=i-t;if(!Number.isFinite(r)||r===0)return{pValue:1,effectSize:0,delta:f,z:0};const l=f/r,o=Math.sqrt(Math.max(0,(s+c)/2)),u=o===0?f:f/o;return{pValue:Ve(2*(1-Ce(Math.abs(l)))),effectSize:u,delta:f,z:l}}function Mt(e,i){const s=ae(e),n=ae(i);return welchFromStats(e.length,s,ze(e,s),i.length,n,ze(i,n))}const Ye=250;function _e(e,i,s){const n=r=>r.filter(f=>Y(s?.find(l=>l.testNumber===f)));if(i)return{testNumbers:n(i.slice().sort((r,f)=>r-f))};const t=Pe(e,"values");if(t.length>Ye){const r=`Test-value analysis was skipped: more than ${Ye} tests were found in the die data, so no test findings were computed. Pass testNumbers: [...] to analyse specific tests.`;return console.warn(`[wafermap] analyzeWaferMap: ${r}`),{testNumbers:[],warning:{code:"test-count-capped",message:r,severity:"warning"}}}return{testNumbers:n(t)}}function ne(e,i,s,n){const t=_e(e,n.testNumbers,s);if(t.warning)return{findings:[],warning:t.warning};const c=t.testNumbers,r=new Map;for(let d=0;d<i.length;d++)for(const y of W(i[d].dies))r.set(y,d);const f=i.length,l=[],o=[];for(const d of e){const y=r.get(me(d));y!==void 0&&(l.push(d),o.push(y))}const u=[],a=new Float64Array(f),h=new Float64Array(f),g=new Float64Array(f);for(const d of c){a.fill(0),h.fill(0),g.fill(0);let y=0,w=0,k=0,S;for(let $=0;$<l.length;$++){const N=l[$],F=j(N,d);if(F===void 0)continue;S===void 0&&(S=F);const b=F-S,v=o[$];a[v]+=1,h[v]+=b,g[v]+=b*b,y+=1,w+=b,k+=b*b}if(S!==void 0)for(let $=0;$<f;$++){const N=a[$],F=y-N;if(N<n.minimumSampleSize||F<n.minimumSampleSize)continue;const b=h[$]/N,M=(w-h[$])/F,z=b+S,D=M+S,C=N>1?Math.max(0,g[$]-N*b*b)/(N-1):0,m=F>1?Math.max(0,k-g[$]-F*M*M)/(F-1):0,{pValue:R,effectSize:P,delta:B,z:x}=welchFromStats(N,z,C,F,D,m),V=i[$],{label:L,unit:q,...H}=zt(d,s),K=D!==0?B/Math.abs(D):void 0,A=Z(V,{id:`test:${d}:${V.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:d,label:L,unit:q,...H},comparison:{family:V.family,left:V.label,right:Q(V.family)},effect:{direction:B===0?"different":B>0?"higher":"lower",absoluteDelta:B,relativeDelta:K,effectSize:P},stats:{method:"welch-z-approx",pValue:R,sampleSizeLeft:N,sampleSizeRight:F},summary:we(V.label,L,B,K,V.family,q),highlight:{kind:"region",regionFamily:V.family,regionKeys:[V.key],dieKeys:[...V.dieKeys]}}),Re={n:N,sum:h[$],sq:g[$],restN:F,restSum:w-h[$],restSq:k-g[$],shift:S};$e.set(A,Re),Ae(A,x,void 0,Re),u.push(A)}}adjustPValues(u);const p=u.filter(d=>{const y=d.stats.adjustedPValue??d.stats.pValue??1,w=Math.abs(d.effect.effectSize??0);return y<=n.significanceLevel&&w>=n.minimumEffectSize});return{activeTestNumbers:c,findings:p.filter(d=>!Rt(d,p,n)).map(d=>({...d,severity:severityForScore(d.stats.adjustedPValue??d.stats.pValue??1,d.effect.effectSize??0)}))}}function Bt(e,i,s,n){if(!s?.length)return[];const t=s.filter(r=>Y(r)&&(r.limitLow!==void 0||r.limitHigh!==void 0));if(!t.length)return[];const c=[];for(const r of t){const f=r.testNumber;for(const l of i){const o=le(e,l),u=[],a=new Map;let h=0,g=0;for(const[p,d]of o){let y=0,w=0;for(const k of d){const S=j(k,f);S!==void 0&&(y++,he(re(S,r))&&w++)}a.set(p,{valid:y,fail:w}),h+=y,g+=w}for(const p of l){const d=a.get(p.key),y=d.valid,w=h-d.valid;if(y<n.minimumSampleSize||w<n.minimumSampleSize)continue;const k=d.fail,S=g-d.fail,$=k/y,N=S/w,F=$-N,b=O(k,y,S,w);u.push(ce(Z(p,{id:`specLimit:${f}:${p.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:f,label:T(r,f),unit:r.unit,...oe(r)},comparison:{family:p.family,left:p.label,right:Q(p.family)},effect:{direction:F===0?"different":F>0?"higher":"lower",absoluteDelta:F,relativeDelta:N===0?void 0:F/N,effectSize:F},stats:{method:"two-proportion-z",pValue:b,sampleSizeLeft:y,sampleSizeRight:w},summary:ve(p.label,T(r,f),F),highlight:{kind:"region",regionFamily:p.family,regionKeys:[p.key],dieKeys:[...p.dieKeys]}}),{hits:k,n:y,restHits:S,restN:w,passRate:!1}))}c.push(...ue(u,n))}}return c}function ke(e,i){return e==="softBin"?i.softEligibleDies:e==="test"?i.testDies:i.eligibleDies}function Nt(e){return e.id.startsWith("specLimit:")}function ie(e){return e.highlight.regionKeys??[]}function Ft(e){const i=e.variable.bin??e.variable.index??"";return`${e.comparison.family}\0${Qe(e)}\0${e.variable.kind}\0${i}\0${e.effect.direction}`}function Pt(e,i){const s=_(e);return s.family==="ring"?s.ring??0:s.family==="sector"?xe(i).indexOf(s.sector??""):0}export function inQuadrantOrder(e){const i=new Set(e),s=pe.findIndex((t,c)=>i.has(t)&&!i.has(pe[(c+3)%4])),n=s<0?0:s;return Array.from({length:4},(t,c)=>pe[(n+c)%4]).filter(t=>i.has(t))}export function findContiguousRuns(e,i,s,n){if(i==="quadrant"){const l=new Map;for(const h of e){const g=_(n(h)).quadrant;g&&l.set(g,h)}const o=[...l.keys()],u=new Set,a=[];for(const h of o){if(u.has(h))continue;const g=[],p=[h];for(u.add(h);p.length;){const d=p.pop();g.push(d);for(const y of o)!u.has(y)&&at(d,y)&&(u.add(y),p.push(y))}a.push(inQuadrantOrder(g).map(d=>l.get(d)))}return a}const t=l=>Pt(n(l),s),c=[...e].sort((l,o)=>t(l)-t(o)),r=[];let f=[];for(const l of c){if(f.length===0){f.push(l);continue}const o=t(f[f.length-1]);t(l)-o===1?f.push(l):(r.push(f),f=[l])}if(f.length&&r.push(f),i==="sector"&&r.length>1){const o=xe(s).length,u=t(r[0][0]),a=t(r[r.length-1][r[r.length-1].length-1]);if(u===0&&a===o-1){const h=r.pop();r[0]=[...h,...r[0]]}}return r}export function mergedRegionLabel(e,i,s){if(e==="ring"){const t=i.map(l=>_(l).ring).filter(l=>l!==void 0).sort((l,o)=>l-o),c=t[0],r=t[t.length-1];let f="";return c===r&&c===1&&s>1?f=" (core)":c===r&&c===s&&s>1&&(f=" (edge)"),c===r?`Ring ${c}${f}`:`Rings ${c}\u2013${r}`}if(e==="sector"){const t=i.map(c=>_(c).sector??"");return t.length===1?`Sector ${t[0]}`:`Sectors ${t[0]}\u2013${t[t.length-1]}`}const n=i.map(t=>_(t).quadrant??"");return n.length===1?n[0]:n.length===2?`Quadrants ${n[0]} & ${n[1]}`:`Quadrants ${n.slice(0,-1).join(", ")} & ${n[n.length-1]}`}function De(e){return[...new Set(e)]}export function weakestMultiplier(e){return ht(e.map(i=>{const s=i.stats.pValue??1;return s>0?(i.stats.adjustedPValue??s)/s:1}))}function X(e,i){return Math.min(1,e*i)}function xt(e,i){const s=e[0],n=s.comparison.family,t=s.variable.kind,c=De(e.flatMap(ie));let r,f;const l=e.map(je);if(l.every(b=>b!==void 0)){const b=new Set;r=[];for(const M of l){const z=W(M.dies);for(let D=0;D<z.length;D++)b.has(z[D])||(b.add(z[D]),r.push(M.dieKeys[D]))}const v=W(ke(s.variable.kind,i));f=M=>b.has(v[M])}else{r=De(e.flatMap(M=>M.highlight.dieKeys??[]));const b=new Set(r),v=kt(ke(s.variable.kind,i));f=M=>b.has(v[M])}const o=mergedRegionLabel(n,e.map(b=>ie(b)[0]??""),i.ringCount),u=ke(t,i),a=i.regionDies[n],h=W(u),g=[],p=[];for(let b=0;b<u.length;b++)a.has(h[b])&&(f(b)?g:p).push(u[b]);const d=weakestMultiplier(e);let y,w,k,S,$;if(Nt(s)){const b=s.variable.index,v=i.testDefs?.find(x=>x.testNumber===b),M=x=>{let V=0,L=0;for(const q of x){const H=j(q,b);H!==void 0&&(V++,v&&he(re(H,v))&&L++)}return{valid:V,fail:L}},z=M(g),D=M(p),C=z.valid>0?z.fail/z.valid:0,m=D.valid>0?D.fail/D.valid:0,R=C-m,P=O(z.fail,z.valid,D.fail,D.valid);y={direction:R===0?"different":R>0?"higher":"lower",absoluteDelta:R,relativeDelta:m===0?void 0:R/m,effectSize:R},w={method:"two-proportion-z",pValue:P,sampleSizeLeft:z.valid,sampleSizeRight:D.valid};const B=X(P,d);k=severityForFinding(B,R,rateRelativeEffect({hits:z.fail,n:z.valid,restHits:D.fail,restN:D.valid,passRate:!1})),S=ve(o,T(v,b),R),$=`specLimit:${b}`}else if(t==="test"){const b=s.variable.index,v=x=>j(x,b),M=g.map(v).filter(x=>x!==void 0),z=p.map(v).filter(x=>x!==void 0),{pValue:D,effectSize:C,delta:m}=Mt(M,z),R=ae(z),P=R!==0?m/Math.abs(R):void 0;y={direction:m===0?"different":m>0?"higher":"lower",absoluteDelta:m,relativeDelta:P,effectSize:C},w={method:"welch-z-approx",pValue:D,sampleSizeLeft:M.length,sampleSizeRight:z.length};const B=X(D,d);k=severityForScore(B,C),S=we(o,s.variable.label,m,P,n,s.variable.unit),$=`test:${b}`}else if(t==="yield"){const b=new Set(i.passBins),v=g.filter(L=>L.hbin!==void 0),M=p.filter(L=>L.hbin!==void 0),z=v.length,D=M.length,C=v.filter(L=>b.has(L.hbin)).length,m=M.filter(L=>b.has(L.hbin)).length,R=z>0?C/z:0,P=D>0?m/D:0,B=R-P,x=O(C,z,m,D);y={direction:B===0?"different":B>0?"higher":"lower",absoluteDelta:B,relativeDelta:P===0?void 0:B/P,effectSize:B},w={method:"two-proportion-z",pValue:x,sampleSizeLeft:z,sampleSizeRight:D};const V=X(x,d);k=severityForFinding(V,B,rateRelativeEffect({hits:C,n:z,restHits:m,restN:D,passRate:!0})),S=ge(o,B,n),$="yield"}else if(t==="functionalTest"){const b=s.variable.index,v=i.testDefs?.find(V=>V.testNumber===b);let M=0,z=0,D=0,C=0;for(const V of g){const L=se(V,b,v);L!==void 0&&(z++,L&&M++)}for(const V of p){const L=se(V,b,v);L!==void 0&&(C++,L&&D++)}const m=z>0?M/z:0,R=C>0?D/C:0,P=m-R,B=O(M,z,D,C);y={direction:P===0?"different":P>0?"higher":"lower",absoluteDelta:P,relativeDelta:R===0?void 0:P/R,effectSize:P},w={method:"two-proportion-z",pValue:B,sampleSizeLeft:z,sampleSizeRight:C};const x=X(B,d);k=severityForFinding(x,P,rateRelativeEffect({hits:M,n:z,restHits:D,restN:C,passRate:!0})),S=Se(o,T(v,b),P,n),$=`functional:${b}`}else{const b=s.variable.bin,v=x=>t==="softBin"?x.sbin:x.hbin,M=g.filter(x=>v(x)===b).length,z=p.filter(x=>v(x)===b).length,D=M/g.length,C=z/p.length,m=D-C,R=O(M,g.length,z,p.length);y={direction:m===0?"different":m>0?"higher":"lower",absoluteDelta:m,relativeDelta:C===0?void 0:m/C,effectSize:m},w={method:"two-proportion-z",pValue:R,sampleSizeLeft:g.length,sampleSizeRight:p.length};const P=X(R,d);k=severityForFinding(P,m,rateRelativeEffect({hits:M,n:g.length,restHits:z,restN:p.length,passRate:!1}));const B=t==="softBin"?i.sbinDefs:i.hbinDefs;S=ye(o,Ee(b,B,t==="softBin"?"SBin":"HBin"),m,n),$=`${t}:${b}`}w={...w,adjustedPValue:X(w.pValue??1,d)};const N=De(c.map(b=>b.replace(`${n}:`,""))).join("-"),F=t==="hardBin"||t==="softBin"?{kind:"bin",bin:s.variable.bin,regionKeys:c,dieKeys:r}:{kind:"region",regionFamily:n,regionKeys:c,dieKeys:r};return{...s,id:`${$}:${n}:${N}`,severity:k,comparison:{family:n,left:o,right:Q(n)},effect:y,stats:w,summary:S,highlight:F,relatedIds:e.map(b=>b.id)}}function Ct(e,i){const s=new Set(["ring","quadrant","sector"]),n=[],t=new Map;for(const r of e){if(!s.has(r.comparison.family)){n.push(r);continue}const f=Ft(r),l=t.get(f)??[];l.push(r),t.set(f,l)}const c=[];for(const r of t.values()){if(r.length===1){c.push(r[0]);continue}const f=r[0].comparison.family;for(const l of findContiguousRuns(r,f,i.sectorCount,o=>ie(o)[0]??""))c.push(l.length===1?l[0]:xt(l,i))}return[...n,...c]}function Vt(e){const i=new Map,s=new Map;for(const t of e){const c=me(t);t.hbin!==void 0&&t.hbin!==null&&(i.get(t.hbin)??i.set(t.hbin,new Set).get(t.hbin)).add(c),t.sbin!==void 0&&t.sbin!==null&&(s.get(t.sbin)??s.set(t.sbin,new Set).get(t.sbin)).add(c)}const n=new Set;for(const[t,c]of i)for(const[r,f]of s){if(c.size!==f.size)continue;let l=!0;for(const o of c)if(!f.has(o)){l=!1;break}l&&n.add(`${t}:${r}`)}return n}function Lt(e,i){const s=new Set(i);let n;for(const t of e){const c=Be(t,s);if(c!==void 0&&c){if(t.sbin==null||n!==void 0&&t.sbin!==n)return;n=t.sbin}}if(n!==void 0){for(const t of e)if(t.sbin===n&&Be(t,s)!==!0)return;return n}}function Kt(e){return`${e.comparison.family}\0${e.comparison.left}`}export function redundancyFacts(e,i,s){return{coincident:Vt(e),yieldSoftBin:Lt(e,i),passBins:i,sectorCount:s}}const Et=3/5;function Qe(e){return/^(?:lot-region:)?([^:|]+)/.exec(e.id)?.[1]??e.id}function Ot(e){const i=ie(e);if(i.length)return i;const s=/(ring|sector|quadrant):([^:|]+)$/.exec(e.id);return s?s[2].split("-").map(n=>`${s[1]}:${n}`):[]}function Ue(e,i){const s=new Set;for(const n of Ot(e)){const t=rt(n,i);if(!t)return;for(const c of t)s.add(c)}return s.size?s:void 0}export function collapseRedundantFindings(e,i){const{coincident:s,yieldSoftBin:n,passBins:t}=i,c=(o,u)=>{o.absorbedIds=[...o.absorbedIds??[],u.id]},r=new Map;for(const o of e){const u=Kt(o);(r.get(u)??r.set(u,[]).get(u)).push(o)}const f=new Set;for(const o of r.values()){for(const a of o)if(!(a.variable.kind!=="hardBin"||a.variable.bin===void 0))for(const h of o){if(h.variable.kind!=="softBin"||h.variable.bin===void 0||f.has(h.id)||!s.has(`${a.variable.bin}:${h.variable.bin}`)||h.effect.direction!==a.effect.direction)continue;c(a,h),f.add(h.id);const g=a.variable.label.replace(/^HBin\s+/,""),p=h.variable.label.replace(/^SBin\s+/,""),d=g===p?`HBin and SBin ${g} (same dies)`:`${a.variable.label} and ${h.variable.label} (same dies)`;a.summary=a.summary.replace(a.variable.label,d),a.variable.label=d;break}const u=o.find(a=>a.variable.kind==="yield");if(u){for(const a of o)if(!(a===u||f.has(a.id)||!(a.variable.kind==="hardBin"&&t.length===1&&a.variable.bin===t[0]||a.variable.kind==="softBin"&&a.variable.bin===n))){c(u,a),f.add(a.id);for(const g of a.absorbedIds??[])u.absorbedIds=[...u.absorbedIds??[],g]}}}const l=new Map;for(const o of e){if(f.has(o.id)||o.comparison.family!=="sector"&&o.comparison.family!=="quadrant")continue;const u=[Qe(o),o.variable.kind,o.variable.bin??"",o.variable.index??"",o.effect.direction].join("\0");(l.get(u)??l.set(u,[]).get(u)).push(o)}for(const o of l.values()){const u=[];for(const a of o.filter(h=>h.comparison.family==="sector")){const h=Ue(a,i.sectorCount);if(h)for(const g of o.filter(p=>p.comparison.family==="quadrant")){const p=Ue(g,i.sectorCount);if(!p)continue;let d=0;for(const w of h)p.has(w)&&d++;const y=h.size+p.size-d;d/y>=Et-1e-9&&u.push({sector:a,quadrant:g,overlap:d/y})}}u.sort((a,h)=>h.overlap-a.overlap);for(const{sector:a,quadrant:h}of u){if(f.has(a.id)||f.has(h.id))continue;const g=(h.stats.pValue??1)<(a.stats.pValue??1),[p,d]=g?[h,a]:[a,h];c(p,d),f.add(d.id),p.absorbedIds=[...p.absorbedIds??[],...d.absorbedIds??[]]}}}export function analyzeWaferMap(e,i={}){const s=U,n=J,t=[];U=t,J=void 0;try{const c=Tt(e,i);return He.set(c,{candidates:t,facts:J}),c}finally{U=s,J=n}}function Tt(e,i){const{resolved:s,warnings:n}=resolveOptions(i),t=Ge(e),c={...s,passBins:t.passBins??I.passBins,ringCount:t.ringCount??I.ringCount},r=t.isLotStack,f=t.aggrMethod,l=!r||f==="mode"||f==="countBin"||f==="percent",o=t.dies.filter(m=>yt(m)),u=o.filter(Fe),a=bt(c,o.length);a.softPassBins=Me(o,a.passBins).soft;const h=t.dies.filter(m=>de(m)),g=h.filter(Fe),p=it(g,t.wafer,a.ringCount),d=tt(g,t.wafer,a.ringCount),y=nt(g,t.reticleConfig),w=ot(h,!1),k=st(g,t.wafer,a.sectorCount),S=[];l&&S.push(...ee(o,p,a.passBins,a),...ee(o,d,a.passBins,a),...ee(o,y,a.passBins,a),...ee(o,w,a.passBins,a),...ee(o,k,a.passBins,a)),l&&S.push(...E(o,p,"hard",t.hbinDefs,"hardBin",a),...E(o,d,"hard",t.hbinDefs,"hardBin",a),...E(o,y,"hard",t.hbinDefs,"hardBin",a),...E(o,w,"hard",t.hbinDefs,"hardBin",a),...E(o,k,"hard",t.hbinDefs,"hardBin",a));{const m=o.filter(R=>R.sbin!==void 0);S.push(...E(m,p,"soft",t.sbinDefs,"softBin",a),...E(m,d,"soft",t.sbinDefs,"softBin",a),...E(m,y,"soft",t.sbinDefs,"softBin",a),...E(m,w,"soft",t.sbinDefs,"softBin",a),...E(m,k,"soft",t.sbinDefs,"softBin",a))}const $=[...n];let N;if(a.enableTestValueAnalysis){const m=ne(o,p,t.testDefs,a),R=ne(o,d,t.testDefs,a),P=ne(o,y,t.testDefs,a),B=ne(o,w,t.testDefs,a),x=ne(o,k,t.testDefs,a);S.push(...m.findings,...R.findings,...P.findings,...B.findings,...x.findings),m.warning&&$.push(m.warning),N=m.activeTestNumbers,S.push(...Bt(o,[p,d,y,w,k],t.testDefs,a)),S.push(...te(o,p,t.testDefs,a),...te(o,d,t.testDefs,a),...te(o,y,t.testDefs,a),...te(o,w,t.testDefs,a),...te(o,k,t.testDefs,a))}{const m=St(r,l,t.testDefs);(!r||l||m!==void 0)&&S.push(...ft(u,t.wafer,{...a,isFailingDie:m}))}const F=Ct(S,{eligibleDies:o,softEligibleDies:o.filter(m=>m.sbin!==void 0),testDies:o,passBins:a.passBins,ringCount:a.ringCount,sectorCount:a.sectorCount,regionDies:{ring:new Set(p.flatMap(m=>W(m.dies))),quadrant:new Set(d.flatMap(m=>W(m.dies))),sector:new Set(k.flatMap(m=>W(m.dies)))},hbinDefs:t.hbinDefs,sbinDefs:t.sbinDefs,testDefs:t.testDefs});S.length=0,S.push(...F);let b;if(l){const m=ct(u,t.wafer,{passBins:a.passBins,ringCount:a.ringCount});if(b=m??void 0,m!==null&&m.pattern!=="random"&&m.pattern!=="none"){const R=dt[m.pattern],P=m.confidence==="high"?"unusual":m.confidence==="medium"?"notable":"info",B=m.features,x=K=>`${(K*100).toFixed(0)}%`,V=m.pattern==="edge-ring"||m.pattern==="edge-local"?`${x(B.edgeRdd)} of edge dies failing`:m.pattern==="center"||m.pattern==="donut"?`centroid at ${x(B.centroidDistNorm)} of wafer radius from centre`:m.pattern==="scratch"?`linear score ${B.linearScore.toFixed(2)}, eccentricity ${B.eccentricity.toFixed(2)}`:`${x(B.globalRdd)} of dies failing`,L=new Set(a.passBins),q=o.filter(K=>ut(K,L)===!0),H=claimForPattern([m.pattern],P,S,K=>ie(K).map(A=>_(A).ring).filter(A=>A!==void 0),a.ringCount);S.push({id:`spatial-pattern:${m.pattern}`,level:"wafer",severity:P,variable:{kind:"spatialPattern",label:R},comparison:{family:"spatial-pattern",left:R,right:"Wafer"},effect:{direction:"different",effectSize:B.globalRdd},stats:{method:"geometry",sampleSizeLeft:q.length,sampleSizeRight:o.length},summary:`Spatial pattern: ${R.toLowerCase()} (${m.confidence} confidence) \u2014 ${V}${m.note?` [${m.note}]`:""}`,highlight:{kind:"dies",dieKeys:q.map(K=>Ne(K))},relatedIds:H.length>0?H:void 0})}}S.sort((m,R)=>{const P=m.stats.adjustedPValue??m.stats.pValue??1,B=R.stats.adjustedPValue??R.stats.pValue??1;return P!==B?P-B:Math.abs(R.effect.absoluteDelta??0)-Math.abs(m.effect.absoluteDelta??0)});const v=wt(t.dies,o.length,t.yield.yieldPercent);r&&(v.isLotStack=!0,f&&(v.aggregationMethod=f));const M=vt(t.dies,t.testDefs);M&&(v.testSpecYield=M);const z=computeFunctionalYield(t.dies,t.testDefs);if(z&&(v.functionalYield=z),b&&(v.spatialPattern=b),N===void 0&&a.computePerTestStats){const m=_e(o,a.testNumbers,t.testDefs);m.warning&&$.push(m.warning),N=m.testNumbers}if(N?.length){const m=$t(t.dies,N,t.testDefs,a.minimumSampleSize);m&&(v.perTestStats=m);const R=Xe([{dies:t.dies}],t.testDefs,N);R&&(v.capability=R)}Object.assign(v,Ze(t.dies,t.testDefs));const D=Je([{dies:t.dies,wafer:t.wafer,passBins:a.passBins,ringCount:a.ringCount}]);D&&(v.regionYield=D),$.length>0&&(v.warnings=$);const C=redundancyFacts(o,a.passBins,a.sectorCount);return collapseRedundantFindings(S,C),U&&(J=C),{level:"wafer",hasNotableFindings:S.some(m=>m.severity==="notable"||m.severity==="unusual"),findings:S,wafer:t.wafer.metadata??void 0,stats:v}}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Die } from '../core/dies.js';
|
|
2
|
+
import type { BinDef } from '../renderer/buildWaferMap.js';
|
|
3
|
+
export type BinMode = 'hard' | 'soft';
|
|
4
|
+
type Counts = Readonly<Record<number, number>>;
|
|
5
|
+
/**
|
|
6
|
+
* Dies per bin. `precomputed` (`StatsSummary.stats.hardBinCounts`/`softBinCounts`, already scoped to the
|
|
7
|
+
* yield-eligible population) is used directly when supplied; otherwise the dies are walked, skipping
|
|
8
|
+
* partial and edge-excluded ones, the same population the yield uses.
|
|
9
|
+
*/
|
|
10
|
+
export declare function binCountsFrom(dies: Iterable<Die>, mode: BinMode, precomputed?: Counts): Map<number, number>;
|
|
11
|
+
/** The same for several wafers: each wafer's own counts summed when every one has them, the pooled dies walked otherwise. */
|
|
12
|
+
export declare function pooledBinCounts(summaries: ReadonlyArray<{
|
|
13
|
+
stats: {
|
|
14
|
+
hardBinCounts?: Counts;
|
|
15
|
+
softBinCounts?: Counts;
|
|
16
|
+
};
|
|
17
|
+
}> | undefined, dies: Iterable<Die>, mode: BinMode): Map<number, number>;
|
|
18
|
+
export interface BinRow {
|
|
19
|
+
bin: number;
|
|
20
|
+
/** The definition's name, when there is one. */
|
|
21
|
+
name?: string;
|
|
22
|
+
/** `Bin 5 · IDDQ`, or `Bin 5` without a name. */
|
|
23
|
+
label: string;
|
|
24
|
+
count: number;
|
|
25
|
+
/** Share of the dies counted, 0–100. */
|
|
26
|
+
percent: number;
|
|
27
|
+
/** Whether this bin passes: pass bins come first, in grey. */
|
|
28
|
+
passing: boolean;
|
|
29
|
+
}
|
|
30
|
+
/** One row per bin in the shared display order: pass bins first, then fail bins by descending count. */
|
|
31
|
+
export declare function binBreakdownRows(counts: ReadonlyMap<number, number>, defs: readonly BinDef[] | undefined,
|
|
32
|
+
/** Bins of THIS type that pass — `BinColors.pass.hard`/`.soft`, never `passBins` for soft bins. */
|
|
33
|
+
passing: Iterable<number>): BinRow[];
|
|
34
|
+
/** The title carries the population, so the percentages below are never a bare number: they are % of dies, not a mean of wafers. */
|
|
35
|
+
export declare const binBreakdownTitle: (mode: BinMode, total: number) => string;
|
|
36
|
+
export declare const totalOf: (counts: ReadonlyMap<number, number>) => number;
|
|
37
|
+
export {};
|
|
38
|
+
//# sourceMappingURL=binRows.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{sortBinsForDisplay as u}from"./binPareto.js";export function binCountsFrom(e,r,s){const o=new Map;if(s){for(const[n,i]of Object.entries(s))o.set(Number(n),i);return o}for(const n of e){if(n.partial||n.edgeExcluded)continue;const i=r==="hard"?n.hbin:n.sbin;i!=null&&o.set(i,(o.get(i)??0)+1)}return o}export function pooledBinCounts(e,r,s){const o=s==="hard"?"hardBinCounts":"softBinCounts";if(e?.length&&e.every(n=>n.stats[o]!==void 0)){const n=new Map;for(const i of e)for(const[t,a]of Object.entries(i.stats[o]))n.set(Number(t),(n.get(Number(t))??0)+a);return n}return binCountsFrom(r,s)}export function binBreakdownRows(e,r,s){const o=new Set(s),n=[...e.values()].reduce((t,a)=>t+a,0),i=r?new Map(r.map(t=>[t.bin,t.name])):void 0;return u(e.entries(),o).map(([t,a])=>{const c=i?.get(t);return{bin:t,...c?{name:c}:{},label:c?`Bin ${t} \xB7 ${c}`:`Bin ${t}`,count:a,percent:n?a/n*100:0,passing:o.has(t)}})}export const binBreakdownTitle=(e,r)=>`${e==="hard"?"Hard":"Soft"} Bin Breakdown \u2014 % of dies (N=${r.toLocaleString("en-GB")})`,totalOf=e=>[...e.values()].reduce((r,s)=>r+s,0);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{isYieldEligibleDie as
|
|
1
|
+
import{isYieldEligibleDie as N}from"../core/dies.js";import{testValue as d}from"../core/dieTable.js";import{fiveNumberSummary as p}from"./math.js";export function buildTestBoxplotData(u,r){return u.map((o,m)=>{const i=o.label??`#${m}`,a=o.testStats?.find(t=>t.testNumber===r);if(a){const{min:t,q1:e,median:s,q3:f,max:l,count:c}=a;return{label:i,min:t,q1:e,median:s,q3:f,max:l,count:c}}const n=[];for(const t of o.dies??[]){if(!N(t))continue;const e=d(t,r);e!==void 0&&Number.isFinite(e)&&n.push(e)}return n.length===0?{label:i,min:NaN,q1:NaN,median:NaN,q3:NaN,max:NaN,count:0}:{label:i,...p(Float64Array.from(n)),count:n.length}})}
|
|
@@ -64,9 +64,9 @@ export declare function buildCapabilityData(items: CapabilityItem[], testDefs: T
|
|
|
64
64
|
/**
|
|
65
65
|
* @internal {@link buildCapabilityData} as a {@link Chunked} computation, for a
|
|
66
66
|
* caller rendering on the main thread — a lot-sized call walks every die of
|
|
67
|
-
* every wafer once per test and then
|
|
68
|
-
* seconds of uninterruptible work at lot scale. Steps are one slice of
|
|
69
|
-
* pass, or one test's
|
|
67
|
+
* every wafer once per test and then finds the quartiles of every test's values,
|
|
68
|
+
* which is seconds of uninterruptible work at lot scale. Steps are one slice of
|
|
69
|
+
* the die pass, or one test's quartiles. Same result, same order; the synchronous entry
|
|
70
70
|
* above is this function drained.
|
|
71
71
|
*/
|
|
72
72
|
export declare function buildCapabilityDataSteps(items: CapabilityItem[], testDefs: TestDef[]): Chunked<CapabilityDatum[]>;
|
|
@@ -81,9 +81,9 @@ export interface PooledTestStats {
|
|
|
81
81
|
stats: Map<number, DescriptiveStats>;
|
|
82
82
|
/**
|
|
83
83
|
* Per test, how many pooled values fell outside the test's own spec limits —
|
|
84
|
-
* the spec-yield tally, counted off the
|
|
84
|
+
* the spec-yield tally, counted off the values this pass already held.
|
|
85
85
|
* Only tests carrying at least one limit appear. `fail` is
|
|
86
|
-
* `
|
|
86
|
+
* `countOutOfSpec` — the per-die `classifySpec` rule, counted.
|
|
87
87
|
*
|
|
88
88
|
* This is here rather than the sorted arrays themselves because the result is
|
|
89
89
|
* memoised: a tally is a handful of numbers per test, whereas the values are
|
|
@@ -109,8 +109,8 @@ export declare function clearPooledTestStatsCache(): void;
|
|
|
109
109
|
* third time inside `buildCapabilityData` for the Ppk column beside them — all
|
|
110
110
|
* reading the same values off the same dies and all of them O(dies × tests).
|
|
111
111
|
* On a 50-wafer lot of 4,000 dies × 100 tests that was 40 s of a 41 s panel
|
|
112
|
-
* render. One pass collects the values, one
|
|
113
|
-
*
|
|
112
|
+
* render. One pass collects the values, one quartile selection per test serves
|
|
113
|
+
* both the table and the chart, and the spec tally counts the same values.
|
|
114
114
|
*
|
|
115
115
|
* {@link Chunked}: one step per slice of the die pass, one per test thereafter.
|
|
116
116
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{isYieldEligibleDie as
|
|
1
|
+
import{isYieldEligibleDie as B}from"../core/dies.js";import{testValuesReader as D}from"../core/dieTable.js";import{drain as q}from"../core/utils.js";import{isParametricTest as v}from"../renderer/buildWaferMap.js";import{countOutOfSpec as H,hasSpecLimits as E}from"../renderer/spec.js";import{testLabel as P,derivedFields as V}from"../renderer/testLabel.js";import{describeValues as _}from"./math.js";const I=25e4;function z(n){if(n.specLow!==void 0&&n.specHigh!==void 0&&n.specHigh>n.specLow)return{lsl:n.specLow,usl:n.specHigh,basis:"spec"};if(n.limitLow!==void 0&&n.limitHigh!==void 0&&n.limitHigh>n.limitLow)return{lsl:n.limitLow,usl:n.limitHigh,basis:"test"}}function x(n){const s=new Map,e=new Map;for(const l of n){const t=l.testNumber;if(t===void 0||!v(l))continue;e.set(t,l);const i=z(l);i&&s.set(t,i)}return{defByTestNumber:e,specByTest:s}}function*C(n,s,e){const l=[...s.keys()],t=l.length,i=new Float64Array(t),r=new Float64Array(t),o=new Float64Array(t),f=new Float64Array(t),p=new Float64Array(t),N=new Float64Array(t),w=new Float64Array(t),m=new Float64Array(t);let d=0;if(e)for(const a of n)d+=a.dies?.length??0;const M=e?Array.from({length:t},()=>new Float64Array(d)):[],S=D(l),h=new Float64Array(t),b=Math.max(500,Math.floor(I/Math.max(1,t)));let F=0;for(const a of n){N.fill(0),w.fill(0),m.fill(0);for(const u of a.dies??[])if(++F>=b&&(F=0,yield),!!B(u)){S(u,h);for(let c=0;c<t;c++){const g=h[c];Number.isFinite(g)&&(i[c]++,r[c]+=g,o[c]+=g*g,N[c]++,w[c]+=g,m[c]+=g*g,e&&(M[c][i[c]-1]=g))}}for(let u=0;u<t;u++){const c=N[u];if(c<2)continue;const g=w[u]/c,$=(m[u]-c*g*g)/(c-1);f[u]+=(c-1)*Math.max(0,$),p[u]+=c-1}}const y=new Map;for(let a=0;a<t;a++){if(i[a]===0)continue;const u=l[a];y.set(u,{n:i[a],sum:r[a],sumSq:o[a],withinNumerator:f[a],withinDenominator:p[a]}),e&&e.set(u,M[a].subarray(0,i[a]))}return y}function R(n,s,e){return q(C(n,s,e))}function A(n,s,e,l){const{n:t,sum:i,sumSq:r,withinNumerator:o,withinDenominator:f}=l,p=i/t,N=t>=2?Math.max(0,(r-t*p*p)/(t-1)):0,w=Math.sqrt(N),m=f>0?Math.sqrt(o/f):NaN;let d=null,M=null,S=null,h=null;if(e){const{lsl:b,usl:F}=e,y=F-b;S=w>0?y/(6*w):null,h=w>0?Math.min((F-p)/(3*w),(p-b)/(3*w)):null,d=Number.isFinite(m)&&m>0?y/(6*m):null,M=Number.isFinite(m)&&m>0?Math.min((F-p)/(3*m),(p-b)/(3*m)):null}return{testNumber:n,label:P(s,n),unit:s.unit,...V(s),hasSpec:e!==void 0,lsl:e?.lsl,usl:e?.usl,...e?{limitBasis:e.basis}:{},mean:p,stdOverall:w,stdWithin:m,n:t,cp:d,cpk:M,pp:S,ppk:h}}function L(n){return n.sort((s,e)=>s.hasSpec!==e.hasSpec?s.hasSpec?-1:1:s.hasSpec?s.ppk===null&&e.ppk===null?0:s.ppk===null?1:e.ppk===null?-1:s.ppk-e.ppk:e.stdOverall-s.stdOverall)}export function buildCapabilityData(n,s){return q(buildCapabilityDataSteps(n,s))}export function*buildCapabilityDataSteps(n,s){return(yield*pooledTestStatsSteps(n,s)).capability.slice()}const T=new Map,j=4,O=new WeakMap;let W=1;function Y(n,s){const e=[];for(const t of n){const i=t.dies;if(!i){e.push(0);continue}let r=O.get(i);r===void 0&&(r=W++,O.set(i,r)),e.push(r)}const l=s.filter(t=>t.testNumber!==void 0&&v(t)).map(t=>`${t.testNumber}${t.testType??"P"}${t.limitLow??""}${t.limitHigh??""}${t.specLow??""}${t.specHigh??""}${t.name??""}${t.unit??""}`);return`${e.join(",")}${l.join("")}`}export function clearPooledTestStatsCache(){T.clear();for(const n of k.values())n.work.return(void 0);k.clear()}export function*pooledTestStatsSteps(n,s){const e=Y(n,s),l=T.get(e);if(l)return T.delete(e),T.set(e,l),l;let t=k.get(e);t||(t={work:K(n,s,e),callers:0},k.set(e,t));const i=t;i.callers++;try{for(;;){if(i.result)return i.result;const r=i.work.next();if(r.done)return i.result=r.value,k.delete(e),r.value;yield}}finally{i.callers--,!i.result&&i.callers===0&&(i.work.return(void 0),k.get(e)===i&&k.delete(e))}}const k=new Map;function*K(n,s,e){const l={stats:new Map,specTally:new Map,capability:[]},{defByTestNumber:t,specByTest:i}=x(s);if(t.size===0)return l;const r=new Map,o=yield*C(n,t,r),f=new Map,p=new Map,N=[];for(const m of s){yield;const d=m.testNumber;if(d===void 0)continue;const M=o.get(d);if(!M||M.n===0||!t.has(d))continue;const S=r.get(d);r.delete(d),E(m)&&p.set(d,{n:S.length,fail:H(S,m)});const h=_(S);f.set(d,h);const b=i.get(d),F=A(d,m,b,M);let y;if(b){const a=b.usl-b.lsl;y=u=>(u-b.lsl)/a}else{const a=h.min,u=h.max-a;y=u>0?c=>(c-a)/u:()=>.5}N.push({...F,min:y(h.min),q1:y(h.q1),median:y(h.median),q3:y(h.q3),max:y(h.max)})}const w={stats:f,specTally:p,capability:L(N)};return T.set(e,w),T.size>j&&T.delete(T.keys().next().value),w}export function buildCapabilityFigures(n,s){const{defByTestNumber:e,specByTest:l}=x(s);if(e.size===0)return[];const t=R(n,e,null),i=[];for(const[r,o]of t)o.n!==0&&i.push(A(r,e.get(r),l.get(r),o));return L(i)}export function poolCapabilityFigures(n,s){const{defByTestNumber:e,specByTest:l}=x(s),t=new Map;for(const r of n)for(const o of r){if(!e.has(o.testNumber)||o.n===0)continue;let f=t.get(o.testNumber);f||(f={n:0,sum:0,sumSq:0,withinNumerator:0,withinDenominator:0},t.set(o.testNumber,f));const p=o.stdOverall*o.stdOverall;f.n+=o.n,f.sum+=o.n*o.mean,f.sumSq+=(o.n-1)*p+o.n*o.mean*o.mean,o.n>=2&&(f.withinNumerator+=(o.n-1)*p,f.withinDenominator+=o.n-1)}const i=[];for(const[r,o]of t)i.push(A(r,e.get(r),l.get(r),o));return L(i)}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{normalCdf as
|
|
1
|
+
import{normalCdf as ot}from"./math.js";import{getDieKey as ct,gridKey as N}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 y=(n-.5-r)/f;return 1-ot(y)}export function buildClusterFindings(n,s,i){const{passBins:r,significanceLevel:f,minimumEffectSize:y,minimumRelativeEffect:q,minimumSampleSize:W,minimumClusterSize:O,ringCount:T}=i,_=new Set(r);if(n.length<W)return[];const G=e=>{const a=e.hbin??e.sbin;return a===void 0||!_.has(a)},H=i.isFailingDie??G,X=n.filter(H),u=X.length/n.length,g=n[0],I=g?g.width:10,B=g?g.height:10,S=Math.max(I,B)*1.5,M=new Map;for(const e of n)M.set(N(e.x,e.y),e);const R=Math.ceil(S/I),w=Math.ceil(S/B),J=at(X),A=[],b=s.center.x,x=s.center.y,Q=s.radius;for(const e of J){if(e.length<O)continue;const a=new Set(e.map(t=>N(t.x,t.y)));for(const t of e)for(let c=-w;c<=w;c++)for(let v=-R;v<=R;v++){const z=N(t.x+v,t.y+c);if(a.has(z))continue;const D=M.get(z);D&&Math.hypot(D.physX-t.physX,D.physY-t.physY)<=S&&a.add(z)}const m=[...a].map(t=>M.get(t)).filter(Boolean),o=e.length,E=m.length;if(E<W)continue;const $=o/E,h=$-u,p=ut(o,E,u),d=u>0?h/u:void 0,U=h>=y||d!==void 0&&d>=q;if(p>f||!U)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 C=e.map(t=>(Math.atan2(t.physY-x,t.physX-b)+2*Math.PI)%(2*Math.PI));let F=C[0],P=C[0];for(const t of C)F=Math.min(F,t),P=Math.max(P,t);const Z=(P-F)*180/Math.PI,tt=1-1/T,Y=e.filter(t=>Math.hypot(t.physX-b,t.physY-x)/Q>tt).length>o/2&&Z<120,L=Y?"edge-arc":"cluster",j=ft(K-b,V-x),et=Y?`Edge arc ~${j}`:`Cluster at (${l.x}, ${l.y})`,nt=o/n.length,st=lt(p,h,nt,d),it=e.map(t=>ct(t));A.push({id:`${L}:${l.x},${l.y}`,level:"wafer",severity:st,variable:{kind:"yield",label:"Yield"},comparison:{family:L,left:et,right:"Rest of wafer"},effect:{direction:"lower",absoluteDelta:-h,relativeDelta:d!==void 0?-d:void 0,effectSize:h},stats:{method:"binomial",pValue:p,adjustedPValue:p,sampleSizeLeft:o,sampleSizeRight:n.length-o},summary:Y?`Edge arc near ${j}: ${o} contiguous failing dies (${($*100).toFixed(0)}% vs ${(u*100).toFixed(0)}% background)`:`Failure cluster at (${l.x}, ${l.y}): ${o} contiguous failing dies (${($*100).toFixed(0)}% vs ${(u*100).toFixed(0)}% background)`,highlight:{kind:"dies",dieKeys:it}})}return A.sort((e,a)=>{const m=e.stats.pValue??1,o=a.stats.pValue??1;return m!==o?m-o:Math.abs(a.effect.effectSize??0)-Math.abs(e.effect.effectSize??0)})}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{gridKey as i}from"../core/dies.js";export function findConnectedComponents(s){if(s.length===0)return[];const u=new Map;for(const n of s)u.set(i(n.x,n.y),n);const e=new Set,f=[];for(const n of s){const p=i(n.x,n.y);if(e.has(p))continue;const y=[],c=[n];for(e.add(p);c.length>0;){const d=c.pop();y.push(d);for(let t=-1;t<=1;t++)for(let o=-1;o<=1;o++){if(o===0&&t===0)continue;const r=i(d.x+o,d.y+t);if(e.has(r))continue;const h=u.get(r);h&&(e.add(r),c.push(h))}}f.push(y)}return f}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{dieHasValues as X,testValue as Y}from"../core/dieTable.js";import{isParametricTest as v}from"../renderer/buildWaferMap.js";import{markedTestLabel as P,derivedFields as L}from"../renderer/testLabel.js";export function pearsonFromSums(e,r,a,i,c,d){if(e<3)return null;const m=r/e,f=a/e,s=d/e-m*f,h=i/e-m*m,w=c/e-f*f,I=Math.sqrt(h*w);return I===0?null:Math.max(-1,Math.min(1,s/I))}export function pearsonOfPairs(e){let r=0,a=0,i=0,c=0,d=0,m=0;for(let f=0;f<e.length;f++){const{x:s,y:h}=e[f];!Number.isFinite(s)||!Number.isFinite(h)||(r++,a+=s,i+=h,c+=s*s,d+=h*h,m+=s*h)}return{r:pearsonFromSums(r,a,i,c,d,m),n:r}}export const CORRELATION_DIE_BUDGET=25e3;function S(e,r){const a=[],i=e/r;for(let c=0;c<r;c++)a.push(Math.floor(c*i));return a}export function correlationSampleNote(e){if(e)return`From a ${e.used.toLocaleString()}-die sample of ${e.of.toLocaleString()}, spread evenly across the lot`}function z(e){return e.filter(r=>r.testNumber!==void 0&&v(r)).map(r=>({testNumber:r.testNumber,label:P(r,r.testNumber),unit:r.unit,...L(r)}))}export function buildCorrelationMatrix(e,r){const a=z(r);if(a.length<2)return{tests:a,cells:[]};const i=a.length,c=a.map(n=>n.testNumber),d=i*(i-1)/2,m=new Float64Array(d),f=new Float64Array(i),s=new Float64Array(d),h=new Float64Array(d),w=new Float64Array(d),I=new Float64Array(d),A=new Float64Array(d);function R(n,o){return n*i-(n*(n+1)>>1)+(o-n-1)}const g=e.filter(X),t=g.length>CORRELATION_DIE_BUDGET,l=t?S(g.length,CORRELATION_DIE_BUDGET).map(n=>g[n]):g,u=new Float64Array(i),x=new Uint8Array(i);for(const n of l){x.fill(0);for(let o=0;o<i;o++){const b=Y(n,c[o]);b!==void 0&&Number.isFinite(b)&&(u[o]=b,x[o]=1)}for(let o=0;o<i;o++){if(!x[o])continue;f[o]++;const b=u[o];for(let F=o+1;F<i;F++){if(!x[F])continue;const M=u[F],N=R(o,F);m[N]++,s[N]+=b,h[N]+=M,w[N]+=b*b,I[N]+=M*M,A[N]+=b*M}}}const y=n=>pearsonFromSums(m[n],s[n],h[n],w[n],I[n],A[n]),p=[];for(let n=0;n<i;n++)for(let o=0;o<i;o++){if(o===n){p.push({xIndex:o,yIndex:n,r:1,n:f[o]});continue}const b=Math.min(o,n),F=Math.max(o,n),M=R(b,F);p.push({xIndex:o,yIndex:n,r:y(M),n:m[M]})}return t?{tests:a,cells:p,sample:{of:g.length,used:l.length}}:{tests:a,cells:p}}export function filterCorrelationMatrix(e,{threshold:r=.3,minTests:a=6,maxTests:i=20}={}){const c=[];let d=0,m=0,f=null;for(const t of e.cells){if(t.xIndex>=t.yIndex||t.r===null)continue;const l=Math.abs(t.r);c.push({xi:t.xIndex,yi:t.yIndex,absR:l}),(f===null||l>Math.abs(f.r))&&(f={xLabel:e.tests[t.xIndex].label,yLabel:e.tests[t.yIndex].label,r:t.r})}c.sort((t,l)=>l.absR-t.absR);const s=new Set;for(const{xi:t,yi:l,absR:u}of c){const x=u<r;s.size>=i||x&&s.size>=a||(s.add(t),s.size<i&&s.add(l))}if(s.size<Math.min(a,e.tests.length))for(let t=0;t<e.tests.length&&s.size<Math.min(i,a,e.tests.length);t++)s.add(t);const h=(()=>{const t=Array.from(s),l=new Map,u=new Map;for(const{xi:y,yi:p,absR:n}of c)!s.has(y)||!s.has(p)||(l.set(y,(l.get(y)??0)+n),l.set(p,(l.get(p)??0)+n),u.set(y,(u.get(y)??0)+1),u.set(p,(u.get(p)??0)+1));const x=y=>(u.get(y)??0)>0?l.get(y)/u.get(y):0;return t.sort((y,p)=>x(p)-x(y))})(),w=h.map(t=>e.tests[t]),I=new Map(h.map((t,l)=>[t,l])),A=new Set(w.map(t=>t.testNumber)),R=e.cells.filter(t=>A.has(e.tests[t.xIndex].testNumber)&&A.has(e.tests[t.yIndex].testNumber)).map(t=>({xIndex:I.get(t.xIndex),yIndex:I.get(t.yIndex),r:t.r,n:t.n}));let g=0;for(const{xi:t,yi:l,absR:u}of c)s.has(t)&&s.has(l)?u>=.7?d++:u>=.4&&m++:u<r&&g++;return{matrix:{tests:w,cells:R},strongPairs:d,moderatePairs:m,hiddenWeakPairs:g,strongestPair:f}}
|
|
@@ -24,4 +24,34 @@ export declare function visibleFindings<T extends {
|
|
|
24
24
|
id: string;
|
|
25
25
|
absorbedIds?: string[];
|
|
26
26
|
}>(findings: T[]): T[];
|
|
27
|
+
/**
|
|
28
|
+
* What makes two findings "the same pattern" on different wafers. One key for
|
|
29
|
+
* the lot's wafer count and the gallery's per-card highlight of a lot finding.
|
|
30
|
+
*/
|
|
31
|
+
export declare function findingPatternKey(f: StatsFinding): string;
|
|
32
|
+
/** A findings list as the reader sees it: patterns with the findings they explain, then the rest grouped by region. */
|
|
33
|
+
export interface ArrangedFindings {
|
|
34
|
+
/** Spatial-pattern findings, each with the findings in the list it names as supporting detail. */
|
|
35
|
+
patterns: Array<{
|
|
36
|
+
finding: StatsFinding;
|
|
37
|
+
children: StatsFinding[];
|
|
38
|
+
}>;
|
|
39
|
+
/** Everything else grouped by region (`family` and `left`), the most severe group first and within it too. */
|
|
40
|
+
groups: Array<{
|
|
41
|
+
family: string;
|
|
42
|
+
left: string;
|
|
43
|
+
worst: StatsSeverity;
|
|
44
|
+
findings: StatsFinding[];
|
|
45
|
+
}>;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The one arrangement of a findings list, read by the Summary panel and both HTML reports, so a
|
|
49
|
+
* finding sits under the same heading and in the same order on every surface.
|
|
50
|
+
*
|
|
51
|
+
* A finding a pattern lists as supporting detail, or that another finding absorbed as an exact
|
|
52
|
+
* restatement, is not repeated among the groups: the first is shown under its pattern, the second
|
|
53
|
+
* is not shown (it stays in `summary.findings` for a host reading them programmatically). Not every
|
|
54
|
+
* finding's `relatedIds`: a run-merge records the constituents it replaced there, and those are gone.
|
|
55
|
+
*/
|
|
56
|
+
export declare function arrangeFindings(findings: readonly StatsFinding[]): ArrangedFindings;
|
|
27
57
|
//# sourceMappingURL=filterFindings.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
function s(
|
|
1
|
+
function s(e){return Array.isArray(e)?e:[e]}export function filterFindings(e,i){let t=e.findings;if(i.severity!==void 0){const r=new Set(s(i.severity));t=t.filter(a=>r.has(a.severity))}if(i.kind!==void 0){const r=new Set(s(i.kind));t=t.filter(a=>r.has(a.variable.kind))}if(i.family!==void 0){const r=new Set(s(i.family));t=t.filter(a=>r.has(a.comparison.family))}if(i.level!==void 0){const r=new Set(s(i.level));t=t.filter(a=>r.has(a.level))}return t}export function visibleFindings(e){const i=new Set(e.flatMap(t=>t.absorbedIds??[]));return e.filter(t=>!i.has(t.id))}export function findingPatternKey(e){return[e.variable.kind,e.variable.index??"",e.variable.bin??"",e.comparison.family,e.comparison.left,e.effect.direction].join("|")}const l={unusual:0,notable:1,info:2};export function arrangeFindings(e){const i=e.filter(n=>n.comparison.family==="spatial-pattern"),t=new Set([...i.flatMap(n=>n.relatedIds??[]),...e.flatMap(n=>n.absorbedIds??[])]),r=i.map(n=>({finding:n,children:e.filter(o=>n.relatedIds?.includes(o.id))})),a=new Map;for(const n of e){if(n.comparison.family==="spatial-pattern"||t.has(n.id))continue;const o=`${n.comparison.family}\0${n.comparison.left}`;a.set(o,[...a.get(o)??[],n])}const d=[...a.entries()].map(([n,o])=>{const c=[...o].sort((y,m)=>l[y.severity]-l[m.severity]),[f,p]=n.split("\0");return{family:f,left:p,worst:c[0].severity,findings:c}});return d.sort((n,o)=>l[n.worst]-l[o.worst]),{patterns:r,groups:d}}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { StatsFinding } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* A finding's hover text, for the Summary panel and both HTML reports alike —
|
|
4
|
+
* the one rule for it. The sentence already carries the `†` for a finding about
|
|
5
|
+
* a derived test; the tooltip adds the words for it and the expression, on a
|
|
6
|
+
* line of their own.
|
|
7
|
+
*/
|
|
8
|
+
export declare function formatFindingTooltip(finding: StatsFinding): string;
|
|
9
|
+
//# sourceMappingURL=findingText.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export function collectTestValues(d,u){const t=[];for(const i of d)for(const
|
|
1
|
+
import{testValue as v}from"../core/dieTable.js";export function collectTestValues(d,u){const t=[];for(const i of d)for(const f of i.dies??[]){const o=v(f,u);o!==void 0&&Number.isFinite(o)&&t.push(o)}return t}export function testValueExtent(d,u){let t=1/0,i=-1/0;for(const f of d)for(const o of f.dies??[]){const n=v(o,u);n!==void 0&&Number.isFinite(n)&&(n<t&&(t=n),n>i&&(i=n))}return t===1/0?{min:NaN,max:NaN}:{min:t,max:i}}export function buildTestHistogramData(d,u,t=16,i,f,o){let n=collectTestValues(d,u);if(o&&(n=n.filter(e=>e>=o.lo&&e<=o.hi)),n.length===0)return[];let r=n[0],a=n[0];for(let e=1;e<n.length;e++)n[e]<r&&(r=n[e]),n[e]>a&&(a=n[e]);const l=i!==void 0?Math.min(r,i):r,M=((f!==void 0?Math.max(a,f):a)-l||1)/t,h=Array.from({length:t},(e,x)=>({rangeLow:l+x*M,rangeHigh:l+(x+1)*M,count:0}));for(const e of n){const x=Math.min(t-1,Math.floor((e-l)/M));h[x].count++}return h}export function buildTestHistogramSeries(d,u,t=16,i,f,o){const n=new Map;let r=1/0,a=-1/0;for(const s of d){const m=[];for(const y of s.items)for(const p of y.dies??[]){const c=v(p,u);if(c!==void 0&&Number.isFinite(c)){if(o&&(c<o.lo||c>o.hi))continue;m.push(c),c<r&&(r=c),c>a&&(a=c)}}n.set(s.key,m)}const l=d.map(s=>s.key).filter(s=>n.get(s).length>0);if(l.length===0)return{ranges:[],series:[]};const g=i!==void 0?Math.min(r,i):r,h=((f!==void 0?Math.max(a,f):a)-g||1)/t,e=Array.from({length:t},(s,m)=>({rangeLow:g+m*h,rangeHigh:g+(m+1)*h})),x=l.map(s=>{const m=new Array(t).fill(0);for(const y of n.get(s)){const p=Math.min(t-1,Math.floor((y-g)/h));m[p]++}return{groupKey:s,counts:m}});return{ranges:e,series:x}}
|