@wafertools/wafermap 0.33.0 → 0.34.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 +6 -6
- package/CHANGELOG.md +212 -0
- package/README.md +5 -5
- package/dist/packages/canvas-adapter/chartPopulation.d.ts +15 -10
- package/dist/packages/canvas-adapter/chartPopulation.js +1 -1
- package/dist/packages/canvas-adapter/charts/barPanel.d.ts +7 -0
- package/dist/packages/canvas-adapter/charts/barPanel.js +1 -1
- package/dist/packages/canvas-adapter/charts/binCluster.d.ts +7 -1
- package/dist/packages/canvas-adapter/charts/binCluster.js +1 -1
- 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 +55 -3
- package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
- package/dist/packages/canvas-adapter/charts/correlation.js +1 -2
- package/dist/packages/canvas-adapter/charts/groupedBarPlot.d.ts +2 -0
- package/dist/packages/canvas-adapter/charts/groupedBarPlot.js +1 -1
- package/dist/packages/canvas-adapter/charts/histogram.d.ts +16 -0
- package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
- package/dist/packages/canvas-adapter/charts/palette.d.ts +2 -0
- package/dist/packages/canvas-adapter/charts/palette.js +1 -1
- package/dist/packages/canvas-adapter/charts/plotChart.d.ts +27 -0
- package/dist/packages/canvas-adapter/charts/plotChart.js +1 -0
- package/dist/packages/canvas-adapter/charts/pointInteractions.d.ts +45 -0
- package/dist/packages/canvas-adapter/charts/pointInteractions.js +1 -0
- package/dist/packages/canvas-adapter/charts/regionYieldDiagram.d.ts +2 -0
- package/dist/packages/canvas-adapter/charts/regionYieldDiagram.js +1 -1
- package/dist/packages/canvas-adapter/charts/scatter.d.ts +19 -0
- package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
- package/dist/packages/canvas-adapter/charts/sweep.d.ts +12 -0
- package/dist/packages/canvas-adapter/charts/sweep.js +1 -1
- package/dist/packages/canvas-adapter/charts/testPassRate.d.ts +7 -0
- package/dist/packages/canvas-adapter/charts/testPassRate.js +1 -1
- package/dist/packages/canvas-adapter/charts/trend.d.ts +11 -0
- package/dist/packages/canvas-adapter/charts/trend.js +1 -1
- package/dist/packages/canvas-adapter/confirmDialog.d.ts +13 -0
- package/dist/packages/canvas-adapter/confirmDialog.js +1 -0
- package/dist/packages/canvas-adapter/dataTab.d.ts +131 -0
- package/dist/packages/canvas-adapter/dataTab.js +1 -0
- package/dist/packages/canvas-adapter/dieList.d.ts +39 -4
- package/dist/packages/canvas-adapter/dieList.js +6 -7
- package/dist/packages/canvas-adapter/drilldown.d.ts +20 -3
- package/dist/packages/canvas-adapter/drilldown.js +1 -1
- package/dist/packages/canvas-adapter/guideWindow.d.ts +96 -0
- package/dist/packages/canvas-adapter/guideWindow.js +1 -0
- package/dist/packages/canvas-adapter/identityHeader.d.ts +16 -0
- package/dist/packages/canvas-adapter/identityHeader.js +1 -1
- package/dist/packages/canvas-adapter/index.d.ts +3 -3
- package/dist/packages/canvas-adapter/index.js +1 -1
- package/dist/packages/canvas-adapter/insightsTab.d.ts +73 -54
- package/dist/packages/canvas-adapter/insightsTab.js +1 -1
- package/dist/packages/canvas-adapter/maplessSummary.d.ts +2 -2
- package/dist/packages/canvas-adapter/plotEditor.d.ts +29 -0
- package/dist/packages/canvas-adapter/plotEditor.js +1 -0
- package/dist/packages/canvas-adapter/plotItems.d.ts +32 -0
- package/dist/packages/canvas-adapter/plotItems.js +1 -0
- package/dist/packages/canvas-adapter/plotModal.d.ts +30 -0
- package/dist/packages/canvas-adapter/plotModal.js +1 -0
- package/dist/packages/canvas-adapter/plotStore.d.ts +52 -0
- package/dist/packages/canvas-adapter/plotStore.js +1 -0
- package/dist/packages/canvas-adapter/plotTab.d.ts +30 -0
- package/dist/packages/canvas-adapter/plotTab.js +1 -0
- package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +3 -2
- package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferMap.d.ts +21 -2
- package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
- package/dist/packages/canvas-adapter/summaryPanel.d.ts +27 -8
- package/dist/packages/canvas-adapter/summaryPanel.js +2 -4
- package/dist/packages/canvas-adapter/summaryRail.d.ts +7 -0
- package/dist/packages/canvas-adapter/summaryRail.js +1 -0
- package/dist/packages/canvas-adapter/sweepEditor.d.ts +17 -0
- package/dist/packages/canvas-adapter/sweepEditor.js +1 -0
- package/dist/packages/canvas-adapter/tableExport.d.ts +22 -0
- package/dist/packages/canvas-adapter/tableExport.js +3 -0
- package/dist/packages/canvas-adapter/toolbar.d.ts +25 -97
- 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 +337 -42
- package/dist/packages/canvas-adapter/version.d.ts +2 -2
- package/dist/packages/canvas-adapter/version.js +1 -1
- package/dist/packages/canvas-adapter/viewSwitch.d.ts +13 -0
- package/dist/packages/canvas-adapter/viewSwitch.js +1 -0
- package/dist/packages/canvas-adapter/virtualTable.d.ts +44 -0
- package/dist/packages/canvas-adapter/virtualTable.js +1 -0
- package/dist/packages/core/classify.d.ts +3 -2
- package/dist/packages/core/classify.js +1 -1
- package/dist/packages/core/passBins.d.ts +17 -4
- package/dist/packages/core/passBins.js +1 -1
- package/dist/packages/core/ringCount.d.ts +22 -0
- package/dist/packages/core/ringCount.js +1 -0
- package/dist/packages/core/tableCsv.d.ts +43 -0
- package/dist/packages/core/tableCsv.js +3 -0
- package/dist/packages/renderer/binColors.d.ts +6 -6
- package/dist/packages/renderer/binColors.js +1 -1
- package/dist/packages/renderer/buildView.d.ts +1 -1
- package/dist/packages/renderer/buildView.js +1 -1
- package/dist/packages/renderer/buildWaferMap.js +1 -1
- package/dist/packages/stats/analyzeWaferLot.js +1 -1
- package/dist/packages/stats/analyzeWaferMap.js +1 -1
- package/dist/packages/stats/binPareto.d.ts +1 -1
- package/dist/packages/stats/binPareto.js +1 -1
- package/dist/packages/stats/histogram.d.ts +27 -0
- package/dist/packages/stats/histogram.js +1 -1
- package/dist/packages/stats/index.d.ts +2 -0
- package/dist/packages/stats/index.js +1 -1
- package/dist/packages/stats/lotDrift.js +1 -1
- package/dist/packages/stats/lotPattern.js +1 -1
- package/dist/packages/stats/math.d.ts +9 -6
- package/dist/packages/stats/math.js +1 -1
- package/dist/packages/stats/patternClassification.d.ts +1 -1
- package/dist/packages/stats/patternClassification.js +1 -1
- package/dist/packages/stats/plotData.d.ts +258 -0
- package/dist/packages/stats/plotData.js +1 -0
- package/dist/packages/stats/plotId.d.ts +3 -0
- package/dist/packages/stats/plotId.js +1 -0
- package/dist/packages/stats/plotSpec.d.ts +119 -0
- package/dist/packages/stats/plotSpec.js +4 -0
- package/dist/packages/stats/presentation.d.ts +2 -0
- package/dist/packages/stats/presentation.js +1 -1
- package/dist/packages/stats/regions.d.ts +6 -0
- package/dist/packages/stats/regions.js +1 -1
- package/dist/packages/stats/renderSummaryReport.d.ts +8 -3
- package/dist/packages/stats/renderSummaryReport.js +15 -15
- package/dist/packages/stats/reportHtml.d.ts +1 -1
- package/dist/packages/stats/reportHtml.js +36 -29
- package/dist/packages/stats/scatter.d.ts +6 -0
- package/dist/packages/stats/scatter.js +1 -1
- package/dist/packages/stats/sweep.d.ts +37 -1
- package/dist/packages/stats/sweep.js +1 -1
- package/dist/packages/stats/sweepText.d.ts +14 -0
- package/dist/packages/stats/sweepText.js +1 -0
- package/dist/packages/stats/sweepXFromName.d.ts +14 -0
- package/dist/packages/stats/sweepXFromName.js +1 -1
- package/dist/packages/stats/synthesis.d.ts +14 -4
- package/dist/packages/stats/synthesis.js +1 -1
- package/dist/packages/stats/testPassRate.d.ts +6 -0
- package/dist/packages/stats/testPassRate.js +1 -1
- package/dist/packages/stats/trend.js +1 -1
- package/dist/packages/stats/types.d.ts +1 -1
- package/dist/packages/stats/yield.d.ts +15 -6
- package/dist/packages/stats/yield.js +1 -1
- package/llms.txt +15 -3
- package/package.json +3 -3
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import type { Die } from '../core/dies.js';
|
|
2
|
+
import type { Wafer } from '../core/wafer.js';
|
|
3
|
+
import type { WaferMetadata } from '../core/metadata.js';
|
|
4
|
+
import { type TestDef } from '../renderer/buildWaferMap.js';
|
|
5
|
+
import type { FacetCuration } from './facets.js';
|
|
6
|
+
import type { PlotSpec, PlotField, PlotAggregate } from './plotSpec.js';
|
|
7
|
+
/** One wafer of the population. The same shape a drilldown source's items have. */
|
|
8
|
+
export interface PlotItem {
|
|
9
|
+
label: string;
|
|
10
|
+
dies: readonly Die[];
|
|
11
|
+
waferIndex?: number;
|
|
12
|
+
metadata?: WaferMetadata;
|
|
13
|
+
/** This wafer's own pass bins, from its built result: every yield the plot states judges by them. */
|
|
14
|
+
passBins: readonly number[];
|
|
15
|
+
/** This wafer's own ring count, from its built result: a ring or quadrant of its dies is the map's ring. */
|
|
16
|
+
ringCount: number;
|
|
17
|
+
/** Needed for ring and quadrant. */
|
|
18
|
+
wafer?: Wafer;
|
|
19
|
+
}
|
|
20
|
+
export interface PlotContext {
|
|
21
|
+
/** The population's reconciled test list (`mergeTestDefs`). */
|
|
22
|
+
testDefs?: readonly TestDef[];
|
|
23
|
+
/** The Insights tab's Group by key, which "follow Group by" resolves to. Absent outside the tab. */
|
|
24
|
+
groupBy?: string;
|
|
25
|
+
curation?: Record<string, FacetCuration>;
|
|
26
|
+
}
|
|
27
|
+
export type FieldKind = 'numeric' | 'categorical';
|
|
28
|
+
/** A test's limits: the tester's (`limitLow`/`limitHigh`) and the specification's (`specLow`/`specHigh`). Any may be absent. */
|
|
29
|
+
export interface TestLimits {
|
|
30
|
+
limitLow?: number;
|
|
31
|
+
limitHigh?: number;
|
|
32
|
+
specLow?: number;
|
|
33
|
+
specHigh?: number;
|
|
34
|
+
}
|
|
35
|
+
/** A field resolved over the population, one value per die (`level: 'die'`) or per wafer (`'wafer'`). */
|
|
36
|
+
export interface FieldColumn {
|
|
37
|
+
field: PlotField;
|
|
38
|
+
/** The reader's name for it ("Vth", "Split", "Yield"). */
|
|
39
|
+
label: string;
|
|
40
|
+
unit?: string;
|
|
41
|
+
level: 'die' | 'wafer';
|
|
42
|
+
kind: FieldKind;
|
|
43
|
+
/** Numeric values; NaN is missing. Present for a numeric field, and for a metadata field whose every value is a number. */
|
|
44
|
+
num?: Float64Array;
|
|
45
|
+
/** Category text; undefined is missing. Present for a categorical field and for metadata (numbers as text). */
|
|
46
|
+
cat?: (string | undefined)[];
|
|
47
|
+
/** Judged dies behind each value (a wafer's yield), or 1 per judged die (a die's pass), so a yield can be pooled. */
|
|
48
|
+
weights?: Float64Array;
|
|
49
|
+
/** The limits of a measured test, as the population's reconciled test list states them. */
|
|
50
|
+
limits?: TestLimits;
|
|
51
|
+
/** Set on a yield taken per die (100 for a passing die, 0 for a failing one): its mean over any set of dies is their yield. */
|
|
52
|
+
perDie?: true;
|
|
53
|
+
/** Why this field cannot be used on this population. */
|
|
54
|
+
issue?: string;
|
|
55
|
+
}
|
|
56
|
+
export interface PlotPoint {
|
|
57
|
+
x: number;
|
|
58
|
+
y: number;
|
|
59
|
+
group: number;
|
|
60
|
+
item: number;
|
|
61
|
+
die?: Die; /** The colour field's value, when it is continuous. */
|
|
62
|
+
value?: number;
|
|
63
|
+
}
|
|
64
|
+
/** What one mark stands for: the wafer it is on, and the die when it is one die (not a wafer's aggregate). */
|
|
65
|
+
export interface PlotUnit {
|
|
66
|
+
item: number;
|
|
67
|
+
die?: Die;
|
|
68
|
+
}
|
|
69
|
+
export type PlotMarks = {
|
|
70
|
+
type: 'scatter';
|
|
71
|
+
points: PlotPoint[];
|
|
72
|
+
}
|
|
73
|
+
/** `units[g][k]` is the unit behind `values[g][k]`, so a bar can say which dies (or wafers) it counts. */
|
|
74
|
+
| {
|
|
75
|
+
type: 'histogram';
|
|
76
|
+
values: number[][];
|
|
77
|
+
units: number[][];
|
|
78
|
+
unit: (u: number) => PlotUnit;
|
|
79
|
+
} | {
|
|
80
|
+
type: 'box' | 'bar';
|
|
81
|
+
categories: string[];
|
|
82
|
+
cells: number[][][];
|
|
83
|
+
cellUnits: number[][][];
|
|
84
|
+
values: number[][];
|
|
85
|
+
categoryItems?: number[];
|
|
86
|
+
unit: (u: number) => PlotUnit;
|
|
87
|
+
} | {
|
|
88
|
+
type: 'line';
|
|
89
|
+
xs: number[];
|
|
90
|
+
values: number[][];
|
|
91
|
+
counts: number[][];
|
|
92
|
+
cellUnits: number[][][];
|
|
93
|
+
unit: (u: number) => PlotUnit;
|
|
94
|
+
};
|
|
95
|
+
/** `label` is the axis title WITHOUT its unit: the chart appends the unit, scaled to the ticks ("µA" for 0.000861 A). */
|
|
96
|
+
export interface ResolvedAxis {
|
|
97
|
+
label: string;
|
|
98
|
+
unit?: string;
|
|
99
|
+
scale: 'linear' | 'log';
|
|
100
|
+
min?: number;
|
|
101
|
+
max?: number;
|
|
102
|
+
reverse?: boolean;
|
|
103
|
+
/** The limits of the test this axis measures, when it measures one: the chart draws them as lines. */
|
|
104
|
+
limits?: TestLimits;
|
|
105
|
+
}
|
|
106
|
+
export interface ResolvedPlot {
|
|
107
|
+
spec: PlotSpec;
|
|
108
|
+
/** Reasons the plot cannot be drawn on this population. Empty when `marks` is set. */
|
|
109
|
+
issues: string[];
|
|
110
|
+
/** Non-blocking things the reader should be told (a log axis that fell back to linear). */
|
|
111
|
+
notes: string[];
|
|
112
|
+
marks?: PlotMarks;
|
|
113
|
+
level: 'die' | 'wafer';
|
|
114
|
+
/** The title the plot has until the user types one. */
|
|
115
|
+
autoTitle: string;
|
|
116
|
+
x?: ResolvedAxis;
|
|
117
|
+
y?: ResolvedAxis;
|
|
118
|
+
/** Colour groups in legend order; `['']` when there is no colour (or the colour is continuous, see `colorScale`). */
|
|
119
|
+
groups: string[];
|
|
120
|
+
/** Set when a scatter is coloured by a continuous field: the range its colourbar covers. */
|
|
121
|
+
colorScale?: {
|
|
122
|
+
label: string;
|
|
123
|
+
unit?: string;
|
|
124
|
+
lo: number;
|
|
125
|
+
hi: number;
|
|
126
|
+
};
|
|
127
|
+
colorLabel?: string;
|
|
128
|
+
/** How values were combined, in words ("mean of Vth per wafer"), when they were. */
|
|
129
|
+
aggregation?: string;
|
|
130
|
+
/** The scope: every wafer and die handed in. */
|
|
131
|
+
population: {
|
|
132
|
+
wafers: number;
|
|
133
|
+
dies: number;
|
|
134
|
+
};
|
|
135
|
+
/** Marks drawn. */
|
|
136
|
+
plotted: number;
|
|
137
|
+
/** Marks left out because a value was missing, by the field it was missing from. */
|
|
138
|
+
omitted: Array<{
|
|
139
|
+
field: string;
|
|
140
|
+
count: number;
|
|
141
|
+
}>;
|
|
142
|
+
}
|
|
143
|
+
/** The reader's name for a field, whether or not the population has it. */
|
|
144
|
+
export declare function fieldLabel(field: PlotField, testDefs?: readonly TestDef[]): string;
|
|
145
|
+
/** One value for a set of numbers. `count` ignores missing values; the rest of an empty set is NaN. */
|
|
146
|
+
export declare function combine(values: readonly number[], how: PlotAggregate): number;
|
|
147
|
+
/** What the cross-field rules need to know of a field: its level, whether it is a number, and whether it is a yield. */
|
|
148
|
+
export interface FieldFacts {
|
|
149
|
+
label: string;
|
|
150
|
+
level: 'die' | 'wafer';
|
|
151
|
+
kind: FieldKind;
|
|
152
|
+
yield: boolean;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Whether the chosen fields can be drawn together, as the reason in two lengths: `long` for the chart card, `short` for the
|
|
156
|
+
* editor's field list, where a choice that would break the plot is greyed with it. ONE function, so the list and the chart
|
|
157
|
+
* can never disagree about what is allowed. Only rules that name two fields are here; a missing field is never a conflict.
|
|
158
|
+
*
|
|
159
|
+
* A yield is passing dies over judged dies, so it can be taken over any set of dies, but it is held per wafer: splitting it
|
|
160
|
+
* by a die-level field (ring, quadrant, bin, die position) needs the verdict per die, which a bar of pooled yield and a line
|
|
161
|
+
* have. Every other chart of it would be one wafer figure pretending to be per die.
|
|
162
|
+
*/
|
|
163
|
+
export declare function combinationIssue(mark: PlotSpec['chart'], f: {
|
|
164
|
+
x?: FieldFacts;
|
|
165
|
+
y?: FieldFacts;
|
|
166
|
+
color?: FieldFacts;
|
|
167
|
+
}, o?: {
|
|
168
|
+
aggregate?: PlotAggregate;
|
|
169
|
+
level?: 'die' | 'wafer';
|
|
170
|
+
continuousColor?: boolean;
|
|
171
|
+
}): {
|
|
172
|
+
long: string;
|
|
173
|
+
short: string;
|
|
174
|
+
} | undefined;
|
|
175
|
+
/** Resolves `spec` over `items`. Never throws for a plot that cannot be drawn: it returns the reasons. */
|
|
176
|
+
export declare function resolvePlot(spec: PlotSpec, items: readonly PlotItem[], ctx?: PlotContext): ResolvedPlot;
|
|
177
|
+
/** The plot's title: the one the user typed, else the automatic one. */
|
|
178
|
+
export declare function plotTitle(resolved: ResolvedPlot): string;
|
|
179
|
+
/** The statement every plot carries about what it shows ("6 wafers · 3,966 dies · 12 dies without Ioff not plotted"). */
|
|
180
|
+
export declare function plotFootnote(resolved: ResolvedPlot): string;
|
|
181
|
+
export type FieldGroup = 'Tests' | 'Die' | 'Wafer';
|
|
182
|
+
/** One entry of the editor's field list. */
|
|
183
|
+
export interface FieldOption {
|
|
184
|
+
field: PlotField;
|
|
185
|
+
/** The list text: the name, plus the test number so a filter box finds either. */
|
|
186
|
+
label: string;
|
|
187
|
+
/** The name alone ("Vth"), as a title would write it. */
|
|
188
|
+
name: string;
|
|
189
|
+
/** For a lot field: how many different values the wafers in view carry. */
|
|
190
|
+
distinct?: number;
|
|
191
|
+
group: FieldGroup;
|
|
192
|
+
kind: FieldKind;
|
|
193
|
+
level: 'die' | 'wafer';
|
|
194
|
+
/** Can divide the data into series or categories (a colour, a bar's X). */
|
|
195
|
+
categorical: boolean;
|
|
196
|
+
}
|
|
197
|
+
/** A stable string for a field, for list values and comparisons. */
|
|
198
|
+
export declare function fieldKey(f: PlotField): string;
|
|
199
|
+
/**
|
|
200
|
+
* Everything a plot could put on an axis or a colour over this population: its parametric tests, the die fields it
|
|
201
|
+
* has data for, and the wafer and lot fields. Offered only when the population has something to show for it (no
|
|
202
|
+
* soft-bin entry for a lot with no soft bins), so the list is the lot's, not the library's.
|
|
203
|
+
*/
|
|
204
|
+
export declare function fieldCatalogue(items: readonly PlotItem[], ctx?: PlotContext): FieldOption[];
|
|
205
|
+
export type PlotRole = 'x' | 'y' | 'color';
|
|
206
|
+
/**
|
|
207
|
+
* The fields that make sense for a role of a mark, so the editor never offers a choice that can only fail:
|
|
208
|
+
* values must be numeric; a bar or box groups by a category (or a lot field with a handful of values); a colour is
|
|
209
|
+
* always a category, since a continuous colour is not drawn yet.
|
|
210
|
+
*/
|
|
211
|
+
export declare function fieldsForRole(catalogue: readonly FieldOption[], mark: PlotSpec['chart'], role: PlotRole): FieldOption[];
|
|
212
|
+
/**
|
|
213
|
+
* A first plot for this population: the scatter of the first two tests, a histogram when there is one, a bar of
|
|
214
|
+
* yield by wafer when there are none. Never an empty editor.
|
|
215
|
+
*/
|
|
216
|
+
export declare function defaultPlot(catalogue: readonly FieldOption[], id: string): PlotSpec;
|
|
217
|
+
export interface TitleDrift {
|
|
218
|
+
/** Fields the title names that the plot does not use: the title is describing something else. */
|
|
219
|
+
names: string[];
|
|
220
|
+
/** Fields the plot plots that the title leaves out, when the title does describe its fields. */
|
|
221
|
+
omits: string[];
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Whether a title the reader TYPED still describes its plot. A title written for one pair of tests goes on naming them
|
|
225
|
+
* after the fields change, and nothing else would tell the reader the chart is no longer what it says. Two checks:
|
|
226
|
+
*
|
|
227
|
+
* - the title names a test (or another measured field) the plot does not use;
|
|
228
|
+
* - the title names one of the plot's X or Y fields but not the other, so it is half a description.
|
|
229
|
+
*
|
|
230
|
+
* A title that names none of the plot's fields is free text ("Process check") and is left alone. Lot fields are not
|
|
231
|
+
* checked for the first, since their names are everyday words and a followed colour changes with Group by. An
|
|
232
|
+
* automatic title is always accurate and is never checked.
|
|
233
|
+
*/
|
|
234
|
+
export declare function titleDrift(spec: PlotSpec, catalogue: readonly FieldOption[]): TitleDrift;
|
|
235
|
+
/** `titleDrift` in a sentence for the reader, or null when the title is fine. */
|
|
236
|
+
export declare function describeTitleDrift(d: TitleDrift): string | null;
|
|
237
|
+
/**
|
|
238
|
+
* One example of each chart type that this population can show, so a reader who has not built a plot before has
|
|
239
|
+
* something to start from: a scatter of the first two tests, a histogram of the first, a box of the first by wafer, a
|
|
240
|
+
* bar of yield by the first lot field that divides the wafers (by wafer when none does), and the first test over wafer
|
|
241
|
+
* order as a line, and a sweep of the first few tests in test order. A type is left out when the population cannot
|
|
242
|
+
* support it (a line needs two wafers, a sweep two tests). Untitled, so each one is named by what it plots; the sweep,
|
|
243
|
+
* which has no fields to name it by, is titled "Example sweep".
|
|
244
|
+
*/
|
|
245
|
+
export declare function examplePlots(catalogue: readonly FieldOption[], waferCount: number, newId: () => string): PlotSpec[];
|
|
246
|
+
/**
|
|
247
|
+
* The test a click on this plot should open a wafer on: the plot's own measurement, so the map shows the values the
|
|
248
|
+
* plot is about rather than the bins. A scatter takes its X (as the Insights scatter does), then its Y, then its
|
|
249
|
+
* colour; every other chart type its values. `undefined` when the plot has no test in it (yield against wafer order),
|
|
250
|
+
* where the wafer's own default map is the right one.
|
|
251
|
+
*/
|
|
252
|
+
export declare function plotTestNumber(spec: PlotSpec): number | undefined;
|
|
253
|
+
/**
|
|
254
|
+
* A new sweep to start from: one series of the lot's first few parametric tests, in test order, on the ordinal axis
|
|
255
|
+
* (no X scale is claimed). Never an empty editor; the reader edits the tests and adds the series they meant.
|
|
256
|
+
*/
|
|
257
|
+
export declare function defaultSweep(testDefs: readonly TestDef[], id: string): PlotSpec;
|
|
258
|
+
//# sourceMappingURL=plotData.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{hasPosition as te}from"../core/dies.js";import{classifyDie as Re}from"../core/classify.js";import{testValue as je,testsPresent as Ce}from"../core/dieTable.js";import{compareNatural as Fe,minOf as ke,maxOf as Se}from"../core/utils.js";import{isParametricTest as Ie}from"../renderer/buildWaferMap.js";import{facetValueOf as qe,buildFacetTable as Ge,FACET_NONE_VALUE as Le}from"./facets.js";import{yieldCounts as Te}from"./yield.js";import{requireRingCount as Xe}from"../core/ringCount.js";import{sameTestName as He}from"./sweep.js";import{sameField as ge,plotIssue as ze,isField as he}from"./plotSpec.js";function Ve(e){if(!e)return;const t={};for(const n of["limitLow","limitHigh","specLow","specHigh"])Number.isFinite(e[n])&&(t[n]=e[n]);return Object.keys(t).length?t:void 0}function Ze(e){let t=0;for(const c of e)t+=c.dies.length;const n=new Array(t),s=new Uint32Array(t);let o=0;return e.forEach((c,f)=>{for(const l of c.dies)n[o]=l,s[o++]=f}),{dies:n,item:s}}const K={wafer:"Wafer",x:"Die X",y:"Die Y",ring:"Ring",quadrant:"Quadrant",hbin:"Hard bin",sbin:"Soft bin",site:"Site",yield:"Yield",dieCount:"Dies",waferOrder:"Wafer order"};export function fieldLabel(e,t){return"test"in e?t?.find(n=>n.testNumber===e.test)?.name||e.name||`Test ${e.test}`:"builtin"in e?K[e.builtin]??String(e.builtin):ne(e.meta)}function ne(e){const t=e.replace(/[_-]+/g," ").replace(/([a-z0-9])([A-Z])/g,"$1 $2").trim();return t?t[0].toUpperCase()+t.slice(1):e}const Ke=/^[+-]?(?:\d+\.?\d*|\.\d+)$/;function q(e,t){const{items:n,rows:s,ctx:o}=t,c=fieldLabel(e,o.testDefs),f={field:e,label:c},l=s.dies.length,y=n.length;if("test"in e){const d=o.testDefs?.find(a=>a.testNumber===e.test);if(d===void 0){if(t.present??=new Set(s.dies.length?Ce(s.dies,"values"):[]),!t.present.has(e.test))return{...f,level:"die",kind:"numeric",issue:`Needs test ${e.test}${e.name?` (${e.name})`:""}, which is not in these dies`}}else if(e.name&&d.name&&!He(e.name,d.name))return{...f,level:"die",kind:"numeric",unit:d.unit,issue:`Test ${e.test} is "${e.name}" in this plot and "${d.name}" here`};const b=new Float64Array(l);for(let a=0;a<l;a++)b[a]=je(s.dies[a],e.test)??NaN;return{...f,level:"die",kind:"numeric",unit:d?.unit,num:b,limits:Ve(d)}}if("meta"in e){const d=n.map(u=>qe(u.metadata,e.meta,o.curation));if(d.every(u=>u===void 0))return{...f,level:"wafer",kind:"categorical",cat:d,issue:`No wafer has a "${ne(e.meta)}" value`};const b=d.map(u=>u===void 0||!Ke.test(u.trim())?NaN:Number(u)),a=d.every((u,r)=>u===void 0||Number.isFinite(b[r]));return{...f,level:"wafer",kind:a?"numeric":"categorical",cat:d,num:a?Float64Array.from(b):void 0}}switch(e.builtin){case"wafer":return{...f,level:"wafer",kind:"categorical",cat:n.map(d=>d.label)};case"waferOrder":return{...f,level:"wafer",kind:"numeric",num:Float64Array.from(n,(d,b)=>b+1)};case"dieCount":return{...f,level:"wafer",kind:"numeric",num:Float64Array.from(n,d=>d.dies.length)};case"yield":{const d=new Float64Array(y),b=new Float64Array(y);return n.forEach((a,u)=>{const{pass:r,total:S}=Te(a.dies,a.passBins);d[u]=S>0?r/S*100:NaN,b[u]=S}),{...f,level:"wafer",kind:"numeric",unit:"%",num:d,weights:b}}case"x":case"y":{const d=new Float64Array(l);for(let b=0;b<l;b++){const a=s.dies[b];d[b]=te(a)?e.builtin==="x"?a.x:a.y:NaN}return{...f,level:"die",kind:"numeric",num:d}}case"hbin":case"sbin":{const d=s.dies.map(b=>{const a=e.builtin==="hbin"?b.hbin:b.sbin;return a===void 0?void 0:`Bin ${a}`});return{...f,level:"die",kind:"categorical",cat:d}}case"site":return{...f,level:"die",kind:"categorical",cat:s.dies.map(d=>d.siteNum===void 0?void 0:`Site ${d.siteNum}`)};case"ring":case"quadrant":{const d=s.dies.map((b,a)=>{const u=n[s.item[a]].wafer;if(!u||!te(b)||b.physX===void 0||b.physY===void 0)return;const r=Re(b,u,{ringCount:Xe(n[s.item[a]],"a ring or quadrant of a plot")});return e.builtin==="ring"?`Ring ${r.ring}`:r.quadrant});return{...f,level:"die",kind:"categorical",cat:d}}default:return{...f,level:"die",kind:"categorical",issue:"Needs a newer version of this tool"}}}function Qe(e,t){const n=e.rows.dies.length,s=new Float64Array(n),o=new Float64Array(n);for(let c=0;c<n;c++){const f=e.items[e.rows.item[c]],{pass:l,total:y}=Te([e.rows.dies[c]],f.passBins);s[c]=y>0?l*100:NaN,o[c]=y}return{...t,level:"die",num:s,weights:o,perDie:!0}}export function combine(e,t){const n=e.filter(Number.isFinite);if(t==="count")return n.length;if(n.length===0)return NaN;switch(t){case"sum":return n.reduce((s,o)=>s+o,0);case"min":return ke(n);case"max":return Se(n);case"median":{const s=[...n].sort((c,f)=>c-f),o=s.length>>1;return s.length%2?s[o]:(s[o-1]+s[o])/2}default:return n.reduce((s,o)=>s+o,0)/n.length}}const ie={mean:"Mean",median:"Median",min:"Min",max:"Max",sum:"Sum",count:"Count",yield:"Pooled yield"};function W(e,t,n,s,o,c){const f=s.axes?.[e]??{};let l=f.scale??"linear";if(l==="log"){for(const y of o)if(Number.isFinite(y)&&y<=0){l="linear",c.push(`The ${e.toUpperCase()} axis stays linear: a log axis needs every value above zero`);break}}return{label:f.label??t,unit:n,scale:l,min:f.min,max:f.max,reverse:f.reverse}}function Je(e,t){const{chart:n}=e,s=e.fields??{},o=s.x?fieldLabel(s.x,t.testDefs):n==="box"||n==="bar"?"Wafer":void 0,c=s.y?fieldLabel(s.y,t.testDefs):void 0;switch(n){case"scatter":return o&&c?`${c} vs ${o}`:"Scatter";case"histogram":return c??(s.x?fieldLabel(s.x,t.testDefs):void 0)??"Histogram";case"line":return o&&c?`${c} over ${o}`:"Line";case"box":return c&&o?`${c} by ${o}`:"Box";case"bar":return o?c?`${c} by ${o}`:`Dies by ${o}`:"Bar";default:return""}}function De(e,t,n,s={}){return{spec:e,issues:n,notes:[],level:"die",autoTitle:Je(e,s),groups:[""],population:{wafers:t.length,dies:t.reduce((o,c)=>o+c.dies.length,0)},plotted:0,omitted:[]}}export function combinationIssue(e,t,n={}){const s=[t.x,n.continuousColor?void 0:t.color].filter(y=>!!y&&y.level==="die"),o=!!t.y&&t.y.yield&&t.y.level==="wafer",c=(e==="bar"||e==="line")&&(n.aggregate===void 0||n.aggregate==="yield"||n.aggregate==="mean");if(o&&s.length&&!c)return{long:`${t.y.label} is one figure per wafer, and ${s[0].label} belongs to dies, so a ${e} of it cannot be split by ${s[0].label}. A bar chart of yield can (pooled yield per ${s[0].label.toLowerCase()}), or plot a measured value instead`,short:`${t.y.label} is per wafer: use a bar or line chart`};const f=[t.x,t.y].filter(y=>!!y);let l=n.level??(f.some(y=>y.level==="die")?"die":"wafer");if(e==="bar"&&!t.y&&s.length&&(l="die"),t.color?.level==="die"&&l==="wafer"&&!n.continuousColor&&!(o&&(e==="bar"||e==="line")))return{long:`${t.color.label} is per die, so it cannot colour a plot with one mark per wafer. Plot a measured value (one mark per die), or make a bar chart of yield, which can be split by ${t.color.label.toLowerCase()}`,short:`${t.color.label} is per die: this plot has one mark per wafer`}}export function resolvePlot(e,t,n={}){const s=ze(e);if(s)return De(e,t,[s],n);const o=(...i)=>De(e,t,i,n);if(e.chart==="sweep")return o("A sweep is drawn by the sweep panel");const c=Ze(t),f={items:t,rows:c,ctx:n},l=e.chart,y=e.fields??{},d=y.x??(l==="box"||l==="bar"?{builtin:"wafer"}:void 0),b=y.y,a=l==="histogram"?b??d:b;if(l==="scatter"||l==="line"){if(!d)return o("Choose a field for X");if(!b)return o("Choose a field for Y")}if(l==="box"&&!b)return o("Choose a field for Y");if(l==="histogram"&&!a)return o("Choose a field to count");const u=l==="histogram"?void 0:d?q(d,f):void 0;let r=a?q(a,f):void 0;const S=l==="scatter"||l==="line"?[["X",u],["Y",r]]:l==="histogram"?[["to count",r]]:l==="box"||l==="bar"?[["Y",r]]:[];for(const[i,m]of S)if(m&&!m.issue&&m.kind==="categorical"&&!m.num)return o(`${m.label} is a category, not a number, so it cannot be ${i==="to count"?"the field a histogram counts":`the ${i} of a ${l}`}. Use it as the X of a bar or box chart (one bar per ${m.label.toLowerCase()}), or as the colour`);const Y=y.color;let w,ye=!1,re;Y===void 0||"follow"in Y?n.groupBy?re=n.groupBy:t.length>1&&(ye=!0):"none"in Y||(w=q(Y,f)),re&&(w=q({meta:re},f)),ye&&(w=q({builtin:"wafer"},f)),w?.issue&&(Y===void 0||"follow"in Y)&&(w=void 0);const oe=[u?.issue,r?.issue,w?.issue].filter(i=>!!i),Q=!!w&&w.kind==="numeric"&&!("meta"in w.field);if(Q&&l!=="scatter"&&oe.push(`${w.label} is continuous, so it can colour a scatter but not a ${l}: colour by a category such as wafer, split or bin`),oe.length)return o(...oe);const Ee=[u,r].filter(i=>!!i);let C=e.level??(Ee.some(i=>i.level==="die")?"die":"wafer");const se=[u,Q?void 0:w].filter(i=>!!i&&i.level==="die"),ae=i=>i&&{label:i.label,level:i.level,kind:i.kind,yield:!!i.weights},we=combinationIssue(l,{x:ae(u),y:ae(r),color:ae(w)},{aggregate:e.aggregate,level:e.level,continuousColor:Q});if(we)return o(we.long);l==="bar"&&!r&&se.length&&(C="die"),r&&r.level==="wafer"&&r.weights&&se.length&&(r=Qe(f,r),C="die"),C==="die"&&r?.weights&&!r.perDie&&!se.length&&(C="wafer");const L=C==="die"?c.dies.length:t.length,J=i=>C==="die"?c.item[i]:i,le=i=>({item:J(i),die:C==="die"?c.dies[i]:void 0}),$e=e.aggregate&&e.aggregate!=="yield"?e.aggregate:"mean",D=i=>{if(!i.num)throw new Error("numeric field expected");if(i.level===C)return i.num;if(i.level==="wafer"){const h=new Float64Array(L);for(let g=0;g<L;g++)h[g]=i.num[J(g)];return h}const m=t.map(()=>[]);for(let h=0;h<i.num.length;h++)m[c.item[h]].push(i.num[h]);return Float64Array.from(m,h=>combine(h,$e))},xe=i=>{if(!i.cat)throw new Error("category field expected");if(i.level===C)return i.cat;if(i.level==="wafer")return Array.from({length:L},(m,h)=>i.cat[J(h)]);throw new Error("a per-die category cannot be used at wafer level")},Be=i=>i.weights&&i.level===C?i.weights:void 0;let ue=[""],O=new Int32Array(L),G,P;if(w&&Q)G=w.label,P=D(w);else if(w){G=w.label;const i=xe(w),m=new Map,h=[];for(let $=0;$<L;$++){const x=i[$]??Le;m.has(x)||(m.set(x,0),h.push(x))}const F="builtin"in w.field&&w.field.builtin==="wafer"?h:w.num&&"meta"in w.field?[...h].sort(($,x)=>(Number($)||0)-(Number(x)||0)):[...h].sort(Fe);F.forEach(($,x)=>m.set($,x)),ue=F;for(let $=0;$<L;$++)O[$]=m.get(i[$]??Le)}const ce=new Map,M=i=>ce.set(i,(ce.get(i)??0)+1),E=[],Ue=C==="die"?"dies":"wafers";let X,T,B,R,ve,H=0;const U=ue.length,ee=i=>i.level==="die"&&C==="wafer"?`${ie[$e].toLowerCase()} of ${i.label} per wafer`:void 0;if(l==="scatter"){const i=D(u),m=D(r),h=[];for(let g=0;g<L;g++){if(!Number.isFinite(i[g])){M(u.label);continue}if(!Number.isFinite(m[g])){M(r.label);continue}if(P&&!Number.isFinite(P[g])){M(w.label);continue}h.push({x:i[g],y:m[g],group:O[g],item:J(g),die:C==="die"?c.dies[g]:void 0,...P?{value:P[g]}:{}})}if(H=h.length,P&&h.length){const g=h.map(F=>F.value);ve={label:w.label,unit:w.unit,lo:ke(g),hi:Se(g)}}X={type:"scatter",points:h},T=W("x",u.label,u.unit,e,h.map(g=>g.x),E),B=W("y",r.label,r.unit,e,h.map(g=>g.y),E),R=[ee(u),ee(r)].filter(Boolean).join("; ")||void 0}else if(l==="histogram"){const i=D(r),m=Array.from({length:U},()=>[]),h=Array.from({length:U},()=>[]);for(let g=0;g<L;g++){if(!Number.isFinite(i[g])){M(r.label);continue}m[O[g]].push(i[g]),h[O[g]].push(g),H++}X={type:"histogram",values:m,units:h,unit:le},T=W("x",r.label,r.unit,e,m.flat(),E),B={label:"Count",scale:"linear",...e.axes?.y?.label?{label:e.axes.y.label}:{}},R=ee(r)}else if(l==="line"){const i=D(u),m=D(r),h=new Set,g=new Map,F=new Map;for(let v=0;v<L;v++){if(!Number.isFinite(i[v])){M(u.label);continue}if(!Number.isFinite(m[v])){M(r.label);continue}h.add(i[v]);const A=`${O[v]}|${i[v]}`;(g.get(A)??g.set(A,[]).get(A)).push(m[v]),H++,(F.get(A)??F.set(A,[]).get(A)).push(v)}const $=[...h].sort((v,A)=>v-A),x=e.aggregate??"mean",j=Array.from({length:U},(v,A)=>$.map(_=>{const V=g.get(`${A}|${_}`);return V?combine(V,x==="yield"?"mean":x):NaN})),k=Array.from({length:U},(v,A)=>$.map(_=>g.get(`${A}|${_}`)?.length??0)),I=Array.from({length:U},(v,A)=>$.map(_=>F.get(`${A}|${_}`)??[]));X={type:"line",xs:$,values:j,counts:k,cellUnits:I,unit:le},T=W("x",u.label,u.unit,e,$,E);const z=x==="yield"?"mean":x;B=W("y",r.perDie?r.label:`${ie[z]} ${r.label}`,r.unit,e,j.flat(),E),R=r.perDie?`passing dies over judged dies, per ${u.label.toLowerCase()}`:`${ie[z].toLowerCase()} of ${r.label} per ${u.label}`}else{const i=u,m="builtin"in i.field&&(i.field.builtin==="x"||i.field.builtin==="y");if(i.kind==="numeric"&&i.level==="die"&&!m)return o(`${i.label} is continuous, so a ${l} cannot have one ${l==="bar"?"bar":"box"} per value; use a scatter or line, or choose a categorical X such as wafer, ring or bin`);const h=i.cat?xe(i):Array.from(D(i),p=>Number.isFinite(p)?String(p):void 0),g=new Map,F=[];for(let p=0;p<L;p++){const N=h[p];N!==void 0&&!g.has(N)&&(g.set(N,0),F.push(N))}const $="builtin"in i.field&&i.field.builtin==="wafer",x=$?F:i.kind==="numeric"?[...F].sort((p,N)=>Number(p)-Number(N)):[...F].sort(Fe);x.forEach((p,N)=>g.set(p,N));const j=l==="bar"&&!r,k=e.aggregate??(j?"count":r?.weights?"yield":"mean");if(k==="yield"&&!r?.weights)return o("Pooled yield needs Yield as the Y field");if(l==="box"&&k==="count")return o("A box plot needs a measured field for Y");const I=r?D(r):void 0,z=r?Be(r):void 0,v=Array.from({length:U},()=>x.map(()=>[])),A=Array.from({length:U},()=>x.map(()=>[])),_=Array.from({length:U},()=>x.map(()=>[]));for(let p=0;p<L;p++){const N=h[p]===void 0?void 0:g.get(h[p]);if(N===void 0){M(i.label);continue}if(I&&!Number.isFinite(I[p])){M(r.label);continue}v[O[p]][N].push(I?I[p]:1),A[O[p]][N].push(p),z&&_[O[p]][N].push(z[p]),H++}const V=v.map((p,N)=>p.map((Z,Ye)=>{if(Z.length===0)return NaN;if(k==="yield"){const Ne=_[N][Ye],Ae=Ne.reduce((de,me)=>de+me,0);return Ae>0?Z.reduce((de,me,Pe)=>de+me*Ne[Pe],0)/Ae:NaN}return combine(Z,k)})),We=$?x.map(p=>h.findIndex((N,Z)=>N===p&&Z>=0)):void 0;if(X={type:l==="box"?"box":"bar",categories:x,cells:v,cellUnits:A,unit:le,values:V,categoryItems:We?.map(p=>C==="die"?c.item[p]:p)},T={label:e.axes?.x?.label??i.label,scale:"linear"},l==="bar"){const p=k==="count"?"Count":ie[k],N=j?`Count of ${Ue}`:k==="yield"?"Pooled yield":`${p} ${r.label}`;B=W("y",N,k==="yield"?"%":k==="count"?void 0:r?.unit,e,V.flat(),E),R=j?void 0:k==="yield"?`passing dies over judged dies, pooled per ${i.label.toLowerCase()}`:`${p.toLowerCase()} of ${r.label}${r.level==="die"&&C==="wafer"?" per wafer":""} per ${i.label.toLowerCase()}`}else B=W("y",r.label,r.unit,e,v.flat(2),E),R=ee(r)}const fe=(i,m)=>i&&!i.perDie&&(m===void 0||m==="mean"||m==="median"||m==="min"||m==="max")?i.limits:void 0,_e=e.aggregate??"mean";return T&&(l==="scatter"||l==="line")&&(T.limits=fe(u)),B&&l!=="histogram"&&(B.limits=fe(r,l==="scatter"||l==="box"?void 0:_e)),l==="histogram"&&T&&(T.limits=fe(r)),{spec:e,issues:[],notes:E,marks:X,level:C,autoTitle:(()=>{const i=u?.label,m=r?.label,h=l==="scatter"?`${m} vs ${i}`:l==="histogram"?`${m}`:l==="line"?`${m} over ${i}`:l==="box"?`${m} by ${i}`:m?`${m} by ${i}`:`Dies by ${i}`;return G&&w&&!ge(w.field,d)?`${h} \xB7 by ${G}`:h})(),x:T,y:B,groups:ue,colorScale:ve,colorLabel:G,aggregation:R,population:{wafers:t.length,dies:c.dies.length},plotted:H,omitted:[...ce].map(([i,m])=>({field:i,count:m}))}}export function plotTitle(e){return e.spec.title?.trim()?e.spec.title:e.autoTitle}export function plotFootnote(e){const{wafers:t,dies:n}=e.population,s=[`${t.toLocaleString("en-GB")} wafer${t===1?"":"s"} \xB7 ${n.toLocaleString("en-GB")} dies`];e.aggregation&&s.push(e.aggregation);for(const o of e.omitted)s.push(`${o.count.toLocaleString("en-GB")} ${e.level==="die"?"dies":"wafers"} without ${o.field} not plotted`);return s.join(" \xB7 ")}export function fieldKey(e){return"test"in e?`test:${e.test}`:"builtin"in e?`builtin:${e.builtin}`:`meta:${e.meta}`}export function fieldCatalogue(e,t={}){const n=[],s=e.flatMap(a=>a.dies),o=new Set(s.length?Ce(s,"values"):[]),c=t.testDefs??[],f=new Map;for(const a of c)a.testType!=="F"&&(o.has(a.testNumber)||!s.length)&&f.set(a.testNumber,a.name);for(const a of o)!f.has(a)&&!c.some(u=>u.testNumber===a&&u.testType==="F")&&f.set(a,void 0);for(const[a,u]of[...f].sort((r,S)=>r[0]-S[0]))n.push({field:{test:a,...u?{name:u}:{}},label:u?`${u} \xB7 ${a}`:`Test ${a}`,name:u||`Test ${a}`,group:"Tests",kind:"numeric",level:"die",categorical:!1});const l=a=>s.some(a),y=(a,u,r)=>n.push({field:{builtin:a},label:K[a],name:K[a],group:"Die",kind:u,level:"die",categorical:r});l(a=>te(a))&&(y("x","numeric",!1),y("y","numeric",!1)),e.some(a=>a.wafer)&&l(a=>te(a))&&(y("ring","categorical",!0),y("quadrant","categorical",!0)),l(a=>a.hbin!==void 0)&&y("hbin","categorical",!0),l(a=>a.sbin!==void 0)&&y("sbin","categorical",!0),l(a=>a.siteNum!==void 0)&&y("site","categorical",!0);const d=(a,u,r)=>n.push({field:{builtin:a},label:K[a],name:K[a],group:"Wafer",kind:u,level:"wafer",categorical:r});d("wafer","categorical",!0),e.length>1&&d("waferOrder","numeric",!1),d("yield","numeric",!1),d("dieCount","numeric",!1);const b={items:e,rows:{dies:[],item:new Uint32Array(0)},ctx:t};for(const a of Ge(e.map(u=>({metadata:u.metadata,dieCount:u.dies.length})),{facetableOnly:!1,curation:t.curation})){if(a.values.length===0)continue;const u=q({meta:a.key},b);n.push({field:{meta:a.key},label:a.label||ne(a.key),name:a.label||ne(a.key),group:"Wafer",kind:u.kind,level:"wafer",categorical:!0,distinct:a.values.length})}return n}export function fieldsForRole(e,t,n){const s=n==="y"||n==="x"&&(t==="scatter"||t==="line"||t==="histogram");return n==="color"?e.filter(o=>o.categorical||t==="scatter"&&o.kind==="numeric"):s?e.filter(o=>o.kind==="numeric"):e.filter(o=>o.categorical)}export function defaultPlot(e,t){const n=e.filter(s=>s.group==="Tests");return n.length>=2?{id:t,chart:"scatter",fields:{x:n[0].field,y:n[1].field}}:n.length===1?{id:t,chart:"histogram",fields:{y:n[0].field}}:{id:t,chart:"scatter",fields:{x:{builtin:"waferOrder"},y:{builtin:"yield"}}}}const be=new Set(["wafer","dies","die x","die y","wafer order"]),et=e=>e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),pe=(e,t)=>t.length>=3&&new RegExp(`(^|[^A-Za-z0-9_])${et(t)}($|[^A-Za-z0-9_])`,"i").test(e);export function titleDrift(e,t){const n=e.title?.trim(),s={names:[],omits:[]};if(!n)return s;const o=e.fields?.color,c=[e.fields?.x,e.fields?.y,o].filter(r=>he(r)),f=r=>c.some(S=>ge(S,r)),l=t.filter(r=>!be.has(r.name.toLowerCase())&&!("meta"in r.field)&&!f(r.field)&&pe(n,r.name)).map(r=>r.name),y=[e.fields?.x,e.fields?.y].filter(r=>he(r)),d=r=>t.find(S=>ge(S.field,r))?.name??fieldLabel(r),b=y.filter(r=>!be.has(d(r).toLowerCase())&&pe(n,d(r))),a=y.filter(r=>!be.has(d(r).toLowerCase())&&!pe(n,d(r))),u=b.length>0||l.length>0?a.map(d):[];return{names:l,omits:u}}const Oe=e=>e.length<2?e.join(""):`${e.slice(0,-1).join(", ")} and ${e[e.length-1]}`;export function describeTitleDrift(e){const t=[];return e.names.length&&t.push(`The title names ${Oe(e.names)}, which this plot does not show.`),e.omits.length&&t.push(`It does not name ${Oe(e.omits)}, which this plot shows.`),t.length?t.join(" "):null}export function examplePlots(e,t,n){const s=e.filter(f=>f.group==="Tests").map(f=>f.field),o=[],c=t>1;if(s.length>=2&&o.push({id:n(),chart:"scatter",fields:{x:s[0],y:s[1]}}),s.length>=1&&o.push({id:n(),chart:"histogram",fields:{y:s[0],color:{none:!0}}}),s.length>=1&&c&&o.push({id:n(),chart:"box",fields:{x:{builtin:"wafer"},y:s[0],color:{none:!0}}}),c){const f=e.find(l=>"meta"in l.field&&l.distinct!==void 0&&l.distinct>=2&&l.distinct<=Math.max(2,Math.floor(t/2)));o.push({id:n(),chart:"bar",fields:{x:f?f.field:{builtin:"wafer"},y:{builtin:"yield"},color:{none:!0}}})}return s.length>=1&&c&&o.push({id:n(),chart:"line",fields:{x:{builtin:"waferOrder"},y:s[0],color:{none:!0}},aggregate:"mean"}),s.length>=2&&o.push({...Me(s.flatMap(f=>"test"in f?[f]:[]),n()),title:"Example sweep"}),o}export function plotTestNumber(e){const{x:t,y:n,color:s}=e.fields??{},o=e.chart==="scatter"?[t,n,s]:e.chart==="histogram"?[n,t]:[n];for(const c of o)if(he(c)&&"test"in c)return c.test}export function defaultSweep(e,t){return Me(e.filter(Ie).map(n=>({test:n.testNumber,name:n.name})),t)}function Me(e,t){const n=[...e].sort((o,c)=>o.test-c.test).slice(0,5),s=Object.fromEntries(n.flatMap(o=>o.name?[[String(o.test),o.name]]:[]));return{id:t,chart:"sweep",sweep:{series:[{label:"Series 1",tests:n.map(o=>o.test),...Object.keys(s).length?{testNames:s}:{}}]}}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
let o=0;export function newPlotId(){const t=Math.floor(Math.random()*4294967296).toString(36).padStart(7,"0");return`plot-${Date.now().toString(36)}-${t}-${(o++).toString(36)}`}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { newPlotId } from './plotId.js';
|
|
2
|
+
import type { SweepSpec } from './sweep.js';
|
|
3
|
+
export { newPlotId };
|
|
4
|
+
export declare const PLOTS_FORMAT = "wafermap-plots";
|
|
5
|
+
export declare const PLOTS_VERSION = 1;
|
|
6
|
+
/** The marker of the sweeps file tsmap wrote before sweeps became plots. A plots file reader accepts it. */
|
|
7
|
+
export declare const SWEEPS_FORMAT = "tsmap-sweeps";
|
|
8
|
+
/** A sweep's own definition: a `SweepSpec` without the `id` and `title` the plot already carries. */
|
|
9
|
+
export type SweepPayload = Omit<SweepSpec, 'id' | 'title'>;
|
|
10
|
+
export declare const PLOT_CHARTS: readonly ["scatter", "histogram", "box", "bar", "line", "sweep"];
|
|
11
|
+
export type PlotChart = (typeof PLOT_CHARTS)[number];
|
|
12
|
+
export declare const PLOT_BUILTINS: readonly ["wafer", "x", "y", "ring", "quadrant", "hbin", "sbin", "site", "yield", "dieCount", "waferOrder"];
|
|
13
|
+
export type PlotBuiltin = (typeof PLOT_BUILTINS)[number];
|
|
14
|
+
/** A series a plot can put on an axis or use for colour. */
|
|
15
|
+
export type PlotField =
|
|
16
|
+
/** A measured test. The number is the key; the name is a check, so a plot saved against another test program is
|
|
17
|
+
* reported rather than drawn against the wrong measurement. */
|
|
18
|
+
{
|
|
19
|
+
test: number;
|
|
20
|
+
name?: string;
|
|
21
|
+
} | {
|
|
22
|
+
builtin: PlotBuiltin;
|
|
23
|
+
}
|
|
24
|
+
/** A wafer or lot metadata key (split, slot, temperature…). */
|
|
25
|
+
| {
|
|
26
|
+
meta: string;
|
|
27
|
+
};
|
|
28
|
+
export interface PlotAxis {
|
|
29
|
+
/** Replaces the automatic axis title. Absent = automatic. */
|
|
30
|
+
label?: string;
|
|
31
|
+
scale?: 'linear' | 'log';
|
|
32
|
+
min?: number;
|
|
33
|
+
max?: number;
|
|
34
|
+
reverse?: boolean;
|
|
35
|
+
}
|
|
36
|
+
export declare const PLOT_AGGREGATES: readonly ["mean", "median", "min", "max", "sum", "count", "yield"];
|
|
37
|
+
export type PlotAggregate = (typeof PLOT_AGGREGATES)[number];
|
|
38
|
+
export declare const PLOT_LIMITS: readonly ["both", "test", "spec", "none"];
|
|
39
|
+
export type PlotLimits = (typeof PLOT_LIMITS)[number];
|
|
40
|
+
export type PlotColor = PlotField | {
|
|
41
|
+
follow: 'groupBy';
|
|
42
|
+
} | {
|
|
43
|
+
none: true;
|
|
44
|
+
};
|
|
45
|
+
export interface PlotSpec {
|
|
46
|
+
/** Stable, generated once, never reused; survives rename and reorder. */
|
|
47
|
+
id: string;
|
|
48
|
+
/** Set only when the user typed one; absent = the automatic title. */
|
|
49
|
+
title?: string;
|
|
50
|
+
/** The kind of chart that draws the plot. */
|
|
51
|
+
chart: PlotChart;
|
|
52
|
+
/** Which field stands for each role. Absent for a sweep, which names runs of tests instead. */
|
|
53
|
+
fields?: {
|
|
54
|
+
x?: PlotField;
|
|
55
|
+
y?: PlotField;
|
|
56
|
+
/** Absent = follow the tab's Group by. */
|
|
57
|
+
color?: PlotColor;
|
|
58
|
+
};
|
|
59
|
+
/** The unit one mark stands for. Absent = the finest level the X and Y fields allow. */
|
|
60
|
+
level?: 'die' | 'wafer';
|
|
61
|
+
/** How die-level values are combined into one mark. Absent = a mean (a count when there is no Y). */
|
|
62
|
+
aggregate?: PlotAggregate;
|
|
63
|
+
axes?: {
|
|
64
|
+
x?: PlotAxis;
|
|
65
|
+
y?: PlotAxis;
|
|
66
|
+
};
|
|
67
|
+
/** Histogram bin count. Absent = automatic. */
|
|
68
|
+
bins?: number;
|
|
69
|
+
/** Which of a plotted test's limits are drawn: `test` limits, `spec` limits (LSL/USL), `both` or `none`. Absent = both. */
|
|
70
|
+
limits?: PlotLimits;
|
|
71
|
+
/** The definition of a `sweep` chart: ordered runs of tests read as curves. Such a plot has no `fields`. */
|
|
72
|
+
sweep?: SweepPayload;
|
|
73
|
+
}
|
|
74
|
+
export interface ReadPlotsResult {
|
|
75
|
+
plots: PlotSpec[];
|
|
76
|
+
/** Problems that dropped a plot or a setting, each naming where. */
|
|
77
|
+
warnings: string[];
|
|
78
|
+
/** Set when nothing could be read at all: the file as a whole is rejected. */
|
|
79
|
+
error?: string;
|
|
80
|
+
}
|
|
81
|
+
/** Two fields name the same series. */
|
|
82
|
+
export declare function sameField(a: PlotField | undefined, b: PlotField | undefined): boolean;
|
|
83
|
+
export declare function isField(v: unknown): v is PlotField;
|
|
84
|
+
/**
|
|
85
|
+
* A sweep's definition from parsed JSON. Lenient like the rest of the file: a series that cannot be read is named and
|
|
86
|
+
* left out, a setting that is wrong is dropped, and settings this build does not know are kept.
|
|
87
|
+
*/
|
|
88
|
+
export declare function readSweepPayload(raw: unknown, where: string, warnings: string[]): SweepPayload;
|
|
89
|
+
/** The plot a sweep becomes: its id and title go on the envelope, the rest is the payload. */
|
|
90
|
+
export declare function sweepToPlot(spec: SweepSpec): PlotSpec;
|
|
91
|
+
/** The sweep a `sweep` plot defines, in the shape the sweep maths reads; `undefined` for any other chart. */
|
|
92
|
+
export declare function plotToSweep(plot: PlotSpec): SweepSpec | undefined;
|
|
93
|
+
/**
|
|
94
|
+
* One plot from parsed JSON, or `null` when it cannot be a plot at all (no chart). Unknown keys survive; a
|
|
95
|
+
* setting that is wrong is dropped with a warning and the rest of the plot is kept.
|
|
96
|
+
*/
|
|
97
|
+
export declare function readPlot(raw: unknown, where: string, warnings: string[]): PlotSpec | null;
|
|
98
|
+
/**
|
|
99
|
+
* Reads a plots file. Lenient: every plot that can be read is kept and each problem is named, because a file a
|
|
100
|
+
* user spent time on must not be refused for one bad setting. Only a file with nothing usable is an error.
|
|
101
|
+
* A bare array of plots is accepted as well as the wrapped form.
|
|
102
|
+
*/
|
|
103
|
+
export declare function readPlotsFile(text: string): ReadPlotsResult;
|
|
104
|
+
/** The file for `plots`: wrapped, versioned, with every setting (including ones this build does not know). */
|
|
105
|
+
export declare function writePlotsFile(plots: readonly PlotSpec[]): string;
|
|
106
|
+
/**
|
|
107
|
+
* Adds `incoming` to `existing` as copies where an id is already in use, never replacing: an import must not
|
|
108
|
+
* overwrite a plot the user has edited since. Returns the merged list and the titles that became copies.
|
|
109
|
+
*/
|
|
110
|
+
export declare function addPlots(existing: readonly PlotSpec[], incoming: readonly PlotSpec[]): {
|
|
111
|
+
plots: PlotSpec[];
|
|
112
|
+
copies: string[];
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* Why this build cannot draw `plot`, or `undefined` when it can: a chart or field kind from a newer version.
|
|
116
|
+
* (A test that is missing from the open lot is a data question, answered by the resolver, not a version one.)
|
|
117
|
+
*/
|
|
118
|
+
export declare function plotIssue(plot: PlotSpec): string | undefined;
|
|
119
|
+
//# sourceMappingURL=plotSpec.d.ts.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{newPlotId as p}from"./plotId.js";export{p as newPlotId};export const PLOTS_FORMAT="wafermap-plots",PLOTS_VERSION=1,SWEEPS_FORMAT="tsmap-sweeps",PLOT_CHARTS=["scatter","histogram","box","bar","line","sweep"],PLOT_BUILTINS=["wafer","x","y","ring","quadrant","hbin","sbin","site","yield","dieCount","waferOrder"],PLOT_AGGREGATES=["mean","median","min","max","sum","count","yield"],PLOT_LIMITS=["both","test","spec","none"];const u=e=>typeof e=="object"&&e!==null&&!Array.isArray(e),c=e=>typeof e=="number"&&Number.isFinite(e);export function sameField(e,t){return!e||!t?e===t:"test"in e?"test"in t&&e.test===t.test:"builtin"in e?"builtin"in t&&e.builtin===t.builtin:"meta"in t&&e.meta===t.meta}export function isField(e){return u(e)?"test"in e?Number.isInteger(e.test):"builtin"in e?PLOT_BUILTINS.includes(e.builtin):"meta"in e?typeof e.meta=="string"&&e.meta!=="":!1:!1}function a(e,t,i){if(e!==void 0){if(!u(e)){i.push(`${t}: not a field, left out`);return}if("test"in e){if(!Number.isInteger(e.test)){i.push(`${t}: test number ${JSON.stringify(e.test)} is not an integer, left out`);return}const s={...e,test:e.test};return typeof e.name=="string"&&e.name!==""?s.name=e.name:delete s.name,s}if("builtin"in e)return PLOT_BUILTINS.includes(e.builtin)?{...e,builtin:e.builtin}:e;if("meta"in e){if(typeof e.meta!="string"||e.meta===""){i.push(`${t}: metadata key is empty, left out`);return}return{...e,meta:e.meta}}return e}}function y(e,t,i){if(e===void 0)return;if(!u(e)){i.push(`${t}: not an object, left out`);return}const s={...e};"label"in e&&typeof e.label!="string"&&(i.push(`${t}.label: not text, left out`),delete s.label),"scale"in e&&e.scale!=="linear"&&e.scale!=="log"&&(i.push(`${t}.scale: "${String(e.scale)}" is not linear or log, left out`),delete s.scale);for(const o of["min","max"])o in e&&!c(e[o])&&(i.push(`${t}.${o}: not a number, left out`),delete s[o]);return"reverse"in e&&typeof e.reverse!="boolean"&&(i.push(`${t}.reverse: not true or false, left out`),delete s.reverse),s}const x=/^\s*\d+\s*(\.\.\s*\d+\s*)?$/,$=e=>typeof e=="number"&&Number.isInteger(e)||typeof e=="string"&&x.test(e);export function readSweepPayload(e,t,i){if(!u(e))return i.push(`${t}: no sweep definition, left empty`),{series:[]};const s={...e};delete s.id,delete s.title;const o=Array.isArray(e.series)?e.series:[];Array.isArray(e.series)||i.push(`${t}.series: not a list, left empty`);const l=[];o.forEach((n,r)=>{const f=`${t}.series ${r+1}`;if(!u(n)){i.push(`${f}: not an object, skipped`);return}const d={...n};if((typeof n.label!="string"||n.label.trim()==="")&&(d.label=`Series ${r+1}`),!Array.isArray(n.tests)||!n.tests.every($)){i.push(`${f}: its tests must be test numbers or ranges such as "1010..1030", skipped`);return}"xValues"in n&&!(Array.isArray(n.xValues)&&n.xValues.every(c))&&(i.push(`${f}.xValues: not a list of numbers, left out`),delete d.xValues),"xFromName"in n&&typeof n.xFromName!="string"&&(i.push(`${f}.xFromName: not text, left out`),delete d.xFromName),"color"in n&&typeof n.color!="string"&&delete d.color,"testNames"in n&&!(u(n.testNames)&&Object.values(n.testNames).every(m=>typeof m=="string"))&&(i.push(`${f}.testNames: not a list of names, left out`),delete d.testNames),l.push(d)}),s.series=l;for(const n of["xLabel","xUnit","yLabel"])n in e&&typeof e[n]!="string"&&(i.push(`${t}.${n}: not text, left out`),delete s[n]);return"xScale"in e&&e.xScale!=="linear"&&e.xScale!=="log"&&(i.push(`${t}.xScale: "${String(e.xScale)}" is not linear or log, left out`),delete s.xScale),"crossing"in e&&typeof e.crossing!="boolean"&&(i.push(`${t}.crossing: not true or false, left out`),delete s.crossing),"separationAt"in e&&!(Array.isArray(e.separationAt)&&e.separationAt.every(c))&&(i.push(`${t}.separationAt: not a list of numbers, left out`),delete s.separationAt),s}export function sweepToPlot(e){const{id:t,title:i,...s}=e;return{id:t,...i?{title:i}:{},chart:"sweep",sweep:s}}export function plotToSweep(e){if(e.chart==="sweep")return{...e.sweep??{series:[]},id:e.id,title:e.title??"Sweep"}}export function readPlot(e,t,i){if(!u(e))return i.push(`${t}: not an object, skipped`),null;const s=e.chart;if(typeof s!="string"||s==="")return i.push(`${t}: no chart, skipped`),null;const o={...e};(typeof e.id!="string"||e.id==="")&&(o.id=p(),i.push(`${t}: no id, given a new one`)),"title"in e&&typeof e.title!="string"?(i.push(`${t}.title: not text, left out`),delete o.title):typeof e.title=="string"&&e.title.trim()===""&&delete o.title;const l=u(e.fields)?e.fields:{};e.fields!==void 0&&!u(e.fields)&&i.push(`${t}.fields: not an object, left empty`);const n={...l};for(const r of["x","y"]){const f=a(l[r],`${t}.fields.${r}`,i);f?n[r]=f:delete n[r]}if(l.color!==void 0){const r=l.color;if(u(r)&&"follow"in r)r.follow==="groupBy"?n.color={follow:"groupBy"}:n.color=r;else if(u(r)&&"none"in r)n.color={none:!0};else{const f=a(r,`${t}.fields.color`,i);f?n.color=f:delete n.color}}if(s==="sweep"&&Object.keys(n).length===0?delete o.fields:o.fields=n,"level"in e&&e.level!=="die"&&e.level!=="wafer"&&(i.push(`${t}.level: "${String(e.level)}" is not die or wafer, left out`),delete o.level),"aggregate"in e&&!PLOT_AGGREGATES.includes(e.aggregate)&&(i.push(`${t}.aggregate: "${String(e.aggregate)}" is not known to this version, left out`),delete o.aggregate),"limits"in e&&!PLOT_LIMITS.includes(e.limits)&&(i.push(`${t}.limits: "${String(e.limits)}" is not both, test, spec or none, left out`),delete o.limits),"bins"in e&&!(Number.isInteger(e.bins)&&e.bins>=1)&&(i.push(`${t}.bins: not a whole number of at least 1, left out`),delete o.bins),s==="sweep"&&(o.sweep=readSweepPayload(e.sweep,`${t}.sweep`,i)),e.axes!==void 0)if(!u(e.axes))i.push(`${t}.axes: not an object, left out`),delete o.axes;else{const r={...e.axes};for(const f of["x","y"]){const d=y(e.axes[f],`${t}.axes.${f}`,i);d?r[f]=d:delete r[f]}o.axes=r}return o}function b(e,t){const i=/position (\d+)/.exec(t);if(!i)return t;const s=Math.min(Number(i[1]),e.length),o=e.slice(0,s),l=o.split(`
|
|
2
|
+
`).length,n=s-(o.lastIndexOf(`
|
|
3
|
+
`)+1)+1;return`${t} (line ${l}, column ${n})`}export function readPlotsFile(e){let t;try{t=JSON.parse(e)}catch(n){return{plots:[],warnings:[],error:`Not valid JSON: ${b(e,n instanceof Error?n.message:String(n))}`}}let i;if(Array.isArray(t))i=t;else if(u(t))if(t.format===SWEEPS_FORMAT){if(!Array.isArray(t.sweeps))return{plots:[],warnings:[],error:'The file has no "sweeps" list'};i=t.sweeps.map(n=>u(n)?{id:n.id,title:n.title,chart:"sweep",sweep:n}:n)}else{if(t.format!==PLOTS_FORMAT)return{plots:[],warnings:[],error:`This is not a plots file (expected "format": "${PLOTS_FORMAT}")`};i=t.plots}else return{plots:[],warnings:[],error:"This is not a plots file"};if(!Array.isArray(i))return{plots:[],warnings:[],error:'The file has no "plots" list'};const s=[];u(t)&&c(t.version)&&t.version>PLOTS_VERSION&&s.push(`Written by a newer version (${t.version}); settings this version does not know are kept but not used`);const o=[],l=new Set;return i.forEach((n,r)=>{const f=u(n)&&typeof n.title=="string"&&n.title?`"${n.title}"`:`plot ${r+1}`,d=readPlot(n,f,s);d&&(l.has(d.id)&&(d.id=p(),s.push(`${f}: its id was already used, given a new one`)),l.add(d.id),o.push(d))}),o.length===0&&i.length>0?{plots:o,warnings:s,error:"No plot in the file could be read"}:{plots:o,warnings:s}}export function writePlotsFile(e){return JSON.stringify({format:PLOTS_FORMAT,version:PLOTS_VERSION,plots:e},null,2)+`
|
|
4
|
+
`}export function addPlots(e,t){const i=new Set(e.map(l=>l.id)),s=[],o=t.map(l=>{if(!i.has(l.id))return i.add(l.id),l;const n=p();return i.add(n),s.push(l.title??l.id),{...l,id:n}});return{plots:[...e,...o],copies:s}}export function plotIssue(e){if(!PLOT_CHARTS.includes(e.chart))return"Needs a newer version of this tool";const t=e.fields??{};for(const i of[t.x,t.y,t.color])if(!(i===void 0||u(i)&&("follow"in i||"none"in i))&&!isField(i))return"Needs a newer version of this tool"}
|
|
@@ -12,6 +12,8 @@ export declare const SEVERITY_MARK: Readonly<Record<StatsSeverity, {
|
|
|
12
12
|
}>>;
|
|
13
13
|
/** An item's impact (share of dies lost) as the word it is shown by. */
|
|
14
14
|
export declare const impactWord: (tier: MeterTier) => string;
|
|
15
|
+
/** The same tier as one word, for a compact list row where "impact" is already the heading's subject. */
|
|
16
|
+
export declare const impactShortWord: (tier: MeterTier) => string;
|
|
15
17
|
/**
|
|
16
18
|
* How strongly a yield difference is tinted: 0 for none, 1–3 light to strong. Only a shortfall of a
|
|
17
19
|
* point or more is tinted — a lot with nothing wrong has no tint at all, which is how a good lot is
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const METER_DOTS=3,filledDots=t=>t==="high"?3:t==="medium"?2:1,SEVERITY_MARK={unusual:{tier:"high",word:"Unusual"},notable:{tier:"medium",word:"Notable"},info:{tier:"low",word:"Minor"}},impactWord=t=>`${t==="medium"?"Moderate":t==="high"?"High":"Low"} impact
|
|
1
|
+
export const METER_DOTS=3,filledDots=t=>t==="high"?3:t==="medium"?2:1,SEVERITY_MARK={unusual:{tier:"high",word:"Unusual"},notable:{tier:"medium",word:"Notable"},info:{tier:"low",word:"Minor"}},impactWord=t=>`${t==="medium"?"Moderate":t==="high"?"High":"Low"} impact`,impactShortWord=t=>t==="medium"?"Moderate":t==="high"?"High":"Low";const e=[{points:4,step:3},{points:2,step:2},{points:1,step:1}];export function shortfallStep(t){return e.find(o=>-t>=o.points)?.step??0}export const SHORTFALL_RGB="190, 60, 60",SHORTFALL_ALPHA={1:.1,2:.2,3:.32},shortfallColor=t=>`rgba(${SHORTFALL_RGB}, ${SHORTFALL_ALPHA[t]})`;export function formatPoints(t){return`${t>0?"+":t<0?"\u2212":""}${Math.abs(t).toFixed(1)}`}export const barPercent=t=>Math.max(0,Math.min(100,t)),shortfallLegend=t=>`Bars run 0\u2013100%. A figure is tinted when it is 1, 2 or 4 or more points below ${t}; nothing above it is.`,MEAN_WAFER_YIELD_LABEL="Mean per-wafer yield";
|
|
@@ -38,6 +38,12 @@ export interface RegionYieldDatum {
|
|
|
38
38
|
* yield itself uses.
|
|
39
39
|
*/
|
|
40
40
|
export declare function buildRegionYieldData(diesByWafer: Die[][], allWafers: Wafer[], ringCount: number, passBins: readonly number[] | ((waferIndex: number) => readonly number[]), regionBuilder: (dies: PositionedDie[], wafer: Wafer, ringCount: number) => StatsRegion[]): RegionYieldDatum[];
|
|
41
|
+
/**
|
|
42
|
+
* The dies behind one region of `buildRegionYieldData`'s output, on one wafer: the same dies its `n` counts (positioned,
|
|
43
|
+
* not partial or edge-excluded, with a bin recorded), found by the same region builder. For picking out what a click on a
|
|
44
|
+
* ring or quadrant of the yield diagram stands for.
|
|
45
|
+
*/
|
|
46
|
+
export declare function diesInRegion(dies: readonly Die[], wafer: Wafer, ringCount: number, key: string, regionBuilder: (dies: PositionedDie[], wafer: Wafer, ringCount: number) => StatsRegion[]): Die[];
|
|
41
47
|
/** Compass bearing names for a given sector count, ordered CCW from East. */
|
|
42
48
|
export declare function sectorCompassNames(sectorCount: number): string[];
|
|
43
49
|
/** Quadrants going round the wafer, counter-clockwise from the north-east. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{classifyDie as E,getRingLabel as
|
|
1
|
+
import{classifyDie as E,getRingLabel as x}from"../core/classify.js";import{getReticleCell as M}from"../core/reticle.js";import{isPositionedDie as W,diePassStatus as R,getDieKey as k}from"../core/dies.js";function g(s){return k(s)}export function buildRegionYieldData(s,r,a,e,n){const i=new Map,t=[];for(let o=0;o<r.length;o++){const c=s[o];if(!c?.length)continue;const u=new Set(typeof e=="function"?e(o):e),d=n(c.filter(W),r[o],a);for(const l of d){t.includes(l.key)||t.push(l.key);const y=i.get(l.key)??{label:l.label,pass:0,total:0};for(const p of l.dies){if(p.partial||p.edgeExcluded)continue;const S=R(p,u);S!==void 0&&(y.total++,S&&y.pass++)}i.set(l.key,y)}}return t.map(o=>{const c=i.get(o);return c.total===0?null:{key:o,label:c.label,n:c.total,passDies:c.pass,yieldPercent:c.pass/c.total*100}}).filter(o=>o!==null)}export function diesInRegion(s,r,a,e,n){const i=n(s.filter(W),r,a).find(t=>t.key===e);return i?i.dies.filter(t=>!t.partial&&!t.edgeExcluded&&(t.hbin??t.sbin)!==void 0):[]}const q=["E","ENE","NE","NNE","N","NNW","NW","WNW","W","WSW","SW","SSW","S","SSE","SE","ESE"],w=["E","NE","N","NW","W","SW","S","SE"],A=["E","N","W","S"];export function sectorCompassNames(s){const r=[4,8,16].includes(s)?s:16;return r===4?A:r===8?w:q}export const QUADRANT_CYCLE=["NE","NW","SW","SE"];const K={NE:["NW","SE"],NW:["NE","SW"],SE:["NE","SW"],SW:["NW","SE"]};export function areQuadrantsAdjacent(s,r){return K[s]?.includes(r)??!1}const f=1440;export function regionAngleBins(s,r){const a=parseRegionKey(s);let e,n;if(a.family==="quadrant"){const t=QUADRANT_CYCLE.indexOf(a.quadrant);if(t<0)return;e=t*f/4,n=f/4}else if(a.family==="sector"){const t=[4,8,16].includes(r)?r:void 0,o=t?sectorCompassNames(t).indexOf(a.sector??""):-1;if(!t||o<0)return;n=f/t,e=o*n-n/2}else return;const i=new Set;for(let t=0;t<n;t++)i.add(((e+t)%f+f)%f);return i}export function parseRegionKey(s){return s.startsWith("ring:")?{family:"ring",ring:Number(s.slice(5))}:s.startsWith("quadrant:")?{family:"quadrant",quadrant:s.slice(9)}:s.startsWith("sector:")?{family:"sector",sector:s.slice(7)}:{family:"unknown"}}export function buildRingRegions(s,r,a){const e=new Map;for(const n of s){const{ring:i}=E(n,r,{ringCount:a}),t=`ring:${i}`,o=e.get(t)??{family:"ring",key:t,label:x(i,a),dieKeys:[],dies:[]};o.dieKeys.push(g(n)),o.dies.push(n),e.set(t,o)}return[...e.values()].sort((n,i)=>n.key.localeCompare(i.key))}export function buildQuadrantRegions(s,r,a){const e=new Map;for(const i of s){const{quadrant:t}=E(i,r,{ringCount:a}),o=`quadrant:${t}`,c=e.get(o)??{family:"quadrant",key:o,label:t,dieKeys:[],dies:[]};c.dieKeys.push(g(i)),c.dies.push(i),e.set(o,c)}const n=new Map([["quadrant:NE",0],["quadrant:NW",1],["quadrant:SE",2],["quadrant:SW",3]]);return[...e.values()].sort((i,t)=>(n.get(i.key)??4)-(n.get(t.key)??4))}export function buildReticlePositionRegions(s,r){if(!r)return[];const a=new Map;for(const e of s){const{column:n,row:i}=M(e,r),t=`reticle-position:cell:${n},${i}`,o=a.get(t)??{family:"reticle-position",key:t,label:`Reticle cell (${n}, ${i})`,dieKeys:[],dies:[]};o.dieKeys.push(g(e)),o.dies.push(e),a.set(t,o)}return[...a.values()].sort((e,n)=>e.key.localeCompare(n.key))}const P=3;export function buildTestSiteRegions(s,r=!1){const a=new Map;for(const e of s){if(e.siteNum===void 0)continue;const n=a.get(e.siteNum)??[];n.push(e),a.set(e.siteNum,n)}if(!r){let e=0;for(const n of a.values())n.length>=P&&e++;if(e<2)return[]}return[...a.entries()].sort(([e],[n])=>e-n).map(([e,n])=>({family:"test-site",key:`test-site:${e}`,label:`Site ${e}`,dieKeys:n.map(g),dies:n}))}export function buildSectorRegions(s,r,a){const e=[4,8,16].includes(a)?a:16,n=sectorCompassNames(e),i=new Map,t=r.center.x,o=r.center.y,c=r.radius;for(const u of s){const d=u.physX-t,l=u.physY-o;if(Math.hypot(d,l)/c<.2)continue;const p=Math.PI/e,S=(Math.atan2(l,d)+2*Math.PI+p)%(2*Math.PI),b=Math.floor(S/(2*Math.PI)*e)%e,h=n[b],m=`sector:${h}`,N=i.get(m)??{family:"sector",key:m,label:`Sector ${h}`,dieKeys:[],dies:[]};N.dieKeys.push(g(u)),N.dies.push(u),i.set(m,N)}return[...i.values()].sort((u,d)=>u.key.localeCompare(d.key))}
|
|
@@ -18,8 +18,10 @@ export interface SummaryReportParams {
|
|
|
18
18
|
sbinDefs?: BinDef[];
|
|
19
19
|
testDefs?: TestDef[];
|
|
20
20
|
statsSummary?: StatsSummary;
|
|
21
|
-
passBins
|
|
22
|
-
|
|
21
|
+
/** The map's own pass bins (`WaferMapResult.passBins`): required, there is no default to judge by instead. */
|
|
22
|
+
passBins: number[];
|
|
23
|
+
/** The map's own ring count (`WaferMapResult.ringCount`): required, there is no default. */
|
|
24
|
+
ringCount: number;
|
|
23
25
|
/** @internal The map's own bin colours, so a bar here is the colour of that bin on the map. Resolved with the default palette when omitted. */
|
|
24
26
|
binColors?: BinColors;
|
|
25
27
|
/** @internal Shown inside the app: a click on a finding's row selects it on the map (see `LIVE_FINDINGS_SCRIPT`). */
|
|
@@ -39,6 +41,8 @@ export interface LotSummaryReportParams {
|
|
|
39
41
|
dies?: Die[];
|
|
40
42
|
/** This wafer's own pass bins (`WaferMapResult.passBins`). Omitted ⇒ the top-level `passBins`. */
|
|
41
43
|
passBins?: number[];
|
|
44
|
+
/** This wafer's own ring count (`WaferMapResult.ringCount`). Omitted ⇒ the top-level `ringCount`. */
|
|
45
|
+
ringCount?: number;
|
|
42
46
|
/** Reused directly as `analyzeWaferLot`'s `perWaferSummaries` — the expensive
|
|
43
47
|
* per-wafer pass (`analyzeWaferMap`) is never re-run here. */
|
|
44
48
|
statsSummary?: StatsSummary;
|
|
@@ -54,8 +58,9 @@ export interface LotSummaryReportParams {
|
|
|
54
58
|
hbinDefs?: BinDef[];
|
|
55
59
|
sbinDefs?: BinDef[];
|
|
56
60
|
testDefs?: TestDef[];
|
|
57
|
-
/**
|
|
61
|
+
/** For items that carry no `passBins` of their own (a hand-built map). No default: an item with neither is refused. */
|
|
58
62
|
passBins?: number[];
|
|
63
|
+
/** For items that carry no `ringCount` of their own (a hand-built map); none ⇒ the first item's. No default. */
|
|
59
64
|
ringCount?: number;
|
|
60
65
|
/** Passthrough to the internal per-group `analyzeWaferLot` call, e.g. `{ enableTestValueAnalysis: true }`. */
|
|
61
66
|
analyzeOptions?: AnalyzeWaferMapOptions;
|