@wafertools/wafermap 0.33.0 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +6 -6
- package/CHANGELOG.md +212 -0
- package/README.md +5 -5
- package/dist/packages/canvas-adapter/chartPopulation.d.ts +15 -10
- package/dist/packages/canvas-adapter/chartPopulation.js +1 -1
- package/dist/packages/canvas-adapter/charts/barPanel.d.ts +7 -0
- package/dist/packages/canvas-adapter/charts/barPanel.js +1 -1
- package/dist/packages/canvas-adapter/charts/binCluster.d.ts +7 -1
- package/dist/packages/canvas-adapter/charts/binCluster.js +1 -1
- package/dist/packages/canvas-adapter/charts/boxplot.js +1 -1
- package/dist/packages/canvas-adapter/charts/capability.js +1 -1
- package/dist/packages/canvas-adapter/charts/chartShell.d.ts +55 -3
- package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
- package/dist/packages/canvas-adapter/charts/correlation.js +1 -2
- package/dist/packages/canvas-adapter/charts/groupedBarPlot.d.ts +2 -0
- package/dist/packages/canvas-adapter/charts/groupedBarPlot.js +1 -1
- package/dist/packages/canvas-adapter/charts/histogram.d.ts +16 -0
- package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
- package/dist/packages/canvas-adapter/charts/palette.d.ts +2 -0
- package/dist/packages/canvas-adapter/charts/palette.js +1 -1
- package/dist/packages/canvas-adapter/charts/plotChart.d.ts +27 -0
- package/dist/packages/canvas-adapter/charts/plotChart.js +1 -0
- package/dist/packages/canvas-adapter/charts/pointInteractions.d.ts +45 -0
- package/dist/packages/canvas-adapter/charts/pointInteractions.js +1 -0
- package/dist/packages/canvas-adapter/charts/regionYieldDiagram.d.ts +2 -0
- package/dist/packages/canvas-adapter/charts/regionYieldDiagram.js +1 -1
- package/dist/packages/canvas-adapter/charts/scatter.d.ts +19 -0
- package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
- package/dist/packages/canvas-adapter/charts/sweep.d.ts +12 -0
- package/dist/packages/canvas-adapter/charts/sweep.js +1 -1
- package/dist/packages/canvas-adapter/charts/testPassRate.d.ts +7 -0
- package/dist/packages/canvas-adapter/charts/testPassRate.js +1 -1
- package/dist/packages/canvas-adapter/charts/trend.d.ts +11 -0
- package/dist/packages/canvas-adapter/charts/trend.js +1 -1
- package/dist/packages/canvas-adapter/confirmDialog.d.ts +13 -0
- package/dist/packages/canvas-adapter/confirmDialog.js +1 -0
- package/dist/packages/canvas-adapter/dataTab.d.ts +131 -0
- package/dist/packages/canvas-adapter/dataTab.js +1 -0
- package/dist/packages/canvas-adapter/dieList.d.ts +39 -4
- package/dist/packages/canvas-adapter/dieList.js +6 -7
- package/dist/packages/canvas-adapter/drilldown.d.ts +20 -3
- package/dist/packages/canvas-adapter/drilldown.js +1 -1
- package/dist/packages/canvas-adapter/guideWindow.d.ts +96 -0
- package/dist/packages/canvas-adapter/guideWindow.js +1 -0
- package/dist/packages/canvas-adapter/identityHeader.d.ts +16 -0
- package/dist/packages/canvas-adapter/identityHeader.js +1 -1
- package/dist/packages/canvas-adapter/index.d.ts +3 -3
- package/dist/packages/canvas-adapter/index.js +1 -1
- package/dist/packages/canvas-adapter/insightsTab.d.ts +73 -54
- package/dist/packages/canvas-adapter/insightsTab.js +1 -1
- package/dist/packages/canvas-adapter/maplessSummary.d.ts +2 -2
- package/dist/packages/canvas-adapter/plotEditor.d.ts +29 -0
- package/dist/packages/canvas-adapter/plotEditor.js +1 -0
- package/dist/packages/canvas-adapter/plotItems.d.ts +32 -0
- package/dist/packages/canvas-adapter/plotItems.js +1 -0
- package/dist/packages/canvas-adapter/plotModal.d.ts +30 -0
- package/dist/packages/canvas-adapter/plotModal.js +1 -0
- package/dist/packages/canvas-adapter/plotStore.d.ts +52 -0
- package/dist/packages/canvas-adapter/plotStore.js +1 -0
- package/dist/packages/canvas-adapter/plotTab.d.ts +30 -0
- package/dist/packages/canvas-adapter/plotTab.js +1 -0
- package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +3 -2
- package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferMap.d.ts +21 -2
- package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
- package/dist/packages/canvas-adapter/summaryPanel.d.ts +27 -8
- package/dist/packages/canvas-adapter/summaryPanel.js +2 -4
- package/dist/packages/canvas-adapter/summaryRail.d.ts +7 -0
- package/dist/packages/canvas-adapter/summaryRail.js +1 -0
- package/dist/packages/canvas-adapter/sweepEditor.d.ts +17 -0
- package/dist/packages/canvas-adapter/sweepEditor.js +1 -0
- package/dist/packages/canvas-adapter/tableExport.d.ts +22 -0
- package/dist/packages/canvas-adapter/tableExport.js +3 -0
- package/dist/packages/canvas-adapter/toolbar.d.ts +25 -97
- package/dist/packages/canvas-adapter/toolbar.js +2 -2
- package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
- package/dist/packages/canvas-adapter/userGuideHtml.js +337 -42
- package/dist/packages/canvas-adapter/version.d.ts +2 -2
- package/dist/packages/canvas-adapter/version.js +1 -1
- package/dist/packages/canvas-adapter/viewSwitch.d.ts +13 -0
- package/dist/packages/canvas-adapter/viewSwitch.js +1 -0
- package/dist/packages/canvas-adapter/virtualTable.d.ts +44 -0
- package/dist/packages/canvas-adapter/virtualTable.js +1 -0
- package/dist/packages/core/classify.d.ts +3 -2
- package/dist/packages/core/classify.js +1 -1
- package/dist/packages/core/passBins.d.ts +17 -4
- package/dist/packages/core/passBins.js +1 -1
- package/dist/packages/core/ringCount.d.ts +22 -0
- package/dist/packages/core/ringCount.js +1 -0
- package/dist/packages/core/tableCsv.d.ts +43 -0
- package/dist/packages/core/tableCsv.js +3 -0
- package/dist/packages/renderer/binColors.d.ts +6 -6
- package/dist/packages/renderer/binColors.js +1 -1
- package/dist/packages/renderer/buildView.d.ts +1 -1
- package/dist/packages/renderer/buildView.js +1 -1
- package/dist/packages/renderer/buildWaferMap.js +1 -1
- package/dist/packages/stats/analyzeWaferLot.js +1 -1
- package/dist/packages/stats/analyzeWaferMap.js +1 -1
- package/dist/packages/stats/binPareto.d.ts +1 -1
- package/dist/packages/stats/binPareto.js +1 -1
- package/dist/packages/stats/histogram.d.ts +27 -0
- package/dist/packages/stats/histogram.js +1 -1
- package/dist/packages/stats/index.d.ts +2 -0
- package/dist/packages/stats/index.js +1 -1
- package/dist/packages/stats/lotDrift.js +1 -1
- package/dist/packages/stats/lotPattern.js +1 -1
- package/dist/packages/stats/math.d.ts +9 -6
- package/dist/packages/stats/math.js +1 -1
- package/dist/packages/stats/patternClassification.d.ts +1 -1
- package/dist/packages/stats/patternClassification.js +1 -1
- package/dist/packages/stats/plotData.d.ts +258 -0
- package/dist/packages/stats/plotData.js +1 -0
- package/dist/packages/stats/plotId.d.ts +3 -0
- package/dist/packages/stats/plotId.js +1 -0
- package/dist/packages/stats/plotSpec.d.ts +119 -0
- package/dist/packages/stats/plotSpec.js +4 -0
- package/dist/packages/stats/presentation.d.ts +2 -0
- package/dist/packages/stats/presentation.js +1 -1
- package/dist/packages/stats/regions.d.ts +6 -0
- package/dist/packages/stats/regions.js +1 -1
- package/dist/packages/stats/renderSummaryReport.d.ts +8 -3
- package/dist/packages/stats/renderSummaryReport.js +15 -15
- package/dist/packages/stats/reportHtml.d.ts +1 -1
- package/dist/packages/stats/reportHtml.js +36 -29
- package/dist/packages/stats/scatter.d.ts +6 -0
- package/dist/packages/stats/scatter.js +1 -1
- package/dist/packages/stats/sweep.d.ts +37 -1
- package/dist/packages/stats/sweep.js +1 -1
- package/dist/packages/stats/sweepText.d.ts +14 -0
- package/dist/packages/stats/sweepText.js +1 -0
- package/dist/packages/stats/sweepXFromName.d.ts +14 -0
- package/dist/packages/stats/sweepXFromName.js +1 -1
- package/dist/packages/stats/synthesis.d.ts +14 -4
- package/dist/packages/stats/synthesis.js +1 -1
- package/dist/packages/stats/testPassRate.d.ts +6 -0
- package/dist/packages/stats/testPassRate.js +1 -1
- package/dist/packages/stats/trend.js +1 -1
- package/dist/packages/stats/types.d.ts +1 -1
- package/dist/packages/stats/yield.d.ts +15 -6
- package/dist/packages/stats/yield.js +1 -1
- package/llms.txt +15 -3
- package/package.json +3 -3
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export declare const WMAP_VERSION = "0.
|
|
2
|
-
export declare const WMAP_BUILD_TIME = "2026-10-
|
|
1
|
+
export declare const WMAP_VERSION = "0.34.0";
|
|
2
|
+
export declare const WMAP_BUILD_TIME = "2026-10-06T09:30:13.804Z";
|
|
3
3
|
//# sourceMappingURL=version.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const WMAP_VERSION="0.
|
|
1
|
+
export const WMAP_VERSION="0.34.0",WMAP_BUILD_TIME="2026-10-06T09:30:13.804Z";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface ViewSwitch {
|
|
2
|
+
el: HTMLDivElement;
|
|
3
|
+
/** Reflect the host's state; never calls `onChange`. */
|
|
4
|
+
setInsightsOpen(open: boolean): void;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* `mapsLabel` is "Map" for a single wafer and "Maps" for a gallery. Built as a two-tab tablist
|
|
8
|
+
* (APG Tabs): arrow keys move and activate, only the selected tab is in the Tab order.
|
|
9
|
+
*/
|
|
10
|
+
export declare function createViewSwitch(doc: Document, mapsLabel: string,
|
|
11
|
+
/** What Insights covers: "this wafer" or "this lot", so the tooltip names the population. */
|
|
12
|
+
scope: 'wafer' | 'lot', onChange: (insightsOpen: boolean) => void): ViewSwitch;
|
|
13
|
+
//# sourceMappingURL=viewSwitch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{CLR as s,FONT as m,RADIUS as w,SHADOW as f,SPACE as p,wireControlHover as A,wireTooltip as S}from"./toolbar.js";export function createViewSwitch(n,u,l,g){const i=n.createElement("div");i.setAttribute("role","tablist"),i.setAttribute("aria-label","View"),i.dataset.wmapViewSwitch="1",Object.assign(i.style,{order:"1",flexShrink:"0",display:"inline-flex",alignItems:"stretch",background:s.menuBg,border:`1px solid ${s.menuBorder}`,borderRadius:w.control,boxShadow:f.panel,overflow:"hidden"});const c=(t,e,h)=>{const r=n.createElement("button");return r.type="button",r.textContent=t,r.dataset.wmapView=e,r.setAttribute("role","tab"),Object.assign(r.style,{border:"none",cursor:"pointer",fontSize:m.body,padding:`${p.sm} ${p.lg}`,whiteSpace:"nowrap"}),A(r,"bare"),S(r,h),r.addEventListener("click",()=>{r.getAttribute("aria-selected")!=="true"&&g(e==="insights")}),r},a=c(u,"maps",l==="lot"?"The wafer maps":"The wafer map"),o=c("Insights","insights",`Charts and statistics for this ${l}`);i.append(a,o);const d=(t,e)=>{t.setAttribute("aria-selected",String(e)),t.dataset.on=String(e),t.tabIndex=e?0:-1,Object.assign(t.style,{background:e?s.bgActive:"transparent",color:e?s.iconActive:s.label,fontWeight:"600"})},b=t=>{d(a,!t),d(o,t)};return b(!1),i.addEventListener("keydown",t=>{if(t.key!=="ArrowLeft"&&t.key!=="ArrowRight")return;const e=n.activeElement===a?o:a;n.activeElement!==a&&n.activeElement!==o||(t.preventDefault(),e.focus(),e.click())}),{el:i,setInsightsOpen:b}}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export interface VirtualColumn<R> {
|
|
2
|
+
header: string;
|
|
3
|
+
/** The cell as shown. */
|
|
4
|
+
get: (row: R) => string;
|
|
5
|
+
/** What to sort by, when that is not the shown text (a number column). Omit to sort by `get`. */
|
|
6
|
+
sortKey?: (row: R) => number | string | undefined;
|
|
7
|
+
/** Right-align (numbers). */
|
|
8
|
+
numeric?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface VirtualTableOptions<R> {
|
|
11
|
+
columns: VirtualColumn<R>[];
|
|
12
|
+
rows: ArrayLike<R>;
|
|
13
|
+
ariaLabel: string;
|
|
14
|
+
ownerDocument?: Document;
|
|
15
|
+
/** Pixel height of every row. Default 24. */
|
|
16
|
+
rowHeight?: number;
|
|
17
|
+
/** Rows drawn beyond the visible window, each side. Default 10. */
|
|
18
|
+
overscan?: number;
|
|
19
|
+
/** Viewport height assumed when the element has not been laid out (tests, hidden parents). Default 480. */
|
|
20
|
+
fallbackViewportHeight?: number;
|
|
21
|
+
onRowClick?: (row: R, rowIndex: number) => void;
|
|
22
|
+
}
|
|
23
|
+
export interface VirtualTable<R> {
|
|
24
|
+
/** The scroll container; mount it in a flex column or give it a height. */
|
|
25
|
+
el: HTMLElement;
|
|
26
|
+
/** Row count currently shown. */
|
|
27
|
+
readonly length: number;
|
|
28
|
+
/**
|
|
29
|
+
* The rows in the order shown NOW (after sorting). Lazily walks a snapshot of that order and of the rows: a sort or
|
|
30
|
+
* `setRows` made while the walk is under way (a large export yields to the page between slices) does not change it,
|
|
31
|
+
* so the file holds every row once, in the order it was started in.
|
|
32
|
+
*/
|
|
33
|
+
orderedRows(): Iterable<R>;
|
|
34
|
+
/** Replace the rows, keeping the current sort. */
|
|
35
|
+
setRows(rows: ArrayLike<R>): void;
|
|
36
|
+
/** The index range currently drawn, for tests. */
|
|
37
|
+
drawnRange(): {
|
|
38
|
+
first: number;
|
|
39
|
+
last: number;
|
|
40
|
+
};
|
|
41
|
+
destroy(): void;
|
|
42
|
+
}
|
|
43
|
+
export declare function createVirtualTable<R>(opts: VirtualTableOptions<R>): VirtualTable<R>;
|
|
44
|
+
//# sourceMappingURL=virtualTable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{ensureStylesInjected as U}from"./dieList.js";import{CLR as _,RADIUS as K}from"./toolbar.js";const X=64,G=260,J=7.4;export function createVirtualTable(h){const l=h.ownerDocument??document;U(l);const b=h.rowHeight??24,L=h.overscan??10,w=h.columns;let c=h.rows,g=null,C=-1,x=1,M=0,k=-1;const i=l.createElement("div");Object.assign(i.style,{overflow:"auto",flex:"1",minHeight:"0",minWidth:"0",border:`1px solid ${_.menuBorder}`,borderRadius:K.control}),i.tabIndex=0,i.setAttribute("role","region"),i.setAttribute("aria-label",h.ariaLabel);const d=l.createElement("table");d.className="wmap-dielist-table",d.setAttribute("aria-label",h.ariaLabel),d.style.tableLayout="fixed";const N=l.createElement("colgroup"),j=Math.min(c.length,50);let H=0;w.forEach(t=>{let n=t.header.length+2;for(let o=0;o<j;o++)n=Math.max(n,t.get(c[o]).length);const e=Math.max(X,Math.min(G,Math.round(n*J+20)));H+=e;const a=l.createElement("col");a.style.width=`${e}px`,N.appendChild(a)}),d.style.width=`${H}px`,d.style.minWidth="100%",d.appendChild(N);const R=l.createElement("thead"),O=l.createElement("tr"),S=[];w.forEach((t,n)=>{const e=l.createElement("th");e.className="wmap-dielist-th",e.scope="col",e.textContent=t.header,e.style.cursor="pointer",e.style.overflow="hidden",e.style.textOverflow="ellipsis",t.numeric&&(e.style.textAlign="right"),e.setAttribute("aria-sort","none"),e.tabIndex=0;const a=()=>B(n);e.addEventListener("click",a),e.addEventListener("keydown",o=>{(o.key==="Enter"||o.key===" ")&&(o.preventDefault(),a())}),S.push(e),O.appendChild(e)}),R.appendChild(O),d.appendChild(R);const $=l.createElement("tbody");d.appendChild($),i.appendChild(d);const D=()=>{const t=l.createElement("tr");t.setAttribute("aria-hidden","true");const n=l.createElement("td");return n.colSpan=w.length,n.style.padding="0",n.style.border="none",t.appendChild(n),t},F=D(),I=D(),q=t=>c[g?g[t]:t];function z(){return i.clientHeight>0?i.clientHeight:h.fallbackViewportHeight??480}function E(t=!1){const n=c.length,e=R.offsetHeight||b,a=Math.max(0,i.scrollTop-e),o=Math.max(0,Math.floor(a/b)-L),r=Math.min(n-1,Math.ceil((a+z())/b)+L);if(!t&&o===M&&r===k)return;M=o,k=r;const p=l.createDocumentFragment();F.firstElementChild.style.height=`${o*b}px`,I.firstElementChild.style.height=`${Math.max(0,n-1-r)*b}px`,p.appendChild(F);for(let s=o;s<=r;s++){const u=q(s),m=l.createElement("tr");m.style.height=`${b}px`,m.setAttribute("aria-rowindex",String(s+2));for(const y of w){const f=l.createElement("td");f.className="wmap-dielist-td",f.textContent=y.get(u),f.style.overflow="hidden",f.style.textOverflow="ellipsis",y.numeric&&(f.style.textAlign="right"),m.appendChild(f)}if(h.onRowClick){m.style.cursor="pointer";const y=g?g[s]:s;m.addEventListener("click",()=>h.onRowClick(u,y))}p.appendChild(m)}p.appendChild(I),$.replaceChildren(p)}function B(t){C===t?x=x===1?-1:1:(C=t,x=1),S.forEach((n,e)=>n.setAttribute("aria-sort",e!==C?"none":x===1?"ascending":"descending")),S.forEach((n,e)=>{n.textContent=w[e].header+(e===C?x===1?" \u25B2":" \u25BC":"")}),T(),i.scrollTop=0,E(!0)}function T(){const t=c.length;if(C<0||t===0){g=null;return}const n=w[C],e=n.sortKey??n.get,a=new Array(t);for(let r=0;r<t;r++)a[r]=e(c[r]);const o=new Uint32Array(t);for(let r=0;r<t;r++)o[r]=r;o.sort((r,p)=>{const s=a[r],u=a[p],m=s===void 0||s===""||typeof s=="number"&&Number.isNaN(s),y=u===void 0||u===""||typeof u=="number"&&Number.isNaN(u);if(m||y)return m===y?r-p:m?1:-1;const f=typeof s=="number"&&typeof u=="number"?s-u:String(s).localeCompare(String(u),void 0,{numeric:!0});return f!==0?f*x:r-p}),g=o}let v=0;const A=l.defaultView,V=()=>{v||(A&&typeof A.requestAnimationFrame=="function"?v=A.requestAnimationFrame(()=>{v=0,E()}):E())};i.addEventListener("scroll",V);const W=typeof ResizeObserver<"u"?new ResizeObserver(()=>E()):null;return W?.observe(i),d.setAttribute("aria-rowcount",String(c.length+1)),E(!0),{el:i,get length(){return c.length},orderedRows(){const t=c,n=g;return(function*(){for(let e=0;e<t.length;e++)yield t[n?n[e]:e]})()},setRows(t){c=t,d.setAttribute("aria-rowcount",String(c.length+1)),T(),i.scrollTop=0,E(!0)},drawnRange:()=>({first:M,last:k}),destroy(){i.removeEventListener("scroll",V),W?.disconnect(),v&&A&&A.cancelAnimationFrame(v),i.remove()}}}
|
|
@@ -6,7 +6,8 @@ export interface DieClassification {
|
|
|
6
6
|
quadrant: Quadrant;
|
|
7
7
|
}
|
|
8
8
|
export interface ClassifyOptions {
|
|
9
|
-
ringCount
|
|
9
|
+
/** The map's own ring count (`WaferMapResult.ringCount`). Required: there is no default ring count to classify by. */
|
|
10
|
+
ringCount: number;
|
|
10
11
|
}
|
|
11
12
|
/**
|
|
12
13
|
* Classify a die by its radial ring (1 = innermost) and physical wafer quadrant.
|
|
@@ -22,7 +23,7 @@ export interface ClassifyOptions {
|
|
|
22
23
|
* first (region builders only ever classify positioned dies; an unpositioned
|
|
23
24
|
* die has no ring/quadrant by definition).
|
|
24
25
|
*/
|
|
25
|
-
export declare function classifyDie(die: PositionedDie, wafer: Wafer, options
|
|
26
|
+
export declare function classifyDie(die: PositionedDie, wafer: Wafer, options: ClassifyOptions): DieClassification;
|
|
26
27
|
/** Human-readable label for a ring index (1-based) given a total ring count. */
|
|
27
28
|
export declare function getRingLabel(ring: number, ringCount: number): string;
|
|
28
29
|
//# sourceMappingURL=classify.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export function classifyDie(t,e,s
|
|
1
|
+
import{requireRingCount as u}from"./ringCount.js";export function classifyDie(t,e,s){const o=Math.max(1,u(s,"classifyDie")),n=t.physX-e.center.x,r=t.physY-e.center.y,a=Math.sqrt(n*n+r*r)/e.radius,c=Math.min(o,Math.max(1,Math.floor(a*o)+1));let i;return n>=0&&r>=0?i="NE":n<0&&r>=0?i="NW":n<0&&r<0?i="SW":i="SE",{ring:c,quadrant:i}}export function getRingLabel(t,e){return e===1?"Full Wafer":t===1?"Ring 1 (core)":t===e?`Ring ${t} (edge)`:`Ring ${t}`}
|
|
@@ -12,11 +12,24 @@
|
|
|
12
12
|
*
|
|
13
13
|
* Deliberately internal (not re-exported from `core/index.ts`).
|
|
14
14
|
*/
|
|
15
|
-
/** The industry convention, applied ONLY to input that states no pass bins. */
|
|
16
|
-
export declare const INPUT_DEFAULT_PASS_BINS: readonly number[];
|
|
17
15
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
16
|
+
* The pass bins of a build: what the input states, or — only when it states
|
|
17
|
+
* none — the industry convention (bin 1). Called by `buildWaferMap` and nothing
|
|
18
|
+
* else; from there the value travels on the result.
|
|
19
|
+
*/
|
|
20
|
+
export declare function normalizePassBins(stated: readonly number[] | undefined): number[];
|
|
21
|
+
/**
|
|
22
|
+
* The pass bins a source carries, or an error naming where they were missing.
|
|
23
|
+
* Downstream of the build there is no default to fall back on: a surface that
|
|
24
|
+
* cannot say which bins pass must not state a yield.
|
|
25
|
+
*/
|
|
26
|
+
export declare function requirePassBins(source: {
|
|
27
|
+
passBins?: readonly number[];
|
|
28
|
+
} | null | undefined, where: string): readonly number[];
|
|
29
|
+
/**
|
|
30
|
+
* One wafer's pass bins: its own (carried from `buildWaferMap`), else
|
|
31
|
+
* `fallback` — a value the CALLER holds for items built without any (a report
|
|
32
|
+
* given `passBins` for hand-built maps) — else an error. Never a built-in default.
|
|
20
33
|
*/
|
|
21
34
|
export declare function itemPassBins(item: {
|
|
22
35
|
passBins?: readonly number[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
const r=[1];export function normalizePassBins(s){return[...s??r]}export function requirePassBins(s,n){const e=s?.passBins;if(!e)throw new Error(`${n}: no pass bins (WaferMapResult.passBins); a map not built by buildWaferMap must state them.`);return e}export function itemPassBins(s,n){return s?.passBins??n??requirePassBins(void 0,"itemPassBins")}export function sameBins(s,n){const e=new Set(s),t=new Set(n);return e.size===t.size&&[...e].every(i=>t.has(i))}export function commonPassBins(s){let n;for(const e of s){if(e===void 0)return;if(n===void 0)n=e;else if(!sameBins(n,e))return}return n}export function passBinsLabel(s){const n=[];for(const t of s)n.some(i=>sameBins(i,t))||n.push(t);const e=t=>t.length===0?"no bins":t.length===1?`bin ${t[0]}`:`bins ${t.join(", ")}`;return n.length===0?"no wafers":n.length===1?e(n[0]):`per wafer: ${n.map(e).join(" \xB7 ")}`}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ring count a map's rings, ring findings, ring yields and ring plots all use — and the ONE place its default lives.
|
|
3
|
+
*
|
|
4
|
+
* Same rule as pass bins (core/passBins.ts): the default applies where data ENTERS the library, `buildWaferMap`
|
|
5
|
+
* (`WaferMapInput.ringCount`, default 4), and from there the value travels on the result (`WaferMapResult.ringCount`).
|
|
6
|
+
* Everywhere else it is read, never replaced: a surface that fell back to 4 for a wafer built with 6 rings would draw one
|
|
7
|
+
* set of rings and report findings for another. `scripts/check-pass-bin-defaults.mjs` and `tests/passBinsDefault.test.mjs`
|
|
8
|
+
* hold this too.
|
|
9
|
+
*
|
|
10
|
+
* Deliberately internal (not re-exported from `core/index.ts`).
|
|
11
|
+
*/
|
|
12
|
+
/** The ring count of an input that states none. Called by `buildWaferMap` and nothing else. */
|
|
13
|
+
export declare function defaultRingCount(): number;
|
|
14
|
+
/** The ring count a source carries, or an error naming where it was missing. There is no default to fall back on. */
|
|
15
|
+
export declare function requireRingCount(source: {
|
|
16
|
+
ringCount?: number;
|
|
17
|
+
} | null | undefined, where: string): number;
|
|
18
|
+
/** One item's ring count: its own, else `fallback` — a value the CALLER holds — else an error. Never a built-in default. */
|
|
19
|
+
export declare function itemRingCount(item: {
|
|
20
|
+
ringCount?: number;
|
|
21
|
+
} | null | undefined, fallback?: number): number;
|
|
22
|
+
//# sourceMappingURL=ringCount.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const u=4;export function defaultRingCount(){return 4}export function requireRingCount(n,t){const r=n?.ringCount;if(typeof r!="number"||!Number.isFinite(r))throw new Error(`${t}: no ring count (WaferMapResult.ringCount); a map not built by buildWaferMap must state it.`);return r}export function itemRingCount(n,t){return n?.ringCount??t??requireRingCount(void 0,"itemRingCount")}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** A cell: a number is written by {@link fileNumber}, a string is escaped by
|
|
2
|
+
* `csvField`, and null/undefined/non-finite are an empty cell. */
|
|
3
|
+
export type CsvCell = string | number | null | undefined;
|
|
4
|
+
/** One column: its header and how to read it from a row. */
|
|
5
|
+
export interface CsvColumn<R> {
|
|
6
|
+
header: string;
|
|
7
|
+
get: (row: R) => CsvCell;
|
|
8
|
+
}
|
|
9
|
+
/** Significant digits written for a number: a double's reliable precision. */
|
|
10
|
+
export declare const FILE_NUMBER_DIGITS = 15;
|
|
11
|
+
/**
|
|
12
|
+
* A number as it goes into a file, in plain or exponent notation (`0.000123`,
|
|
13
|
+
* `1.5e-7`) that every consumer parses. Non-finite → empty.
|
|
14
|
+
*
|
|
15
|
+
* - **A whole number** is written as is.
|
|
16
|
+
* - **A single-precision reading** (an STDF `R*4`, which wmap keeps as `f32`)
|
|
17
|
+
* is written as the shortest decimal that is still that same `f32`: `0.5123457`,
|
|
18
|
+
* not the 17-digit expansion of the double it was widened to
|
|
19
|
+
* (`0.51234567165374756`), which claims precision the tester never recorded and
|
|
20
|
+
* doubles the size of the file.
|
|
21
|
+
* - **Anything else** keeps {@link FILE_NUMBER_DIGITS} significant digits, which
|
|
22
|
+
* drops float noise (`0.1 + 0.2` → `0.3`) and nothing a measurement carried.
|
|
23
|
+
*/
|
|
24
|
+
export declare function fileNumber(n: number): string;
|
|
25
|
+
/** One cell as CSV text, escaped. */
|
|
26
|
+
export declare function csvCell(value: CsvCell): string;
|
|
27
|
+
/** One CSV line from cells, without a line ending. */
|
|
28
|
+
export declare function csvLine(cells: readonly CsvCell[]): string;
|
|
29
|
+
/** A header row and one line per row, joined with `\n` (no trailing newline —
|
|
30
|
+
* the format every export here has always used). */
|
|
31
|
+
export declare function tableToCsv<R>(columns: readonly CsvColumn<R>[], rows: Iterable<R>): string;
|
|
32
|
+
/** The header line, then one line per row, as they are produced. The unit a
|
|
33
|
+
* streaming writer works in: it can stop between any two lines, which a joined
|
|
34
|
+
* string cannot. */
|
|
35
|
+
export declare function csvLines<R>(columns: readonly CsvColumn<R>[], rows: Iterable<R>): Generator<string>;
|
|
36
|
+
/**
|
|
37
|
+
* The same table as tab-separated text, for the clipboard: what a spreadsheet or
|
|
38
|
+
* JMP pastes into cells. Numbers are written as in a file; a text cell has its
|
|
39
|
+
* tabs and line breaks turned into spaces, since either would start a new cell
|
|
40
|
+
* or row. No quoting — a paste does not parse it.
|
|
41
|
+
*/
|
|
42
|
+
export declare function tableToTsv<R>(columns: readonly CsvColumn<R>[], rows: Iterable<R>): string;
|
|
43
|
+
//# sourceMappingURL=tableCsv.d.ts.map
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{csvField as f}from"./utils.js";export const FILE_NUMBER_DIGITS=15;export function fileNumber(r){return Number.isFinite(r)?Number.isInteger(r)&&Math.abs(r)<1e15?String(r):Math.fround(r)===r?u(r):String(Number(r.toPrecision(FILE_NUMBER_DIGITS))):""}function u(r){const o=Math.floor(Math.log10(Math.abs(r)));for(let t=7;t<=9;t++){const n=t-1-o;if(n<0||n>22)break;const e=10**n,i=Math.round(r*e)/e;if(Math.fround(i)===r)return String(i)}for(let t=7;t<=9;t++){const n=r.toPrecision(t);if(Math.fround(Number(n))===r)return String(Number(n))}return String(Number(r.toPrecision(FILE_NUMBER_DIGITS)))}export function csvCell(r){return r==null?"":typeof r=="number"?fileNumber(r):f(r)}export function csvLine(r){return r.map(csvCell).join(",")}export function tableToCsv(r,o){return Array.from(csvLines(r,o)).join(`
|
|
2
|
+
`)}export function*csvLines(r,o){yield csvLine(r.map(t=>t.header));for(const t of o)yield csvLine(r.map(n=>n.get(t)))}export function tableToTsv(r,o){const t=e=>e==null?"":typeof e=="number"?fileNumber(e):e.replace(/[\t\r\n]+/g," "),n=[r.map(e=>t(e.header)).join(" ")];for(const e of o)n.push(r.map(i=>t(i.get(e))).join(" "));return n.join(`
|
|
3
|
+
`)}
|
|
@@ -34,8 +34,8 @@ export interface BinColors {
|
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
36
|
export interface BinColorOptions {
|
|
37
|
-
/** Which bins pass
|
|
38
|
-
passBins
|
|
37
|
+
/** Which bins pass: the map's `passBins`. Required (no default); must match the `passBins` behind the yield figure. */
|
|
38
|
+
passBins: readonly number[];
|
|
39
39
|
/** Registered bin palette name (`registerBinColorScheme`). Default `'default'`. */
|
|
40
40
|
binColorScheme?: string;
|
|
41
41
|
hbinDefs?: readonly BinDef[];
|
|
@@ -82,7 +82,7 @@ export interface BinColorOptions {
|
|
|
82
82
|
* colours that bin on every card, and `shared` describes the whole gallery.
|
|
83
83
|
* A lone map resolves over its own dies.
|
|
84
84
|
*/
|
|
85
|
-
export declare function resolveBinColors(dies: Iterable<Die>, options
|
|
85
|
+
export declare function resolveBinColors(dies: Iterable<Die>, options: BinColorOptions): BinColors;
|
|
86
86
|
/** The inputs a built map carries for its bin colours. A `WaferMapResult` is one. */
|
|
87
87
|
export interface BinColorSource {
|
|
88
88
|
dies: Iterable<Die>;
|
|
@@ -124,8 +124,8 @@ export declare function mergeBinDefs(lists: Iterable<readonly BinDef[] | undefin
|
|
|
124
124
|
/** One wafer's dies and the pass bins that wafer was built with. */
|
|
125
125
|
export interface BinPassGroup {
|
|
126
126
|
dies: Iterable<Die>;
|
|
127
|
-
/**
|
|
128
|
-
passBins
|
|
127
|
+
/** Required: `WaferMapResult.passBins`. There is no default to judge a wafer by instead. */
|
|
128
|
+
passBins: readonly number[];
|
|
129
129
|
}
|
|
130
130
|
/**
|
|
131
131
|
* `resolveBinColors` over several wafers that may carry DIFFERENT pass bins — a
|
|
@@ -147,7 +147,7 @@ export declare function resolveBinColorsByWafer(groups: Iterable<BinPassGroup>,
|
|
|
147
147
|
* caller that has dies but no resolved assignment (the summary report). The same
|
|
148
148
|
* tally `resolveBinColors` uses, so the two cannot disagree about a verdict.
|
|
149
149
|
*/
|
|
150
|
-
export declare function binPassSets(dies: Iterable<Die>, passBins
|
|
150
|
+
export declare function binPassSets(dies: Iterable<Die>, passBins: readonly number[]): BinColors['pass'];
|
|
151
151
|
/** `binPassSets` over wafers that each carry their own pass bins. */
|
|
152
152
|
export declare function binPassSetsByWafer(groups: Iterable<BinPassGroup>): BinColors['pass'];
|
|
153
153
|
/** The `pass-bins-mixed` advisory for `resolveBinColorsByWafer`'s `mixedHardBins`, or null. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{diePassStatus as
|
|
1
|
+
import{diePassStatus as x}from"../core/dies.js";import{itemPassBins as C,requirePassBins as u}from"../core/passBins.js";import{getBinColorScheme as S}from"./colorSchemes.js";import{arrayEqual as p,mapEqual as b,setEqual as B}from"../core/utils.js";export function resolveBinColors(s,e){return resolveBinColorsByWafer([{dies:s,passBins:u(e,"resolveBinColors")}],e).colors}export function binColorsForMaps(s,e={}){return resolveBinColorsForMaps(Array.isArray(s)?s:[s],e).colors}export function resolveBinColorsForMaps(s,e={}){return resolveBinColorsByWafer(s.map(n=>({dies:n.dies,passBins:C(n)})),{...e,hbinDefs:mergeBinDefs(s.map(n=>n.hbinDefs)),sbinDefs:mergeBinDefs(s.map(n=>n.sbinDefs))})}export function mergeBinDefs(s){const e=new Set,n=[];for(const a of s)for(const r of a??[])e.has(r.bin)||(e.add(r.bin),n.push(r));return n}export function resolveBinColorsByWafer(s,e={}){const n=S(e.binColorScheme),a=e.useDefinedBinColors??!0,r=d(s),f=w(r.hardBins,i=>r.pass.hard.has(i),n,a?e.hbinDefs:void 0,!1),t=w(r.softBins,i=>r.pass.soft.has(i),n,a?e.sbinDefs:void 0,!0);return{colors:{hard:f.colors,soft:t.colors,shared:{hard:f.shared,soft:t.shared},pass:r.pass},mixedHardBins:r.mixedHardBins}}export function binPassSets(s,e){return d([{dies:s,passBins:e}]).pass}export function binPassSetsByWafer(s){return d(s).pass}function d(s){const e=new Map,n=new Map,a=new Set;for(const t of s){const i=new Set(u(t,"resolveBinColorsByWafer"));for(const l of t.dies){const o=x(l,i)===!0;l.hbin!=null&&(e.set(l.hbin,(e.get(l.hbin)??!0)&&o),o&&a.add(l.hbin)),l.sbin!=null&&n.set(l.sbin,(n.get(l.sbin)??!0)&&o)}}const r=t=>new Set([...t].filter(([,i])=>i).map(([i])=>i)),f={hard:r(e),soft:r(n)};return{hardBins:[...e.keys()],softBins:[...n.keys()],pass:f,mixedHardBins:[...a].filter(t=>!f.hard.has(t)).sort((t,i)=>t-i)}}function g(s){return s.length>8?`${s.slice(0,8).join(", ")} and ${s.length-8} more`:s.join(", ")}export function mixedPassBinsWarning(s){if(!s.length)return null;const e=s.length===1;return{code:"pass-bins-mixed",severity:"warning",message:`Hard ${e?"bin":"bins"} ${g(s)} ${e?"passes":"pass"} on some wafers and ${e?"fails":"fail"} on others: these wafers were built with different pass bins. Each wafer's own yield and die verdicts are correct, but a bin has one colour and one legend row, so ${e?"it is":"they are"} shown as failing there.`}}export function binColorsCover(s,e){if(!s.pass)return!1;for(const n of e)if(n.hbin!=null&&!s.hard.has(n.hbin)||n.sbin!=null&&!s.soft.has(n.sbin))return!1;return!0}export function binColorWarning(s,e){const n=e==="softBin"?"soft":e==="hardBin"?"hard":null;if(!n)return null;const a=s.shared[n];if(!a.length)return null;const r=g(a);return{code:"bin-colors-shared",severity:"warning",message:`${n==="hard"?"Hard":"Soft"} bins ${r} are drawn in a colour another bin also has, so colour alone cannot tell them apart. There are more bins than the colour scheme has distinct colours (or a bin definition repeats one). Use the legend, the tooltip, or highlight one bin at a time.`}}const y=1,v=2;function m(s,e,n,a){const r=s.length,f=a?Math.floor(r/2):0;return s[((Math.floor(e)-n+f)%r+r)%r]}function w(s,e,n,a,r){const f=new Map;for(const o of a??[])o.color&&f.set(o.bin,o.color);const t=new Map;for(const o of s)t.set(o,f.get(o)??(e(o)?m(n.pass,o,y,r):m(n.fail,o,v,r)));const i=new Map;for(const[o,h]of t){const c=h.toLowerCase();i.set(c,[...i.get(c)??[],o])}const l=[...i.values()].filter(o=>o.length>1).flat().sort((o,h)=>o-h);return{colors:t,shared:l}}export function binColorsEqual(s,e){return s===e?!0:!s||!e?!1:b(s.hard,e.hard)&&b(s.soft,e.soft)&&p(s.shared.hard,e.shared.hard)&&p(s.shared.soft,e.shared.soft)&&B(s.pass.hard,e.pass.hard)&&B(s.pass.soft,e.pass.soft)}
|
|
@@ -385,7 +385,7 @@ export interface ViewOptions {
|
|
|
385
385
|
*/
|
|
386
386
|
colorbarRangeMode?: 'spec' | 'data';
|
|
387
387
|
/**
|
|
388
|
-
* Which bins count as passing.
|
|
388
|
+
* Which bins count as passing. Required — there is no default; a view built without them is refused.
|
|
389
389
|
*
|
|
390
390
|
* Sets `ViewRect.binFail` and decides which bins take the palette's pass
|
|
391
391
|
* colours (see `resolveBinColors`) — never the bin number. `buildWaferMap` and
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{hasPosition as pe}from"../core/dies.js";import{dieValueEntries as ge,dieVerdictEntries as ye,recordedVerdict as be,testsPresent as _t}from"../core/dieTable.js";import{getReticleCell as xe}from"../core/reticle.js";import{metadataCategoricalValue as Me,metadataDisplayValue as Se}from"../core/metadata.js";import{affineRotation as bt,affineMirror as Ot,affineCompose as st,affinePoint as G,affineVector as ve,affineSwapsAxes as ke}from"../core/transforms.js";import{contrastTextColor as Q,SPEC_PASS_FILL as xt,SPEC_FAIL_LOW as Be,SPEC_FAIL_HIGH as Mt}from"./colorMap.js";import{resolveValueColorFn as $e}from"./colorSchemes.js";import{resolveBinColors as Ie,binColorsCover as Te}from"./binColors.js";import{NO_DATA_FILL as z}from"./colorMap.js";import{diePassStatus as De}from"../core/dies.js";import{compactView as Fe}from"./compactView.js";import{buildXYIndicator as Ve}from"./xyIndicator.js";import{getDieTestValue as L,getTestPassStatus as U,isParametricTest as St}from"./buildWaferMap.js";import{classifySpec as ot}from"./spec.js";export{ot as classifySpec};import{fmt as vt,fmtColorbarAxis as kt,fmtAggregationMethod as wt}from"./fmt.js";import{metadataValueColor as Ce}from"./colorMap.js";import{asList as Kt,clamp01 as Wt,compareNatural as Le,escHtml as R}from"../core/utils.js";import{prettyKey as qt}from"../core/utils.js";import{testLabel as rt,markedTestLabel as lt,derivedTestNote as zt,isDerivedTest as Re,DERIVED_MARK as Pe,DERIVED_KEY as Ne}from"./testLabel.js";export function resolveTestNumber(t,e){return e?.length?{testNumber:(findTestDef(e,t)??e[0]).testNumber}:{testNumber:t}}export function collectMetadataValues(t,e){const n=new Set;for(const i of t){if(i.partial||i.edgeExcluded)continue;const s=ct(i,e);s!==void 0&&n.add(s)}return[...n].sort(Le)}export function findTestDef(t,e){return t?.find(n=>n.testNumber===e)}export function getUniqueTestNumbers(t){return _t(t,"values")}const Ee="#d3d6db",jt="#e8e9ea",Ae="#aab0ba";function ct(t,e){return e?Me(t.metadata?.[e]):void 0}function He(t,e,n){const{x:i,y:s}=t.center,a=bt(t.orientation,i,s),o=st(Ot(e?.x??!1,e?.y??!1,i,s),bt(t.orientation,i,s)),r=st(Ot(n?.flipX??!1,n?.flipY??!1,i,s),bt(n?.rotation??0,i,s));return{bakedToScreen:r,physicalToScreen:st(r,a),gridToScreen:st(r,o)}}function Y(t,e=!1){return{points:[t],closed:e}}function _e(t,e){const{center:n,radius:i,notch:s}=t;let a=n.x+i*Math.cos(e),o=n.y+i*Math.sin(e);if(!s)return{x:a,y:o};const r=Math.sqrt(i**2-(s.length/2)**2),d=s.length/2,y=a-n.x,b=o-n.y;return s.type==="bottom"&&b<-r?(o=n.y-r,a=n.x+Math.max(-d,Math.min(d,y))):s.type==="top"&&b>r?(o=n.y+r,a=n.x+Math.max(-d,Math.min(d,y))):s.type==="left"&&y<-r?(a=n.x-r,o=n.y+Math.max(-d,Math.min(d,b))):s.type==="right"&&y>r&&(a=n.x+r,o=n.y+Math.max(-d,Math.min(d,b))),{x:a,y:o}}function Xt(t,e){return t.map(e).join(" / ")}function Oe(t,e=256){const n=Array.from({length:e},(i,s)=>t(s/(e-1)));return i=>n[Math.min(e-1,Math.max(0,Math.round(i*(e-1))))]}function we(t,e){const n=Math.max(1,Math.min(t.width,t.height)),i=t.width/Math.max(e.length,1);return Math.max(8,Math.min(16,Math.round(Math.min(n*.55,i*1.8))))}function Ke(t,e,n){if(e?.length){const a=e.flatMap(o=>{const r=o.testNumber;if(!St(o)){const y=U(t,r,o);return y===void 0?[]:[{key:r,label:lt(o,r),value:y?"Pass":"Fail"}]}const d=L(t,r);return d===void 0?[]:[{key:r,label:lt(o,r),value:vt(d,o.unit,n),recordedFail:be(t,r)===!1}]});if(a.length)return a}const i=ge(t);if(i.length)return i.map(([a,o])=>({key:a,label:rt(void 0,a),value:vt(o,void 0,n)}));const s=ye(t);return s.length?s.map(([a,o])=>({key:a,label:rt(void 0,a),value:o?"Pass":"Fail"})):[]}export function buildHoverText(t,e,n={}){const{testDefs:i,hbinDefs:s,sbinDefs:a,fallbackFormat:o,aggrMethod:r,lotSize:d,activeTest:y,reticleConfig:b,metadataFields:B}=n,D=s?new Map(s.map(f=>[f.bin,f])):null,I=a?new Map(a.map(f=>[f.bin,f])):null,g=[R(`Die (${t.x}, ${t.y})`)];if(b&&pe(t)){const f=xe(t,b);g.push(R(`Reticle (${f.column}, ${f.row})`))}if(e==="stackedValues"){const f=L(t,0);if(f!==void 0){const u=i?.[0],h=u?lt(u,u.testNumber):"Value",S=r?` (${r})`:"";g.push(R(`${h}${S}: ${vt(f,u?.unit,o)}`));const m=zt(u);m&&g.push(`<i>${R(m)}</i>`)}}else if(e==="stackedBins"||e==="stackedSoftBins"){const f=L(t,0),u=e==="stackedSoftBins"?t.sbin:t.hbin;if(f!==void 0){const h=e==="stackedSoftBins"?I:D,S=u!==void 0?h?.get(u)?.name?`${u} \xB7 ${h.get(u).name}`:`Bin ${u}`:"Bin";let m;if(r==="percent")m=`${f.toFixed(0)}%`;else{const F=d?` (${(f/d*100).toFixed(0)}% of stacked wafers)`:"";m=`${f}${F}`}const x=r?` [${wt(r)}]`:"";g.push(R(`${S}: ${m}${x}`))}}else{const f=Ke(t,i,o);if(e==="value"&&f.length){const{testNumber:u}=resolveTestNumber(y??0,i),h=findTestDef(i,u),S=L(t,u);let m=f.findIndex(A=>A.key===u);m<0&&(m=0);const x=f[m];let F=`<b>${R(`${x.label}: ${x.value}`)}${x.recordedFail?" <i>(recorded fail)</i>":""}</b>`;if(x.key===u&&h){const A=ot(S,h);(A==="failLow"||A==="failHigh")&&(F+=" <i>(outside test limits)</i>")}g.push(F);const E=zt(findTestDef(i,x.key));E&&g.push(`<i>${R(E)}</i>`);const p=f.length-1;p>0&&g.push(`<i>+${p} more test${p===1?"":"s"}</i>`)}else if(f.length){const u=f.length;g.push(`<i>${u} test result${u===1?"":"s"} recorded</i>`)}if(t.hbin!==void 0||t.sbin!==void 0){const u=[];if(t.hbin!==void 0){const h=D?.get(t.hbin)?.name,S=h?`${t.hbin} \xB7 ${h}`:String(t.hbin);u.push(`HBin: ${S}`)}if(t.sbin!==void 0){const h=I?.get(t.sbin)?.name,S=h?`${t.sbin} \xB7 ${h}`:String(t.sbin);u.push(`SBin: ${S}`)}g.push(u.map(R).join(" \xB7 "))}}t.retestCount!==void 0&&g.push(R(`Retests: ${t.retestCount}`)),t.siteNum!==void 0&&g.push(R(`Site: ${t.siteNum}`)),t.partId!==void 0&&g.push(R(`Part ID: ${t.partId}`)),t.partial&&g.push("<i>partial die</i>"),t.probeIndex!==void 0&&g.push(R(`Probe: #${t.probeIndex}`));const k={...t.metadata??{}};for(const[f,u]of Object.entries(k)){const h=Se(u);if(h===void 0)continue;const S=B?.find(m=>m.key===f)?.label??qt(f);g.push(R(`${S}: ${h}`))}return g.join("<br>")}function Bt(t){return t?.name?lt(t,t.testNumber):void 0}function $t(t){return Re(t)?{note:`${Pe} ${Ne}`}:{}}export function buildMapTitle(t,e="engineering",n){const i=t.isLotStack&&t.lotSize!==void 0?`stacked (${t.lotSize} ${t.lotSize===1?"wafer":"wafers"})`:"stacked",s=a=>{const o=n?.length===1?n[0]:void 0;return o===void 0?`${a} Bin`:o.name?`${a} Bin ${o.bin} \xB7 ${o.name}`:`${a} Bin ${o.bin}`};switch(t.plotMode){case"hardBin":return{primary:"Hard Bin",secondary:""};case"softBin":return{primary:"Soft Bin",secondary:""};case"stackedBins":return{primary:s("Hard"),secondary:i};case"stackedSoftBins":return{primary:s("Soft"),secondary:i};case"metadata":{const a=t.metadataFields?.find(r=>r.key===t.activeMetadataKey),o=t.activeMetadataKey?qt(t.activeMetadataKey):"Metadata";return{primary:a?.label??o,secondary:""}}case"stackedValues":{const a=t.testDefs?.[0],o=t.valueRange[1]||t.valueRange[0]||0,{axisLabel:r}=kt(o,Bt(a),a?.unit,e),d=r||"Value",y=t.aggrMethod?` \xB7 ${wt(t.aggrMethod)}`:"";return{primary:`${d}${y}`,secondary:i,...$t(a)}}default:{const a=findTestDef(t.testDefs,t.activeTest),o=t.valueRange[1]||t.valueRange[0]||0,{axisLabel:r}=kt(o,Bt(a),a?.unit,e),d=r||Bt(a);if(t.passFailDisplay!=="off"){const y=a?.testNumber??t.activeTest,b=d?`${d} \xB7 #${y}`:rt(void 0,y),B=t.passFailDisplay==="spec"?"Limit pass/fail":a!==void 0&&!St(a)?"Functional pass/fail":"Tester pass/fail";return{primary:b,secondary:B,...$t(a)}}return{primary:d??rt(void 0,t.activeTest),secondary:"",...$t(a)}}}}export function generateTextOverlay(t,e,n){const{plotMode:i,colorFns:s,normalize:a,activeTest:o,valueRange:r,testDefs:d,fallbackFormat:y,passFailDisplay:b="off",activeMetadataKey:B,metadataColorMap:D,allIntegerValues:I}=n,g=findTestDef(d,o),{tickFmt:k}=kt(r[1],g?.name,g?.unit,y),f=I?h=>String(Math.round(h)):k,{testNumber:u}=resolveTestNumber(o,d);return t.flatMap((h,S)=>{let m="",x="#111111";if(i==="value"&&b==="test"){const p=U(h,u,g);if(p===void 0)return[];m=p?"P":"F",x=Q(p?xt:Mt)}else if(i==="value"){const p=L(h,u);if(p===void 0)return[];m=Xt([p],f),x=Q(s.forValue(a(p)))}else if(i==="hardBin"||i==="softBin"){const p=i==="softBin"?h.sbin:h.hbin;if(p===void 0)return[];m=String(p),x=Q(s.forBin(p))}else if(i==="metadata"){const p=ct(h,B);if(p===void 0)return[];m=p,x=Q(D?.get(m)??z)}else{const p=L(h,0);if(p===void 0)return[];m=Xt([p],f),x=Q(s.forValue(a(p)))}const F=e?e[S*2]:h.physX,E=e?e[S*2+1]:h.physY;return[{x:F,y:E,text:m,fontSize:we(h,m),color:x,align:"center"}]})}function Yt(t){return t==="top"?Math.PI/2:t==="bottom"?-Math.PI/2:t==="left"?Math.PI:0}function We(t,e,n=720){const{center:i,radius:s,notch:a}=t,o=k=>G(e,k.x,k.y);if(!(a!==void 0&&t.diameter>150)){const k=[];for(let f=0;f<=n;f++){const u=2*Math.PI*f/n;k.push(o(_e(t,u)))}return[{kind:"wafer-boundary",...Y(k,!0),lineColor:"#888888",lineWidth:1}]}const d=1.25,y=a.length,b=Yt(a.type),B=Math.atan2(y,s),D=s-d,I=[];let g=!1;for(let k=0;k<=n;k++){const f=2*Math.PI*k/n;let u=f-b;for(;u>Math.PI;)u-=2*Math.PI;for(;u<-Math.PI;)u+=2*Math.PI;if(Math.abs(u)<=B){if(!g){g=!0;const h={x:i.x+s*Math.cos(b-B),y:i.y+s*Math.sin(b-B)},S={x:i.x+D*Math.cos(b),y:i.y+D*Math.sin(b)},m={x:i.x+s*Math.cos(b+B),y:i.y+s*Math.sin(b+B)};I.push(o(h),o(S),o(m))}continue}I.push(o({x:i.x+s*Math.cos(f),y:i.y+s*Math.sin(f)}))}return[{kind:"wafer-boundary",...Y(I,!0),lineColor:"#888888",lineWidth:1}]}function qe(t,e,n,i=360){const s=[],a=Math.max(1,Math.floor(n));for(let o=1;o<a;o++){const r=t.radius*o/a,d=[];for(let y=0;y<=i;y++){const b=2*Math.PI*y/i,B={x:t.center.x+r*Math.cos(b),y:t.center.y+r*Math.sin(b)};d.push(G(e,B.x,B.y))}s.push({kind:"ring-boundary",...Y(d,!0),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5})}return s}function ze(t,e,n,i){const{x:s,y:a}=t.center,o=t.radius,r=(f,u)=>G(e,f,u),d=n-s,y=Math.sqrt(Math.max(0,o*o-d*d)),b=r(n,a-y),B=r(n,a+y),D=i-a,I=Math.sqrt(Math.max(0,o*o-D*D)),g=r(s-I,i),k=r(s+I,i);return[{kind:"quadrant-boundary",...Y([b,B]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5},{kind:"quadrant-boundary",...Y([g,k]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5}]}function je(t,e){return t.map(n=>{const i=n.width/2,s=n.height/2;return{kind:"reticle",points:[[[n.x-i,n.y-s],[n.x+i,n.y-s],[n.x+i,n.y+s],[n.x-i,n.y+s]].map(([o,r])=>G(e,o,r))],closed:!0,lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5,fill:"rgba(0,0,0,0)"}})}function Xe(t){const e=t.filter(n=>n.probeIndex!==void 0).sort((n,i)=>(n.probeIndex??0)-(i.probeIndex??0));return e.length?[{kind:"probe-path",...Y(e.map(n=>({x:n.physX,y:n.physY}))),lineColor:"rgba(220,80,0,0.55)",lineWidth:1}]:[]}function Ye(t,e,n,i,s,a,o,r,d,y,b,B,D,I,g="off",k,f,u){const h=e.width-o,S=e.height-o,m=a?S:h,x=a?h:S,F=M=>s==="softBin"?M.sbin:M.hbin,E=M=>ct(M,k);if(e.partial){t.push({x:n,y:i,width:m,height:x,fill:Ee,type:"stacked",metadata:e.metadata});return}if(e.edgeExcluded){t.push({x:n,y:i,width:m,height:x,fill:Ae,type:"stacked",metadata:e.metadata});return}if(d!==void 0&&(s==="hardBin"||s==="softBin")&&!d.has(F(e))){t.push({x:n,y:i,width:m,height:x,fill:jt,type:"hardBin",metadata:e.metadata});return}if(u!==void 0&&s==="metadata"&&!u.has(E(e))){t.push({x:n,y:i,width:m,height:x,fill:jt,type:"metadata",metadata:e.metadata});return}if(s==="value"){if(g==="test"){const K=U(e,b,I),ut=K===void 0?z:K?xt:Mt;t.push({x:n,y:i,width:m,height:x,fill:ut,type:"value",metadata:e.metadata});return}const M=L(e,b),P=ot(M,I);let w,J;M===void 0?w=z:g==="spec"?w=P==="failLow"?Be:P==="failHigh"?Mt:xt:(w=r.forValue(y(M)),(P==="failLow"||P==="failHigh")&&(J=P)),t.push({x:n,y:i,width:m,height:x,fill:w,type:"value",specMark:J,metadata:e.metadata});return}if(s==="hardBin"||s==="softBin"){const M=F(e),P=M!=null?r.forBin(M):z,w=M!=null&&De(e,D)===!1?!0:void 0;t.push({x:n,y:i,width:m,height:x,fill:P,type:s,binFail:w,metadata:e.metadata});return}if(s==="metadata"){const M=E(e),P=M!==void 0?f?.get(M)??z:z;t.push({x:n,y:i,width:m,height:x,fill:P,type:"metadata",metadata:e.metadata});return}const p=L(e,0),A=p!==void 0?r.forValue(y(p)):z;t.push({x:n,y:i,width:m,height:x,fill:A,type:"value",metadata:e.metadata})}function Ge(t,e,n){const i=t.radius;return Ve(e,i*.15,(s,a)=>({x:t.center.x+s*i*.9,y:t.center.y+a*i*.9}),n).overlays}export function buildView(t,e,n={},i){const s=n.reticles??[],{plotMode:a="value",showDieLabels:o=!1,showReticle:r=!1,showProbePath:d=!1,ringCount:y=4,showRingBoundaries:b=!1,showQuadrantBoundaries:B=!1,showXYIndicator:D=!1,binColorScheme:I="default",valueColorScheme:g="default",reverseValueScheme:k=!1,useDefinedBinColors:f=!0,binColors:u,passBins:h=[1],highlightBin:S,highlightMetadataValue:m,interactiveTransform:x,valueRange:F,metadataValueOrder:E,testDefs:p,activeTest:A=0,activeMetadataKey:M,fallbackFormat:P="engineering",aggregationMethod:w,lotSize:J,dataAxisFlip:K,isLotStack:ut=!1,logScale:Gt,colorbarRangeMode:Qt="spec",passFailDisplay:Ut}=n,It=Ut??"off",ft=i?.hbinDefs,dt=i?.sbinDefs,Tt=i?.metadataFields,Jt=Tt?.find(l=>l.key===M),Zt={x:(K?.x??!1)!==(x?.flipX??!1),y:(K?.y??!1)!==(x?.flipY??!1)},te=ft?new Map(ft.map(l=>[l.bin,l])):null,ee=dt?new Map(dt.map(l=>[l.bin,l])):null,ne=a==="softBin"?ee:te,ae=new Set(h),ht=u&&Te(u,e)?u:Ie(e,{passBins:h,binColorScheme:I,hbinDefs:ft,sbinDefs:dt,useDefinedBinColors:f}),ie=a==="softBin"?ht.soft:ht.hard,Dt={forValue:Oe($e(g,k)),forBin:l=>ie.get(l)??z};let{testNumber:H}=resolveTestNumber(A,p);if(!p?.length&&a==="value"){const l=_t(e,"values");!l.includes(H)&&l.length&&(H=l[0])}let Z;if(Array.isArray(F))Z=F;else if(F){const{testNumber:l}=resolveTestNumber(F.test,p);Z=l===H?F.range:void 0}const T=findTestDef(p,H),Ft=a==="value"&&T!==void 0&&!St(T),se=T!==void 0&&(T.limitLow!==void 0||T.limitHigh!==void 0);let O="off";a==="value"&&(Ft?O="test":It==="spec"&&se?O="spec":It==="test"&&e.some(l=>U(l,H,T)!==void 0)&&(O="test"));const Vt=O==="spec"?"spec":Qt,mt=a==="stackedValues"||a==="stackedBins"||a==="stackedSoftBins";let N,_,tt=!1;if(Z){[N,_]=Z;let l=!0,c=!1;for(const v of e){const C=mt?L(v,0):L(v,H);if(C!==void 0&&(c=!0,!Number.isInteger(C))){l=!1;break}}tt=c&&l}else{const l=!mt&&a==="value"&&Vt==="spec"&&T&&(T.limitLow!==void 0||T.limitHigh!==void 0);let c=1/0,v=-1/0,C=!0;for(const $ of e){const q=mt?L($,0):L($,H);q!==void 0&&(q<c&&(c=q),q>v&&(v=q),Number.isInteger(q)||(C=!1))}isFinite(c)||(C=!1),tt=C,l?(N=T.limitLow!==void 0?T.limitLow:isFinite(c)?c:0,_=T.limitHigh!==void 0?T.limitHigh:isFinite(v)?v:1):(N=isFinite(c)?c:0,_=isFinite(v)?v:1)}N===_&&(_=N+1);const Ct=Ft?!1:Gt??T?.logScale??!1,Lt=Ct&&N>0&&_>0,oe=Lt;let et;if(Lt){const l=Math.log10(N),c=Math.log10(_)-l;et=v=>v<=0?0:Wt((Math.log10(v)-l)/c)}else et=l=>Wt((l-N)/(_-N));const V=He(t,K,x);let Rt=null;if(t.notch){const l=Yt(t.notch.type),c=ve(V.physicalToScreen,Math.cos(l)*t.radius,Math.sin(l)*t.radius),v=Math.hypot(c.x,c.y);v>0&&(Rt={x:c.x/v,y:c.y/v})}let j=1/0;for(let l=0;l<e.length;l++){const c=e[l];c.width<j&&(j=c.width),c.height<j&&(j=c.height)}const Pt=1,re=j!==1/0?Math.min(Pt,j*.12):Pt,Nt=[],pt=[],le=V.bakedToScreen.a===1&&V.bakedToScreen.b===0&&V.bakedToScreen.c===0&&V.bakedToScreen.d===1&&V.bakedToScreen.e===0&&V.bakedToScreen.f===0;let W=null;if(!le){W=new Float64Array(e.length*2);for(let l=0;l<e.length;l++){const c=G(V.bakedToScreen,e[l].physX,e[l].physY);W[l*2]=c.x,W[l*2+1]=c.y}}const ce=ke(V.gridToScreen),ue=a==="hardBin"||a==="softBin"||a==="stackedBins"||a==="stackedSoftBins",fe=a==="softBin"||a==="stackedSoftBins";let nt=ue?new Map:void 0;const gt=a==="value"&&O==="spec"?{pass:0,failHigh:0,failLow:0}:void 0,yt=a==="value"&&O==="test"?{pass:0,fail:0}:void 0,Et=a==="metadata";let at=Et?new Map:void 0,it=null;if(Et&&M){const l=collectMetadataValues(e,M),c=E?.key===M?E.values:void 0,v=c?[...c,...l.filter(C=>!c.includes(C))]:l;it=new Map(v.map((C,$)=>[C,Jt?.values?.find(q=>q.value===C)?.color??Ce($)]))}const de=S===void 0?void 0:new Set(Kt(S)),he=m===void 0?void 0:new Set(Kt(m));for(let l=0;l<e.length;l++){const c=e[l],v=W?W[l*2]:c.physX,C=W?W[l*2+1]:c.physY;if(pt.push({x:v,y:C}),nt&&!c.partial&&!c.edgeExcluded){const $=fe?c.sbin:c.hbin;$!=null&&nt.set($,(nt.get($)??0)+1)}if(gt&&!c.partial&&!c.edgeExcluded){const $=ot(L(c,H),T);$&>[$]++}if(yt&&!c.partial&&!c.edgeExcluded){const $=U(c,H,T);$!==void 0&&yt[$?"pass":"fail"]++}if(at&&!c.partial&&!c.edgeExcluded){const $=ct(c,M);$!==void 0&&at.set($,(at.get($)??0)+1)}Ye(Nt,c,v,C,a,ce,re,Dt,de,et,H,ne,ae,T,O,M,it,he)}const At=o?generateTextOverlay(e,W,{plotMode:a,colorFns:Dt,normalize:et,activeTest:A,valueRange:[N,_],testDefs:p,fallbackFormat:P,passFailDisplay:O,activeMetadataKey:M,metadataColorMap:it,allIntegerValues:tt}):[],X=We(t,V.physicalToScreen);b&&X.push(...qe(t,V.physicalToScreen,y)),B&&X.push(...ze(t,V.physicalToScreen,t.center.x,t.center.y)),r&&X.push(...je(s,V.gridToScreen)),d&&X.push(...Xe(e)),D&&X.push(...Ge(t,V.gridToScreen,At));const me=pt.length>0?{minX:t.center.x-t.radius,maxX:t.center.x+t.radius,minY:t.center.y-t.radius,maxY:t.center.y+t.radius}:null,Ht={rectangles:Nt,hoverPoints:pt,texts:At,overlays:X,plotMode:a,binColorScheme:I,valueColorScheme:g,reverseValueScheme:k,binColors:ht,metadata:t.metadata??null,dies:e,valueRange:[N,_],allIntegerValues:tt,testDefs:p,activeTest:A,logScale:oe,logScaleRequested:Ct,aggrMethod:w,lotSize:J,axisFlip:Zt,dataAxisFlip:{x:K?.x??!1,y:K?.y??!1},gridToScreen:V.gridToScreen,rotation:((t.orientation+(x?.rotation??0))%360+360)%360,hasReticle:s.length>0,isLotStack:ut,passFailDisplay:O,colorbarRangeMode:Vt,waferCenter:t.center,waferRadius:t.radius,notchDir:Rt,binCounts:nt,metadataCounts:at,metadataColorMap:it,activeMetadataKey:M,metadataFields:Tt,specCounts:gt,passFailCounts:yt,dieBounds:me};return n.compact?Fe(Ht,n.compact,{showXYIndicator:D}):Ht}export{getDieKey}from"../core/dies.js";
|
|
1
|
+
import{requirePassBins as ge}from"../core/passBins.js";import{requireRingCount as ye}from"../core/ringCount.js";import{hasPosition as be}from"../core/dies.js";import{dieValueEntries as xe,dieVerdictEntries as Me,recordedVerdict as Se,testsPresent as Ot}from"../core/dieTable.js";import{getReticleCell as ve}from"../core/reticle.js";import{metadataCategoricalValue as Be,metadataDisplayValue as ke}from"../core/metadata.js";import{affineRotation as bt,affineMirror as wt,affineCompose as st,affinePoint as G,affineVector as $e,affineSwapsAxes as Ie}from"../core/transforms.js";import{contrastTextColor as Q,SPEC_PASS_FILL as xt,SPEC_FAIL_LOW as Te,SPEC_FAIL_HIGH as Mt}from"./colorMap.js";import{resolveValueColorFn as De}from"./colorSchemes.js";import{resolveBinColors as Ce,binColorsCover as Ve}from"./binColors.js";import{NO_DATA_FILL as z}from"./colorMap.js";import{diePassStatus as Fe}from"../core/dies.js";import{compactView as Le}from"./compactView.js";import{buildXYIndicator as Re}from"./xyIndicator.js";import{getDieTestValue as L,getTestPassStatus as U,isParametricTest as St}from"./buildWaferMap.js";import{classifySpec as ot}from"./spec.js";export{ot as classifySpec};import{fmt as vt,fmtColorbarAxis as Bt,fmtAggregationMethod as qt}from"./fmt.js";import{metadataValueColor as Pe}from"./colorMap.js";import{asList as Kt,clamp01 as Wt,compareNatural as Ne,escHtml as R}from"../core/utils.js";import{prettyKey as zt}from"../core/utils.js";import{testLabel as rt,markedTestLabel as lt,derivedTestNote as jt,isDerivedTest as Ee,DERIVED_MARK as Ae,DERIVED_KEY as He}from"./testLabel.js";export function resolveTestNumber(t,e){return e?.length?{testNumber:(findTestDef(e,t)??e[0]).testNumber}:{testNumber:t}}export function collectMetadataValues(t,e){const n=new Set;for(const i of t){if(i.partial||i.edgeExcluded)continue;const s=ct(i,e);s!==void 0&&n.add(s)}return[...n].sort(Ne)}export function findTestDef(t,e){return t?.find(n=>n.testNumber===e)}export function getUniqueTestNumbers(t){return Ot(t,"values")}const _e="#d3d6db",Xt="#e8e9ea",Oe="#aab0ba";function ct(t,e){return e?Be(t.metadata?.[e]):void 0}function we(t,e,n){const{x:i,y:s}=t.center,a=bt(t.orientation,i,s),o=st(wt(e?.x??!1,e?.y??!1,i,s),bt(t.orientation,i,s)),r=st(wt(n?.flipX??!1,n?.flipY??!1,i,s),bt(n?.rotation??0,i,s));return{bakedToScreen:r,physicalToScreen:st(r,a),gridToScreen:st(r,o)}}function Y(t,e=!1){return{points:[t],closed:e}}function qe(t,e){const{center:n,radius:i,notch:s}=t;let a=n.x+i*Math.cos(e),o=n.y+i*Math.sin(e);if(!s)return{x:a,y:o};const r=Math.sqrt(i**2-(s.length/2)**2),d=s.length/2,y=a-n.x,b=o-n.y;return s.type==="bottom"&&b<-r?(o=n.y-r,a=n.x+Math.max(-d,Math.min(d,y))):s.type==="top"&&b>r?(o=n.y+r,a=n.x+Math.max(-d,Math.min(d,y))):s.type==="left"&&y<-r?(a=n.x-r,o=n.y+Math.max(-d,Math.min(d,b))):s.type==="right"&&y>r&&(a=n.x+r,o=n.y+Math.max(-d,Math.min(d,b))),{x:a,y:o}}function Yt(t,e){return t.map(e).join(" / ")}function Ke(t,e=256){const n=Array.from({length:e},(i,s)=>t(s/(e-1)));return i=>n[Math.min(e-1,Math.max(0,Math.round(i*(e-1))))]}function We(t,e){const n=Math.max(1,Math.min(t.width,t.height)),i=t.width/Math.max(e.length,1);return Math.max(8,Math.min(16,Math.round(Math.min(n*.55,i*1.8))))}function ze(t,e,n){if(e?.length){const a=e.flatMap(o=>{const r=o.testNumber;if(!St(o)){const y=U(t,r,o);return y===void 0?[]:[{key:r,label:lt(o,r),value:y?"Pass":"Fail"}]}const d=L(t,r);return d===void 0?[]:[{key:r,label:lt(o,r),value:vt(d,o.unit,n),recordedFail:Se(t,r)===!1}]});if(a.length)return a}const i=xe(t);if(i.length)return i.map(([a,o])=>({key:a,label:rt(void 0,a),value:vt(o,void 0,n)}));const s=Me(t);return s.length?s.map(([a,o])=>({key:a,label:rt(void 0,a),value:o?"Pass":"Fail"})):[]}export function buildHoverText(t,e,n={}){const{testDefs:i,hbinDefs:s,sbinDefs:a,fallbackFormat:o,aggrMethod:r,lotSize:d,activeTest:y,reticleConfig:b,metadataFields:k}=n,D=s?new Map(s.map(f=>[f.bin,f])):null,I=a?new Map(a.map(f=>[f.bin,f])):null,g=[R(`Die (${t.x}, ${t.y})`)];if(b&&be(t)){const f=ve(t,b);g.push(R(`Reticle (${f.column}, ${f.row})`))}if(e==="stackedValues"){const f=L(t,0);if(f!==void 0){const u=i?.[0],m=u?lt(u,u.testNumber):"Value",S=r?` (${r})`:"";g.push(R(`${m}${S}: ${vt(f,u?.unit,o)}`));const h=jt(u);h&&g.push(`<i>${R(h)}</i>`)}}else if(e==="stackedBins"||e==="stackedSoftBins"){const f=L(t,0),u=e==="stackedSoftBins"?t.sbin:t.hbin;if(f!==void 0){const m=e==="stackedSoftBins"?I:D,S=u!==void 0?m?.get(u)?.name?`${u} \xB7 ${m.get(u).name}`:`Bin ${u}`:"Bin";let h;if(r==="percent")h=`${f.toFixed(0)}%`;else{const C=d?` (${(f/d*100).toFixed(0)}% of stacked wafers)`:"";h=`${f}${C}`}const x=r?` [${qt(r)}]`:"";g.push(R(`${S}: ${h}${x}`))}}else{const f=ze(t,i,o);if(e==="value"&&f.length){const{testNumber:u}=resolveTestNumber(y??0,i),m=findTestDef(i,u),S=L(t,u);let h=f.findIndex(A=>A.key===u);h<0&&(h=0);const x=f[h];let C=`<b>${R(`${x.label}: ${x.value}`)}${x.recordedFail?" <i>(recorded fail)</i>":""}</b>`;if(x.key===u&&m){const A=ot(S,m);(A==="failLow"||A==="failHigh")&&(C+=" <i>(outside test limits)</i>")}g.push(C);const E=jt(findTestDef(i,x.key));E&&g.push(`<i>${R(E)}</i>`);const p=f.length-1;p>0&&g.push(`<i>+${p} more test${p===1?"":"s"}</i>`)}else if(f.length){const u=f.length;g.push(`<i>${u} test result${u===1?"":"s"} recorded</i>`)}if(t.hbin!==void 0||t.sbin!==void 0){const u=[];if(t.hbin!==void 0){const m=D?.get(t.hbin)?.name,S=m?`${t.hbin} \xB7 ${m}`:String(t.hbin);u.push(`HBin: ${S}`)}if(t.sbin!==void 0){const m=I?.get(t.sbin)?.name,S=m?`${t.sbin} \xB7 ${m}`:String(t.sbin);u.push(`SBin: ${S}`)}g.push(u.map(R).join(" \xB7 "))}}t.retestCount!==void 0&&g.push(R(`Retests: ${t.retestCount}`)),t.siteNum!==void 0&&g.push(R(`Site: ${t.siteNum}`)),t.partId!==void 0&&g.push(R(`Part ID: ${t.partId}`)),t.partial&&g.push("<i>partial die</i>"),t.probeIndex!==void 0&&g.push(R(`Probe: #${t.probeIndex}`));const B={...t.metadata??{}};for(const[f,u]of Object.entries(B)){const m=ke(u);if(m===void 0)continue;const S=k?.find(h=>h.key===f)?.label??zt(f);g.push(R(`${S}: ${m}`))}return g.join("<br>")}function kt(t){return t?.name?lt(t,t.testNumber):void 0}function $t(t){return Ee(t)?{note:`${Ae} ${He}`}:{}}export function buildMapTitle(t,e="engineering",n){const i=t.isLotStack&&t.lotSize!==void 0?`stacked (${t.lotSize} ${t.lotSize===1?"wafer":"wafers"})`:"stacked",s=a=>{const o=n?.length===1?n[0]:void 0;return o===void 0?`${a} Bin`:o.name?`${a} Bin ${o.bin} \xB7 ${o.name}`:`${a} Bin ${o.bin}`};switch(t.plotMode){case"hardBin":return{primary:"Hard Bin",secondary:""};case"softBin":return{primary:"Soft Bin",secondary:""};case"stackedBins":return{primary:s("Hard"),secondary:i};case"stackedSoftBins":return{primary:s("Soft"),secondary:i};case"metadata":{const a=t.metadataFields?.find(r=>r.key===t.activeMetadataKey),o=t.activeMetadataKey?zt(t.activeMetadataKey):"Metadata";return{primary:a?.label??o,secondary:""}}case"stackedValues":{const a=t.testDefs?.[0],o=t.valueRange[1]||t.valueRange[0]||0,{axisLabel:r}=Bt(o,kt(a),a?.unit,e),d=r||"Value",y=t.aggrMethod?` \xB7 ${qt(t.aggrMethod)}`:"";return{primary:`${d}${y}`,secondary:i,...$t(a)}}default:{const a=findTestDef(t.testDefs,t.activeTest),o=t.valueRange[1]||t.valueRange[0]||0,{axisLabel:r}=Bt(o,kt(a),a?.unit,e),d=r||kt(a);if(t.passFailDisplay!=="off"){const y=a?.testNumber??t.activeTest,b=d?`${d} \xB7 #${y}`:rt(void 0,y),k=t.passFailDisplay==="spec"?"Limit pass/fail":a!==void 0&&!St(a)?"Functional pass/fail":"Tester pass/fail";return{primary:b,secondary:k,...$t(a)}}return{primary:d??rt(void 0,t.activeTest),secondary:"",...$t(a)}}}}export function generateTextOverlay(t,e,n){const{plotMode:i,colorFns:s,normalize:a,activeTest:o,valueRange:r,testDefs:d,fallbackFormat:y,passFailDisplay:b="off",activeMetadataKey:k,metadataColorMap:D,allIntegerValues:I}=n,g=findTestDef(d,o),{tickFmt:B}=Bt(r[1],g?.name,g?.unit,y),f=I?m=>String(Math.round(m)):B,{testNumber:u}=resolveTestNumber(o,d);return t.flatMap((m,S)=>{let h="",x="#111111";if(i==="value"&&b==="test"){const p=U(m,u,g);if(p===void 0)return[];h=p?"P":"F",x=Q(p?xt:Mt)}else if(i==="value"){const p=L(m,u);if(p===void 0)return[];h=Yt([p],f),x=Q(s.forValue(a(p)))}else if(i==="hardBin"||i==="softBin"){const p=i==="softBin"?m.sbin:m.hbin;if(p===void 0)return[];h=String(p),x=Q(s.forBin(p))}else if(i==="metadata"){const p=ct(m,k);if(p===void 0)return[];h=p,x=Q(D?.get(h)??z)}else{const p=L(m,0);if(p===void 0)return[];h=Yt([p],f),x=Q(s.forValue(a(p)))}const C=e?e[S*2]:m.physX,E=e?e[S*2+1]:m.physY;return[{x:C,y:E,text:h,fontSize:We(m,h),color:x,align:"center"}]})}function Gt(t){return t==="top"?Math.PI/2:t==="bottom"?-Math.PI/2:t==="left"?Math.PI:0}function je(t,e,n=720){const{center:i,radius:s,notch:a}=t,o=B=>G(e,B.x,B.y);if(!(a!==void 0&&t.diameter>150)){const B=[];for(let f=0;f<=n;f++){const u=2*Math.PI*f/n;B.push(o(qe(t,u)))}return[{kind:"wafer-boundary",...Y(B,!0),lineColor:"#888888",lineWidth:1}]}const d=1.25,y=a.length,b=Gt(a.type),k=Math.atan2(y,s),D=s-d,I=[];let g=!1;for(let B=0;B<=n;B++){const f=2*Math.PI*B/n;let u=f-b;for(;u>Math.PI;)u-=2*Math.PI;for(;u<-Math.PI;)u+=2*Math.PI;if(Math.abs(u)<=k){if(!g){g=!0;const m={x:i.x+s*Math.cos(b-k),y:i.y+s*Math.sin(b-k)},S={x:i.x+D*Math.cos(b),y:i.y+D*Math.sin(b)},h={x:i.x+s*Math.cos(b+k),y:i.y+s*Math.sin(b+k)};I.push(o(m),o(S),o(h))}continue}I.push(o({x:i.x+s*Math.cos(f),y:i.y+s*Math.sin(f)}))}return[{kind:"wafer-boundary",...Y(I,!0),lineColor:"#888888",lineWidth:1}]}function Xe(t,e,n,i=360){const s=[],a=Math.max(1,Math.floor(n));for(let o=1;o<a;o++){const r=t.radius*o/a,d=[];for(let y=0;y<=i;y++){const b=2*Math.PI*y/i,k={x:t.center.x+r*Math.cos(b),y:t.center.y+r*Math.sin(b)};d.push(G(e,k.x,k.y))}s.push({kind:"ring-boundary",...Y(d,!0),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5})}return s}function Ye(t,e,n,i){const{x:s,y:a}=t.center,o=t.radius,r=(f,u)=>G(e,f,u),d=n-s,y=Math.sqrt(Math.max(0,o*o-d*d)),b=r(n,a-y),k=r(n,a+y),D=i-a,I=Math.sqrt(Math.max(0,o*o-D*D)),g=r(s-I,i),B=r(s+I,i);return[{kind:"quadrant-boundary",...Y([b,k]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5},{kind:"quadrant-boundary",...Y([g,B]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5}]}function Ge(t,e){return t.map(n=>{const i=n.width/2,s=n.height/2;return{kind:"reticle",points:[[[n.x-i,n.y-s],[n.x+i,n.y-s],[n.x+i,n.y+s],[n.x-i,n.y+s]].map(([o,r])=>G(e,o,r))],closed:!0,lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5,fill:"rgba(0,0,0,0)"}})}function Qe(t){const e=t.filter(n=>n.probeIndex!==void 0).sort((n,i)=>(n.probeIndex??0)-(i.probeIndex??0));return e.length?[{kind:"probe-path",...Y(e.map(n=>({x:n.physX,y:n.physY}))),lineColor:"rgba(220,80,0,0.55)",lineWidth:1}]:[]}function Ue(t,e,n,i,s,a,o,r,d,y,b,k,D,I,g="off",B,f,u){const m=e.width-o,S=e.height-o,h=a?S:m,x=a?m:S,C=M=>s==="softBin"?M.sbin:M.hbin,E=M=>ct(M,B);if(e.partial){t.push({x:n,y:i,width:h,height:x,fill:_e,type:"stacked",metadata:e.metadata});return}if(e.edgeExcluded){t.push({x:n,y:i,width:h,height:x,fill:Oe,type:"stacked",metadata:e.metadata});return}if(d!==void 0&&(s==="hardBin"||s==="softBin")&&!d.has(C(e))){t.push({x:n,y:i,width:h,height:x,fill:Xt,type:"hardBin",metadata:e.metadata});return}if(u!==void 0&&s==="metadata"&&!u.has(E(e))){t.push({x:n,y:i,width:h,height:x,fill:Xt,type:"metadata",metadata:e.metadata});return}if(s==="value"){if(g==="test"){const q=U(e,b,I),ut=q===void 0?z:q?xt:Mt;t.push({x:n,y:i,width:h,height:x,fill:ut,type:"value",metadata:e.metadata});return}const M=L(e,b),P=ot(M,I);let w,J;M===void 0?w=z:g==="spec"?w=P==="failLow"?Te:P==="failHigh"?Mt:xt:(w=r.forValue(y(M)),(P==="failLow"||P==="failHigh")&&(J=P)),t.push({x:n,y:i,width:h,height:x,fill:w,type:"value",specMark:J,metadata:e.metadata});return}if(s==="hardBin"||s==="softBin"){const M=C(e),P=M!=null?r.forBin(M):z,w=M!=null&&Fe(e,D)===!1?!0:void 0;t.push({x:n,y:i,width:h,height:x,fill:P,type:s,binFail:w,metadata:e.metadata});return}if(s==="metadata"){const M=E(e),P=M!==void 0?f?.get(M)??z:z;t.push({x:n,y:i,width:h,height:x,fill:P,type:"metadata",metadata:e.metadata});return}const p=L(e,0),A=p!==void 0?r.forValue(y(p)):z;t.push({x:n,y:i,width:h,height:x,fill:A,type:"value",metadata:e.metadata})}function Je(t,e,n){const i=t.radius;return Re(e,i*.15,(s,a)=>({x:t.center.x+s*i*.9,y:t.center.y+a*i*.9}),n).overlays}export function buildView(t,e,n={},i){const s=n.reticles??[],{plotMode:a="value",showDieLabels:o=!1,showReticle:r=!1,showProbePath:d=!1,ringCount:y,showRingBoundaries:b=!1,showQuadrantBoundaries:k=!1,showXYIndicator:D=!1,binColorScheme:I="default",valueColorScheme:g="default",reverseValueScheme:B=!1,useDefinedBinColors:f=!0,binColors:u,passBins:m,highlightBin:S,highlightMetadataValue:h,interactiveTransform:x,valueRange:C,metadataValueOrder:E,testDefs:p,activeTest:A=0,activeMetadataKey:M,fallbackFormat:P="engineering",aggregationMethod:w,lotSize:J,dataAxisFlip:q,isLotStack:ut=!1,logScale:Qt,colorbarRangeMode:Ut="spec",passFailDisplay:Jt}=n,It=Jt??"off",ft=i?.hbinDefs,dt=i?.sbinDefs,Tt=i?.metadataFields,Zt=Tt?.find(l=>l.key===M),te={x:(q?.x??!1)!==(x?.flipX??!1),y:(q?.y??!1)!==(x?.flipY??!1)},ee=ft?new Map(ft.map(l=>[l.bin,l])):null,ne=dt?new Map(dt.map(l=>[l.bin,l])):null,ae=a==="softBin"?ne:ee,Dt=ge({passBins:m},"buildView"),ie=new Set(Dt),ht=u&&Ve(u,e)?u:Ce(e,{passBins:Dt,binColorScheme:I,hbinDefs:ft,sbinDefs:dt,useDefinedBinColors:f}),se=a==="softBin"?ht.soft:ht.hard,Ct={forValue:Ke(De(g,B)),forBin:l=>se.get(l)??z};let{testNumber:H}=resolveTestNumber(A,p);if(!p?.length&&a==="value"){const l=Ot(e,"values");!l.includes(H)&&l.length&&(H=l[0])}let Z;if(Array.isArray(C))Z=C;else if(C){const{testNumber:l}=resolveTestNumber(C.test,p);Z=l===H?C.range:void 0}const T=findTestDef(p,H),Vt=a==="value"&&T!==void 0&&!St(T),oe=T!==void 0&&(T.limitLow!==void 0||T.limitHigh!==void 0);let O="off";a==="value"&&(Vt?O="test":It==="spec"&&oe?O="spec":It==="test"&&e.some(l=>U(l,H,T)!==void 0)&&(O="test"));const Ft=O==="spec"?"spec":Ut,mt=a==="stackedValues"||a==="stackedBins"||a==="stackedSoftBins";let N,_,tt=!1;if(Z){[N,_]=Z;let l=!0,c=!1;for(const v of e){const F=mt?L(v,0):L(v,H);if(F!==void 0&&(c=!0,!Number.isInteger(F))){l=!1;break}}tt=c&&l}else{const l=!mt&&a==="value"&&Ft==="spec"&&T&&(T.limitLow!==void 0||T.limitHigh!==void 0);let c=1/0,v=-1/0,F=!0;for(const $ of e){const W=mt?L($,0):L($,H);W!==void 0&&(W<c&&(c=W),W>v&&(v=W),Number.isInteger(W)||(F=!1))}isFinite(c)||(F=!1),tt=F,l?(N=T.limitLow!==void 0?T.limitLow:isFinite(c)?c:0,_=T.limitHigh!==void 0?T.limitHigh:isFinite(v)?v:1):(N=isFinite(c)?c:0,_=isFinite(v)?v:1)}N===_&&(_=N+1);const Lt=Vt?!1:Qt??T?.logScale??!1,Rt=Lt&&N>0&&_>0,re=Rt;let et;if(Rt){const l=Math.log10(N),c=Math.log10(_)-l;et=v=>v<=0?0:Wt((Math.log10(v)-l)/c)}else et=l=>Wt((l-N)/(_-N));const V=we(t,q,x);let Pt=null;if(t.notch){const l=Gt(t.notch.type),c=$e(V.physicalToScreen,Math.cos(l)*t.radius,Math.sin(l)*t.radius),v=Math.hypot(c.x,c.y);v>0&&(Pt={x:c.x/v,y:c.y/v})}let j=1/0;for(let l=0;l<e.length;l++){const c=e[l];c.width<j&&(j=c.width),c.height<j&&(j=c.height)}const Nt=1,le=j!==1/0?Math.min(Nt,j*.12):Nt,Et=[],pt=[],ce=V.bakedToScreen.a===1&&V.bakedToScreen.b===0&&V.bakedToScreen.c===0&&V.bakedToScreen.d===1&&V.bakedToScreen.e===0&&V.bakedToScreen.f===0;let K=null;if(!ce){K=new Float64Array(e.length*2);for(let l=0;l<e.length;l++){const c=G(V.bakedToScreen,e[l].physX,e[l].physY);K[l*2]=c.x,K[l*2+1]=c.y}}const ue=Ie(V.gridToScreen),fe=a==="hardBin"||a==="softBin"||a==="stackedBins"||a==="stackedSoftBins",de=a==="softBin"||a==="stackedSoftBins";let nt=fe?new Map:void 0;const gt=a==="value"&&O==="spec"?{pass:0,failHigh:0,failLow:0}:void 0,yt=a==="value"&&O==="test"?{pass:0,fail:0}:void 0,At=a==="metadata";let at=At?new Map:void 0,it=null;if(At&&M){const l=collectMetadataValues(e,M),c=E?.key===M?E.values:void 0,v=c?[...c,...l.filter(F=>!c.includes(F))]:l;it=new Map(v.map((F,$)=>[F,Zt?.values?.find(W=>W.value===F)?.color??Pe($)]))}const he=S===void 0?void 0:new Set(Kt(S)),me=h===void 0?void 0:new Set(Kt(h));for(let l=0;l<e.length;l++){const c=e[l],v=K?K[l*2]:c.physX,F=K?K[l*2+1]:c.physY;if(pt.push({x:v,y:F}),nt&&!c.partial&&!c.edgeExcluded){const $=de?c.sbin:c.hbin;$!=null&&nt.set($,(nt.get($)??0)+1)}if(gt&&!c.partial&&!c.edgeExcluded){const $=ot(L(c,H),T);$&>[$]++}if(yt&&!c.partial&&!c.edgeExcluded){const $=U(c,H,T);$!==void 0&&yt[$?"pass":"fail"]++}if(at&&!c.partial&&!c.edgeExcluded){const $=ct(c,M);$!==void 0&&at.set($,(at.get($)??0)+1)}Ue(Et,c,v,F,a,ue,le,Ct,he,et,H,ae,ie,T,O,M,it,me)}const Ht=o?generateTextOverlay(e,K,{plotMode:a,colorFns:Ct,normalize:et,activeTest:A,valueRange:[N,_],testDefs:p,fallbackFormat:P,passFailDisplay:O,activeMetadataKey:M,metadataColorMap:it,allIntegerValues:tt}):[],X=je(t,V.physicalToScreen);b&&X.push(...Xe(t,V.physicalToScreen,ye({ringCount:y},"buildView"))),k&&X.push(...Ye(t,V.physicalToScreen,t.center.x,t.center.y)),r&&X.push(...Ge(s,V.gridToScreen)),d&&X.push(...Qe(e)),D&&X.push(...Je(t,V.gridToScreen,Ht));const pe=pt.length>0?{minX:t.center.x-t.radius,maxX:t.center.x+t.radius,minY:t.center.y-t.radius,maxY:t.center.y+t.radius}:null,_t={rectangles:Et,hoverPoints:pt,texts:Ht,overlays:X,plotMode:a,binColorScheme:I,valueColorScheme:g,reverseValueScheme:B,binColors:ht,metadata:t.metadata??null,dies:e,valueRange:[N,_],allIntegerValues:tt,testDefs:p,activeTest:A,logScale:re,logScaleRequested:Lt,aggrMethod:w,lotSize:J,axisFlip:te,dataAxisFlip:{x:q?.x??!1,y:q?.y??!1},gridToScreen:V.gridToScreen,rotation:((t.orientation+(x?.rotation??0))%360+360)%360,hasReticle:s.length>0,isLotStack:ut,passFailDisplay:O,colorbarRangeMode:Ft,waferCenter:t.center,waferRadius:t.radius,notchDir:Pt,binCounts:nt,metadataCounts:at,metadataColorMap:it,activeMetadataKey:M,metadataFields:Tt,specCounts:gt,passFailCounts:yt,dieBounds:pe};return n.compact?Le(_t,n.compact,{showXYIndicator:D}):_t}export{getDieKey}from"../core/dies.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createWafer as z}from"../core/wafer.js";import{isYieldEligibleDie as ye,getDieKey as X,hasPosition as F}from"../core/dies.js";import{applyOrientation as ee,transformDies as be,clipDiesToWafer as we}from"../core/transforms.js";import{generateDies as xe}from"../core/dies.js";import{dieHasAnyTestData as De,dieLink as G,finaliseDie as j,linkDie as ve,recordedVerdict as Me,recordedVerdictsReader as Se,tableFromRows as Ce,testValue as q,testsPresent as ke,VERDICT_FAIL as Ae,VERDICT_NONE as Oe,VERDICT_PASS as Ne}from"../core/dieTable.js";import{isDieColumns as $e,linkColumns as Re,linkRows as te,relink as Te}from"./columnarInput.js";import{affineRotation as Ee,affineMirror as We,affineCompose as Fe,affinePoint as Ie}from"../core/transforms.js";import{inferWaferFromXY as _e}from"../core/inference/wafer.js";import{resolveGridPitch as Pe}from"../core/inference/pitch.js";import{assignGridIndices as Ve}from"../core/inference/grid.js";import{generateReticleGrid as Be}from"../core/reticle.js";import{applyDerivedTests as Le}from"./derivedTests/apply.js";import{isStdfBin as Ye,isStdfCoord as ne,isStdfTestNumber as ie,isStdfSite as Xe,STDF_BIN_MAX as je,STDF_COORD_MAX as se,STDF_TEST_NUM_MAX as Ke,STDF_SITE_MAX as He}from"../core/stdf.js";import{hasAnyKey as Ue,maxOf as J,minOf as re,modeOf as ze}from"../core/utils.js";import{aggregateValues as oe,aggregateBinCounts as Ge,stackHoldsMeasurements as qe}from"../core/aggregates.js";export function isParametricTest(t){return t?.testType!=="F"}export function isBuiltMap(t){return"wafer"in t&&"dies"in t&&"dataAxisFlip"in t}function Je(t){const e=(i,a)=>({ringCount:i,ringCountWarning:{code:"analysis-option-corrected",severity:"warning",message:`ringCount=${String(t)} ${a} (using ${i}).`}});if(t===void 0)return{ringCount:4,ringCountWarning:void 0};if(typeof t!="number"||!Number.isFinite(t))return e(4,"is not a finite number");const n=Math.max(1,Math.round(t));return n===t?{ringCount:t,ringCountWarning:void 0}:e(n,"is not a whole number of at least 1")}function Qe(t,e){const n=Array.isArray(t)?{}:t,i={hbin:0,sbin:0,siteNum:0,testValues:0,testPass:0};let a;const o=(l,b)=>{i[l]++,a??=`${l} ${JSON.stringify(b)}`},s={bins:0,positions:0,values:0,sites:0,orientation:0},r=new Set;let m=0;if(e){i.testValues+=e.wrongValues,i.testPass+=e.wrongVerdicts,e.example!==void 0&&(a??=e.example),s.values+=e.nonFinite;for(const l of e.badTestNumbers)r.add(l)}const h=l=>l==null?!0:typeof l!="number"?!1:Number.isNaN(l)?(m++,!1):Ye(l)?!0:(s.bins++,!1);function d(l){const b=new Set,w=new Set;for(let O=0;O<l.length;O+=O<50?1:100){const k=l[O];if(!(k===null||typeof k!="object")){if(k.testValues&&typeof k.testValues=="object")for(const M of Object.keys(k.testValues))b.add(M);if(k.testPass&&typeof k.testPass=="object")for(const M of Object.keys(k.testPass))w.add(M)}}const g=[...b],y=[...w],A=O=>ie(Number(O));for(const O of new Set([...g,...y]))A(O)||r.add(O);const R=g.map(A),v=y.map(A),N=g.map(Number),B=y.map(Number);let I;for(let O=0;O<l.length;O++){const k=l[O];if(k===null||typeof k!="object")continue;const M=k;let Y;const _=()=>Y??={...M};M.hbin!=null&&typeof M.hbin!="number"&&o("hbin",M.hbin),M.sbin!=null&&typeof M.sbin!="number"&&o("sbin",M.sbin),h(M.hbin)||(_().hbin=void 0),h(M.sbin)||(_().sbin=void 0);const L=typeof M.x=="number"&&!ne(M.x),f=typeof M.y=="number"&&!ne(M.y);if(L||f){s.positions+=(L?1:0)+(f?1:0);const S=_();S.x=void 0,S.y=void 0}M.siteNum!=null&&typeof M.siteNum!="number"?(o("siteNum",M.siteNum),_().siteNum=void 0):typeof M.siteNum=="number"&&!Xe(M.siteNum)&&(s.sites++,_().siteNum=void 0);const C=M.testValues;if(C&&typeof C=="object"){let S;for(let $=0;$<g.length;$++){const E=g[$],W=C[N[$]];if(W!=null){if(!R[$]){S??={...C},delete S[E];continue}typeof W!="number"?(o("testValues",W),S??={...C},delete S[E]):Number.isFinite(W)||(s.values++,S??={...C},delete S[E])}}S&&(_().testValues=S)}const T=M.testPass;if(T&&typeof T=="object"){let S;for(let $=0;$<y.length;$++){const E=y[$],W=T[B[$]];if(W!=null){if(!v[$]){S??={...T},delete S[E];continue}typeof W!="boolean"&&(o("testPass",W),S??={...T},delete S[E])}}S&&(_().testPass=S)}Y&&((I??=l.slice())[O]=Y)}return I}const c={warnings:[]};if(Array.isArray(t))c.results=d(t);else{Array.isArray(n.results)&&(c.results=d(n.results)),Array.isArray(n.dies)&&(c.dies=d(n.dies));const l=n.lotStack?.results;if(Array.isArray(l)){const b=l.map(w=>Array.isArray(w)?d(w):void 0);b.some(Boolean)&&(c.stackResults=b.map((w,g)=>w??l[g]))}}if(Array.isArray(n.testDefs)){const l=n.testDefs,b=l.filter(w=>w==null||typeof w.testNumber!="number"||ie(w.testNumber));for(const w of l)b.includes(w)||r.add(String(w.testNumber));b.length!==l.length&&(c.testDefs=b)}const p=n.waferConfig?.orientation;if(typeof p=="number"){const l=Number.isFinite(p)?(p%360+360)%360:NaN;l===0||l===90||l===180||l===270?c.orientation=l:(s.orientation++,c.orientation=0)}const u=(l,b,w=`${b}s`)=>`${l} ${l===1?b:w}`,x=[];if(i.hbin&&x.push(u(i.hbin,"hard bin")),i.sbin&&x.push(u(i.sbin,"soft bin")),i.siteNum&&x.push(u(i.siteNum,"site number")),i.testValues&&x.push(u(i.testValues,"test value")),i.testPass&&x.push(u(i.testPass,"pass/fail verdict")),x.length){const l=`buildWaferMap received ${x.join(", ")} of the wrong type (for example ${a}), treated as missing. Bins, sites and test values must be numbers and verdicts true/false \u2014 a bin of "1" is not pass bin 1 \u2014 so those dies have no bin, no site, no value or no verdict, and yield and every chart leave them out. Convert them with Number() (and verdicts to booleans) and rebuild.`;console.warn(`[wafermap] ${l}`),c.warnings.push({code:"input-values-not-numbers",message:l,severity:"error"})}const D=[];if(s.bins&&D.push(`${u(s.bins,"bin")} (legal: whole numbers 0\u2013${je}) \u2014 those dies have no bin`),s.positions&&D.push(`${u(s.positions,"coordinate")} (legal: whole numbers \u2212${se} to ${se}) \u2014 those dies have no position`),r.size&&D.push(`${u(r.size,"test number")} (legal: whole numbers 0\u2013${Ke}) \u2014 those tests are left out`),s.values&&D.push(`${u(s.values,"test value")} that ${s.values===1?"is":"are"} not finite \u2014 left out`),s.sites&&D.push(`${u(s.sites,"site number")} (legal: 0\u2013${He}) \u2014 those dies have no site`),s.orientation&&D.push("a waferConfig.orientation other than 0, 90, 180 or 270 \u2014 the map is built at 0"),D.length||m){const l=[];D.length&&l.push(`buildWaferMap received values outside the STDF V4 ranges, treated as missing: ${D.join("; ")}.`),m&&l.push(`${u(m,"bin was","bins were")} NaN and ${m===1?"is":"are"} treated as no bin.`);const b=l.join(" ");console.warn(`[wafermap] ${b}`),c.warnings.push({code:"input-values-outside-stdf",message:b,severity:"warning"})}return c}function ae(t){const e=(h,d)=>h!==null&&typeof h=="object"&&h[d]!==void 0,n=Array.isArray(t)?{}:t,i=[],a=(h,d,c)=>{h&&i.push(`\`${d}\` (now \`${c}\`)`)};a(e(n,"data"),"data","results"),a(e(n,"die"),"die","dieConfig"),a(e(n,"stack"),"stack","lotStack"),a(e(n.dieConfig,"origin"),"dieConfig.origin","dieConfig.coordinateOrigin"),a(e(n.waferConfig,"flat"),"waferConfig.flat","waferConfig.notch"),a(e(n.reticleConfig,"anchor"),"reticleConfig.anchor","reticleConfig.anchorDie"),a(e(n.lotStack,"aggr"),"lotStack.aggr","lotStack.method"),a(Array.isArray(n.testDefs)&&n.testDefs.some(h=>e(h,"index")&&!e(h,"testNumber")),"TestDef.index","TestDef.testNumber");const o=e(n.lotStack,"results")?n.lotStack.results:[],s=(Array.isArray(t)?[t]:[n.results,...Array.isArray(o)?o:[]]).filter(h=>Array.isArray(h));if(a(s.some(h=>h.slice(0,100).some(d=>e(d,"values"))),"values","testValues"),!i.length)return;const r=i.length>1,m=`buildWaferMap ignored ${i.join(", ")}: ${r?"these names were":"that name was"} removed in an earlier release, so what ${r?"they":"it"} described is not on this map. Rename and rebuild.`;return console.warn(`[wafermap] ${m}`),{code:"input-field-removed",message:m,severity:"error"}}function Ze(t){if(t.results!==void 0||t.lotStack!==void 0)throw new Error("buildWaferMap: pass `layout`, `results` or `lotStack` \u2014 only one.");const e=t.waferConfig?.diameter,n=t.dieConfig?.width,i=t.dieConfig?.height;if(!(e>0&&n>0&&i>0))throw new Error("buildWaferMap: `layout` needs waferConfig.diameter and dieConfig.width and height, all positive.");const a=z({diameter:e,notch:t.waferConfig?.notch,orientation:0}),o=we(xe(a,{width:n,height:i}),a,{width:n,height:i}).filter(d=>!d.partial),{flipX:s,flipY:r}=de(t.dieConfig,{type:"center"}),{layout:m,...h}=t;return{...h,results:o.map(d=>({x:s?-d.x:d.x,y:r?-d.y:d.y})),waferConfig:{...t.waferConfig,center:{x:0,y:0}},dieConfig:{...t.dieConfig,coordinateOrigin:{type:"center"}}}}function et(t){if(!Array.isArray(t)&&"results"in t&&t.results!==void 0&&"lotStack"in t&&t.lotStack!==void 0)throw new Error("buildWaferMap: pass either `results` or `lotStack`, not both.");const e={wrongVerdicts:0,wrongValues:0,nonFinite:0,badTestNumbers:[]},n=!Array.isArray(t)&&$e(t.results)?{...t,results:Re(t.results,e).records}:t,i=Qe(n,e);return Array.isArray(n)?{results:Q(i.results??n),waferOpts:void 0,dieOpts:void 0,explicitDies:void 0,reticleOpts:void 0,lotStackOpts:void 0,passBins:[1],ringCount:4,ringCountWarning:void 0,removedFieldWarning:ae(n),inputValueWarnings:i.warnings,standardDiameters:void 0,testDefs:void 0,hbinDefs:void 0,sbinDefs:void 0,metadataFields:void 0,derivedTests:void 0,retestPolicy:"last",edgeDieYieldMode:"exclude"}:{results:Q(i.results??n.results??[]),waferOpts:i.orientation===void 0||n.waferConfig===void 0?n.waferConfig:{...n.waferConfig,orientation:i.orientation},dieOpts:n.dieConfig,explicitDies:i.dies??n.dies,reticleOpts:n.reticleConfig,lotStackOpts:n.lotStack?{...n.lotStack,results:(i.stackResults??n.lotStack.results).map(Q)}:void 0,passBins:n.passBins??[1],...Je(n.ringCount),removedFieldWarning:ae(n),inputValueWarnings:i.warnings,standardDiameters:n.standardDiameters,testDefs:(i.testDefs??n.testDefs)?.length?i.testDefs??n.testDefs:void 0,hbinDefs:n.hbinDefs,sbinDefs:n.sbinDefs,metadataFields:n.metadataFields,derivedTests:n.derivedTests?.length?n.derivedTests:void 0,retestPolicy:n.retestPolicy??"last",edgeDieYieldMode:n.edgeDieYieldMode??"exclude"}}function Q(t){return t.length>0&&G(t[0])!==void 0?t:te(t).records}function tt(t,e){return e?.coordinateOrigin?e.coordinateOrigin:{type:"center"}}function nt(t,e,n){if(e.type==="custom"&&e.offset)return{offsetX:e.offset.x,offsetY:e.offset.y};if(e.type!=="center"){let i=1/0,a=-1/0,o=1/0,s=-1/0;for(const r of t)r.x<i&&(i=r.x),r.x>a&&(a=r.x),r.y<o&&(o=r.y),r.y>s&&(s=r.y);return{offsetX:Math.round((a+i)/2),offsetY:Math.round((s+o)/2)}}return{offsetX:n.offsetX,offsetY:n.offsetY}}function it(t,e,n,i,a,o){if(o)return{colMidX:(Math.round(o.x)-e)*i,colMidY:(Math.round(o.y)-n)*a,anchored:!0};if(t.length===0)return{colMidX:0,colMidY:0,anchored:!1};let s=1/0,r=-1/0,m=1/0,h=-1/0;for(const d of t){const c=Math.round(d.x)-e,p=Math.round(d.y)-n;c<s&&(s=c),c>r&&(r=c),p<m&&(m=p),p>h&&(h=p)}return{colMidX:(s+r)/2*i,colMidY:(m+h)/2*a,anchored:!1}}function st(t,e,n){let i=1/0,a=-1/0,o=1/0,s=-1/0,r=0,m=0;for(const c of t){const p=Math.round(c.x)-e,u=Math.round(c.y)-n;r+=p,m+=u,p<i&&(i=p),p>a&&(a=p),u<o&&(o=u),u>s&&(s=u)}const h=t.length,d=(c,p,u)=>{const x=(u-p)/2;return x<=0?0:Math.abs(c/h-(p+u)/2)/x};return d(r,i,a)>.11||d(m,o,s)>.11}function de(t,e){let n=t?.xAxisDirection==="left",i=t?.yAxisDirection==="down";return(e.type==="UL"||e.type==="UR")&&(i=!0),(e.type==="LR"||e.type==="UR")&&(n=!0),{flipX:n,flipY:i}}function rt(t){const e={...t};return delete e.limitLow,delete e.limitHigh,delete e.limitLowInclusive,delete e.limitHighInclusive,delete e.specLow,delete e.specHigh,e}function ot(t,e){const{method:n,targetBin:i}=t,a=t.results.map(o=>o.filter(F));if(n==="mean"||n==="median"||n==="stddev"||n==="min"||n==="max"||n==="count"){const o=new Set((e??[]).filter(d=>!isParametricTest(d)).map(d=>d.testNumber)),s=new Set;for(const d of a)for(const c of ke(d,"values"))o.has(c)||s.add(c);if(s.size===0)return oe(a,n);const r=[...s],m=r.map(d=>oe(a,n,d)),h=new Map;for(let d=0;d<r.length;d++){const c=r[d];for(const p of m[d]){const u=X(p);h.has(u)||h.set(u,{template:p,testValues:{}});const x=h.get(u),D=p.testValues?.[0];D!==void 0&&(x.testValues[c]=D)}}return[...h.values()].map(({template:d,testValues:c})=>({...d,testValues:Ue(c)?c:void 0}))}if(n==="countBin"||n==="percent"){if(i===void 0)return[];const o=Ge(a,i,"hard");if(n==="countBin")return o;const s=a.length;return o.map(r=>({...r,testValues:{0:s>0?(r.testValues?.[0]??0)/s*100:0}}))}if(n==="mode"){const o=new Map;for(const r of a)for(const m of r){const h=X(m);o.has(h)||o.set(h,[]),o.get(h).push(m)}const s=[];for(const[r,m]of o){const h=r.split(","),d=Number(h[0]),c=Number(h[1]),p=m.map(x=>x.hbin).filter(x=>x!==void 0),u=ze(p);u!==null&&s.push({x:d,y:c,hbin:u})}return s}return[]}function fe(t){const e=t.filter(F),n=e.length,i=e.filter(o=>o.edgeExcluded).length,a=e.filter(o=>o.hbin!==void 0||o.sbin!==void 0||dieHasTestData(o)).length;return{filledDies:a,totalDies:n,edgeExcludedDies:i,unpositionedDies:t.length-e.length,ratio:n>0?a/n:0}}function ue(t,e,n="exclude"){const i=new Set(e),o=t.filter(u=>!u.partial).filter(u=>u.edgeExcluded).length,s=t.filter(u=>u.partial).length;let r=0,m=0,h=!1;for(const u of t){if(!ye(u))continue;const x=u.hbin??u.sbin;x!==void 0&&(h=!0,i.has(x)?r++:m++)}const d=r+m,c=h&&d>0?r/d*100:null;let p=null;if(n==="denominator-only"){const u=d+o;p=h&&u>0?r/u*100:null}return{passDies:r,failDies:m,edgeExcludedDies:o,partialDies:s,totalDies:d,yieldPercent:c,yieldPercentGross:p}}function le(t,e,n,i,a,o=0,s=0,r=0,m=0,h=0,d=!1,c=!1){if(!t)return[];const p=t.anchorDie??{x:0,y:0},u=Be(e,{width:t.width,height:t.height,diePitchX:i,diePitchY:a,anchorDie:{x:p.x-o,y:p.y-s},gridOrigin:{x:-r,y:-m}}),x=Fe(We(d,c,e.center.x,e.center.y),Ee(h,e.center.x,e.center.y));return u.filter(D=>{const l=D.width/2,b=D.height/2,w=[[D.x-l,D.y-b],[D.x+l,D.y-b],[D.x+l,D.y+b],[D.x-l,D.y+b]].map(([v,N])=>Ie(x,v,N)),g=re(w.map(v=>v.x)),y=J(w.map(v=>v.x)),A=re(w.map(v=>v.y)),R=J(w.map(v=>v.y));return n.some(v=>v.physX>=g&&v.physX<y&&v.physY>=A&&v.physY<R)})}function at(t,e,n){const a=Math.max(0,e.radius-n)**2;return t.map(o=>{const s=o.physX-e.center.x,r=o.physY-e.center.y;return s*s+r*r>a?{...o,edgeExcluded:!0}:o})}const dt=.2;function ft(t,e,n){const i=new Set(n);function a(g,y){if(e==="last")return!0;if(e==="first")return!1;const A=K(g.hbin),R=K(y.hbin);if(A===void 0||R===void 0)return!1;const v=i.has(A),N=i.has(R);return e==="best"?N!==v?N:R<A:N!==v?v:R>A}const o=(g,y)=>y.supersedes!==void 0||a(g,y),s=g=>{const y=g.partId===void 0?"":String(g.partId).trim();return y===""?void 0:y},r=t.filter(g=>!F(g)),m=new Map;for(const g of r){const y=s(g);y!==void 0&&m.set(y,(m.get(y)??0)+1)}const d=(r.length===0?0:Math.max(0,...m.values())/r.length)<=dt,c=new Map,p=[],u=new Map;let x=0,D=0;for(const g of t){const y=s(g);let A;if(F(g)?A=`p:${X(g)}`:d&&y!==void 0?A=`i:${y}`:A=`u:${D++}`,g.supersedes==="partId"&&y!==void 0){const v=u.get(y);v!==void 0&&v!==A&&c.has(v)&&c.delete(v)}const R=c.get(A);R===void 0?(c.set(A,{winner:g,count:1}),p.push(A)):(R.count++,A.startsWith("i:")&&x++,o(R.winner,g)&&(R.winner=g)),y!==void 0&&u.set(y,A)}const l=[],b=[...p.filter(g=>g.startsWith("p:")),...p.filter(g=>!g.startsWith("p:"))];for(const g of b){const y=c.get(g);y!==void 0&&l.push(y.count>1?{...y.winner,retestCount:y.count}:y.winner)}const w=x>0?{code:"retests-by-part-id",severity:"info",message:`${x} record${x===1?"":"s"} with no position shared a part ID with an earlier record and ${x===1?"was":"were"} treated as a retest of it.`}:void 0;return{results:l,warning:w}}export function getDieTestValue(t,e){return q(t,e)}export function getTestPassStatus(t,e,n){const i=Me(t,e);return i!==void 0?i:n!==void 0&&!isParametricTest(n)?ce(q(t,e)):void 0}function ce(t){return t===0||t===1?t===1:void 0}export function testPassStatusReader(t){const e=t.map(a=>a.testNumber),n=Se(e),i=t.map(a=>!isParametricTest(a));return function(o,s){n(o,s);for(let r=0;r<s.length;r++){if(s[r]!==Oe||!i[r])continue;const m=ce(q(o,e[r]));m!==void 0&&(s[r]=m?Ne:Ae)}return s}}export function dieHasTestData(t){return De(t)}function K(t){return t===void 0||Number.isNaN(t)?void 0:t}function H(t,e){const n={},i=K(e.hbin),a=K(e.sbin);i!==void 0&&(n.hbin=i),a!==void 0&&(n.sbin=a),e.retestCount!==void 0&&(n.retestCount=e.retestCount),e.siteNum!==void 0&&(n.siteNum=e.siteNum),e.partId!==void 0&&(n.partId=e.partId),e.metadata!==void 0&&(n.metadata=e.metadata);const o=G(e);return ve({...t,...n},o.table,o.row)}function he(t){return t.some(dieHasTestData)?"value":"hardBin"}function pe(t,e){return t.map(({code:n,message:i})=>({code:n,message:i,severity:n==="non-standard-diameter"||n==="diameter-exceeds-die-extent"?"warning":"error",confidence:e.wafer.confidence}))}const ut="do not fit inside the supplied",lt="Inferred wafer diameter",ct="The probed dies reach only",ht=.75,pt=20;export const STANDARD_WAFER_DIAMETERS_MM=[100,125,150,200,300];const mt=.02;function me(t,e){return e.reduce((n,i)=>Math.abs(i-t)<Math.abs(n-t)?i:n)}function gt(t,e){if(!e.length)return!0;const n=me(t,e);return Math.abs(n-t)/n<=mt}function yt(t){return t===void 0?STANDARD_WAFER_DIAMETERS_MM:t.filter(e=>Number.isFinite(e)&&e>0)}export function buildWaferMap(t){const e=et(!Array.isArray(t)&&t.layout?Ze(t):t),n=[];if(e.derivedTests){const f=new Set(e.passBins),C=$=>{const E=$.length>0?G($[0]):void 0,W=Le($,E?.table??Ce([]),e.testDefs,e.derivedTests,f);return E&&Te($,W.table),W},S=(e.lotStackOpts?e.lotStackOpts.results:[e.results]).map(C);e.testDefs=S[0]?.testDefs??e.testDefs,n.push(...S[0]?.warnings??[])}e.lotStackOpts&&!qe(e.lotStackOpts.method)&&(e.testDefs=e.testDefs?.map(rt));const i=e.lotStackOpts?te(ot(e.lotStackOpts,e.testDefs)).records:e.results;for(let f=0;f<Math.min(5,i.length);f++){const C=i[f];if(typeof C.x=="string"||typeof C.y=="string")throw new TypeError("buildWaferMap: x and y must be numbers, received strings. Did you forget to cast CSV values? e.g. { x: +row.x, y: +row.y }")}for(const f of i)if(f.x==null!=(f.y==null))throw new TypeError(`buildWaferMap: die has only one of x/y set (x=${String(f.x)}, y=${String(f.y)}). A die must be either fully positioned (both x and y) or fully unpositioned (neither) \u2014 omit both to mark a die as having no reported position.`);const{results:a,warning:o}=ft(i,e.retestPolicy,e.passBins),s={wafer:{confidence:1,method:"provided"},diePitch:{confidence:1,units:"mm"},grid:{confidence:1}},r=[];if(e.explicitDies){let f=e.explicitDies.filter(F);const C=e.explicitDies.filter(V=>!F(V)),T=a.filter(F),S=a.filter(V=>!F(V));if(T.length>0){const V=new Map(T.map(P=>[X(P),P]));f=f.map(P=>{const Z=V.get(X(P));return Z?H(P,Z):P})}const $=e.waferOpts?.diameter??300,E=z({diameter:$,notch:e.waferOpts?.notch,orientation:e.waferOpts?.orientation??0,metadata:e.waferOpts?.metadata});E.orientation!==0&&(f=ee(f,E)),f=f.map(j);const W=le(e.reticleOpts,E,f,1,1,0,0,0,0,E.orientation),ge=S.map((V,P)=>j(H({id:`unpositioned_${P}`,width:f[0]?.width??1,height:f[0]?.height??1},V))),U=[...f,...C,...ge];return{wafer:E,dies:U,dataAxisFlip:{x:!1,y:!1},reticleConfig:e.reticleOpts,units:"mm",inference:s,warnings:[...e.removedFieldWarning?[e.removedFieldWarning]:[],...e.inputValueWarnings,...o?[o]:[],...pe(r,s),...n,...e.ringCountWarning?[e.ringCountWarning]:[]],plotMode:he(a),metadata:E.metadata??null,isLotStack:!1,dataCoverage:fe(U),passBins:e.passBins,ringCount:e.ringCount,yield:ue(U,e.passBins,e.edgeDieYieldMode),reticles:W,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs,metadataFields:e.metadataFields}}const m=a.filter(F),h=a.filter(f=>!F(f)),d=m.map(f=>({x:f.x,y:f.y})),c=Pe(d,e.dieOpts,e.waferOpts?.diameter);s.diePitch={confidence:c.confidence,units:c.units};const{pitchX:p,pitchY:u}=c,x=c.units,D=tt(m,e.dieOpts),l=Ve(d);s.grid={confidence:l.confidence};const{offsetX:b,offsetY:w}=nt(d,D,l),{flipX:g,flipY:y}=de(e.dieOpts,D),{colMidX:A,colMidY:R,anchored:v}=it(d,b,w,p,u,e.waferOpts?.center);let N=e.waferOpts?.diameter;const B=d.map(f=>({x:(Math.round(f.x)-b)*p-A,y:(Math.round(f.y)-w)*u-R})),I=J(B.map(({x:f,y:C})=>Math.hypot(Math.abs(f)+p/2,Math.abs(C)+u/2)));if(N===void 0)if(d.length>0)if(x==="mm"){const f=_e(B,{minRadius:I});N=f.diameter,s.wafer={confidence:f.confidence,method:f.method};const C=yt(e.standardDiameters);if(!gt(N,C)){const T=me(N,C);r.push({code:"non-standard-diameter",message:`${lt} ${N.toFixed(1)} mm is not a standard wafer size (nearest is ${T} mm). The diameter was derived from the die extent, which assumes the probed grid reaches the wafer edge \u2014 a non-standard result usually means it does not, because the outer dies were never probed. Every die is then placed against a wafer that is too small, which moves dies between rings and changes ring and edge findings. Supply waferConfig.diameter.`})}}else N=I*2,s.wafer={confidence:c.confidence*.8,method:"extent"};else N=x==="mm"?300:30,s.wafer={confidence:0,method:"default"};if(!v&&d.length>0&&st(d,b,w)&&(s.wafer.method="inferred-partial",r.push({code:"partial-coverage",message:"Wafer geometry was inferred from die positions alone. The data does not span a full symmetric wafer, so the inferred diameter and centre may be wrong and dies may be mis-positioned relative to the true wafer boundary. Supply waferConfig.diameter and waferConfig.center (the prober coordinate of the wafer centre) to position partial data correctly."})),e.waferOpts?.diameter!==void 0&&e.waferOpts.center===void 0&&I>0&&d.length>=pt){const f=I/(N/2);f<ht&&r.push({code:"diameter-exceeds-die-extent",message:`${ct} ${(f*100).toFixed(0)}% of the ${N} mm wafer's radius. Either the diameter is larger than the real one, or this is a partial map covering only part of the wafer. Ring bands are equal-radius, so the outer rings are mostly or entirely empty and ring, quadrant and edge findings describe the assumed wafer rather than the probed area. Check waferConfig.diameter, or drop it and let the diameter be inferred from the die extent.`})}if(e.waferOpts?.diameter!==void 0){const f=e.dieOpts?.width!==void 0&&e.dieOpts?.height!==void 0;if(f&&I>N/2+1e-9){const C=B.filter(({x:T,y:S})=>Math.hypot(Math.abs(T)+p/2,Math.abs(S)+u/2)>N/2+1e-9).length;r.push({code:"geometry-conflict",message:`${C} of ${B.length} probed die positions ${ut} ${N} mm wafer at the supplied die pitch of ${p} \xD7 ${u} mm. A die with test results is a real prober position and is always fully on the wafer, so one of the two supplied values must be wrong: containing these dies at this pitch would need a diameter of at least ${(I*2).toFixed(1)} mm. Check waferConfig.diameter and dieConfig.width/height against the real device.`})}}const O=z({diameter:N,notch:e.waferOpts?.notch,orientation:e.waferOpts?.orientation??0,metadata:e.waferOpts?.metadata});let k=m.map(f=>{const C=Math.round(f.x)-b,T=Math.round(f.y)-w,S={id:`${C}_${T}`,x:C,y:T,physX:C*p-A,physY:T*u-R,width:p,height:u,insideWafer:!0,partial:!1};return H(S,f)});(b!==0||w!==0)&&(k=k.map(f=>({...f,x:f.x+b,y:f.y+w,id:`${f.x+b}_${f.y+w}`}))),k=ee(k,O),(g||y)&&(k=be(k,{flipX:g,flipY:y},O.center));const M=[];e.waferOpts?.edgeExclusion&&e.waferOpts.edgeExclusion>0&&(k=at(k,O,e.waferOpts.edgeExclusion),e.waferOpts.edgeExclusion>O.radius&&M.push({code:"edge-exclusion-exceeds-radius",message:`edgeExclusion (${e.waferOpts.edgeExclusion}mm) exceeds the resolved wafer radius (${O.radius}mm) \u2014 the entire wafer is excluded.`,severity:"warning"})),k=k.map(j);const Y=le(e.reticleOpts,O,k,p,u,b,w,A,R,O.orientation,g,y),_=h.map((f,C)=>j(H({id:`unpositioned_${C}`,width:p,height:u},f))),L=[...k,..._];return{wafer:O,dies:L,dataAxisFlip:{x:g,y},reticleConfig:e.reticleOpts,units:x,inference:s,warnings:[...e.removedFieldWarning?[e.removedFieldWarning]:[],...e.inputValueWarnings,...o?[o]:[],...pe(r,s),...M,...n,...e.ringCountWarning?[e.ringCountWarning]:[]],plotMode:he(a),metadata:O.metadata??null,isLotStack:e.lotStackOpts!==void 0,dataCoverage:fe(L),passBins:e.passBins,ringCount:e.ringCount,yield:ue(L,e.passBins,e.edgeDieYieldMode),reticles:Y,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs,metadataFields:e.metadataFields,aggrMethod:e.lotStackOpts?.method,lotSize:e.lotStackOpts?.results.length}}
|
|
1
|
+
import{createWafer as U}from"../core/wafer.js";import{isYieldEligibleDie as we,getDieKey as X,hasPosition as F}from"../core/dies.js";import{applyOrientation as te,transformDies as xe,clipDiesToWafer as De}from"../core/transforms.js";import{generateDies as ve}from"../core/dies.js";import{dieHasAnyTestData as Me,dieLink as G,finaliseDie as j,linkDie as Se,recordedVerdict as Ce,recordedVerdictsReader as ke,tableFromRows as Ae,testValue as q,testsPresent as Oe,VERDICT_FAIL as Ne,VERDICT_NONE as $e,VERDICT_PASS as Re}from"../core/dieTable.js";import{isDieColumns as Te,linkColumns as Ee,linkRows as ne,relink as We}from"./columnarInput.js";import{affineRotation as Fe,affineMirror as Ie,affineCompose as _e,affinePoint as Pe}from"../core/transforms.js";import{inferWaferFromXY as Ve}from"../core/inference/wafer.js";import{resolveGridPitch as Be}from"../core/inference/pitch.js";import{assignGridIndices as Le}from"../core/inference/grid.js";import{generateReticleGrid as Ye}from"../core/reticle.js";import{applyDerivedTests as Xe}from"./derivedTests/apply.js";import{isStdfBin as je,isStdfCoord as ie,isStdfTestNumber as se,isStdfSite as Ke,STDF_BIN_MAX as He,STDF_COORD_MAX as re,STDF_TEST_NUM_MAX as ze,STDF_SITE_MAX as Ue}from"../core/stdf.js";import{hasAnyKey as Ge,maxOf as J,minOf as oe,modeOf as qe}from"../core/utils.js";import{aggregateValues as ae,aggregateBinCounts as Je,stackHoldsMeasurements as Qe}from"../core/aggregates.js";import{normalizePassBins as de}from"../core/passBins.js";import{defaultRingCount as Q}from"../core/ringCount.js";export function isParametricTest(t){return t?.testType!=="F"}export function isBuiltMap(t){return"wafer"in t&&"dies"in t&&"dataAxisFlip"in t}function Ze(t){const e=(i,a)=>({ringCount:i,ringCountWarning:{code:"analysis-option-corrected",severity:"warning",message:`ringCount=${String(t)} ${a} (using ${i}).`}});if(t===void 0)return{ringCount:Q(),ringCountWarning:void 0};if(typeof t!="number"||!Number.isFinite(t))return e(Q(),"is not a finite number");const n=Math.max(1,Math.round(t));return n===t?{ringCount:t,ringCountWarning:void 0}:e(n,"is not a whole number of at least 1")}function et(t,e){const n=Array.isArray(t)?{}:t,i={hbin:0,sbin:0,siteNum:0,testValues:0,testPass:0};let a;const o=(l,b)=>{i[l]++,a??=`${l} ${JSON.stringify(b)}`},s={bins:0,positions:0,values:0,sites:0,orientation:0},r=new Set;let m=0;if(e){i.testValues+=e.wrongValues,i.testPass+=e.wrongVerdicts,e.example!==void 0&&(a??=e.example),s.values+=e.nonFinite;for(const l of e.badTestNumbers)r.add(l)}const h=l=>l==null?!0:typeof l!="number"?!1:Number.isNaN(l)?(m++,!1):je(l)?!0:(s.bins++,!1);function d(l){const b=new Set,w=new Set;for(let O=0;O<l.length;O+=O<50?1:100){const k=l[O];if(!(k===null||typeof k!="object")){if(k.testValues&&typeof k.testValues=="object")for(const M of Object.keys(k.testValues))b.add(M);if(k.testPass&&typeof k.testPass=="object")for(const M of Object.keys(k.testPass))w.add(M)}}const g=[...b],y=[...w],A=O=>se(Number(O));for(const O of new Set([...g,...y]))A(O)||r.add(O);const R=g.map(A),v=y.map(A),N=g.map(Number),B=y.map(Number);let I;for(let O=0;O<l.length;O++){const k=l[O];if(k===null||typeof k!="object")continue;const M=k;let Y;const _=()=>Y??={...M};M.hbin!=null&&typeof M.hbin!="number"&&o("hbin",M.hbin),M.sbin!=null&&typeof M.sbin!="number"&&o("sbin",M.sbin),h(M.hbin)||(_().hbin=void 0),h(M.sbin)||(_().sbin=void 0);const L=typeof M.x=="number"&&!ie(M.x),f=typeof M.y=="number"&&!ie(M.y);if(L||f){s.positions+=(L?1:0)+(f?1:0);const S=_();S.x=void 0,S.y=void 0}M.siteNum!=null&&typeof M.siteNum!="number"?(o("siteNum",M.siteNum),_().siteNum=void 0):typeof M.siteNum=="number"&&!Ke(M.siteNum)&&(s.sites++,_().siteNum=void 0);const C=M.testValues;if(C&&typeof C=="object"){let S;for(let $=0;$<g.length;$++){const E=g[$],W=C[N[$]];if(W!=null){if(!R[$]){S??={...C},delete S[E];continue}typeof W!="number"?(o("testValues",W),S??={...C},delete S[E]):Number.isFinite(W)||(s.values++,S??={...C},delete S[E])}}S&&(_().testValues=S)}const T=M.testPass;if(T&&typeof T=="object"){let S;for(let $=0;$<y.length;$++){const E=y[$],W=T[B[$]];if(W!=null){if(!v[$]){S??={...T},delete S[E];continue}typeof W!="boolean"&&(o("testPass",W),S??={...T},delete S[E])}}S&&(_().testPass=S)}Y&&((I??=l.slice())[O]=Y)}return I}const c={warnings:[]};if(Array.isArray(t))c.results=d(t);else{Array.isArray(n.results)&&(c.results=d(n.results)),Array.isArray(n.dies)&&(c.dies=d(n.dies));const l=n.lotStack?.results;if(Array.isArray(l)){const b=l.map(w=>Array.isArray(w)?d(w):void 0);b.some(Boolean)&&(c.stackResults=b.map((w,g)=>w??l[g]))}}if(Array.isArray(n.testDefs)){const l=n.testDefs,b=l.filter(w=>w==null||typeof w.testNumber!="number"||se(w.testNumber));for(const w of l)b.includes(w)||r.add(String(w.testNumber));b.length!==l.length&&(c.testDefs=b)}const p=n.waferConfig?.orientation;if(typeof p=="number"){const l=Number.isFinite(p)?(p%360+360)%360:NaN;l===0||l===90||l===180||l===270?c.orientation=l:(s.orientation++,c.orientation=0)}const u=(l,b,w=`${b}s`)=>`${l} ${l===1?b:w}`,x=[];if(i.hbin&&x.push(u(i.hbin,"hard bin")),i.sbin&&x.push(u(i.sbin,"soft bin")),i.siteNum&&x.push(u(i.siteNum,"site number")),i.testValues&&x.push(u(i.testValues,"test value")),i.testPass&&x.push(u(i.testPass,"pass/fail verdict")),x.length){const l=`buildWaferMap received ${x.join(", ")} of the wrong type (for example ${a}), treated as missing. Bins, sites and test values must be numbers and verdicts true/false \u2014 a bin of "1" is not pass bin 1 \u2014 so those dies have no bin, no site, no value or no verdict, and yield and every chart leave them out. Convert them with Number() (and verdicts to booleans) and rebuild.`;console.warn(`[wafermap] ${l}`),c.warnings.push({code:"input-values-not-numbers",message:l,severity:"error"})}const D=[];if(s.bins&&D.push(`${u(s.bins,"bin")} (legal: whole numbers 0\u2013${He}) \u2014 those dies have no bin`),s.positions&&D.push(`${u(s.positions,"coordinate")} (legal: whole numbers \u2212${re} to ${re}) \u2014 those dies have no position`),r.size&&D.push(`${u(r.size,"test number")} (legal: whole numbers 0\u2013${ze}) \u2014 those tests are left out`),s.values&&D.push(`${u(s.values,"test value")} that ${s.values===1?"is":"are"} not finite \u2014 left out`),s.sites&&D.push(`${u(s.sites,"site number")} (legal: 0\u2013${Ue}) \u2014 those dies have no site`),s.orientation&&D.push("a waferConfig.orientation other than 0, 90, 180 or 270 \u2014 the map is built at 0"),D.length||m){const l=[];D.length&&l.push(`buildWaferMap received values outside the STDF V4 ranges, treated as missing: ${D.join("; ")}.`),m&&l.push(`${u(m,"bin was","bins were")} NaN and ${m===1?"is":"are"} treated as no bin.`);const b=l.join(" ");console.warn(`[wafermap] ${b}`),c.warnings.push({code:"input-values-outside-stdf",message:b,severity:"warning"})}return c}function fe(t){const e=(h,d)=>h!==null&&typeof h=="object"&&h[d]!==void 0,n=Array.isArray(t)?{}:t,i=[],a=(h,d,c)=>{h&&i.push(`\`${d}\` (now \`${c}\`)`)};a(e(n,"data"),"data","results"),a(e(n,"die"),"die","dieConfig"),a(e(n,"stack"),"stack","lotStack"),a(e(n.dieConfig,"origin"),"dieConfig.origin","dieConfig.coordinateOrigin"),a(e(n.waferConfig,"flat"),"waferConfig.flat","waferConfig.notch"),a(e(n.reticleConfig,"anchor"),"reticleConfig.anchor","reticleConfig.anchorDie"),a(e(n.lotStack,"aggr"),"lotStack.aggr","lotStack.method"),a(Array.isArray(n.testDefs)&&n.testDefs.some(h=>e(h,"index")&&!e(h,"testNumber")),"TestDef.index","TestDef.testNumber");const o=e(n.lotStack,"results")?n.lotStack.results:[],s=(Array.isArray(t)?[t]:[n.results,...Array.isArray(o)?o:[]]).filter(h=>Array.isArray(h));if(a(s.some(h=>h.slice(0,100).some(d=>e(d,"values"))),"values","testValues"),!i.length)return;const r=i.length>1,m=`buildWaferMap ignored ${i.join(", ")}: ${r?"these names were":"that name was"} removed in an earlier release, so what ${r?"they":"it"} described is not on this map. Rename and rebuild.`;return console.warn(`[wafermap] ${m}`),{code:"input-field-removed",message:m,severity:"error"}}function tt(t){if(t.results!==void 0||t.lotStack!==void 0)throw new Error("buildWaferMap: pass `layout`, `results` or `lotStack` \u2014 only one.");const e=t.waferConfig?.diameter,n=t.dieConfig?.width,i=t.dieConfig?.height;if(!(e>0&&n>0&&i>0))throw new Error("buildWaferMap: `layout` needs waferConfig.diameter and dieConfig.width and height, all positive.");const a=U({diameter:e,notch:t.waferConfig?.notch,orientation:0}),o=De(ve(a,{width:n,height:i}),a,{width:n,height:i}).filter(d=>!d.partial),{flipX:s,flipY:r}=ue(t.dieConfig,{type:"center"}),{layout:m,...h}=t;return{...h,results:o.map(d=>({x:s?-d.x:d.x,y:r?-d.y:d.y})),waferConfig:{...t.waferConfig,center:{x:0,y:0}},dieConfig:{...t.dieConfig,coordinateOrigin:{type:"center"}}}}function nt(t){if(!Array.isArray(t)&&"results"in t&&t.results!==void 0&&"lotStack"in t&&t.lotStack!==void 0)throw new Error("buildWaferMap: pass either `results` or `lotStack`, not both.");const e={wrongVerdicts:0,wrongValues:0,nonFinite:0,badTestNumbers:[]},n=!Array.isArray(t)&&Te(t.results)?{...t,results:Ee(t.results,e).records}:t,i=et(n,e);return Array.isArray(n)?{results:Z(i.results??n),waferOpts:void 0,dieOpts:void 0,explicitDies:void 0,reticleOpts:void 0,lotStackOpts:void 0,passBins:de(void 0),ringCount:Q(),ringCountWarning:void 0,removedFieldWarning:fe(n),inputValueWarnings:i.warnings,standardDiameters:void 0,testDefs:void 0,hbinDefs:void 0,sbinDefs:void 0,metadataFields:void 0,derivedTests:void 0,retestPolicy:"last",edgeDieYieldMode:"exclude"}:{results:Z(i.results??n.results??[]),waferOpts:i.orientation===void 0||n.waferConfig===void 0?n.waferConfig:{...n.waferConfig,orientation:i.orientation},dieOpts:n.dieConfig,explicitDies:i.dies??n.dies,reticleOpts:n.reticleConfig,lotStackOpts:n.lotStack?{...n.lotStack,results:(i.stackResults??n.lotStack.results).map(Z)}:void 0,passBins:de(n.passBins),...Ze(n.ringCount),removedFieldWarning:fe(n),inputValueWarnings:i.warnings,standardDiameters:n.standardDiameters,testDefs:(i.testDefs??n.testDefs)?.length?i.testDefs??n.testDefs:void 0,hbinDefs:n.hbinDefs,sbinDefs:n.sbinDefs,metadataFields:n.metadataFields,derivedTests:n.derivedTests?.length?n.derivedTests:void 0,retestPolicy:n.retestPolicy??"last",edgeDieYieldMode:n.edgeDieYieldMode??"exclude"}}function Z(t){return t.length>0&&G(t[0])!==void 0?t:ne(t).records}function it(t,e){return e?.coordinateOrigin?e.coordinateOrigin:{type:"center"}}function st(t,e,n){if(e.type==="custom"&&e.offset)return{offsetX:e.offset.x,offsetY:e.offset.y};if(e.type!=="center"){let i=1/0,a=-1/0,o=1/0,s=-1/0;for(const r of t)r.x<i&&(i=r.x),r.x>a&&(a=r.x),r.y<o&&(o=r.y),r.y>s&&(s=r.y);return{offsetX:Math.round((a+i)/2),offsetY:Math.round((s+o)/2)}}return{offsetX:n.offsetX,offsetY:n.offsetY}}function rt(t,e,n,i,a,o){if(o)return{colMidX:(Math.round(o.x)-e)*i,colMidY:(Math.round(o.y)-n)*a,anchored:!0};if(t.length===0)return{colMidX:0,colMidY:0,anchored:!1};let s=1/0,r=-1/0,m=1/0,h=-1/0;for(const d of t){const c=Math.round(d.x)-e,p=Math.round(d.y)-n;c<s&&(s=c),c>r&&(r=c),p<m&&(m=p),p>h&&(h=p)}return{colMidX:(s+r)/2*i,colMidY:(m+h)/2*a,anchored:!1}}function ot(t,e,n){let i=1/0,a=-1/0,o=1/0,s=-1/0,r=0,m=0;for(const c of t){const p=Math.round(c.x)-e,u=Math.round(c.y)-n;r+=p,m+=u,p<i&&(i=p),p>a&&(a=p),u<o&&(o=u),u>s&&(s=u)}const h=t.length,d=(c,p,u)=>{const x=(u-p)/2;return x<=0?0:Math.abs(c/h-(p+u)/2)/x};return d(r,i,a)>.11||d(m,o,s)>.11}function ue(t,e){let n=t?.xAxisDirection==="left",i=t?.yAxisDirection==="down";return(e.type==="UL"||e.type==="UR")&&(i=!0),(e.type==="LR"||e.type==="UR")&&(n=!0),{flipX:n,flipY:i}}function at(t){const e={...t};return delete e.limitLow,delete e.limitHigh,delete e.limitLowInclusive,delete e.limitHighInclusive,delete e.specLow,delete e.specHigh,e}function dt(t,e){const{method:n,targetBin:i}=t,a=t.results.map(o=>o.filter(F));if(n==="mean"||n==="median"||n==="stddev"||n==="min"||n==="max"||n==="count"){const o=new Set((e??[]).filter(d=>!isParametricTest(d)).map(d=>d.testNumber)),s=new Set;for(const d of a)for(const c of Oe(d,"values"))o.has(c)||s.add(c);if(s.size===0)return ae(a,n);const r=[...s],m=r.map(d=>ae(a,n,d)),h=new Map;for(let d=0;d<r.length;d++){const c=r[d];for(const p of m[d]){const u=X(p);h.has(u)||h.set(u,{template:p,testValues:{}});const x=h.get(u),D=p.testValues?.[0];D!==void 0&&(x.testValues[c]=D)}}return[...h.values()].map(({template:d,testValues:c})=>({...d,testValues:Ge(c)?c:void 0}))}if(n==="countBin"||n==="percent"){if(i===void 0)return[];const o=Je(a,i,"hard");if(n==="countBin")return o;const s=a.length;return o.map(r=>({...r,testValues:{0:s>0?(r.testValues?.[0]??0)/s*100:0}}))}if(n==="mode"){const o=new Map;for(const r of a)for(const m of r){const h=X(m);o.has(h)||o.set(h,[]),o.get(h).push(m)}const s=[];for(const[r,m]of o){const h=r.split(","),d=Number(h[0]),c=Number(h[1]),p=m.map(x=>x.hbin).filter(x=>x!==void 0),u=qe(p);u!==null&&s.push({x:d,y:c,hbin:u})}return s}return[]}function le(t){const e=t.filter(F),n=e.length,i=e.filter(o=>o.edgeExcluded).length,a=e.filter(o=>o.hbin!==void 0||o.sbin!==void 0||dieHasTestData(o)).length;return{filledDies:a,totalDies:n,edgeExcludedDies:i,unpositionedDies:t.length-e.length,ratio:n>0?a/n:0}}function ce(t,e,n="exclude"){const i=new Set(e),o=t.filter(u=>!u.partial).filter(u=>u.edgeExcluded).length,s=t.filter(u=>u.partial).length;let r=0,m=0,h=!1;for(const u of t){if(!we(u))continue;const x=u.hbin??u.sbin;x!==void 0&&(h=!0,i.has(x)?r++:m++)}const d=r+m,c=h&&d>0?r/d*100:null;let p=null;if(n==="denominator-only"){const u=d+o;p=h&&u>0?r/u*100:null}return{passDies:r,failDies:m,edgeExcludedDies:o,partialDies:s,totalDies:d,yieldPercent:c,yieldPercentGross:p}}function he(t,e,n,i,a,o=0,s=0,r=0,m=0,h=0,d=!1,c=!1){if(!t)return[];const p=t.anchorDie??{x:0,y:0},u=Ye(e,{width:t.width,height:t.height,diePitchX:i,diePitchY:a,anchorDie:{x:p.x-o,y:p.y-s},gridOrigin:{x:-r,y:-m}}),x=_e(Ie(d,c,e.center.x,e.center.y),Fe(h,e.center.x,e.center.y));return u.filter(D=>{const l=D.width/2,b=D.height/2,w=[[D.x-l,D.y-b],[D.x+l,D.y-b],[D.x+l,D.y+b],[D.x-l,D.y+b]].map(([v,N])=>Pe(x,v,N)),g=oe(w.map(v=>v.x)),y=J(w.map(v=>v.x)),A=oe(w.map(v=>v.y)),R=J(w.map(v=>v.y));return n.some(v=>v.physX>=g&&v.physX<y&&v.physY>=A&&v.physY<R)})}function ft(t,e,n){const a=Math.max(0,e.radius-n)**2;return t.map(o=>{const s=o.physX-e.center.x,r=o.physY-e.center.y;return s*s+r*r>a?{...o,edgeExcluded:!0}:o})}const ut=.2;function lt(t,e,n){const i=new Set(n);function a(g,y){if(e==="last")return!0;if(e==="first")return!1;const A=K(g.hbin),R=K(y.hbin);if(A===void 0||R===void 0)return!1;const v=i.has(A),N=i.has(R);return e==="best"?N!==v?N:R<A:N!==v?v:R>A}const o=(g,y)=>y.supersedes!==void 0||a(g,y),s=g=>{const y=g.partId===void 0?"":String(g.partId).trim();return y===""?void 0:y},r=t.filter(g=>!F(g)),m=new Map;for(const g of r){const y=s(g);y!==void 0&&m.set(y,(m.get(y)??0)+1)}const d=(r.length===0?0:Math.max(0,...m.values())/r.length)<=ut,c=new Map,p=[],u=new Map;let x=0,D=0;for(const g of t){const y=s(g);let A;if(F(g)?A=`p:${X(g)}`:d&&y!==void 0?A=`i:${y}`:A=`u:${D++}`,g.supersedes==="partId"&&y!==void 0){const v=u.get(y);v!==void 0&&v!==A&&c.has(v)&&c.delete(v)}const R=c.get(A);R===void 0?(c.set(A,{winner:g,count:1}),p.push(A)):(R.count++,A.startsWith("i:")&&x++,o(R.winner,g)&&(R.winner=g)),y!==void 0&&u.set(y,A)}const l=[],b=[...p.filter(g=>g.startsWith("p:")),...p.filter(g=>!g.startsWith("p:"))];for(const g of b){const y=c.get(g);y!==void 0&&l.push(y.count>1?{...y.winner,retestCount:y.count}:y.winner)}const w=x>0?{code:"retests-by-part-id",severity:"info",message:`${x} record${x===1?"":"s"} with no position shared a part ID with an earlier record and ${x===1?"was":"were"} treated as a retest of it.`}:void 0;return{results:l,warning:w}}export function getDieTestValue(t,e){return q(t,e)}export function getTestPassStatus(t,e,n){const i=Ce(t,e);return i!==void 0?i:n!==void 0&&!isParametricTest(n)?pe(q(t,e)):void 0}function pe(t){return t===0||t===1?t===1:void 0}export function testPassStatusReader(t){const e=t.map(a=>a.testNumber),n=ke(e),i=t.map(a=>!isParametricTest(a));return function(o,s){n(o,s);for(let r=0;r<s.length;r++){if(s[r]!==$e||!i[r])continue;const m=pe(q(o,e[r]));m!==void 0&&(s[r]=m?Re:Ne)}return s}}export function dieHasTestData(t){return Me(t)}function K(t){return t===void 0||Number.isNaN(t)?void 0:t}function H(t,e){const n={},i=K(e.hbin),a=K(e.sbin);i!==void 0&&(n.hbin=i),a!==void 0&&(n.sbin=a),e.retestCount!==void 0&&(n.retestCount=e.retestCount),e.siteNum!==void 0&&(n.siteNum=e.siteNum),e.partId!==void 0&&(n.partId=e.partId),e.metadata!==void 0&&(n.metadata=e.metadata);const o=G(e);return Se({...t,...n},o.table,o.row)}function me(t){return t.some(dieHasTestData)?"value":"hardBin"}function ge(t,e){return t.map(({code:n,message:i})=>({code:n,message:i,severity:n==="non-standard-diameter"||n==="diameter-exceeds-die-extent"?"warning":"error",confidence:e.wafer.confidence}))}const ct="do not fit inside the supplied",ht="Inferred wafer diameter",pt="The probed dies reach only",mt=.75,gt=20;export const STANDARD_WAFER_DIAMETERS_MM=[100,125,150,200,300];const yt=.02;function ye(t,e){return e.reduce((n,i)=>Math.abs(i-t)<Math.abs(n-t)?i:n)}function bt(t,e){if(!e.length)return!0;const n=ye(t,e);return Math.abs(n-t)/n<=yt}function wt(t){return t===void 0?STANDARD_WAFER_DIAMETERS_MM:t.filter(e=>Number.isFinite(e)&&e>0)}export function buildWaferMap(t){const e=nt(!Array.isArray(t)&&t.layout?tt(t):t),n=[];if(e.derivedTests){const f=new Set(e.passBins),C=$=>{const E=$.length>0?G($[0]):void 0,W=Xe($,E?.table??Ae([]),e.testDefs,e.derivedTests,f);return E&&We($,W.table),W},S=(e.lotStackOpts?e.lotStackOpts.results:[e.results]).map(C);e.testDefs=S[0]?.testDefs??e.testDefs,n.push(...S[0]?.warnings??[])}e.lotStackOpts&&!Qe(e.lotStackOpts.method)&&(e.testDefs=e.testDefs?.map(at));const i=e.lotStackOpts?ne(dt(e.lotStackOpts,e.testDefs)).records:e.results;for(let f=0;f<Math.min(5,i.length);f++){const C=i[f];if(typeof C.x=="string"||typeof C.y=="string")throw new TypeError("buildWaferMap: x and y must be numbers, received strings. Did you forget to cast CSV values? e.g. { x: +row.x, y: +row.y }")}for(const f of i)if(f.x==null!=(f.y==null))throw new TypeError(`buildWaferMap: die has only one of x/y set (x=${String(f.x)}, y=${String(f.y)}). A die must be either fully positioned (both x and y) or fully unpositioned (neither) \u2014 omit both to mark a die as having no reported position.`);const{results:a,warning:o}=lt(i,e.retestPolicy,e.passBins),s={wafer:{confidence:1,method:"provided"},diePitch:{confidence:1,units:"mm"},grid:{confidence:1}},r=[];if(e.explicitDies){let f=e.explicitDies.filter(F);const C=e.explicitDies.filter(V=>!F(V)),T=a.filter(F),S=a.filter(V=>!F(V));if(T.length>0){const V=new Map(T.map(P=>[X(P),P]));f=f.map(P=>{const ee=V.get(X(P));return ee?H(P,ee):P})}const $=e.waferOpts?.diameter??300,E=U({diameter:$,notch:e.waferOpts?.notch,orientation:e.waferOpts?.orientation??0,metadata:e.waferOpts?.metadata});E.orientation!==0&&(f=te(f,E)),f=f.map(j);const W=he(e.reticleOpts,E,f,1,1,0,0,0,0,E.orientation),be=S.map((V,P)=>j(H({id:`unpositioned_${P}`,width:f[0]?.width??1,height:f[0]?.height??1},V))),z=[...f,...C,...be];return{wafer:E,dies:z,dataAxisFlip:{x:!1,y:!1},reticleConfig:e.reticleOpts,units:"mm",inference:s,warnings:[...e.removedFieldWarning?[e.removedFieldWarning]:[],...e.inputValueWarnings,...o?[o]:[],...ge(r,s),...n,...e.ringCountWarning?[e.ringCountWarning]:[]],plotMode:me(a),metadata:E.metadata??null,isLotStack:!1,dataCoverage:le(z),passBins:e.passBins,ringCount:e.ringCount,yield:ce(z,e.passBins,e.edgeDieYieldMode),reticles:W,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs,metadataFields:e.metadataFields}}const m=a.filter(F),h=a.filter(f=>!F(f)),d=m.map(f=>({x:f.x,y:f.y})),c=Be(d,e.dieOpts,e.waferOpts?.diameter);s.diePitch={confidence:c.confidence,units:c.units};const{pitchX:p,pitchY:u}=c,x=c.units,D=it(m,e.dieOpts),l=Le(d);s.grid={confidence:l.confidence};const{offsetX:b,offsetY:w}=st(d,D,l),{flipX:g,flipY:y}=ue(e.dieOpts,D),{colMidX:A,colMidY:R,anchored:v}=rt(d,b,w,p,u,e.waferOpts?.center);let N=e.waferOpts?.diameter;const B=d.map(f=>({x:(Math.round(f.x)-b)*p-A,y:(Math.round(f.y)-w)*u-R})),I=J(B.map(({x:f,y:C})=>Math.hypot(Math.abs(f)+p/2,Math.abs(C)+u/2)));if(N===void 0)if(d.length>0)if(x==="mm"){const f=Ve(B,{minRadius:I});N=f.diameter,s.wafer={confidence:f.confidence,method:f.method};const C=wt(e.standardDiameters);if(!bt(N,C)){const T=ye(N,C);r.push({code:"non-standard-diameter",message:`${ht} ${N.toFixed(1)} mm is not a standard wafer size (nearest is ${T} mm). The diameter was derived from the die extent, which assumes the probed grid reaches the wafer edge \u2014 a non-standard result usually means it does not, because the outer dies were never probed. Every die is then placed against a wafer that is too small, which moves dies between rings and changes ring and edge findings. Supply waferConfig.diameter.`})}}else N=I*2,s.wafer={confidence:c.confidence*.8,method:"extent"};else N=x==="mm"?300:30,s.wafer={confidence:0,method:"default"};if(!v&&d.length>0&&ot(d,b,w)&&(s.wafer.method="inferred-partial",r.push({code:"partial-coverage",message:"Wafer geometry was inferred from die positions alone. The data does not span a full symmetric wafer, so the inferred diameter and centre may be wrong and dies may be mis-positioned relative to the true wafer boundary. Supply waferConfig.diameter and waferConfig.center (the prober coordinate of the wafer centre) to position partial data correctly."})),e.waferOpts?.diameter!==void 0&&e.waferOpts.center===void 0&&I>0&&d.length>=gt){const f=I/(N/2);f<mt&&r.push({code:"diameter-exceeds-die-extent",message:`${pt} ${(f*100).toFixed(0)}% of the ${N} mm wafer's radius. Either the diameter is larger than the real one, or this is a partial map covering only part of the wafer. Ring bands are equal-radius, so the outer rings are mostly or entirely empty and ring, quadrant and edge findings describe the assumed wafer rather than the probed area. Check waferConfig.diameter, or drop it and let the diameter be inferred from the die extent.`})}if(e.waferOpts?.diameter!==void 0){const f=e.dieOpts?.width!==void 0&&e.dieOpts?.height!==void 0;if(f&&I>N/2+1e-9){const C=B.filter(({x:T,y:S})=>Math.hypot(Math.abs(T)+p/2,Math.abs(S)+u/2)>N/2+1e-9).length;r.push({code:"geometry-conflict",message:`${C} of ${B.length} probed die positions ${ct} ${N} mm wafer at the supplied die pitch of ${p} \xD7 ${u} mm. A die with test results is a real prober position and is always fully on the wafer, so one of the two supplied values must be wrong: containing these dies at this pitch would need a diameter of at least ${(I*2).toFixed(1)} mm. Check waferConfig.diameter and dieConfig.width/height against the real device.`})}}const O=U({diameter:N,notch:e.waferOpts?.notch,orientation:e.waferOpts?.orientation??0,metadata:e.waferOpts?.metadata});let k=m.map(f=>{const C=Math.round(f.x)-b,T=Math.round(f.y)-w,S={id:`${C}_${T}`,x:C,y:T,physX:C*p-A,physY:T*u-R,width:p,height:u,insideWafer:!0,partial:!1};return H(S,f)});(b!==0||w!==0)&&(k=k.map(f=>({...f,x:f.x+b,y:f.y+w,id:`${f.x+b}_${f.y+w}`}))),k=te(k,O),(g||y)&&(k=xe(k,{flipX:g,flipY:y},O.center));const M=[];e.waferOpts?.edgeExclusion&&e.waferOpts.edgeExclusion>0&&(k=ft(k,O,e.waferOpts.edgeExclusion),e.waferOpts.edgeExclusion>O.radius&&M.push({code:"edge-exclusion-exceeds-radius",message:`edgeExclusion (${e.waferOpts.edgeExclusion}mm) exceeds the resolved wafer radius (${O.radius}mm) \u2014 the entire wafer is excluded.`,severity:"warning"})),k=k.map(j);const Y=he(e.reticleOpts,O,k,p,u,b,w,A,R,O.orientation,g,y),_=h.map((f,C)=>j(H({id:`unpositioned_${C}`,width:p,height:u},f))),L=[...k,..._];return{wafer:O,dies:L,dataAxisFlip:{x:g,y},reticleConfig:e.reticleOpts,units:x,inference:s,warnings:[...e.removedFieldWarning?[e.removedFieldWarning]:[],...e.inputValueWarnings,...o?[o]:[],...ge(r,s),...M,...n,...e.ringCountWarning?[e.ringCountWarning]:[]],plotMode:me(a),metadata:O.metadata??null,isLotStack:e.lotStackOpts!==void 0,dataCoverage:le(L),passBins:e.passBins,ringCount:e.ringCount,yield:ce(L,e.passBins,e.edgeDieYieldMode),reticles:Y,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs,metadataFields:e.metadataFields,aggrMethod:e.lotStackOpts?.method,lotSize:e.lotStackOpts?.results.length}}
|