@wafertools/wafermap 0.32.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 +7 -7
- package/CHANGELOG.md +374 -1
- 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 +57 -15
- 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 +51 -2
- package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
- package/dist/packages/canvas-adapter/summaryPanel.d.ts +49 -11
- package/dist/packages/canvas-adapter/summaryPanel.js +2 -3
- 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/toCanvas.js +1 -1
- package/dist/packages/canvas-adapter/toolbar.d.ts +74 -93
- 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 +409 -51
- 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/aggregates.d.ts +6 -0
- package/dist/packages/core/aggregates.js +1 -1
- package/dist/packages/core/classify.d.ts +3 -2
- package/dist/packages/core/classify.js +1 -1
- package/dist/packages/core/compact.d.ts +205 -0
- package/dist/packages/core/compact.js +3 -0
- package/dist/packages/core/passBins.d.ts +23 -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/axisTicks.d.ts +14 -0
- package/dist/packages/renderer/axisTicks.js +1 -1
- package/dist/packages/renderer/binColors.d.ts +6 -6
- package/dist/packages/renderer/binColors.js +1 -1
- package/dist/packages/renderer/buildView.d.ts +27 -2
- package/dist/packages/renderer/buildView.js +1 -1
- package/dist/packages/renderer/buildWaferMap.js +1 -1
- package/dist/packages/renderer/compactView.d.ts +22 -0
- package/dist/packages/renderer/compactView.js +1 -0
- package/dist/packages/renderer/xyIndicator.d.ts +27 -0
- package/dist/packages/renderer/xyIndicator.js +1 -0
- package/dist/packages/stats/analyzeWaferLot.d.ts +28 -0
- package/dist/packages/stats/analyzeWaferLot.js +1 -1
- package/dist/packages/stats/analyzeWaferMap.d.ts +36 -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/binRows.d.ts +38 -0
- package/dist/packages/stats/binRows.js +1 -0
- package/dist/packages/stats/filterFindings.d.ts +25 -0
- package/dist/packages/stats/filterFindings.js +1 -1
- package/dist/packages/stats/findingText.d.ts +9 -0
- package/dist/packages/stats/findingText.js +2 -0
- 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.d.ts +16 -0
- package/dist/packages/stats/lotDrift.js +1 -0
- package/dist/packages/stats/lotPattern.d.ts +33 -0
- package/dist/packages/stats/lotPattern.js +1 -0
- package/dist/packages/stats/math.d.ts +60 -6
- package/dist/packages/stats/math.js +1 -1
- package/dist/packages/stats/patternClassification.d.ts +32 -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 +44 -0
- package/dist/packages/stats/presentation.js +1 -0
- package/dist/packages/stats/regions.d.ts +15 -0
- package/dist/packages/stats/regions.js +1 -1
- package/dist/packages/stats/renderSummaryReport.d.ts +23 -5
- package/dist/packages/stats/renderSummaryReport.js +32 -27
- package/dist/packages/stats/reportHtml.d.ts +54 -9
- package/dist/packages/stats/reportHtml.js +145 -55
- 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 +76 -0
- package/dist/packages/stats/synthesis.js +2 -0
- 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 +2 -2
- package/dist/packages/stats/yield.d.ts +15 -6
- package/dist/packages/stats/yield.js +1 -1
- package/dist/packages/stats/yieldRows.d.ts +31 -0
- package/dist/packages/stats/yieldRows.js +1 -0
- package/llms.txt +16 -4
- package/package.json +3 -3
- package/dist/packages/stats/findingsNarrative.d.ts +0 -3
- package/dist/packages/stats/findingsNarrative.js +0 -1
|
@@ -4,6 +4,7 @@ import type { Reticle } from '../core/reticle.js';
|
|
|
4
4
|
import type { DieMetadata, WaferMetadata } from '../core/metadata.js';
|
|
5
5
|
import { type Affine } from '../core/transforms.js';
|
|
6
6
|
import { type BinColors } from './binColors.js';
|
|
7
|
+
import type { CompactMap } from '../core/compact.js';
|
|
7
8
|
import type { TestDef, BinDef, MetadataFieldDef, ReticleConfig } from './buildWaferMap.js';
|
|
8
9
|
import { classifySpec, type SpecCategory } from './spec.js';
|
|
9
10
|
export { classifySpec };
|
|
@@ -89,7 +90,7 @@ export interface ViewHoverPoint {
|
|
|
89
90
|
y: number;
|
|
90
91
|
}
|
|
91
92
|
export interface ViewOverlay {
|
|
92
|
-
kind: 'wafer-boundary' | 'reticle' | 'probe-path' | 'ring-boundary' | 'quadrant-boundary' | 'xy-indicator';
|
|
93
|
+
kind: 'wafer-boundary' | 'reticle' | 'probe-path' | 'ring-boundary' | 'quadrant-boundary' | 'xy-indicator' | 'compact-group';
|
|
93
94
|
points: Point[][];
|
|
94
95
|
closed: boolean;
|
|
95
96
|
lineColor: string;
|
|
@@ -267,6 +268,21 @@ export interface View {
|
|
|
267
268
|
pass: number;
|
|
268
269
|
fail: number;
|
|
269
270
|
};
|
|
271
|
+
/**
|
|
272
|
+
* Present only on a compact view (see `ViewOptions.compact`): the original die coordinate of
|
|
273
|
+
* each compact column and row, and the compact index that sits at the display origin.
|
|
274
|
+
* Axis labels read this instead of inverting `gridToScreen`, so they stay original die
|
|
275
|
+
* coordinates, never compact indices.
|
|
276
|
+
*/
|
|
277
|
+
compact?: {
|
|
278
|
+
columns: readonly number[];
|
|
279
|
+
rows: readonly number[];
|
|
280
|
+
/** Compact indices that start a group after the first, per axis: where the axis labels go. */
|
|
281
|
+
columnBreaks: readonly number[];
|
|
282
|
+
rowBreaks: readonly number[];
|
|
283
|
+
centreColumn: number;
|
|
284
|
+
centreRow: number;
|
|
285
|
+
};
|
|
270
286
|
/** Bounding box of all die centres in scene coordinates (mm). */
|
|
271
287
|
dieBounds: {
|
|
272
288
|
minX: number;
|
|
@@ -316,6 +332,15 @@ export interface ViewOptions {
|
|
|
316
332
|
flipX?: boolean;
|
|
317
333
|
flipY?: boolean;
|
|
318
334
|
};
|
|
335
|
+
/**
|
|
336
|
+
* Draw the dies on this compact layout (see `buildCompactMap`) instead of at their physical
|
|
337
|
+
* positions: the empty columns and rows are gone, each group of dies is outlined, and the
|
|
338
|
+
* wafer outline, rings, quadrants, reticle grid and XY indicator are not drawn, because the
|
|
339
|
+
* dies no longer sit where they are. Every die is still drawn and still counts: only the
|
|
340
|
+
* layout changes. Rotation, flips and the notch marker follow the same transform as the
|
|
341
|
+
* physical view.
|
|
342
|
+
*/
|
|
343
|
+
compact?: CompactMap;
|
|
319
344
|
/**
|
|
320
345
|
* Explicit value colour normalization range.
|
|
321
346
|
*
|
|
@@ -360,7 +385,7 @@ export interface ViewOptions {
|
|
|
360
385
|
*/
|
|
361
386
|
colorbarRangeMode?: 'spec' | 'data';
|
|
362
387
|
/**
|
|
363
|
-
* Which bins count as passing.
|
|
388
|
+
* Which bins count as passing. Required — there is no default; a view built without them is refused.
|
|
364
389
|
*
|
|
365
390
|
* Sets `ViewRect.binFail` and decides which bins take the palette's pass
|
|
366
391
|
* colours (see `resolveBinColors`) — never the bin number. `buildWaferMap` and
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{hasPosition as pe}from"../core/dies.js";import{dieValueEntries as ye,dieVerdictEntries as ge,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 xt,affineSwapsAxes as ve}from"../core/transforms.js";import{contrastTextColor as Q,SPEC_PASS_FILL as Mt,SPEC_FAIL_LOW as ke,SPEC_FAIL_HIGH as St}from"./colorMap.js";import{resolveValueColorFn as Be}from"./colorSchemes.js";import{resolveBinColors as $e,binColorsCover as Te}from"./binColors.js";import{NO_DATA_FILL as q}from"./colorMap.js";import{diePassStatus as Ie}from"../core/dies.js";import{getDieTestValue as L,getTestPassStatus as U,isParametricTest as vt}from"./buildWaferMap.js";import{classifySpec as ot}from"./spec.js";export{ot as classifySpec};import{fmt as kt,fmtColorbarAxis as Bt,fmtAggregationMethod as Wt}from"./fmt.js";import{metadataValueColor as De}from"./colorMap.js";import{asList as wt,clamp01 as zt,compareNatural as Ce,escHtml as R}from"../core/utils.js";import{prettyKey as Kt}from"../core/utils.js";import{testLabel as rt,markedTestLabel as lt,derivedTestNote as qt,isDerivedTest as Fe,DERIVED_MARK as Ve,DERIVED_KEY as Le}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 a=new Set;for(const i of t){if(i.partial||i.edgeExcluded)continue;const s=ct(i,e);s!==void 0&&a.add(s)}return[...a].sort(Ce)}export function findTestDef(t,e){return t?.find(a=>a.testNumber===e)}export function getUniqueTestNumbers(t){return _t(t,"values")}const Re="#d3d6db",Xt="#e8e9ea",Pe="#aab0ba";function ct(t,e){return e?Me(t.metadata?.[e]):void 0}function Ne(t,e,a){const{x:i,y:s}=t.center,n=bt(t.orientation,i,s),o=st(Ot(e?.x??!1,e?.y??!1,i,s),bt(t.orientation,i,s)),r=st(Ot(a?.flipX??!1,a?.flipY??!1,i,s),bt(a?.rotation??0,i,s));return{bakedToScreen:r,physicalToScreen:st(r,n),gridToScreen:st(r,o)}}function X(t,e=!1){return{points:[t],closed:e}}function Ee(t,e){const{center:a,radius:i,notch:s}=t;let n=a.x+i*Math.cos(e),o=a.y+i*Math.sin(e);if(!s)return{x:n,y:o};const r=Math.sqrt(i**2-(s.length/2)**2),f=s.length/2,p=n-a.x,y=o-a.y;return s.type==="bottom"&&y<-r?(o=a.y-r,n=a.x+Math.max(-f,Math.min(f,p))):s.type==="top"&&y>r?(o=a.y+r,n=a.x+Math.max(-f,Math.min(f,p))):s.type==="left"&&p<-r?(n=a.x-r,o=a.y+Math.max(-f,Math.min(f,y))):s.type==="right"&&p>r&&(n=a.x+r,o=a.y+Math.max(-f,Math.min(f,y))),{x:n,y:o}}function Yt(t,e){return t.map(e).join(" / ")}function Ae(t,e=256){const a=Array.from({length:e},(i,s)=>t(s/(e-1)));return i=>a[Math.min(e-1,Math.max(0,Math.round(i*(e-1))))]}function He(t,e){const a=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(a*.55,i*1.8))))}function _e(t,e,a){if(e?.length){const n=e.flatMap(o=>{const r=o.testNumber;if(!vt(o)){const p=U(t,r,o);return p===void 0?[]:[{key:r,label:lt(o,r),value:p?"Pass":"Fail"}]}const f=L(t,r);return f===void 0?[]:[{key:r,label:lt(o,r),value:kt(f,o.unit,a),recordedFail:be(t,r)===!1}]});if(n.length)return n}const i=ye(t);if(i.length)return i.map(([n,o])=>({key:n,label:rt(void 0,n),value:kt(o,void 0,a)}));const s=ge(t);return s.length?s.map(([n,o])=>({key:n,label:rt(void 0,n),value:o?"Pass":"Fail"})):[]}export function buildHoverText(t,e,a={}){const{testDefs:i,hbinDefs:s,sbinDefs:n,fallbackFormat:o,aggrMethod:r,lotSize:f,activeTest:p,reticleConfig:y,metadataFields:B}=a,F=s?new Map(s.map(d=>[d.bin,d])):null,T=n?new Map(n.map(d=>[d.bin,d])):null,b=[R(`Die (${t.x}, ${t.y})`)];if(y&&pe(t)){const d=xe(t,y);b.push(R(`Reticle (${d.column}, ${d.row})`))}if(e==="stackedValues"){const d=L(t,0);if(d!==void 0){const u=i?.[0],h=u?lt(u,u.testNumber):"Value",S=r?` (${r})`:"";b.push(R(`${h}${S}: ${kt(d,u?.unit,o)}`));const m=qt(u);m&&b.push(`<i>${R(m)}</i>`)}}else if(e==="stackedBins"||e==="stackedSoftBins"){const d=L(t,0),u=e==="stackedSoftBins"?t.sbin:t.hbin;if(d!==void 0){const h=e==="stackedSoftBins"?T:F,S=u!==void 0?h?.get(u)?.name?`${u} \xB7 ${h.get(u).name}`:`Bin ${u}`:"Bin";let m;if(r==="percent")m=`${d.toFixed(0)}%`;else{const D=f?` (${(d/f*100).toFixed(0)}% of stacked wafers)`:"";m=`${d}${D}`}const x=r?` [${Wt(r)}]`:"";b.push(R(`${S}: ${m}${x}`))}}else{const d=_e(t,i,o);if(e==="value"&&d.length){const{testNumber:u}=resolveTestNumber(p??0,i),h=findTestDef(i,u),S=L(t,u);let m=d.findIndex(A=>A.key===u);m<0&&(m=0);const x=d[m];let D=`<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")&&(D+=" <i>(outside test limits)</i>")}b.push(D);const E=qt(findTestDef(i,x.key));E&&b.push(`<i>${R(E)}</i>`);const g=d.length-1;g>0&&b.push(`<i>+${g} more test${g===1?"":"s"}</i>`)}else if(d.length){const u=d.length;b.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=F?.get(t.hbin)?.name,S=h?`${t.hbin} \xB7 ${h}`:String(t.hbin);u.push(`HBin: ${S}`)}if(t.sbin!==void 0){const h=T?.get(t.sbin)?.name,S=h?`${t.sbin} \xB7 ${h}`:String(t.sbin);u.push(`SBin: ${S}`)}b.push(u.map(R).join(" \xB7 "))}}t.retestCount!==void 0&&b.push(R(`Retests: ${t.retestCount}`)),t.siteNum!==void 0&&b.push(R(`Site: ${t.siteNum}`)),t.partId!==void 0&&b.push(R(`Part ID: ${t.partId}`)),t.partial&&b.push("<i>partial die</i>"),t.probeIndex!==void 0&&b.push(R(`Probe: #${t.probeIndex}`));const k={...t.metadata??{}};for(const[d,u]of Object.entries(k)){const h=Se(u);if(h===void 0)continue;const S=B?.find(m=>m.key===d)?.label??Kt(d);b.push(R(`${S}: ${h}`))}return b.join("<br>")}function $t(t){return t?.name?lt(t,t.testNumber):void 0}function Tt(t){return Fe(t)?{note:`${Ve} ${Le}`}:{}}export function buildMapTitle(t,e="engineering",a){const i=t.isLotStack&&t.lotSize!==void 0?`stacked (${t.lotSize} ${t.lotSize===1?"wafer":"wafers"})`:"stacked",s=n=>{const o=a?.length===1?a[0]:void 0;return o===void 0?`${n} Bin`:o.name?`${n} Bin ${o.bin} \xB7 ${o.name}`:`${n} 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 n=t.metadataFields?.find(r=>r.key===t.activeMetadataKey),o=t.activeMetadataKey?Kt(t.activeMetadataKey):"Metadata";return{primary:n?.label??o,secondary:""}}case"stackedValues":{const n=t.testDefs?.[0],o=t.valueRange[1]||t.valueRange[0]||0,{axisLabel:r}=Bt(o,$t(n),n?.unit,e),f=r||"Value",p=t.aggrMethod?` \xB7 ${Wt(t.aggrMethod)}`:"";return{primary:`${f}${p}`,secondary:i,...Tt(n)}}default:{const n=findTestDef(t.testDefs,t.activeTest),o=t.valueRange[1]||t.valueRange[0]||0,{axisLabel:r}=Bt(o,$t(n),n?.unit,e),f=r||$t(n);if(t.passFailDisplay!=="off"){const p=n?.testNumber??t.activeTest,y=f?`${f} \xB7 #${p}`:rt(void 0,p),B=t.passFailDisplay==="spec"?"Limit pass/fail":n!==void 0&&!vt(n)?"Functional pass/fail":"Tester pass/fail";return{primary:y,secondary:B,...Tt(n)}}return{primary:f??rt(void 0,t.activeTest),secondary:"",...Tt(n)}}}}export function generateTextOverlay(t,e,a){const{plotMode:i,colorFns:s,normalize:n,activeTest:o,valueRange:r,testDefs:f,fallbackFormat:p,passFailDisplay:y="off",activeMetadataKey:B,metadataColorMap:F,allIntegerValues:T}=a,b=findTestDef(f,o),{tickFmt:k}=Bt(r[1],b?.name,b?.unit,p),d=T?h=>String(Math.round(h)):k,{testNumber:u}=resolveTestNumber(o,f);return t.flatMap((h,S)=>{let m="",x="#111111";if(i==="value"&&y==="test"){const g=U(h,u,b);if(g===void 0)return[];m=g?"P":"F",x=Q(g?Mt:St)}else if(i==="value"){const g=L(h,u);if(g===void 0)return[];m=Yt([g],d),x=Q(s.forValue(n(g)))}else if(i==="hardBin"||i==="softBin"){const g=i==="softBin"?h.sbin:h.hbin;if(g===void 0)return[];m=String(g),x=Q(s.forBin(g))}else if(i==="metadata"){const g=ct(h,B);if(g===void 0)return[];m=g,x=Q(F?.get(m)??q)}else{const g=L(h,0);if(g===void 0)return[];m=Yt([g],d),x=Q(s.forValue(n(g)))}const D=e?e[S*2]:h.physX,E=e?e[S*2+1]:h.physY;return[{x:D,y:E,text:m,fontSize:He(h,m),color:x,align:"center"}]})}function jt(t){return t==="top"?Math.PI/2:t==="bottom"?-Math.PI/2:t==="left"?Math.PI:0}function Oe(t,e,a=720){const{center:i,radius:s,notch:n}=t,o=k=>G(e,k.x,k.y);if(!(n!==void 0&&t.diameter>150)){const k=[];for(let d=0;d<=a;d++){const u=2*Math.PI*d/a;k.push(o(Ee(t,u)))}return[{kind:"wafer-boundary",...X(k,!0),lineColor:"#888888",lineWidth:1}]}const f=1.25,p=n.length,y=jt(n.type),B=Math.atan2(p,s),F=s-f,T=[];let b=!1;for(let k=0;k<=a;k++){const d=2*Math.PI*k/a;let u=d-y;for(;u>Math.PI;)u-=2*Math.PI;for(;u<-Math.PI;)u+=2*Math.PI;if(Math.abs(u)<=B){if(!b){b=!0;const h={x:i.x+s*Math.cos(y-B),y:i.y+s*Math.sin(y-B)},S={x:i.x+F*Math.cos(y),y:i.y+F*Math.sin(y)},m={x:i.x+s*Math.cos(y+B),y:i.y+s*Math.sin(y+B)};T.push(o(h),o(S),o(m))}continue}T.push(o({x:i.x+s*Math.cos(d),y:i.y+s*Math.sin(d)}))}return[{kind:"wafer-boundary",...X(T,!0),lineColor:"#888888",lineWidth:1}]}function We(t,e,a,i=360){const s=[],n=Math.max(1,Math.floor(a));for(let o=1;o<n;o++){const r=t.radius*o/n,f=[];for(let p=0;p<=i;p++){const y=2*Math.PI*p/i,B={x:t.center.x+r*Math.cos(y),y:t.center.y+r*Math.sin(y)};f.push(G(e,B.x,B.y))}s.push({kind:"ring-boundary",...X(f,!0),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5})}return s}function we(t,e,a,i){const{x:s,y:n}=t.center,o=t.radius,r=(d,u)=>G(e,d,u),f=a-s,p=Math.sqrt(Math.max(0,o*o-f*f)),y=r(a,n-p),B=r(a,n+p),F=i-n,T=Math.sqrt(Math.max(0,o*o-F*F)),b=r(s-T,i),k=r(s+T,i);return[{kind:"quadrant-boundary",...X([y,B]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5},{kind:"quadrant-boundary",...X([b,k]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5}]}function ze(t,e){return t.map(a=>{const i=a.width/2,s=a.height/2;return{kind:"reticle",points:[[[a.x-i,a.y-s],[a.x+i,a.y-s],[a.x+i,a.y+s],[a.x-i,a.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 Ke(t){const e=t.filter(a=>a.probeIndex!==void 0).sort((a,i)=>(a.probeIndex??0)-(i.probeIndex??0));return e.length?[{kind:"probe-path",...X(e.map(a=>({x:a.physX,y:a.physY}))),lineColor:"rgba(220,80,0,0.55)",lineWidth:1}]:[]}function qe(t,e,a,i,s,n,o,r,f,p,y,B,F,T,b="off",k,d,u){const h=e.width-o,S=e.height-o,m=n?S:h,x=n?h:S,D=M=>s==="softBin"?M.sbin:M.hbin,E=M=>ct(M,k);if(e.partial){t.push({x:a,y:i,width:m,height:x,fill:Re,type:"stacked",metadata:e.metadata});return}if(e.edgeExcluded){t.push({x:a,y:i,width:m,height:x,fill:Pe,type:"stacked",metadata:e.metadata});return}if(f!==void 0&&(s==="hardBin"||s==="softBin")&&!f.has(D(e))){t.push({x:a,y:i,width:m,height:x,fill:Xt,type:"hardBin",metadata:e.metadata});return}if(u!==void 0&&s==="metadata"&&!u.has(E(e))){t.push({x:a,y:i,width:m,height:x,fill:Xt,type:"metadata",metadata:e.metadata});return}if(s==="value"){if(b==="test"){const w=U(e,y,T),ut=w===void 0?q:w?Mt:St;t.push({x:a,y:i,width:m,height:x,fill:ut,type:"value",metadata:e.metadata});return}const M=L(e,y),P=ot(M,T);let W,J;M===void 0?W=q:b==="spec"?W=P==="failLow"?ke:P==="failHigh"?St:Mt:(W=r.forValue(p(M)),(P==="failLow"||P==="failHigh")&&(J=P)),t.push({x:a,y:i,width:m,height:x,fill:W,type:"value",specMark:J,metadata:e.metadata});return}if(s==="hardBin"||s==="softBin"){const M=D(e),P=M!=null?r.forBin(M):q,W=M!=null&&Ie(e,F)===!1?!0:void 0;t.push({x:a,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?d?.get(M)??q:q;t.push({x:a,y:i,width:m,height:x,fill:P,type:"metadata",metadata:e.metadata});return}const g=L(e,0),A=g!==void 0?r.forValue(p(g)):q;t.push({x:a,y:i,width:m,height:x,fill:A,type:"value",metadata:e.metadata})}function Xe(t,e,a){const i=t.radius*.15,s=xt(e,i,0),n=xt(e,0,i),o=s.x+n.x>=0?-1:1,r=s.y+n.y>=0?-1:1,f={x:t.center.x+o*t.radius*.9,y:t.center.y+r*t.radius*.9},p={x:f.x+s.x,y:f.y+s.y},y={x:f.x+n.x,y:f.y+n.y};return a.push({x:p.x+s.x*.35,y:p.y+s.y*.35,text:"+X",fontSize:10,color:"#cc3300",align:"center",role:"indicator"},{x:y.x+n.x*.35,y:y.y+n.y*.35,text:"+Y",fontSize:10,color:"#0044cc",align:"center",role:"indicator"}),[{kind:"xy-indicator",...X([f,p]),lineColor:"#cc3300",lineWidth:2},{kind:"xy-indicator",...X([f,y]),lineColor:"#0044cc",lineWidth:2}]}export function buildView(t,e,a={},i){const s=a.reticles??[],{plotMode:n="value",showDieLabels:o=!1,showReticle:r=!1,showProbePath:f=!1,ringCount:p=4,showRingBoundaries:y=!1,showQuadrantBoundaries:B=!1,showXYIndicator:F=!1,binColorScheme:T="default",valueColorScheme:b="default",reverseValueScheme:k=!1,useDefinedBinColors:d=!0,binColors:u,passBins:h=[1],highlightBin:S,highlightMetadataValue:m,interactiveTransform:x,valueRange:D,metadataValueOrder:E,testDefs:g,activeTest:A=0,activeMetadataKey:M,fallbackFormat:P="engineering",aggregationMethod:W,lotSize:J,dataAxisFlip:w,isLotStack:ut=!1,logScale:Gt,colorbarRangeMode:Qt="spec",passFailDisplay:Ut}=a,It=Ut??"off",ft=i?.hbinDefs,dt=i?.sbinDefs,Dt=i?.metadataFields,Jt=Dt?.find(l=>l.key===M),Zt={x:(w?.x??!1)!==(x?.flipX??!1),y:(w?.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=n==="softBin"?ee:te,ae=new Set(h),ht=u&&Te(u,e)?u:$e(e,{passBins:h,binColorScheme:T,hbinDefs:ft,sbinDefs:dt,useDefinedBinColors:d}),ie=n==="softBin"?ht.soft:ht.hard,Ct={forValue:Ae(Be(b,k)),forBin:l=>ie.get(l)??q};let{testNumber:H}=resolveTestNumber(A,g);if(!g?.length&&n==="value"){const l=_t(e,"values");!l.includes(H)&&l.length&&(H=l[0])}let Z;if(Array.isArray(D))Z=D;else if(D){const{testNumber:l}=resolveTestNumber(D.test,g);Z=l===H?D.range:void 0}const I=findTestDef(g,H),Ft=n==="value"&&I!==void 0&&!vt(I),se=I!==void 0&&(I.limitLow!==void 0||I.limitHigh!==void 0);let O="off";n==="value"&&(Ft?O="test":It==="spec"&&se?O="spec":It==="test"&&e.some(l=>U(l,H,I)!==void 0)&&(O="test"));const Vt=O==="spec"?"spec":Qt,mt=n==="stackedValues"||n==="stackedBins"||n==="stackedSoftBins";let N,_,tt=!1;if(Z){[N,_]=Z;let l=!0,c=!1;for(const v of e){const V=mt?L(v,0):L(v,H);if(V!==void 0&&(c=!0,!Number.isInteger(V))){l=!1;break}}tt=c&&l}else{const l=!mt&&n==="value"&&Vt==="spec"&&I&&(I.limitLow!==void 0||I.limitHigh!==void 0);let c=1/0,v=-1/0,V=!0;for(const $ of e){const K=mt?L($,0):L($,H);K!==void 0&&(K<c&&(c=K),K>v&&(v=K),Number.isInteger(K)||(V=!1))}isFinite(c)||(V=!1),tt=V,l?(N=I.limitLow!==void 0?I.limitLow:isFinite(c)?c:0,_=I.limitHigh!==void 0?I.limitHigh:isFinite(v)?v:1):(N=isFinite(c)?c:0,_=isFinite(v)?v:1)}N===_&&(_=N+1);const Lt=Ft?!1:Gt??I?.logScale??!1,Rt=Lt&&N>0&&_>0,oe=Rt;let et;if(Rt){const l=Math.log10(N),c=Math.log10(_)-l;et=v=>v<=0?0:zt((Math.log10(v)-l)/c)}else et=l=>zt((l-N)/(_-N));const C=Ne(t,w,x);let Pt=null;if(t.notch){const l=jt(t.notch.type),c=xt(C.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 Y=1/0;for(let l=0;l<e.length;l++){const c=e[l];c.width<Y&&(Y=c.width),c.height<Y&&(Y=c.height)}const Nt=1,re=Y!==1/0?Math.min(Nt,Y*.12):Nt,Et=[],pt=[],le=C.bakedToScreen.a===1&&C.bakedToScreen.b===0&&C.bakedToScreen.c===0&&C.bakedToScreen.d===1&&C.bakedToScreen.e===0&&C.bakedToScreen.f===0;let z=null;if(!le){z=new Float64Array(e.length*2);for(let l=0;l<e.length;l++){const c=G(C.bakedToScreen,e[l].physX,e[l].physY);z[l*2]=c.x,z[l*2+1]=c.y}}const ce=ve(C.gridToScreen),ue=n==="hardBin"||n==="softBin"||n==="stackedBins"||n==="stackedSoftBins",fe=n==="softBin"||n==="stackedSoftBins";let nt=ue?new Map:void 0;const yt=n==="value"&&O==="spec"?{pass:0,failHigh:0,failLow:0}:void 0,gt=n==="value"&&O==="test"?{pass:0,fail:0}:void 0,At=n==="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(V=>!c.includes(V))]:l;it=new Map(v.map((V,$)=>[V,Jt?.values?.find(K=>K.value===V)?.color??De($)]))}const de=S===void 0?void 0:new Set(wt(S)),he=m===void 0?void 0:new Set(wt(m));for(let l=0;l<e.length;l++){const c=e[l],v=z?z[l*2]:c.physX,V=z?z[l*2+1]:c.physY;if(pt.push({x:v,y:V}),nt&&!c.partial&&!c.edgeExcluded){const $=fe?c.sbin:c.hbin;$!=null&&nt.set($,(nt.get($)??0)+1)}if(yt&&!c.partial&&!c.edgeExcluded){const $=ot(L(c,H),I);$&&yt[$]++}if(gt&&!c.partial&&!c.edgeExcluded){const $=U(c,H,I);$!==void 0&>[$?"pass":"fail"]++}if(at&&!c.partial&&!c.edgeExcluded){const $=ct(c,M);$!==void 0&&at.set($,(at.get($)??0)+1)}qe(Et,c,v,V,n,ce,re,Ct,de,et,H,ne,ae,I,O,M,it,he)}const Ht=o?generateTextOverlay(e,z,{plotMode:n,colorFns:Ct,normalize:et,activeTest:A,valueRange:[N,_],testDefs:g,fallbackFormat:P,passFailDisplay:O,activeMetadataKey:M,metadataColorMap:it,allIntegerValues:tt}):[],j=Oe(t,C.physicalToScreen);y&&j.push(...We(t,C.physicalToScreen,p)),B&&j.push(...we(t,C.physicalToScreen,t.center.x,t.center.y)),r&&j.push(...ze(s,C.gridToScreen)),f&&j.push(...Ke(e)),F&&j.push(...Xe(t,C.gridToScreen,Ht));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;return{rectangles:Et,hoverPoints:pt,texts:Ht,overlays:j,plotMode:n,binColorScheme:T,valueColorScheme:b,reverseValueScheme:k,binColors:ht,metadata:t.metadata??null,dies:e,valueRange:[N,_],allIntegerValues:tt,testDefs:g,activeTest:A,logScale:oe,logScaleRequested:Lt,aggrMethod:W,lotSize:J,axisFlip:Zt,dataAxisFlip:{x:w?.x??!1,y:w?.y??!1},gridToScreen:C.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:Pt,binCounts:nt,metadataCounts:at,metadataColorMap:it,activeMetadataKey:M,metadataFields:Dt,specCounts:yt,passFailCounts:gt,dieBounds:me}}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 G}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 H,finaliseDie as j,linkDie as ve,recordedVerdict as Me,recordedVerdictsReader as Ce,tableFromRows as Se,testValue as q,testsPresent as Ae,VERDICT_FAIL as ke,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 Ye}from"./derivedTests/apply.js";import{isStdfBin as Le,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 Ue}from"../core/stdf.js";import{hasAnyKey as ze,maxOf as J,minOf as re,modeOf as Ge}from"../core/utils.js";import{aggregateValues as oe,aggregateBinCounts as He}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 qe(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 Je(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):Le(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 A=l[O];if(!(A===null||typeof A!="object")){if(A.testValues&&typeof A.testValues=="object")for(const M of Object.keys(A.testValues))b.add(M);if(A.testPass&&typeof A.testPass=="object")for(const M of Object.keys(A.testPass))w.add(M)}}const g=[...b],y=[...w],k=O=>ie(Number(O));for(const O of new Set([...g,...y]))k(O)||r.add(O);const R=g.map(k),v=y.map(k),N=g.map(Number),B=y.map(Number);let I;for(let O=0;O<l.length;O++){const A=l[O];if(A===null||typeof A!="object")continue;const M=A;let L;const _=()=>L??={...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 Y=typeof M.x=="number"&&!ne(M.x),f=typeof M.y=="number"&&!ne(M.y);if(Y||f){s.positions+=(Y?1:0)+(f?1:0);const C=_();C.x=void 0,C.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 S=M.testValues;if(S&&typeof S=="object"){let C;for(let $=0;$<g.length;$++){const E=g[$],W=S[N[$]];if(W!=null){if(!R[$]){C??={...S},delete C[E];continue}typeof W!="number"?(o("testValues",W),C??={...S},delete C[E]):Number.isFinite(W)||(s.values++,C??={...S},delete C[E])}}C&&(_().testValues=C)}const T=M.testPass;if(T&&typeof T=="object"){let C;for(let $=0;$<y.length;$++){const E=y[$],W=T[B[$]];if(W!=null){if(!v[$]){C??={...T},delete C[E];continue}typeof W!="boolean"&&(o("testPass",W),C??={...T},delete C[E])}}C&&(_().testPass=C)}L&&((I??=l.slice())[O]=L)}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${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 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 Qe(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=G({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 Ze(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=Je(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],...qe(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&&H(t[0])!==void 0?t:te(t).records}function et(t,e){return e?.coordinateOrigin?e.coordinateOrigin:{type:"center"}}function tt(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 nt(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 it(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 st(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 Ae(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:ze(c)?c:void 0}))}if(n==="countBin"||n==="percent"){if(i===void 0)return[];const o=He(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=Ge(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)),k=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>=k&&v.physY<R)})}function rt(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 ot=.2;function at(t,e,n){const i=new Set(n);function a(g,y){if(e==="last")return!0;if(e==="first")return!1;const k=K(g.hbin),R=K(y.hbin);if(k===void 0||R===void 0)return!1;const v=i.has(k),N=i.has(R);return e==="best"?N!==v?N:R<k:N!==v?v:R>k}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)<=ot,c=new Map,p=[],u=new Map;let x=0,D=0;for(const g of t){const y=s(g);let k;if(F(g)?k=`p:${X(g)}`:d&&y!==void 0?k=`i:${y}`:k=`u:${D++}`,g.supersedes==="partId"&&y!==void 0){const v=u.get(y);v!==void 0&&v!==k&&c.has(v)&&c.delete(v)}const R=c.get(k);R===void 0?(c.set(k,{winner:g,count:1}),p.push(k)):(R.count++,k.startsWith("i:")&&x++,o(R.winner,g)&&(R.winner=g)),y!==void 0&&u.set(y,k)}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=Ce(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:ke)}return s}}export function dieHasTestData(t){return De(t)}function K(t){return t===void 0||Number.isNaN(t)?void 0:t}function U(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=H(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 dt="do not fit inside the supplied",ft="Inferred wafer diameter",ut="The probed dies reach only",lt=.75,ct=20;export const STANDARD_WAFER_DIAMETERS_MM=[100,125,150,200,300];const ht=.02;function me(t,e){return e.reduce((n,i)=>Math.abs(i-t)<Math.abs(n-t)?i:n)}function pt(t,e){if(!e.length)return!0;const n=me(t,e);return Math.abs(n-t)/n<=ht}function mt(t){return t===void 0?STANDARD_WAFER_DIAMETERS_MM:t.filter(e=>Number.isFinite(e)&&e>0)}export function buildWaferMap(t){const e=Ze(!Array.isArray(t)&&t.layout?Qe(t):t),n=[];if(e.derivedTests){const f=new Set(e.passBins),S=$=>{const E=$.length>0?H($[0]):void 0,W=Ye($,E?.table??Se([]),e.testDefs,e.derivedTests,f);return E&&Te($,W.table),W},C=(e.lotStackOpts?e.lotStackOpts.results:[e.results]).map(S);e.testDefs=C[0]?.testDefs??e.testDefs,n.push(...C[0]?.warnings??[])}const i=e.lotStackOpts?te(st(e.lotStackOpts,e.testDefs)).records:e.results;for(let f=0;f<Math.min(5,i.length);f++){const S=i[f];if(typeof S.x=="string"||typeof S.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}=at(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 S=e.explicitDies.filter(V=>!F(V)),T=a.filter(F),C=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?U(P,Z):P})}const $=e.waferOpts?.diameter??300,E=G({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=C.map((V,P)=>j(U({id:`unpositioned_${P}`,width:f[0]?.width??1,height:f[0]?.height??1},V))),z=[...f,...S,...ge];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]:[],...pe(r,s),...n,...e.ringCountWarning?[e.ringCountWarning]:[]],plotMode:he(a),metadata:E.metadata??null,isLotStack:!1,dataCoverage:fe(z),passBins:e.passBins,ringCount:e.ringCount,yield:ue(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=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=et(m,e.dieOpts),l=Ve(d);s.grid={confidence:l.confidence};const{offsetX:b,offsetY:w}=tt(d,D,l),{flipX:g,flipY:y}=de(e.dieOpts,D),{colMidX:k,colMidY:R,anchored:v}=nt(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-k,y:(Math.round(f.y)-w)*u-R})),I=J(B.map(({x:f,y:S})=>Math.hypot(Math.abs(f)+p/2,Math.abs(S)+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 S=mt(e.standardDiameters);if(!pt(N,S)){const T=me(N,S);r.push({code:"non-standard-diameter",message:`${ft} ${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&&it(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>=ct){const f=I/(N/2);f<lt&&r.push({code:"diameter-exceeds-die-extent",message:`${ut} ${(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 S=B.filter(({x:T,y:C})=>Math.hypot(Math.abs(T)+p/2,Math.abs(C)+u/2)>N/2+1e-9).length;r.push({code:"geometry-conflict",message:`${S} of ${B.length} probed die positions ${dt} ${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=G({diameter:N,notch:e.waferOpts?.notch,orientation:e.waferOpts?.orientation??0,metadata:e.waferOpts?.metadata});let A=m.map(f=>{const S=Math.round(f.x)-b,T=Math.round(f.y)-w,C={id:`${S}_${T}`,x:S,y:T,physX:S*p-k,physY:T*u-R,width:p,height:u,insideWafer:!0,partial:!1};return U(C,f)});(b!==0||w!==0)&&(A=A.map(f=>({...f,x:f.x+b,y:f.y+w,id:`${f.x+b}_${f.y+w}`}))),A=ee(A,O),(g||y)&&(A=be(A,{flipX:g,flipY:y},O.center));const M=[];e.waferOpts?.edgeExclusion&&e.waferOpts.edgeExclusion>0&&(A=rt(A,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"})),A=A.map(j);const L=le(e.reticleOpts,O,A,p,u,b,w,k,R,O.orientation,g,y),_=h.map((f,S)=>j(U({id:`unpositioned_${S}`,width:p,height:u},f))),Y=[...A,..._];return{wafer:O,dies:Y,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(Y),passBins:e.passBins,ringCount:e.ringCount,yield:ue(Y,e.passBins,e.edgeDieYieldMode),reticles:L,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}}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type CompactMap } from '../core/compact.js';
|
|
2
|
+
import type { View } from './buildView.js';
|
|
3
|
+
/**
|
|
4
|
+
* Lay a built view's dies out on a compact grid.
|
|
5
|
+
*
|
|
6
|
+
* Only positions move. Every die keeps its colour, rectangle size, label and place in
|
|
7
|
+
* `view.dies`, so legends, tallies, hit-testing and statistics, all of which read the same
|
|
8
|
+
* dies, are unchanged. Compact cells are one die pitch apart, so the grid has the uniform
|
|
9
|
+
* pitch hit-testing assumes; their placement goes through the linear part of
|
|
10
|
+
* `view.gridToScreen`, so rotation and flips act on the compact grid exactly as they do on
|
|
11
|
+
* the physical one, about the centre of the grid.
|
|
12
|
+
*
|
|
13
|
+
* The wafer outline, ring and quadrant boundaries and reticle grid describe the physical
|
|
14
|
+
* wafer, which these positions no longer follow, so they are replaced by one outline per
|
|
15
|
+
* group of dies. The notch marker is kept: `notchDir` is untouched and the wafer circle it
|
|
16
|
+
* is measured from is replaced by the grid's own edge. The +X/+Y indicator names the
|
|
17
|
+
* die-grid axes, not the wafer, so it stays, drawn in a margin beside the grid.
|
|
18
|
+
*/
|
|
19
|
+
export declare function compactView(view: View, map: CompactMap, options?: {
|
|
20
|
+
showXYIndicator?: boolean;
|
|
21
|
+
}): View;
|
|
22
|
+
//# sourceMappingURL=compactView.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{compactDies as A}from"../core/compact.js";import{maxOf as W,minOf as D}from"../core/utils.js";import{buildXYIndicator as E}from"./xyIndicator.js";export function compactView(o,s,H={}){const{placements:u}=A(o.dies,s),l=s.columns.length,h=s.rows.length;if(u.length===0||l===0||h===0)return o;const y=o.dies[0].width,d=o.dies[0].height,Y=(l-1)/2,g=(h-1)/2,{a:b,b:w,c:$,d:k}=o.gridToScreen,B=(n,t)=>{const r=(n-Y)*y,x=(t-g)*d;return{x:b*r+$*x,y:w*r+k*x}},f=new Map,O=o.hoverPoints.map((n,t)=>{const r=B(u[t].column,u[t].row);return f.set(`${n.x},${n.y}`,r),r}),P=o.rectangles.map(n=>{const t=f.get(`${n.x},${n.y}`);return t?{...n,x:t.x,y:t.y}:n}),p=[];for(const n of o.texts){if(n.role==="indicator")continue;const t=f.get(`${n.x},${n.y}`);p.push(t?{...n,x:t.x,y:t.y}:n)}const c=(n,t)=>B(n-.5,t-.5),C=s.groups.map(n=>({kind:"compact-group",points:[[c(n.columns[0],n.rows[0]),c(n.columns[1],n.rows[0]),c(n.columns[1],n.rows[1]),c(n.columns[0],n.rows[1])]],closed:!0,lineColor:"#888888",lineWidth:1})),I=[c(0,0),c(l,0),c(l,h),c(0,h)],R=I.map(n=>n.x),S=I.map(n=>n.y),a=(W(R)-D(R))/2,e=(W(S)-D(S))/2,M=Math.abs(b)*y+Math.abs($)*d,X=Math.abs(w)*y+Math.abs(k)*d,m={minX:0,maxX:0,minY:0,maxY:0};if(H.showXYIndicator){const n=Math.max(M,X),t=2*n,{overlays:r,signX:x,signY:j}=E(o.gridToScreen,.15*Math.max(a,e),(q,z)=>({x:q*(a+t),y:z*(e+t)}),p);C.push(...r),m[x<0?"minX":"maxX"]=t+n/2,m[j<0?"minY":"maxY"]=t+n/2}const T={minX:-a-m.minX+M/2,maxX:a+m.maxX-M/2,minY:-e-m.minY+X/2,maxY:e+m.maxY-X/2},i=o.notchDir,V=i?Math.min(Math.abs(i.x)>1e-9?a/Math.abs(i.x):1/0,Math.abs(i.y)>1e-9?e/Math.abs(i.y):1/0):Math.max(a,e);return{...o,rectangles:P,hoverPoints:O,texts:p,overlays:C,hasReticle:!1,waferCenter:{x:0,y:0},waferRadius:V,dieBounds:T,compact:{columns:s.columns,rows:s.rows,columnBreaks:s.columnBreaks,rowBreaks:s.rowBreaks,centreColumn:Y,centreRow:g}}}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type Affine } from '../core/transforms.js';
|
|
2
|
+
import type { ViewOverlay, ViewText } from './buildView.js';
|
|
3
|
+
interface Point {
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The +X/+Y axis arrows, in display space. The ONE place their direction, colours and labels
|
|
9
|
+
* are decided, for the wafer view and the compact view alike; the two differ only in where the
|
|
10
|
+
* arrows sit, which each supplies through `anchorAt`.
|
|
11
|
+
*
|
|
12
|
+
* The arrows name the DIE-GRID axes (+X = increasing `die.x`), so they take `gridToScreen`,
|
|
13
|
+
* including the data-axis flip: a transform that omitted it made the arrows point opposite to
|
|
14
|
+
* the way the die indices run, contradicting the axis tick labels on the same map. As
|
|
15
|
+
* directions rather than positions they go through `affineVector`, which ignores translation.
|
|
16
|
+
*
|
|
17
|
+
* `anchorAt` is given the signs of the corner the arrows point AWAY from, so they extend into
|
|
18
|
+
* the space beside that corner and never clip; the anchor itself is never transformed, so it
|
|
19
|
+
* stays in its corner whatever the rotation or flip.
|
|
20
|
+
*/
|
|
21
|
+
export declare function buildXYIndicator(gridToScreen: Affine<'grid', 'screen'>, len: number, anchorAt: (signX: 1 | -1, signY: 1 | -1) => Point, texts: ViewText[]): {
|
|
22
|
+
overlays: ViewOverlay[];
|
|
23
|
+
signX: 1 | -1;
|
|
24
|
+
signY: 1 | -1;
|
|
25
|
+
};
|
|
26
|
+
export {};
|
|
27
|
+
//# sourceMappingURL=xyIndicator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{affineVector as l}from"../core/transforms.js";export function buildXYIndicator(r,x,s,a){const o=l(r,x,0),i=l(r,0,x),e=o.x+i.x>=0?-1:1,y=o.y+i.y>=0?-1:1,n=s(e,y),t={x:n.x+o.x,y:n.y+o.y},c={x:n.x+i.x,y:n.y+i.y};return a.push({x:t.x+o.x*.35,y:t.y+o.y*.35,text:"+X",fontSize:10,color:"#cc3300",align:"center",role:"indicator"},{x:c.x+i.x*.35,y:c.y+i.y*.35,text:"+Y",fontSize:10,color:"#0044cc",align:"center",role:"indicator"}),{overlays:[{kind:"xy-indicator",points:[[n,t]],closed:!1,lineColor:"#cc3300",lineWidth:2},{kind:"xy-indicator",points:[[n,c]],closed:!1,lineColor:"#0044cc",lineWidth:2}],signX:e,signY:y}}
|
|
@@ -1,4 +1,32 @@
|
|
|
1
1
|
import type { AnalyzeWaferLotInput, AnalyzeWaferMapOptions, LotStatsSummary, StatsSummary } from './types.js';
|
|
2
|
+
export interface WaferOutlier {
|
|
3
|
+
/** Position in the `yields` array passed in. */
|
|
4
|
+
index: number;
|
|
5
|
+
severity: 'notable' | 'unusual';
|
|
6
|
+
/** The test's own statistic: Dixon's Q, or the distance from the median in IQR units. */
|
|
7
|
+
statistic: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The one rule for "outlier wafer", over per-wafer yields — the lot findings and
|
|
11
|
+
* the Summary panel's yield bars both read it, so the list and the findings never
|
|
12
|
+
* disagree about a wafer.
|
|
13
|
+
*
|
|
14
|
+
* - 3–7 wafers: Dixon's Q test on the lowest and highest wafer — gap to its
|
|
15
|
+
* nearest neighbour over the range. Beyond the 95% critical value is
|
|
16
|
+
* `notable`, beyond 99% `unusual`.
|
|
17
|
+
* - 8 or more: Tukey's fences — beyond 1.5 × IQR from the quartiles is
|
|
18
|
+
* `notable`, beyond 3 × IQR `unusual`.
|
|
19
|
+
* - Fewer than 3 wafers, or no spread: none — with two wafers there is no
|
|
20
|
+
* telling which is the odd one.
|
|
21
|
+
*
|
|
22
|
+
* Both tests judge a wafer against the wafers' own spread, so either may also
|
|
23
|
+
* flag a trivially small difference in a tight lot; a wafer is an outlier only
|
|
24
|
+
* when it is also at least `OUTLIER_MIN_POINTS` yield points from the median.
|
|
25
|
+
*/
|
|
26
|
+
export declare function outlierWafers(yields: readonly number[]): {
|
|
27
|
+
method: 'dixon-q' | 'tukey-fence';
|
|
28
|
+
outliers: WaferOutlier[];
|
|
29
|
+
} | null;
|
|
2
30
|
export declare function analyzeWaferLot(items: AnalyzeWaferLotInput, options?: AnalyzeWaferMapOptions & {
|
|
3
31
|
perWaferSummaries?: StatsSummary[];
|
|
4
32
|
}): LotStatsSummary;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{analyzeWaferMap as M,candidatesOf as $,describeRegional as A,resolveOptions as q,adjustPValues as C,twoProportionZ as O,zPValue as x,welchOfSums as j,rateRelativeEffect as V,passesRateGate as _,severityForFinding as Y,severityForScore as G,collapseRedundantFindings as H}from"./analyzeWaferMap.js";import{normalizeInput as U}from"./normalizeInput.js";import{poolCapability as Z,poolRegionYield as J,poolPassRates as Q}from"./summaryFigures.js";import{mergeTestDefs as X}from"./mergeTestDefs.js";import{findingPatternKey as ee}from"./filterFindings.js";import{median as B}from"../core/utils.js";import{describeWaferPopulation as te,populationStat as ne}from"./population.js";const ie=1.3,D=2;function F(c,o){const r={info:0,notable:1,unusual:2};return r[c]>=r[o]?c:o}const se=new Set(["ring","quadrant","sector","reticle-position","test-site"]),ae=new Set(["yield","hardBin","softBin","functionalTest","test"]);function re(c){return se.has(c.comparison.family)&&ae.has(c.variable.kind)}function oe(c){const o=c.map(s=>s.facts);if(!o.length||o.some(s=>!s))return{coincident:new Set,yieldSoftBin:void 0,passBins:[]};const[r,...n]=o,l=n.every(s=>s.passBins.length===r.passBins.length&&s.passBins.every(i=>r.passBins.includes(i)));return{coincident:new Set([...r.coincident].filter(s=>n.every(i=>i.coincident.has(s)))),yieldSoftBin:n.every(s=>s.yieldSoftBin===r.yieldSoftBin)?r.yieldSoftBin:void 0,passBins:l?r.passBins:[]}}function E(c,o){let r=0,n=0,l=0,s=0,i=0,m=0,f=0,p=0,t=0,d=!0,e=0;for(const{waferIndex:h,c:u}of c){const b=o(h,u),k=u.rate??u.mean,v=Math.sqrt(k.n+b.n);if(u.rate)r+=v*O(u.rate.hits,u.rate.n,b.hits,b.n),l+=u.rate.hits,s+=u.rate.n,i+=b.hits,m+=b.n;else{const S=j(u.mean,b.n,b.sum,b.sq);r+=v*S.z,f+=u.mean.n*S.effectSize,p+=u.mean.n*S.delta,e+=u.mean.n,u.relativeDelta===void 0?d=!1:t+=u.mean.n*u.relativeDelta}n+=v*v}const a=n>0?r/Math.sqrt(n):0,y=c[0].c;if(y.rate){const h=i/m,u=l/s-h;return{z:a,delta:u,relativeDelta:h===0?void 0:u/h,size:V({hits:l,n:s,restHits:i,restN:m,passRate:y.rate.passRate})}}return{z:a,delta:p/e,relativeDelta:d?t/e:void 0,size:f/e}}function K(c,o,r,n){return c.rate?_(o,r.delta,r.size,n):o<=n.significanceLevel&&Math.abs(r.size)>=n.minimumEffectSize}function le(c,o,r){const n=new Map;for(const{waferIndex:t,byKey:d}of c)for(const e of d.values())(n.get(e.key)??n.set(e.key,[]).get(e.key)).push({waferIndex:t,c:e});const l=new Map(c.map(t=>[t.waferIndex,t.byKey])),s=(t,d)=>d.rate?{hits:d.rate.restHits,n:d.rate.restN,sum:0,sq:0}:{hits:0,n:d.mean.restN,sum:d.mean.restSum,sq:d.mean.restSq},i=[];for(const[t,d]of n){if(d.length<D)continue;const e=E(d,s);if(e.z===0)continue;const a=d[0].c;i.push({key:t,entries:d,combined:e,finding:{id:`lot-region:${t}`,level:"lot",severity:"info",variable:{...a.variable,label:a.label},comparison:{...a.comparison},effect:{direction:e.z>0?"higher":"lower",absoluteDelta:e.delta,relativeDelta:e.relativeDelta,effectSize:a.rate?e.delta:e.size},stats:{method:"stouffer-z",pValue:x(e.z),sampleSizeLeft:0,sampleSizeRight:0},summary:"",highlight:{kind:"wafer",waferIndices:[]}}})}C(i.map(t=>t.finding));const m=i.filter(t=>K(t.entries[0].c,t.finding.stats.adjustedPValue??1,t.combined,o)),f=(t,d)=>{const e=t.entries[0].c,a=d.entries[0].c;return e.source===a.source&&e.variable.kind===a.variable.kind&&e.variable.bin===a.variable.bin&&e.variable.index===a.variable.index&&e.region.family===a.region.family},p=[];for(const t of m){const d=t.finding.stats.pValue??1,e=m.filter(g=>g!==t&&f(t,g)&&g.finding.effect.direction!==t.finding.effect.direction&&(g.finding.stats.pValue??1)<d);if(e.length){const g=(I,T)=>{const z=s(I,T);for(const L of e){const w=l.get(I)?.get(L.key);w&&(w.rate?(z.hits-=w.rate.hits,z.n-=w.rate.n):(z.n-=w.mean.n,z.sum-=w.mean.sum,z.sq-=w.mean.sq))}return z},R=t.entries.filter(I=>g(I.waferIndex,I.c).n>=o.minimumSampleSize);if(!R.length)continue;const P=E(R,g),N=d>0?(t.finding.stats.adjustedPValue??d)/d:1;if(Math.sign(P.z)!==Math.sign(t.combined.z)||!K(t.entries[0].c,Math.min(1,x(P.z)*N),P,o))continue}const a=Math.sign(t.combined.z),y=t.entries.filter(g=>Math.sign(g.c.delta)===a),h=c.length,u=t.entries[0].c,b=t.finding.stats.adjustedPValue??d,k=u.rate?Y(b,t.combined.delta,t.combined.size):G(b,t.combined.size),v=F(k,y.length/h>=.6?"unusual":"info"),S={};for(const g of y)S[g.waferIndex]=[...g.c.region.dieKeys];p.push({...t.finding,severity:v,stats:{...t.finding.stats,sampleSizeLeft:y.length,sampleSizeRight:h-y.length},summary:`${A(u,t.combined.delta,t.combined.relativeDelta)} \u2014 ${t.finding.effect.direction} on ${y.length}/${h} wafers, all wafers' data combined`,highlight:{kind:"wafer",waferIndices:y.map(g=>g.waferIndex),dieKeysByWafer:S}})}return H(p,r),p}const W={"Edge-ring":"edge","Edge-local":"edge","Center cluster":"centre or donut",Donut:"centre or donut"};function ce(c){const o=new Map;for(const n of c){const l=new Set,s=new Set(n.summary.findings.flatMap(i=>i.absorbedIds??[]));for(const i of n.summary.findings){if(s.has(i.id)||re(i))continue;const m=i.variable.kind==="spatialPattern"?W[i.comparison.left]:void 0,f=m?`spatialPattern|${m}`:ee(i);if(l.has(f))continue;l.add(f);const p=o.get(f)??{finding:i,waferIndices:[],severity:i.severity,labels:new Map,dieKeysByWafer:{}};if(p.waferIndices.push(n.waferIndex),i.variable.kind==="spatialPattern"){p.labels.set(i.comparison.left,(p.labels.get(i.comparison.left)??0)+1);const t=i.highlight.dieKeys;t&&(p.dieKeysByWafer[n.waferIndex]=t)}p.severity=F(p.severity,i.severity),o.set(f,p)}}const r=[];for(const[n,l]of o){if(l.waferIndices.length<D)continue;const s=l.waferIndices.length/Math.max(1,c.length),i=s>=.6||l.severity==="unusual"?"unusual":"notable",m=l.finding;r.push({id:`lot-repeat:${n}`,level:"lot",severity:i,variable:{...m.variable},comparison:{...m.comparison},effect:{direction:m.effect.direction,absoluteDelta:m.effect.absoluteDelta,relativeDelta:m.effect.relativeDelta,effectSize:m.effect.effectSize},stats:{method:"wafer-finding-frequency",sampleSizeLeft:l.waferIndices.length,sampleSizeRight:c.length-l.waferIndices.length},summary:`${fe(m,l.labels)} \u2014 seen on ${l.waferIndices.length}/${c.length} wafers (${Math.round(s*100)}%)`,highlight:{kind:"wafer",waferIndices:l.waferIndices,...l.labels.size?{dieKeysByWafer:l.dieKeysByWafer}:{}}})}return r}function fe(c,o){if(!o.size)return c.summary;const r=s=>s.toLowerCase();if(o.size===1)return`Spatial pattern: ${r([...o.keys()][0])}`;const n=W[c.comparison.left],l=[...o].sort((s,i)=>i[1]-s[1]).map(([s,i])=>`${r(s)} on ${i}`);return`Spatial pattern: ${n} (${l.join(", ")})`}function de(c,o){const r=ne(o,"median"),n=c.map(f=>({waferIndex:f.waferIndex,yieldPercent:f.summary.stats.yieldPercent})).filter(f=>f.yieldPercent!==null);if(n.length<3)return[];const l=n.map(f=>f.yieldPercent),s=B(l),i=B(l.map(f=>Math.abs(f-s)));if(!Number.isFinite(i)||i===0)return[];const m=[];for(const f of n){const p=.6745*(f.yieldPercent-s)/i;if(Math.abs(p)<ie)continue;const t=f.yieldPercent-s;m.push({id:`inter-wafer:yield:${f.waferIndex}`,level:"inter-wafer",severity:Math.abs(p)>=2?"unusual":"notable",variable:{kind:"yield",label:"Yield"},comparison:{family:"wafer",left:`Wafer ${f.waferIndex+1}`,right:r.charAt(0).toUpperCase()+r.slice(1)},effect:{direction:t>0?"higher":"lower",absoluteDelta:t,relativeDelta:s===0?void 0:t/s,effectSize:p},stats:{method:"mad-z-score",sampleSizeLeft:1,sampleSizeRight:n.length-1},summary:`Wafer ${f.waferIndex+1} yield is ${Math.abs(t).toFixed(1)} percentage points ${t>0?"higher":"lower"} than the ${r}`,highlight:{kind:"wafer",waferIndices:[f.waferIndex]}})}return m}export function analyzeWaferLot(c,o={}){const r=c.map(U),n=r.map((e,a)=>({waferIndex:a,summary:o.perWaferSummaries?.[a]??M(e,o)})),l=n.map(({waferIndex:e,summary:a})=>$(a)??$(M(r[e],o))),s=l.map((e,a)=>({waferIndex:a,byKey:new Map(e.candidates.map(y=>[y.key,y]))})),i=[...le(s,q(o).resolved,oe(l)),...ce(n),...de(n,te(n.map(e=>e.summary.wafer)))].sort((e,a)=>{const y=e.severity==="unusual"?2:e.severity==="notable"?1:0,h=a.severity==="unusual"?2:a.severity==="notable"?1:0;return y!==h?h-y:e.summary.localeCompare(a.summary,"en")}),m=n.map(e=>e.summary.wafer).filter(e=>!!e),f={},p=[];if(m.length>0){const e=new Set;for(const a of m)for(const y of Object.keys(a))e.add(y);e.delete("wafer"),e.delete("waferId");for(const a of e){const y=m.filter(u=>a in u).map(u=>u[a]);y.every(u=>u===y[0])?f[a]=y[0]:p.push(a)}}const t=n.map(({waferIndex:e,summary:a})=>({waferIndex:e,yieldPercent:a.stats.yieldPercent})),d=n.map(({waferIndex:e,summary:a})=>a.stats.perTestStats?.length?{waferIndex:e,tests:a.stats.perTestStats}:null).filter(e=>e!==null);return{level:"lot",hasNotableFindings:i.some(e=>e.severity!=="info")||n.some(e=>e.summary.hasNotableFindings),findings:i,lot:Object.keys(f).length>0?f:void 0,...p.length>0?{mixedIdentityFields:p}:{},stats:{waferCount:c.length,...ue(r,n.map(e=>e.summary))},lotYieldSeries:t,perWafer:n,...d.length>0?{perWaferTestStats:d}:{}}}function ue(c,o){const r={...Q(o)},n=Z(o,X(c).defs);n&&(r.capability=n);const l=J(o,c.map(s=>s.ringCount));return l&&(r.regionYield=l),r}
|
|
1
|
+
import{requireRingCount as se}from"../core/ringCount.js";import{analyzeWaferMap as H,candidatesOf as U,describeRegional as Q,resolveOptions as ae,adjustPValues as re,twoProportionZ as X,zPValue as W,welchOfSums as Z,rateRelativeEffect as oe,passesRateGate as ce,severityForFinding as J,severityForScore as ee,collapseRedundantFindings as le,findContiguousRuns as fe,mergedRegionLabel as de}from"./analyzeWaferMap.js";import{normalizeInput as ue}from"./normalizeInput.js";import{poolCapability as me,poolRegionYield as ge,poolPassRates as pe}from"./summaryFigures.js";import{mergeTestDefs as he}from"./mergeTestDefs.js";import{findingPatternKey as ye}from"./filterFindings.js";import{median as te}from"../core/utils.js";import{robustFence as ne}from"./math.js";import{findLotPattern as be}from"./lotPattern.js";import{buildDriftFindings as we}from"./lotDrift.js";import{claimForPattern as ve,weakestMultiplier as Se}from"./analyzeWaferMap.js";import{PATTERN_LABELS as ze}from"./patternClassification.js";import{describeWaferPopulation as Ie,populationStat as ke}from"./population.js";const T=8,Me={3:[.97,.994],4:[.829,.926],5:[.71,.821],6:[.625,.74],7:[.568,.68]},Re=3;export function outlierWafers(i){const o=i.length;if(o<3)return null;const r=te([...i]),s=e=>Math.abs(e-r)>=Re;if(o>=T){const e=ne(i,1.5,T),l=ne(i,3,T);if(!e||!l)return null;const f=(e.hi-e.lo)/4,a=[];return i.forEach((d,h)=>{if(d>=e.lo&&d<=e.hi||!s(d))return;const t=d<l.lo||d>l.hi?"unusual":"notable";a.push({index:h,severity:t,statistic:(d-r)/f})}),{method:"tukey-fence",outliers:a}}const u=i.map((e,l)=>l).sort((e,l)=>i[e]-i[l]),p=i[u[o-1]]-i[u[0]];if(p===0)return null;const[n,c]=Me[o],g=[],y=(e,l)=>{const f=Math.abs(i[e]-i[l])/p;f>n&&s(i[e])&&g.push({index:e,severity:f>c?"unusual":"notable",statistic:f})};return y(u[0],u[1]),y(u[o-1],u[o-2]),{method:"dixon-q",outliers:g}}const A=2;function _(i,o){const r={info:0,notable:1,unusual:2};return r[i]>=r[o]?i:o}const Pe=new Set(["ring","quadrant","sector"]),xe=new Set(["ring","quadrant","sector","reticle-position","test-site"]),$e=new Set(["yield","hardBin","softBin","functionalTest","test"]);function Be(i){return xe.has(i.comparison.family)&&$e.has(i.variable.kind)}function De(i,o){const r=i.map(n=>n.facts);if(!r.length||r.some(n=>!n))return{coincident:new Set,yieldSoftBin:void 0,passBins:[],sectorCount:o};const[s,...u]=r,p=u.every(n=>n.passBins.length===s.passBins.length&&n.passBins.every(c=>s.passBins.includes(c)));return{coincident:new Set([...s.coincident].filter(n=>u.every(c=>c.coincident.has(n)))),yieldSoftBin:u.every(n=>n.yieldSoftBin===s.yieldSoftBin)?s.yieldSoftBin:void 0,passBins:p?s.passBins:[],sectorCount:o}}function j(i,o){let r=0,s=0,u=0,p=0,n=0,c=0,g=0,y=0,e=0,l=!0,f=0;for(const{waferIndex:h,c:t}of i){const m=o(h,t),S=t.rate??t.mean,M=Math.sqrt(S.n+m.n);if(t.rate)r+=M*X(t.rate.hits,t.rate.n,m.hits,m.n),u+=t.rate.hits,p+=t.rate.n,n+=m.hits,c+=m.n;else{const z=Z(t.mean,m.n,m.sum,m.sq);r+=M*z.z,g+=t.mean.n*z.effectSize,y+=t.mean.n*z.delta,f+=t.mean.n,t.relativeDelta===void 0?l=!1:e+=t.mean.n*t.relativeDelta}s+=M*M}const a=s>0?r/Math.sqrt(s):0,d=i[0].c;if(d.rate){const h=n/c,t=u/p-h;return{z:a,delta:t,relativeDelta:h===0?void 0:t/h,size:oe({hits:u,n:p,restHits:n,restN:c,passRate:d.rate.passRate})}}return{z:a,delta:y/f,relativeDelta:l?e/f:void 0,size:g/f}}function O(i,o,r,s){return i.rate?ce(o,r.delta,r.size,s):o<=s.significanceLevel&&Math.abs(r.size)>=s.minimumEffectSize}function Ee(i,o,r,s){const u=new Map;for(const{waferIndex:a,byKey:d}of i)for(const h of d.values())(u.get(h.key)??u.set(h.key,[]).get(h.key)).push({waferIndex:a,c:h});const p=new Map(i.map(a=>[a.waferIndex,a.byKey])),n=(a,d)=>d.rate?{hits:d.rate.restHits,n:d.rate.restN,sum:0,sq:0}:{hits:0,n:d.mean.restN,sum:d.mean.restSum,sq:d.mean.restSq},c=[];for(const[a,d]of u){if(d.length<A)continue;const h=j(d,n);if(h.z===0)continue;const t=d[0].c;c.push({key:a,entries:d,combined:h,finding:{id:`lot-region:${a}`,level:"lot",severity:"info",variable:{...t.variable,label:t.label},comparison:{...t.comparison},effect:{direction:h.z>0?"higher":"lower",absoluteDelta:h.delta,relativeDelta:h.relativeDelta,effectSize:t.rate?h.delta:h.size},stats:{method:"stouffer-z",pValue:W(h.z),sampleSizeLeft:0,sampleSizeRight:0},summary:"",highlight:{kind:"wafer",waferIndices:[]}}})}re(c.map(a=>a.finding));const g=c.filter(a=>O(a.entries[0].c,a.finding.stats.adjustedPValue??1,a.combined,o)),y=(a,d)=>{const h=a.entries[0].c,t=d.entries[0].c;return h.source===t.source&&h.variable.kind===t.variable.kind&&h.variable.bin===t.variable.bin&&h.variable.index===t.variable.index&&h.region.family===t.region.family},e=[],l=new Map;for(const a of g){const d=a.finding.stats.pValue??1,h=g.filter(b=>b!==a&&y(a,b)&&b.finding.effect.direction!==a.finding.effect.direction&&(b.finding.stats.pValue??1)<d);if(h.length){const b=($,v)=>{const I=n($,v);for(const B of h){const k=p.get($)?.get(B.key);k&&(k.rate?(I.hits-=k.rate.hits,I.n-=k.rate.n):(I.n-=k.mean.n,I.sum-=k.mean.sum,I.sq-=k.mean.sq))}return I},R=a.entries.filter($=>b($.waferIndex,$.c).n>=o.minimumSampleSize);if(!R.length)continue;const x=j(R,b),L=d>0?(a.finding.stats.adjustedPValue??d)/d:1;if(Math.sign(x.z)!==Math.sign(a.combined.z)||!O(a.entries[0].c,Math.min(1,W(x.z)*L),x,o))continue}const t=Math.sign(a.combined.z),m=a.entries.filter(b=>Math.sign(b.c.delta)===t),S=i.length,M=a.entries[0].c,z=a.finding.stats.adjustedPValue??d,K=M.rate?J(z,a.combined.delta,a.combined.size):ee(z,a.combined.size),w=_(K,m.length/S>=.6?"unusual":"info"),N={};for(const b of m)N[b.waferIndex]=[...b.c.region.dieKeys];const E={...a.finding,severity:w,stats:{...a.finding.stats,sampleSizeLeft:m.length,sampleSizeRight:S-m.length},summary:`${Q(M,a.combined.delta,a.combined.relativeDelta)} \u2014 ${a.finding.effect.direction} on ${m.length}/${S} wafers, all wafers' data combined`,highlight:{kind:"wafer",waferIndices:m.map(b=>b.waferIndex),dieKeysByWafer:N}};e.push(E),l.set(E,a)}const f=Le(l,p,i.length,o,s,n);return e.splice(0,e.length,...e.filter(a=>!f.replaced.has(a)),...f.findings),le(e,r),e}function Le(i,o,r,s,u,p){const n=new Map;for(const[y,e]of i){const l=e.entries[0].c;if(!Pe.has(l.region.family))continue;const f=[l.source,l.variable.kind,l.variable.bin??"",l.variable.index??"",l.region.family,y.effect.direction].join("|");(n.get(f)??n.set(f,[]).get(f)).push({finding:y,c:l,key:e.key})}const c=[],g=new Set;for(const y of n.values()){if(y.length<2)continue;const e=y[0].c.region.family;for(const l of fe(y,e,s.sectorCount,f=>f.c.region.key)){if(l.length<2)continue;const f=qe(l,o,r,s,u,p);if(f){c.push(f);for(const a of l)g.add(a.finding)}}}return{findings:c,replaced:g}}function qe(i,o,r,s,u,p){const n=i[0].c,c=i[0].finding.effect.direction==="higher"?1:-1,g=de(n.region.family,i.map(w=>w.c.region.key),u),y=[];for(const[w,N]of o){const E=i.map(v=>N.get(v.key));if(E.some(v=>!v))continue;const b=E,R=b[0];let x,L;if(R.rate){const v=b.reduce((P,D)=>P+D.rate.hits,0),I=b.reduce((P,D)=>P+D.rate.n,0),B=R.rate.hits+R.rate.restHits-v,k=R.rate.n+R.rate.restN-I;if(I<s.minimumSampleSize||k<s.minimumSampleSize)continue;x={...R,rate:{hits:v,n:I,restHits:B,restN:k,passRate:R.rate.passRate}},L=X(v,I,B,k)}else{const v=R.mean,I=b.reduce((q,F)=>q+F.mean.n,0),B=b.reduce((q,F)=>q+F.mean.sum,0),k=b.reduce((q,F)=>q+F.mean.sq,0),P=v.n+v.restN-I,D=v.sum+v.restSum-B,C=v.sq+v.restSq-k;if(I<s.minimumSampleSize||P<s.minimumSampleSize)continue;const V={n:I,sum:B,sq:k,restN:P,restSum:D,restSq:C,shift:v.shift},Y=Z(V,P,D,C),G=D/P+v.shift;x={...R,mean:V,relativeDelta:G!==0?Y.delta/Math.abs(G):void 0},L=Y.z}const $=[...new Set(b.flatMap(v=>[...v.region.dieKeys]))];y.push({waferIndex:w,c:x,z:L,dieKeys:$})}if(y.length<A)return;const e=j(y,p);if(e.z===0||Math.sign(e.z)!==c)return;const l=W(e.z),f=Se(i.map(w=>w.finding)),a=Math.min(1,l*f);if(!O(n,a,e,s))return;const d=y.filter(w=>Math.sign(w.z)===c&&W(w.z)<=s.significanceLevel);if(!d.length)return;const h=n.rate?J(a,e.delta,e.size):ee(a,e.size),t=_(h,d.length/r>=.6?"unusual":"info"),m={};for(const w of d)m[w.waferIndex]=w.dieKeys;const S=i.map(w=>w.c.region.key.slice(w.c.region.key.indexOf(":")+1)).join("-"),M={...n,comparison:{...n.comparison,left:g}},z=i[0].finding,K=z.effect.direction;return{...z,id:`lot-region:${i[0].key.split("|").slice(0,-1).join("|")}|${n.region.family}:${S}`,severity:t,comparison:{...z.comparison,left:g},effect:{direction:K,absoluteDelta:e.delta,relativeDelta:e.relativeDelta,effectSize:n.rate?e.delta:e.size},stats:{method:"stouffer-z",pValue:l,adjustedPValue:a,sampleSizeLeft:d.length,sampleSizeRight:r-d.length},summary:`${Q(M,e.delta,e.relativeDelta)} \u2014 ${K} on ${d.length}/${r} wafers, all wafers' data combined`,highlight:{kind:"wafer",waferIndices:d.map(w=>w.waferIndex),dieKeysByWafer:m},relatedIds:i.map(w=>w.finding.id)}}const ie={"Edge-ring":"edge","Edge-local":"edge","Center cluster":"centre or donut",Donut:"centre or donut"};function Fe(i,o){const r=new Map,s=n=>{const c=n.variable.kind==="spatialPattern"?ie[n.comparison.left]:void 0;return c?`spatialPattern|${c}`:ye(n)};for(const n of i){const c=new Set,g=new Map;for(const e of n.summary.findings)if(e.variable.kind==="spatialPattern")for(const l of e.relatedIds??[])g.set(l,s(e));const y=new Set(n.summary.findings.flatMap(e=>e.absorbedIds??[]));for(const e of n.summary.findings){if(y.has(e.id)||Be(e)||e.variable.kind==="spatialPattern"&&o?.carriers.has(n.waferIndex))continue;const l=s(e);if(c.has(l))continue;c.add(l);const f=r.get(l)??{finding:e,waferIndices:[],severity:e.severity,labels:new Map,dieKeysByWafer:{},explainedBy:new Map};f.waferIndices.push(n.waferIndex);const a=g.get(e.id);if(a&&f.explainedBy.set(a,(f.explainedBy.get(a)??0)+1),e.variable.kind==="spatialPattern"){f.labels.set(e.comparison.left,(f.labels.get(e.comparison.left)??0)+1);const d=e.highlight.dieKeys;d&&(f.dieKeysByWafer[n.waferIndex]=d)}f.severity=_(f.severity,e.severity),r.set(l,f)}}const u=[];for(const[n,c]of r){if(c.waferIndices.length<A)continue;const g=c.waferIndices.length/Math.max(1,i.length),y=g>=.6||c.severity==="unusual"?"unusual":"notable",e=c.finding;u.push({id:`lot-repeat:${n}`,level:"lot",severity:y,variable:{...e.variable},comparison:{...e.comparison},effect:{direction:e.effect.direction,absoluteDelta:e.effect.absoluteDelta,relativeDelta:e.effect.relativeDelta,effectSize:e.effect.effectSize},stats:{method:"wafer-finding-frequency",sampleSizeLeft:c.waferIndices.length,sampleSizeRight:i.length-c.waferIndices.length},summary:`${We(e,c.labels)} \u2014 seen on ${c.waferIndices.length}/${i.length} wafers (${Math.round(g*100)}%)`,highlight:{kind:"wafer",waferIndices:c.waferIndices,...c.labels.size?{dieKeysByWafer:c.dieKeysByWafer}:{}}})}const p=new Map(u.map(n=>[n.id,n]));for(const[n,c]of r){const g=p.get(`lot-repeat:${n}`);if(g)for(const[y,e]of c.explainedBy){const l=p.get(`lot-repeat:${y}`);!l||e!==c.waferIndices.length||(l.relatedIds??=[]).push(g.id)}}return u}function Ke(i,o){const{classification:r,carriers:s}=i,u=ze[r.pattern],p=s.size/Math.max(1,o),n={};for(const[c,g]of s)n[c]=g;return{id:"lot-repeat:spatialPattern|lot",level:"lot",severity:p>=.6||r.confidence==="high"?"unusual":"notable",variable:{kind:"spatialPattern",label:u},comparison:{family:"spatial-pattern",left:u,right:"Wafer"},effect:{direction:"different",effectSize:r.features.globalRdd},stats:{method:"wafer-finding-frequency",sampleSizeLeft:s.size,sampleSizeRight:o-s.size},summary:`Spatial pattern: ${u.toLowerCase()} \u2014 seen on ${s.size}/${o} wafers (${Math.round(p*100)}%)`,highlight:{kind:"wafer",waferIndices:[...s.keys()].sort((c,g)=>c-g),dieKeysByWafer:n}}}function Ne(i){const o=/^Rings? (\d+)(?:\s*[–-]\s*(\d+))?/.exec(i.comparison.left);if(!o)return[];const r=+o[1],s=o[2]?+o[2]:r;return Array.from({length:Math.max(0,s-r+1)},(u,p)=>r+p)}function We(i,o){if(!o.size)return i.summary;const r=p=>p.toLowerCase();if(o.size===1)return`Spatial pattern: ${r([...o.keys()][0])}`;const s=ie[i.comparison.left],u=[...o].sort((p,n)=>n[1]-p[1]).map(([p,n])=>`${r(p)} on ${n}`);return`Spatial pattern: ${s} (${u.join(", ")})`}function Ae(i,o){const r=ke(o,"median"),s=i.map(g=>({waferIndex:g.waferIndex,yieldPercent:g.summary.stats.yieldPercent})).filter(g=>g.yieldPercent!==null),u=s.map(g=>g.yieldPercent),p=outlierWafers(u);if(!p)return[];const n=te(u),c=[];for(const{index:g,severity:y,statistic:e}of p.outliers){const l=s[g],f=l.yieldPercent-n;c.push({id:`inter-wafer:yield:${l.waferIndex}`,level:"inter-wafer",severity:y,variable:{kind:"yield",label:"Yield"},comparison:{family:"wafer",left:`Wafer ${l.waferIndex+1}`,right:r.charAt(0).toUpperCase()+r.slice(1)},effect:{direction:f>0?"higher":"lower",absoluteDelta:f/100,relativeDelta:n===0?void 0:f/n,effectSize:e},stats:{method:p.method,sampleSizeLeft:1,sampleSizeRight:s.length-1},summary:`Wafer ${l.waferIndex+1} yield is ${Math.abs(f).toFixed(1)} percentage points ${f>0?"higher":"lower"} than the ${r}`,highlight:{kind:"wafer",waferIndices:[l.waferIndex]}})}return c}export function analyzeWaferLot(i,o={}){const r=i.map(ue),s=r.map((t,m)=>({waferIndex:m,summary:o.perWaferSummaries?.[m]??H(t,o)})),u=s.map(({waferIndex:t,summary:m})=>U(m)??U(H(r[t],o))),p=u.map((t,m)=>({waferIndex:m,byKey:new Map(t.candidates.map(S=>[S.key,S]))})),n=ae(o).resolved,c=r.length>0?se(r[0],"analyzeWaferLot"):void 0,g=be(r,n),y=g&&g.carriers.size>=A?g:null,e=[...c===void 0?[]:Ee(p,n,De(u,n.sectorCount),c),...Fe(s,y),...Ae(s,Ie(s.map(t=>t.summary.wafer))),...we(s,n.significanceLevel)];if(y&&c!==void 0){const t=Ke(y,s.length);t.relatedIds=ve([y.classification.pattern],t.severity,e,Ne,c),t.relatedIds.length||delete t.relatedIds,e.push(t)}e.sort((t,m)=>{const S=t.severity==="unusual"?2:t.severity==="notable"?1:0,M=m.severity==="unusual"?2:m.severity==="notable"?1:0;return S!==M?M-S:t.summary.localeCompare(m.summary,"en")});const l=s.map(t=>t.summary.wafer).filter(t=>!!t),f={},a=[];if(l.length>0){const t=new Set;for(const m of l)for(const S of Object.keys(m))t.add(S);t.delete("wafer"),t.delete("waferId");for(const m of t){const S=l.filter(z=>m in z).map(z=>z[m]);S.every(z=>z===S[0])?f[m]=S[0]:a.push(m)}}const d=s.map(({waferIndex:t,summary:m})=>({waferIndex:t,yieldPercent:m.stats.yieldPercent})),h=s.map(({waferIndex:t,summary:m})=>m.stats.perTestStats?.length?{waferIndex:t,tests:m.stats.perTestStats}:null).filter(t=>t!==null);return{level:"lot",hasNotableFindings:e.some(t=>t.severity!=="info")||s.some(t=>t.summary.hasNotableFindings),findings:e,lot:Object.keys(f).length>0?f:void 0,...a.length>0?{mixedIdentityFields:a}:{},stats:{waferCount:i.length,...Te(r,s.map(t=>t.summary))},lotYieldSeries:d,perWafer:s,...h.length>0?{perWaferTestStats:h}:{}}}function Te(i,o){const r={...pe(o)},s=me(o,he(i).defs);s&&(r.capability=s);const u=ge(o,i.map(p=>p.ringCount));return u&&(r.regionYield=u),r}
|
|
@@ -2,6 +2,7 @@ import type { Die } from '../core/dies.js';
|
|
|
2
2
|
import type { TestDef, WaferWarning } from '../renderer/buildWaferMap.js';
|
|
3
3
|
import type { AnalyzeWaferMapInput, AnalyzeWaferMapOptions, StatsFinding, StatsSeverity, StatsSummary } from './types.js';
|
|
4
4
|
import { type StatsRegion } from './regions.js';
|
|
5
|
+
import { type PatternLabel } from './patternClassification.js';
|
|
5
6
|
interface EligibleDie extends Die {
|
|
6
7
|
hbin?: number;
|
|
7
8
|
}
|
|
@@ -108,6 +109,18 @@ export declare function candidatesOf(summary: StatsSummary): WaferComparisons |
|
|
|
108
109
|
*/
|
|
109
110
|
export declare function describeRegional(c: RegionCandidate, delta: number, relativeDelta: number | undefined): string;
|
|
110
111
|
export declare function passesRateGate(adjustedP: number, delta: number, relEffect: number, options: ResolvedOptions): boolean;
|
|
112
|
+
/**
|
|
113
|
+
* The ids of the `candidates` a spatial pattern explains (`patternExplains`), for
|
|
114
|
+
* its `relatedIds` — the wafer's pattern over the wafer's findings, the lot's
|
|
115
|
+
* over the lot's. `patterns` holds more than one label for a lot row that
|
|
116
|
+
* counts a family together (edge-ring and edge-local wafers).
|
|
117
|
+
*
|
|
118
|
+
* A ring, quadrant or sector row the pattern claims is downgraded to info so it
|
|
119
|
+
* does not count twice towards the badge and `hasNotableFindings`; cluster and
|
|
120
|
+
* edge-arc rows keep their own severity — they carry their own evidence (a
|
|
121
|
+
* p-value, an exact die count) and are supporting detail, not a restatement.
|
|
122
|
+
*/
|
|
123
|
+
export declare function claimForPattern<F extends StatsFinding>(patterns: PatternLabel[], severity: StatsSeverity, candidates: readonly F[], ringsOf: (f: F) => number[], ringCount: number): string[];
|
|
111
124
|
/** A test-value finding's per-region sums, in the builder's shifted space. */
|
|
112
125
|
export interface MeanStats {
|
|
113
126
|
n: number;
|
|
@@ -116,6 +129,7 @@ export interface MeanStats {
|
|
|
116
129
|
restN: number;
|
|
117
130
|
restSum: number;
|
|
118
131
|
restSq: number;
|
|
132
|
+
shift: number;
|
|
119
133
|
}
|
|
120
134
|
/** Welch's test of a region's sums against a rest given as sums (one wafer's shifted space). */
|
|
121
135
|
export declare function welchOfSums(own: MeanStats, restN: number, restSum: number, restSq: number): ReturnType<typeof welchFromStats>;
|
|
@@ -130,6 +144,25 @@ export declare function welchFromStats(leftN: number, leftMean: number, leftVar:
|
|
|
130
144
|
delta: number;
|
|
131
145
|
z: number;
|
|
132
146
|
};
|
|
147
|
+
/**
|
|
148
|
+
* A connected set of quadrants in reading order round the wafer, starting after
|
|
149
|
+
* a gap when there is one ("NW, SW & SE", not whatever order the search met
|
|
150
|
+
* them in). All four quadrants have no gap and start at the north-east.
|
|
151
|
+
*/
|
|
152
|
+
export declare function inQuadrantOrder(quadrants: string[]): string[];
|
|
153
|
+
/**
|
|
154
|
+
* Partition a same-signal group into maximal runs of spatially adjacent regions.
|
|
155
|
+
* Singletons (no adjacent same-signal neighbour) come back as length-1 runs.
|
|
156
|
+
*/
|
|
157
|
+
export declare function findContiguousRuns<T>(group: T[], family: string, sectorCount: number, keyOf: (item: T) => string): T[][];
|
|
158
|
+
/** The label of a run of adjacent regions, from their region keys in run order. */
|
|
159
|
+
export declare function mergedRegionLabel(family: string, keys: string[], ringCount: number): string;
|
|
160
|
+
/**
|
|
161
|
+
* The Benjamini–Hochberg multiplier (adjusted p over raw p) of the weakest finding
|
|
162
|
+
* in a merged run. A merged finding is graded on its own p-value times this, so a
|
|
163
|
+
* run is judged no more leniently than its parts. Wafer and lot merges both use it.
|
|
164
|
+
*/
|
|
165
|
+
export declare function weakestMultiplier(run: readonly RawFinding[]): number;
|
|
133
166
|
/** What {@link collapseRedundantFindings} needs to know about a wafer's dies. */
|
|
134
167
|
export interface RedundancyFacts {
|
|
135
168
|
/** `"hard:soft"` bin pairs carried by exactly the same dies. */
|
|
@@ -137,8 +170,10 @@ export interface RedundancyFacts {
|
|
|
137
170
|
/** The soft bin whose rate is the yield, if one is. */
|
|
138
171
|
yieldSoftBin: number | undefined;
|
|
139
172
|
passBins: number[];
|
|
173
|
+
/** How the sectors divide the wafer, for comparing a sector run with a quadrant. */
|
|
174
|
+
sectorCount: number;
|
|
140
175
|
}
|
|
141
|
-
export declare function redundancyFacts(eligibleDies: EligibleDie[], passBins: number[]): RedundancyFacts;
|
|
176
|
+
export declare function redundancyFacts(eligibleDies: EligibleDie[], passBins: number[], sectorCount: number): RedundancyFacts;
|
|
142
177
|
export declare function collapseRedundantFindings(findings: RawFinding[], facts: RedundancyFacts): void;
|
|
143
178
|
export declare function analyzeWaferMap(input: AnalyzeWaferMapInput, options?: AnalyzeWaferMapOptions): StatsSummary;
|
|
144
179
|
export {};
|