@wafertools/wafermap 0.30.3 → 0.31.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 +9 -19
- package/CHANGELOG.md +140 -0
- package/README.md +2 -2
- package/dist/packages/canvas-adapter/charts/boxplot.js +1 -1
- package/dist/packages/canvas-adapter/charts/capability.js +1 -1
- package/dist/packages/canvas-adapter/charts/chartShell.d.ts +89 -6
- package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
- package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
- package/dist/packages/canvas-adapter/charts/scatter.d.ts +16 -2
- package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
- package/dist/packages/canvas-adapter/charts/sweep.js +1 -1
- package/dist/packages/canvas-adapter/charts/testPassRate.js +1 -1
- package/dist/packages/canvas-adapter/charts/trend.js +1 -1
- package/dist/packages/canvas-adapter/dieList.d.ts +1 -1
- package/dist/packages/canvas-adapter/exportName.d.ts +1 -12
- package/dist/packages/canvas-adapter/exportName.js +1 -1
- package/dist/packages/canvas-adapter/index.d.ts +2 -3
- package/dist/packages/canvas-adapter/index.js +1 -1
- package/dist/packages/canvas-adapter/insightsTab.d.ts +11 -1
- package/dist/packages/canvas-adapter/insightsTab.js +1 -1
- package/dist/packages/canvas-adapter/maplessSummary.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +4 -7
- package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferMap.d.ts +15 -30
- package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
- package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
- package/dist/packages/canvas-adapter/toCanvas.js +1 -1
- package/dist/packages/canvas-adapter/toolbar.js +1 -1
- package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
- package/dist/packages/canvas-adapter/userGuideHtml.js +49 -43
- package/dist/packages/canvas-adapter/version.d.ts +2 -2
- package/dist/packages/canvas-adapter/version.js +1 -1
- package/dist/packages/canvas-adapter/warnings.js +1 -1
- package/dist/packages/core/aggregates.d.ts +0 -6
- package/dist/packages/core/aggregates.js +1 -1
- package/dist/packages/core/dies.d.ts +1 -3
- package/dist/packages/core/dies.js +1 -1
- package/dist/packages/core/index.d.ts +8 -10
- package/dist/packages/core/index.js +1 -1
- package/dist/packages/core/stdf.d.ts +9 -0
- package/dist/packages/core/stdf.js +1 -0
- package/dist/packages/core/transforms.d.ts +0 -23
- package/dist/packages/core/transforms.js +1 -1
- package/dist/packages/renderer/binColors.js +1 -1
- package/dist/packages/renderer/buildView.d.ts +1 -9
- package/dist/packages/renderer/buildView.js +1 -1
- package/dist/packages/renderer/buildWaferMap.d.ts +54 -34
- package/dist/packages/renderer/buildWaferMap.js +1 -1
- package/dist/packages/renderer/colorMap.d.ts +0 -2
- package/dist/packages/renderer/colorMap.js +1 -1
- package/dist/packages/renderer/deprecate.d.ts +1 -1
- package/dist/packages/renderer/deprecate.js +1 -1
- package/dist/packages/renderer/derivedTests/apply.js +1 -1
- package/dist/packages/renderer/derivedTests/parser.js +1 -1
- package/dist/packages/renderer/index.d.ts +6 -5
- package/dist/packages/renderer/index.js +1 -1
- package/dist/packages/renderer/spec.d.ts +17 -0
- package/dist/packages/renderer/spec.js +1 -1
- package/dist/packages/stats/analyzeWaferMap.js +1 -1
- package/dist/packages/stats/capability.d.ts +4 -2
- package/dist/packages/stats/capability.js +1 -1
- package/dist/packages/stats/clusterDetection.d.ts +2 -1
- package/dist/packages/stats/correlation.d.ts +1 -1
- package/dist/packages/stats/facets.d.ts +1 -1
- package/dist/packages/stats/index.d.ts +6 -16
- package/dist/packages/stats/index.js +1 -1
- package/dist/packages/stats/mergeTestDefs.d.ts +1 -1
- package/dist/packages/stats/mergeTestDefs.js +1 -1
- package/dist/packages/stats/patternClassification.d.ts +2 -1
- package/dist/packages/stats/regions.d.ts +2 -1
- package/dist/packages/stats/renderFindingsReport.d.ts +0 -4
- package/dist/packages/stats/renderFindingsReport.js +1 -21
- package/dist/packages/stats/renderSummaryReport.js +11 -11
- package/dist/packages/stats/sweep.d.ts +8 -0
- package/dist/packages/stats/sweep.js +1 -1
- package/dist/packages/stats/types.d.ts +9 -3
- package/package.json +2 -2
- package/dist/packages/canvas-adapter/deprecated.d.ts +0 -7
- package/dist/packages/canvas-adapter/deprecated.js +0 -1
- package/dist/packages/core/deprecated.d.ts +0 -60
- package/dist/packages/core/deprecated.js +0 -1
- package/dist/packages/renderer/deprecated.d.ts +0 -45
- package/dist/packages/renderer/deprecated.js +0 -1
- package/dist/packages/stats/deprecated.d.ts +0 -85
- package/dist/packages/stats/deprecated.js +0 -1
|
@@ -4,7 +4,7 @@ import type { Wafer } from '../core/wafer.js';
|
|
|
4
4
|
import type { Reticle } from '../core/reticle.js';
|
|
5
5
|
import { type DerivedTestDef } from './derivedTests/apply.js';
|
|
6
6
|
export type { DerivedTestDef } from './derivedTests/apply.js';
|
|
7
|
-
import { type View, type
|
|
7
|
+
import { type View, type PlotMode } from './buildView.js';
|
|
8
8
|
/**
|
|
9
9
|
* Test result for a single die position, as output by the prober.
|
|
10
10
|
* `x` and `y` are **die grid positions** (prober step coordinates) — integers
|
|
@@ -57,12 +57,19 @@ export interface DieResult {
|
|
|
57
57
|
*/
|
|
58
58
|
siteNum?: number;
|
|
59
59
|
/**
|
|
60
|
-
* STDF `
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
60
|
+
* STDF PRR `PART_ID` — the tester's part identification, text in STDF (a
|
|
61
|
+
* number is accepted too). Data about the part, not an identifier: a die's
|
|
62
|
+
* identity is its position. Its meaning is fab-specific; at many fabs it
|
|
63
|
+
* encodes the probe sequence.
|
|
64
64
|
*/
|
|
65
|
-
partId?: number;
|
|
65
|
+
partId?: number | string;
|
|
66
|
+
/**
|
|
67
|
+
* The tester marked this record as replacing an earlier one (STDF PRR
|
|
68
|
+
* `PART_FLG` bit 0/1): `'partId'` — the earlier record with the same part ID;
|
|
69
|
+
* `'position'` — the earlier record at the same position. Such a record always
|
|
70
|
+
* wins, whatever `retestPolicy` says.
|
|
71
|
+
*/
|
|
72
|
+
supersedes?: 'partId' | 'position';
|
|
66
73
|
/** Per-die metadata — all fields appear automatically in hover tooltips. See `DieMetadata → §12.4`. */
|
|
67
74
|
metadata?: DieMetadata;
|
|
68
75
|
}
|
|
@@ -104,9 +111,11 @@ export interface WaferConfig {
|
|
|
104
111
|
* clockwise. The notch/flat position is set by `notch.type` and is not
|
|
105
112
|
* affected by this value — `orientation` rotates the *die grid* on the display.
|
|
106
113
|
*
|
|
107
|
-
*
|
|
114
|
+
* One of 0 (default), 90, 180 or 270 — the angles STDF can record. An
|
|
115
|
+
* equivalent angle such as −90 is read as 270; any other is treated as
|
|
116
|
+
* missing (built at 0) and reported as `input-values-outside-stdf`.
|
|
108
117
|
*/
|
|
109
|
-
orientation?:
|
|
118
|
+
orientation?: 0 | 90 | 180 | 270;
|
|
110
119
|
metadata?: WaferMetadata;
|
|
111
120
|
/**
|
|
112
121
|
* Physical edge exclusion zone in mm measured from the wafer edge inward.
|
|
@@ -214,16 +223,28 @@ export interface TestDef {
|
|
|
214
223
|
*/
|
|
215
224
|
logScale?: boolean;
|
|
216
225
|
/**
|
|
217
|
-
* Lower
|
|
218
|
-
*
|
|
219
|
-
*
|
|
226
|
+
* Lower **test limit** (STDF `LO_LIMIT`) — the pass/fail limit, in the test
|
|
227
|
+
* value's units. A value below it is outside the test limits. Both limits are
|
|
228
|
+
* optional independently — some tests have one-sided limits.
|
|
220
229
|
*/
|
|
221
230
|
limitLow?: number;
|
|
231
|
+
/** Upper test limit (STDF `HI_LIMIT`). A value above it is outside the test limits. */
|
|
232
|
+
limitHigh?: number;
|
|
222
233
|
/**
|
|
223
|
-
*
|
|
224
|
-
*
|
|
234
|
+
* Whether a value exactly equal to `limitLow` passes. Default `true`. STDF
|
|
235
|
+
* states it per test in `PARM_FLG` bit 6, ATDF in the Limit Compare field.
|
|
225
236
|
*/
|
|
226
|
-
|
|
237
|
+
limitLowInclusive?: boolean;
|
|
238
|
+
/** Whether a value exactly equal to `limitHigh` passes. Default `true`; STDF `PARM_FLG` bit 7. */
|
|
239
|
+
limitHighInclusive?: boolean;
|
|
240
|
+
/**
|
|
241
|
+
* Lower **specification limit** (STDF `LO_SPEC`) — separate from the test limits,
|
|
242
|
+
* and what process capability (Cp/Cpk/Pp/Ppk) is judged against when both spec
|
|
243
|
+
* limits are given. Without them, capability uses the test limits and says so.
|
|
244
|
+
*/
|
|
245
|
+
specLow?: number;
|
|
246
|
+
/** Upper specification limit (STDF `HI_SPEC`). See `specLow`. */
|
|
247
|
+
specHigh?: number;
|
|
227
248
|
/**
|
|
228
249
|
* Test kind: `'P'` (parametric — a continuous measured value) or `'F'`
|
|
229
250
|
* (functional — a pass/fail outcome, conventionally recorded as 1 = pass,
|
|
@@ -335,12 +356,11 @@ export interface WaferMapInputBase {
|
|
|
335
356
|
ringCount?: number;
|
|
336
357
|
/**
|
|
337
358
|
* Wafer diameters (mm) treated as standard when sanity-checking an *inferred*
|
|
338
|
-
* diameter
|
|
339
|
-
* (100/125/150/200/300) and the measurements behind it.
|
|
359
|
+
* diameter. Default `[100, 125, 150, 200, 300]`.
|
|
340
360
|
*
|
|
341
|
-
* **Replaces** the default rather than adding to it, so
|
|
342
|
-
* `standardDiameters: [
|
|
343
|
-
* also runs 3-inch. Pass `[]` to disable the check entirely, for genuinely
|
|
361
|
+
* **Replaces** the default rather than adding to it, so list the defaults to
|
|
362
|
+
* extend it: `standardDiameters: [100, 125, 150, 200, 300, 76.2]` for a line
|
|
363
|
+
* that also runs 3-inch. Pass `[]` to disable the check entirely, for genuinely
|
|
344
364
|
* non-standard substrates (panels, reclaim, odd R&D shapes) — that is the
|
|
345
365
|
* intended opt-out, rather than suppressing every geometry advisory.
|
|
346
366
|
*
|
|
@@ -475,10 +495,6 @@ export interface WaferMapInputLayout extends WaferMapInputBase {
|
|
|
475
495
|
* ({@link WaferMapInputLayout}). Combining them is a type error.
|
|
476
496
|
*/
|
|
477
497
|
export type WaferMapInput = WaferMapInputSingle | WaferMapInputLotStack | WaferMapInputLayout;
|
|
478
|
-
/** Options forwarded to {@link buildView}. */
|
|
479
|
-
export interface WaferMapOptions extends ViewOptions {
|
|
480
|
-
debug?: boolean;
|
|
481
|
-
}
|
|
482
498
|
export interface YieldSummary {
|
|
483
499
|
/** Dies with a bin in `passBins`. */
|
|
484
500
|
passDies: number;
|
|
@@ -567,6 +583,16 @@ export interface WaferWarning {
|
|
|
567
583
|
* were given as text (or verdicts as something other than true/false), as a
|
|
568
584
|
* CSV parser produces. They are not converted, so those dies are judged and
|
|
569
585
|
* plotted wrongly — a bin of "1" is not pass bin 1. The message counts them.
|
|
586
|
+
* - `'input-values-outside-stdf'` — raised by `buildWaferMap`: bins, coordinates,
|
|
587
|
+
* test numbers, site numbers or `waferConfig.orientation` outside the STDF V4
|
|
588
|
+
* ranges, or test values that are not finite. Each is treated as missing: the
|
|
589
|
+
* bin or site is absent, the die has no position, the test is left out, the
|
|
590
|
+
* value is dropped, the map is built at orientation 0. A `NaN` bin is no bin,
|
|
591
|
+
* and is counted here too.
|
|
592
|
+
* - `'retests-by-part-id'` — raised by `buildWaferMap` (severity `'info'`): dies
|
|
593
|
+
* with no position that share a part ID were treated as retests of one die.
|
|
594
|
+
* Blank part IDs never match, and part IDs are not used on a wafer where one
|
|
595
|
+
* value covers more than 20% of the unpositioned records (a default, not an ID).
|
|
570
596
|
* - `'derived-test-invalid'` — raised by `buildWaferMap`: a `derivedTests` entry
|
|
571
597
|
* could not be compiled — a parse or type error in its `expression`, a
|
|
572
598
|
* `testNumber` that collides with a measured test, a `testType` that
|
|
@@ -586,7 +612,7 @@ export interface WaferWarning {
|
|
|
586
612
|
* The union is intentionally open to string so future advisory codes can be
|
|
587
613
|
* added without a breaking change; switch with a `default` branch.
|
|
588
614
|
*/
|
|
589
|
-
code: 'partial-coverage' | 'geometry-conflict' | 'non-standard-diameter' | 'diameter-exceeds-die-extent' | 'test-count-capped' | 'edge-exclusion-exceeds-radius' | 'analysis-option-corrected' | 'bin-colors-shared' | 'pass-bins-mixed' | 'ring-count-mixed' | 'input-field-removed' | 'input-values-not-numbers' | 'derived-test-invalid' | (string & {});
|
|
615
|
+
code: 'partial-coverage' | 'geometry-conflict' | 'non-standard-diameter' | 'diameter-exceeds-die-extent' | 'test-count-capped' | 'edge-exclusion-exceeds-radius' | 'analysis-option-corrected' | 'bin-colors-shared' | 'pass-bins-mixed' | 'ring-count-mixed' | 'input-field-removed' | 'input-values-not-numbers' | 'input-values-outside-stdf' | 'retests-by-part-id' | 'derived-test-invalid' | (string & {});
|
|
590
616
|
/** Human-readable explanation, suitable for direct display. */
|
|
591
617
|
message: string;
|
|
592
618
|
/**
|
|
@@ -622,20 +648,14 @@ export interface WaferMapResult {
|
|
|
622
648
|
metadata: import('../core/metadata.js').WaferMetadata | null;
|
|
623
649
|
/** `true` when the result was built from a `lotStack` aggregation. */
|
|
624
650
|
isLotStack: boolean;
|
|
625
|
-
/**
|
|
626
|
-
* @internal Renderer-agnostic draw list consumed by `renderWaferMap` and `toCanvas`.
|
|
627
|
-
* Not part of the public API — access the named fields on `WaferMapResult` instead.
|
|
628
|
-
*/
|
|
629
651
|
/**
|
|
630
652
|
* @internal The initial draw list. Kept on the result because `analyzeWaferMap`
|
|
631
653
|
* discriminates its input on `'view' in input`, and because `renderWaferMap`
|
|
632
|
-
* reads `dataAxisFlip` from it — not
|
|
654
|
+
* reads `dataAxisFlip` from it — not for a host to read.
|
|
633
655
|
*
|
|
634
656
|
* Read the promoted top-level fields instead (`plotMode`, `metadata`,
|
|
635
|
-
* `isLotStack`, `hbinDefs`, `sbinDefs`, `testDefs`):
|
|
636
|
-
* every option change, so anything
|
|
637
|
-
* Hosts driving the low-level pipeline should call `buildView` themselves,
|
|
638
|
-
* which is the supported way to hold a {@link View}.
|
|
657
|
+
* `isLotStack`, `hbinDefs`, `sbinDefs`, `testDefs`): the renderers rebuild the
|
|
658
|
+
* draw list on every option change, so anything cached from this goes stale.
|
|
639
659
|
*/
|
|
640
660
|
view: View;
|
|
641
661
|
/** Reticle configuration used to generate the overlay and reticle-local groupings. */
|
|
@@ -795,5 +815,5 @@ export declare function dieHasTestData(die: Pick<Die, 'testValues' | 'testPass'>
|
|
|
795
815
|
* mis-inferring to 50 mm went undetected.
|
|
796
816
|
*/
|
|
797
817
|
export declare const STANDARD_WAFER_DIAMETERS_MM: readonly number[];
|
|
798
|
-
export declare function buildWaferMap(input: DieResult[] | WaferMapInput
|
|
818
|
+
export declare function buildWaferMap(input: DieResult[] | WaferMapInput): WaferMapResult;
|
|
799
819
|
//# sourceMappingURL=buildWaferMap.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createWafer as j}from"../core/wafer.js";import{isYieldEligibleDie as ge,getDieKey as V,hasPosition as R}from"../core/dies.js";import{applyOrientation as U,transformDies as pe,clipDiesToWafer as ye}from"../core/transforms.js";import{generateDies as be}from"../core/dies.js";import{affineRotation as we,affineMirror as xe,affineCompose as De,affinePoint as Me}from"../core/transforms.js";import{inferWaferFromXY as ve}from"../core/inference/wafer.js";import{resolveGridPitch as Ce}from"../core/inference/pitch.js";import{assignGridIndices as Oe}from"../core/inference/grid.js";import{generateReticleGrid as Se}from"../core/reticle.js";import{applyDerivedTests as J}from"./derivedTests/apply.js";import{buildView as Q}from"./buildView.js";import{maxOf as K,minOf as Z,modeOf as ke}from"../core/utils.js";import{aggregateValues as ee,aggregateBinCounts as Ae}from"../core/aggregates.js";export function isParametricTest(t){return t?.testType!=="F"}function Re(t){const n=(c,i)=>({ringCount:c,ringCountWarning:{code:"analysis-option-corrected",severity:"warning",message:`ringCount=${String(t)} ${i} (using ${c}).`}});if(t===void 0)return{ringCount:4,ringCountWarning:void 0};if(typeof t!="number"||!Number.isFinite(t))return n(4,"is not a finite number");const e=Math.max(1,Math.round(t));return e===t?{ringCount:t,ringCountWarning:void 0}:n(e,"is not a whole number of at least 1")}function te(t){const n=Array.isArray(t)?{}:t,e=Array.isArray(n.lotStack?.results)?n.lotStack.results:[],c=(Array.isArray(t)?[t]:[n.results,...e]).filter(a=>Array.isArray(a)),i={hbin:0,sbin:0,testValues:0,testPass:0};let o;const u=(a,s)=>{i[a]++,o??=`${a} ${JSON.stringify(s)}`};for(const a of c){const s=new Set,m=new Set;for(let p=0;p<a.length;p+=p<50?1:100){const h=a[p];if(!(h===null||typeof h!="object")){if(h.testValues&&typeof h.testValues=="object")for(const x of Object.keys(h.testValues))s.add(x);if(h.testPass&&typeof h.testPass=="object")for(const x of Object.keys(h.testPass))m.add(x)}}const f=[...s],l=[...m];for(const p of a){if(p===null||typeof p!="object")continue;const h=p;h.hbin!=null&&typeof h.hbin!="number"&&u("hbin",h.hbin),h.sbin!=null&&typeof h.sbin!="number"&&u("sbin",h.sbin);const x=h.testValues;if(x&&typeof x=="object")for(let D=0;D<f.length;D++){const b=x[f[D]];b!=null&&typeof b!="number"&&u("testValues",b)}const C=h.testPass;if(C&&typeof C=="object")for(let D=0;D<l.length;D++){const b=C[l[D]];b!=null&&typeof b!="boolean"&&u("testPass",b)}}}const r=[];if(i.hbin&&r.push(`${i.hbin} hard bin${i.hbin===1?"":"s"}`),i.sbin&&r.push(`${i.sbin} soft bin${i.sbin===1?"":"s"}`),i.testValues&&r.push(`${i.testValues} test value${i.testValues===1?"":"s"}`),i.testPass&&r.push(`${i.testPass} pass/fail verdict${i.testPass===1?"":"s"}`),!r.length)return;const g=`buildWaferMap received ${r.join(", ")} of the wrong type (for example ${o}). Bins and test values must be numbers and verdicts true/false \u2014 a bin of "1" is not pass bin 1, so those dies count as fails and yield is wrong, and a test value given as text is not plotted or analysed correctly. Convert them with Number() (and verdicts to booleans) and rebuild.`;return console.warn(`[wafermap] ${g}`),{code:"input-values-not-numbers",message:g,severity:"error"}}function ne(t){const n=(a,s)=>a!==null&&typeof a=="object"&&a[s]!==void 0,e=Array.isArray(t)?{}:t,c=[],i=(a,s,m)=>{a&&c.push(`\`${s}\` (now \`${m}\`)`)};i(n(e,"data"),"data","results"),i(n(e,"die"),"die","dieConfig"),i(n(e,"stack"),"stack","lotStack"),i(n(e.dieConfig,"origin"),"dieConfig.origin","dieConfig.coordinateOrigin"),i(n(e.waferConfig,"flat"),"waferConfig.flat","waferConfig.notch"),i(n(e.reticleConfig,"anchor"),"reticleConfig.anchor","reticleConfig.anchorDie"),i(n(e.lotStack,"aggr"),"lotStack.aggr","lotStack.method"),i(Array.isArray(e.testDefs)&&e.testDefs.some(a=>n(a,"index")&&!n(a,"testNumber")),"TestDef.index","TestDef.testNumber");const o=n(e.lotStack,"results")?e.lotStack.results:[],u=(Array.isArray(t)?[t]:[e.results,...Array.isArray(o)?o:[]]).filter(a=>Array.isArray(a));if(i(u.some(a=>a.slice(0,100).some(s=>n(s,"values"))),"values","testValues"),!c.length)return;const r=c.length>1,g=`buildWaferMap ignored ${c.join(", ")}: ${r?"these names were":"that name was"} removed in an earlier release, so what ${r?"they":"it"} described is not on this map. Rename and rebuild.`;return console.warn(`[wafermap] ${g}`),{code:"input-field-removed",message:g,severity:"error"}}function We(t){if(t.results!==void 0||t.lotStack!==void 0)throw new Error("buildWaferMap: pass `layout`, `results` or `lotStack` \u2014 only one.");const n=t.waferConfig?.diameter,e=t.dieConfig?.width,c=t.dieConfig?.height;if(!(n>0&&e>0&&c>0))throw new Error("buildWaferMap: `layout` needs waferConfig.diameter and dieConfig.width and height, all positive.");const i=j({diameter:n,notch:t.waferConfig?.notch,orientation:0}),o=ye(be(i,{width:e,height:c}),i,{width:e,height:c}).filter(s=>!s.partial),{flipX:u,flipY:r}=se(t.dieConfig,{type:"center"}),{layout:g,...a}=t;return{...a,results:o.map(s=>({x:u?-s.x:s.x,y:r?-s.y:s.y})),waferConfig:{...t.waferConfig,center:{x:0,y:0}},dieConfig:{...t.dieConfig,coordinateOrigin:{type:"center"}}}}function $e(t){if(!Array.isArray(t)&&"results"in t&&t.results!==void 0&&"lotStack"in t&&t.lotStack!==void 0)throw new Error("buildWaferMap: pass either `results` or `lotStack`, not both.");return Array.isArray(t)?{results:t,waferOpts:void 0,dieOpts:void 0,explicitDies:void 0,reticleOpts:void 0,lotStackOpts:void 0,passBins:[1],ringCount:4,ringCountWarning:void 0,removedFieldWarning:ne(t),nonNumericWarning:te(t),standardDiameters:void 0,testDefs:void 0,hbinDefs:void 0,sbinDefs:void 0,metadataFields:void 0,derivedTests:void 0,retestPolicy:"last",edgeDieYieldMode:"exclude"}:{results:t.results??[],waferOpts:t.waferConfig,dieOpts:t.dieConfig,explicitDies:t.dies,reticleOpts:t.reticleConfig,lotStackOpts:t.lotStack,passBins:t.passBins??[1],...Re(t.ringCount),removedFieldWarning:ne(t),nonNumericWarning:te(t),standardDiameters:t.standardDiameters,testDefs:t.testDefs?.length?t.testDefs:void 0,hbinDefs:t.hbinDefs,sbinDefs:t.sbinDefs,metadataFields:t.metadataFields,derivedTests:t.derivedTests?.length?t.derivedTests:void 0,retestPolicy:t.retestPolicy??"last",edgeDieYieldMode:t.edgeDieYieldMode??"exclude"}}function Ee(t,n){return n?.coordinateOrigin?n.coordinateOrigin:{type:"center"}}function Fe(t,n,e){if(n.type==="custom"&&n.offset)return{offsetX:n.offset.x,offsetY:n.offset.y};if(n.type!=="center"){let c=1/0,i=-1/0,o=1/0,u=-1/0;for(const r of t)r.x<c&&(c=r.x),r.x>i&&(i=r.x),r.y<o&&(o=r.y),r.y>u&&(u=r.y);return{offsetX:Math.round((i+c)/2),offsetY:Math.round((u+o)/2)}}return{offsetX:e.offsetX,offsetY:e.offsetY}}function Pe(t,n,e,c,i,o){if(o)return{colMidX:(Math.round(o.x)-n)*c,colMidY:(Math.round(o.y)-e)*i,anchored:!0};if(t.length===0)return{colMidX:0,colMidY:0,anchored:!1};let u=1/0,r=-1/0,g=1/0,a=-1/0;for(const s of t){const m=Math.round(s.x)-n,f=Math.round(s.y)-e;m<u&&(u=m),m>r&&(r=m),f<g&&(g=f),f>a&&(a=f)}return{colMidX:(u+r)/2*c,colMidY:(g+a)/2*i,anchored:!1}}function Te(t,n,e){let c=1/0,i=-1/0,o=1/0,u=-1/0,r=0,g=0;for(const m of t){const f=Math.round(m.x)-n,l=Math.round(m.y)-e;r+=f,g+=l,f<c&&(c=f),f>i&&(i=f),l<o&&(o=l),l>u&&(u=l)}const a=t.length,s=(m,f,l)=>{const p=(l-f)/2;return p<=0?0:Math.abs(m/a-(f+l)/2)/p};return s(r,c,i)>.11||s(g,o,u)>.11}function se(t,n){let e=t?.xAxisDirection==="left",c=t?.yAxisDirection==="down";return(n.type==="UL"||n.type==="UR")&&(c=!0),(n.type==="LR"||n.type==="UR")&&(e=!0),{flipX:e,flipY:c}}function Ie(t,n){const{method:e,targetBin:c}=t,i=t.results.map(o=>o.filter(R));if(e==="mean"||e==="median"||e==="stddev"||e==="min"||e==="max"||e==="count"){const o=new Set((n??[]).filter(s=>!isParametricTest(s)).map(s=>s.testNumber)),u=new Set;for(const s of i)for(const m of s)if(m.testValues)for(const f of Object.keys(m.testValues)){const l=Number(f);o.has(l)||u.add(l)}if(u.size===0)return ee(i,e);const r=[...u],g=r.map(s=>ee(i,e,s)),a=new Map;for(let s=0;s<r.length;s++){const m=r[s];for(const f of g[s]){const l=V(f);a.has(l)||a.set(l,{template:f,testValues:{}});const p=a.get(l),h=f.testValues?.[0];h!==void 0&&(p.testValues[m]=h)}}return[...a.values()].map(({template:s,testValues:m})=>({...s,testValues:Object.keys(m).length>0?m:void 0}))}if(e==="countBin"||e==="percent"){if(c===void 0)return[];const o=Ae(i,c,"hard");if(e==="countBin")return o;const u=i.length;return o.map(r=>({...r,testValues:{0:u>0?(r.testValues?.[0]??0)/u*100:0}}))}if(e==="mode"){const o=new Map;for(const r of i)for(const g of r){const a=V(g);o.has(a)||o.set(a,[]),o.get(a).push(g)}const u=[];for(const[r,g]of o){const a=r.split(","),s=Number(a[0]),m=Number(a[1]),f=g.map(p=>p.hbin).filter(p=>p!==void 0),l=ke(f);l!==null&&u.push({x:s,y:m,hbin:l})}return u}return[]}function ie(t){const n=t.filter(R),e=n.length,c=n.filter(o=>o.edgeExcluded).length,i=n.filter(o=>dieHasTestData(o)||o.hbin!==void 0||o.sbin!==void 0).length;return{filledDies:i,totalDies:e,edgeExcludedDies:c,unpositionedDies:t.length-n.length,ratio:e>0?i/e:0}}function re(t,n,e="exclude"){const c=new Set(n),o=t.filter(l=>!l.partial).filter(l=>l.edgeExcluded).length,u=t.filter(l=>l.partial).length;let r=0,g=0,a=!1;for(const l of t){if(!ge(l))continue;const p=l.hbin??l.sbin;p!==void 0&&(a=!0,c.has(p)?r++:g++)}const s=r+g,m=a&&s>0?r/s*100:null;let f=null;if(e==="denominator-only"){const l=s+o;f=a&&l>0?r/l*100:null}return{passDies:r,failDies:g,edgeExcludedDies:o,partialDies:u,totalDies:s,yieldPercent:m,yieldPercentGross:f}}function oe(t,n,e,c,i,o=0,u=0,r=0,g=0,a=0,s=!1,m=!1){if(!t)return[];const f=t.anchorDie??{x:0,y:0},l=Se(n,{width:t.width,height:t.height,diePitchX:c,diePitchY:i,anchorDie:{x:f.x-o,y:f.y-u},gridOrigin:{x:-r,y:-g}}),p=De(xe(s,m,n.center.x,n.center.y),we(a,n.center.x,n.center.y));return l.filter(h=>{const x=h.width/2,C=h.height/2,D=[[h.x-x,h.y-C],[h.x+x,h.y-C],[h.x+x,h.y+C],[h.x-x,h.y+C]].map(([w,P])=>Me(p,w,P)),b=Z(D.map(w=>w.x)),S=K(D.map(w=>w.x)),W=Z(D.map(w=>w.y)),$=K(D.map(w=>w.y));return e.some(w=>w.physX>=b&&w.physX<S&&w.physY>=W&&w.physY<$)})}function Ne(t,n,e){const i=Math.max(0,n.radius-e)**2;return t.map(o=>{const u=o.physX-n.center.x,r=o.physY-n.center.y;return u*u+r*r>i?{...o,edgeExcluded:!0}:o})}function Ve(t,n,e){const c=t.filter(R),i=t.filter(s=>!R(s)),o=new Map;for(const s of c){let m=o.get(s.x);m||(m=new Map,o.set(s.x,m)),m.set(s.y,(m.get(s.y)??0)+1)}const u=new Set(e);function r(s,m){const f=s.hbin,l=m.hbin;if(f===void 0||l===void 0)return!1;const p=u.has(f),h=u.has(l);return n==="best"?h!==p?h:l<f:h!==p?p:l>f}const g=new Map;for(const s of c){const m=V(s),f=g.get(m);n==="first"&&f||(n==="best"||n==="worst")&&f&&!r(f,s)||g.set(m,s)}return[...Array.from(g.values()).map(s=>{const f=o.get(s.x)?.get(s.y)??1;return f>1?{...s,retestCount:f}:s}),...i]}export function getDieTestValue(t,n){return t.testValues?.[n]}export function getTestPassStatus(t,n,e){const c=t.testPass?.[n];if(c!==void 0)return c;if(e!==void 0&&!isParametricTest(e)){const i=t.testValues?.[n];if(i===0||i===1)return i===1}}export function dieHasTestData(t){return t.testValues!==void 0&&Object.keys(t.testValues).length>0||t.testPass!==void 0&&Object.keys(t.testPass).length>0}function _(t,n){const e={};return n.hbin!==void 0&&(e.hbin=n.hbin),n.sbin!==void 0&&(e.sbin=n.sbin),n.retestCount!==void 0&&(e.retestCount=n.retestCount),n.siteNum!==void 0&&(e.siteNum=n.siteNum),n.partId!==void 0&&(e.partId=n.partId),n.metadata!==void 0&&(e.metadata=n.metadata),n.testPass!==void 0&&(e.testPass=n.testPass),n.testValues!==void 0&&(e.testValues=n.testValues),{...t,...e}}function ae(t,n){return n.plotMode?n.plotMode:t.some(dieHasTestData)?"value":"hardBin"}function de(t,n){return t.map(({code:e,message:c})=>({code:e,message:c,severity:e==="non-standard-diameter"||e==="diameter-exceeds-die-extent"?"warning":"error",confidence:n.wafer.confidence}))}const Be="do not fit inside the supplied",_e="Inferred wafer diameter",Ye="The probed dies reach only",Le=.75,Xe=20;export const STANDARD_WAFER_DIAMETERS_MM=[100,125,150,200,300];const je=.02;function fe(t,n){return n.reduce((e,c)=>Math.abs(c-t)<Math.abs(e-t)?c:e)}function Ke(t,n){if(!n.length)return!0;const e=fe(t,n);return Math.abs(e-t)/e<=je}function ze(t){return t===void 0?STANDARD_WAFER_DIAMETERS_MM:t.filter(n=>Number.isFinite(n)&&n>0)}export function buildWaferMap(t,n){const e=$e(!Array.isArray(t)&&t.layout?We(t):t),{debug:c,...i}=n??{},o=[];if(e.derivedTests){const d=new Set(e.passBins);if(e.lotStackOpts){const y=e.lotStackOpts.results.map(v=>J(v,e.testDefs,e.derivedTests,d));e.lotStackOpts={...e.lotStackOpts,results:y.map(v=>v.results)},e.testDefs=y[0]?.testDefs??e.testDefs,o.push(...y[0]?.warnings??[])}else{const y=J(e.results,e.testDefs,e.derivedTests,d);e.results=y.results,e.testDefs=y.testDefs,o.push(...y.warnings)}}const u=e.lotStackOpts?Ie(e.lotStackOpts,e.testDefs):e.results;for(let d=0;d<Math.min(5,u.length);d++){const y=u[d];if(typeof y.x=="string"||typeof y.y=="string")throw new TypeError("buildWaferMap: x and y must be numbers, received strings. Did you forget to cast CSV values? e.g. { x: +row.x, y: +row.y }")}for(const d of u)if(d.x==null!=(d.y==null))throw new TypeError(`buildWaferMap: die has only one of x/y set (x=${String(d.x)}, y=${String(d.y)}). A die must be either fully positioned (both x and y) or fully unpositioned (neither) \u2014 omit both to mark a die as having no reported position.`);const r=Ve(u,e.retestPolicy,e.passBins),g={wafer:{confidence:1,method:"provided"},diePitch:{confidence:1,units:"mm"},grid:{confidence:1}},a=[];if(e.explicitDies){let d=e.explicitDies;const y=r.filter(R),v=r.filter(N=>!R(N));if(y.length>0){const N=new Map(y.map(A=>[V(A),A]));d=d.map(A=>{const H=N.get(V(A));return H?_(A,H):A})}const I=e.waferOpts?.diameter??300,F=j({diameter:I,notch:e.waferOpts?.notch,orientation:e.waferOpts?.orientation??0,metadata:e.waferOpts?.metadata});F.orientation!==0&&(d=U(d,F));const q=oe(e.reticleOpts,F,d,1,1,0,0,0,0,F.orientation),he=i.showReticle??e.reticleOpts!==void 0,L=Q(F,d,{...i,reticles:q,showReticle:he,plotMode:ae(r,i),testDefs:e.testDefs,passBins:e.passBins,ringCount:e.ringCount,isLotStack:!1},{hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,metadataFields:e.metadataFields}),me=v.map((N,A)=>_({id:`unpositioned_${A}`,width:d[0]?.width??1,height:d[0]?.height??1},N)),X=[...d,...me];return{wafer:F,dies:X,view:L,reticleConfig:e.reticleOpts,units:"mm",inference:g,warnings:[...e.removedFieldWarning?[e.removedFieldWarning]:[],...e.nonNumericWarning?[e.nonNumericWarning]:[],...de(a,g),...o,...e.ringCountWarning?[e.ringCountWarning]:[]],plotMode:L.plotMode,metadata:L.metadata,isLotStack:!1,dataCoverage:ie(X),passBins:e.passBins,ringCount:e.ringCount,yield:re(X,e.passBins,e.edgeDieYieldMode),reticles:q,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs,metadataFields:e.metadataFields}}const s=r.filter(R),m=r.filter(d=>!R(d)),f=s.map(d=>({x:d.x,y:d.y})),l=Ce(f,e.dieOpts,e.waferOpts?.diameter);g.diePitch={confidence:l.confidence,units:l.units};const{pitchX:p,pitchY:h}=l,x=l.units,C=Ee(s,e.dieOpts),D=Oe(f);g.grid={confidence:D.confidence};const{offsetX:b,offsetY:S}=Fe(f,C,D),{flipX:W,flipY:$}=se(e.dieOpts,C),{colMidX:w,colMidY:P,anchored:ce}=Pe(f,b,S,p,h,e.waferOpts?.center);let M=e.waferOpts?.diameter;const B=f.map(d=>({x:(Math.round(d.x)-b)*p-w,y:(Math.round(d.y)-S)*h-P})),E=K(B.map(({x:d,y})=>Math.hypot(Math.abs(d)+p/2,Math.abs(y)+h/2)));if(M===void 0)if(f.length>0)if(x==="mm"){const d=ve(B,{minRadius:E});M=d.diameter,g.wafer={confidence:d.confidence,method:d.method};const y=ze(e.standardDiameters);if(!Ke(M,y)){const v=fe(M,y);a.push({code:"non-standard-diameter",message:`${_e} ${M.toFixed(1)} mm is not a standard wafer size (nearest is ${v} mm). The diameter was derived from the die extent, which assumes the probed grid reaches the wafer edge \u2014 a non-standard result usually means it does not, because the outer dies were never probed. Every die is then placed against a wafer that is too small, which moves dies between rings and changes ring and edge findings. Supply waferConfig.diameter.`})}}else M=E*2,g.wafer={confidence:l.confidence*.8,method:"extent"};else M=x==="mm"?300:30,g.wafer={confidence:0,method:"default"};if(!ce&&f.length>0&&Te(f,b,S)&&(g.wafer.method="inferred-partial",a.push({code:"partial-coverage",message:"Wafer geometry was inferred from die positions alone. The data does not span a full symmetric wafer, so the inferred diameter and centre may be wrong and dies may be mis-positioned relative to the true wafer boundary. Supply waferConfig.diameter and waferConfig.center (the prober coordinate of the wafer centre) to position partial data correctly."})),e.waferOpts?.diameter!==void 0&&e.waferOpts.center===void 0&&E>0&&f.length>=Xe){const d=E/(M/2);d<Le&&a.push({code:"diameter-exceeds-die-extent",message:`${Ye} ${(d*100).toFixed(0)}% of the ${M} mm wafer's radius. Either the diameter is larger than the real one, or this is a partial map covering only part of the wafer. Ring bands are equal-radius, so the outer rings are mostly or entirely empty and ring, quadrant and edge findings describe the assumed wafer rather than the probed area. Check waferConfig.diameter, or drop it and let the diameter be inferred from the die extent.`})}if(e.waferOpts?.diameter!==void 0){const d=e.dieOpts?.width!==void 0&&e.dieOpts?.height!==void 0;if(d&&E>M/2+1e-9){const y=B.filter(({x:v,y:I})=>Math.hypot(Math.abs(v)+p/2,Math.abs(I)+h/2)>M/2+1e-9).length;a.push({code:"geometry-conflict",message:`${y} of ${B.length} probed die positions ${Be} ${M} mm wafer at the supplied die pitch of ${p} \xD7 ${h} mm. A die with test results is a real prober position and is always fully on the wafer, so one of the two supplied values must be wrong: containing these dies at this pitch would need a diameter of at least ${(E*2).toFixed(1)} mm. Check waferConfig.diameter and dieConfig.width/height against the real device.`})}}const k=j({diameter:M,notch:e.waferOpts?.notch,orientation:e.waferOpts?.orientation??0,metadata:e.waferOpts?.metadata});let O=s.map(d=>{const y=Math.round(d.x)-b,v=Math.round(d.y)-S,I={id:`${y}_${v}`,x:y,y:v,physX:y*p-w,physY:v*h-P,width:p,height:h,insideWafer:!0,partial:!1};return _(I,d)});(b!==0||S!==0)&&(O=O.map(d=>({...d,x:d.x+b,y:d.y+S,id:`${d.x+b}_${d.y+S}`}))),O=U(O,k),(W||$)&&(O=pe(O,{flipX:W,flipY:$},k.center));const z=[];e.waferOpts?.edgeExclusion&&e.waferOpts.edgeExclusion>0&&(O=Ne(O,k,e.waferOpts.edgeExclusion),e.waferOpts.edgeExclusion>k.radius&&z.push({code:"edge-exclusion-exceeds-radius",message:`edgeExclusion (${e.waferOpts.edgeExclusion}mm) exceeds the resolved wafer radius (${k.radius}mm) \u2014 the entire wafer is excluded.`,severity:"warning"}));const G=oe(e.reticleOpts,k,O,p,h,b,S,w,P,k.orientation,W,$),le=i.showReticle??e.reticleOpts!==void 0,T=Q(k,O,{...i,reticles:G,showReticle:le,plotMode:ae(r,i),testDefs:e.testDefs,passBins:e.passBins,ringCount:e.ringCount,dataAxisFlip:{x:W,y:$},isLotStack:e.lotStackOpts!==void 0,aggregationMethod:e.lotStackOpts?.method,lotSize:e.lotStackOpts?.results.length},{hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,metadataFields:e.metadataFields}),ue=m.map((d,y)=>_({id:`unpositioned_${y}`,width:p,height:h},d)),Y=[...O,...ue];return{wafer:k,dies:Y,view:T,reticleConfig:e.reticleOpts,units:x,inference:g,warnings:[...e.removedFieldWarning?[e.removedFieldWarning]:[],...e.nonNumericWarning?[e.nonNumericWarning]:[],...de(a,g),...z,...o,...e.ringCountWarning?[e.ringCountWarning]:[]],plotMode:T.plotMode,metadata:T.metadata,isLotStack:e.lotStackOpts!==void 0,dataCoverage:ie(Y),passBins:e.passBins,ringCount:e.ringCount,yield:re(Y,e.passBins,e.edgeDieYieldMode),reticles:G,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs,metadataFields:e.metadataFields,aggrMethod:T.aggrMethod,lotSize:T.lotSize}}
|
|
1
|
+
import{createWafer as G}from"../core/wafer.js";import{isYieldEligibleDie as ge,getDieKey as j,hasPosition as _}from"../core/dies.js";import{applyOrientation as Q,transformDies as ye,clipDiesToWafer as be}from"../core/transforms.js";import{generateDies as we}from"../core/dies.js";import{affineRotation as De,affineMirror as xe,affineCompose as Me,affinePoint as ve}from"../core/transforms.js";import{inferWaferFromXY as Se}from"../core/inference/wafer.js";import{resolveGridPitch as Ce}from"../core/inference/pitch.js";import{assignGridIndices as Oe}from"../core/inference/grid.js";import{generateReticleGrid as ke}from"../core/reticle.js";import{applyDerivedTests as Z}from"./derivedTests/apply.js";import{buildView as ee}from"./buildView.js";import{isStdfBin as $e,isStdfCoord as te,isStdfTestNumber as ne,isStdfSite as Ae,STDF_BIN_MAX as Re,STDF_COORD_MAX as se,STDF_TEST_NUM_MAX as We,STDF_SITE_MAX as Ne}from"../core/stdf.js";import{maxOf as H,minOf as ie,modeOf as Ee}from"../core/utils.js";import{aggregateValues as re,aggregateBinCounts as Te}from"../core/aggregates.js";export function isParametricTest(t){return t?.testType!=="F"}function Fe(t){const e=(a,r)=>({ringCount:a,ringCountWarning:{code:"analysis-option-corrected",severity:"warning",message:`ringCount=${String(t)} ${r} (using ${a}).`}});if(t===void 0)return{ringCount:4,ringCountWarning:void 0};if(typeof t!="number"||!Number.isFinite(t))return e(4,"is not a finite number");const n=Math.max(1,Math.round(t));return n===t?{ringCount:t,ringCountWarning:void 0}:e(n,"is not a whole number of at least 1")}function Ie(t){const e=Array.isArray(t)?{}:t,n={hbin:0,sbin:0,testValues:0,testPass:0};let a;const r=(d,v)=>{n[d]++,a??=`${d} ${JSON.stringify(v)}`},s={bins:0,positions:0,values:0,sites:0,orientation:0},u=new Set;let o=0;const w=d=>typeof d!="number"?!0:Number.isNaN(d)?(o++,!1):$e(d)?!0:(s.bins++,!1);function h(d){const v=new Set,x=new Set;for(let M=0;M<d.length;M+=M<50?1:100){const g=d[M];if(!(g===null||typeof g!="object")){if(g.testValues&&typeof g.testValues=="object")for(const m of Object.keys(g.testValues))v.add(m);if(g.testPass&&typeof g.testPass=="object")for(const m of Object.keys(g.testPass))x.add(m)}}const O=[...v],y=[...x],b=M=>ne(Number(M));for(const M of new Set([...O,...y]))b(M)||u.add(M);let C;for(let M=0;M<d.length;M++){const g=d[M];if(g===null||typeof g!="object")continue;const m=g;let F;const A=()=>F??={...m};m.hbin!=null&&typeof m.hbin!="number"&&r("hbin",m.hbin),m.sbin!=null&&typeof m.sbin!="number"&&r("sbin",m.sbin),w(m.hbin)||(A().hbin=void 0),w(m.sbin)||(A().sbin=void 0);const N=typeof m.x=="number"&&!te(m.x),$=typeof m.y=="number"&&!te(m.y);if(N||$){s.positions+=(N?1:0)+($?1:0);const k=A();k.x=void 0,k.y=void 0}typeof m.siteNum=="number"&&!Ae(m.siteNum)&&(s.sites++,A().siteNum=void 0);const I=m.testValues;if(I&&typeof I=="object"){let k;for(let R=0;R<O.length;R++){const T=O[R],E=I[T];if(E!=null){if(!b(T)){k??={...I},delete k[T];continue}typeof E!="number"?r("testValues",E):Number.isFinite(E)||(s.values++,k??={...I},delete k[T])}}k&&(A().testValues=k)}const V=m.testPass;if(V&&typeof V=="object"){let k;for(let R=0;R<y.length;R++){const T=y[R],E=V[T];if(E!=null){if(!b(T)){k??={...V},delete k[T];continue}typeof E!="boolean"&&r("testPass",E)}}k&&(A().testPass=k)}F&&((C??=d.slice())[M]=F)}return C}const i={warnings:[]};if(Array.isArray(t))i.results=h(t);else{Array.isArray(e.results)&&(i.results=h(e.results));const d=e.lotStack?.results;if(Array.isArray(d)){const v=d.map(x=>Array.isArray(x)?h(x):void 0);v.some(Boolean)&&(i.stackResults=v.map((x,O)=>x??d[O]))}}if(Array.isArray(e.testDefs)){const d=e.testDefs,v=d.filter(x=>x==null||typeof x.testNumber!="number"||ne(x.testNumber));for(const x of d)v.includes(x)||u.add(String(x.testNumber));v.length!==d.length&&(i.testDefs=v)}const p=e.waferConfig?.orientation;if(typeof p=="number"){const d=Number.isFinite(p)?(p%360+360)%360:NaN;d===0||d===90||d===180||d===270?i.orientation=d:(s.orientation++,i.orientation=0)}const l=(d,v,x=`${v}s`)=>`${d} ${d===1?v:x}`,f=[];if(n.hbin&&f.push(l(n.hbin,"hard bin")),n.sbin&&f.push(l(n.sbin,"soft bin")),n.testValues&&f.push(l(n.testValues,"test value")),n.testPass&&f.push(l(n.testPass,"pass/fail verdict")),f.length){const d=`buildWaferMap received ${f.join(", ")} of the wrong type (for example ${a}). Bins and test values must be numbers and verdicts true/false \u2014 a bin of "1" is not pass bin 1, so those dies count as fails and yield is wrong, and a test value given as text is not plotted or analysed correctly. Convert them with Number() (and verdicts to booleans) and rebuild.`;console.warn(`[wafermap] ${d}`),i.warnings.push({code:"input-values-not-numbers",message:d,severity:"error"})}const D=[];if(s.bins&&D.push(`${l(s.bins,"bin")} (legal: whole numbers 0\u2013${Re}) \u2014 those dies have no bin`),s.positions&&D.push(`${l(s.positions,"coordinate")} (legal: whole numbers \u2212${se} to ${se}) \u2014 those dies have no position`),u.size&&D.push(`${l(u.size,"test number")} (legal: whole numbers 0\u2013${We}) \u2014 those tests are left out`),s.values&&D.push(`${l(s.values,"test value")} that ${s.values===1?"is":"are"} not finite \u2014 left out`),s.sites&&D.push(`${l(s.sites,"site number")} (legal: 0\u2013${Ne}) \u2014 those dies have no site`),s.orientation&&D.push("a waferConfig.orientation other than 0, 90, 180 or 270 \u2014 the map is built at 0"),D.length||o){const d=[];D.length&&d.push(`buildWaferMap received values outside the STDF V4 ranges, treated as missing: ${D.join("; ")}.`),o&&d.push(`${l(o,"bin was","bins were")} NaN and ${o===1?"is":"are"} treated as no bin.`);const v=d.join(" ");console.warn(`[wafermap] ${v}`),i.warnings.push({code:"input-values-outside-stdf",message:v,severity:"warning"})}return i}function oe(t){const e=(h,i)=>h!==null&&typeof h=="object"&&h[i]!==void 0,n=Array.isArray(t)?{}:t,a=[],r=(h,i,p)=>{h&&a.push(`\`${i}\` (now \`${p}\`)`)};r(e(n,"data"),"data","results"),r(e(n,"die"),"die","dieConfig"),r(e(n,"stack"),"stack","lotStack"),r(e(n.dieConfig,"origin"),"dieConfig.origin","dieConfig.coordinateOrigin"),r(e(n.waferConfig,"flat"),"waferConfig.flat","waferConfig.notch"),r(e(n.reticleConfig,"anchor"),"reticleConfig.anchor","reticleConfig.anchorDie"),r(e(n.lotStack,"aggr"),"lotStack.aggr","lotStack.method"),r(Array.isArray(n.testDefs)&&n.testDefs.some(h=>e(h,"index")&&!e(h,"testNumber")),"TestDef.index","TestDef.testNumber");const s=e(n.lotStack,"results")?n.lotStack.results:[],u=(Array.isArray(t)?[t]:[n.results,...Array.isArray(s)?s:[]]).filter(h=>Array.isArray(h));if(r(u.some(h=>h.slice(0,100).some(i=>e(i,"values"))),"values","testValues"),!a.length)return;const o=a.length>1,w=`buildWaferMap ignored ${a.join(", ")}: ${o?"these names were":"that name was"} removed in an earlier release, so what ${o?"they":"it"} described is not on this map. Rename and rebuild.`;return console.warn(`[wafermap] ${w}`),{code:"input-field-removed",message:w,severity:"error"}}function Pe(t){if(t.results!==void 0||t.lotStack!==void 0)throw new Error("buildWaferMap: pass `layout`, `results` or `lotStack` \u2014 only one.");const e=t.waferConfig?.diameter,n=t.dieConfig?.width,a=t.dieConfig?.height;if(!(e>0&&n>0&&a>0))throw new Error("buildWaferMap: `layout` needs waferConfig.diameter and dieConfig.width and height, all positive.");const r=G({diameter:e,notch:t.waferConfig?.notch,orientation:0}),s=be(we(r,{width:n,height:a}),r,{width:n,height:a}).filter(i=>!i.partial),{flipX:u,flipY:o}=ae(t.dieConfig,{type:"center"}),{layout:w,...h}=t;return{...h,results:s.map(i=>({x:u?-i.x:i.x,y:o?-i.y:i.y})),waferConfig:{...t.waferConfig,center:{x:0,y:0}},dieConfig:{...t.dieConfig,coordinateOrigin:{type:"center"}}}}function _e(t){if(!Array.isArray(t)&&"results"in t&&t.results!==void 0&&"lotStack"in t&&t.lotStack!==void 0)throw new Error("buildWaferMap: pass either `results` or `lotStack`, not both.");const e=Ie(t);return Array.isArray(t)?{results:e.results??t,waferOpts:void 0,dieOpts:void 0,explicitDies:void 0,reticleOpts:void 0,lotStackOpts:void 0,passBins:[1],ringCount:4,ringCountWarning:void 0,removedFieldWarning:oe(t),inputValueWarnings:e.warnings,standardDiameters:void 0,testDefs:void 0,hbinDefs:void 0,sbinDefs:void 0,metadataFields:void 0,derivedTests:void 0,retestPolicy:"last",edgeDieYieldMode:"exclude"}:{results:e.results??t.results??[],waferOpts:e.orientation===void 0||t.waferConfig===void 0?t.waferConfig:{...t.waferConfig,orientation:e.orientation},dieOpts:t.dieConfig,explicitDies:t.dies,reticleOpts:t.reticleConfig,lotStackOpts:e.stackResults&&t.lotStack?{...t.lotStack,results:e.stackResults}:t.lotStack,passBins:t.passBins??[1],...Fe(t.ringCount),removedFieldWarning:oe(t),inputValueWarnings:e.warnings,standardDiameters:t.standardDiameters,testDefs:(e.testDefs??t.testDefs)?.length?e.testDefs??t.testDefs:void 0,hbinDefs:t.hbinDefs,sbinDefs:t.sbinDefs,metadataFields:t.metadataFields,derivedTests:t.derivedTests?.length?t.derivedTests:void 0,retestPolicy:t.retestPolicy??"last",edgeDieYieldMode:t.edgeDieYieldMode??"exclude"}}function Ve(t,e){return e?.coordinateOrigin?e.coordinateOrigin:{type:"center"}}function Be(t,e,n){if(e.type==="custom"&&e.offset)return{offsetX:e.offset.x,offsetY:e.offset.y};if(e.type!=="center"){let a=1/0,r=-1/0,s=1/0,u=-1/0;for(const o of t)o.x<a&&(a=o.x),o.x>r&&(r=o.x),o.y<s&&(s=o.y),o.y>u&&(u=o.y);return{offsetX:Math.round((r+a)/2),offsetY:Math.round((u+s)/2)}}return{offsetX:n.offsetX,offsetY:n.offsetY}}function Ye(t,e,n,a,r,s){if(s)return{colMidX:(Math.round(s.x)-e)*a,colMidY:(Math.round(s.y)-n)*r,anchored:!0};if(t.length===0)return{colMidX:0,colMidY:0,anchored:!1};let u=1/0,o=-1/0,w=1/0,h=-1/0;for(const i of t){const p=Math.round(i.x)-e,l=Math.round(i.y)-n;p<u&&(u=p),p>o&&(o=p),l<w&&(w=l),l>h&&(h=l)}return{colMidX:(u+o)/2*a,colMidY:(w+h)/2*r,anchored:!1}}function Xe(t,e,n){let a=1/0,r=-1/0,s=1/0,u=-1/0,o=0,w=0;for(const p of t){const l=Math.round(p.x)-e,f=Math.round(p.y)-n;o+=l,w+=f,l<a&&(a=l),l>r&&(r=l),f<s&&(s=f),f>u&&(u=f)}const h=t.length,i=(p,l,f)=>{const D=(f-l)/2;return D<=0?0:Math.abs(p/h-(l+f)/2)/D};return i(o,a,r)>.11||i(w,s,u)>.11}function ae(t,e){let n=t?.xAxisDirection==="left",a=t?.yAxisDirection==="down";return(e.type==="UL"||e.type==="UR")&&(a=!0),(e.type==="LR"||e.type==="UR")&&(n=!0),{flipX:n,flipY:a}}function je(t,e){const{method:n,targetBin:a}=t,r=t.results.map(s=>s.filter(_));if(n==="mean"||n==="median"||n==="stddev"||n==="min"||n==="max"||n==="count"){const s=new Set((e??[]).filter(i=>!isParametricTest(i)).map(i=>i.testNumber)),u=new Set;for(const i of r)for(const p of i)if(p.testValues)for(const l of Object.keys(p.testValues)){const f=Number(l);s.has(f)||u.add(f)}if(u.size===0)return re(r,n);const o=[...u],w=o.map(i=>re(r,n,i)),h=new Map;for(let i=0;i<o.length;i++){const p=o[i];for(const l of w[i]){const f=j(l);h.has(f)||h.set(f,{template:l,testValues:{}});const D=h.get(f),d=l.testValues?.[0];d!==void 0&&(D.testValues[p]=d)}}return[...h.values()].map(({template:i,testValues:p})=>({...i,testValues:Object.keys(p).length>0?p:void 0}))}if(n==="countBin"||n==="percent"){if(a===void 0)return[];const s=Te(r,a,"hard");if(n==="countBin")return s;const u=r.length;return s.map(o=>({...o,testValues:{0:u>0?(o.testValues?.[0]??0)/u*100:0}}))}if(n==="mode"){const s=new Map;for(const o of r)for(const w of o){const h=j(w);s.has(h)||s.set(h,[]),s.get(h).push(w)}const u=[];for(const[o,w]of s){const h=o.split(","),i=Number(h[0]),p=Number(h[1]),l=w.map(D=>D.hbin).filter(D=>D!==void 0),f=Ee(l);f!==null&&u.push({x:i,y:p,hbin:f})}return u}return[]}function de(t){const e=t.filter(_),n=e.length,a=e.filter(s=>s.edgeExcluded).length,r=e.filter(s=>dieHasTestData(s)||s.hbin!==void 0||s.sbin!==void 0).length;return{filledDies:r,totalDies:n,edgeExcludedDies:a,unpositionedDies:t.length-e.length,ratio:n>0?r/n:0}}function fe(t,e,n="exclude"){const a=new Set(e),s=t.filter(f=>!f.partial).filter(f=>f.edgeExcluded).length,u=t.filter(f=>f.partial).length;let o=0,w=0,h=!1;for(const f of t){if(!ge(f))continue;const D=f.hbin??f.sbin;D!==void 0&&(h=!0,a.has(D)?o++:w++)}const i=o+w,p=h&&i>0?o/i*100:null;let l=null;if(n==="denominator-only"){const f=i+s;l=h&&f>0?o/f*100:null}return{passDies:o,failDies:w,edgeExcludedDies:s,partialDies:u,totalDies:i,yieldPercent:p,yieldPercentGross:l}}function ce(t,e,n,a,r,s=0,u=0,o=0,w=0,h=0,i=!1,p=!1){if(!t)return[];const l=t.anchorDie??{x:0,y:0},f=ke(e,{width:t.width,height:t.height,diePitchX:a,diePitchY:r,anchorDie:{x:l.x-s,y:l.y-u},gridOrigin:{x:-o,y:-w}}),D=Me(xe(i,p,e.center.x,e.center.y),De(h,e.center.x,e.center.y));return f.filter(d=>{const v=d.width/2,x=d.height/2,O=[[d.x-v,d.y-x],[d.x+v,d.y-x],[d.x+v,d.y+x],[d.x-v,d.y+x]].map(([g,m])=>ve(D,g,m)),y=ie(O.map(g=>g.x)),b=H(O.map(g=>g.x)),C=ie(O.map(g=>g.y)),M=H(O.map(g=>g.y));return n.some(g=>g.physX>=y&&g.physX<b&&g.physY>=C&&g.physY<M)})}function Le(t,e,n){const r=Math.max(0,e.radius-n)**2;return t.map(s=>{const u=s.physX-e.center.x,o=s.physY-e.center.y;return u*u+o*o>r?{...s,edgeExcluded:!0}:s})}const Ke=.2;function ze(t,e,n){const a=new Set(n);function r(y,b){if(e==="last")return!0;if(e==="first")return!1;const C=L(y.hbin),M=L(b.hbin);if(C===void 0||M===void 0)return!1;const g=a.has(C),m=a.has(M);return e==="best"?m!==g?m:M<C:m!==g?g:M>C}const s=(y,b)=>b.supersedes!==void 0||r(y,b),u=y=>{const b=y.partId===void 0?"":String(y.partId).trim();return b===""?void 0:b},o=t.filter(y=>!_(y)),w=new Map;for(const y of o){const b=u(y);b!==void 0&&w.set(b,(w.get(b)??0)+1)}const i=(o.length===0?0:Math.max(0,...w.values())/o.length)<=Ke,p=new Map,l=[],f=new Map;let D=0,d=0;for(const y of t){const b=u(y);let C;if(_(y)?C=`p:${j(y)}`:i&&b!==void 0?C=`i:${b}`:C=`u:${d++}`,y.supersedes==="partId"&&b!==void 0){const g=f.get(b);g!==void 0&&g!==C&&p.has(g)&&p.delete(g)}const M=p.get(C);M===void 0?(p.set(C,{winner:y,count:1}),l.push(C)):(M.count++,C.startsWith("i:")&&D++,s(M.winner,y)&&(M.winner=y)),b!==void 0&&f.set(b,C)}const v=[],x=[...l.filter(y=>y.startsWith("p:")),...l.filter(y=>!y.startsWith("p:"))];for(const y of x){const b=p.get(y);b!==void 0&&v.push(b.count>1?{...b.winner,retestCount:b.count}:b.winner)}const O=D>0?{code:"retests-by-part-id",severity:"info",message:`${D} record${D===1?"":"s"} with no position shared a part ID with an earlier record and ${D===1?"was":"were"} treated as a retest of it.`}:void 0;return{results:v,warning:O}}export function getDieTestValue(t,e){return t.testValues?.[e]}export function getTestPassStatus(t,e,n){const a=t.testPass?.[e];if(a!==void 0)return a;if(n!==void 0&&!isParametricTest(n)){const r=t.testValues?.[e];if(r===0||r===1)return r===1}}export function dieHasTestData(t){return t.testValues!==void 0&&Object.keys(t.testValues).length>0||t.testPass!==void 0&&Object.keys(t.testPass).length>0}function L(t){return t===void 0||Number.isNaN(t)?void 0:t}function K(t,e){const n={},a=L(e.hbin),r=L(e.sbin);return a!==void 0&&(n.hbin=a),r!==void 0&&(n.sbin=r),e.retestCount!==void 0&&(n.retestCount=e.retestCount),e.siteNum!==void 0&&(n.siteNum=e.siteNum),e.partId!==void 0&&(n.partId=e.partId),e.metadata!==void 0&&(n.metadata=e.metadata),e.testPass!==void 0&&(n.testPass=e.testPass),e.testValues!==void 0&&(n.testValues=e.testValues),{...t,...n}}function le(t){return t.some(dieHasTestData)?"value":"hardBin"}function ue(t,e){return t.map(({code:n,message:a})=>({code:n,message:a,severity:n==="non-standard-diameter"||n==="diameter-exceeds-die-extent"?"warning":"error",confidence:e.wafer.confidence}))}const Ue="do not fit inside the supplied",Ge="Inferred wafer diameter",He="The probed dies reach only",qe=.75,Je=20;export const STANDARD_WAFER_DIAMETERS_MM=[100,125,150,200,300];const Qe=.02;function he(t,e){return e.reduce((n,a)=>Math.abs(a-t)<Math.abs(n-t)?a:n)}function Ze(t,e){if(!e.length)return!0;const n=he(t,e);return Math.abs(n-t)/n<=Qe}function et(t){return t===void 0?STANDARD_WAFER_DIAMETERS_MM:t.filter(e=>Number.isFinite(e)&&e>0)}export function buildWaferMap(t){const e=_e(!Array.isArray(t)&&t.layout?Pe(t):t),n=[];if(e.derivedTests){const c=new Set(e.passBins);if(e.lotStackOpts){const S=e.lotStackOpts.results.map(W=>Z(W,e.testDefs,e.derivedTests,c));e.lotStackOpts={...e.lotStackOpts,results:S.map(W=>W.results)},e.testDefs=S[0]?.testDefs??e.testDefs,n.push(...S[0]?.warnings??[])}else{const S=Z(e.results,e.testDefs,e.derivedTests,c);e.results=S.results,e.testDefs=S.testDefs,n.push(...S.warnings)}}const a=e.lotStackOpts?je(e.lotStackOpts,e.testDefs):e.results;for(let c=0;c<Math.min(5,a.length);c++){const S=a[c];if(typeof S.x=="string"||typeof S.y=="string")throw new TypeError("buildWaferMap: x and y must be numbers, received strings. Did you forget to cast CSV values? e.g. { x: +row.x, y: +row.y }")}for(const c of a)if(c.x==null!=(c.y==null))throw new TypeError(`buildWaferMap: die has only one of x/y set (x=${String(c.x)}, y=${String(c.y)}). A die must be either fully positioned (both x and y) or fully unpositioned (neither) \u2014 omit both to mark a die as having no reported position.`);const{results:r,warning:s}=ze(a,e.retestPolicy,e.passBins),u={wafer:{confidence:1,method:"provided"},diePitch:{confidence:1,units:"mm"},grid:{confidence:1}},o=[];if(e.explicitDies){let c=e.explicitDies;const S=r.filter(_),W=r.filter(X=>!_(X));if(S.length>0){const X=new Map(S.map(P=>[j(P),P]));c=c.map(P=>{const J=X.get(j(P));return J?K(P,J):P})}const Y=e.waferOpts?.diameter??300,B=G({diameter:Y,notch:e.waferOpts?.notch,orientation:e.waferOpts?.orientation??0,metadata:e.waferOpts?.metadata});B.orientation!==0&&(c=Q(c,B));const q=ce(e.reticleOpts,B,c,1,1,0,0,0,0,B.orientation),pe=e.reticleOpts!==void 0,z=ee(B,c,{reticles:q,showReticle:pe,plotMode:le(r),testDefs:e.testDefs,passBins:e.passBins,ringCount:e.ringCount,isLotStack:!1},{hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,metadataFields:e.metadataFields}),me=W.map((X,P)=>K({id:`unpositioned_${P}`,width:c[0]?.width??1,height:c[0]?.height??1},X)),U=[...c,...me];return{wafer:B,dies:U,view:z,reticleConfig:e.reticleOpts,units:"mm",inference:u,warnings:[...e.removedFieldWarning?[e.removedFieldWarning]:[],...e.inputValueWarnings,...s?[s]:[],...ue(o,u),...n,...e.ringCountWarning?[e.ringCountWarning]:[]],plotMode:z.plotMode,metadata:z.metadata,isLotStack:!1,dataCoverage:de(U),passBins:e.passBins,ringCount:e.ringCount,yield:fe(U,e.passBins,e.edgeDieYieldMode),reticles:q,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs,metadataFields:e.metadataFields}}const w=r.filter(_),h=r.filter(c=>!_(c)),i=w.map(c=>({x:c.x,y:c.y})),p=Ce(i,e.dieOpts,e.waferOpts?.diameter);u.diePitch={confidence:p.confidence,units:p.units};const{pitchX:l,pitchY:f}=p,D=p.units,d=Ve(w,e.dieOpts),v=Oe(i);u.grid={confidence:v.confidence};const{offsetX:x,offsetY:O}=Be(i,d,v),{flipX:y,flipY:b}=ae(e.dieOpts,d),{colMidX:C,colMidY:M,anchored:g}=Ye(i,x,O,l,f,e.waferOpts?.center);let m=e.waferOpts?.diameter;const F=i.map(c=>({x:(Math.round(c.x)-x)*l-C,y:(Math.round(c.y)-O)*f-M})),A=H(F.map(({x:c,y:S})=>Math.hypot(Math.abs(c)+l/2,Math.abs(S)+f/2)));if(m===void 0)if(i.length>0)if(D==="mm"){const c=Se(F,{minRadius:A});m=c.diameter,u.wafer={confidence:c.confidence,method:c.method};const S=et(e.standardDiameters);if(!Ze(m,S)){const W=he(m,S);o.push({code:"non-standard-diameter",message:`${Ge} ${m.toFixed(1)} mm is not a standard wafer size (nearest is ${W} mm). The diameter was derived from the die extent, which assumes the probed grid reaches the wafer edge \u2014 a non-standard result usually means it does not, because the outer dies were never probed. Every die is then placed against a wafer that is too small, which moves dies between rings and changes ring and edge findings. Supply waferConfig.diameter.`})}}else m=A*2,u.wafer={confidence:p.confidence*.8,method:"extent"};else m=D==="mm"?300:30,u.wafer={confidence:0,method:"default"};if(!g&&i.length>0&&Xe(i,x,O)&&(u.wafer.method="inferred-partial",o.push({code:"partial-coverage",message:"Wafer geometry was inferred from die positions alone. The data does not span a full symmetric wafer, so the inferred diameter and centre may be wrong and dies may be mis-positioned relative to the true wafer boundary. Supply waferConfig.diameter and waferConfig.center (the prober coordinate of the wafer centre) to position partial data correctly."})),e.waferOpts?.diameter!==void 0&&e.waferOpts.center===void 0&&A>0&&i.length>=Je){const c=A/(m/2);c<qe&&o.push({code:"diameter-exceeds-die-extent",message:`${He} ${(c*100).toFixed(0)}% of the ${m} mm wafer's radius. Either the diameter is larger than the real one, or this is a partial map covering only part of the wafer. Ring bands are equal-radius, so the outer rings are mostly or entirely empty and ring, quadrant and edge findings describe the assumed wafer rather than the probed area. Check waferConfig.diameter, or drop it and let the diameter be inferred from the die extent.`})}if(e.waferOpts?.diameter!==void 0){const c=e.dieOpts?.width!==void 0&&e.dieOpts?.height!==void 0;if(c&&A>m/2+1e-9){const S=F.filter(({x:W,y:Y})=>Math.hypot(Math.abs(W)+l/2,Math.abs(Y)+f/2)>m/2+1e-9).length;o.push({code:"geometry-conflict",message:`${S} of ${F.length} probed die positions ${Ue} ${m} mm wafer at the supplied die pitch of ${l} \xD7 ${f} mm. A die with test results is a real prober position and is always fully on the wafer, so one of the two supplied values must be wrong: containing these dies at this pitch would need a diameter of at least ${(A*2).toFixed(1)} mm. Check waferConfig.diameter and dieConfig.width/height against the real device.`})}}const N=G({diameter:m,notch:e.waferOpts?.notch,orientation:e.waferOpts?.orientation??0,metadata:e.waferOpts?.metadata});let $=w.map(c=>{const S=Math.round(c.x)-x,W=Math.round(c.y)-O,Y={id:`${S}_${W}`,x:S,y:W,physX:S*l-C,physY:W*f-M,width:l,height:f,insideWafer:!0,partial:!1};return K(Y,c)});(x!==0||O!==0)&&($=$.map(c=>({...c,x:c.x+x,y:c.y+O,id:`${c.x+x}_${c.y+O}`}))),$=Q($,N),(y||b)&&($=ye($,{flipX:y,flipY:b},N.center));const I=[];e.waferOpts?.edgeExclusion&&e.waferOpts.edgeExclusion>0&&($=Le($,N,e.waferOpts.edgeExclusion),e.waferOpts.edgeExclusion>N.radius&&I.push({code:"edge-exclusion-exceeds-radius",message:`edgeExclusion (${e.waferOpts.edgeExclusion}mm) exceeds the resolved wafer radius (${N.radius}mm) \u2014 the entire wafer is excluded.`,severity:"warning"}));const V=ce(e.reticleOpts,N,$,l,f,x,O,C,M,N.orientation,y,b),k=e.reticleOpts!==void 0,R=ee(N,$,{reticles:V,showReticle:k,plotMode:le(r),testDefs:e.testDefs,passBins:e.passBins,ringCount:e.ringCount,dataAxisFlip:{x:y,y:b},isLotStack:e.lotStackOpts!==void 0,aggregationMethod:e.lotStackOpts?.method,lotSize:e.lotStackOpts?.results.length},{hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,metadataFields:e.metadataFields}),T=h.map((c,S)=>K({id:`unpositioned_${S}`,width:l,height:f},c)),E=[...$,...T];return{wafer:N,dies:E,view:R,reticleConfig:e.reticleOpts,units:D,inference:u,warnings:[...e.removedFieldWarning?[e.removedFieldWarning]:[],...e.inputValueWarnings,...s?[s]:[],...ue(o,u),...I,...n,...e.ringCountWarning?[e.ringCountWarning]:[]],plotMode:R.plotMode,metadata:R.metadata,isLotStack:e.lotStackOpts!==void 0,dataCoverage:de(E),passBins:e.passBins,ringCount:e.ringCount,yield:fe(E,e.passBins,e.edgeDieYieldMode),reticles:V,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs,metadataFields:e.metadataFields,aggrMethod:R.aggrMethod,lotSize:R.lotSize}}
|
|
@@ -28,8 +28,6 @@ export declare function wangHash(n: number): number;
|
|
|
28
28
|
/** Linear interpolation across RGB keypoints for t ∈ [0, 1]. */
|
|
29
29
|
export declare function lerpKp(kp: readonly [number, number, number][], t: number): string;
|
|
30
30
|
export declare const VIRIDIS: readonly [number, number, number][];
|
|
31
|
-
/** Map t ∈ [0, 1] to a Viridis RGB colour string. */
|
|
32
|
-
export declare function valueToViridis(t: number): string;
|
|
33
31
|
/**
|
|
34
32
|
* Categorical colour for the `index`-th distinct value of an active
|
|
35
33
|
* `'metadata'` field, where `index` comes from sorting the field's distinct
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{clamp01 as s}from"../core/utils.js";export const SPEC_PASS_FILL="#2ecc71",SPEC_FAIL_LOW="#3498db",SPEC_FAIL_HIGH="#e74c3c",NO_DATA_FILL="#d6d9dd",BIN_PALETTE=["#95a5a6","#1eb84b","#932cdd","#b8a51e","#2cbfdd","#b81e71","#58dd2c","#251eb8","#dd672c","#1eb87f","#ce2cdd","#98b81e","#2c84dd","#b81e3e","#2cdd3b","#581eb8","#dda22c","#1eb8b2","#dd2cb0","#64b81e","#2c49dd","#b8321e","#2cdd76","#8c1eb8","#dcdd2c","#1e8bb8","#dd2c75","#31b81e","#4b2cdd","#b8651e","#2cddb1","#b81eb1","#a1dd2c","#1e57b8","#dd2c3a","#1eb83f","#852cdd","#b8991e","#2ccddd","#b81e7e","#66dd2c","#1e24b8","#dd5a2c","#1eb872","#c02cdd","#a4b81e","#2c92dd","#b81e4a","#2cdd2e","#4c1eb8","#dd942c","#1eb8a6","#dd2cbe","#70b81e","#2c57dd","#b8261e","#2cdd69","#801eb8","#ddcf2c","#1e97b8","#dd2c83","#3db81e","#3d2cdd","#b8591e"];export function wangHash(e){let c=e|0;return c=Math.imul(c^c>>>16,73244475),c=Math.imul(c^c>>>16,73244475),(c^c>>>16)>>>0}const u=BIN_PALETTE.length-1;export function lerpKp(e,c){const n=s(c)*(e.length-1),d=Math.floor(n),t=Math.min(d+1,e.length-1),a=n-d,b=Math.round(e[d][0]+a*(e[t][0]-e[d][0])),o=Math.round(e[d][1]+a*(e[t][1]-e[d][1])),l=Math.round(e[d][2]+a*(e[t][2]-e[d][2]));return`rgb(${b},${o},${l})`}export const VIRIDIS=[[68,1,84],[59,82,139],[33,145,140],[94,201,98],[253,231,37]];
|
|
1
|
+
import{clamp01 as s}from"../core/utils.js";export const SPEC_PASS_FILL="#2ecc71",SPEC_FAIL_LOW="#3498db",SPEC_FAIL_HIGH="#e74c3c",NO_DATA_FILL="#d6d9dd",BIN_PALETTE=["#95a5a6","#1eb84b","#932cdd","#b8a51e","#2cbfdd","#b81e71","#58dd2c","#251eb8","#dd672c","#1eb87f","#ce2cdd","#98b81e","#2c84dd","#b81e3e","#2cdd3b","#581eb8","#dda22c","#1eb8b2","#dd2cb0","#64b81e","#2c49dd","#b8321e","#2cdd76","#8c1eb8","#dcdd2c","#1e8bb8","#dd2c75","#31b81e","#4b2cdd","#b8651e","#2cddb1","#b81eb1","#a1dd2c","#1e57b8","#dd2c3a","#1eb83f","#852cdd","#b8991e","#2ccddd","#b81e7e","#66dd2c","#1e24b8","#dd5a2c","#1eb872","#c02cdd","#a4b81e","#2c92dd","#b81e4a","#2cdd2e","#4c1eb8","#dd942c","#1eb8a6","#dd2cbe","#70b81e","#2c57dd","#b8261e","#2cdd69","#801eb8","#ddcf2c","#1e97b8","#dd2c83","#3db81e","#3d2cdd","#b8591e"];export function wangHash(e){let c=e|0;return c=Math.imul(c^c>>>16,73244475),c=Math.imul(c^c>>>16,73244475),(c^c>>>16)>>>0}const u=BIN_PALETTE.length-1;export function lerpKp(e,c){const n=s(c)*(e.length-1),d=Math.floor(n),t=Math.min(d+1,e.length-1),a=n-d,b=Math.round(e[d][0]+a*(e[t][0]-e[d][0])),o=Math.round(e[d][1]+a*(e[t][1]-e[d][1])),l=Math.round(e[d][2]+a*(e[t][2]-e[d][2]));return`rgb(${b},${o},${l})`}export const VIRIDIS=[[68,1,84],[59,82,139],[33,145,140],[94,201,98],[253,231,37]];const f=["#4e79a7","#f28e2b","#e15759","#76b7b2","#59a14f","#edc948","#b07aa1","#ff9da7","#9c755f","#bab0ac"],h=668265263;export function metadataValueColor(e){return e<f.length?f[e]:BIN_PALETTE[wangHash(e^h)%u+1]}export function valueToGreyscale(e){const c=Math.round(s(e)*200+30);return`rgb(${c},${c},${c})`}export function contrastTextColor(e){let c=0,r=0,n=0;const d=e.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);if(d)c=+d[1],r=+d[2],n=+d[3];else{const b=e.replace("#","");c=parseInt(b.slice(0,2),16),r=parseInt(b.slice(2,4),16),n=parseInt(b.slice(4,6),16)}const t=b=>{const o=b/255;return o<=.03928?o/12.92:((o+.055)/1.055)**2.4};return .2126*t(c)+.7152*t(r)+.0722*t(n)>.179?"#000000":"#ffffff"}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* and held to by tests/deprecations.test.mjs, which fails once the changelog or
|
|
5
5
|
* package.json reaches an export's removal release with it still here.
|
|
6
6
|
*/
|
|
7
|
-
export declare const DEPRECATED_REMOVAL_VERSION = "0.
|
|
7
|
+
export declare const DEPRECATED_REMOVAL_VERSION = "0.32.0";
|
|
8
8
|
/**
|
|
9
9
|
* @internal The release that removes each deprecated name. A name deprecated
|
|
10
10
|
* after the default removal release was scheduled gets a later one — a removal
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const DEPRECATED_REMOVAL_VERSION="0.
|
|
1
|
+
export const DEPRECATED_REMOVAL_VERSION="0.32.0",DEPRECATED_REMOVALS=new Map,DEPRECATED_EXPORTS=new Map;const r=new Set;export function noticeOnce(t,e){r.has(t)||(r.add(t),console.warn(`[wafermap] ${e}`))}export function deprecated(t,e,n,o=DEPRECATED_REMOVAL_VERSION){DEPRECATED_EXPORTS.set(e,"function"),DEPRECATED_REMOVALS.set(e,o);const c=t;return((...E)=>(noticeOnce(e,`${e} is deprecated and will be removed in ${o}. ${n}`),c(...E)))}export function deprecatedValue(t,e,n=DEPRECATED_REMOVAL_VERSION){return DEPRECATED_EXPORTS.set(e,"value"),DEPRECATED_REMOVALS.set(e,n),t}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{diePassStatus as P}from"../../core/dies.js";import{getTestPassStatus as
|
|
1
|
+
import{isStdfTestNumber as S,STDF_TEST_NUM_MAX as D}from"../../core/stdf.js";import{diePassStatus as P}from"../../core/dies.js";import{getTestPassStatus as T,isParametricTest as M}from"../buildWaferMap.js";import{classifySpec as V}from"../spec.js";import{parseExpression as k}from"./parser.js";import{evaluate as O}from"./evaluate.js";function p(m){return{code:"derived-test-invalid",message:m,severity:"warning"}}function F(m,l){const w=[],o=n=>{switch(n.k){case"acc":{if(n.kind!=="t")return;const i=l.get(n.test)?.unit;return i?new Set([i]):void 0}case"unary":return n.op==="-"?o(n.arg):void 0;case"binary":{const i=o(n.left),a=o(n.right);if(n.op==="+"||n.op==="-"){if(i&&a){const d=new Set([...i,...a]);return d.size>1&&w.push(`${[...d].join(" and ")} are combined by "${n.op}"`),d}return i??a}return}case"call":{if(n.name==="min"||n.name==="max"||n.name==="mean"||n.name==="sum"){const i=n.args[0];if(i?.k==="vec"&&i.kind==="t"){const a=new Set(i.tests.map(d=>l.get(d)?.unit).filter(d=>!!d));return a.size>1&&w.push(`${[...a].join(" and ")} are combined by "${n.name}"`),a}}return}default:return}};return o(m),w}function j(m,l,w){const o=[],n=new Map((l??[]).map(e=>[e.testNumber,e])),i=e=>`${JSON.stringify(e.name??"")} (test ${e.testNumber})`,a=[],d=new Set;for(const e of m){const t=i(e);if(!S(e.testNumber)){o.push(p(`Derived test ${t} was dropped: \`testNumber\` must be a whole number from 0 to ${D}, as STDF stores it.`));continue}if(w.has(e.testNumber)||n.has(e.testNumber)){o.push(p(`Derived test ${t} was dropped: test number ${e.testNumber} already exists in the data. Measured values are never replaced by a derived test \u2014 give it an unused number.`));continue}if(d.has(e.testNumber)){o.push(p(`Derived test ${t} was dropped: test number ${e.testNumber} is used by an earlier derived test.`));continue}if(typeof e.expression!="string"||e.expression.trim()===""){o.push(p(`Derived test ${t} was dropped: \`expression\` is empty.`));continue}d.add(e.testNumber),a.push(e)}const y=new Map(n);for(const e of a){const{expression:t,constants:u,...c}=e;y.set(e.testNumber,{...c,testType:e.testType??"P"})}const s=new Map,r=new Set;for(const e of a){const t=i(e),u={testDefs:y,constants:e.constants??{}},c=k(e.expression,u);if(!c.ok){o.push(p(`Derived test ${t} was dropped: ${c.message}`)),r.add(e.testNumber);continue}const f=c.type==="boolean",g=!M(e);if(f!==g){o.push(p(f?`Derived test ${t} was dropped: its expression produces a true/false verdict, so it must declare \`testType: 'F'\`.`:`Derived test ${t} was dropped: it declares \`testType: 'F'\` but its expression produces a number. Functional tests carry a verdict, not a measurement.`)),r.add(e.testNumber);continue}if(f&&(e.limitLow!==void 0||e.limitHigh!==void 0)&&o.push(p(`Derived test ${t}: test limits are ignored on a true/false derived test.`)),e.testNumber===void 0)continue;const N=e.testNumber,x=[...new Set(c.reads.map(h=>h.test))].filter(h=>h!==N&&d.has(h));if(c.reads.some(h=>h.test===N)){o.push(p(`Derived test ${t} was dropped: its expression reads itself.`)),r.add(N);continue}for(const h of F(c.ast,y))o.push(p(`Derived test ${t} combines different units \u2014 ${h}. The result is computed as written; set \`unit\` on the derived test to say what it is.`));s.set(N,{raw:e,compiled:{def:{...e,testType:f?"F":"P",derived:!0},ast:c.ast,boolean:f},deps:x})}const b=[],v=new Map,$=(e,t)=>{if(v.get(e)==="done")return!r.has(e);if(v.get(e)==="visiting"){const f=[...t.slice(t.indexOf(e)),e].join(" \u2192 ");for(const g of t.slice(t.indexOf(e)))r.has(g)||(r.add(g),o.push(p(`Derived test ${i(s.get(g).raw)} was dropped: its expression depends on itself through ${f}.`)));return!1}const u=s.get(e);if(u===void 0)return!1;v.set(e,"visiting");let c=!0;for(const f of u.deps)if(!$(f,[...t,e])){if(!r.has(e)){r.add(e);const g=s.get(f)?.raw.name??`test ${f}`;o.push(p(`Derived test ${i(u.raw)} was dropped: it reads ${JSON.stringify(g)} (test ${f}), which was itself dropped.`))}c=!1;break}return v.set(e,"done"),!c||r.has(e)?!1:(b.push(u.compiled),!0)};for(const e of a)$(e.testNumber,[]);return{compiled:b,warnings:o}}export function applyDerivedTests(m,l,w,o){if(w===void 0||w.length===0)return{results:m,testDefs:l,warnings:[]};const n=new Set;for(const s of m){for(const r of Object.keys(s.testValues??{}))n.add(Number(r));for(const r of Object.keys(s.testPass??{}))n.add(Number(r))}const{compiled:i,warnings:a}=j(w,l,n);if(i.length===0)return{results:m,testDefs:l,warnings:a};const d=new Map((l??[]).map(s=>[s.testNumber,s]));for(const s of i)d.set(s.def.testNumber,s.def);return{results:m.map(s=>{let r,b;const v=t=>r?.[t]??s.testValues?.[t],e={value:v,testPass:t=>b?.[t]??T({testValues:r??s.testValues,testPass:b??s.testPass},t,d.get(t)),specPass:t=>{const u=V(v(t),d.get(t));return u===null?void 0:u==="pass"},diePass:()=>P(s,o)};for(const t of i){const u=O(t.ast,e);u!==void 0&&(t.boolean?(b??={...s.testPass},b[t.def.testNumber]=u):(r??={...s.testValues},r[t.def.testNumber]=u))}return r===void 0&&b===void 0?s:{...s,...r!==void 0?{testValues:r}:{},...b!==void 0?{testPass:b}:{}}}),testDefs:[...l??[],...i.map(s=>s.def)],warnings:a}}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import{isParametricTest as c}from"../buildWaferMap.js";import{hasSpecLimits as l}from"../spec.js";export const FUNCTIONS={abs:[{arity:1,returns:"number"}],sqrt:[{arity:1,returns:"number"}],ln:[{arity:1,returns:"number"}],log10:[{arity:1,returns:"number"}],exp:[{arity:1,returns:"number"}],floor:[{arity:1,returns:"number"}],ceil:[{arity:1,returns:"number"}],round:[{arity:1,returns:"number"}],sign:[{arity:1,returns:"number"}],pow:[{arity:2,returns:"number"}],min:[{vecOf:"numberVec",returns:"number"},{variadic:!0,returns:"number"}],max:[{vecOf:"numberVec",returns:"number"},{variadic:!0,returns:"number"}],if:[{arity:3,returns:"number"}],all:[{vecOf:"boolVec",returns:"boolean"}],any:[{vecOf:"boolVec",returns:"boolean"}],none:[{vecOf:"boolVec",returns:"boolean"}],countTrue:[{vecOf:"boolVec",returns:"number"}],countFalse:[{vecOf:"boolVec",returns:"number"}],mean:[{vecOf:"numberVec",returns:"number"}],sum:[{vecOf:"numberVec",returns:"number"}],countKnown:[{vecOf:"any",returns:"number"}]};const d=["t","testPass","specPass"],m=["<=",">=","==","!=","..","+","-","*","/","%","^","<",">"],b=["(",")","[","]",","];class o extends Error{position;constructor(t,e){super(t),this.position=e}}function h(r){const t=[];let e=0;for(;e<r.length;){const n=r[e];if(n===" "||n===" "||n===`
|
|
2
|
-
`||n==="\r"){e++;continue}if(n>="0"&&n<="9"){const s=e;for(;e<r.length&&r[e]>="0"&&r[e]<="9";)e++;if(r[e]==="."&&r[e+1]!==".")for(e++;e<r.length&&r[e]>="0"&&r[e]<="9";)e++;if(r[e]==="e"||r[e]==="E"){const a=e;if(e++,(r[e]==="+"||r[e]==="-")&&e++,r[e]>="0"&&r[e]<="9")for(;e<r.length&&r[e]>="0"&&r[e]<="9";)e++;else e=a}const u=r.slice(s,e);t.push({kind:"num",text:u,pos:s,value:Number(u)});continue}if(/[A-Za-z_]/.test(n)){const s=e;for(;e<r.length&&/[A-Za-z0-9_]/.test(r[e]);)e++;t.push({kind:"ident",text:r.slice(s,e),pos:s});continue}const i=m.find(s=>r.startsWith(s,e));if(i){t.push({kind:"op",text:i,pos:e}),e+=i.length;continue}if(b.includes(n)){t.push({kind:"punc",text:n,pos:e}),e++;continue}throw new o(`Unexpected character ${JSON.stringify(n)} at position ${e}`,e)}return t.push({kind:"eof",text:"",pos:r.length}),t}class f{toks;ctx;p=0;reads=[];usesDiePass=!1;constructor(t,e){this.toks=t,this.ctx=e}peek(){return this.toks[this.p]}next(){return this.toks[this.p++]}at(t){const e=this.peek();return(e.kind==="op"||e.kind==="punc"||e.kind==="ident")&&e.text===t}eat(t){return this.at(t)?(this.p++,!0):!1}expect(t){if(!this.at(t)){const e=this.peek();throw new o(`Expected ${JSON.stringify(t)} but found ${e.kind==="eof"?"end of expression":JSON.stringify(e.text)} at position ${e.pos}`,e.pos)}return this.next()}parse(){const t=this.parseOr();return this.expectEnd(),t}parseTestRefOnly(){const t=this.parseTestRef(this.peek().pos);return this.expectEnd(),t}expectEnd(){const t=this.peek();if(t.kind!=="eof")throw new o(`Unexpected ${JSON.stringify(t.text)} at position ${t.pos}`,t.pos)}requireScalar(t,e,n){if(t.type==="numberVec"||t.type==="boolVec")throw new o(`${e} cannot be applied to a test range at position ${n} \u2014 a range must be reduced first, e.g. mean(t[1010..1015]) or all(testPass[1010..1015])`,n);return t.type}parseOr(){let t=this.parseAnd();for(;this.at("or");){const e=this.next().pos,n=this.parseAnd();this.requireBool(t,"or",e),this.requireBool(n,"or",e),t={node:{k:"binary",op:"or",left:t.node,right:n.node},type:"boolean"}}return t}parseAnd(){let t=this.parseNot();for(;this.at("and");){const e=this.next().pos,n=this.parseNot();this.requireBool(t,"and",e),this.requireBool(n,"and",e),t={node:{k:"binary",op:"and",left:t.node,right:n.node},type:"boolean"}}return t}requireBool(t,e,n){if(this.requireScalar(t,`Operator ${JSON.stringify(e)}`,n),t.type!=="boolean")throw new o(`Operator ${JSON.stringify(e)} needs a true/false operand at position ${n}, but got a number \u2014 compare it first, e.g. t[1020] > 5`,n)}parseNot(){if(this.at("not")){const t=this.next().pos,e=this.parseNot();return this.requireBool(e,"not",t),{node:{k:"unary",op:"not",arg:e.node},type:"boolean"}}return this.parseCompare()}parseCompare(){const t=this.parseAdd(),e=this.peek();if(e.kind==="op"&&["<","<=",">",">=","==","!="].includes(e.text)){this.next();const n=this.parseAdd(),i=this.requireScalar(t,`Operator ${JSON.stringify(e.text)}`,e.pos),s=this.requireScalar(n,`Operator ${JSON.stringify(e.text)}`,e.pos);if(i!==s)throw new o(`Cannot compare a ${i} with a ${s} at position ${e.pos}`,e.pos);if(i==="boolean"&&e.text!=="=="&&e.text!=="!=")throw new o(`Operator ${JSON.stringify(e.text)} cannot order true/false values at position ${e.pos}`,e.pos);return{node:{k:"binary",op:e.text,left:t.node,right:n.node},type:"boolean"}}return t}parseAdd(){let t=this.parseMul();for(;;){const e=this.peek();if(e.kind!=="op"||e.text!=="+"&&e.text!=="-")return t;this.next();const n=this.parseMul();this.requireNum(t,e.text,e.pos),this.requireNum(n,e.text,e.pos),t={node:{k:"binary",op:e.text,left:t.node,right:n.node},type:"number"}}}parseMul(){let t=this.parsePow();for(;;){const e=this.peek();if(e.kind!=="op"||!["*","/","%"].includes(e.text))return t;this.next();const n=this.parsePow();this.requireNum(t,e.text,e.pos),this.requireNum(n,e.text,e.pos),t={node:{k:"binary",op:e.text,left:t.node,right:n.node},type:"number"}}}parsePow(){const t=this.parseUnary(),e=this.peek();if(e.kind==="op"&&e.text==="^"){this.next();const n=this.parsePow();return this.requireNum(t,"^",e.pos),this.requireNum(n,"^",e.pos),{node:{k:"binary",op:"^",left:t.node,right:n.node},type:"number"}}return t}requireNum(t,e,n){if(this.requireScalar(t,`Operator ${JSON.stringify(e)}`,n),t.type!=="number")throw new o(`Operator ${JSON.stringify(e)} needs a numeric operand at position ${n}, but got a true/false value`,n)}parseUnary(){const t=this.peek();if(t.kind==="op"&&t.text==="-"){this.next();const e=this.parseUnary();return this.requireNum(e,"-",t.pos),{node:{k:"unary",op:"-",arg:e.node},type:"number"}}return this.parsePrimary()}parsePrimary(){const t=this.next();if(t.kind==="num")return{node:{k:"num",value:t.value},type:"number"};if(t.kind==="punc"&&t.text==="("){const e=this.parseOr();return this.expect(")"),e}if(t.kind==="ident"){if(d.includes(t.text))return this.parseAccessor(t.text,t.pos);if(t.text==="diePass")return this.expect("("),this.expect(")"),this.usesDiePass=!0,{node:{k:"diePass"},type:"boolean"};if(this.at("("))return this.parseCall(t.text,t.pos);if(Object.prototype.hasOwnProperty.call(this.ctx.constants,t.text)){const e=this.ctx.constants[t.text];if(typeof e!="number"||!Number.isFinite(e))throw new o(`Constant ${JSON.stringify(t.text)} at position ${t.pos} is not a finite number`,t.pos);return{node:{k:"num",value:e},type:"number"}}throw new o(`Unknown name ${JSON.stringify(t.text)} at position ${t.pos}. Test values are read as t[1020], verdicts as testPass[1020] or specPass[1020]; other names must be declared in \`constants\`.`,t.pos)}throw new o(`Unexpected ${t.kind==="eof"?"end of expression":JSON.stringify(t.text)} at position ${t.pos}`,t.pos)}parseAccessor(t,e){this.expect("[");const{tests:n,range:i}=this.parseTestRef(e);this.expect("]");for(const u of n)this.checkAccessible(t,u,e);for(const u of n)this.reads.push({kind:t,test:u});const s=t==="t"?"number":"boolean";return i?{node:{k:"vec",kind:t,tests:n},type:s==="number"?"numberVec":"boolVec"}:{node:{k:"acc",kind:t,test:n[0]},type:s}}checkAccessible(t,e,n){const i=this.ctx.testDefs.get(e);if(i===void 0)throw new o(`Test ${e} at position ${n} is not declared in \`testDefs\``,n);if(t==="t"&&!c(i))throw new o(`t[${e}] at position ${n} reads a measured value, but test ${e} is functional (testType 'F') and has no measured value \u2014 use testPass[${e}] for its verdict`,n);if(t==="specPass"){if(!c(i))throw new o(`specPass[${e}] at position ${n} needs
|
|
2
|
+
`||n==="\r"){e++;continue}if(n>="0"&&n<="9"){const s=e;for(;e<r.length&&r[e]>="0"&&r[e]<="9";)e++;if(r[e]==="."&&r[e+1]!==".")for(e++;e<r.length&&r[e]>="0"&&r[e]<="9";)e++;if(r[e]==="e"||r[e]==="E"){const a=e;if(e++,(r[e]==="+"||r[e]==="-")&&e++,r[e]>="0"&&r[e]<="9")for(;e<r.length&&r[e]>="0"&&r[e]<="9";)e++;else e=a}const u=r.slice(s,e);t.push({kind:"num",text:u,pos:s,value:Number(u)});continue}if(/[A-Za-z_]/.test(n)){const s=e;for(;e<r.length&&/[A-Za-z0-9_]/.test(r[e]);)e++;t.push({kind:"ident",text:r.slice(s,e),pos:s});continue}const i=m.find(s=>r.startsWith(s,e));if(i){t.push({kind:"op",text:i,pos:e}),e+=i.length;continue}if(b.includes(n)){t.push({kind:"punc",text:n,pos:e}),e++;continue}throw new o(`Unexpected character ${JSON.stringify(n)} at position ${e}`,e)}return t.push({kind:"eof",text:"",pos:r.length}),t}class f{toks;ctx;p=0;reads=[];usesDiePass=!1;constructor(t,e){this.toks=t,this.ctx=e}peek(){return this.toks[this.p]}next(){return this.toks[this.p++]}at(t){const e=this.peek();return(e.kind==="op"||e.kind==="punc"||e.kind==="ident")&&e.text===t}eat(t){return this.at(t)?(this.p++,!0):!1}expect(t){if(!this.at(t)){const e=this.peek();throw new o(`Expected ${JSON.stringify(t)} but found ${e.kind==="eof"?"end of expression":JSON.stringify(e.text)} at position ${e.pos}`,e.pos)}return this.next()}parse(){const t=this.parseOr();return this.expectEnd(),t}parseTestRefOnly(){const t=this.parseTestRef(this.peek().pos);return this.expectEnd(),t}expectEnd(){const t=this.peek();if(t.kind!=="eof")throw new o(`Unexpected ${JSON.stringify(t.text)} at position ${t.pos}`,t.pos)}requireScalar(t,e,n){if(t.type==="numberVec"||t.type==="boolVec")throw new o(`${e} cannot be applied to a test range at position ${n} \u2014 a range must be reduced first, e.g. mean(t[1010..1015]) or all(testPass[1010..1015])`,n);return t.type}parseOr(){let t=this.parseAnd();for(;this.at("or");){const e=this.next().pos,n=this.parseAnd();this.requireBool(t,"or",e),this.requireBool(n,"or",e),t={node:{k:"binary",op:"or",left:t.node,right:n.node},type:"boolean"}}return t}parseAnd(){let t=this.parseNot();for(;this.at("and");){const e=this.next().pos,n=this.parseNot();this.requireBool(t,"and",e),this.requireBool(n,"and",e),t={node:{k:"binary",op:"and",left:t.node,right:n.node},type:"boolean"}}return t}requireBool(t,e,n){if(this.requireScalar(t,`Operator ${JSON.stringify(e)}`,n),t.type!=="boolean")throw new o(`Operator ${JSON.stringify(e)} needs a true/false operand at position ${n}, but got a number \u2014 compare it first, e.g. t[1020] > 5`,n)}parseNot(){if(this.at("not")){const t=this.next().pos,e=this.parseNot();return this.requireBool(e,"not",t),{node:{k:"unary",op:"not",arg:e.node},type:"boolean"}}return this.parseCompare()}parseCompare(){const t=this.parseAdd(),e=this.peek();if(e.kind==="op"&&["<","<=",">",">=","==","!="].includes(e.text)){this.next();const n=this.parseAdd(),i=this.requireScalar(t,`Operator ${JSON.stringify(e.text)}`,e.pos),s=this.requireScalar(n,`Operator ${JSON.stringify(e.text)}`,e.pos);if(i!==s)throw new o(`Cannot compare a ${i} with a ${s} at position ${e.pos}`,e.pos);if(i==="boolean"&&e.text!=="=="&&e.text!=="!=")throw new o(`Operator ${JSON.stringify(e.text)} cannot order true/false values at position ${e.pos}`,e.pos);return{node:{k:"binary",op:e.text,left:t.node,right:n.node},type:"boolean"}}return t}parseAdd(){let t=this.parseMul();for(;;){const e=this.peek();if(e.kind!=="op"||e.text!=="+"&&e.text!=="-")return t;this.next();const n=this.parseMul();this.requireNum(t,e.text,e.pos),this.requireNum(n,e.text,e.pos),t={node:{k:"binary",op:e.text,left:t.node,right:n.node},type:"number"}}}parseMul(){let t=this.parsePow();for(;;){const e=this.peek();if(e.kind!=="op"||!["*","/","%"].includes(e.text))return t;this.next();const n=this.parsePow();this.requireNum(t,e.text,e.pos),this.requireNum(n,e.text,e.pos),t={node:{k:"binary",op:e.text,left:t.node,right:n.node},type:"number"}}}parsePow(){const t=this.parseUnary(),e=this.peek();if(e.kind==="op"&&e.text==="^"){this.next();const n=this.parsePow();return this.requireNum(t,"^",e.pos),this.requireNum(n,"^",e.pos),{node:{k:"binary",op:"^",left:t.node,right:n.node},type:"number"}}return t}requireNum(t,e,n){if(this.requireScalar(t,`Operator ${JSON.stringify(e)}`,n),t.type!=="number")throw new o(`Operator ${JSON.stringify(e)} needs a numeric operand at position ${n}, but got a true/false value`,n)}parseUnary(){const t=this.peek();if(t.kind==="op"&&t.text==="-"){this.next();const e=this.parseUnary();return this.requireNum(e,"-",t.pos),{node:{k:"unary",op:"-",arg:e.node},type:"number"}}return this.parsePrimary()}parsePrimary(){const t=this.next();if(t.kind==="num")return{node:{k:"num",value:t.value},type:"number"};if(t.kind==="punc"&&t.text==="("){const e=this.parseOr();return this.expect(")"),e}if(t.kind==="ident"){if(d.includes(t.text))return this.parseAccessor(t.text,t.pos);if(t.text==="diePass")return this.expect("("),this.expect(")"),this.usesDiePass=!0,{node:{k:"diePass"},type:"boolean"};if(this.at("("))return this.parseCall(t.text,t.pos);if(Object.prototype.hasOwnProperty.call(this.ctx.constants,t.text)){const e=this.ctx.constants[t.text];if(typeof e!="number"||!Number.isFinite(e))throw new o(`Constant ${JSON.stringify(t.text)} at position ${t.pos} is not a finite number`,t.pos);return{node:{k:"num",value:e},type:"number"}}throw new o(`Unknown name ${JSON.stringify(t.text)} at position ${t.pos}. Test values are read as t[1020], verdicts as testPass[1020] or specPass[1020]; other names must be declared in \`constants\`.`,t.pos)}throw new o(`Unexpected ${t.kind==="eof"?"end of expression":JSON.stringify(t.text)} at position ${t.pos}`,t.pos)}parseAccessor(t,e){this.expect("[");const{tests:n,range:i}=this.parseTestRef(e);this.expect("]");for(const u of n)this.checkAccessible(t,u,e);for(const u of n)this.reads.push({kind:t,test:u});const s=t==="t"?"number":"boolean";return i?{node:{k:"vec",kind:t,tests:n},type:s==="number"?"numberVec":"boolVec"}:{node:{k:"acc",kind:t,test:n[0]},type:s}}checkAccessible(t,e,n){const i=this.ctx.testDefs.get(e);if(i===void 0)throw new o(`Test ${e} at position ${n} is not declared in \`testDefs\``,n);if(t==="t"&&!c(i))throw new o(`t[${e}] at position ${n} reads a measured value, but test ${e} is functional (testType 'F') and has no measured value \u2014 use testPass[${e}] for its verdict`,n);if(t==="specPass"){if(!c(i))throw new o(`specPass[${e}] at position ${n} needs test limits, but test ${e} is functional (testType 'F') \u2014 use testPass[${e}] for its recorded verdict`,n);if(!l(i))throw new o(`specPass[${e}] at position ${n} needs \`limitLow\` or \`limitHigh\` on test ${e}, which declares neither`,n)}}parseTestRef(t){const e=this.expectInt();if(!this.eat(".."))return{tests:[e],range:!1};const n=this.expectInt();if(n<e)throw new o(`Test range ${e}..${n} at position ${t} runs backwards`,t);const i=[...this.ctx.testDefs.keys()].filter(s=>s>=e&&s<=n).sort((s,u)=>s-u);if(i.length===0)throw new o(`Test range ${e}..${n} at position ${t} matches no declared test`,t);return{tests:i,range:!0}}expectInt(){const t=this.peek();if(t.kind!=="num"||!Number.isInteger(t.value))throw new o(`Expected a test number at position ${t.pos}`,t.pos);return this.next(),t.value}parseCall(t,e){const n=FUNCTIONS[t];if(n===void 0)throw new o(`Unknown function ${JSON.stringify(t)} at position ${e}. Available: ${Object.keys(FUNCTIONS).sort().join(", ")}`,e);this.expect("(");const i=[];if(!this.at(")"))do i.push(this.parseOr());while(this.eat(","));this.expect(")");const s=i.map(a=>a.type),u={k:"call",name:t,args:i.map(a=>a.node)};for(const a of n){if(a.vecOf!==void 0){if(s.length!==1)continue;const p=s[0];if(!(p==="numberVec"||p==="boolVec")||a.vecOf!=="any"&&a.vecOf!==p)continue;return{node:u,type:a.returns}}if(a.variadic){if(s.length<2||!s.every(p=>p==="number"))continue;return{node:u,type:a.returns}}if(a.arity!==void 0){if(s.length!==a.arity)continue;if(t==="if"){if(s[0]!=="boolean"||s[1]!=="number"||s[2]!=="number")continue;return{node:u,type:"number"}}if(!s.every(p=>p==="number"))continue;return{node:u,type:a.returns}}}throw new o(`${t}(${s.join(", ")}) at position ${e} is not a valid call \u2014 ${y(t,n)}`,e)}}function y(r,t){return`expected ${t.map(n=>n.vecOf==="boolVec"?`${r}(<true/false test range>)`:n.vecOf==="numberVec"?`${r}(<value test range>)`:n.vecOf==="any"?`${r}(<test range>)`:n.variadic?`${r}(number, number, \u2026)`:r==="if"?"if(<true/false>, number, number)":`${r}(${Array.from({length:n.arity??0},()=>"number").join(", ")})`).join(" or ")}`}export function parseExpression(r,t){try{if(r.length>MAX_EXPRESSION_LENGTH)return{ok:!1,message:`Expression is ${r.length} characters, over the ${MAX_EXPRESSION_LENGTH} limit`,position:MAX_EXPRESSION_LENGTH};const e=new f(h(r),t),{node:n,type:i}=e.parse();return i==="numberVec"||i==="boolVec"?{ok:!1,message:"Expression results in a test range, not a single value \u2014 reduce it, e.g. mean(t[1010..1015]) or all(testPass[1010..1015])",position:0}:{ok:!0,ast:n,type:i,reads:e.reads,usesDiePass:e.usesDiePass}}catch(e){if(e instanceof o)return{ok:!1,message:e.message,position:e.position};throw e}}export function parseTestReference(r,t){try{if(r.length>MAX_EXPRESSION_LENGTH)return{ok:!1,message:`Test reference is ${r.length} characters, over the ${MAX_EXPRESSION_LENGTH} limit`,position:MAX_EXPRESSION_LENGTH};const{tests:e,range:n}=new f(h(r),{testDefs:t,constants:{}}).parseTestRefOnly();return{ok:!0,tests:e,range:n}}catch(e){if(e instanceof o)return{ok:!1,message:e.message,position:e.position};throw e}}export const MAX_EXPRESSION_LENGTH=2e3;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
export { getDieKey } from './buildView.js';
|
|
2
|
-
export type {
|
|
3
|
-
export
|
|
2
|
+
export type { PlotMode } from './buildView.js';
|
|
3
|
+
export { buildWaferMap, getTestPassStatus } from './buildWaferMap.js';
|
|
4
|
+
export type { DerivedTestDef, DieResult, WaferConfig, DieConfig, ReticleConfig, LotStackConfig, TestDef, BinDef, MetadataFieldDef, WaferMapInputBase, WaferMapInputSingle, WaferMapInputLotStack, WaferMapInputLayout, WaferMapInput, YieldSummary, WaferWarning, WaferMapResult, } from './buildWaferMap.js';
|
|
4
5
|
export type { WaferMetadata, DieMetadata } from '../core/metadata.js';
|
|
5
|
-
export
|
|
6
|
-
export type {
|
|
6
|
+
export { registerValueColorScheme, listValueColorSchemes, resolveValueColorFn, registerBinColorScheme, listBinColorSchemes } from './colorSchemes.js';
|
|
7
|
+
export type { ValueColorScheme, BinColorScheme } from './colorSchemes.js';
|
|
8
|
+
export type { BinColors, BinColorSource, MapBinColorOptions } from './binColors.js';
|
|
7
9
|
export { binColorsForMaps } from './binColors.js';
|
|
8
10
|
export { DERIVED_MARK, DERIVED_KEY } from './testLabel.js';
|
|
9
|
-
export { valueToViridis, valueToGreyscale, getValueColorScheme, buildView, buildHoverText, buildMapTitle, resolveBinColors, getBinColorScheme, contrastTextColor, dieHasTestData, isParametricTest, getDieTestValue, STANDARD_WAFER_DIAMETERS_MM, } from './deprecated.js';
|
|
10
11
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export{getDieKey}from"./buildView.js";export
|
|
1
|
+
export{getDieKey}from"./buildView.js";export{buildWaferMap,getTestPassStatus}from"./buildWaferMap.js";export{registerValueColorScheme,listValueColorSchemes,resolveValueColorFn,registerBinColorScheme,listBinColorSchemes}from"./colorSchemes.js";export{binColorsForMaps}from"./binColors.js";export{DERIVED_MARK,DERIVED_KEY}from"./testLabel.js";
|
|
@@ -22,6 +22,10 @@
|
|
|
22
22
|
* `null` means no verdict and must be treated as no-data by every caller, never
|
|
23
23
|
* as a pass and never as a fail.
|
|
24
24
|
*
|
|
25
|
+
* A value exactly equal to a limit passes unless the test says otherwise
|
|
26
|
+
* (`limitLowInclusive`/`limitHighInclusive: false`, which STDF's `PARM_FLG` bits
|
|
27
|
+
* 6/7 and ATDF's Limit Compare letters state per test).
|
|
28
|
+
*
|
|
25
29
|
* Out-of-spec *classification* depends ONLY on whether limits are defined — never on
|
|
26
30
|
* `colorbarRangeMode`. An out-of-spec die is always flagged when limits exist, regardless
|
|
27
31
|
* of how the colorbar is scaled. The *form* of the indication, decided in
|
|
@@ -36,12 +40,25 @@ export type SpecCategory = 'pass' | 'failHigh' | 'failLow';
|
|
|
36
40
|
export interface SpecLimits {
|
|
37
41
|
limitLow?: number;
|
|
38
42
|
limitHigh?: number;
|
|
43
|
+
/** A value equal to `limitLow` passes. Default `true`; STDF `PARM_FLG` bit 6. */
|
|
44
|
+
limitLowInclusive?: boolean;
|
|
45
|
+
/** A value equal to `limitHigh` passes. Default `true`; STDF `PARM_FLG` bit 7. */
|
|
46
|
+
limitHighInclusive?: boolean;
|
|
39
47
|
}
|
|
40
48
|
/**
|
|
41
49
|
* Classify `value` against `limits`. Returns `null` when no verdict is possible:
|
|
42
50
|
* no value, a non-finite value, or a test with neither limit defined.
|
|
43
51
|
*/
|
|
44
52
|
export declare function classifySpec(value: number | undefined, limits: SpecLimits | undefined): SpecCategory | null;
|
|
53
|
+
/** True for a verdict that is outside the limits. */
|
|
54
|
+
export declare function isOutOfSpec(category: SpecCategory | null): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* How many values in an ascending, finite array `classifySpec` would judge out of
|
|
57
|
+
* spec — the same rule, counted by binary search instead of one call per value,
|
|
58
|
+
* for callers that already hold the values sorted. `tests/specLimits.test.mjs`
|
|
59
|
+
* holds the two in agreement.
|
|
60
|
+
*/
|
|
61
|
+
export declare function countOutOfSpecSorted(sorted: ArrayLike<number>, limits: SpecLimits): number;
|
|
45
62
|
/** True when `limits` can produce a verdict at all — i.e. at least one limit is
|
|
46
63
|
* defined. Used to reject `specPass[n]` at parse time rather than silently
|
|
47
64
|
* yielding no-data for every die. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export function classifySpec(n
|
|
1
|
+
export function classifySpec(i,n){return i===void 0||!Number.isFinite(i)||n===void 0||n.limitLow===void 0&&n.limitHigh===void 0?null:n.limitLow!==void 0&&(n.limitLowInclusive===!1?i<=n.limitLow:i<n.limitLow)?"failLow":n.limitHigh!==void 0&&(n.limitHighInclusive===!1?i>=n.limitHigh:i>n.limitHigh)?"failHigh":"pass"}export function isOutOfSpec(i){return i==="failLow"||i==="failHigh"}export function countOutOfSpecSorted(i,n){let u=0,h=0;if(n.limitLow!==void 0){const l=n.limitLow,o=n.limitLowInclusive===!1?e=>e<=l:e=>e<l;let f=0,t=i.length;for(;f<t;){const e=f+t>>1;o(i[e])?f=e+1:t=e}u=f}if(n.limitHigh!==void 0){const l=n.limitHigh,o=n.limitHighInclusive===!1?e=>e<l:e=>e<=l;let f=0,t=i.length;for(;f<t;){const e=f+t>>1;o(i[e])?f=e+1:t=e}h=i.length-Math.max(f,u)}return u+h}export function hasSpecLimits(i){return i!==void 0&&(i.limitLow!==void 0||i.limitHigh!==void 0)}
|