@wafertools/wafermap 0.26.1 → 0.28.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 +10 -2
- package/CHANGELOG.md +670 -0
- package/README.md +46 -3
- package/dist/packages/canvas-adapter/canvasTheme.d.ts +4 -0
- package/dist/packages/canvas-adapter/canvasTheme.js +1 -1
- package/dist/packages/canvas-adapter/charts/barPanel.d.ts +16 -0
- package/dist/packages/canvas-adapter/charts/barPanel.js +1 -1
- package/dist/packages/canvas-adapter/charts/binCluster.d.ts +0 -1
- package/dist/packages/canvas-adapter/charts/binCluster.js +1 -1
- package/dist/packages/canvas-adapter/charts/boxplot.d.ts +21 -2
- package/dist/packages/canvas-adapter/charts/boxplot.js +1 -1
- package/dist/packages/canvas-adapter/charts/capability.d.ts +10 -11
- package/dist/packages/canvas-adapter/charts/capability.js +1 -1
- package/dist/packages/canvas-adapter/charts/chartShell.d.ts +337 -3
- package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
- package/dist/packages/canvas-adapter/charts/correlation.d.ts +8 -10
- package/dist/packages/canvas-adapter/charts/correlation.js +2 -1
- package/dist/packages/canvas-adapter/charts/groupedBarPlot.d.ts +50 -0
- package/dist/packages/canvas-adapter/charts/groupedBarPlot.js +1 -0
- package/dist/packages/canvas-adapter/charts/histogram.d.ts +12 -2
- package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
- package/dist/packages/canvas-adapter/charts/regionYieldDiagram.d.ts +0 -1
- package/dist/packages/canvas-adapter/charts/regionYieldDiagram.js +1 -1
- package/dist/packages/canvas-adapter/charts/scatter.d.ts +6 -1
- package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
- package/dist/packages/canvas-adapter/charts/testPassRate.d.ts +20 -0
- package/dist/packages/canvas-adapter/charts/testPassRate.js +1 -0
- package/dist/packages/canvas-adapter/charts/trend.d.ts +37 -0
- package/dist/packages/canvas-adapter/charts/trend.js +1 -0
- package/dist/packages/canvas-adapter/dieList.js +7 -7
- package/dist/packages/canvas-adapter/hitGrid.d.ts +22 -0
- package/dist/packages/canvas-adapter/hitGrid.js +1 -0
- package/dist/packages/canvas-adapter/identityHeader.d.ts +46 -0
- package/dist/packages/canvas-adapter/identityHeader.js +1 -0
- package/dist/packages/canvas-adapter/index.d.ts +2 -1
- package/dist/packages/canvas-adapter/index.js +1 -1
- package/dist/packages/canvas-adapter/insightsTab.d.ts +51 -5
- package/dist/packages/canvas-adapter/insightsTab.js +1 -1
- package/dist/packages/canvas-adapter/maplessSummary.d.ts +5 -1
- package/dist/packages/canvas-adapter/maplessSummary.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +15 -2
- package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -20
- package/dist/packages/canvas-adapter/renderWaferMap.d.ts +110 -18
- package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
- package/dist/packages/canvas-adapter/summaryPanel.d.ts +233 -37
- package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
- package/dist/packages/canvas-adapter/toCanvas.d.ts +6 -0
- package/dist/packages/canvas-adapter/toCanvas.js +1 -1
- package/dist/packages/canvas-adapter/toolbar.d.ts +352 -15
- 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 +115 -41
- 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.d.ts +21 -0
- package/dist/packages/canvas-adapter/warnings.js +1 -1
- package/dist/packages/core/dies.d.ts +11 -0
- package/dist/packages/core/dies.js +1 -1
- package/dist/packages/core/utils.d.ts +11 -0
- package/dist/packages/core/utils.js +1 -1
- package/dist/packages/renderer/binColors.d.ts +80 -0
- package/dist/packages/renderer/binColors.js +1 -0
- package/dist/packages/renderer/buildView.d.ts +100 -6
- package/dist/packages/renderer/buildView.js +1 -1
- package/dist/packages/renderer/buildWaferMap.d.ts +112 -7
- package/dist/packages/renderer/buildWaferMap.js +1 -1
- package/dist/packages/renderer/colorMap.d.ts +16 -15
- package/dist/packages/renderer/colorMap.js +1 -1
- package/dist/packages/renderer/colorSchemes.d.ts +45 -26
- package/dist/packages/renderer/colorSchemes.js +1 -1
- package/dist/packages/renderer/index.d.ts +3 -1
- package/dist/packages/renderer/index.js +1 -1
- package/dist/packages/stats/analyzeWaferLot.js +1 -1
- package/dist/packages/stats/analyzeWaferMap.js +1 -1
- package/dist/packages/stats/binPareto.d.ts +15 -0
- package/dist/packages/stats/binPareto.js +1 -1
- package/dist/packages/stats/clusterDetection.js +1 -1
- package/dist/packages/stats/connectedComponents.d.ts +14 -0
- package/dist/packages/stats/connectedComponents.js +1 -0
- package/dist/packages/stats/correlation.d.ts +26 -0
- package/dist/packages/stats/correlation.js +1 -1
- package/dist/packages/stats/facets.js +1 -1
- package/dist/packages/stats/filterFindings.d.ts +18 -0
- package/dist/packages/stats/filterFindings.js +1 -1
- package/dist/packages/stats/findingsNarrative.js +1 -1
- package/dist/packages/stats/histogram.d.ts +47 -2
- package/dist/packages/stats/histogram.js +1 -1
- package/dist/packages/stats/index.d.ts +6 -0
- package/dist/packages/stats/index.js +1 -1
- package/dist/packages/stats/mergeTestDefs.d.ts +44 -0
- package/dist/packages/stats/mergeTestDefs.js +1 -0
- package/dist/packages/stats/metadataColumns.d.ts +20 -4
- package/dist/packages/stats/metadataColumns.js +1 -1
- package/dist/packages/stats/patternClassification.js +1 -1
- package/dist/packages/stats/population.d.ts +23 -0
- package/dist/packages/stats/population.js +1 -0
- package/dist/packages/stats/renderFindingsReport.js +15 -15
- package/dist/packages/stats/renderSummaryReport.js +35 -32
- package/dist/packages/stats/reportHtml.js +38 -13
- package/dist/packages/stats/testPassRate.d.ts +85 -0
- package/dist/packages/stats/testPassRate.js +1 -0
- package/dist/packages/stats/trend.d.ts +40 -0
- package/dist/packages/stats/trend.js +1 -0
- package/dist/packages/stats/types.d.ts +117 -6
- package/dist/packages/stats/yield.js +1 -1
- package/llms.txt +6 -0
- package/package.json +9 -5
- package/dist/packages/canvas-adapter/metadataBadge.d.ts +0 -30
- package/dist/packages/canvas-adapter/metadataBadge.js +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
export function
|
|
1
|
+
export function collectTestValues(d,u){const t=[];for(const i of d)for(const a of i.dies??[]){const s=a.testValues?.[u];s!==void 0&&Number.isFinite(s)&&t.push(s)}return t}export function testValueExtent(d,u){let t=1/0,i=-1/0;for(const a of d)for(const s of a.dies??[]){const n=s.testValues?.[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,a,s){let n=collectTestValues(d,u);if(s&&(n=n.filter(e=>e>=s.lo&&e<=s.hi)),n.length===0)return[];let f=n[0],r=n[0];for(let e=1;e<n.length;e++)n[e]<f&&(f=n[e]),n[e]>r&&(r=n[e]);const l=i!==void 0?Math.min(f,i):f,M=((a!==void 0?Math.max(r,a):r)-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,a,s){const n=new Map;let f=1/0,r=-1/0;for(const o of d){const m=[];for(const y of o.items)for(const p of y.dies??[]){const c=p.testValues?.[u];if(c!==void 0&&Number.isFinite(c)){if(s&&(c<s.lo||c>s.hi))continue;m.push(c),c<f&&(f=c),c>r&&(r=c)}}n.set(o.key,m)}const l=d.map(o=>o.key).filter(o=>n.get(o).length>0);if(l.length===0)return{ranges:[],series:[]};const g=i!==void 0?Math.min(f,i):f,h=((a!==void 0?Math.max(r,a):r)-g||1)/t,e=Array.from({length:t},(o,m)=>({rangeLow:g+m*h,rangeHigh:g+(m+1)*h})),x=l.map(o=>{const m=new Array(t).fill(0);for(const y of n.get(o)){const p=Math.min(t-1,Math.floor((y-g)/h));m[p]++}return{groupKey:o,counts:m}});return{ranges:e,series:x}}
|
|
@@ -12,12 +12,18 @@ export { buildFacetTable, facetValueOf, FACET_NONE_VALUE, DEFAULT_FACET_CURATION
|
|
|
12
12
|
export type { FacetField, FacetValue, FacetCuration, FacetItem, BuildFacetTableOptions } from './facets.js';
|
|
13
13
|
export { resolveMetadataColumns, discoverDieMetadataKeys } from './metadataColumns.js';
|
|
14
14
|
export type { MetadataColumn, MetadataColumnScope, MetadataColumnSet, MetadataKeySelection, ResolveMetadataColumnsOptions, } from './metadataColumns.js';
|
|
15
|
+
export { mergeTestDefs } from './mergeTestDefs.js';
|
|
16
|
+
export type { MergedTestDefs, TestDefConflict, TestDefConflictKind } from './mergeTestDefs.js';
|
|
15
17
|
export { buildCapabilityData } from './capability.js';
|
|
16
18
|
export type { CapabilityDatum, CapabilityItem } from './capability.js';
|
|
17
19
|
export { buildCorrelationMatrix, filterCorrelationMatrix } from './correlation.js';
|
|
18
20
|
export type { CorrelationTestInfo, CorrelationCell, CorrelationMatrix, CorrelationSummary } from './correlation.js';
|
|
19
21
|
export { buildTestBoxplotData } from './boxplot.js';
|
|
20
22
|
export type { BoxplotDatum, BoxplotItem } from './boxplot.js';
|
|
23
|
+
export { buildTestTrendData, trendCentre } from './trend.js';
|
|
24
|
+
export type { TrendDatum, TrendItem } from './trend.js';
|
|
25
|
+
export { buildTestPassRateData, hasJudgeableTests } from './testPassRate.js';
|
|
26
|
+
export type { TestPassKind, TestPassRateData, TestPassRateItem, TestPassRateRow, TestPassRateValue } from './testPassRate.js';
|
|
21
27
|
export { buildTestHistogramData, buildTestHistogramSeries } from './histogram.js';
|
|
22
28
|
export type { HistogramBucket, HistogramItem, HistogramSeries, HistogramSeriesData } from './histogram.js';
|
|
23
29
|
export { buildScatterData, buildScatterDataGrouped } from './scatter.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export*from"./types.js";export*from"./regions.js";export*from"./analyzeWaferMap.js";export*from"./analyzeWaferLot.js";export*from"./filterFindings.js";export{classifyPattern}from"./patternClassification.js";export{renderFindingsReportHtml,openHtmlReport,setReportOpener}from"./renderFindingsReport.js";export{renderSummaryReportHtml,renderLotSummaryReportHtml}from"./renderSummaryReport.js";export{buildFacetTable,facetValueOf,FACET_NONE_VALUE,DEFAULT_FACET_CURATION}from"./facets.js";export{resolveMetadataColumns,discoverDieMetadataKeys}from"./metadataColumns.js";export{buildCapabilityData}from"./capability.js";export{buildCorrelationMatrix,filterCorrelationMatrix}from"./correlation.js";export{buildTestBoxplotData}from"./boxplot.js";export{buildTestHistogramData,buildTestHistogramSeries}from"./histogram.js";export{buildScatterData,buildScatterDataGrouped}from"./scatter.js";export{buildYieldData,buildYieldDataCombined}from"./yield.js";export{buildBinParetoData,buildBinClusterData}from"./binPareto.js";
|
|
1
|
+
export*from"./types.js";export*from"./regions.js";export*from"./analyzeWaferMap.js";export*from"./analyzeWaferLot.js";export*from"./filterFindings.js";export{classifyPattern}from"./patternClassification.js";export{renderFindingsReportHtml,openHtmlReport,setReportOpener}from"./renderFindingsReport.js";export{renderSummaryReportHtml,renderLotSummaryReportHtml}from"./renderSummaryReport.js";export{buildFacetTable,facetValueOf,FACET_NONE_VALUE,DEFAULT_FACET_CURATION}from"./facets.js";export{resolveMetadataColumns,discoverDieMetadataKeys}from"./metadataColumns.js";export{mergeTestDefs}from"./mergeTestDefs.js";export{buildCapabilityData}from"./capability.js";export{buildCorrelationMatrix,filterCorrelationMatrix}from"./correlation.js";export{buildTestBoxplotData}from"./boxplot.js";export{buildTestTrendData,trendCentre}from"./trend.js";export{buildTestPassRateData,hasJudgeableTests}from"./testPassRate.js";export{buildTestHistogramData,buildTestHistogramSeries}from"./histogram.js";export{buildScatterData,buildScatterDataGrouped}from"./scatter.js";export{buildYieldData,buildYieldDataCombined}from"./yield.js";export{buildBinParetoData,buildBinClusterData}from"./binPareto.js";
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { TestDef, WaferWarning } from '../renderer/buildWaferMap.js';
|
|
2
|
+
/** Which field disagreed. `'limits'` is the soft tier; the rest are hard. */
|
|
3
|
+
export type TestDefConflictKind = 'name' | 'unit' | 'testType' | 'limits';
|
|
4
|
+
/** One test number whose defs could not be reconciled across the population. */
|
|
5
|
+
export interface TestDefConflict {
|
|
6
|
+
testNumber: number;
|
|
7
|
+
kind: TestDefConflictKind;
|
|
8
|
+
/** True when the test was withheld from `defs` (the hard tier). */
|
|
9
|
+
excluded: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* The distinct STATED values that disagreed, in first-seen order, already
|
|
12
|
+
* stringified for display. Absent values are not represented — they were
|
|
13
|
+
* never in disagreement with anything.
|
|
14
|
+
*/
|
|
15
|
+
values: string[];
|
|
16
|
+
}
|
|
17
|
+
export interface MergedTestDefs {
|
|
18
|
+
/**
|
|
19
|
+
* The union of every test number across every item, each carrying a def
|
|
20
|
+
* reconciled from all items that described it. Hard-conflicting tests are
|
|
21
|
+
* absent. Ordered by first appearance, so a single-program population gets
|
|
22
|
+
* back exactly the order it supplied.
|
|
23
|
+
*/
|
|
24
|
+
defs: TestDef[];
|
|
25
|
+
/** Every unreconcilable test, both tiers. Empty for a clean population. */
|
|
26
|
+
conflicts: TestDefConflict[];
|
|
27
|
+
/**
|
|
28
|
+
* `conflicts` rendered as structured advisories, ready to hand to
|
|
29
|
+
* `collectWarnings` so the toolbar indicator and Summary banner report them
|
|
30
|
+
* with no host work. At most one warning per severity — a program mismatch
|
|
31
|
+
* hits many tests at once, and one line per test would bury everything else.
|
|
32
|
+
*/
|
|
33
|
+
warnings: WaferWarning[];
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Reconcile every item's `testDefs` into one list for the whole population.
|
|
37
|
+
*
|
|
38
|
+
* Items with no defs are skipped, not treated as disagreeing — a wafer that
|
|
39
|
+
* describes nothing contradicts nothing.
|
|
40
|
+
*/
|
|
41
|
+
export declare function mergeTestDefs(items: Array<{
|
|
42
|
+
testDefs?: TestDef[];
|
|
43
|
+
} | null | undefined>): MergedTestDefs;
|
|
44
|
+
//# sourceMappingURL=mergeTestDefs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const k=1e-6;function w(t,e){if(t===e)return!0;if(!Number.isFinite(t)||!Number.isFinite(e))return!1;const i=Math.max(Math.abs(t),Math.abs(e));return Math.abs(t-e)<=1e-6*i}function v(t){const e=t.name?.trim();return e||void 0}function y(t){const e=t.unit?.trim();return e||void 0}function L(t){return t.toLowerCase()}function h(t,e){const i=new Set,s=[];for(const o of t){const r=e(o);i.has(r)||(i.add(r),s.push(o))}return s}function g(t){const e=[];for(const i of t)e.some(s=>w(s,i))||e.push(i);return e}function T(t){return`${t.testNumber} (${t.values.join(" vs ")})`}function b(t,e=5){const i=t.slice(0,e).map(T).join(", "),s=t.length-Math.min(e,t.length);return s>0?`${i}, and ${s} more`:i}export function mergeTestDefs(t){const e=new Map,i=[];for(const r of t)for(const u of r?.testDefs??[]){const f=u.testNumber;let d=e.get(f);d||(d=[],e.set(f,d),i.push(f)),d.push(u)}const s=[],o=[];for(const r of i){const u=e.get(r),f=h(u.map(v).filter(n=>n!==void 0),L),d=h(u.map(y).filter(n=>n!==void 0),n=>n),l=h(u.map(n=>n.testType).filter(n=>n!==void 0),n=>n),p=f.length>1?{testNumber:r,kind:"name",excluded:!0,values:f}:d.length>1?{testNumber:r,kind:"unit",excluded:!0,values:d}:l.length>1?{testNumber:r,kind:"testType",excluded:!0,values:l}:void 0;if(p){o.push(p);continue}const a=g(u.map(n=>n.limitLow).filter(n=>n!==void 0)),c=g(u.map(n=>n.limitHigh).filter(n=>n!==void 0)),m=a.length>1||c.length>1;m&&o.push({testNumber:r,kind:"limits",excluded:!1,values:(a.length>1?a:c).map(n=>String(n))}),s.push({testNumber:r,name:f[0]??"",...d[0]!==void 0?{unit:d[0]}:{},...l[0]!==void 0?{testType:l[0]}:{},...u.find(n=>n.logScale!==void 0)?{logScale:u.find(n=>n.logScale!==void 0).logScale}:{},...!m&&a[0]!==void 0?{limitLow:a[0]}:{},...!m&&c[0]!==void 0?{limitHigh:c[0]}:{}})}return{defs:s,conflicts:o,warnings:N(o)}}function N(t){const e=[],i=t.filter(o=>o.excluded),s=t.filter(o=>o.kind==="limits");return i.length>0&&e.push({code:"test-def-collision",severity:"error",message:`${i.length} test ${i.length===1?"number describes":"numbers describe"} a different measurement in different wafers, so the same number cannot identify one test across this population. Affected: `+b(i)+". These tests are withheld from every cross-wafer chart, report and map mode \u2014 pooling them would mix unrelated measurements into one distribution. Each wafer's own view is unaffected. Load one test program at a time to see them."}),s.length>0&&e.push({code:"test-limit-conflict",severity:"warning",message:`${s.length} ${s.length===1?"test is":"tests are"} held to different spec limits in different wafers. Affected: `+b(s)+". The measured values are still comparable, so distributions include them, but capability (Cp/Cpk/Pp/Ppk), spec yield and the limit lines are withheld for these tests \u2014 there is no single spec to judge the merged population against."}),e}
|
|
@@ -5,7 +5,21 @@ import type { MetadataFieldDef } from '../renderer/buildWaferMap.js';
|
|
|
5
5
|
* every row; a `'die'` column genuinely varies die to die. */
|
|
6
6
|
export type MetadataColumnScope = 'die' | 'wafer';
|
|
7
7
|
/** How metadata keys are chosen. `'auto'` = every key present in the data. */
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* `'auto'` — every displayable key.
|
|
10
|
+
* `'identity'` — only the curated keys that identify a population (lot, wafer,
|
|
11
|
+
* product, program, split, operator, date…), i.e. `DEFAULT_FACET_CURATION`.
|
|
12
|
+
* `'none'` — no columns.
|
|
13
|
+
* `string[]` — exactly these keys, when present.
|
|
14
|
+
*
|
|
15
|
+
* `'identity'` exists because `'auto'` is the wrong default for a per-test
|
|
16
|
+
* export: a host that maps an STDF header into wafer metadata carries WCR
|
|
17
|
+
* geometry (`Center X`, `Die Ht`, `Wf Flat`, `Wafr Siz`, `Pos X`…) alongside the
|
|
18
|
+
* identity fields, and `'auto'` stamped all fifteen of them as constant leading
|
|
19
|
+
* columns on every row — pushing the actual statistics off the right of the
|
|
20
|
+
* screen while answering none of "which wafer is this?".
|
|
21
|
+
*/
|
|
22
|
+
export type MetadataKeySelection = 'auto' | 'identity' | 'none' | string[];
|
|
9
23
|
export interface MetadataColumn {
|
|
10
24
|
/** The metadata key this column reads. */
|
|
11
25
|
key: string;
|
|
@@ -62,9 +76,11 @@ export declare function discoverDieMetadataKeys(dies: Die[], metadataFields?: Me
|
|
|
62
76
|
* Build the metadata column set for a population.
|
|
63
77
|
*
|
|
64
78
|
* A key present on both a die and its wafer yields exactly ONE column, scope
|
|
65
|
-
* `'die'`, carrying the die value
|
|
66
|
-
*
|
|
67
|
-
*
|
|
79
|
+
* `'die'`, carrying the die value. Two columns for one key could otherwise
|
|
80
|
+
* disagree in the same row. (This used to cite the hover tooltip as applying the
|
|
81
|
+
* same `{ ...waferMeta, ...die.metadata }` shadowing; the tooltip no longer shows
|
|
82
|
+
* wafer metadata at all — that identity moved to the identity header — so the
|
|
83
|
+
* rule now stands on its own reasoning rather than a parallel that has gone.)
|
|
68
84
|
*/
|
|
69
85
|
export declare function resolveMetadataColumns(o: ResolveMetadataColumnsOptions): MetadataColumnSet;
|
|
70
86
|
//# sourceMappingURL=metadataColumns.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{metadataDisplayValue as k}from"../core/metadata.js";import{compareNatural as w,prettyKey as
|
|
1
|
+
import{metadataDisplayValue as k}from"../core/metadata.js";import{compareNatural as w,prettyKey as A}from"../core/utils.js";import{DEFAULT_FACET_CURATION as K}from"./facets.js";const b=64;export function discoverDieMetadataKeys(e,n,s=b){const d=new Set;for(const c of e)if(c.metadata)for(const i of Object.keys(c.metadata))d.add(i);const t=[];for(const c of n??[])d.has(c.key)&&!t.includes(c.key)&&t.push(c.key);const y=[...d].filter(c=>!t.includes(c)).sort(w),a=[...t,...y];return{keys:a.slice(0,s),truncated:a.slice(s)}}function v(e){const n=Object.keys(e).filter(t=>k(e[t])!==void 0),s=Object.keys(K).filter(t=>n.includes(t)),d=n.filter(t=>!s.includes(t)).sort(w);return[...s,...d]}const h=(e,n)=>e==="none"?[]:Array.isArray(e)?e.filter(s=>n.includes(s)):e==="identity"?n.filter(s=>s in K):n;export function resolveMetadataColumns(e){const n=e.waferMetadata??{},s=e.waferPlacement??"csv",d=discoverDieMetadataKeys(e.dies??[],e.metadataFields,e.maxDieKeys??b),t=h(e.dieKeys,d.keys),y=s==="none"?[]:h(e.waferKeys,v(n)).filter(r=>!t.includes(r)),a=new Set(e.reservedLabels??[]),c=(r,o)=>{const f=e.metadataFields?.find(u=>u.key===r)?.label??A(r);if(!a.has(f))return a.add(f),f;const m=o==="die"?`${f} (metadata)`:`${f} (wafer metadata)`;if(!a.has(m))return a.add(m),m;const l=`${f} (${r})`;if(!a.has(l))return a.add(l),l;for(let u=2;;u++){const p=`${l} (${u})`;if(!a.has(p))return a.add(p),p}},i=[];for(const r of t)i.push({key:r,label:c(r,"die"),scope:"die",get:o=>k(o.metadata?.[r])??"",csvOnly:!1});for(const r of y){const o=k(n[r])??"";i.push({key:r,label:c(r,"wafer"),scope:"wafer",get:()=>o,constant:o,csvOnly:s==="csv"})}return{columns:i,truncatedKeys:d.truncated}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
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 F(t,o,s){const i=t.physX-o.center.x,r=t.physY-o.center.y,d=Math.sqrt(i*i+r*r)/o.radius;return Math.min(s,Math.max(1,Math.floor(d*s)+1))===s}function Q(t,o,s){if(t.length<3)return 0;let i=0,r=0,d=0;for(const g of t){const l=g.physX-o,D=g.physY-s;i+=l*l,r+=D*D,d+=l*D}i/=t.length,r/=t.length,d/=t.length;const c=i+r,n=i*r-d*d,a=Math.max(0,(c/2)**2-n),m=c/2+Math.sqrt(a),R=c/2-Math.sqrt(a);if(m<=0)return 0;const f=K(R/m);return Math.sqrt(1-f)}function V(t,o){if(o===0)return 0;const s=new Map,i=new Map,r=new Map,d=new Map;for(const n of t)s.set(n.y,(s.get(n.y)??0)+1),i.set(n.x,(i.get(n.x)??0)+1),r.set(n.x-n.y,(r.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 s.values())n>c&&(c=n);for(const n of i.values())n>c&&(c=n);for(const n of r.values())n>c&&(c=n);for(const n of d.values())n>c&&(c=n);return c/o}function W(t,o,s,i){const r=o.length,d=t.length,c=r>0?d/r:0,n=o.filter(e=>F(e,s,i)),a=t.filter(e=>F(e,s,i)),m=n.length>0?a.length/n.length:0,R=J(t),f=R.length>0?[...R].sort((e,h)=>h.length-e.length):[t],g=f[0],l=f.slice(0,5).flat(),D=g.length,O=d>0?D/d:0,u=s.center.x,p=s.center.y,y=s.radius;let E=0,N=0,M=1/0,x=0;for(const e of g){E+=e.physX,N+=e.physY;const h=Math.sqrt((e.physX-u)**2+(e.physY-p)**2);h<M&&(M=h),h>x&&(x=h)}const H=E/g.length,P=N/g.length,T=Math.sqrt((H-u)**2+(P-p)**2)/y,I=M===1/0?0:M/y,v=Math.min(x/y,1),C=t.map(e=>Math.sqrt((e.physX-u)**2+(e.physY-p)**2)/y).sort((e,h)=>e-h),w=C[Math.floor(C.length*.25)]??0,b=o.filter(e=>Math.sqrt((e.physX-u)**2+(e.physY-p)**2)/y<.5).length,X=o.filter(e=>Math.sqrt((e.physX-u)**2+(e.physY-p)**2)/y>=.5).length,z=t.filter(e=>Math.sqrt((e.physX-u)**2+(e.physY-p)**2)/y<.5).length,A=t.filter(e=>Math.sqrt((e.physX-u)**2+(e.physY-p)**2)/y>=.5).length,Y=b>0?z/b:0,L=X>0?A/X:0,_=L>0?Y/L:Y>0?2:1,G=l.reduce((e,h)=>e+h.physX,0)/l.length,k=l.reduce((e,h)=>e+h.physY,0)/l.length,U=Q(l,G,k),B=V(l,l.length),S=16,q=new Set;for(const e of a){const h=(Math.atan2(e.physY-p,e.physX-u)+2*Math.PI)%(2*Math.PI);q.add(Math.floor(h/(2*Math.PI)*S)%S)}const j=a.length>0?q.size/S:0;return{globalRdd:c,edgeRdd:m,centroidDistNorm:T,minDistNorm:I,maxDistNorm:v,p25DistNorm:w,eccentricity:U,linearScore:B,salienceSize:D,salienceFraction:O,edgeAngularSpread:j,innerOuterRatio:_}}function Z(t,o,s){const i=Math.max(5,Math.round(s*.003)),r=t.salienceSize>=i;if(t.globalRdd<o.nearFullGlobalRdd&&t.salienceFraction<.1)return{pattern:"random",confidence:"low"};if(t.globalRdd>=o.nearFullGlobalRdd)return{pattern:"near-full",confidence:t.globalRdd>=.8?"high":"medium"};if(r&&t.linearScore>=o.scratchLinearScore&&t.eccentricity>=o.scratchEccentricity){const a=t.linearScore>=o.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=r&&t.edgeRdd>=o.edgeLocalEdgeRdd&&t.maxDistNorm>=.7;if(d&&t.p25DistNorm>=o.edgeLocalCentroidDist&&t.maxDistNorm-t.minDistNorm<=o.edgeRingRadialSpread)return{pattern:"edge-ring",confidence:t.edgeRdd>=o.edgeRingEdgeRddHigh?"high":"medium"};if(d&&t.p25DistNorm>=.55&&t.p25DistNorm<o.edgeLocalCentroidDist)return{pattern:"edge-local",confidence:t.edgeRdd>=o.edgeRingEdgeRdd?"medium":"low"};const c=r&&t.centroidDistNorm<=o.centerCentroidDist&&t.edgeRdd<=o.centerEdgeRdd,n="Center and donut patterns have similar geometry; classification may be imprecise";if(c&&t.p25DistNorm<o.donutP25Dist){const a=t.centroidDistNorm<=o.centerCentroidDistHigh?"high":"medium";return{pattern:"center",confidence:a,note:a!=="high"?n:void 0}}return c&&t.p25DistNorm>=o.donutP25Dist&&t.p25DistNorm<.55?{pattern:"donut",confidence:"medium",note:n}:{pattern:"random",confidence:"low"}}export function classifyPattern(t,o,s){const i={...DEFAULT_PATTERN_THRESHOLDS},r=s.ringCount??4,d=new Set(s.passBins),c=t.filter(g=>{const l=g.hbin??g.sbin;return l!==void 0&&!d.has(l)}),n=Math.max(i.minimumFailingDies,Math.round(t.length*.003));if(c.length<n)return null;const a=W(c,t,o,r),{pattern:m,confidence:R,note:f}=Z(a,i,t.length);return{pattern:m,confidence:R,features:a,...f?{note:f}:{}}}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface WaferPopulation {
|
|
2
|
+
waferCount: number;
|
|
3
|
+
/** The lot ID every wafer records, when they all record the same one. */
|
|
4
|
+
lotId?: string;
|
|
5
|
+
/** Distinct lot IDs recorded across the wafers — 0 when none records one. */
|
|
6
|
+
lotCount: number;
|
|
7
|
+
}
|
|
8
|
+
type Metadata = Record<string, unknown> | null | undefined;
|
|
9
|
+
/** One entry per wafer — its `wafer.metadata` (or a summary's `wafer` identity). */
|
|
10
|
+
export declare function describeWaferPopulation(metadatas: readonly Metadata[]): WaferPopulation;
|
|
11
|
+
/**
|
|
12
|
+
* What the set is, for a title: "Lot LOT123 · 13 wafers", "26 wafers from
|
|
13
|
+
* 2 lots", or "13 wafers" (no lot ID recorded).
|
|
14
|
+
*/
|
|
15
|
+
export declare function populationLabel(p: WaferPopulation): string;
|
|
16
|
+
/**
|
|
17
|
+
* A statistic taken across the set, as a noun phrase: "lot median" for one
|
|
18
|
+
* lot, otherwise "median of all wafers". Reads correctly after "the" and
|
|
19
|
+
* "vs".
|
|
20
|
+
*/
|
|
21
|
+
export declare function populationStat(p: WaferPopulation, stat: 'mean' | 'median'): string;
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=population.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function f(n){const t=n?.lot??n?.lotId;return t==null||t===""?void 0:String(t)}export function describeWaferPopulation(n){const t=n.map(f),o=new Set(t.filter(e=>e!==void 0)),u=o.size===1&&t.every(e=>e!==void 0);return{waferCount:n.length,lotCount:o.size,...u?{lotId:[...o][0]}:{}}}const r=n=>`${n} wafer${n===1?"":"s"}`;export function populationLabel(n){return n.lotId!==void 0?`Lot ${n.lotId} \xB7 ${r(n.waferCount)}`:n.lotCount>1?`${r(n.waferCount)} from ${n.lotCount} lots`:r(n.waferCount)}export function populationStat(n,t){return n.lotId!==void 0?`lot ${t}`:`${t} of all wafers`}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import{
|
|
2
|
-
<td class="tight">${
|
|
3
|
-
<td class="tight">${
|
|
4
|
-
<td>${
|
|
5
|
-
<td class="numeric">${
|
|
6
|
-
<td class="numeric">${
|
|
1
|
+
import{describeWaferPopulation as b,populationLabel as h}from"./population.js";import{formatFindingDelta as g,formatFindingCoverage as v,formatFindingTooltip as y,escHtml as o,buildMetadataRows as d,renderDefinitionList as p,renderSection as c,renderSeverityBadge as R,reportStyles as $}from"./reportHtml.js";import{buildFindingsNarrative as F}from"./findingsNarrative.js";import{plainBinTerms as f}from"../renderer/fmt.js";import{visibleFindings as S}from"./filterFindings.js";function T(e,n){if(e.level==="lot"){const a=e,l=a.perWafer.map(r=>r.summary.wafer?.wafer??r.summary.wafer?.waferId).filter(r=>r!=null).map(String).join(", ");return p([...d(a.perWafer.map(r=>({metadata:r.summary.wafer}))),...l?[{label:"Wafers",value:l}]:[],{label:"Wafer count",value:String(a.stats.waferCount)},{label:"Generated",value:n}])}const t=e,i=t.stats.yieldPercent!==null?`${t.stats.yieldPercent.toFixed(1)}%`:"N/A";return p([...d([{metadata:t.wafer}]),{label:"Total dies",value:String(t.stats.totalDies)},{label:"Analysed dies",value:String(t.stats.analyzedDies)},{label:"Yield",value:i},{label:"Generated",value:n}])}function H(e,n){return e.length?e.map(t=>`<tr title="${o(y(t))}">
|
|
2
|
+
<td class="tight">${R(t.severity)}</td>
|
|
3
|
+
<td class="tight">${o(t.comparison.left)}</td>
|
|
4
|
+
<td>${o(f(t.variable.label))}</td>
|
|
5
|
+
<td class="numeric">${o(g(t))}</td>
|
|
6
|
+
<td class="numeric">${o(v(t,n))}</td>
|
|
7
7
|
</tr>`).join(`
|
|
8
|
-
`):'<tr><td colspan="5" class="no-data">No significant findings</td></tr>'}function
|
|
8
|
+
`):'<tr><td colspan="5" class="no-data">No significant findings</td></tr>'}function L(e,n){return`<table class="report-table findings-table compact">
|
|
9
9
|
<thead>
|
|
10
10
|
<tr>
|
|
11
11
|
<th>Severity</th>
|
|
@@ -16,26 +16,26 @@ import{formatFindingDelta as b,formatFindingCoverage as g,formatFindingTooltip a
|
|
|
16
16
|
</tr>
|
|
17
17
|
</thead>
|
|
18
18
|
<tbody>
|
|
19
|
-
${
|
|
19
|
+
${H(e,n)}
|
|
20
20
|
</tbody>
|
|
21
|
-
</table>`}export function renderFindingsReportHtml(e,n={}){const t=e.level==="lot",i=n.title??(t?
|
|
22
|
-
`:"",
|
|
21
|
+
</table>`}export function renderFindingsReportHtml(e,n={}){const t=e.level==="lot"?b(e.perWafer.map(w=>w.summary.wafer)):void 0,i=n.title??(t?t.lotId!==void 0?`Lot ${t.lotId} Findings Report`:`Findings Report \u2014 ${h(t)}`:"Wafer Findings Report"),a=new Date().toLocaleString(),l=S(e.findings),r=e.level==="lot"?e.stats.waferCount:void 0,s=f(F(l)??""),u=s?`<p class="findings-narrative">${o(s)}</p>
|
|
22
|
+
`:"",m=[c("Summary",T(e,a)),c("Findings",u+L(l,r))].join(`
|
|
23
23
|
`);return`<!DOCTYPE html>
|
|
24
24
|
<html lang="en">
|
|
25
25
|
<head>
|
|
26
26
|
<meta charset="UTF-8">
|
|
27
|
-
<title>${
|
|
27
|
+
<title>${o(i)}</title>
|
|
28
28
|
<style>
|
|
29
|
-
${
|
|
29
|
+
${$()}
|
|
30
30
|
</style>
|
|
31
31
|
</head>
|
|
32
32
|
<body>
|
|
33
33
|
<main class="report">
|
|
34
34
|
<header class="report-header">
|
|
35
|
-
<h1>${
|
|
36
|
-
<p class="report-subtitle">Generated ${a
|
|
35
|
+
<h1>${o(i)}</h1>
|
|
36
|
+
<p class="report-subtitle">Generated ${o(a)}</p>
|
|
37
37
|
</header>
|
|
38
|
-
${
|
|
38
|
+
${m}
|
|
39
39
|
</main>
|
|
40
40
|
</body>
|
|
41
41
|
</html>`}export function openHtmlReport(e){if(typeof window.__openHtmlReport=="function"){window.__openHtmlReport(e);return}const n=URL.createObjectURL(new Blob([e],{type:"text/html"})),t=window.open(n,"_blank");setTimeout(()=>URL.revokeObjectURL(n),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,68 +1,71 @@
|
|
|
1
|
-
import{isParametricTest as
|
|
2
|
-
<td class="tight">${
|
|
3
|
-
<td class="tight">${
|
|
4
|
-
<td>${
|
|
5
|
-
<td class="numeric">${
|
|
6
|
-
<td class="numeric">${
|
|
1
|
+
import{isParametricTest as ge}from"../renderer/buildWaferMap.js";import{buildRingRegions as E,buildQuadrantRegions as k}from"./regions.js";import{openHtmlReport as he}from"./renderFindingsReport.js";import{analyzeWaferLot as be}from"./analyzeWaferLot.js";import{computeFunctionalYield as ye}from"./analyzeWaferMap.js";import{sortBinsForDisplay as C}from"./binPareto.js";import{buildFacetTable as Se,facetValueOf as we,FACET_NONE_VALUE as $e}from"./facets.js";import{visibleFindings as H}from"./filterFindings.js";import{buildYieldDataCombined as xe}from"./yield.js";import{describeWaferPopulation as I,populationLabel as ve}from"./population.js";import{buildTestPassRateData as Te,hasJudgeableTests as Fe,poolFunctionalYield as De}from"./testPassRate.js";import{buildCapabilityData as Pe}from"./capability.js";import{fmt as B}from"../renderer/fmt.js";import{getDieKey as L,isPositionedDie as j}from"../core/dies.js";import{formatFindingDelta as Be,formatFindingCoverage as Ye,formatFindingTooltip as Me,escHtml as S,renderMetadataSection as A,renderMetricGrid as O,renderSection as v,renderSeverityBadge as Ne,renderTable as F,reportStyles as W}from"./reportHtml.js";function Y(s,l){return l===0?"\u2014":`${(s/l*100).toFixed(1)}%`}function We(s){if(!s)return"";const l=s.lot??s.lotId,i=s.wafer??s.waferId,n=[l,i].filter(Boolean).map(String);return n.length?` \u2014 ${n.map(S).join(" \xB7 ")}`:""}function z(s,l,i,n,a=[1]){const e=new Map;if(n)for(const[t,c]of Object.entries(n))e.set(Number(t),c);else for(const t of s){if(t.partial||t.edgeExcluded)continue;const c=i==="hard"?t.hbin:t.sbin;c!=null&&e.set(c,(e.get(c)??0)+1)}if(!e.size)return"";const o=[...e.values()].reduce((t,c)=>t+c,0),r=l?new Map(l.map(t=>[t.bin,t])):null,d=C(e.entries(),a).map(([t,c])=>{const m=r?.get(t)?.name??"\u2014";return[String(t),m,String(c),Y(c,o)]});return v(i==="hard"?"Hard Bin Breakdown":"Soft Bin Breakdown",F(["Bin","Name","Count","%"],d,{className:"compact"}))}function G(s,l,i,n){const a=new Set(n),e=new Map(i.map(d=>[L(d),d]));if(!i.some(d=>d.hbin!=null||d.sbin!=null)||!l.length)return"";const r=[];for(const d of l){let u=0,t=0;for(const c of d.dieKeys){const b=e.get(c);if(!b||b.partial||b.edgeExcluded)continue;const m=b.hbin??b.sbin;m!=null&&(t++,a.has(m)&&u++)}t&&r.push([d.label,String(u),String(t),Y(u,t)])}return r.length?v(s,F(["Region","Pass","Total","Yield"],r,{className:"compact"})):""}function K(s,l,i){if(!l.length)return"";const n=s.filter(o=>!o.partial&&!o.edgeExcluded),a=new Map((i??[]).map(o=>[o.testNumber,o])),e=[];for(const o of l.filter(ge)){const r=o.testNumber,d=o.unit||void 0,u=a.get(r);let t,c,b;if(u)({min:t,max:c,mean:b}=u);else{const m=n.map(w=>w.testValues?.[r]).filter(w=>w!==void 0&&isFinite(w));if(!m.length)continue;m.sort((w,p)=>w-p),t=m[0],c=m[m.length-1],b=m.reduce((w,p)=>w+p,0)/m.length}e.push([S(o.name),B(t,d),B(b,d),B(c,d)])}return e.length?v("Test Values",F(["Test","Min","Mean","Max"],e,{className:"compact"})):""}function U(s,l,i){const n=i??ye(s,l);if(!n?.length)return"";const a=n.map(e=>[S(e.label),String(e.totalDies),String(e.passDies),String(e.failDies),e.passRatePercent!==null?`${e.passRatePercent.toFixed(1)}%`:"\u2014"]);return v("Functional Tests",F(["Test","N","Pass","Fail","Pass Rate"],a,{className:"compact"}))}function V(s,l){const i=Pe(s,l);if(!i.length)return"";const n=e=>e===null?"\u2014":e.toFixed(2),a=i.map(e=>[S(e.label),e.hasSpec?`${B(e.lsl,e.unit)} \u2013 ${B(e.usl,e.unit)}`:"\u2014",B(e.mean,e.unit),n(e.cp),n(e.cpk),n(e.pp),n(e.ppk)]);return v("Process Capability",F(["Test","Spec (LSL\u2013USL)","Mean","Cp","Cpk","Pp","Ppk"],a,{className:"compact"}))}function _(s,l){const i=s.map(a=>`<tr title="${S(Me(a))}">
|
|
2
|
+
<td class="tight">${Ne(a.severity)}</td>
|
|
3
|
+
<td class="tight">${S(a.comparison.left)}</td>
|
|
4
|
+
<td>${S(a.variable.label)}</td>
|
|
5
|
+
<td class="numeric">${S(Be(a))}</td>
|
|
6
|
+
<td class="numeric">${S(Ye(a,l))}</td>
|
|
7
7
|
</tr>`).join(`
|
|
8
|
-
`)
|
|
9
|
-
<th>Severity</th><th>Region</th><th>Metric</th><th class="numeric">Delta</th><th class="numeric">${
|
|
10
|
-
</tr></thead><tbody>${i}</tbody></table
|
|
11
|
-
`)),
|
|
8
|
+
`);return`<table class="report-table findings-table compact"><thead><tr>
|
|
9
|
+
<th>Severity</th><th>Region</th><th>Metric</th><th class="numeric">Delta</th><th class="numeric">${l!==void 0?"Wafers":"N (region/rest)"}</th>
|
|
10
|
+
</tr></thead><tbody>${i}</tbody></table>`}function Q(s,l){const i=H(s);return i.length?v("Findings",_(i,l)):""}export function renderSummaryReportHtml(s,l={}){const{wafer:i,dies:n,yieldSummary:a,dataCoverage:e,hbinDefs:o,sbinDefs:r,testDefs:d=[],statsSummary:u,passBins:t=[1],ringCount:c=4}=s,b=i.metadata,m=We(b),w=l.title??`Wafer Summary${m}`,p=new Date().toLocaleString(),f=n.some(P=>P.hbin!=null),g=n.some(P=>P.sbin!=null),y=n.filter(j),$=E(y,i,c),x=k(y,i,c),T=[{label:"Total dies",value:String(a.totalDies)},{label:"Filled dies",value:String(e.filledDies),hint:`${Y(e.filledDies,e.totalDies)} fill`},{label:"Pass dies",value:String(a.passDies)},{label:"Fail dies",value:String(a.failDies)},...a.partialDies>0?[{label:"Partial",value:String(a.partialDies)}]:[],...a.yieldPercent!==null?[{label:`Yield (pass: ${t.length===1?`bin ${t[0]}`:`bins ${t.join(", ")}`})`,value:`${a.yieldPercent.toFixed(1)}%`}]:[],...a.edgeExcludedDies>0?[{label:"Edge excluded (outer zone)",value:String(a.edgeExcludedDies)}]:[]],N=[A([{metadata:i.metadata}]),v("Summary",[O(T)].filter(Boolean).join(`
|
|
11
|
+
`)),f?z(n,o,"hard",u?.stats.hardBinCounts,t):g?z(n,r,"soft",u?.stats.softBinCounts,t):"",G("Ring Yield",$,n,t),G("Quadrant Yield",x,n,t),K(n,d,u?.stats.perTestStats),U(n,d,u?.stats.functionalYield),V([{dies:n}],d),u?Q(u.findings):""].filter(Boolean).join(`
|
|
12
12
|
`);return`<!DOCTYPE html>
|
|
13
13
|
<html lang="en">
|
|
14
14
|
<head>
|
|
15
15
|
<meta charset="UTF-8">
|
|
16
|
-
<title>${
|
|
16
|
+
<title>${S(w)}</title>
|
|
17
17
|
<style>
|
|
18
|
-
${
|
|
18
|
+
${W()}
|
|
19
19
|
</style>
|
|
20
20
|
</head>
|
|
21
21
|
<body>
|
|
22
22
|
<main class="report">
|
|
23
23
|
<header class="report-header">
|
|
24
|
-
<h1>${
|
|
25
|
-
<p class="report-subtitle">Generated ${
|
|
24
|
+
<h1>${S(w)}</h1>
|
|
25
|
+
<p class="report-subtitle">Generated ${S(p)}</p>
|
|
26
26
|
</header>
|
|
27
27
|
${N}
|
|
28
|
-
<p class="footer">Generated ${
|
|
28
|
+
<p class="footer">Generated ${S(p)}</p>
|
|
29
29
|
</main>
|
|
30
30
|
</body>
|
|
31
|
-
</html>`}function
|
|
32
|
-
`);return{
|
|
31
|
+
</html>`}function Re(s,l){const i=s.perWafer.map(n=>{const a=l[n.waferIndex]?.label??`W${n.waferIndex+1}`,e=n.summary.stats.yieldPercent;return[a,e!==null?`${e.toFixed(1)}%`:"N/A"]});return F(["Wafer","Yield"],i,{className:"compact"})}function Ee(s){const l=[];for(const e of s.perWafer){const o=H(e.summary.findings);if(!o.length)continue;const r=e.summary.wafer?.waferId??`Wafer ${e.waferIndex+1}`;l.push(`<h3 class="report-subheading">${S(r)}</h3>`+_(o))}if(!l.length)return"";const i=l.length,n=s.perWafer.length,a=`<p class="report-note">${i} of ${n} wafer${n===1?"":"s"} have per-wafer findings; the rest had none.</p>`;return v("Findings by Wafer",a+l.join(`
|
|
32
|
+
`))}const ke=3;function Ce(s,l,i){const n=s.map(d=>({metadata:d.wafer?.metadata})),a=Se(n,{facetableOnly:!0}).filter(d=>d.splittable);if(!a.length)return"";const e=a.slice(0,ke),o=[];for(const d of e){const u=new Map;s.forEach((f,g)=>{const y=we(f.wafer?.metadata,d.key)??$e;(u.get(y)??u.set(y,[]).get(y)).push(g)});const t=[...u.entries()].map(([f,g])=>({key:f,items:g.map(y=>s[y])}));if(t.length<2)continue;const c=t.map(f=>[S(f.key),S(f.items.map(g=>g.label).join(", "))]),b=xe(t.map(f=>({key:f.key,items:f.items.map(g=>({label:g.label,dies:g.dies}))})),i).map(f=>[S(f.label),String(f.itemCount),`${f.percent.toFixed(1)}%`]),m=[F([d.label,"Wafers"],c,{className:"compact"}),F([d.label,"Wafers","Yield"],b,{className:"compact"})],w=s.flatMap(f=>f.dies??[]);let p=0;for(const f of["spec","testFlag","functional"]){if(!Fe(l,f,w))continue;const g=Te(t.map(x=>({key:x.key,items:x.items.map(T=>({dies:T.dies,testSpecYield:T.statsSummary?.stats.testSpecYield,functionalYield:T.statsSummary?.stats.functionalYield}))})),l,f);if(!g.rows.length)continue;const y=g.rows.map(x=>[S(x.label),...x.byGroup.map(T=>T.passRatePercent===null?"\u2014":`${T.passRatePercent.toFixed(1)}%`),x.overall.passRatePercent===null?"\u2014":`${x.overall.passRatePercent.toFixed(1)}%`]),$=f==="spec"?"Pass rate \xB7 spec limits":f==="testFlag"?"Pass rate \xB7 tester flag":"Pass rate \xB7 functional";g.disagreementDies&&(p=g.disagreementDies),m.push(`<h4 class="report-subheading">${S($)}</h4>`+F(["Test",...g.groups,"All"],y,{className:"compact"}))}p&&m.push(`<p class="report-note">${p.toLocaleString()} dies judged differently by limits and tester flag.</p>`),o.push(`<h3 class="report-subheading">${S(d.label)}</h3>${m.join(`
|
|
33
|
+
`)}`)}if(!o.length)return"";const r=a.length>e.length?`<p class="report-note">${a.length-e.length} further splittable field(s) not shown.</p>`:"";return v("Split Comparison",o.join(`
|
|
34
|
+
`)+r)}function q(s,l,i,n,a=[1]){const e=new Map,o=i==="hard"?"hardBinCounts":"softBinCounts";if(n?.length&&n.every(t=>t.stats[o]!==void 0))for(const t of n)for(const[c,b]of Object.entries(t.stats[o])){const m=Number(c);e.set(m,(e.get(m)??0)+b)}else for(const t of s){if(t.partial||t.edgeExcluded)continue;const c=i==="hard"?t.hbin:t.sbin;c!=null&&e.set(c,(e.get(c)??0)+1)}if(!e.size)return"";const r=[...e.values()].reduce((t,c)=>t+c,0),d=l?new Map(l.map(t=>[t.bin,t])):null,u=C(e.entries(),a).map(([t,c])=>{const b=d?.get(t);return[b?.name?`Bin ${t} \xB7 ${b.name} (${c})`:`Bin ${t} (${c})`,Y(c,r)]});return v(i==="hard"?"Hard Bin Breakdown (All Wafers)":"Soft Bin Breakdown (All Wafers)",F(["Bin","Yield"],u,{className:"compact"}))}function J(s,l,i,n,a,e){const o=new Set(e),r=new Map,d=[];for(let t=0;t<n.length;t++){const c=n[t],b=i[t]??[];if(!b.length)continue;const m=l(b.filter(j),c,a),w=new Map(b.map(p=>[L(p),p]));for(const p of m){d.includes(p.label)||d.push(p.label);const f=r.get(p.label)??{pass:0,total:0};for(const g of p.dieKeys){const y=w.get(g);if(!y||y.partial||y.edgeExcluded)continue;const $=y.hbin??y.sbin;$!=null&&(f.total++,o.has($)&&f.pass++)}r.set(p.label,f)}}if(!r.size)return"";const u=d.filter(t=>r.get(t)?.total).map(t=>{const c=r.get(t);return[t,Y(c.pass,c.total)]});return u.length?v(s,F(["Region","Yield"],u,{className:"compact"})):""}function He(s,l,i){let n;if(i?.length&&i.every(a=>a.stats.perTestStats!==void 0)){const a=new Map;for(const e of i)for(const o of e.stats.perTestStats??[]){const r=a.get(o.testNumber);r?(r.n+=o.count,r.sum+=o.mean*o.count,r.min=Math.min(r.min,o.min),r.max=Math.max(r.max,o.max)):a.set(o.testNumber,{n:o.count,sum:o.mean*o.count,min:o.min,max:o.max})}n=[...a.entries()].map(([e,o])=>({testNumber:e,min:o.min,max:o.max,mean:o.sum/o.n}))}return K(s,l,n)}function Ie(s,l,i){const n=De(i);return U(s,l,n)}const R=["lot","product","testProgram","temperature"];function Le(s){const l=new Map(R.map(e=>[e,new Set]));for(const e of s)for(const o of R){const r=e.wafer?.metadata?.[o];r!=null&&r!==""&&l.get(o).add(String(r))}const i=R.filter(e=>l.get(e).size>1);if(i.length===0)return[{label:"",items:s,varying:i}];const n=new Map,a=[];for(const e of s){const o=i.map(r=>String(e.wafer?.metadata?.[r]??"(none)")).join(" \xB7 ");n.has(o)||(n.set(o,[]),a.push(o)),n.get(o).push(e)}return a.map(e=>({label:e,items:n.get(e),varying:i}))}function X(s,l,i,n,a,e,o){const r=be(s,{perWaferSummaries:s.map(h=>h.statsSummary),...o}),d=[],u=[],t=[];for(const h of s){const D=h.dies??[];h.wafer&&(d.push(h.wafer),t.push(D)),D.length&&u.push(...D)}const c=u.some(h=>h.hbin!=null),b=u.some(h=>h.sbin!=null),m=c||b,w=r.perWafer.map(h=>h.summary.stats.yieldPercent).filter(h=>h!==null),p=w.length?w.reduce((h,D)=>h+D,0)/w.length:null,f=new Set(a);let g=0,y=0,$=0,x=0,T=0;for(const h of u){if(g++,h.edgeExcluded&&x++,h.partial&&T++,h.partial||h.edgeExcluded)continue;const D=h.hbin??h.sbin;D!=null&&(y++,f.has(D)&&$++)}const N=y-$,P=y>0?$/y*100:null,ee=[{label:"Wafers",value:String(r.stats.waferCount)},{label:"Total dies",value:String(g)},...m?[{label:"Good dies",value:String($)},{label:"Bad dies",value:String(N)}]:[],...x>0?[{label:"Edge excluded",value:String(x)}]:[],...T>0?[{label:"Partial dies",value:String(T)}]:[],...p!==null?[{label:"Mean wafer yield",value:`${p.toFixed(1)}%`}]:[],...P!==null?[{label:"Total yield",value:`${P.toFixed(1)}%`}]:[]],te=I(r.perWafer.map(h=>h.summary.wafer)),ne=v(te.lotId!==void 0?"Lot Summary":"Summary",O(ee)),ae=A(s.map(h=>({metadata:h.wafer?.metadata}))),se=v("Per-Wafer Yield",Re(r,s)),oe=Ce(s,n,a),M=r.perWafer.map(h=>h.summary),ie=m?c?q(u,l,"hard",M,a):q(u,i,"soft",M,a):"",le=m&&d.length?J("Ring Yield (All Wafers)",E,t,d,e,a):"",re=m&&d.length?J("Quadrant Yield (All Wafers)",k,t,d,e,a):"",ce=n.length?He(u,n,M):"",de=n.length?Ie(u,n,M):"",ue=n.length?V(s,n):"",fe=r.findings.length?Q(r.findings,r.stats.waferCount):"",me=Ee(r),pe=[ne,ae,se,oe,ie,le,re,ce,de,ue,fe,me].filter(Boolean).join(`
|
|
35
|
+
`);return{lotSummary:r,sections:pe}}function Z(s,l,i){return`<main class="report">
|
|
33
36
|
<header class="report-header">
|
|
34
|
-
<h1>${
|
|
35
|
-
<p class="report-subtitle">Generated ${
|
|
37
|
+
<h1>${S(s)}</h1>
|
|
38
|
+
<p class="report-subtitle">Generated ${S(i)}</p>
|
|
36
39
|
</header>
|
|
37
|
-
${
|
|
38
|
-
<p class="footer">Generated ${
|
|
39
|
-
</main>`}export function renderLotSummaryReportHtml(
|
|
40
|
+
${l}
|
|
41
|
+
<p class="footer">Generated ${S(i)}</p>
|
|
42
|
+
</main>`}export function renderLotSummaryReportHtml(s,l={}){const{items:i,hbinDefs:n,sbinDefs:a,testDefs:e=[],passBins:o=[1],ringCount:r=4,analyzeOptions:d}=s,u=new Date().toLocaleString(),t=Le(i);if(t.length===1){const{lotSummary:p,sections:f}=X(t[0].items,n,a,e,o,r,d),g=I(p.perWafer.map($=>$.summary.wafer)),y=l.title??(g.lotId!==void 0?`Lot Summary \u2014 ${g.lotId}`:`Summary \u2014 ${ve(g)}`);return`<!DOCTYPE html>
|
|
40
43
|
<html lang="en">
|
|
41
44
|
<head>
|
|
42
45
|
<meta charset="UTF-8">
|
|
43
|
-
<title>${
|
|
46
|
+
<title>${S(y)}</title>
|
|
44
47
|
<style>
|
|
45
|
-
${
|
|
48
|
+
${W()}
|
|
46
49
|
</style>
|
|
47
50
|
</head>
|
|
48
51
|
<body>
|
|
49
|
-
${
|
|
52
|
+
${Z(y,f,u)}
|
|
50
53
|
</body>
|
|
51
|
-
</html>`}const
|
|
52
|
-
${
|
|
54
|
+
</html>`}const c=l.title??"Summary",b=t.map(p=>{const{lotSummary:f,sections:g}=X(p.items,n,a,e,o,r,d),y=p.varying.includes("lot")?void 0:f.lot?.lot??f.lot?.lotId,$=y?`${p.label} \xB7 Lot ${String(y)}`:p.label;return Z(`${c} \u2014 ${$}`,g,u)}),m=`<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 ${t.length} groups by identity \u2014 shown separately below so stats are never pooled across them.</p>`,w=b.map((p,f)=>`${f===0?"":'<hr style="max-width:1080px;margin:32px auto 0;border:none;border-top:3px solid var(--report-line-strong);">'}
|
|
55
|
+
${p}`).join(`
|
|
53
56
|
`);return`<!DOCTYPE html>
|
|
54
57
|
<html lang="en">
|
|
55
58
|
<head>
|
|
56
59
|
<meta charset="UTF-8">
|
|
57
|
-
<title>${
|
|
60
|
+
<title>${S(c)}</title>
|
|
58
61
|
<style>
|
|
59
|
-
${
|
|
62
|
+
${W()}
|
|
60
63
|
</style>
|
|
61
64
|
</head>
|
|
62
65
|
<body>
|
|
63
66
|
<div class="report" style="padding-top:24px;">
|
|
64
|
-
${
|
|
67
|
+
${m}
|
|
65
68
|
</div>
|
|
66
|
-
${
|
|
69
|
+
${w}
|
|
67
70
|
</body>
|
|
68
|
-
</html>`}export{
|
|
71
|
+
</html>`}export{he as openHtmlReport};
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import{fmt as
|
|
2
|
-
<h2>${escHtml(
|
|
3
|
-
${
|
|
4
|
-
</section>`}export function renderDefinitionList(t
|
|
5
|
-
`);return`<dl class="${
|
|
1
|
+
import{fmt as n}from"../renderer/fmt.js";import{buildFacetTable as s,prettyKey as d}from"./facets.js";export function escHtml(e){return e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}export function renderSection(e,t,r=""){return`<section class="${["report-section",r].filter(Boolean).join(" ")}">
|
|
2
|
+
<h2>${escHtml(e)}</h2>
|
|
3
|
+
${t}
|
|
4
|
+
</section>`}export function renderDefinitionList(e,t="meta-list"){if(!e.length)return"";const r=e.map(o=>`<div class="definition-item"><dt>${escHtml(o.label)}</dt><dd>${escHtml(o.value)}</dd></div>`).join(`
|
|
5
|
+
`);return`<dl class="${t}">
|
|
6
6
|
${r}
|
|
7
|
-
</dl>`}export function buildMetadataRows(
|
|
8
|
-
${
|
|
7
|
+
</dl>`}export function buildMetadataRows(e){return s(e,{facetableOnly:e.length>1}).map(r=>({label:d(r.key),value:r.values.map(o=>o.value).join(", ")}))}export function renderMetadataSection(e){const t=buildMetadataRows(e);return t.length?renderSection("Wafer Info",renderDefinitionList(t)):""}export function renderMetricGrid(e){return e.length?`<dl class="metric-grid">
|
|
8
|
+
${e.map(r=>`<div class="metric">
|
|
9
9
|
<dt>${escHtml(r.label)}</dt>
|
|
10
10
|
<dd>${escHtml(r.value)}</dd>
|
|
11
11
|
${r.hint?`<span class="metric-hint">${escHtml(r.hint)}</span>`:""}
|
|
12
12
|
</div>`).join(`
|
|
13
13
|
`)}
|
|
14
|
-
</dl>`:""}export function renderTable(t,
|
|
15
|
-
`);return`<table class="${
|
|
16
|
-
<thead><tr>${
|
|
14
|
+
</dl>`:""}export function renderTable(e,t,r={}){if(!t.length)return`<p class="no-data">${escHtml(r.emptyMessage??"No data")}</p>`;const o=["report-table",r.className].filter(Boolean).join(" "),i=e.map(a=>`<th>${escHtml(a)}</th>`).join(""),p=t.map(a=>`<tr>${a.map(l=>`<td>${l}</td>`).join("")}</tr>`).join(`
|
|
15
|
+
`);return`<table class="${o}">
|
|
16
|
+
<thead><tr>${i}</tr></thead>
|
|
17
17
|
<tbody>${p}</tbody>
|
|
18
|
-
</table>`}export function renderSeverityBadge(
|
|
18
|
+
</table>`}export function renderSeverityBadge(e){const t=e.charAt(0).toUpperCase()+e.slice(1);return`<span class="badge badge-${e}">${t}</span>`}export function formatFindingDelta(e){const t=e.effect.absoluteDelta??0,r=t>0?"+":t<0?"\u2212":"";if(e.variable.kind==="yield"||e.variable.kind==="hardBin"||e.variable.kind==="softBin")return`${r}${Math.abs(t*100).toFixed(1)} pp`;if(e.variable.kind==="test"){if(e.effect.relativeDelta!==void 0&&Number.isFinite(e.effect.relativeDelta)){const o=Math.abs(e.effect.relativeDelta*100).toFixed(1);return`${r}${o}%`}return`${r}${n(Math.abs(t),e.variable.unit)}`}return`${r}${n(Math.abs(t),e.variable.unit)}`}export function formatFindingTooltip(e){return e.summary}export function formatFindingCoverage(e,t){return t!==void 0?`${e.stats.sampleSizeLeft}/${t}`:`${e.stats.sampleSizeLeft}/${e.stats.sampleSizeRight}`}export function reportStyles(){return`
|
|
19
19
|
:root {
|
|
20
20
|
--report-font: Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
21
21
|
--report-text: #1f2328;
|
|
@@ -72,6 +72,26 @@ ${t.map(r=>`<div class="metric">
|
|
|
72
72
|
font-size: 12px;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
/* Sub-headings and notes inside a section \u2014 used by the split comparison,
|
|
76
|
+
which nests one block per facet under a single section heading. */
|
|
77
|
+
.report-subheading {
|
|
78
|
+
margin: 14px 0 6px;
|
|
79
|
+
font-size: 13px;
|
|
80
|
+
font-weight: 600;
|
|
81
|
+
color: var(--report-text);
|
|
82
|
+
}
|
|
83
|
+
h4.report-subheading {
|
|
84
|
+
margin: 10px 0 4px;
|
|
85
|
+
font-size: 12px;
|
|
86
|
+
font-weight: 600;
|
|
87
|
+
color: var(--report-muted);
|
|
88
|
+
}
|
|
89
|
+
.report-note {
|
|
90
|
+
margin: 4px 0 0;
|
|
91
|
+
font-size: 11px;
|
|
92
|
+
color: var(--report-muted);
|
|
93
|
+
}
|
|
94
|
+
|
|
75
95
|
section.report-section {
|
|
76
96
|
margin-top: 18px;
|
|
77
97
|
break-inside: avoid;
|
|
@@ -186,7 +206,12 @@ ${t.map(r=>`<div class="metric">
|
|
|
186
206
|
padding-top: 3px;
|
|
187
207
|
padding-bottom: 5px;
|
|
188
208
|
color: var(--report-muted);
|
|
189
|
-
|
|
209
|
+
/* 11px, not 10px. This document prints (see @page below), where 10px is
|
|
210
|
+
about 7.5pt \u2014 too small to read on paper. The report keeps its own
|
|
211
|
+
document scale (20 title / 18 stat / 13 / 12 body / 11 label) rather than
|
|
212
|
+
the app's denser tiers; this is a print-legibility floor, not an
|
|
213
|
+
alignment to those. */
|
|
214
|
+
font-size: 11px;
|
|
190
215
|
font-weight: 600;
|
|
191
216
|
text-transform: uppercase;
|
|
192
217
|
letter-spacing: 0.08em;
|
|
@@ -230,7 +255,7 @@ ${t.map(r=>`<div class="metric">
|
|
|
230
255
|
padding: 1px 6px;
|
|
231
256
|
border: 1px solid transparent;
|
|
232
257
|
border-radius: 999px;
|
|
233
|
-
font-size:
|
|
258
|
+
font-size: 11px; /* print floor \u2014 see the table-header note above */
|
|
234
259
|
font-weight: 600;
|
|
235
260
|
line-height: 1.35;
|
|
236
261
|
letter-spacing: 0.04em;
|