@wafertools/wafermap 0.30.2 → 0.30.4
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 +16 -5
- package/CHANGELOG.md +432 -0
- package/README.md +6 -3
- package/dist/packages/canvas-adapter/chartPopulation.d.ts +48 -0
- package/dist/packages/canvas-adapter/chartPopulation.js +1 -0
- package/dist/packages/canvas-adapter/charts/barPanel.d.ts +4 -1
- package/dist/packages/canvas-adapter/charts/barPanel.js +1 -1
- package/dist/packages/canvas-adapter/charts/boxplot.d.ts +4 -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 +82 -8
- package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
- package/dist/packages/canvas-adapter/charts/correlation.js +2 -2
- package/dist/packages/canvas-adapter/charts/groupedBarPlot.js +1 -1
- package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
- package/dist/packages/canvas-adapter/charts/regionYieldDiagram.js +1 -1
- package/dist/packages/canvas-adapter/charts/scatter.d.ts +5 -1
- package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
- package/dist/packages/canvas-adapter/charts/sweep.d.ts +31 -0
- package/dist/packages/canvas-adapter/charts/sweep.js +1 -0
- package/dist/packages/canvas-adapter/charts/testPassRate.js +1 -1
- package/dist/packages/canvas-adapter/charts/trend.d.ts +3 -1
- package/dist/packages/canvas-adapter/charts/trend.js +1 -1
- package/dist/packages/canvas-adapter/dieList.js +2 -2
- package/dist/packages/canvas-adapter/drilldown.d.ts +23 -0
- package/dist/packages/canvas-adapter/drilldown.js +1 -0
- package/dist/packages/canvas-adapter/icons.js +1 -1
- package/dist/packages/canvas-adapter/insightsTab.d.ts +65 -3
- package/dist/packages/canvas-adapter/insightsTab.js +1 -1
- package/dist/packages/canvas-adapter/maplessSummary.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
- 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.d.ts +30 -2
- 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 +162 -37
- 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/dies.d.ts +1 -1
- package/dist/packages/renderer/axisTicks.d.ts +32 -0
- package/dist/packages/renderer/axisTicks.js +1 -0
- package/dist/packages/renderer/binColors.js +1 -1
- package/dist/packages/renderer/buildView.d.ts +11 -37
- package/dist/packages/renderer/buildView.js +1 -1
- package/dist/packages/renderer/buildWaferMap.d.ts +97 -12
- package/dist/packages/renderer/buildWaferMap.js +1 -1
- package/dist/packages/renderer/deprecate.d.ts +13 -5
- package/dist/packages/renderer/deprecate.js +1 -1
- package/dist/packages/renderer/derivedTests/apply.d.ts +85 -0
- package/dist/packages/renderer/derivedTests/apply.js +1 -0
- package/dist/packages/renderer/derivedTests/evaluate.d.ts +48 -0
- package/dist/packages/renderer/derivedTests/evaluate.js +1 -0
- package/dist/packages/renderer/derivedTests/parser.d.ts +148 -0
- package/dist/packages/renderer/derivedTests/parser.js +2 -0
- package/dist/packages/renderer/fmt.d.ts +24 -2
- package/dist/packages/renderer/fmt.js +1 -1
- package/dist/packages/renderer/index.d.ts +1 -0
- package/dist/packages/renderer/index.js +1 -1
- package/dist/packages/renderer/spec.d.ts +66 -0
- package/dist/packages/renderer/spec.js +1 -0
- package/dist/packages/renderer/testLabel.d.ts +154 -0
- package/dist/packages/renderer/testLabel.js +1 -0
- package/dist/packages/stats/analyzeWaferMap.js +1 -1
- package/dist/packages/stats/capability.d.ts +8 -2
- package/dist/packages/stats/capability.js +1 -1
- package/dist/packages/stats/correlation.d.ts +4 -1
- package/dist/packages/stats/correlation.js +1 -1
- package/dist/packages/stats/deprecated.d.ts +4 -1
- package/dist/packages/stats/deprecated.js +1 -1
- package/dist/packages/stats/index.d.ts +2 -2
- 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/renderFindingsReport.js +8 -28
- package/dist/packages/stats/renderSummaryReport.js +19 -28
- package/dist/packages/stats/reportHtml.d.ts +27 -0
- package/dist/packages/stats/reportHtml.js +35 -14
- package/dist/packages/stats/sweep.d.ts +186 -0
- package/dist/packages/stats/sweep.js +1 -0
- package/dist/packages/stats/sweepXFromName.d.ts +27 -0
- package/dist/packages/stats/sweepXFromName.js +1 -0
- package/dist/packages/stats/testPassRate.js +1 -1
- package/dist/packages/stats/types.d.ts +37 -4
- package/llms.txt +3 -2
- package/package.json +1 -1
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Evaluator for a parsed derived-test expression.
|
|
3
|
+
*
|
|
4
|
+
* Two rules govern every operation here, and they are deliberately different
|
|
5
|
+
* from each other:
|
|
6
|
+
*
|
|
7
|
+
* 1. **Scalars propagate "unknown" strictly.** If any operand is `undefined` —
|
|
8
|
+
* a test not run on this die, a verdict never recorded, a value outside its
|
|
9
|
+
* limits' knowability — the whole expression is `undefined`, which becomes an
|
|
10
|
+
* absent entry and renders as no-data grey. Never `?? 0`, never `?? false`.
|
|
11
|
+
* A missing measurement is not a zero and a missing verdict is not a fail.
|
|
12
|
+
*
|
|
13
|
+
* 2. **Reducers skip unknowns and count only what is known.** This is the one
|
|
14
|
+
* intentional break from (1), and it is why `countKnown` exists: a die binned
|
|
15
|
+
* out early legitimately has no verdict for the tests that never ran, and
|
|
16
|
+
* under strict propagation `all(testPass[1010..1025])` would grey out most of
|
|
17
|
+
* a bad wafer instead of saying what it was asked. A reducer over an entirely
|
|
18
|
+
* unknown vector still returns `undefined` — there is nothing to reduce.
|
|
19
|
+
*
|
|
20
|
+
* A non-finite arithmetic result (`0/0`, `ln(-1)`, overflow) is `undefined` too,
|
|
21
|
+
* for the same reason: `NaN` must reach the map as no-data, not as a value that
|
|
22
|
+
* drags a mean toward zero or classifies as in-spec.
|
|
23
|
+
*/
|
|
24
|
+
import type { ExprNode } from './parser.js';
|
|
25
|
+
/** Per-die data the evaluator is allowed to reach. Deliberately a closed set of
|
|
26
|
+
* functions rather than the die object, so the AST has no path to anything
|
|
27
|
+
* else on the die or in the host. */
|
|
28
|
+
export interface EvalContext {
|
|
29
|
+
/** Measured value of a parametric test, or `undefined` if not recorded. */
|
|
30
|
+
value(test: number): number | undefined;
|
|
31
|
+
/** Tester's recorded verdict, via `getTestPassStatus`. */
|
|
32
|
+
testPass(test: number): boolean | undefined;
|
|
33
|
+
/** Spec-limit judgement, via `classifySpec`. */
|
|
34
|
+
specPass(test: number): boolean | undefined;
|
|
35
|
+
/** The die's bin-based pass verdict, via `diePassStatus`. */
|
|
36
|
+
diePass(): boolean | undefined;
|
|
37
|
+
}
|
|
38
|
+
type Scalar = number | boolean | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Evaluate `node` for one die. Returns `undefined` when no answer is knowable.
|
|
41
|
+
*
|
|
42
|
+
* The AST is a plain data tree built by `parseExpression` and already statically
|
|
43
|
+
* type-checked, so this walk performs no validation and cannot reach any host
|
|
44
|
+
* value not exposed through `ctx`.
|
|
45
|
+
*/
|
|
46
|
+
export declare function evaluate(node: ExprNode, ctx: EvalContext): Scalar;
|
|
47
|
+
export {};
|
|
48
|
+
//# sourceMappingURL=evaluate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{minOf as c,maxOf as f}from"../../core/utils.js";function s(e){return Number.isFinite(e)?e:void 0}function l(e,t){const n=r=>e.kind==="t"?t.value(r):e.kind==="testPass"?t.testPass(r):t.specPass(r);return e.tests.map(n)}function o(e,t){const n=t.filter(r=>r!==void 0);if(e==="countKnown")return n.length;if(n.length!==0)switch(e){case"all":return n.every(Boolean);case"any":return n.some(Boolean);case"none":return!n.some(Boolean);case"countTrue":return n.filter(Boolean).length;case"countFalse":return n.filter(r=>!r).length;case"min":return c(n);case"max":return f(n);case"sum":return s(n.reduce((r,a)=>r+a,0));case"mean":return s(n.reduce((r,a)=>r+a,0)/n.length);default:return}}const d={abs:e=>Math.abs(e[0]),sqrt:e=>Math.sqrt(e[0]),ln:e=>Math.log(e[0]),log10:e=>Math.log10(e[0]),exp:e=>Math.exp(e[0]),floor:e=>Math.floor(e[0]),ceil:e=>Math.ceil(e[0]),round:e=>Math.round(e[0]),sign:e=>Math.sign(e[0]),pow:e=>Math.pow(e[0],e[1]),min:e=>c(e),max:e=>f(e)};export function evaluate(e,t){switch(e.k){case"num":return e.value;case"acc":return e.kind==="t"?t.value(e.test):e.kind==="testPass"?t.testPass(e.test):t.specPass(e.test);case"diePass":return t.diePass();case"vec":return;case"unary":{const n=evaluate(e.arg,t);return n===void 0?void 0:e.op==="-"?s(-n):!n}case"binary":{const n=evaluate(e.left,t);if(n===void 0)return;const r=evaluate(e.right,t);if(r===void 0)return;switch(e.op){case"+":return s(n+r);case"-":return s(n-r);case"*":return s(n*r);case"/":return s(n/r);case"%":return s(n%r);case"^":return s(Math.pow(n,r));case"<":return n<r;case"<=":return n<=r;case">":return n>r;case">=":return n>=r;case"==":return n===r;case"!=":return n!==r;case"and":return n&&r;case"or":return n||r}return}case"call":{if(e.name==="if"){const u=evaluate(e.args[0],t);return u===void 0?void 0:evaluate(e.args[u?1:2],t)}const n=e.args[0];if(e.args.length===1&&n!==void 0&&n.k==="vec")return o(e.name,l(n,t));const r=d[e.name];if(r===void 0)return;const a=[];for(const u of e.args){const i=evaluate(u,t);if(i===void 0)return;a.push(i)}return s(r(a))}}}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Expression parser for derived tests.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately hand-written and dependency-free. A general-purpose expression
|
|
5
|
+
* library (mathjs, expr-eval) would be the only security boundary between a
|
|
6
|
+
* shared `.json` template and the host application, and would have to be trusted
|
|
7
|
+
* to contain no path back to `Function`/`eval`. This grammar is closed: the only
|
|
8
|
+
* identifiers that resolve are the caller's own declared constants, the only data
|
|
9
|
+
* reachable is the die being evaluated, and there is no member access, no
|
|
10
|
+
* assignment, no indexing by anything but an integer literal, and no way to name
|
|
11
|
+
* a host object. Nothing in the AST can express a call to something not in
|
|
12
|
+
* `FUNCTIONS` below.
|
|
13
|
+
*
|
|
14
|
+
* Grammar (recursive descent, standard precedence):
|
|
15
|
+
*
|
|
16
|
+
* ```
|
|
17
|
+
* expr := or
|
|
18
|
+
* or := and ('or' and)*
|
|
19
|
+
* and := not ('and' not)*
|
|
20
|
+
* not := 'not' not | compare
|
|
21
|
+
* compare := add (('<' | '<=' | '>' | '>=' | '==' | '!=') add)?
|
|
22
|
+
* add := mul (('+' | '-') mul)*
|
|
23
|
+
* mul := pow (('*' | '/' | '%') pow)*
|
|
24
|
+
* pow := unary ('^' pow)? // right-associative
|
|
25
|
+
* unary := '-' unary | primary
|
|
26
|
+
* primary := NUMBER | IDENT | call | accessor | '(' expr ')'
|
|
27
|
+
* accessor := ('t' | 'testPass' | 'specPass') '[' INT ('..' INT)? ']'
|
|
28
|
+
* call := IDENT '(' (expr (',' expr)*)? ')'
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* The one non-scalar path: a range accessor (`t[1010..1015]`) produces a VECTOR,
|
|
32
|
+
* and a vector may ONLY be consumed by a reducer. There is no vector arithmetic,
|
|
33
|
+
* no elementwise operator and no vector-valued constant — which is what stops
|
|
34
|
+
* this grammar drifting into an array language.
|
|
35
|
+
*/
|
|
36
|
+
import type { TestDef } from '../buildWaferMap.js';
|
|
37
|
+
/** Static type of an expression node. Checked at parse time, so a type error is
|
|
38
|
+
* a build warning with a position rather than a runtime surprise on one die. */
|
|
39
|
+
export type ExprType = 'number' | 'boolean' | 'numberVec' | 'boolVec';
|
|
40
|
+
/** Which per-die quantity an accessor reads. Mirrors `passFailDisplay`'s
|
|
41
|
+
* vocabulary exactly: `'test'` is the tester's recorded verdict, `'spec'` is the
|
|
42
|
+
* spec-limit judgement. `t` is the measured value itself. */
|
|
43
|
+
export type AccessorKind = 't' | 'testPass' | 'specPass';
|
|
44
|
+
export type ExprNode = {
|
|
45
|
+
k: 'num';
|
|
46
|
+
value: number;
|
|
47
|
+
} | {
|
|
48
|
+
k: 'acc';
|
|
49
|
+
kind: AccessorKind;
|
|
50
|
+
test: number;
|
|
51
|
+
} | {
|
|
52
|
+
k: 'vec';
|
|
53
|
+
kind: AccessorKind;
|
|
54
|
+
tests: number[];
|
|
55
|
+
} | {
|
|
56
|
+
k: 'diePass';
|
|
57
|
+
} | {
|
|
58
|
+
k: 'unary';
|
|
59
|
+
op: '-' | 'not';
|
|
60
|
+
arg: ExprNode;
|
|
61
|
+
} | {
|
|
62
|
+
k: 'binary';
|
|
63
|
+
op: BinaryOp;
|
|
64
|
+
left: ExprNode;
|
|
65
|
+
right: ExprNode;
|
|
66
|
+
} | {
|
|
67
|
+
k: 'call';
|
|
68
|
+
name: string;
|
|
69
|
+
args: ExprNode[];
|
|
70
|
+
};
|
|
71
|
+
export type BinaryOp = '+' | '-' | '*' | '/' | '%' | '^' | '<' | '<=' | '>' | '>=' | '==' | '!=' | 'and' | 'or';
|
|
72
|
+
export interface ParseOk {
|
|
73
|
+
ok: true;
|
|
74
|
+
ast: ExprNode;
|
|
75
|
+
type: ExprType;
|
|
76
|
+
/** Every test number the expression reads, for dependency and validity checks. */
|
|
77
|
+
reads: {
|
|
78
|
+
kind: AccessorKind;
|
|
79
|
+
test: number;
|
|
80
|
+
}[];
|
|
81
|
+
/** True when the expression consults the die's bin-based pass verdict. */
|
|
82
|
+
usesDiePass: boolean;
|
|
83
|
+
}
|
|
84
|
+
export interface ParseErr {
|
|
85
|
+
ok: false;
|
|
86
|
+
/** Human-readable, already naming the position. */
|
|
87
|
+
message: string;
|
|
88
|
+
/** 0-based character offset in the source expression. */
|
|
89
|
+
position: number;
|
|
90
|
+
}
|
|
91
|
+
export type ParseResult = ParseOk | ParseErr;
|
|
92
|
+
export interface ParseContext {
|
|
93
|
+
/** Tests the expression may reference — the *source* defs only. A derived test
|
|
94
|
+
* referencing another derived test is rejected: chaining needs a dependency
|
|
95
|
+
* sort and a cycle check, which is a later feature, not a first one. */
|
|
96
|
+
testDefs: Map<number, TestDef>;
|
|
97
|
+
/** Caller-declared named constants. These are the ONLY resolvable identifiers
|
|
98
|
+
* besides function names, and they are substituted at parse time. */
|
|
99
|
+
constants: Record<string, number>;
|
|
100
|
+
}
|
|
101
|
+
/** Argument spec for a whitelisted function. `vec` accepts either vector type
|
|
102
|
+
* unless narrowed by `vecOf`. */
|
|
103
|
+
interface FnSpec {
|
|
104
|
+
/** Fixed scalar arity, when the function takes numbers. */
|
|
105
|
+
arity?: number;
|
|
106
|
+
/** Variadic scalar form, e.g. `min(a, b, c)`. */
|
|
107
|
+
variadic?: boolean;
|
|
108
|
+
/** Reducer form: takes exactly one vector of this type. */
|
|
109
|
+
vecOf?: 'numberVec' | 'boolVec' | 'any';
|
|
110
|
+
/** Result type. */
|
|
111
|
+
returns: ExprType;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The complete set of callable names. Scalar maths plus the vector reducers.
|
|
115
|
+
*
|
|
116
|
+
* `min`/`max` are the only overloaded names: `min(a, b)` over scalars and
|
|
117
|
+
* `min(t[1010..1015])` over a vector. Both mean the same thing, so one name is
|
|
118
|
+
* right; the parser picks the form from the argument types.
|
|
119
|
+
*/
|
|
120
|
+
export declare const FUNCTIONS: Record<string, FnSpec[]>;
|
|
121
|
+
/**
|
|
122
|
+
* Parse and statically type-check one derived-test expression.
|
|
123
|
+
*
|
|
124
|
+
* Never throws: a malformed expression comes back as `{ ok: false }` with a
|
|
125
|
+
* message and a character position, which `buildWaferMap` turns into a
|
|
126
|
+
* `derived-test-invalid` warning. The derived test is then dropped rather than
|
|
127
|
+
* evaluated — a half-working expression would silently plot wrong numbers.
|
|
128
|
+
*/
|
|
129
|
+
export declare function parseExpression(source: string, ctx: ParseContext): ParseResult;
|
|
130
|
+
export type TestReferenceResult = {
|
|
131
|
+
ok: true;
|
|
132
|
+
tests: number[];
|
|
133
|
+
range: boolean;
|
|
134
|
+
} | ParseErr;
|
|
135
|
+
/**
|
|
136
|
+
* Parse a standalone test reference — `"1020"` or `"1010..1030"` — with exactly
|
|
137
|
+
* the lexer and range rule an expression's `t[…]` uses. This is how a sweep's
|
|
138
|
+
* `tests` accepts ranges: the syntax is shared, never re-implemented, so the
|
|
139
|
+
* same text can never mean different tests in the two places.
|
|
140
|
+
*
|
|
141
|
+
* Never throws.
|
|
142
|
+
*/
|
|
143
|
+
export declare function parseTestReference(source: string, testDefs: Map<number, TestDef>): TestReferenceResult;
|
|
144
|
+
/** Bound on source length. A shared template is untrusted input; an expression
|
|
145
|
+
* longer than this is a mistake or an attack, not a measurement. */
|
|
146
|
+
export declare const MAX_EXPRESSION_LENGTH = 2000;
|
|
147
|
+
export {};
|
|
148
|
+
//# sourceMappingURL=parser.d.ts.map
|
|
@@ -0,0 +1,2 @@
|
|
|
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 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,3 +1,8 @@
|
|
|
1
|
+
/** Multiplier per SI prefix letter. Exported for the one place that PARSES a
|
|
2
|
+
* prefixed number (`stats/sweepXFromName.ts`), so reading and printing values
|
|
3
|
+
* agree on every prefix. Internal — not re-exported from the package. */
|
|
4
|
+
export declare const SI_PREFIX_SCALE: Record<string, number>;
|
|
5
|
+
export declare const KNOWN_BASE_UNITS: string[];
|
|
1
6
|
/**
|
|
2
7
|
* Plain-language labels for lot-stack aggregation methods. Maps internal API terms to the words
|
|
3
8
|
* shown to the user (e.g. `countBin` → "occurrence count"). Shared by the canvas map title and the
|
|
@@ -23,8 +28,25 @@ export declare function plainBinTerms(text: string): string;
|
|
|
23
28
|
* engineering notation (E±N, multiples of 3) outside that range.
|
|
24
29
|
*/
|
|
25
30
|
export declare function fmt(v: number, unit?: string, fallbackFormat?: 'si' | 'engineering'): string;
|
|
26
|
-
export
|
|
31
|
+
export interface AxisFormat {
|
|
32
|
+
/** A tick label: a bare number in the axis's shared scale. */
|
|
27
33
|
tickFmt: (v: number) => string;
|
|
34
|
+
/** A data value at an axis end (not on the tick grid) — see `makeEdgeFormatter`. */
|
|
35
|
+
edgeFmt: (v: number) => string;
|
|
36
|
+
/** Name plus scaled unit, shown once for the axis — e.g. `"Idsat (mA)"`. */
|
|
28
37
|
axisLabel: string;
|
|
29
|
-
|
|
38
|
+
/** The scaled unit alone (`"mA"`), for an axis that prints it on every tick; `''` when none. */
|
|
39
|
+
scaledUnit: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The formatting for one numeric axis: a shared SI scale chosen from `vRef`
|
|
43
|
+
* (typically the largest-magnitude value), applied to every tick so the axis
|
|
44
|
+
* reads consistently, with the scaled unit returned once.
|
|
45
|
+
*
|
|
46
|
+
* Pass `step` — the spacing of the ticks, from `fitTicks` (`axisTicks.ts`) —
|
|
47
|
+
* for a tick grid. Omit it for lone values, which keep size-based decimals.
|
|
48
|
+
* Without a unit, values in [0.1, 9999] are unscaled and, with no step, use
|
|
49
|
+
* `fmt()` as before.
|
|
50
|
+
*/
|
|
51
|
+
export declare function fmtColorbarAxis(vRef: number, name: string | null | undefined, unit: string | undefined, fallbackFormat?: 'si' | 'engineering', step?: number): AxisFormat;
|
|
30
52
|
//# sourceMappingURL=fmt.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const
|
|
1
|
+
import{stepDecimals as m}from"./axisTicks.js";const g=[[1e12,"T"],[1e9,"G"],[1e6,"M"],[1e3,"k"],[1,""],[.001,"m"],[1e-6,"\xB5"],[1e-9,"n"],[1e-12,"p"],[1e-15,"f"]];export const SI_PREFIX_SCALE={T:1e12,G:1e9,M:1e6,k:1e3,m:.001,\u00B5:1e-6,u:1e-6,n:1e-9,p:1e-12,f:1e-15},KNOWN_BASE_UNITS=["Hz","V","A","W","F","\u03A9","Ohm","ohm","S","H","J","C","s"];function M(e){for(const t of KNOWN_BASE_UNITS)if(e.length===t.length+1&&e.endsWith(t)){const i=e[0];if(i in SI_PREFIX_SCALE)return{preScale:SI_PREFIX_SCALE[i],base:t}}return{preScale:1,base:e}}function F(e,t){const{preScale:i,base:n}=M(t),r=e*i,o=Math.abs(r),[c,b]=g.find(([s])=>o>=s*.9999)??[1e-15,"f"],f=r/c,h=Math.abs(f),d=h>=100?0:h>=10?1:2;return`${f.toFixed(d)} ${b}${n}`}function S(e){const t=Math.abs(e),i=Math.floor(Math.log10(t)/3)*3,n=Math.max(-15,Math.min(12,i)),r=e/Math.pow(10,n),o=Math.abs(r),c=o>=100?0:o>=10?1:2,b=n===0?"":`E${n>0?"+":""}${n}`;return`${r.toFixed(c)}${b}`}export const AGGREGATION_METHOD_LABELS={mean:"mean",median:"median",stddev:"std dev",min:"min",max:"max",count:"count",countBin:"occurrence count",percent:"occurrence %"};export function fmtAggregationMethod(e){return AGGREGATION_METHOD_LABELS[e??""]??e??"aggregated"}export function plainBinTerms(e){return e.replace(/\bHBin\b/g,"hard bin").replace(/\bSBin\b/g,"soft bin")}export function fmt(e,t,i){if(!isFinite(e))return String(e);const n=Math.abs(e);return n===0?t?`0 ${t}`:"0":t!==void 0?F(e,t):i==="si"?F(e,""):n>=.1&&n<1e4?n>=1e3?e.toFixed(0):n>=100?e.toFixed(1):n>=10?e.toFixed(2):e.toFixed(3):S(e)}function E(e,t){const i=t!==void 0&&t>0?m(t/e):void 0;return n=>{if(!isFinite(n))return String(n);if(n===0)return"0";const r=n/e;if(i!==void 0)return r.toFixed(i);const o=Math.abs(r),c=o>=100?0:o>=10?1:2;return r.toFixed(c)}}function A(e,t,i){if(t===void 0||!(t>0))return i;const n=m(t/e)+1;return r=>{if(!isFinite(r))return String(r);if(r===0)return"0";const o=(r/e).toFixed(n);return o.endsWith("0")?o.slice(0,n===1?-2:-1):o}}export function fmtColorbarAxis(e,t,i,n="engineering",r){const o=Math.abs(e),c=(s,u,a,x)=>{const l=x&&r===void 0?x:E(s,r);return{tickFmt:l,edgeFmt:A(s,r,l),axisLabel:u,scaledUnit:a}};if(i){const{preScale:s,base:u}=M(i),a=o*s,[x,l]=a===0?[1,""]:g.find(([$])=>a>=$*.9999)??[1e-15,"f"],p=`${l}${u}`;return c(x/s,t?`${t} (${p})`:p,p)}if(o===0||o>=.1&&o<1e4)return c(1,t??"","",s=>fmt(s,void 0,n));if(n==="si"){const[s]=g.find(([x])=>o>=x*.9999)??[1e-15,"f"],u=Math.round(Math.log10(s)),a=u===0?"":`\xD710E${u}`;return c(s,t?a?`${t} (${a})`:t:a,a)}const b=Math.floor(Math.log10(o)/3)*3,f=Math.max(-15,Math.min(12,b)),h=Math.pow(10,f),d=f===0?"":`\xD710E${f}`;return c(h,t?d?`${t} (${d})`:t:d,d)}
|
|
@@ -5,5 +5,6 @@ export type { WaferMetadata, DieMetadata } from '../core/metadata.js';
|
|
|
5
5
|
export * from './colorSchemes.js';
|
|
6
6
|
export type { BinColors, BinColorOptions, BinColorSource, MapBinColorOptions } from './binColors.js';
|
|
7
7
|
export { binColorsForMaps } from './binColors.js';
|
|
8
|
+
export { DERIVED_MARK, DERIVED_KEY } from './testLabel.js';
|
|
8
9
|
export { valueToViridis, valueToGreyscale, getValueColorScheme, buildView, buildHoverText, buildMapTitle, resolveBinColors, getBinColorScheme, contrastTextColor, dieHasTestData, isParametricTest, getDieTestValue, STANDARD_WAFER_DIAMETERS_MM, } from './deprecated.js';
|
|
9
10
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export{getDieKey}from"./buildView.js";export*from"./buildWaferMap.js";export*from"./colorSchemes.js";export{binColorsForMaps}from"./binColors.js";export{valueToViridis,valueToGreyscale,getValueColorScheme,buildView,buildHoverText,buildMapTitle,resolveBinColors,getBinColorScheme,contrastTextColor,dieHasTestData,isParametricTest,getDieTestValue,STANDARD_WAFER_DIAMETERS_MM}from"./deprecated.js";
|
|
1
|
+
export{getDieKey}from"./buildView.js";export*from"./buildWaferMap.js";export*from"./colorSchemes.js";export{binColorsForMaps}from"./binColors.js";export{DERIVED_MARK,DERIVED_KEY}from"./testLabel.js";export{valueToViridis,valueToGreyscale,getValueColorScheme,buildView,buildHoverText,buildMapTitle,resolveBinColors,getBinColorScheme,contrastTextColor,dieHasTestData,isParametricTest,getDieTestValue,STANDARD_WAFER_DIAMETERS_MM}from"./deprecated.js";
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The single spec-limit judgement in the library: "is this measured value within
|
|
3
|
+
* the test's limits?"
|
|
4
|
+
*
|
|
5
|
+
* Every surface that shows or counts an out-of-spec die reads this — value-mode
|
|
6
|
+
* die fills and ▽/△ markers (`buildView`), the legend's spec tally, the spec
|
|
7
|
+
* pass-rate tables (`stats/testPassRate.ts`), and the `specPass[n]` accessor in
|
|
8
|
+
* derived-test expressions. It previously existed twice, once here and once in
|
|
9
|
+
* `testPassRate.ts`, and the two disagreed on both edge cases below.
|
|
10
|
+
*
|
|
11
|
+
* Two rules, and both are the *strict* reading:
|
|
12
|
+
*
|
|
13
|
+
* 1. **No limits defined ⇒ `null` (no judgement).** A test with no limits has not
|
|
14
|
+
* been given a spec, so there is nothing to be inside of. Reporting `'pass'`
|
|
15
|
+
* would manufacture an in-spec verdict out of an absence, and it would inflate
|
|
16
|
+
* a spec pass-rate to 100% for every unlimited test.
|
|
17
|
+
* 2. **A non-finite value ⇒ `null`.** `NaN` is not in spec. Comparison operators
|
|
18
|
+
* are false for `NaN`, so a naive `v < low` / `v > high` pair falls through to
|
|
19
|
+
* "pass" — which is exactly the "never drawn as plain in-spec" failure this
|
|
20
|
+
* judgement exists to prevent.
|
|
21
|
+
*
|
|
22
|
+
* `null` means no verdict and must be treated as no-data by every caller, never
|
|
23
|
+
* as a pass and never as a fail.
|
|
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
|
+
*
|
|
29
|
+
* Out-of-spec *classification* depends ONLY on whether limits are defined — never on
|
|
30
|
+
* `colorbarRangeMode`. An out-of-spec die is always flagged when limits exist, regardless
|
|
31
|
+
* of how the colorbar is scaled. The *form* of the indication, decided in
|
|
32
|
+
* `pushDieRectangles`, depends only on the effective `passFailDisplay`: under `'spec'` the
|
|
33
|
+
* die gets a solid green/blue/red categorical fill; in normal value/gradient mode it keeps
|
|
34
|
+
* the value gradient fill (so the distribution stays readable and out-of-spec colours don't
|
|
35
|
+
* collide with the scheme) and is flagged with a ▽/△ marker (`ViewRect.specMark`).
|
|
36
|
+
*/
|
|
37
|
+
export type SpecCategory = 'pass' | 'failHigh' | 'failLow';
|
|
38
|
+
/** Just the fields of a `TestDef` this judgement reads — kept structural so
|
|
39
|
+
* `core/` and `stats/` can call it without importing the full def. */
|
|
40
|
+
export interface SpecLimits {
|
|
41
|
+
limitLow?: number;
|
|
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;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Classify `value` against `limits`. Returns `null` when no verdict is possible:
|
|
50
|
+
* no value, a non-finite value, or a test with neither limit defined.
|
|
51
|
+
*/
|
|
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;
|
|
62
|
+
/** True when `limits` can produce a verdict at all — i.e. at least one limit is
|
|
63
|
+
* defined. Used to reject `specPass[n]` at parse time rather than silently
|
|
64
|
+
* yielding no-data for every die. */
|
|
65
|
+
export declare function hasSpecLimits(limits: SpecLimits | undefined): boolean;
|
|
66
|
+
//# sourceMappingURL=spec.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
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)}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import type { TestDef } from './buildWaferMap.js';
|
|
2
|
+
/**
|
|
3
|
+
* The derived-test part of anything that describes a test: a `TestDef`, or a
|
|
4
|
+
* narrower record carried onward from one — a finding's `variable`, a
|
|
5
|
+
* capability row. Structural, so every such shape is read by the same helpers.
|
|
6
|
+
*/
|
|
7
|
+
export type DerivedSource = {
|
|
8
|
+
derived?: true;
|
|
9
|
+
expression?: string;
|
|
10
|
+
} | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* Marks a test name as derived rather than measured.
|
|
13
|
+
*
|
|
14
|
+
* A dagger rather than the obvious `ƒ`: in this domain `f` is femto, and a `ƒ`
|
|
15
|
+
* beside a column of `fA`/`fF` units is a genuine misread. The dagger collides
|
|
16
|
+
* with no unit and no operator, and already means "see the note" — which is the
|
|
17
|
+
* contract: wherever the glyph can appear, {@link DERIVED_KEY} appears with it.
|
|
18
|
+
*/
|
|
19
|
+
export declare const DERIVED_MARK = "\u2020";
|
|
20
|
+
/**
|
|
21
|
+
* What {@link DERIVED_MARK} means, in words — the key every marked surface shows.
|
|
22
|
+
*
|
|
23
|
+
* Short on purpose: it has to fit the lower-right corner of a map beside the
|
|
24
|
+
* colorbar as well as a legend, and one string everywhere cannot drift. What a
|
|
25
|
+
* value was computed FROM is the expression, which tooltips show alongside it.
|
|
26
|
+
*/
|
|
27
|
+
export declare const DERIVED_KEY = "Derived, not measured";
|
|
28
|
+
/**
|
|
29
|
+
* True when this def describes a test computed from other tests rather than
|
|
30
|
+
* measured by the tester. Reads the flag `buildWaferMap` stamps on the defs it
|
|
31
|
+
* admits from `derivedTests` — never infers it from the test number, which is
|
|
32
|
+
* an application-defined identifier carrying no such meaning.
|
|
33
|
+
*/
|
|
34
|
+
export declare function isDerivedTest(def: DerivedSource): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* The display name for a test: its `name`, or `"Test <number>"` when it has no
|
|
37
|
+
* def or no name. The single source of that fallback.
|
|
38
|
+
*
|
|
39
|
+
* `testNumber` is passed separately so a caller holding only a number and a
|
|
40
|
+
* possibly-missing def gets the same string as one holding a complete def — the
|
|
41
|
+
* shape every previous copy of this rule had.
|
|
42
|
+
*
|
|
43
|
+
* Typed to the structural minimum rather than `TestDef` so the chart test
|
|
44
|
+
* picker's own `TestSelectItem` (which carries only a number and an optional
|
|
45
|
+
* name) uses the same fallback as everything else instead of keeping its copy.
|
|
46
|
+
*/
|
|
47
|
+
export declare function testLabel(def: {
|
|
48
|
+
name?: string;
|
|
49
|
+
} | undefined, testNumber: number): string;
|
|
50
|
+
/**
|
|
51
|
+
* {@link testLabel} with the derived-test marker in front when the def is
|
|
52
|
+
* derived — `"† On/Off Ratio"`. The form every surface uses to name a test: the
|
|
53
|
+
* map title, tooltips, findings, tables, pickers and menus.
|
|
54
|
+
*
|
|
55
|
+
* The marker always PRECEDES the name, everywhere (Paul, 2026-09-23). In a list
|
|
56
|
+
* the eye runs down the left edge, so leading marks form a column and a derived
|
|
57
|
+
* test is found at a glance, where trailing ones land at a different x on every
|
|
58
|
+
* row; and truncating a long name can never cut the marker off. One position on
|
|
59
|
+
* every surface, so a reader learns it once. A unit follows the name as usual —
|
|
60
|
+
* `"† Vth Margin (V)"` — so the marker qualifies the whole test.
|
|
61
|
+
*
|
|
62
|
+
* A list that should keep measured names aligned with marked ones pads them by
|
|
63
|
+
* the marker's width when any entry is derived — {@link DERIVED_LANE_PAD} for
|
|
64
|
+
* plain text, a fixed-width slot where there is DOM or canvas to measure with.
|
|
65
|
+
*/
|
|
66
|
+
export declare function markedTestLabel(def: (TestDef | {
|
|
67
|
+
name?: string;
|
|
68
|
+
derived?: true;
|
|
69
|
+
}) | undefined, testNumber: number): string;
|
|
70
|
+
/**
|
|
71
|
+
* Plain-text stand-in for `"† "` in front of a MEASURED name, for a text-only
|
|
72
|
+
* list (a native `<select>`) that holds derived tests too, so every name starts
|
|
73
|
+
* at about the same x. A figure space plus a thin space: close to the width of
|
|
74
|
+
* a dagger and a space in the UI fonts, and invisible. Approximate by nature —
|
|
75
|
+
* a surface that can measure uses a real fixed-width slot instead.
|
|
76
|
+
*/
|
|
77
|
+
export declare const DERIVED_LANE_PAD = "\u2007\u2009";
|
|
78
|
+
/**
|
|
79
|
+
* The one-line explanation of a derived test for a tooltip:
|
|
80
|
+
* `"† Derived, not measured: t[1010] / t[1020]"`, or without
|
|
81
|
+
* the expression when none was carried. `undefined` for a measured test.
|
|
82
|
+
*
|
|
83
|
+
* Plain text — the caller escapes it, like every other tooltip line.
|
|
84
|
+
*/
|
|
85
|
+
export declare function derivedTestNote(def: DerivedSource): string | undefined;
|
|
86
|
+
/**
|
|
87
|
+
* What a derived test was computed from, as a phrase fit to show a user, or
|
|
88
|
+
* `undefined` for a measured test.
|
|
89
|
+
*
|
|
90
|
+
* Deliberately returns the expression verbatim rather than prettifying it: it
|
|
91
|
+
* is the exact text the application author wrote and can be matched against
|
|
92
|
+
* their own test-program source, which a reformatted version could not be.
|
|
93
|
+
*/
|
|
94
|
+
export declare function derivedTestSource(def: DerivedSource): string | undefined;
|
|
95
|
+
/**
|
|
96
|
+
* `{ derived: true, expression }` for a derived test, `{}` for a measured one —
|
|
97
|
+
* spread into any row or record that carries a test onward (a capability row,
|
|
98
|
+
* a summary table entry, a finding's variable, a stacked card's def).
|
|
99
|
+
*
|
|
100
|
+
* The single form of that hand-off. Every place a test is re-described in a
|
|
101
|
+
* narrower shape is a place the flag can be dropped — it was, in
|
|
102
|
+
* `mergeTestDefs`, the Summary panel's table and `poolFunctionalYield`, each a
|
|
103
|
+
* row rebuilt field by field. A shape that spreads this cannot forget the
|
|
104
|
+
* expression while remembering the flag.
|
|
105
|
+
*/
|
|
106
|
+
export declare function derivedFields(def: DerivedSource): {
|
|
107
|
+
derived?: true;
|
|
108
|
+
expression?: string;
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* A label with the derived-test marker taken back out — for a surface that
|
|
112
|
+
* states derivation another way and so must not also carry a bare glyph (a
|
|
113
|
+
* CSV column, the key line that opens with the marker). The inverse of
|
|
114
|
+
* {@link markedTestLabel}, which is the only thing that marks a label, so the
|
|
115
|
+
* two live side by side.
|
|
116
|
+
*/
|
|
117
|
+
export declare function unmarkedLabel(label: string): string;
|
|
118
|
+
/** Header of the column a CSV export adds when any row is a derived test. */
|
|
119
|
+
export declare const DERIVED_CSV_HEADER = "Derived from";
|
|
120
|
+
/**
|
|
121
|
+
* The {@link DERIVED_CSV_HEADER} cell for one row. A CSV has nowhere to put a
|
|
122
|
+
* key, so a derived test is stated as data: its expression, or the key's
|
|
123
|
+
* words if none was carried — never blank, which would read as measured.
|
|
124
|
+
* Blank for a measured test.
|
|
125
|
+
*/
|
|
126
|
+
export declare function derivedCsvCell(entry: DerivedSource): string;
|
|
127
|
+
/**
|
|
128
|
+
* The key line for a surface that shows several tests at once with no hover to
|
|
129
|
+
* explain each one — a stats table, a findings list, a printed report:
|
|
130
|
+
* `"† Derived, not measured — Leakage Shift: abs(t[1020] - t[1010]); …"`.
|
|
131
|
+
* `undefined` when none of `entries` is derived, so the line appears exactly
|
|
132
|
+
* when a marked name is on the surface.
|
|
133
|
+
*
|
|
134
|
+
* Entries are de-duplicated by name, and the marker is dropped from each name
|
|
135
|
+
* ({@link unmarkedLabel}) since the line already opens with it. Plain text; the
|
|
136
|
+
* caller escapes it.
|
|
137
|
+
*/
|
|
138
|
+
export declare function derivedKeyText(entries: ReadonlyArray<{
|
|
139
|
+
label: string;
|
|
140
|
+
derived?: true;
|
|
141
|
+
expression?: string;
|
|
142
|
+
}>): string | undefined;
|
|
143
|
+
/**
|
|
144
|
+
* True when any test in this population is derived — the gate for reserving a
|
|
145
|
+
* marker gutter in a view.
|
|
146
|
+
*
|
|
147
|
+
* Reserving it per view rather than per row is what keeps the marker from
|
|
148
|
+
* shifting content relative to itself: every row of a view indents equally, so
|
|
149
|
+
* the markers align in a column instead of leaving a ragged edge, and a
|
|
150
|
+
* population with no derived tests — the common case — is unchanged to the
|
|
151
|
+
* pixel.
|
|
152
|
+
*/
|
|
153
|
+
export declare function hasDerivedTests(defs: readonly TestDef[] | undefined): boolean;
|
|
154
|
+
//# sourceMappingURL=testLabel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const DERIVED_MARK="\u2020",DERIVED_KEY="Derived, not measured";export function isDerivedTest(e){return e?.derived===!0}export function testLabel(e,r){const t=e?.name;return t!==void 0&&t!==""?t:`Test ${r}`}export function markedTestLabel(e,r){const t=testLabel(e,r);return e?.derived===!0?`\u2020 ${t}`:t}export const DERIVED_LANE_PAD="\u2007\u2009";export function derivedTestNote(e){if(!isDerivedTest(e))return;const r=derivedTestSource(e);return r?`\u2020 ${DERIVED_KEY}: ${r}`:`\u2020 ${DERIVED_KEY}`}export function derivedTestSource(e){if(!(!isDerivedTest(e)||!e?.expression))return e.expression}export function derivedFields(e){return isDerivedTest(e)?e.expression!==void 0?{derived:!0,expression:e.expression}:{derived:!0}:{}}export function unmarkedLabel(e){return e.startsWith("\u2020 ")?e.slice(2):e}export const DERIVED_CSV_HEADER="Derived from";export function derivedCsvCell(e){return isDerivedTest(e)?derivedTestSource(e)??DERIVED_KEY:""}export function derivedKeyText(e){const r=new Map;for(const n of e){if(!isDerivedTest(n))continue;const i=unmarkedLabel(n.label);r.has(i)||r.set(i,n.expression)}if(r.size===0)return;const t=[...r].map(([n,i])=>i?`${n}: ${i}`:n);return`\u2020 ${DERIVED_KEY} \u2014 ${t.join("; ")}`}export function hasDerivedTests(e){return(e??[]).some(isDerivedTest)}
|