@wafertools/wafermap 0.30.4 → 0.32.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 +21 -31
- package/CHANGELOG.md +265 -1
- package/README.md +3 -3
- package/dist/packages/canvas-adapter/charts/boxplot.js +1 -1
- package/dist/packages/canvas-adapter/charts/chartShell.d.ts +89 -6
- package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
- package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
- package/dist/packages/canvas-adapter/charts/scatter.d.ts +11 -1
- package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
- package/dist/packages/canvas-adapter/charts/trend.js +1 -1
- package/dist/packages/canvas-adapter/dieList.d.ts +1 -1
- package/dist/packages/canvas-adapter/dieList.js +7 -7
- package/dist/packages/canvas-adapter/drilldown.js +1 -1
- package/dist/packages/canvas-adapter/exportName.d.ts +1 -12
- package/dist/packages/canvas-adapter/exportName.js +1 -1
- package/dist/packages/canvas-adapter/index.d.ts +2 -3
- package/dist/packages/canvas-adapter/index.js +1 -1
- package/dist/packages/canvas-adapter/insightsTab.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +4 -7
- package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferMap.d.ts +32 -34
- package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
- package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
- package/dist/packages/canvas-adapter/toCanvas.d.ts +23 -2
- package/dist/packages/canvas-adapter/toCanvas.js +1 -1
- package/dist/packages/canvas-adapter/toolbar.js +2 -2
- package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
- package/dist/packages/canvas-adapter/userGuideHtml.js +38 -24
- package/dist/packages/canvas-adapter/version.d.ts +2 -2
- package/dist/packages/canvas-adapter/version.js +1 -1
- package/dist/packages/core/aggregates.d.ts +0 -6
- package/dist/packages/core/aggregates.js +1 -1
- package/dist/packages/core/dieTable.d.ts +137 -0
- package/dist/packages/core/dieTable.js +1 -0
- package/dist/packages/core/dies.d.ts +22 -2
- package/dist/packages/core/dies.js +1 -1
- package/dist/packages/core/index.d.ts +8 -10
- package/dist/packages/core/index.js +1 -1
- package/dist/packages/core/stdf.d.ts +9 -0
- package/dist/packages/core/stdf.js +1 -0
- package/dist/packages/core/transforms.d.ts +0 -23
- package/dist/packages/core/transforms.js +1 -1
- package/dist/packages/core/utils.d.ts +14 -0
- package/dist/packages/core/utils.js +1 -1
- package/dist/packages/renderer/buildView.d.ts +4 -12
- package/dist/packages/renderer/buildView.js +1 -1
- package/dist/packages/renderer/buildWaferMap.d.ts +42 -32
- package/dist/packages/renderer/buildWaferMap.js +1 -1
- package/dist/packages/renderer/colorMap.d.ts +0 -2
- package/dist/packages/renderer/colorMap.js +1 -1
- package/dist/packages/renderer/columnarInput.d.ts +87 -0
- package/dist/packages/renderer/columnarInput.js +1 -0
- package/dist/packages/renderer/deprecate.d.ts +1 -1
- package/dist/packages/renderer/deprecate.js +1 -1
- package/dist/packages/renderer/derivedTests/apply.d.ts +8 -6
- package/dist/packages/renderer/derivedTests/apply.js +1 -1
- package/dist/packages/renderer/index.d.ts +7 -5
- package/dist/packages/renderer/index.js +1 -1
- package/dist/packages/renderer/spec.d.ts +3 -7
- package/dist/packages/renderer/spec.js +1 -1
- package/dist/packages/stats/analyzeWaferLot.js +1 -1
- package/dist/packages/stats/analyzeWaferMap.d.ts +134 -2
- package/dist/packages/stats/analyzeWaferMap.js +1 -1
- package/dist/packages/stats/boxplot.js +1 -1
- package/dist/packages/stats/capability.d.ts +7 -7
- package/dist/packages/stats/capability.js +1 -1
- package/dist/packages/stats/clusterDetection.d.ts +2 -1
- package/dist/packages/stats/clusterDetection.js +1 -1
- package/dist/packages/stats/connectedComponents.js +1 -1
- package/dist/packages/stats/correlation.js +1 -1
- package/dist/packages/stats/facets.d.ts +1 -1
- package/dist/packages/stats/filterFindings.d.ts +5 -0
- package/dist/packages/stats/filterFindings.js +1 -1
- package/dist/packages/stats/histogram.js +1 -1
- package/dist/packages/stats/index.d.ts +6 -16
- package/dist/packages/stats/index.js +1 -1
- package/dist/packages/stats/math.d.ts +22 -6
- package/dist/packages/stats/math.js +1 -1
- package/dist/packages/stats/normalizeInput.js +1 -1
- package/dist/packages/stats/patternClassification.d.ts +5 -4
- package/dist/packages/stats/patternClassification.js +1 -1
- package/dist/packages/stats/regions.d.ts +9 -1
- package/dist/packages/stats/regions.js +1 -1
- package/dist/packages/stats/renderFindingsReport.d.ts +0 -4
- package/dist/packages/stats/renderFindingsReport.js +1 -21
- package/dist/packages/stats/renderSummaryReport.js +18 -18
- package/dist/packages/stats/scatter.js +1 -1
- package/dist/packages/stats/sweep.js +1 -1
- package/dist/packages/stats/testPassRate.js +1 -1
- package/dist/packages/stats/trend.js +1 -1
- package/dist/packages/stats/types.d.ts +5 -0
- package/dist/packages/worker/index.js +1 -1
- package/dist/packages/worker/wafermap.worker.d.ts +3 -0
- package/dist/packages/worker/wafermap.worker.js +1 -1
- package/package.json +2 -2
- package/dist/packages/canvas-adapter/deprecated.d.ts +0 -7
- package/dist/packages/canvas-adapter/deprecated.js +0 -1
- package/dist/packages/core/deprecated.d.ts +0 -60
- package/dist/packages/core/deprecated.js +0 -1
- package/dist/packages/renderer/deprecated.d.ts +0 -45
- package/dist/packages/renderer/deprecated.js +0 -1
- package/dist/packages/stats/deprecated.d.ts +0 -85
- package/dist/packages/stats/deprecated.js +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{dieHasValues as X,testValue as Y}from"../core/dieTable.js";import{isParametricTest as v}from"../renderer/buildWaferMap.js";import{markedTestLabel as P,derivedFields as L}from"../renderer/testLabel.js";export function pearsonFromSums(e,r,a,i,c,d){if(e<3)return null;const m=r/e,f=a/e,s=d/e-m*f,h=i/e-m*m,w=c/e-f*f,I=Math.sqrt(h*w);return I===0?null:Math.max(-1,Math.min(1,s/I))}export function pearsonOfPairs(e){let r=0,a=0,i=0,c=0,d=0,m=0;for(let f=0;f<e.length;f++){const{x:s,y:h}=e[f];!Number.isFinite(s)||!Number.isFinite(h)||(r++,a+=s,i+=h,c+=s*s,d+=h*h,m+=s*h)}return{r:pearsonFromSums(r,a,i,c,d,m),n:r}}export const CORRELATION_DIE_BUDGET=25e3;function S(e,r){const a=[],i=e/r;for(let c=0;c<r;c++)a.push(Math.floor(c*i));return a}export function correlationSampleNote(e){if(e)return`From a ${e.used.toLocaleString()}-die sample of ${e.of.toLocaleString()}, spread evenly across the lot`}function z(e){return e.filter(r=>r.testNumber!==void 0&&v(r)).map(r=>({testNumber:r.testNumber,label:P(r,r.testNumber),unit:r.unit,...L(r)}))}export function buildCorrelationMatrix(e,r){const a=z(r);if(a.length<2)return{tests:a,cells:[]};const i=a.length,c=a.map(n=>n.testNumber),d=i*(i-1)/2,m=new Float64Array(d),f=new Float64Array(i),s=new Float64Array(d),h=new Float64Array(d),w=new Float64Array(d),I=new Float64Array(d),A=new Float64Array(d);function R(n,o){return n*i-(n*(n+1)>>1)+(o-n-1)}const g=e.filter(X),t=g.length>CORRELATION_DIE_BUDGET,l=t?S(g.length,CORRELATION_DIE_BUDGET).map(n=>g[n]):g,u=new Float64Array(i),x=new Uint8Array(i);for(const n of l){x.fill(0);for(let o=0;o<i;o++){const b=Y(n,c[o]);b!==void 0&&Number.isFinite(b)&&(u[o]=b,x[o]=1)}for(let o=0;o<i;o++){if(!x[o])continue;f[o]++;const b=u[o];for(let F=o+1;F<i;F++){if(!x[F])continue;const M=u[F],N=R(o,F);m[N]++,s[N]+=b,h[N]+=M,w[N]+=b*b,I[N]+=M*M,A[N]+=b*M}}}const y=n=>pearsonFromSums(m[n],s[n],h[n],w[n],I[n],A[n]),p=[];for(let n=0;n<i;n++)for(let o=0;o<i;o++){if(o===n){p.push({xIndex:o,yIndex:n,r:1,n:f[o]});continue}const b=Math.min(o,n),F=Math.max(o,n),M=R(b,F);p.push({xIndex:o,yIndex:n,r:y(M),n:m[M]})}return t?{tests:a,cells:p,sample:{of:g.length,used:l.length}}:{tests:a,cells:p}}export function filterCorrelationMatrix(e,{threshold:r=.3,minTests:a=6,maxTests:i=20}={}){const c=[];let d=0,m=0,f=null;for(const t of e.cells){if(t.xIndex>=t.yIndex||t.r===null)continue;const l=Math.abs(t.r);c.push({xi:t.xIndex,yi:t.yIndex,absR:l}),(f===null||l>Math.abs(f.r))&&(f={xLabel:e.tests[t.xIndex].label,yLabel:e.tests[t.yIndex].label,r:t.r})}c.sort((t,l)=>l.absR-t.absR);const s=new Set;for(const{xi:t,yi:l,absR:u}of c){const x=u<r;s.size>=i||x&&s.size>=a||(s.add(t),s.size<i&&s.add(l))}if(s.size<Math.min(a,e.tests.length))for(let t=0;t<e.tests.length&&s.size<Math.min(i,a,e.tests.length);t++)s.add(t);const h=(()=>{const t=Array.from(s),l=new Map,u=new Map;for(const{xi:y,yi:p,absR:n}of c)!s.has(y)||!s.has(p)||(l.set(y,(l.get(y)??0)+n),l.set(p,(l.get(p)??0)+n),u.set(y,(u.get(y)??0)+1),u.set(p,(u.get(p)??0)+1));const x=y=>(u.get(y)??0)>0?l.get(y)/u.get(y):0;return t.sort((y,p)=>x(p)-x(y))})(),w=h.map(t=>e.tests[t]),I=new Map(h.map((t,l)=>[t,l])),A=new Set(w.map(t=>t.testNumber)),R=e.cells.filter(t=>A.has(e.tests[t.xIndex].testNumber)&&A.has(e.tests[t.yIndex].testNumber)).map(t=>({xIndex:I.get(t.xIndex),yIndex:I.get(t.yIndex),r:t.r,n:t.n}));let g=0;for(const{xi:t,yi:l,absR:u}of c)s.has(t)&&s.has(l)?u>=.7?d++:u>=.4&&m++:u<r&&g++;return{matrix:{tests:w,cells:R},strongPairs:d,moderatePairs:m,hiddenWeakPairs:g,strongestPair:f}}
|
|
@@ -59,7 +59,7 @@ export declare function commonMetadata(items: Array<{
|
|
|
59
59
|
metadata?: WaferMetadata;
|
|
60
60
|
}>): WaferMetadata;
|
|
61
61
|
export interface BuildFacetTableOptions {
|
|
62
|
-
/** Extra/override curation entries layered on top of
|
|
62
|
+
/** Extra/override curation entries layered on top of wmap's default curation — a caller's own app-specific keys (or a relabel of a default one) without losing wmap's built-in defaults. */
|
|
63
63
|
curation?: Record<string, FacetCuration>;
|
|
64
64
|
/** Restrict to fields curated `facet: true` (the default) plus any uncurated key. Pass `false` to include curated-but-`facet:false` fields too. */
|
|
65
65
|
facetableOnly?: boolean;
|
|
@@ -24,4 +24,9 @@ export declare function visibleFindings<T extends {
|
|
|
24
24
|
id: string;
|
|
25
25
|
absorbedIds?: string[];
|
|
26
26
|
}>(findings: T[]): T[];
|
|
27
|
+
/**
|
|
28
|
+
* What makes two findings "the same pattern" on different wafers. One key for
|
|
29
|
+
* the lot's wafer count and the gallery's per-card highlight of a lot finding.
|
|
30
|
+
*/
|
|
31
|
+
export declare function findingPatternKey(f: StatsFinding): string;
|
|
27
32
|
//# sourceMappingURL=filterFindings.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
function
|
|
1
|
+
function r(e){return Array.isArray(e)?e:[e]}export function filterFindings(e,i){let n=e.findings;if(i.severity!==void 0){const t=new Set(r(i.severity));n=n.filter(a=>t.has(a.severity))}if(i.kind!==void 0){const t=new Set(r(i.kind));n=n.filter(a=>t.has(a.variable.kind))}if(i.family!==void 0){const t=new Set(r(i.family));n=n.filter(a=>t.has(a.comparison.family))}if(i.level!==void 0){const t=new Set(r(i.level));n=n.filter(a=>t.has(a.level))}return n}export function visibleFindings(e){const i=new Set(e.flatMap(n=>n.absorbedIds??[]));return e.filter(n=>!i.has(n.id))}export function findingPatternKey(e){return[e.variable.kind,e.variable.index??"",e.variable.bin??"",e.comparison.family,e.comparison.left,e.effect.direction].join("|")}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export function collectTestValues(d,u){const t=[];for(const i of d)for(const
|
|
1
|
+
import{testValue as v}from"../core/dieTable.js";export function collectTestValues(d,u){const t=[];for(const i of d)for(const f of i.dies??[]){const o=v(f,u);o!==void 0&&Number.isFinite(o)&&t.push(o)}return t}export function testValueExtent(d,u){let t=1/0,i=-1/0;for(const f of d)for(const o of f.dies??[]){const n=v(o,u);n!==void 0&&Number.isFinite(n)&&(n<t&&(t=n),n>i&&(i=n))}return t===1/0?{min:NaN,max:NaN}:{min:t,max:i}}export function buildTestHistogramData(d,u,t=16,i,f,o){let n=collectTestValues(d,u);if(o&&(n=n.filter(e=>e>=o.lo&&e<=o.hi)),n.length===0)return[];let r=n[0],a=n[0];for(let e=1;e<n.length;e++)n[e]<r&&(r=n[e]),n[e]>a&&(a=n[e]);const l=i!==void 0?Math.min(r,i):r,M=((f!==void 0?Math.max(a,f):a)-l||1)/t,h=Array.from({length:t},(e,x)=>({rangeLow:l+x*M,rangeHigh:l+(x+1)*M,count:0}));for(const e of n){const x=Math.min(t-1,Math.floor((e-l)/M));h[x].count++}return h}export function buildTestHistogramSeries(d,u,t=16,i,f,o){const n=new Map;let r=1/0,a=-1/0;for(const s of d){const m=[];for(const y of s.items)for(const p of y.dies??[]){const c=v(p,u);if(c!==void 0&&Number.isFinite(c)){if(o&&(c<o.lo||c>o.hi))continue;m.push(c),c<r&&(r=c),c>a&&(a=c)}}n.set(s.key,m)}const l=d.map(s=>s.key).filter(s=>n.get(s).length>0);if(l.length===0)return{ranges:[],series:[]};const g=i!==void 0?Math.min(r,i):r,h=((f!==void 0?Math.max(a,f):a)-g||1)/t,e=Array.from({length:t},(s,m)=>({rangeLow:g+m*h,rangeHigh:g+(m+1)*h})),x=l.map(s=>{const m=new Array(t).fill(0);for(const y of n.get(s)){const p=Math.min(t-1,Math.floor((y-g)/h));m[p]++}return{groupKey:s,counts:m}});return{ranges:e,series:x}}
|
|
@@ -1,26 +1,16 @@
|
|
|
1
1
|
export * from './types.js';
|
|
2
|
-
export
|
|
3
|
-
export
|
|
4
|
-
export
|
|
5
|
-
export
|
|
2
|
+
export { analyzeWaferMap } from './analyzeWaferMap.js';
|
|
3
|
+
export { analyzeWaferLot } from './analyzeWaferLot.js';
|
|
4
|
+
export { filterFindings, visibleFindings } from './filterFindings.js';
|
|
5
|
+
export type { FindingsFilter } from './filterFindings.js';
|
|
6
6
|
export type { PatternLabel, PatternClassification, PatternFeatures } from './patternClassification.js';
|
|
7
7
|
export { setReportOpener } from './renderFindingsReport.js';
|
|
8
8
|
export { renderWaferReportHtml, renderLotReportHtml } from './renderSummaryReport.js';
|
|
9
9
|
export type { ReportMap } from './renderSummaryReport.js';
|
|
10
|
-
export type { SummaryReportParams
|
|
10
|
+
export type { SummaryReportParams } from './renderSummaryReport.js';
|
|
11
11
|
export { buildFacetTable, facetValueOf, FACET_NONE_VALUE } from './facets.js';
|
|
12
12
|
export type { FacetField, FacetValue, FacetCuration, FacetItem, BuildFacetTableOptions } from './facets.js';
|
|
13
|
-
export type {
|
|
13
|
+
export type { MetadataKeySelection } from './metadataColumns.js';
|
|
14
14
|
export { mergeTestDefs } from './mergeTestDefs.js';
|
|
15
15
|
export type { MergedTestDefs, TestDefConflict, TestDefConflictKind } from './mergeTestDefs.js';
|
|
16
|
-
export type { CapabilityDatum, CapabilityItem } from './capability.js';
|
|
17
|
-
export type { CorrelationTestInfo, CorrelationCell, CorrelationMatrix, CorrelationSummary } from './correlation.js';
|
|
18
|
-
export type { BoxplotDatum, BoxplotItem } from './boxplot.js';
|
|
19
|
-
export type { TrendDatum, TrendItem } from './trend.js';
|
|
20
|
-
export type { TestPassKind, TestPassRateData, TestPassRateItem, TestPassRateRow, TestPassRateValue } from './testPassRate.js';
|
|
21
|
-
export type { HistogramBucket, HistogramItem, HistogramSeries, HistogramSeriesData } from './histogram.js';
|
|
22
|
-
export type { ScatterPoint, ScatterItem } from './scatter.js';
|
|
23
|
-
export type { ChartDatum, YieldItem, YieldSortBy } from './yield.js';
|
|
24
|
-
export type { BinType, BinItem, BinCluster, BinClusterData } from './binPareto.js';
|
|
25
|
-
export { buildCapabilityData, buildCorrelationMatrix, filterCorrelationMatrix, buildTestBoxplotData, buildTestTrendData, trendCentre, buildTestPassRateData, hasJudgeableTests, buildTestHistogramData, buildTestHistogramSeries, buildScatterData, buildScatterDataGrouped, buildYieldData, buildYieldDataCombined, buildBinParetoData, buildBinClusterData, buildRingRegions, buildQuadrantRegions, buildSectorRegions, buildReticlePositionRegions, buildTestSiteRegions, buildRegionYieldData, areQuadrantsAdjacent, parseRegionKey, sectorCompassNames, classifyPattern, computeFunctionalYield, resolveMetadataColumns, discoverDieMetadataKeys, openHtmlReport, renderSummaryReportHtml, renderLotSummaryReportHtml, DEFAULT_FACET_CURATION, renderFindingsReportHtml, } from './deprecated.js';
|
|
26
16
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export*from"./types.js";export
|
|
1
|
+
export*from"./types.js";export{analyzeWaferMap}from"./analyzeWaferMap.js";export{analyzeWaferLot}from"./analyzeWaferLot.js";export{filterFindings,visibleFindings}from"./filterFindings.js";export{setReportOpener}from"./renderFindingsReport.js";export{renderWaferReportHtml,renderLotReportHtml}from"./renderSummaryReport.js";export{buildFacetTable,facetValueOf,FACET_NONE_VALUE}from"./facets.js";export{mergeTestDefs}from"./mergeTestDefs.js";
|
|
@@ -9,27 +9,43 @@ export declare function normalCdf(value: number): number;
|
|
|
9
9
|
*
|
|
10
10
|
* `ArrayLike<number>`, not `number[]`, so a caller holding its values in a
|
|
11
11
|
* `Float64Array` does not have to copy them back into a plain array to be
|
|
12
|
-
* read
|
|
12
|
+
* read. */
|
|
13
13
|
export declare function quantile(sorted: ArrayLike<number>, q: number): number;
|
|
14
|
-
/** Descriptive statistics of one population of values — see {@link
|
|
14
|
+
/** Descriptive statistics of one population of values — see {@link describeValues}. */
|
|
15
15
|
export interface DescriptiveStats {
|
|
16
16
|
min: number;
|
|
17
17
|
max: number;
|
|
18
18
|
mean: number;
|
|
19
19
|
count: number;
|
|
20
|
-
/** Population standard deviation (divide by n) — see {@link
|
|
20
|
+
/** Population standard deviation (divide by n) — see {@link describeValues}. */
|
|
21
21
|
stddev: number;
|
|
22
22
|
median: number;
|
|
23
23
|
q1: number;
|
|
24
24
|
q3: number;
|
|
25
25
|
}
|
|
26
|
-
/** Min/max/mean/count/σ and the quartiles of
|
|
27
|
-
*
|
|
26
|
+
/** Min/max/mean/count/σ and the quartiles of `values` — the one copy of the
|
|
27
|
+
* descriptive-statistics formulas behind the Test Values table.
|
|
28
|
+
*
|
|
29
|
+
* **Reorders `values`** (a partial sort, in place): the quartiles are found by
|
|
30
|
+
* selection, not by sorting every value. They are the same order statistics a
|
|
31
|
+
* sort would give, interpolated as {@link quantile} does, so the figures are
|
|
32
|
+
* identical; a full sort was ~24 ms per test at 266k values in Chrome and made
|
|
33
|
+
* up almost all of a large lot panel's time. The mean and σ sum in whatever
|
|
34
|
+
* order `values` arrives in.
|
|
28
35
|
*
|
|
29
36
|
* σ is the POPULATION standard deviation (divide by n), which is what this
|
|
30
37
|
* table has always shown; the capability indices next to it deliberately use
|
|
31
38
|
* the sample form (n−1) and say so. Two-pass, not `Σx² − n·x̄²`: the moments
|
|
32
39
|
* form loses most of its significant digits when the variance is small beside
|
|
33
40
|
* the mean, which is the normal shape of a passing parametric test. */
|
|
34
|
-
export declare function
|
|
41
|
+
export declare function describeValues(values: Float64Array): DescriptiveStats;
|
|
42
|
+
/** Min, quartiles and max of `values` — what sorting them and reading the
|
|
43
|
+
* ends and {@link quantile}s would give — by selection. **Reorders `values`.** */
|
|
44
|
+
export declare function fiveNumberSummary(values: Float64Array): {
|
|
45
|
+
min: number;
|
|
46
|
+
q1: number;
|
|
47
|
+
median: number;
|
|
48
|
+
q3: number;
|
|
49
|
+
max: number;
|
|
50
|
+
};
|
|
35
51
|
//# sourceMappingURL=math.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export function errorFunction(
|
|
1
|
+
export function errorFunction(t){const o=t<0?-1:1,n=Math.abs(t),e=.254829592,c=-.284496736,s=1.421413741,i=-1.453152027,f=1.061405429,r=1/(1+.3275911*n),u=1-((((f*r+i)*r+s)*r+c)*r+e)*r*Math.exp(-n*n);return o*u}export function normalCdf(t){return .5*(1+errorFunction(t/Math.sqrt(2)))}export function quantile(t,o){if(t.length===0)return NaN;const n=o*(t.length-1),e=Math.floor(n),c=Math.ceil(n);return t[e]+(t[c]-t[e])*(n-e)}export function describeValues(t){const o=t.length;let n=0;for(let u=0;u<o;u++)n+=t[u];const e=n/o;let c=0;for(let u=0;u<o;u++)c+=(t[u]-e)**2;const{min:s,q1:i,median:f,q3:l,max:r}=fiveNumberSummary(t);return{min:s,max:r,mean:e,count:o,stddev:Math.sqrt(c/o),median:f,q1:i,q3:l}}export function fiveNumberSummary(t){let o=1/0,n=-1/0;for(let i=0;i<t.length;i++){const f=t[i];f<o&&(o=f),f>n&&(n=f)}t.length===0&&(o=NaN,n=NaN);const{q1:e,median:c,q3:s}=y(t);return{min:o,q1:e,median:c,q3:s,max:n}}function y(t){const o=t.length;if(o===0)return{q1:NaN,median:NaN,q3:NaN};const n=o-1,e=(M,q,N)=>{const p=M*n,h=Math.floor(p),a=g(t,h,q,N);if(p===h)return a;let m=1/0;for(let x=h+1;x<=N;x++)t[x]<m&&(m=t[x]);return a+(m-a)*(p-h)},c=Math.floor(.5*n),s=e(.5,0,n),i=Math.floor(.25*n),f=i===c?e(.25,c,n):u(.25,i,c),r=Math.floor(.75*n)===c?e(.75,c,n):e(.75,c+1,n);return{q1:f,median:s,q3:r};function u(M,q,N){const p=M*n,h=g(t,q,0,N-1);if(p===q)return h;let a=t[N];for(let m=q+1;m<N;m++)t[m]<a&&(a=t[m]);return h+(a-h)*(p-q)}}function g(t,o,n,e){for(;n<e;){const c=t[n],s=t[n+e>>>1],i=t[e],f=c<s?s<i?s:c<i?i:c:c<i?c:s<i?i:s;let l=n,r=e;for(;l<=r;){for(;t[l]<f;)l++;for(;t[r]>f;)r--;if(l<=r){const u=t[l];t[l]=t[r],t[r]=u,l++,r--}}if(o<=r)e=r;else if(o>=l)n=l;else break}return t[o]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{buildWaferMap as
|
|
1
|
+
import{buildWaferMap as o,isBuiltMap as a}from"../renderer/buildWaferMap.js";export function normalizeInput(r){return a(r)?r:o(r)}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { PositionedDie
|
|
1
|
+
import type { PositionedDie } from '../core/dies.js';
|
|
2
|
+
import type { Wafer } from '../core/wafer.js';
|
|
2
3
|
export type PatternLabel = 'center' | 'donut' | 'edge-ring' | 'edge-local' | 'scratch' | 'near-full' | 'random' | 'none';
|
|
3
4
|
export interface PatternFeatures {
|
|
4
5
|
/** Ratio of failing dies to total eligible dies (whole-wafer). */
|
|
@@ -80,14 +81,14 @@ export interface PatternThresholds {
|
|
|
80
81
|
* | Pattern | Recall | Notes |
|
|
81
82
|
* |------------|--------|--------------------------------------------|
|
|
82
83
|
* | Near-full | 100% | |
|
|
83
|
-
* | Edge-ring |
|
|
84
|
+
* | Edge-ring | 75% | |
|
|
84
85
|
* | Edge-local | 65% | |
|
|
85
86
|
* | Center | 60% | |
|
|
86
87
|
* | Random | 59% | |
|
|
87
|
-
* | Scratch |
|
|
88
|
+
* | Scratch | 24% | Fragmented patterns harder to detect |
|
|
88
89
|
* | Donut | 15% | Geometrically similar to center with noise |
|
|
89
90
|
*
|
|
90
|
-
* Overall accuracy: 64% exact match, 86% detection rate (any pattern flagged).
|
|
91
|
+
* Overall accuracy: 64% exact match, 86.4% detection rate (any pattern flagged).
|
|
91
92
|
*/
|
|
92
93
|
export declare const DEFAULT_PATTERN_THRESHOLDS: PatternThresholds;
|
|
93
94
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{findConnectedComponents as J}from"./connectedComponents.js";import{clamp01 as K}from"../core/utils.js";export const DEFAULT_PATTERN_THRESHOLDS={nearFullGlobalRdd:.6,edgeRingEdgeRdd:.18,edgeRingEdgeRddHigh:.3,edgeRingRadialSpread:.45,edgeRingAngularSpread:.6,edgeLocalEdgeRdd:.1,edgeLocalCentroidDist:.76,donutP25Dist:.4,centerCentroidDist:.22,centerCentroidDistHigh:.1,centerEdgeRdd:.35,donutEdgeRdd:.22,scratchLinearScore:.25,scratchLinearScoreHigh:.45,scratchEccentricity:.75,minimumFailingDies:5};function
|
|
1
|
+
import{findConnectedComponents as J}from"./connectedComponents.js";import{clamp01 as K}from"../core/utils.js";export const DEFAULT_PATTERN_THRESHOLDS={nearFullGlobalRdd:.6,edgeRingEdgeRdd:.18,edgeRingEdgeRddHigh:.3,edgeRingRadialSpread:.45,edgeRingAngularSpread:.6,edgeLocalEdgeRdd:.1,edgeLocalCentroidDist:.76,donutP25Dist:.4,centerCentroidDist:.22,centerCentroidDistHigh:.1,centerEdgeRdd:.35,donutEdgeRdd:.22,scratchLinearScore:.25,scratchLinearScoreHigh:.45,scratchEccentricity:.75,minimumFailingDies:5};function q(e,t,r){const i=e.physX-t.center.x,s=e.physY-t.center.y,d=Math.sqrt(i*i+s*s)/t.radius;return Math.min(r,Math.max(1,Math.floor(d*r)+1))===r}function Q(e,t,r){if(e.length<3)return 0;let i=0,s=0,d=0;for(const h of e){const l=h.physX-t,D=h.physY-r;i+=l*l,s+=D*D,d+=l*D}i/=e.length,s/=e.length,d/=e.length;const c=i+s,n=i*s-d*d,a=Math.max(0,(c/2)**2-n),u=c/2+Math.sqrt(a),f=c/2-Math.sqrt(a);if(u<=0)return 0;const R=K(f/u);return Math.sqrt(1-R)}function V(e,t){if(t===0)return 0;const r=new Map,i=new Map,s=new Map,d=new Map;for(const n of e)r.set(n.y,(r.get(n.y)??0)+1),i.set(n.x,(i.get(n.x)??0)+1),s.set(n.x-n.y,(s.get(n.x-n.y)??0)+1),d.set(n.x+n.y,(d.get(n.x+n.y)??0)+1);let c=0;for(const n of r.values())n>c&&(c=n);for(const n of i.values())n>c&&(c=n);for(const n of s.values())n>c&&(c=n);for(const n of d.values())n>c&&(c=n);return c/t}function W(e,t,r,i){const s=t.length,d=e.length,c=s>0?d/s:0,n=t.filter(o=>q(o,r,i)),a=e.filter(o=>q(o,r,i)),u=n.length>0?a.length/n.length:0,f=J(e),R=f.length>0?[...f].sort((o,g)=>g.length-o.length):[e],h=R[0],l=R.slice(0,5).flat(),D=h.length,H=d>0?D/d:0,m=r.center.x,p=r.center.y,y=r.radius;let E=0,N=0,M=1/0,S=0;for(const o of h){E+=o.physX,N+=o.physY;const g=Math.sqrt((o.physX-m)**2+(o.physY-p)**2);g<M&&(M=g),g>S&&(S=g)}const O=E/h.length,P=N/h.length,T=Math.sqrt((O-m)**2+(P-p)**2)/y,I=M===1/0?0:M/y,A=Math.min(S/y,1),C=e.map(o=>Math.sqrt((o.physX-m)**2+(o.physY-p)**2)/y).sort((o,g)=>o-g),v=C[Math.floor(C.length*.25)]??0,b=t.filter(o=>Math.sqrt((o.physX-m)**2+(o.physY-p)**2)/y<.5).length,L=t.filter(o=>Math.sqrt((o.physX-m)**2+(o.physY-p)**2)/y>=.5).length,w=e.filter(o=>Math.sqrt((o.physX-m)**2+(o.physY-p)**2)/y<.5).length,z=e.filter(o=>Math.sqrt((o.physX-m)**2+(o.physY-p)**2)/y>=.5).length,X=b>0?w/b:0,Y=L>0?z/L:0,G=Y>0?X/Y:X>0?2:1,_=l.reduce((o,g)=>o+g.physX,0)/l.length,k=l.reduce((o,g)=>o+g.physY,0)/l.length,U=Q(l,_,k),B=V(l,l.length),x=16,F=new Set;for(const o of a){const g=(Math.atan2(o.physY-p,o.physX-m)+2*Math.PI)%(2*Math.PI);F.add(Math.floor(g/(2*Math.PI)*x)%x)}const j=a.length>0?F.size/x:0;return{globalRdd:c,edgeRdd:u,centroidDistNorm:T,minDistNorm:I,maxDistNorm:A,p25DistNorm:v,eccentricity:U,linearScore:B,salienceSize:D,salienceFraction:H,edgeAngularSpread:j,innerOuterRatio:G}}function Z(e,t,r){const i=Math.max(5,Math.round(r*.003)),s=e.salienceSize>=i;if(e.globalRdd<t.nearFullGlobalRdd&&e.p25DistNorm>=t.edgeLocalCentroidDist&&e.edgeAngularSpread>=t.edgeRingAngularSpread&&e.edgeRdd>=t.edgeLocalEdgeRdd)return{pattern:"edge-ring",confidence:e.edgeRdd>=t.edgeRingEdgeRddHigh?"high":"medium"};if(e.globalRdd<t.nearFullGlobalRdd&&e.salienceFraction<.1)return{pattern:"random",confidence:"low"};if(e.globalRdd>=t.nearFullGlobalRdd)return{pattern:"near-full",confidence:e.globalRdd>=.8?"high":"medium"};if(s&&e.linearScore>=t.scratchLinearScore&&e.eccentricity>=t.scratchEccentricity){const a=e.linearScore>=t.scratchLinearScoreHigh?"high":"medium";return{pattern:"scratch",confidence:a,note:a==="medium"?"Scratch detection is less reliable for fragmented or diagonal patterns":void 0}}const d=s&&e.edgeRdd>=t.edgeLocalEdgeRdd&&e.maxDistNorm>=.7;if(d&&e.p25DistNorm>=t.edgeLocalCentroidDist&&e.maxDistNorm-e.minDistNorm<=t.edgeRingRadialSpread)return{pattern:"edge-ring",confidence:e.edgeRdd>=t.edgeRingEdgeRddHigh?"high":"medium"};if(d&&e.p25DistNorm>=.55&&e.p25DistNorm<t.edgeLocalCentroidDist)return{pattern:"edge-local",confidence:e.edgeRdd>=t.edgeRingEdgeRdd?"medium":"low"};const c=s&&e.centroidDistNorm<=t.centerCentroidDist&&e.edgeRdd<=t.centerEdgeRdd,n="Center and donut patterns have similar geometry; classification may be imprecise";if(c&&e.p25DistNorm<t.donutP25Dist){const a=e.centroidDistNorm<=t.centerCentroidDistHigh?"high":"medium";return{pattern:"center",confidence:a,note:a!=="high"?n:void 0}}return c&&e.p25DistNorm>=t.donutP25Dist&&e.p25DistNorm<.55?{pattern:"donut",confidence:"medium",note:n}:{pattern:"random",confidence:"low"}}export function classifyPattern(e,t,r){const i={...DEFAULT_PATTERN_THRESHOLDS},s=r.ringCount??4,d=new Set(r.passBins),c=e.filter(h=>{const l=h.hbin??h.sbin;return l!==void 0&&!d.has(l)}),n=Math.max(i.minimumFailingDies,Math.round(e.length*.003));if(c.length<n)return null;const a=W(c,e,t,s),{pattern:u,confidence:f,note:R}=Z(a,i,e.length);return{pattern:u,confidence:f,features:a,...R?{note:R}:{}}}
|
|
@@ -1,10 +1,18 @@
|
|
|
1
|
-
import type { Die, PositionedDie
|
|
1
|
+
import type { Die, PositionedDie } from '../core/dies.js';
|
|
2
|
+
import type { Wafer } from '../core/wafer.js';
|
|
2
3
|
import type { ReticleConfig } from '../renderer/buildWaferMap.js';
|
|
3
4
|
export interface StatsRegion {
|
|
4
5
|
family: 'ring' | 'quadrant' | 'reticle-position' | 'test-site' | 'sector';
|
|
5
6
|
key: string;
|
|
6
7
|
label: string;
|
|
8
|
+
/** `getDieKey` of each member — what findings carry, for highlighting. */
|
|
7
9
|
dieKeys: string[];
|
|
10
|
+
/**
|
|
11
|
+
* The member dies themselves, parallel to `dieKeys`. Analysis reads these:
|
|
12
|
+
* finding a region's dies again by key string cost more than a quarter of a
|
|
13
|
+
* large wafer's analysis.
|
|
14
|
+
*/
|
|
15
|
+
dies: Die[];
|
|
8
16
|
}
|
|
9
17
|
export interface RegionYieldDatum {
|
|
10
18
|
/** Region identity, e.g. `ring:2` or `quadrant:NE` — parse with `parseRegionKey`. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{classifyDie as
|
|
1
|
+
import{classifyDie as N,getRingLabel as W}from"../core/classify.js";import{getReticleCell as b}from"../core/reticle.js";import{isPositionedDie as x,diePassStatus as M,getDieKey as R}from"../core/dies.js";function p(n){return R(n)}export function buildRegionYieldData(n,o,r,e,t){const i=new Map,a=[];for(let s=0;s<o.length;s++){const c=n[s];if(!c?.length)continue;const u=new Set(typeof e=="function"?e(s):e),d=t(c.filter(x),o[s],r);for(const l of d){a.includes(l.key)||a.push(l.key);const g=i.get(l.key)??{label:l.label,pass:0,total:0};for(const f of l.dies){if(f.partial||f.edgeExcluded)continue;const y=M(f,u);y!==void 0&&(g.total++,y&&g.pass++)}i.set(l.key,g)}}return a.map(s=>{const c=i.get(s);return c.total===0?null:{key:s,label:c.label,n:c.total,passDies:c.pass,yieldPercent:c.pass/c.total*100}}).filter(s=>s!==null)}const k=["E","ENE","NE","NNE","N","NNW","NW","WNW","W","WSW","SW","SSW","S","SSE","SE","ESE"],q=["E","NE","N","NW","W","SW","S","SE"],K=["E","N","W","S"];export function sectorCompassNames(n){const o=[4,8,16,32].includes(n)?n:16;return o===4?K:o===8?q:k}const P={NE:["NW","SE"],NW:["NE","SW"],SE:["NE","SW"],SW:["NW","SE"]};export function areQuadrantsAdjacent(n,o){return P[n]?.includes(o)??!1}export function parseRegionKey(n){return n.startsWith("ring:")?{family:"ring",ring:Number(n.slice(5))}:n.startsWith("quadrant:")?{family:"quadrant",quadrant:n.slice(9)}:n.startsWith("sector:")?{family:"sector",sector:n.slice(7)}:{family:"unknown"}}export function buildRingRegions(n,o,r){const e=new Map;for(const t of n){const{ring:i}=N(t,o,{ringCount:r}),a=`ring:${i}`,s=e.get(a)??{family:"ring",key:a,label:W(i,r),dieKeys:[],dies:[]};s.dieKeys.push(p(t)),s.dies.push(t),e.set(a,s)}return[...e.values()].sort((t,i)=>t.key.localeCompare(i.key))}export function buildQuadrantRegions(n,o,r){const e=new Map;for(const i of n){const{quadrant:a}=N(i,o,{ringCount:r}),s=`quadrant:${a}`,c=e.get(s)??{family:"quadrant",key:s,label:a,dieKeys:[],dies:[]};c.dieKeys.push(p(i)),c.dies.push(i),e.set(s,c)}const t=new Map([["quadrant:NE",0],["quadrant:NW",1],["quadrant:SE",2],["quadrant:SW",3]]);return[...e.values()].sort((i,a)=>(t.get(i.key)??4)-(t.get(a.key)??4))}export function buildReticlePositionRegions(n,o){if(!o)return[];const r=new Map;for(const e of n){const{column:t,row:i}=b(e,o),a=`reticle-position:cell:${t},${i}`,s=r.get(a)??{family:"reticle-position",key:a,label:`Reticle cell (${t}, ${i})`,dieKeys:[],dies:[]};s.dieKeys.push(p(e)),s.dies.push(e),r.set(a,s)}return[...r.values()].sort((e,t)=>e.key.localeCompare(t.key))}const w=3;export function buildTestSiteRegions(n,o=!1){const r=new Map;for(const e of n){if(e.siteNum===void 0)continue;const t=r.get(e.siteNum)??[];t.push(e),r.set(e.siteNum,t)}if(!o){let e=0;for(const t of r.values())t.length>=w&&e++;if(e<2)return[]}return[...r.entries()].sort(([e],[t])=>e-t).map(([e,t])=>({family:"test-site",key:`test-site:${e}`,label:`Site ${e}`,dieKeys:t.map(p),dies:t}))}export function buildSectorRegions(n,o,r){const e=[4,8,16,32].includes(r)?r:16,t=sectorCompassNames(e),i=new Map,a=o.center.x,s=o.center.y,c=o.radius;for(const u of n){const d=u.physX-a,l=u.physY-s;if(Math.hypot(d,l)/c<.2)continue;const f=Math.PI/e,y=(Math.atan2(l,d)+2*Math.PI+f)%(2*Math.PI),E=Math.floor(y/(2*Math.PI)*e)%e,h=t[E],S=`sector:${h}`,m=i.get(S)??{family:"sector",key:S,label:`Sector ${h}`,dieKeys:[],dies:[]};m.dieKeys.push(p(u)),m.dies.push(u),i.set(S,m)}return[...i.values()].sort((u,d)=>u.key.localeCompare(d.key))}
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
import type { StatsSummary, LotStatsSummary } from './types.js';
|
|
2
|
-
export declare function renderFindingsReportHtml(summary: StatsSummary | LotStatsSummary, options?: {
|
|
3
|
-
title?: string;
|
|
4
|
-
}): string;
|
|
5
1
|
export declare function openHtmlReport(html: string): void;
|
|
6
2
|
export declare function setReportOpener(opener: (html: string) => void): void;
|
|
7
3
|
//# sourceMappingURL=renderFindingsReport.d.ts.map
|
|
@@ -1,21 +1 @@
|
|
|
1
|
-
|
|
2
|
-
`:"",u=[f("Summary",F(e,r)),f("Findings",c+g(a,n))].join(`
|
|
3
|
-
`);return`<!DOCTYPE html>
|
|
4
|
-
<html lang="en">
|
|
5
|
-
<head>
|
|
6
|
-
<meta charset="UTF-8">
|
|
7
|
-
<title>${l(i)}</title>
|
|
8
|
-
<style>
|
|
9
|
-
${h()}
|
|
10
|
-
</style>
|
|
11
|
-
</head>
|
|
12
|
-
<body>
|
|
13
|
-
<main class="report">
|
|
14
|
-
<header class="report-header">
|
|
15
|
-
<h1>${l(i)}</h1>
|
|
16
|
-
<p class="report-subtitle">Generated ${l(r)}</p>
|
|
17
|
-
</header>
|
|
18
|
-
${u}
|
|
19
|
-
</main>
|
|
20
|
-
</body>
|
|
21
|
-
</html>`}export function openHtmlReport(e){if(typeof window.__openHtmlReport=="function"){window.__openHtmlReport(e);return}const o=URL.createObjectURL(new Blob([e],{type:"text/html"})),t=window.open(o,"_blank");setTimeout(()=>URL.revokeObjectURL(o),3e4),t||console.warn("wmap: window.open() returned null, so the report could not be shown. If this host cannot use window.open (e.g. Tauri, or popups are blocked), call setReportOpener() to supply your own opener.")}export function setReportOpener(e){window.__openHtmlReport=e}
|
|
1
|
+
export function openHtmlReport(e){if(typeof window.__openHtmlReport=="function"){window.__openHtmlReport(e);return}const o=URL.createObjectURL(new Blob([e],{type:"text/html"})),n=window.open(o,"_blank");setTimeout(()=>URL.revokeObjectURL(o),3e4),n||console.warn("wmap: window.open() returned null, so the report could not be shown. If this host cannot use window.open (e.g. Tauri, or popups are blocked), call setReportOpener() to supply your own opener.")}export function setReportOpener(e){window.__openHtmlReport=e}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import{waferDisplayLabel as H}from"../core/waferLabel.js";import{binPassSets as
|
|
2
|
-
`)),
|
|
1
|
+
import{waferDisplayLabel as H}from"../core/waferLabel.js";import{binPassSets as $e,binPassSetsByWafer as xe,mergeBinDefs as I}from"../renderer/binColors.js";import{itemPassBins as R,passBinsLabel as De}from"../core/passBins.js";import{isParametricTest as Be,isBuiltMap as j}from"../renderer/buildWaferMap.js";import{buildRingRegions as z,buildQuadrantRegions as A,buildRegionYieldData as ve}from"./regions.js";import{openHtmlReport as Te}from"./renderFindingsReport.js";import{analyzeWaferLot as Pe}from"./analyzeWaferLot.js";import{computeFunctionalYield as Fe,analyzeWaferMap as Ye}from"./analyzeWaferMap.js";import{mergeTestDefs as We}from"./mergeTestDefs.js";import{sortBinsForDisplay as O}from"./binPareto.js";import{buildFacetTable as Me,facetValueOf as Ce,FACET_NONE_VALUE as Re}from"./facets.js";import{visibleFindings as G}from"./filterFindings.js";import{buildYieldDataCombined as Ne}from"./yield.js";import{describeWaferPopulation as U,populationLabel as Ee}from"./population.js";import{buildTestPassRateData as Le,hasJudgeableTests as ke,poolFunctionalYield as He}from"./testPassRate.js";import{buildCapabilityData as Ie}from"./capability.js";import{fmt as T}from"../renderer/fmt.js";import{getDieKey as je,isPositionedDie as ze,diePassStatus as V}from"../core/dies.js";import{testValue as Ae}from"../core/dieTable.js";import{findingsTableHtml as _,renderMetadataSection as K,renderMetricGrid as Q,renderSection as $,renderTable as v,reportStyles as N}from"./reportHtml.js";import{escHtml as y}from"../core/utils.js";function C(t,o){return o===0?"\u2014":`${(t/o*100).toFixed(1)}%`}function Oe(t){if(!t)return"";const o=t.lot??t.lotId,a=t.wafer??t.waferId,n=[o,a].filter(Boolean).map(String);return n.length?` \u2014 ${n.map(y).join(" \xB7 ")}`:""}function q(t,o,a,n,r=[1]){const e=new Map;if(n)for(const[s,c]of Object.entries(n))e.set(Number(s),c);else for(const s of t){if(s.partial||s.edgeExcluded)continue;const c=a==="hard"?s.hbin:s.sbin;c!=null&&e.set(c,(e.get(c)??0)+1)}if(!e.size)return"";const i=[...e.values()].reduce((s,c)=>s+c,0),l=o?new Map(o.map(s=>[s.bin,s])):null,u=O(e.entries(),$e(t,r)[a]).map(([s,c])=>{const b=l?.get(s)?.name??"\u2014";return[String(s),b,String(c),C(c,i)]});return $(a==="hard"?"Hard Bin Breakdown":"Soft Bin Breakdown",v(["Bin","Name","Count","%"],u,{className:"compact"}))}function J(t,o,a,n){const r=new Set(n),e=new Map(a.map(u=>[je(u),u]));if(!a.some(u=>u.hbin!=null||u.sbin!=null)||!o.length)return"";const l=[];for(const u of o){let f=0,s=0;for(const c of u.dieKeys){const g=e.get(c);if(!g||g.partial||g.edgeExcluded)continue;const b=V(g,r);b!==void 0&&(s++,b&&f++)}s&&l.push([u.label,String(f),String(s),C(f,s)])}return l.length?$(t,v(["Region","Pass","Total","Yield"],l,{className:"compact"})):""}function X(t,o,a){if(!o.length)return"";const n=t.filter(i=>!i.partial&&!i.edgeExcluded),r=new Map((a??[]).map(i=>[i.testNumber,i])),e=[];for(const i of o.filter(Be)){const l=i.testNumber,u=i.unit||void 0,f=r.get(l);let s,c,g;if(f)({min:s,max:c,mean:g}=f);else{let b=0,B=0;s=1/0,c=-1/0;for(const h of n){const d=Ae(h,l);d===void 0||!isFinite(d)||(b++,B+=d,d<s&&(s=d),d>c&&(c=d))}if(b===0)continue;g=B/b}e.push([y(i.name),T(s,u),T(g,u),T(c,u)])}return e.length?$("Test Values",v(["Test","Min","Mean","Max"],e,{className:"compact"})):""}function Z(t,o,a){const n=a??Fe(t,o);if(!n?.length)return"";const r=n.map(e=>[y(e.label),String(e.totalDies),String(e.passDies),String(e.failDies),e.passRatePercent!==null?`${e.passRatePercent.toFixed(1)}%`:"\u2014"]);return $("Functional Tests",v(["Test","N","Pass","Fail","Pass Rate"],r,{className:"compact"}))}function ee(t,o){const a=Ie(t,o);if(!a.length)return"";const n=e=>e===null?"\u2014":e.toFixed(2),r=a.map(e=>[y(e.label),e.hasSpec?e.limitBasis==="spec"?`LSL ${T(e.lsl,e.unit)} \u2013 USL ${T(e.usl,e.unit)}`:`Lo limit ${T(e.lsl,e.unit)} \u2013 Hi limit ${T(e.usl,e.unit)}`:"\u2014",T(e.mean,e.unit),n(e.cp),n(e.cpk),n(e.pp),n(e.ppk)]);return $("Process Capability",v(["Test","Limits","Mean","Cp","Cpk","Pp","Ppk"],r,{className:"compact"}))}function te(t,o){const a=G(t);return a.length?$("Findings",_(a,o)):""}export function renderSummaryReportHtml(t,o={}){const{wafer:a,dies:n,yieldSummary:r,dataCoverage:e,hbinDefs:i,sbinDefs:l,testDefs:u=[],statsSummary:f,passBins:s=[1],ringCount:c=4}=t,g=a.metadata,b=Oe(g),B=o.title??`Wafer Summary${b}`,h=new Date().toLocaleString(),d=n.some(W=>W.hbin!=null),p=n.some(W=>W.sbin!=null),S=n.filter(ze),x=z(S,a,c),w=A(S,a,c),D=[{label:"Total dies",value:String(r.totalDies)},{label:"Filled dies",value:String(e.filledDies),hint:`${C(e.filledDies,e.totalDies)} fill`},{label:"Pass dies",value:String(r.passDies)},{label:"Fail dies",value:String(r.failDies)},...r.partialDies>0?[{label:"Partial",value:String(r.partialDies)}]:[],...r.yieldPercent!==null?[{label:`Yield (pass: ${De([s])})`,value:`${r.yieldPercent.toFixed(1)}%`}]:[],...r.edgeExcludedDies>0?[{label:"Edge excluded (outer zone)",value:String(r.edgeExcludedDies)}]:[]],Y=[K([{metadata:a.metadata}]),$("Summary",[Q(D)].filter(Boolean).join(`
|
|
2
|
+
`)),d?q(n,i,"hard",f?.stats.hardBinCounts,s):p?q(n,l,"soft",f?.stats.softBinCounts,s):"",J("Ring Yield",x,n,s),J("Quadrant Yield",w,n,s),X(n,u,f?.stats.perTestStats),Z(n,u,f?.stats.functionalYield),ee([{dies:n}],u),f?te(f.findings):""].filter(Boolean).join(`
|
|
3
3
|
`);return`<!DOCTYPE html>
|
|
4
4
|
<html lang="en">
|
|
5
5
|
<head>
|
|
6
6
|
<meta charset="UTF-8">
|
|
7
|
-
<title>${y(
|
|
7
|
+
<title>${y(B)}</title>
|
|
8
8
|
<style>
|
|
9
9
|
${N()}
|
|
10
10
|
</style>
|
|
@@ -12,25 +12,25 @@ ${N()}
|
|
|
12
12
|
<body>
|
|
13
13
|
<main class="report">
|
|
14
14
|
<header class="report-header">
|
|
15
|
-
<h1>${y(
|
|
16
|
-
<p class="report-subtitle">Generated ${y(
|
|
15
|
+
<h1>${y(B)}</h1>
|
|
16
|
+
<p class="report-subtitle">Generated ${y(h)}</p>
|
|
17
17
|
</header>
|
|
18
18
|
${Y}
|
|
19
|
-
<p class="footer">Generated ${y(
|
|
19
|
+
<p class="footer">Generated ${y(h)}</p>
|
|
20
20
|
</main>
|
|
21
21
|
</body>
|
|
22
|
-
</html>`}function
|
|
23
|
-
`))}const
|
|
24
|
-
`)}`)}if(!i.length)return"";const l=r.length>e.length?`<p class="report-note">${r.length-e.length} further splittable field(s) not shown.</p>`:"";return
|
|
25
|
-
`)+l)}function
|
|
26
|
-
`);return{lotSummary:l,sections:
|
|
22
|
+
</html>`}function Ge(t,o){const a=t.perWafer.map(n=>{const r=H(o[n.waferIndex],n.waferIndex),e=n.summary.stats.yieldPercent;return[r,e!==null?`${e.toFixed(1)}%`:"N/A"]});return v(["Wafer","Yield"],a,{className:"compact"})}function Ue(t){const o=[];for(const e of t.perWafer){const i=G(e.summary.findings);if(!i.length)continue;const l=e.summary.wafer?.waferId??`Wafer ${e.waferIndex+1}`;o.push(`<h3 class="report-subheading">${y(l)}</h3>`+_(i))}if(!o.length)return"";const a=o.length,n=t.perWafer.length,r=`<p class="report-note">${a} of ${n} wafer${n===1?"":"s"} have per-wafer findings; the rest had none.</p>`;return $("Findings by Wafer",r+o.join(`
|
|
23
|
+
`))}const Ve=3;function _e(t,o,a){const n=t.map(u=>({metadata:u.wafer?.metadata})),r=Me(n,{facetableOnly:!0}).filter(u=>u.splittable);if(!r.length)return"";const e=r.slice(0,Ve),i=[];for(const u of e){const f=new Map;t.forEach((d,p)=>{const S=Ce(d.wafer?.metadata,u.key)??Re;(f.get(S)??f.set(S,[]).get(S)).push(p)});const s=[...f.entries()].map(([d,p])=>({key:d,items:p.map(S=>t[S])}));if(s.length<2)continue;const c=s.map(d=>[y(d.key),y(d.items.map(p=>p.label).join(", "))]),g=Ne(s.map(d=>({key:d.key,items:d.items.map(p=>({label:p.label,dies:p.dies,passBins:R(p,a)}))})),a).map(d=>[y(d.label),String(d.itemCount),`${d.percent.toFixed(1)}%`]),b=[v([u.label,"Wafers"],c,{className:"compact"}),v([u.label,"Wafers","Yield"],g,{className:"compact"})],B=t.flatMap(d=>d.dies??[]);let h=0;for(const d of["spec","testFlag","functional"]){if(!ke(o,d,B))continue;const p=Le(s.map(w=>({key:w.key,items:w.items.map(D=>({dies:D.dies,testSpecYield:D.statsSummary?.stats.testSpecYield,functionalYield:D.statsSummary?.stats.functionalYield}))})),o,d);if(!p.rows.length)continue;const S=p.rows.map(w=>[y(w.label),...w.byGroup.map(D=>D.passRatePercent===null?"\u2014":`${D.passRatePercent.toFixed(1)}%`),w.overall.passRatePercent===null?"\u2014":`${w.overall.passRatePercent.toFixed(1)}%`]),x=d==="spec"?"Pass rate \xB7 test limits":d==="testFlag"?"Pass rate \xB7 tester flag":"Pass rate \xB7 functional";p.disagreementDies&&(h=p.disagreementDies),b.push(`<h4 class="report-subheading">${y(x)}</h4>`+v(["Test",...p.groups,"All"],S,{className:"compact"}))}h&&b.push(`<p class="report-note">${h.toLocaleString()} dies judged differently by limits and tester flag.</p>`),i.push(`<h3 class="report-subheading">${y(u.label)}</h3>${b.join(`
|
|
24
|
+
`)}`)}if(!i.length)return"";const l=r.length>e.length?`<p class="report-note">${r.length-e.length} further splittable field(s) not shown.</p>`:"";return $("Split Comparison",i.join(`
|
|
25
|
+
`)+l)}function ne(t,o,a,n,r){const e=new Map,i=a==="hard"?"hardBinCounts":"softBinCounts";if(n?.length&&n.every(s=>s.stats[i]!==void 0))for(const s of n)for(const[c,g]of Object.entries(s.stats[i])){const b=Number(c);e.set(b,(e.get(b)??0)+g)}else for(const s of t){if(s.partial||s.edgeExcluded)continue;const c=a==="hard"?s.hbin:s.sbin;c!=null&&e.set(c,(e.get(c)??0)+1)}if(!e.size)return"";const l=[...e.values()].reduce((s,c)=>s+c,0),u=o?new Map(o.map(s=>[s.bin,s])):null,f=O(e.entries(),xe(r)[a]).map(([s,c])=>{const g=u?.get(s);return[g?.name?`Bin ${s} \xB7 ${g.name} (${c})`:`Bin ${s} (${c})`,C(c,l)]});return $(a==="hard"?"Hard Bin Breakdown (All Wafers)":"Soft Bin Breakdown (All Wafers)",v(["Bin","Yield"],f,{className:"compact"}))}function se(t,o,a,n,r,e){const i=ve(a,n,r,l=>e[l],o).map(l=>[l.label,`${l.yieldPercent.toFixed(1)}%`]);return i.length?$(t,v(["Region","Yield"],i,{className:"compact"})):""}function Ke(t,o,a){let n;if(a?.length&&a.every(r=>r.stats.perTestStats!==void 0)){const r=new Map;for(const e of a)for(const i of e.stats.perTestStats??[]){const l=r.get(i.testNumber);l?(l.n+=i.count,l.sum+=i.mean*i.count,l.min=Math.min(l.min,i.min),l.max=Math.max(l.max,i.max)):r.set(i.testNumber,{n:i.count,sum:i.mean*i.count,min:i.min,max:i.max})}n=[...r.entries()].map(([e,i])=>({testNumber:e,min:i.min,max:i.max,mean:i.sum/i.n}))}return X(t,o,n)}function Qe(t,o,a){const n=He(a);return Z(t,o,n)}const E=["lot","product","testProgram","temperature"];function qe(t){const o=new Map(E.map(e=>[e,new Set]));for(const e of t)for(const i of E){const l=e.wafer?.metadata?.[i];l!=null&&l!==""&&o.get(i).add(String(l))}const a=E.filter(e=>o.get(e).size>1);if(a.length===0)return[{label:"",items:t,varying:a}];const n=new Map,r=[];for(const e of t){const i=a.map(l=>String(e.wafer?.metadata?.[l]??"(none)")).join(" \xB7 ");n.has(i)||(n.set(i,[]),r.push(i)),n.get(i).push(e)}return r.map(e=>({label:e,items:n.get(e),varying:a}))}function ae(t,o,a,n,r,e,i){const l=Pe(t.map(m=>m.source??m),{perWaferSummaries:t.map(m=>m.statsSummary),...i}),u=[],f=[],s=[],c=[];for(const m of t){const F=m.dies??[];m.wafer&&(u.push(m.wafer),s.push(F),c.push(R(m,r)));for(const P of F)f.push(P)}const g=t.map(m=>({dies:m.dies??[],passBins:R(m,r)})),b=f.some(m=>m.hbin!=null),B=f.some(m=>m.sbin!=null),h=b||B,d=l.perWafer.map(m=>m.summary.stats.yieldPercent).filter(m=>m!==null),p=d.length?d.reduce((m,F)=>m+F,0)/d.length:null;let S=0,x=0,w=0,D=0,Y=0;for(const m of g){const F=new Set(m.passBins);for(const P of m.dies){if(S++,P.edgeExcluded&&D++,P.partial&&Y++,P.partial||P.edgeExcluded)continue;const k=V(P,F);k!==void 0&&(x++,k&&w++)}}const W=x-w,L=x>0?w/x*100:null,oe=[{label:"Wafers",value:String(l.stats.waferCount)},{label:"Total dies",value:String(S)},...h?[{label:"Good dies",value:String(w)},{label:"Bad dies",value:String(W)}]:[],...D>0?[{label:"Edge excluded",value:String(D)}]:[],...Y>0?[{label:"Partial dies",value:String(Y)}]:[],...p!==null?[{label:"Mean wafer yield",value:`${p.toFixed(1)}%`}]:[],...L!==null?[{label:"Total yield",value:`${L.toFixed(1)}%`}]:[]],re=U(l.perWafer.map(m=>m.summary.wafer)),le=$(re.lotId!==void 0?"Lot Summary":"Summary",Q(oe)),ce=K(t.map(m=>({metadata:m.wafer?.metadata}))),de=$("Per-Wafer Yield",Ge(l,t)),ue=_e(t,n,r),M=l.perWafer.map(m=>m.summary),fe=h?b?ne(f,o,"hard",M,g):ne(f,a,"soft",M,g):"",me=h&&u.length?se("Ring Yield (All Wafers)",z,s,u,e,c):"",pe=h&&u.length?se("Quadrant Yield (All Wafers)",A,s,u,e,c):"",ge=n.length?Ke(f,n,M):"",be=n.length?Qe(f,n,M):"",he=n.length?ee(t,n):"",ye=l.findings.length?te(l.findings,l.stats.waferCount):"",Se=Ue(l),we=[le,ce,de,ue,fe,me,pe,ge,be,he,ye,Se].filter(Boolean).join(`
|
|
26
|
+
`);return{lotSummary:l,sections:we}}function ie(t,o,a){return`<main class="report">
|
|
27
27
|
<header class="report-header">
|
|
28
28
|
<h1>${y(t)}</h1>
|
|
29
29
|
<p class="report-subtitle">Generated ${y(a)}</p>
|
|
30
30
|
</header>
|
|
31
31
|
${o}
|
|
32
32
|
<p class="footer">Generated ${y(a)}</p>
|
|
33
|
-
</main>`}export function renderLotSummaryReportHtml(t,o={}){const{items:a,hbinDefs:n,sbinDefs:r,testDefs:e=[],passBins:i=[1],ringCount:l=4,analyzeOptions:
|
|
33
|
+
</main>`}export function renderLotSummaryReportHtml(t,o={}){const{items:a,hbinDefs:n,sbinDefs:r,testDefs:e=[],passBins:i=[1],ringCount:l=4,analyzeOptions:u}=t,f=new Date().toLocaleString(),s=qe(a);if(s.length===1){const{lotSummary:h,sections:d}=ae(s[0].items,n,r,e,i,l,u),p=U(h.perWafer.map(x=>x.summary.wafer)),S=o.title??(p.lotId!==void 0?`Lot Summary \u2014 ${p.lotId}`:`Summary \u2014 ${Ee(p)}`);return`<!DOCTYPE html>
|
|
34
34
|
<html lang="en">
|
|
35
35
|
<head>
|
|
36
36
|
<meta charset="UTF-8">
|
|
@@ -40,10 +40,10 @@ ${N()}
|
|
|
40
40
|
</style>
|
|
41
41
|
</head>
|
|
42
42
|
<body>
|
|
43
|
-
${
|
|
43
|
+
${ie(S,d,f)}
|
|
44
44
|
</body>
|
|
45
|
-
</html>`}const c=o.title??"Summary",
|
|
46
|
-
${
|
|
45
|
+
</html>`}const c=o.title??"Summary",g=s.map(h=>{const{lotSummary:d,sections:p}=ae(h.items,n,r,e,i,l,u),S=h.varying.includes("lot")?void 0:d.lot?.lot??d.lot?.lotId,x=S?`${h.label} \xB7 Lot ${String(S)}`:h.label;return ie(`${c} \u2014 ${x}`,p,f)}),b=`<p style="max-width:1080px;margin:0 auto 8px;padding:8px 12px;background:var(--report-surface);border:1px solid var(--report-line);border-radius:4px;font-size:12px;color:var(--report-muted);">This load spans ${s.length} groups by identity \u2014 shown separately below so stats are never pooled across them.</p>`,B=g.map((h,d)=>`${d===0?"":'<hr style="max-width:1080px;margin:32px auto 0;border:none;border-top:3px solid var(--report-line-strong);">'}
|
|
46
|
+
${h}`).join(`
|
|
47
47
|
`);return`<!DOCTYPE html>
|
|
48
48
|
<html lang="en">
|
|
49
49
|
<head>
|
|
@@ -55,8 +55,8 @@ ${N()}
|
|
|
55
55
|
</head>
|
|
56
56
|
<body>
|
|
57
57
|
<div class="report" style="padding-top:24px;">
|
|
58
|
-
${
|
|
58
|
+
${b}
|
|
59
59
|
</div>
|
|
60
|
-
${
|
|
60
|
+
${B}
|
|
61
61
|
</body>
|
|
62
|
-
</html>`}export{
|
|
62
|
+
</html>`}export{Te as openHtmlReport};export function renderWaferReportHtml(t,o,a={}){return renderSummaryReportHtml({wafer:t.wafer,dies:t.dies,yieldSummary:t.yield,dataCoverage:t.dataCoverage,hbinDefs:t.hbinDefs,sbinDefs:t.sbinDefs,testDefs:t.testDefs,statsSummary:o??t.statsSummary??(j(t)?Ye(t):void 0),passBins:[...t.passBins],ringCount:t.ringCount},a)}export function renderLotReportHtml(t,o={}){const a=I(t.map(e=>e.hbinDefs)),n=I(t.map(e=>e.sbinDefs)),r=t.some(e=>e.testDefs?.length)?We([...t]).defs:void 0;return renderLotSummaryReportHtml({items:t.map((e,i)=>({label:H(e,i),wafer:e.wafer,dies:e.dies,passBins:[...e.passBins],statsSummary:e.statsSummary,source:j(e)?e:void 0})),hbinDefs:a.length?a:void 0,sbinDefs:n.length?n:void 0,testDefs:r,ringCount:t[0]?.ringCount,analyzeOptions:o.analyzeOptions},{title:o.title})}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
function
|
|
1
|
+
import{testValue as u}from"../core/dieTable.js";function c(e,o,s,t,n){for(const i of e??[]){const r=u(i,o),f=u(i,s);r!==void 0&&f!==void 0&&Number.isFinite(r)&&Number.isFinite(f)&&n.push(t===void 0?{x:r,y:f,hbin:i.hbin}:{x:r,y:f,hbin:i.hbin,group:t})}}export function buildScatterData(e,o,s){const t=[];for(const n of e)c(n.dies,o,s,void 0,t);return t}export function buildScatterDataGrouped(e,o,s){const t=[];for(const n of e)for(const i of n.items)c(i.dies,o,s,n.key,t);return t}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{isYieldEligibleDie as G}from"../core/dies.js";import{isParametricTest as M}from"../renderer/buildWaferMap.js";import{markedTestLabel as
|
|
1
|
+
import{isYieldEligibleDie as G}from"../core/dies.js";import{testValue as H}from"../core/dieTable.js";import{isParametricTest as M}from"../renderer/buildWaferMap.js";import{markedTestLabel as J,derivedFields as K}from"../renderer/testLabel.js";import{parseTestReference as Q}from"../renderer/derivedTests/parser.js";import{quantile as V}from"./math.js";import{compileXNamePattern as W,readXFromName as Z}from"./sweepXFromName.js";function j(o,a,s){const e=[];let t=!1;for(const r of o.tests){if(typeof r=="number"){Number.isInteger(r)?e.push(r):s.push(`"${o.label}": ${r} is not a test number.`);continue}const i=Q(String(r),a);if(!i.ok){s.push(`"${o.label}": "${r}" \u2014 ${i.message}.`);continue}i.range&&(t=!0),e.push(...i.tests)}return{spec:o,tests:e,usedRange:t}}export function sweepAppliesTo(o,a){const s=new Map((a??[]).map(e=>[e.testNumber,e]));return o.series.some(e=>j(e,s,[]).tests.some(t=>{const r=s.get(t);return r!==void 0&&M(r)}))}function B(o,a=16){return o.length<=a?o.join(", "):`${o.slice(0,a).join(", ")}, \u2026 (${o.length-a} more)`}function _(o,a){const s=o?.[a];return s!==void 0&&Number.isFinite(s)?s:a}function ee(o,a,s){const e=o.spec;if(e.xValues!==void 0&&e.xFromName!==void 0)return s.push(`"${e.label}": gives both xValues and xFromName \u2014 use one.`),{asked:!0};if(e.xValues!==void 0){const t=e.xValues.length;return t===o.tests.length?{xs:e.xValues,asked:!0}:(s.push(o.usedRange?`"${e.label}": ${t} x values for ${o.tests.length} tests \u2014 the ranges matched ${B(o.tests)}.`:`"${e.label}": ${t} x values for ${o.tests.length} tests.`),{asked:!0})}if(e.xFromName!==void 0){const t=W(e.xFromName);if("error"in t)return s.push(`"${e.label}": xFromName "${e.xFromName}" \u2014 ${t.error}.`),{asked:!0};const r=[],i=[],u=[];for(const l of o.tests){const c=a.get(l)?.name,f=c===void 0?null:Z(t,c);typeof f=="number"?r.push(f):(i.push(l),f!==null&&u.push(f.ambiguous))}return i.length>0?(s.push(`"${e.label}": xFromName "${e.xFromName}" found no x value in the name of ${i.length===1?"test":"tests"} ${B(i)}`+(u.length>0?` \u2014 "${u[0]}" is written in capitals, where M could be milli or mega; put the letter in the pattern ("\u2026{x}M\u2026") and the unit in xLabel.`:".")),{asked:!0}):{xs:r,asked:!0}}return{asked:!1}}function ne(o,a,s){const e=new Map;for(let t=0;t<s.length;t++){const r=e.get(s[t]);if(r!==void 0)return`"${o}": tests ${a[r]} and ${a[t]} have the same x value (${s[t]}) \u2014 a curve cannot be measured where it revisits an x.`;e.set(s[t],t)}return null}function D(o,a){const s=o.filter(e=>e.count>0);for(let e=1;e<s.length;e++){const t=s[e-1],r=s[e],i=Math.min(t.median,r.median),u=Math.max(t.median,r.median);if(a<i||a>u)continue;if(t.median===r.median)return t.x;const l=(a-t.median)/(r.median-t.median);return t.x+l*(r.x-t.x)}return null}function te(o,a){const s=new Map(a.filter(i=>i.count>0).map(i=>[i.x,i])),e=[];for(const i of o){if(i.count===0)continue;const u=s.get(i.x);u!==void 0&&e.push({x:i.x,d:i.median-u.median,y:i.median})}if(e.length<2)return null;const t=[];for(let i=0;i<e.length;i++){const u=e[i];if(u.d===0){t.push({x:u.x,y:u.y});continue}if(i===0)continue;const l=e[i-1];if(l.d===0||l.d<0==u.d<0)continue;const c=l.d/(l.d-u.d);t.push({x:l.x+c*(u.x-l.x),y:l.y+c*(u.y-l.y)})}const r=t[0];return r===void 0?null:{x:r.x,y:r.y,multiple:t.length>1}}export function buildSweepData(o,a,s){const e=[],t=new Map((a??[]).map(n=>[n.testNumber,n])),r=o.filter(n=>G(n)),i=s.series.map(n=>j(n,t,e)),u=i.map(n=>ee(n,t,e)),l=i.length>0&&u.every(n=>n.xs!==void 0),c=!l&&u.some(n=>n.asked);c&&u.some(n=>!n.asked)&&e.push("x values are given for some series but not all.");const f=l?i.map((n,d)=>ne(n.spec.label,n.tests,u[d].xs)).find(n=>n!==null)??null:null;f!==null&&e.push(f);const S=s.xScale==="log",T=l&&u.every(n=>n.xs.every(d=>d>0)),b=S&&T?"log":"linear";S&&!T&&e.push(l?"A log x axis needs every x value to be positive \u2014 drawn on a linear axis.":"A log x axis needs x values (xValues or xFromName) \u2014 drawn in test order.");const L=i.map(({spec:n,tests:d},p)=>{const v=l?u[p].xs:void 0,N=d.map((x,z)=>{const h=t.get(x);h===void 0?e.push(`"${n.label}": test ${x} is not in this program's test definitions.`):M(h)||e.push(`"${n.label}": test ${x} is functional and has no measured value.`);const m=[];if(h!==void 0&&M(h))for(const F of r){const g=H(F,x);g!==void 0&&Number.isFinite(g)&&m.push(g)}return m.sort((F,g)=>F-g),{testNumber:x,label:J(h,x),...K(h),x:_(v,z),median:m.length?V(m,.5):NaN,p10:m.length?V(m,.1):NaN,p90:m.length?V(m,.9):NaN,count:m.length}});return{label:n.label,color:n.color,points:N}}),[X,C]=L,$=X!==void 0&&C!==void 0&&!c&&f===null,I=s.crossing??!0,Y=n=>b==="log"?Math.log10(n):n,k=n=>b==="log"?10**n:n,P=n=>n.map(d=>({...d,x:Y(d.x)})),q=$?P(X.points):[],R=$?P(C.points):[],E=(c||f!==null?[]:s.separationAt??[]).map(n=>{if(!$)return{y:n,distance:null};const d=D(q,n),p=D(R,n);if(d===null)return{y:n,distance:null,reason:"first-series-never-reaches"};if(p===null)return{y:n,distance:null,reason:"second-series-never-reaches"};const v=k(d),N=k(p);return{y:n,distance:Math.abs(N-v),from:v,to:N,...b==="log"?{ratio:10**Math.abs(d-p)}:{}}}),w=$&&I?te(q,R):null,O=w&&{...w,x:k(w.x)},U=n=>n===void 0?void 0:t.get(n.tests[0]??-1)?.unit,y=U(i[0]),A=U(i[1]);return y!==void 0&&A!==void 0&&y!==A&&e.push(`Series measure different units (${y} and ${A}) \u2014 crossing and separation compare unlike quantities.`),{title:s.title,xLabel:c?"Test":s.xLabel??(l?"Sweep value":"Test"),yLabel:s.yLabel??"Value",series:L,xIsPhysical:l,...l&&s.xUnit?{xUnit:s.xUnit}:{},xScale:b,crossing:O,separations:E,dieCount:r.length,warnings:e,...c&&s.series.length>=2?{notMeasured:"x values do not match the tests, so the sweep is drawn in test order and the crossing and widths are not measured"}:f!==null&&s.series.length>=2?{notMeasured:"a series revisits an x value, so its points cannot be paired with the other series"}:{}}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{isYieldEligibleDie as
|
|
1
|
+
import{isYieldEligibleDie as x}from"../core/dies.js";import{dieHasVerdicts as M,testValuesReader as S}from"../core/dieTable.js";import{isParametricTest as R,getTestPassStatus as E,testPassStatusReader as z}from"../renderer/buildWaferMap.js";import{classifySpec as C}from"../renderer/spec.js";import{testLabel as I,derivedFields as J}from"../renderer/testLabel.js";const K=(s,o)=>o===0?null:s/o*100;export function buildTestPassRateData(s,o,n){const r=s.map(e=>e.key),t={groups:r,rows:[],disagreementDies:null};if(!o?.length)return t;const i=o.filter(e=>e.testNumber!==void 0&&G(e,n));if(!i.length)return t;const v=()=>({pass:0,fail:0,failLow:0,failHigh:0}),g=new Map;for(const e of i){const l=e.testNumber;g.set(l,{label:I(e,l),overall:v(),perGroup:r.map(()=>v())})}const N=(e,l,u)=>{const f=g.get(e);if(f)for(const p of[f.overall,f.perGroup[l]])p.pass+=u.pass??0,p.fail+=u.fail??0,p.failLow+=u.failLow??0,p.failHigh+=u.failHigh??0},T=S(i.map(e=>e.testNumber)),L=new Float64Array(i.length),V=z(i),h=new Int8Array(i.length),H=n==="spec"||n==="testFlag";let y=0,P=0;s.forEach(function(l,u){for(const f of l.items){if(n==="spec"&&f.testSpecYield&&!O(f)){for(const a of f.testSpecYield)N(a.testNumber,u,{pass:a.passDies,fail:a.failLowDies+a.failHighDies,failLow:a.failLowDies,failHigh:a.failHighDies});continue}if(n==="functional"&&f.functionalYield){for(const a of f.functionalYield)N(a.testNumber,u,{pass:a.passDies,fail:a.failDies});continue}const p=i.map(a=>g.get(a.testNumber).overall),F=i.map(a=>g.get(a.testNumber).perGroup[u]);for(const a of f.dies??[])if(x(a)){T(a,L),V(a,h);for(let c=0;c<i.length;c++){const A=i[c],D=C(L[c],A),b=h[c]===-1?void 0:h[c]===1;H&&D!==null&&b!==void 0&&(y++,D==="pass"!==b&&P++);const m=p[c],d=F[c];if(n==="spec"){if(D===null)continue;if(D==="pass"){m.pass++,d.pass++;continue}m.fail++,d.fail++,D==="failLow"?(m.failLow++,d.failLow++):(m.failHigh++,d.failHigh++)}else{if(b===void 0)continue;b?(m.pass++,d.pass++):(m.fail++,d.fail++)}}}}});const Y=e=>({passDies:e.pass,failDies:e.fail,totalDies:e.pass+e.fail,passRatePercent:K(e.pass,e.pass+e.fail)}),w=[];for(const[e,l]of g)l.overall.pass+l.overall.fail!==0&&w.push({testNumber:e,label:l.label,overall:Y(l.overall),byGroup:l.perGroup.map(Y),...n==="spec"?{failLowDies:l.overall.failLow,failHighDies:l.overall.failHigh}:{}});return w.sort((e,l)=>(e.overall.passRatePercent??101)-(l.overall.passRatePercent??101)||e.testNumber-l.testNumber),{groups:r,rows:w,disagreementDies:H&&y>0?P:null}}function O(s){return(s.dies??[]).some(M)}function G(s,o){return o==="functional"?!R(s):R(s)?o==="spec"?s.limitLow!==void 0||s.limitHigh!==void 0:!0:!1}export function hasJudgeableTests(s,o,n){if(!s?.length)return!1;const r=s.filter(t=>t.testNumber!==void 0&&G(t,o));return r.length?o!=="testFlag"?!0:(n??[]).some(t=>r.some(i=>E(t,i.testNumber,i)!==void 0)):!1}export function poolFunctionalYield(s){if(!s?.length||!s.every(r=>r.stats.functionalYield!==void 0))return;const o=new Map;for(const r of s)for(const t of r.stats.functionalYield??[]){const i=o.get(t.testNumber)??{label:t.label,...J(t),passDies:0,failDies:0,totalDies:0};i.passDies+=t.passDies,i.failDies+=t.failDies,i.totalDies+=t.totalDies,o.set(t.testNumber,i)}const n=[...o.entries()].map(([r,t])=>({testNumber:r,...t,passRatePercent:t.totalDies>0?t.passDies/t.totalDies*100:null}));return n.length?n:void 0}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{isYieldEligibleDie as f}from"../core/dies.js";export function buildTestTrendData(
|
|
1
|
+
import{isYieldEligibleDie as f}from"../core/dies.js";import{testValue as m}from"../core/dieTable.js";export function buildTestTrendData(i,o){return i.map((t,n)=>{const s=t.label??`#${n}`,c=t.testStats?.find(e=>e.testNumber===o);if(c){const{mean:e,stddev:u,count:d}=c;return{label:s,mean:e,stddev:u,count:d,key:t.key}}const r=(t.dies??[]).filter(e=>f(e)).map(e=>m(e,o)).filter(e=>e!==void 0&&Number.isFinite(e));if(r.length===0)return{label:s,mean:NaN,stddev:0,count:0,key:t.key};const l=r.reduce((e,u)=>e+u,0)/r.length,a=r.length>1?r.reduce((e,u)=>e+(u-l)**2,0)/(r.length-1):0;return{label:s,mean:l,stddev:Math.sqrt(a),count:r.length,key:t.key}})}export function trendCentre(i){let o=0,t=0;for(const n of i)!n.count||!Number.isFinite(n.mean)||(o+=n.count,t+=n.mean*n.count);return o===0?null:t/o}
|
|
@@ -30,6 +30,11 @@ export interface HighlightBinTarget {
|
|
|
30
30
|
export interface HighlightWaferTarget {
|
|
31
31
|
kind: 'wafer';
|
|
32
32
|
waferIndices: number[];
|
|
33
|
+
/**
|
|
34
|
+
* For a lot finding about a region, that region's dies on each counted wafer,
|
|
35
|
+
* keyed by wafer index — what to highlight on each wafer's map.
|
|
36
|
+
*/
|
|
37
|
+
dieKeysByWafer?: Record<number, string[]>;
|
|
33
38
|
}
|
|
34
39
|
export interface HighlightDieTarget {
|
|
35
40
|
kind: 'dies';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export function createWafermapWorker(
|
|
1
|
+
import{attachTables as m}from"../core/dieTable.js";export function createWafermapWorker(a){let i=0;const s=new Map;return a.onmessage=t=>{const e=t.data;if(e.type==="pong")return;const r=s.get(e.id);r&&(s.delete(e.id),e.type==="error"?r.reject(new Error(e.message)):e.type==="result"?(m([e.result.dies],e.tables),r.resolve(e.result)):e.type==="analyzed"?r.resolve({waferSummaries:e.waferSummaries,lotSummary:e.lotSummary}):e.type==="resultWithAnalysis"&&(m(e.results.map(n=>n.dies),e.tables),r.resolve({results:e.results,waferSummaries:e.waferSummaries,lotSummary:e.lotSummary})))},a.onerror=t=>{const e=new Error(t.message??"Worker error");for(const r of s.values())r.reject(e);s.clear()},{run(t){return new Promise((e,r)=>{const n=i++;s.set(n,{resolve:e,reject:r}),a.postMessage({type:"run",id:n,input:t})})},runAnalysis(t,e,r){return new Promise((n,l)=>{const o=i++;s.set(o,{resolve:n,reject:l}),a.postMessage({type:"analyze",id:o,results:t,options:e,hasMultiWafer:r})})},runWithAnalysis(t,e,r){return new Promise((n,l)=>{const o=i++;s.set(o,{resolve:n,reject:l}),a.postMessage({type:"runWithAnalysis",id:o,inputs:t,options:e,hasMultiWafer:r})})},terminate(){a.terminate();const t=new Error("Worker terminated");for(const e of s.values())e.reject(t);s.clear()}}}
|