@wafertools/wafermap 0.21.1 → 0.22.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 +170 -0
- package/CHANGELOG.md +160 -1
- package/README.md +9 -1
- package/dist/packages/canvas-adapter/icons.js +1 -1
- package/dist/packages/canvas-adapter/index.d.ts +2 -0
- package/dist/packages/canvas-adapter/index.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +14 -0
- package/dist/packages/canvas-adapter/renderWaferGallery.js +6 -6
- package/dist/packages/canvas-adapter/renderWaferMap.d.ts +14 -0
- package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
- package/dist/packages/canvas-adapter/summaryPanel.d.ts +11 -1
- package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
- package/dist/packages/canvas-adapter/toolbar.d.ts +4 -1
- package/dist/packages/canvas-adapter/toolbar.js +1 -1
- package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
- package/dist/packages/canvas-adapter/userGuideHtml.js +59 -22
- package/dist/packages/canvas-adapter/version.d.ts +2 -2
- package/dist/packages/canvas-adapter/version.js +1 -1
- package/dist/packages/canvas-adapter/warnings.d.ts +52 -0
- package/dist/packages/canvas-adapter/warnings.js +1 -0
- package/dist/packages/renderer/buildWaferMap.d.ts +15 -1
- package/dist/packages/renderer/buildWaferMap.js +1 -1
- package/dist/packages/renderer/index.d.ts +1 -1
- package/dist/packages/renderer/index.js +1 -1
- package/dist/packages/stats/analyzeWaferLot.js +1 -1
- package/dist/packages/stats/analyzeWaferMap.js +1 -1
- package/dist/packages/stats/renderFindingsReport.js +15 -15
- package/dist/packages/stats/types.d.ts +34 -4
- package/llms.txt +39 -0
- package/package.json +12 -8
|
@@ -63,7 +63,7 @@ export const USER_GUIDE_HTML=`<div class="wmap-guide">
|
|
|
63
63
|
.wmap-guide-online-link{display:none!important}
|
|
64
64
|
}
|
|
65
65
|
</style>
|
|
66
|
-
<h1 id="wafer-map-user-guide">Wafer Map \u2014 User Guide<span class="wmap-guide-version" title="Built 2026-08-
|
|
66
|
+
<h1 id="wafer-map-user-guide">Wafer Map \u2014 User Guide<span class="wmap-guide-version" title="Built 2026-08-07T07:44:28.515Z">v0.22.0</span></h1>
|
|
67
67
|
<p class="wmap-guide-online-link">This is a quick reference. <a href="https://wafertools.github.io/wafermap/user-guide/" target="_blank" rel="noopener">View the full illustrated guide online \u2197</a></p>
|
|
68
68
|
<p>This guide describes the display and analysis features of the wafer map viewer.
|
|
69
69
|
It is written for users who may be semiconductor test engineers, device engineers, and yield engineers
|
|
@@ -219,7 +219,7 @@ verdicts, as an alternative to spec-limit judgement.</p>
|
|
|
219
219
|
<p>The toolbar sits at the top-right of the map and is always visible. Controls that
|
|
220
220
|
are not applicable to the current mode are hidden automatically. (Hovering the map
|
|
221
221
|
shows die tooltips \u2014 a separate thing from the toolbar, which is always present.)</p>
|
|
222
|
-
<p><img src="data:image/png;base64,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" alt="Single-map toolbar"></p>
|
|
222
|
+
<p><img src="data:image/png;base64,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" alt="Single-map toolbar"></p>
|
|
223
223
|
<h3 id="single-map">Single map</h3>
|
|
224
224
|
<table>
|
|
225
225
|
<thead>
|
|
@@ -235,11 +235,6 @@ shows die tooltips \u2014 a separate thing from the toolbar, which is always pre
|
|
|
235
235
|
<td>Switches the active plot mode (see <a href="#2-plot-modes" onclick="(function(e){e.preventDefault();var g=e.target.closest('.wmap-guide');var el=g&&g.querySelector('[id=\\'2-plot-modes\\']');if(el)el.scrollIntoView({behavior:'smooth'});})(event)">Section 2</a>). When multiple tests are available, a test selector appears alongside it.</td>
|
|
236
236
|
</tr>
|
|
237
237
|
<tr>
|
|
238
|
-
<td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xOCA0SDZsNiA4LTYgOGgxMiIvPjwvc3ZnPgo=" width="20" height="20"></td>
|
|
239
|
-
<td>Aggregation method</td>
|
|
240
|
-
<td>Stacked modes only. Selects how values from multiple wafers are combined per die position: Mean, Median, Std Dev, Min, Max, or Count.</td>
|
|
241
|
-
</tr>
|
|
242
|
-
<tr>
|
|
243
238
|
<td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0yIDEyczMuNi03IDEwLTcgMTAgNyAxMCA3LTMuNiA3LTEwIDctMTAtNy0xMC03eiIvPjxjaXJjbGUgY3g9IjEyIiBjeT0iMTIiIHI9IjMiLz48L3N2Zz4K" width="20" height="20"></td>
|
|
244
239
|
<td>Overlays</td>
|
|
245
240
|
<td>Check-menu of optional display layers: XY axis indicator, ring boundaries, quadrant lines, die coordinate labels, reticle grid (when geometry is configured), Spec pass/fail (Test Value mode with limits), and Test pass/fail (Test Value mode, active test is functional or has recorded verdicts).</td>
|
|
@@ -255,24 +250,19 @@ shows die tooltips \u2014 a separate thing from the toolbar, which is always pre
|
|
|
255
250
|
<td>Test Value and Stacked Test Values modes only. Applies a log\u2081\u2080 scale to the colour mapping. Only active when all displayed values are positive. Hidden whenever a pass/fail display is active or the active test is functional.</td>
|
|
256
251
|
</tr>
|
|
257
252
|
<tr>
|
|
253
|
+
<td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik01IDQgTDUgMjAiLz48cGF0aCBkPSJNNSA0IEw4IDQiLz48cGF0aCBkPSJNNSAyMCBMOCAyMCIvPjxwYXRoIGQ9Ik0xOSA0IEwxOSAyMCIvPjxwYXRoIGQ9Ik0xOSA0IEwxNiA0Ii8+PHBhdGggZD0iTTE5IDIwIEwxNiAyMCIvPjxwYXRoIGQ9Ik01IDEyIEwxOSAxMiIgc3Ryb2tlLWRhc2hhcnJheT0iMyAyIi8+PC9zdmc+Cg==" width="20" height="20"></td>
|
|
254
|
+
<td>Colorbar range</td>
|
|
255
|
+
<td>Test Value mode with spec limits only. Toggles the colorbar between the <strong>spec-limit range</strong> (default \u2014 the colours mean the same thing on every wafer, so maps are comparable) and the <strong>data range</strong> (stretches the scale to the values actually present, which shows more contrast but is not comparable between wafers).</td>
|
|
256
|
+
</tr>
|
|
257
|
+
<tr>
|
|
258
258
|
<td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjciIHg9IjMiIHk9IjMiIHJ4PSIxIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iNyIgeD0iMyIgeT0iMTQiIHJ4PSIxIi8+PHBhdGggZD0iTTE0IDRoNyIvPjxwYXRoIGQ9Ik0xNCA5aDciLz48cGF0aCBkPSJNMTQgMTVoNyIvPjxwYXRoIGQ9Ik0xNCAyMGg3Ii8+PC9zdmc+Cg==" width="20" height="20"></td>
|
|
259
259
|
<td>Legend style</td>
|
|
260
260
|
<td>Bin modes only. Controls where the bin legend is positioned relative to the map: Default (right), Compact, Left, Top, Bottom, or Floating.</td>
|
|
261
261
|
</tr>
|
|
262
262
|
<tr>
|
|
263
|
-
<td><img src="data:image/svg+xml;base64,
|
|
264
|
-
<td>
|
|
265
|
-
<td>
|
|
266
|
-
</tr>
|
|
267
|
-
<tr>
|
|
268
|
-
<td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Im0zIDcgNSA1LTUgNVY3Ii8+PHBhdGggZD0ibTIxIDctNSA1IDUgNVY3Ii8+PHBhdGggZD0iTTEyIDIwdjIiLz48cGF0aCBkPSJNMTIgMTR2MiIvPjxwYXRoIGQ9Ik0xMiA4djIiLz48cGF0aCBkPSJNMTIgMnYyIi8+PC9zdmc+Cg==" width="20" height="20"></td>
|
|
269
|
-
<td>Flip horizontal</td>
|
|
270
|
-
<td>Mirrors the display left/right. Die coordinates are unaffected.</td>
|
|
271
|
-
</tr>
|
|
272
|
-
<tr>
|
|
273
|
-
<td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Im0xNyAzLTUgNS01LTVoMTAiLz48cGF0aCBkPSJtMTcgMjEtNS01LTUgNWgxMCIvPjxwYXRoIGQ9Ik00IDEySDIiLz48cGF0aCBkPSJNMTAgMTJIOCIvPjxwYXRoIGQ9Ik0xNiAxMmgtMiIvPjxwYXRoIGQ9Ik0yMiAxMmgtMiIvPjwvc3ZnPgo=" width="20" height="20"></td>
|
|
274
|
-
<td>Flip vertical</td>
|
|
275
|
-
<td>Mirrors the display top/bottom. Die coordinates are unaffected.</td>
|
|
263
|
+
<td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxjaXJjbGUgY3g9IjEyIiBjeT0iMTIiIHI9IjEwIi8+PHBvbHlnb24gcG9pbnRzPSIxNi4yNCA3Ljc2IDE0LjEyIDE0LjEyIDcuNzYgMTYuMjQgOS44OCA5Ljg4IDE2LjI0IDcuNzYiIGZpbGw9IiMzNzQxNTEiIHN0cm9rZT0ibm9uZSIvPjwvc3ZnPgo=" width="20" height="20"></td>
|
|
264
|
+
<td>Orientation</td>
|
|
265
|
+
<td>Menu of display transforms: <strong>Rotate 90\xB0 clockwise</strong> (applies cumulatively), <strong>Flip horizontal</strong>, <strong>Flip vertical</strong>. These change only how the wafer is drawn \u2014 die coordinates in tooltips and labels are always the original values, whatever the orientation.</td>
|
|
276
266
|
</tr>
|
|
277
267
|
<tr>
|
|
278
268
|
<td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Im0xMyAxMy41IDItMi41LTItMi41Ii8+PHBhdGggZD0ibTIxIDIxLTQuMy00LjMiLz48cGF0aCBkPSJNOSA4LjUgNyAxMWwyIDIuNSIvPjxjaXJjbGUgY3g9IjExIiBjeT0iMTEiIHI9IjgiLz48L3N2Zz4K" width="20" height="20"></td>
|
|
@@ -325,6 +315,11 @@ shows die tooltips \u2014 a separate thing from the toolbar, which is always pre
|
|
|
325
315
|
<td>Opens or closes the Findings sidebar (see <a href="#6-findings-sidebar" onclick="(function(e){e.preventDefault();var g=e.target.closest('.wmap-guide');var el=g&&g.querySelector('[id=\\'6-findings-sidebar\\']');if(el)el.scrollIntoView({behavior:'smooth'});})(event)">Section 6</a>).</td>
|
|
326
316
|
</tr>
|
|
327
317
|
<tr>
|
|
318
|
+
<td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNMTAuMjkgMy44NiAxLjgyIDE4YTIgMiAwIDAgMCAxLjcxIDNoMTYuOTRhMiAyIDAgMCAwIDEuNzEtM0wxMy43MSAzLjg2YTIgMiAwIDAgMC0zLjQyIDB6Ii8+PHBhdGggZD0iTTEyIDl2NCIvPjxwYXRoIGQ9Ik0xMiAxN2guMDEiLz48L3N2Zz4K" width="20" height="20"></td>
|
|
319
|
+
<td>Data warnings</td>
|
|
320
|
+
<td>Appears <strong>only when there is something to report</strong> about the data behind the map. Click it for the details. A red \u26D4 means the map may be positionally wrong \u2014 usually that wafer geometry was guessed rather than supplied, so dies may not sit where they appear to. An amber \u26A0 means something expected is missing or was skipped, but what is drawn is correct.</td>
|
|
321
|
+
</tr>
|
|
322
|
+
<tr>
|
|
328
323
|
<td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48Y2lyY2xlIGN4PSIxMiIgY3k9IjEyIiByPSIxMCIvPjxwYXRoIGQ9Ik05LjA5IDlhMyAzIDAgMCAxIDUuODMgMWMwIDItMyAzLTMgMyIvPjxwYXRoIGQ9Ik0xMiAxN2guMDEiLz48L3N2Zz4K" width="20" height="20"></td>
|
|
329
324
|
<td>User guide</td>
|
|
330
325
|
<td>Opens this guide.</td>
|
|
@@ -432,11 +427,31 @@ failure category across the wafer.</p>
|
|
|
432
427
|
<td>Sets the number of columns in the card grid.</td>
|
|
433
428
|
</tr>
|
|
434
429
|
<tr>
|
|
435
|
-
<td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xMy45OTcgNGEyIDIgMCAwIDEgMS43NiAxLjA1bC40ODYuOUEyIDIgMCAwIDAgMTguMDAzIDdIMjBhMiAyIDAgMCAxIDIgMnY5YTIgMiAwIDAgMS0yIDJINGEyIDIgMCAwIDEtMi0yVjlhMiAyIDAgMCAxIDItMmgxLjk5N2EyIDIgMCAwIDAgMS43NTktMS4wNDhsLjQ4OS0uOTA0QTIgMiAwIDAgMSAxMC4wMDQgNHoiLz48Y2lyY2xlIGN4PSIxMiIgY3k9IjEzIiByPSIzIi8+
|
|
430
|
+
<td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xMy45OTcgNGEyIDIgMCAwIDEgMS43NiAxLjA1bC40ODYuOUEyIDIgMCAwIDAgMTguMDAzIDdIMjBhMiAyIDAgMCAxIDIgMnY5YTIgMiAwIDAgMS0yIDJINGEyIDIgMCAwIDEtMi0yVjlhMiAyIDAgMCAxIDItMmgxLjk5N2EyIDIgMCAwIDAgMS43NTktMS4wNDhsLjQ4OS0uOTA0QTIgMiAwIDAgMSAxMC4wMDQgNHoiLz48Y2lyY2xlIGN4PSIxMiIgY3k9IjEzIiByPSIzIi8+PGNpcmNsZSBjeD0iNS41IiBjeT0iNC41IiByPSIwLjgiIGZpbGw9IiMzNzQxNTEiIHN0cm9rZT0ibm9uZSIvPjxjaXJjbGUgY3g9IjgiIGN5PSI0LjUiIHI9IjAuOCIgZmlsbD0iIzM3NDE1MSIgc3Ryb2tlPSJub25lIi8+PGNpcmNsZSBjeD0iNS41IiBjeT0iNyIgcj0iMC44IiBmaWxsPSIjMzc0MTUxIiBzdHJva2U9Im5vbmUiLz48Y2lyY2xlIGN4PSI4IiBjeT0iNyIgcj0iMC44IiBmaWxsPSIjMzc0MTUxIiBzdHJva2U9Im5vbmUiLz48L3N2Zz4K" width="20" height="20"></td>
|
|
436
431
|
<td>Save image</td>
|
|
437
432
|
<td>Downloads the full gallery grid as a single PNG.</td>
|
|
438
433
|
</tr>
|
|
439
434
|
<tr>
|
|
435
|
+
<td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xOCA0SDZsNiA4LTYgOGgxMiIvPjwvc3ZnPgo=" width="20" height="20"></td>
|
|
436
|
+
<td>Aggregation method</td>
|
|
437
|
+
<td>Stacked modes only. Selects how values from multiple wafers are combined per die position: Mean, Median, Std Dev, Min, Max, or Count.</td>
|
|
438
|
+
</tr>
|
|
439
|
+
<tr>
|
|
440
|
+
<td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik01IDIxIEw1IDMiLz48cGF0aCBkPSJNMy41IDUgTDUgMyBMNi41IDUiLz48cGF0aCBkPSJNNSAyMSBMMjEgMjEiLz48cGF0aCBkPSJNMTkgMTkuNSBMMjEgMjEgTDE5IDIyLjUiLz48cGF0aCBkPSJNNiAyMCBDIDYgMTIsIDEwIDcsIDIwIDYiLz48L3N2Zz4K" width="20" height="20"></td>
|
|
441
|
+
<td>Log scale</td>
|
|
442
|
+
<td>Test Value and Stacked Test Values modes only. Applies a log\u2081\u2080 scale to the colour mapping for all cards.</td>
|
|
443
|
+
</tr>
|
|
444
|
+
<tr>
|
|
445
|
+
<td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjciIHg9IjMiIHk9IjMiIHJ4PSIxIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iNyIgeD0iMyIgeT0iMTQiIHJ4PSIxIi8+PHBhdGggZD0iTTE0IDRoNyIvPjxwYXRoIGQ9Ik0xNCA5aDciLz48cGF0aCBkPSJNMTQgMTVoNyIvPjxwYXRoIGQ9Ik0xNCAyMGg3Ii8+PC9zdmc+Cg==" width="20" height="20"></td>
|
|
446
|
+
<td>Legend style</td>
|
|
447
|
+
<td>Bin modes only. Sets where the bin legend sits relative to each card.</td>
|
|
448
|
+
</tr>
|
|
449
|
+
<tr>
|
|
450
|
+
<td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik01IDQgTDUgMjAiLz48cGF0aCBkPSJNNSA0IEw4IDQiLz48cGF0aCBkPSJNNSAyMCBMOCAyMCIvPjxwYXRoIGQ9Ik0xOSA0IEwxOSAyMCIvPjxwYXRoIGQ9Ik0xOSA0IEwxNiA0Ii8+PHBhdGggZD0iTTE5IDIwIEwxNiAyMCIvPjxwYXRoIGQ9Ik01IDEyIEwxOSAxMiIgc3Ryb2tlLWRhc2hhcnJheT0iMyAyIi8+PC9zdmc+Cg==" width="20" height="20"></td>
|
|
451
|
+
<td>Colorbar range</td>
|
|
452
|
+
<td>Test Value mode with spec limits only. Toggles all cards between the spec-limit range and the data range. Leave it on spec limits when comparing wafers \u2014 the data range rescales per view.</td>
|
|
453
|
+
</tr>
|
|
454
|
+
<tr>
|
|
440
455
|
<td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNOCAydjQiLz48cGF0aCBkPSJNMTIgMnY0Ii8+PHBhdGggZD0iTTE2IDJ2NCIvPjxyZWN0IHdpZHRoPSIxNiIgaGVpZ2h0PSIxOCIgeD0iNCIgeT0iNCIgcng9IjIiLz48cGF0aCBkPSJNOCAxMGg2Ii8+PHBhdGggZD0iTTggMTRoOCIvPjxwYXRoIGQ9Ik04IDE4aDUiLz48L3N2Zz4K" width="20" height="20"></td>
|
|
441
456
|
<td>Findings</td>
|
|
442
457
|
<td>Opens or closes the lot-level Findings sidebar.</td>
|
|
@@ -446,6 +461,16 @@ failure category across the wafer.</p>
|
|
|
446
461
|
<td>Insights</td>
|
|
447
462
|
<td>Swaps the grid for a lot-wide chart suite \u2014 yield, bin breakdown, process capability, and more (see <a href="#8-insights-tab" onclick="(function(e){e.preventDefault();var g=e.target.closest('.wmap-guide');var el=g&&g.querySelector('[id=\\'8-insights-tab\\']');if(el)el.scrollIntoView({behavior:'smooth'});})(event)">Section 8</a>). Only shown when the application has enabled it.</td>
|
|
448
463
|
</tr>
|
|
464
|
+
<tr>
|
|
465
|
+
<td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNMTAuMjkgMy44NiAxLjgyIDE4YTIgMiAwIDAgMCAxLjcxIDNoMTYuOTRhMiAyIDAgMCAwIDEuNzEtM0wxMy43MSAzLjg2YTIgMiAwIDAgMC0zLjQyIDB6Ii8+PHBhdGggZD0iTTEyIDl2NCIvPjxwYXRoIGQ9Ik0xMiAxN2guMDEiLz48L3N2Zz4K" width="20" height="20"></td>
|
|
466
|
+
<td>Data warnings</td>
|
|
467
|
+
<td>Appears only when something is worth reporting about the lot. Collected across every wafer and de-duplicated, so a problem affecting the whole lot is stated once rather than repeated per card.</td>
|
|
468
|
+
</tr>
|
|
469
|
+
<tr>
|
|
470
|
+
<td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48Y2lyY2xlIGN4PSIxMiIgY3k9IjEyIiByPSIxMCIvPjxwYXRoIGQ9Ik05LjA5IDlhMyAzIDAgMCAxIDUuODMgMWMwIDItMyAzLTMgMyIvPjxwYXRoIGQ9Ik0xMiAxN2guMDEiLz48L3N2Zz4K" width="20" height="20"></td>
|
|
471
|
+
<td>User guide</td>
|
|
472
|
+
<td>Opens this guide.</td>
|
|
473
|
+
</tr>
|
|
449
474
|
</tbody></table>
|
|
450
475
|
<p>Click a card's expand button to detach it into its own separate window with the
|
|
451
476
|
complete single-map toolbar (falls back to a floating window inside the page if
|
|
@@ -559,8 +584,20 @@ doesn't need the map on screen the way a finding's highlight does.</p>
|
|
|
559
584
|
<p>A <strong>Summary report</strong> button (when present) opens a printable full-detail
|
|
560
585
|
report in a new window or tab, still covering everything findings-adjacent \u2014
|
|
561
586
|
yield, bin breakdown, ring and quadrant statistics, per-test statistics, and
|
|
562
|
-
the
|
|
587
|
+
the findings list \u2014 and can be saved as a PDF from your browser's print
|
|
563
588
|
dialog.</p>
|
|
589
|
+
<h3 id="why-some-findings-name-two-bins">Why some findings name two bins</h3>
|
|
590
|
+
<p>A finding may read <strong>"hard bin 3 and soft bin 3 (same dies)"</strong>. That is one
|
|
591
|
+
group of dies counted in two bin spaces, not two separate groups added together.
|
|
592
|
+
Hard and soft bins are independent numbering systems, so this wording appears
|
|
593
|
+
only when the two happen to cover exactly the same dies \u2014 reporting it twice
|
|
594
|
+
would look like two independent problems.</p>
|
|
595
|
+
<p>For the same reason, when a single pass bin is configured you will see the
|
|
596
|
+
<strong>yield</strong> finding for a region but not a separate finding for the pass bin
|
|
597
|
+
itself: "pass-bin occurrence is 22 points lower" and "yield is 22 points lower"
|
|
598
|
+
are the same sentence.</p>
|
|
599
|
+
<p>Nothing is discarded \u2014 an application reading the findings programmatically
|
|
600
|
+
still receives every one of them.</p>
|
|
564
601
|
<p>For <strong>lot-level views</strong> (gallery), the sidebar shows lot-level findings by
|
|
565
602
|
default, with a <strong>Wafers</strong> tab listing every wafer that has its own per-wafer
|
|
566
603
|
findings \u2014 click a row to open that wafer.</p>
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export declare const WMAP_VERSION = "0.
|
|
2
|
-
export declare const WMAP_BUILD_TIME = "2026-08-
|
|
1
|
+
export declare const WMAP_VERSION = "0.22.0";
|
|
2
|
+
export declare const WMAP_BUILD_TIME = "2026-08-07T07:44:28.693Z";
|
|
3
3
|
//# sourceMappingURL=version.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const WMAP_VERSION="0.
|
|
1
|
+
export const WMAP_VERSION="0.22.0",WMAP_BUILD_TIME="2026-08-07T07:44:28.693Z";
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { WaferWarning, WaferMapResult } from '../renderer/buildWaferMap.js';
|
|
2
|
+
import type { StatsSummary, LotStatsSummary } from '../stats/types.js';
|
|
3
|
+
export type { WaferWarning };
|
|
4
|
+
/**
|
|
5
|
+
* Controls the built-in warning UI.
|
|
6
|
+
*
|
|
7
|
+
* Hosts that already have their own notification system should set
|
|
8
|
+
* `display: false` and read the warnings from `onWarning` — that way the library
|
|
9
|
+
* still does the collecting, de-duplicating and severity ordering, and the host
|
|
10
|
+
* only owns presentation. Turning the UI off without wiring `onWarning` means
|
|
11
|
+
* nobody is told, which is the situation this feature exists to end.
|
|
12
|
+
*/
|
|
13
|
+
export interface WarningsOptions {
|
|
14
|
+
/**
|
|
15
|
+
* Show the built-in warning indicator in the toolbar and the banner in the
|
|
16
|
+
* Summary panel. Default `true`.
|
|
17
|
+
*/
|
|
18
|
+
display?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Called with the collected, de-duplicated, severity-ordered warnings whenever
|
|
21
|
+
* they change (including once on mount, with an empty array when there are
|
|
22
|
+
* none, so a host can clear its own display).
|
|
23
|
+
*/
|
|
24
|
+
onWarning?: (warnings: WaferWarning[]) => void;
|
|
25
|
+
}
|
|
26
|
+
/** Severity with the documented default applied. */
|
|
27
|
+
export declare function severityOf(w: WaferWarning): NonNullable<WaferWarning['severity']>;
|
|
28
|
+
/**
|
|
29
|
+
* Gather every advisory relevant to one rendered map, most serious first.
|
|
30
|
+
*
|
|
31
|
+
* De-duplicated on `code` + `message`: a lot gallery legitimately produces the
|
|
32
|
+
* same geometry advisory on many wafers, and repeating it once per wafer would
|
|
33
|
+
* bury the one that differs.
|
|
34
|
+
*/
|
|
35
|
+
export declare function collectWarnings(sources: {
|
|
36
|
+
result?: Pick<WaferMapResult, 'warnings'> | null;
|
|
37
|
+
statsSummary?: StatsSummary | null;
|
|
38
|
+
lotStatsSummary?: LotStatsSummary | null;
|
|
39
|
+
}): WaferWarning[];
|
|
40
|
+
/**
|
|
41
|
+
* The Summary panel banner. One block per severity present, so an "dies may be
|
|
42
|
+
* mis-positioned" error is never visually flattened into an advisory.
|
|
43
|
+
*/
|
|
44
|
+
export declare function buildWarningsBanner(warnings: WaferWarning[]): HTMLDivElement;
|
|
45
|
+
/**
|
|
46
|
+
* The popup opened by the toolbar's warning indicator.
|
|
47
|
+
*
|
|
48
|
+
* Anchored like the toolbar's other menus (see `buildCheckMenuEl`) and flips to
|
|
49
|
+
* right-aligned near the viewport edge.
|
|
50
|
+
*/
|
|
51
|
+
export declare function buildWarningsMenuEl(anchorRect: DOMRect, warnings: WaferWarning[], ownerWindow?: Window): HTMLDivElement;
|
|
52
|
+
//# sourceMappingURL=warnings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{CLR as r,Z_BASE as y}from"./toolbar.js";const f={error:0,warning:1,info:2};export function severityOf(e){return e.severity??"warning"}export function collectWarnings(e){const i=[],s=new Set,n=t=>{if(!t?.message)return;const o=`${t.code}\0${t.message}`;s.has(o)||(s.add(o),i.push(t))};for(const t of e.result?.warnings??[])n(t);for(const t of e.statsSummary?.stats.warnings??[])n(t);for(const t of e.lotStatsSummary?.perWafer??[])for(const o of t.summary.stats.warnings??[])n(o);return i.sort((t,o)=>f[severityOf(t)]-f[severityOf(o)])}function b(e){return e==="error"?{bg:r.errBg,border:r.errBorder,text:r.errText}:{bg:r.warnBg,border:r.warnBorder,text:r.warnText}}function h(e){return e==="error"?"\u26D4":e==="info"?"\u2139":"\u26A0"}export function buildWarningsBanner(e){const i=document.createElement("div");Object.assign(i.style,{marginBottom:"10px"});for(const s of e){const n=severityOf(s),t=b(n),o=document.createElement("div");Object.assign(o.style,{background:t.bg,border:`1px solid ${t.border}`,borderRadius:"4px",padding:"7px 9px",marginBottom:"6px",fontSize:"10px",color:t.text,lineHeight:"1.5"}),o.setAttribute("role","status"),o.textContent=`${h(n)} ${s.message}`,i.appendChild(o)}return i}export function buildWarningsMenuEl(e,i,s=window){const n=document.createElement("div"),t=280,w=e.left+t<=(s.innerWidth??1/0)?e.left:Math.max(4,e.right-t);Object.assign(n.style,{position:"fixed",top:`${e.bottom+4}px`,left:`${w}px`,background:r.menuBg,border:`1px solid ${r.menuBorder}`,borderRadius:"4px",boxShadow:"0 4px 12px rgba(0,0,0,0.15)",zIndex:y,width:`${t}px`,maxHeight:"320px",overflowY:"auto",padding:"4px 0",pointerEvents:"auto"}),n.setAttribute("role","dialog"),n.setAttribute("aria-label","Data warnings");const d=document.createElement("div");Object.assign(d.style,{padding:"6px 12px 4px",fontSize:"10px",fontWeight:"700",letterSpacing:"0.06em",textTransform:"uppercase",color:r.label}),d.textContent=i.length===1?"1 warning":`${i.length} warnings`,n.appendChild(d);for(const c of i){const x=severityOf(c),m=b(x),a=document.createElement("div");Object.assign(a.style,{display:"flex",gap:"8px",padding:"8px 12px",fontSize:"11px",lineHeight:"1.5",color:r.text,borderTop:`1px solid ${r.menuBorder}`,alignItems:"flex-start"});const p=document.createElement("span");Object.assign(p.style,{color:m.text,flexShrink:"0"}),p.textContent=h(x),a.appendChild(p);const l=document.createElement("div"),g=document.createElement("div");Object.assign(g.style,{fontWeight:"700",color:m.text,marginBottom:"2px"}),g.textContent=c.code,l.appendChild(g);const u=document.createElement("div");u.textContent=c.message,l.appendChild(u),a.appendChild(l),n.appendChild(a)}return n}
|
|
@@ -415,13 +415,27 @@ export interface WaferWarning {
|
|
|
415
415
|
* so the pitch was derived as `diameter ÷ grid span`, assuming the grid spans
|
|
416
416
|
* the full wafer. That assumption fails whenever edge dies are absent, which
|
|
417
417
|
* silently scales every die position. Supply `dieConfig.width`/`height`.
|
|
418
|
+
* - `'test-count-capped'` — raised by `analyzeWaferMap`: more tests were found in
|
|
419
|
+
* the die data than the analysis cap allows, so test-value analysis was skipped
|
|
420
|
+
* entirely and NO test findings were produced. Pass `testNumbers` to scope it.
|
|
418
421
|
*
|
|
419
422
|
* The union is intentionally open to string so future advisory codes can be
|
|
420
423
|
* added without a breaking change; switch with a `default` branch.
|
|
421
424
|
*/
|
|
422
|
-
code: 'partial-coverage' | 'geometry-conflict' | 'inferred-pitch' | (string & {});
|
|
425
|
+
code: 'partial-coverage' | 'geometry-conflict' | 'inferred-pitch' | 'test-count-capped' | (string & {});
|
|
423
426
|
/** Human-readable explanation, suitable for direct display. */
|
|
424
427
|
message: string;
|
|
428
|
+
/**
|
|
429
|
+
* How much this affects trust in what is on screen. Drives the built-in
|
|
430
|
+
* warning indicator's colour and ordering; default `'warning'` when absent.
|
|
431
|
+
*
|
|
432
|
+
* - `'error'` — the map may be *positionally wrong* (dies drawn in the wrong
|
|
433
|
+
* place). Nothing downstream of the geometry can be trusted.
|
|
434
|
+
* - `'warning'` — something the viewer expected is absent or degraded, but what
|
|
435
|
+
* is drawn is correct.
|
|
436
|
+
* - `'info'` — worth knowing, no impact on correctness.
|
|
437
|
+
*/
|
|
438
|
+
severity?: 'error' | 'warning' | 'info';
|
|
425
439
|
/** Inference confidence in [0, 1] for the related quantity, when one exists. */
|
|
426
440
|
confidence?: number;
|
|
427
441
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createWafer as $}from"../core/wafer.js";import{isYieldEligibleDie as Q,getDieKey as I}from"../core/dies.js";import{applyOrientation as A,transformDies as Z}from"../core/transforms.js";import{affineRotation as ee,affineMirror as te,affineCompose as ne,affinePoint as ie}from"../core/transforms.js";import{inferWaferFromXY as se}from"../core/inference/wafer.js";import{resolveGridPitch as oe}from"../core/inference/pitch.js";import{assignGridIndices as re}from"../core/inference/grid.js";import{generateReticleGrid as ae}from"../core/reticle.js";import{buildView as T}from"./buildView.js";import{modeOf as fe}from"../core/utils.js";import{aggregateValues as L,aggregateBinCounts as de}from"../core/aggregates.js";export function isParametricTest(t){return t?.testType!=="F"}function ce(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.");return Array.isArray(t)?{results:t,waferOpts:void 0,dieOpts:void 0,explicitDies:void 0,reticleOpts:void 0,lotStackOpts:void 0,passBins:[1],testDefs:void 0,hbinDefs:void 0,sbinDefs:void 0,metadataFields:void 0,retestPolicy:"last",edgeDieYieldMode:"exclude"}:{results:t.results??[],waferOpts:t.waferConfig,dieOpts:t.dieConfig,explicitDies:t.dies,reticleOpts:t.reticleConfig,lotStackOpts:t.lotStack,passBins:t.passBins??[1],testDefs:t.testDefs,hbinDefs:t.hbinDefs,sbinDefs:t.sbinDefs,metadataFields:t.metadataFields,retestPolicy:t.retestPolicy??"last",edgeDieYieldMode:t.edgeDieYieldMode??"exclude"}}function le(t,n){return n?.coordinateOrigin?n.coordinateOrigin:{type:"center"}}function ue(t,n,e){if(n.type==="custom"&&n.offset)return{offsetX:n.offset.x,offsetY:n.offset.y};if(n.type!=="center"){let o=1/0,r=-1/0,h=1/0,f=-1/0;for(const i of t)i.x<o&&(o=i.x),i.x>r&&(r=i.x),i.y<h&&(h=i.y),i.y>f&&(f=i.y);return{offsetX:Math.round((r+o)/2),offsetY:Math.round((f+h)/2)}}return{offsetX:e.offsetX,offsetY:e.offsetY}}function he(t,n,e,o,r,h){if(h)return{colMidX:(Math.round(h.x)-n)*o,colMidY:(Math.round(h.y)-e)*r,anchored:!0};if(t.length===0)return{colMidX:0,colMidY:0,anchored:!1};let f=1/0,i=-1/0,d=1/0,a=-1/0;for(const s of t){const c=Math.round(s.x)-n,p=Math.round(s.y)-e;c<f&&(f=c),c>i&&(i=c),p<d&&(d=p),p>a&&(a=p)}return{colMidX:(f+i)/2*o,colMidY:(d+a)/2*r,anchored:!1}}function pe(t,n,e){let o=1/0,r=-1/0,h=1/0,f=-1/0,i=0,d=0;for(const c of t){const p=Math.round(c.x)-n,u=Math.round(c.y)-e;i+=p,d+=u,p<o&&(o=p),p>r&&(r=p),u<h&&(h=u),u>f&&(f=u)}const a=t.length,s=(c,p,u)=>{const g=(u-p)/2;return g<=0?0:Math.abs(c/a-(p+u)/2)/g};return s(i,o,r)>.11||s(d,h,f)>.11}function me(t,n){let e=t?.xAxisDirection==="left",o=t?.yAxisDirection==="down";return(n.type==="UL"||n.type==="UR")&&(o=!0),(n.type==="LR"||n.type==="UR")&&(e=!0),{flipX:e,flipY:o}}function ye(t,n){const{results:e,method:o,targetBin:r}=t;if(o==="mean"||o==="median"||o==="stddev"||o==="min"||o==="max"||o==="count"){const h=new Set((n??[]).filter(s=>!isParametricTest(s)).map(s=>s.testNumber)),f=new Set;for(const s of e)for(const c of s)if(c.testValues)for(const p of Object.keys(c.testValues)){const u=Number(p);h.has(u)||f.add(u)}if(f.size===0)return L(e,o);const i=[...f],d=i.map(s=>L(e,o,s)),a=new Map;for(let s=0;s<i.length;s++){const c=i[s];for(const p of d[s]){const u=I(p);a.has(u)||a.set(u,{template:p,testValues:{}});const g=a.get(u),m=p.testValues?.[0];m!==void 0&&(g.testValues[c]=m)}}return[...a.values()].map(({template:s,testValues:c})=>({...s,testValues:Object.keys(c).length>0?c:void 0}))}if(o==="countBin"||o==="percent"){if(r===void 0)return[];const h=de(e,r,"hard");if(o==="countBin")return h;const f=e.length;return h.map(i=>({...i,testValues:{0:f>0?(i.testValues?.[0]??0)/f*100:0}}))}if(o==="mode"){const h=new Map;for(const i of e)for(const d of i){const a=I(d);h.has(a)||h.set(a,[]),h.get(a).push(d)}const f=[];for(const[i,d]of h){const a=i.split(","),s=Number(a[0]),c=Number(a[1]),p=d.map(g=>g.hbin).filter(g=>g!==void 0),u=fe(p);u!==null&&f.push({x:s,y:c,hbin:u})}return f}return[]}function W(t){const n=t.length,e=t.filter(r=>r.edgeExcluded).length,o=t.filter(r=>dieHasTestData(r)||r.hbin!==void 0||r.sbin!==void 0).length;return{filledDies:o,totalDies:n,edgeExcludedDies:e,ratio:n>0?o/n:0}}function N(t,n,e="exclude"){const o=new Set(n),h=t.filter(u=>!u.partial).filter(u=>u.edgeExcluded).length,f=t.filter(u=>u.partial).length;let i=0,d=0,a=!1;for(const u of t){if(!Q(u))continue;const g=u.hbin??u.sbin;g!==void 0&&(a=!0,o.has(g)?i++:d++)}const s=i+d,c=a&&s>0?i/s*100:null;let p=null;if(e==="denominator-only"){const u=s+h;p=a&&u>0?i/u*100:null}return{passDies:i,failDies:d,edgeExcludedDies:h,partialDies:f,totalDies:s,yieldPercent:c,yieldPercentGross:p}}function _(t,n,e,o,r,h=0,f=0,i=0,d=0,a=0,s=!1,c=!1){if(!t)return[];const p=t.anchorDie??{x:0,y:0},u=ae(n,{width:t.width,height:t.height,diePitchX:o,diePitchY:r,anchorDie:{x:p.x-h,y:p.y-f},gridOrigin:{x:-i,y:-d}}),g=ne(te(s,c,n.center.x,n.center.y),ee(a,n.center.x,n.center.y));return u.filter(m=>{const x=m.width/2,O=m.height/2,S=[[m.x-x,m.y-O],[m.x+x,m.y-O],[m.x+x,m.y+O],[m.x-x,m.y+O]].map(([y,C])=>ie(g,y,C)),R=Math.min(...S.map(y=>y.x)),Y=Math.max(...S.map(y=>y.x)),E=Math.min(...S.map(y=>y.y)),D=Math.max(...S.map(y=>y.y));return e.some(y=>y.physX>=R&&y.physX<Y&&y.physY>=E&&y.physY<D)})}function ge(t,n,e){const o=(n.radius-e)**2;return t.map(r=>{const h=r.physX-n.center.x,f=r.physY-n.center.y;return h*h+f*f>o?{...r,edgeExcluded:!0}:r})}function xe(t,n,e){const o=new Map;for(const i of t){let d=o.get(i.x);d||(d=new Map,o.set(i.x,d)),d.set(i.y,(d.get(i.y)??0)+1)}const r=new Set(e);function h(i,d){const a=i.hbin,s=d.hbin;if(a===void 0||s===void 0)return!1;const c=r.has(a),p=r.has(s);return n==="best"?p!==c?p:s<a:p!==c?c:s>a}const f=new Map;for(const i of t){const d=I(i),a=f.get(d);n==="first"&&a||(n==="best"||n==="worst")&&a&&!h(a,i)||f.set(d,i)}return Array.from(f.values()).map(i=>{const a=o.get(i.x)?.get(i.y)??1;return a>1?{...i,retestCount:a}:i})}export function getDieTestValue(t,n){return t.testValues?.[n]}export function getTestPassStatus(t,n,e){const o=t.testPass?.[n];if(o!==void 0)return o;if(e!==void 0&&!isParametricTest(e)){const r=t.testValues?.[n];if(r===0||r===1)return r===1}}export function dieHasTestData(t){return t.testValues!==void 0&&Object.keys(t.testValues).length>0||t.testPass!==void 0&&Object.keys(t.testPass).length>0}function G(t,n){const e={};return n.hbin!==void 0&&(e.hbin=n.hbin),n.sbin!==void 0&&(e.sbin=n.sbin),n.retestCount!==void 0&&(e.retestCount=n.retestCount),n.siteNum!==void 0&&(e.siteNum=n.siteNum),n.partId!==void 0&&(e.partId=n.partId),n.metadata!==void 0&&(e.metadata=n.metadata),n.testPass!==void 0&&(e.testPass=n.testPass),n.testValues!==void 0&&(e.testValues=n.testValues),{...t,...e}}function K(t,n){return n.plotMode?n.plotMode:t.some(dieHasTestData)?"value":"hardBin"}function z(t){return(t.warnings??[]).map(n=>({code:we(n),message:n,confidence:t.wafer.confidence}))}function we(t){return t.includes(j)?"geometry-conflict":t.includes(H)?"inferred-pitch":"partial-coverage"}const j="do not fit inside the supplied",H="The die pitch was inferred as";export function buildWaferMap(t,n){const e=ce(t),{debug:o,...r}=n??{},h=e.lotStackOpts?ye(e.lotStackOpts,e.testDefs):e.results;for(let l=0;l<Math.min(5,h.length);l++){const M=h[l];if(typeof M.x=="string"||typeof M.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 }")}const f=xe(h,e.retestPolicy,e.passBins),i={wafer:{confidence:1,method:"provided"},diePitch:{confidence:1,units:"mm"},grid:{confidence:1}};if(e.explicitDies){let l=e.explicitDies;if(f.length>0){const J=new Map(f.map(P=>[I(P),P]));l=l.map(P=>{const X=J.get(I(P));return X?G(P,X):P})}const M=e.waferOpts?.diameter??300,b=$({diameter:M,notch:e.waferOpts?.notch,orientation:e.waferOpts?.orientation??0,metadata:e.waferOpts?.metadata});b.orientation!==0&&(l=A(l,b));const v=_(e.reticleOpts,b,l,1,1,0,0,0,0,b.orientation),q=r.showReticle??e.reticleOpts!==void 0,V=T(b,l,{...r,reticles:v,showReticle:q,plotMode:K(f,r),testDefs:e.testDefs,isLotStack:!1},{hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,metadataFields:e.metadataFields});return{wafer:b,dies:l,view:V,reticleConfig:e.reticleOpts,units:"mm",inference:i,warnings:z(i),plotMode:V.plotMode,metadata:V.metadata,isLotStack:!1,dataCoverage:W(l),yield:N(l,e.passBins,e.edgeDieYieldMode),reticles:v,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs,metadataFields:e.metadataFields}}const d=f.map(l=>({x:l.x,y:l.y})),a=oe(d,e.dieOpts,e.waferOpts?.diameter);i.diePitch={confidence:a.confidence,units:a.units};const{pitchX:s,pitchY:c}=a,p=a.units,u=le(f,e.dieOpts),g=re(d);i.grid={confidence:g.confidence};const{offsetX:m,offsetY:x}=ue(d,u,g),{flipX:O,flipY:S}=me(e.dieOpts,u),{colMidX:R,colMidY:Y,anchored:E}=he(d,m,x,s,c,e.waferOpts?.center);let D=e.waferOpts?.diameter;const y=d.map(l=>({x:(Math.round(l.x)-m)*s-R,y:(Math.round(l.y)-x)*c-Y})),C=y.length>0?Math.max(...y.map(({x:l,y:M})=>Math.hypot(Math.abs(l)+s/2,Math.abs(M)+c/2))):0;if(D===void 0)if(d.length>0)if(p==="mm"){const l=se(y,{minRadius:C});D=l.diameter,i.wafer={confidence:l.confidence,method:l.method}}else D=C*2,i.wafer={confidence:a.confidence*.8,method:"extent"};else D=p==="mm"?300:30,i.wafer={confidence:0,method:"default"};if(!E&&d.length>0&&pe(d,m,x)&&(i.wafer.method="inferred-partial",(i.warnings??=[]).push("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){const l=e.dieOpts?.width!==void 0&&e.dieOpts?.height!==void 0;if(l&&C>D/2+1e-9){const M=y.filter(({x:b,y:v})=>Math.hypot(Math.abs(b)+s/2,Math.abs(v)+c/2)>D/2+1e-9).length;(i.warnings??=[]).push(`${M} of ${y.length} probed die positions ${j} ${D} mm wafer at the supplied die pitch of ${s} \xD7 ${c} 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 ${(C*2).toFixed(1)} mm. Check waferConfig.diameter and dieConfig.width/height against the real device.`)}else l||(i.warnings??=[]).push(`${H} ${s.toFixed(3)} \xD7 ${c.toFixed(3)} mm by assuming the die grid fits within the ${D} mm wafer. The assumed aspect ratio may not match the true die shape whenever edge dies are absent from the data (a reticle-complete map, or partial dies filtered out upstream). Supply dieConfig.width and dieConfig.height \u2014 the die pitch, not the diameter, is what fixes placement.`)}const k=$({diameter:D,notch:e.waferOpts?.notch,orientation:e.waferOpts?.orientation??0,metadata:e.waferOpts?.metadata}),Me={width:s,height:c};let w=f.map(l=>{const M=Math.round(l.x)-m,b=Math.round(l.y)-x,v={id:`${M}_${b}`,x:M,y:b,physX:M*s-R,physY:b*c-Y,width:s,height:c,insideWafer:!0,partial:!1};return G(v,l)});(m!==0||x!==0)&&(w=w.map(l=>({...l,x:l.x+m,y:l.y+x,id:`${l.x+m}_${l.y+x}`}))),w=A(w,k),(O||S)&&(w=Z(w,{flipX:O,flipY:S},k.center)),e.waferOpts?.edgeExclusion&&e.waferOpts.edgeExclusion>0&&(w=ge(w,k,e.waferOpts.edgeExclusion));const B=_(e.reticleOpts,k,w,s,c,m,x,R,Y,k.orientation,O,S),U=r.showReticle??e.reticleOpts!==void 0,F=T(k,w,{...r,reticles:B,showReticle:U,plotMode:K(f,r),testDefs:e.testDefs,dataAxisFlip:{x:O,y:S},isLotStack:e.lotStackOpts!==void 0,aggregationMethod:e.lotStackOpts?.method,lotSize:e.lotStackOpts?.results.length},{hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,metadataFields:e.metadataFields});return{wafer:k,dies:w,view:F,reticleConfig:e.reticleOpts,units:p,inference:i,warnings:z(i),plotMode:F.plotMode,metadata:F.metadata,isLotStack:e.lotStackOpts!==void 0,dataCoverage:W(w),yield:N(w,e.passBins,e.edgeDieYieldMode),reticles:B,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs,metadataFields:e.metadataFields,aggrMethod:F.aggrMethod,lotSize:F.lotSize}}
|
|
1
|
+
import{createWafer as $}from"../core/wafer.js";import{isYieldEligibleDie as Q,getDieKey as I}from"../core/dies.js";import{applyOrientation as A,transformDies as Z}from"../core/transforms.js";import{affineRotation as ee,affineMirror as te,affineCompose as ne,affinePoint as ie}from"../core/transforms.js";import{inferWaferFromXY as se}from"../core/inference/wafer.js";import{resolveGridPitch as oe}from"../core/inference/pitch.js";import{assignGridIndices as re}from"../core/inference/grid.js";import{generateReticleGrid as ae}from"../core/reticle.js";import{buildView as T}from"./buildView.js";import{modeOf as fe}from"../core/utils.js";import{aggregateValues as L,aggregateBinCounts as de}from"../core/aggregates.js";export function isParametricTest(t){return t?.testType!=="F"}function ce(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.");return Array.isArray(t)?{results:t,waferOpts:void 0,dieOpts:void 0,explicitDies:void 0,reticleOpts:void 0,lotStackOpts:void 0,passBins:[1],testDefs:void 0,hbinDefs:void 0,sbinDefs:void 0,metadataFields:void 0,retestPolicy:"last",edgeDieYieldMode:"exclude"}:{results:t.results??[],waferOpts:t.waferConfig,dieOpts:t.dieConfig,explicitDies:t.dies,reticleOpts:t.reticleConfig,lotStackOpts:t.lotStack,passBins:t.passBins??[1],testDefs:t.testDefs,hbinDefs:t.hbinDefs,sbinDefs:t.sbinDefs,metadataFields:t.metadataFields,retestPolicy:t.retestPolicy??"last",edgeDieYieldMode:t.edgeDieYieldMode??"exclude"}}function le(t,n){return n?.coordinateOrigin?n.coordinateOrigin:{type:"center"}}function ue(t,n,e){if(n.type==="custom"&&n.offset)return{offsetX:n.offset.x,offsetY:n.offset.y};if(n.type!=="center"){let o=1/0,r=-1/0,h=1/0,f=-1/0;for(const i of t)i.x<o&&(o=i.x),i.x>r&&(r=i.x),i.y<h&&(h=i.y),i.y>f&&(f=i.y);return{offsetX:Math.round((r+o)/2),offsetY:Math.round((f+h)/2)}}return{offsetX:e.offsetX,offsetY:e.offsetY}}function he(t,n,e,o,r,h){if(h)return{colMidX:(Math.round(h.x)-n)*o,colMidY:(Math.round(h.y)-e)*r,anchored:!0};if(t.length===0)return{colMidX:0,colMidY:0,anchored:!1};let f=1/0,i=-1/0,d=1/0,a=-1/0;for(const s of t){const c=Math.round(s.x)-n,p=Math.round(s.y)-e;c<f&&(f=c),c>i&&(i=c),p<d&&(d=p),p>a&&(a=p)}return{colMidX:(f+i)/2*o,colMidY:(d+a)/2*r,anchored:!1}}function pe(t,n,e){let o=1/0,r=-1/0,h=1/0,f=-1/0,i=0,d=0;for(const c of t){const p=Math.round(c.x)-n,u=Math.round(c.y)-e;i+=p,d+=u,p<o&&(o=p),p>r&&(r=p),u<h&&(h=u),u>f&&(f=u)}const a=t.length,s=(c,p,u)=>{const g=(u-p)/2;return g<=0?0:Math.abs(c/a-(p+u)/2)/g};return s(i,o,r)>.11||s(d,h,f)>.11}function me(t,n){let e=t?.xAxisDirection==="left",o=t?.yAxisDirection==="down";return(n.type==="UL"||n.type==="UR")&&(o=!0),(n.type==="LR"||n.type==="UR")&&(e=!0),{flipX:e,flipY:o}}function ye(t,n){const{results:e,method:o,targetBin:r}=t;if(o==="mean"||o==="median"||o==="stddev"||o==="min"||o==="max"||o==="count"){const h=new Set((n??[]).filter(s=>!isParametricTest(s)).map(s=>s.testNumber)),f=new Set;for(const s of e)for(const c of s)if(c.testValues)for(const p of Object.keys(c.testValues)){const u=Number(p);h.has(u)||f.add(u)}if(f.size===0)return L(e,o);const i=[...f],d=i.map(s=>L(e,o,s)),a=new Map;for(let s=0;s<i.length;s++){const c=i[s];for(const p of d[s]){const u=I(p);a.has(u)||a.set(u,{template:p,testValues:{}});const g=a.get(u),m=p.testValues?.[0];m!==void 0&&(g.testValues[c]=m)}}return[...a.values()].map(({template:s,testValues:c})=>({...s,testValues:Object.keys(c).length>0?c:void 0}))}if(o==="countBin"||o==="percent"){if(r===void 0)return[];const h=de(e,r,"hard");if(o==="countBin")return h;const f=e.length;return h.map(i=>({...i,testValues:{0:f>0?(i.testValues?.[0]??0)/f*100:0}}))}if(o==="mode"){const h=new Map;for(const i of e)for(const d of i){const a=I(d);h.has(a)||h.set(a,[]),h.get(a).push(d)}const f=[];for(const[i,d]of h){const a=i.split(","),s=Number(a[0]),c=Number(a[1]),p=d.map(g=>g.hbin).filter(g=>g!==void 0),u=fe(p);u!==null&&f.push({x:s,y:c,hbin:u})}return f}return[]}function W(t){const n=t.length,e=t.filter(r=>r.edgeExcluded).length,o=t.filter(r=>dieHasTestData(r)||r.hbin!==void 0||r.sbin!==void 0).length;return{filledDies:o,totalDies:n,edgeExcludedDies:e,ratio:n>0?o/n:0}}function N(t,n,e="exclude"){const o=new Set(n),h=t.filter(u=>!u.partial).filter(u=>u.edgeExcluded).length,f=t.filter(u=>u.partial).length;let i=0,d=0,a=!1;for(const u of t){if(!Q(u))continue;const g=u.hbin??u.sbin;g!==void 0&&(a=!0,o.has(g)?i++:d++)}const s=i+d,c=a&&s>0?i/s*100:null;let p=null;if(e==="denominator-only"){const u=s+h;p=a&&u>0?i/u*100:null}return{passDies:i,failDies:d,edgeExcludedDies:h,partialDies:f,totalDies:s,yieldPercent:c,yieldPercentGross:p}}function _(t,n,e,o,r,h=0,f=0,i=0,d=0,a=0,s=!1,c=!1){if(!t)return[];const p=t.anchorDie??{x:0,y:0},u=ae(n,{width:t.width,height:t.height,diePitchX:o,diePitchY:r,anchorDie:{x:p.x-h,y:p.y-f},gridOrigin:{x:-i,y:-d}}),g=ne(te(s,c,n.center.x,n.center.y),ee(a,n.center.x,n.center.y));return u.filter(m=>{const x=m.width/2,O=m.height/2,S=[[m.x-x,m.y-O],[m.x+x,m.y-O],[m.x+x,m.y+O],[m.x-x,m.y+O]].map(([y,C])=>ie(g,y,C)),R=Math.min(...S.map(y=>y.x)),Y=Math.max(...S.map(y=>y.x)),E=Math.min(...S.map(y=>y.y)),D=Math.max(...S.map(y=>y.y));return e.some(y=>y.physX>=R&&y.physX<Y&&y.physY>=E&&y.physY<D)})}function ge(t,n,e){const o=(n.radius-e)**2;return t.map(r=>{const h=r.physX-n.center.x,f=r.physY-n.center.y;return h*h+f*f>o?{...r,edgeExcluded:!0}:r})}function xe(t,n,e){const o=new Map;for(const i of t){let d=o.get(i.x);d||(d=new Map,o.set(i.x,d)),d.set(i.y,(d.get(i.y)??0)+1)}const r=new Set(e);function h(i,d){const a=i.hbin,s=d.hbin;if(a===void 0||s===void 0)return!1;const c=r.has(a),p=r.has(s);return n==="best"?p!==c?p:s<a:p!==c?c:s>a}const f=new Map;for(const i of t){const d=I(i),a=f.get(d);n==="first"&&a||(n==="best"||n==="worst")&&a&&!h(a,i)||f.set(d,i)}return Array.from(f.values()).map(i=>{const a=o.get(i.x)?.get(i.y)??1;return a>1?{...i,retestCount:a}:i})}export function getDieTestValue(t,n){return t.testValues?.[n]}export function getTestPassStatus(t,n,e){const o=t.testPass?.[n];if(o!==void 0)return o;if(e!==void 0&&!isParametricTest(e)){const r=t.testValues?.[n];if(r===0||r===1)return r===1}}export function dieHasTestData(t){return t.testValues!==void 0&&Object.keys(t.testValues).length>0||t.testPass!==void 0&&Object.keys(t.testPass).length>0}function G(t,n){const e={};return n.hbin!==void 0&&(e.hbin=n.hbin),n.sbin!==void 0&&(e.sbin=n.sbin),n.retestCount!==void 0&&(e.retestCount=n.retestCount),n.siteNum!==void 0&&(e.siteNum=n.siteNum),n.partId!==void 0&&(e.partId=n.partId),n.metadata!==void 0&&(e.metadata=n.metadata),n.testPass!==void 0&&(e.testPass=n.testPass),n.testValues!==void 0&&(e.testValues=n.testValues),{...t,...e}}function K(t,n){return n.plotMode?n.plotMode:t.some(dieHasTestData)?"value":"hardBin"}function z(t){return(t.warnings??[]).map(n=>({code:we(n),message:n,severity:"error",confidence:t.wafer.confidence}))}function we(t){return t.includes(j)?"geometry-conflict":t.includes(H)?"inferred-pitch":"partial-coverage"}const j="do not fit inside the supplied",H="The die pitch was inferred as";export function buildWaferMap(t,n){const e=ce(t),{debug:o,...r}=n??{},h=e.lotStackOpts?ye(e.lotStackOpts,e.testDefs):e.results;for(let l=0;l<Math.min(5,h.length);l++){const M=h[l];if(typeof M.x=="string"||typeof M.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 }")}const f=xe(h,e.retestPolicy,e.passBins),i={wafer:{confidence:1,method:"provided"},diePitch:{confidence:1,units:"mm"},grid:{confidence:1}};if(e.explicitDies){let l=e.explicitDies;if(f.length>0){const J=new Map(f.map(P=>[I(P),P]));l=l.map(P=>{const X=J.get(I(P));return X?G(P,X):P})}const M=e.waferOpts?.diameter??300,b=$({diameter:M,notch:e.waferOpts?.notch,orientation:e.waferOpts?.orientation??0,metadata:e.waferOpts?.metadata});b.orientation!==0&&(l=A(l,b));const v=_(e.reticleOpts,b,l,1,1,0,0,0,0,b.orientation),q=r.showReticle??e.reticleOpts!==void 0,V=T(b,l,{...r,reticles:v,showReticle:q,plotMode:K(f,r),testDefs:e.testDefs,isLotStack:!1},{hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,metadataFields:e.metadataFields});return{wafer:b,dies:l,view:V,reticleConfig:e.reticleOpts,units:"mm",inference:i,warnings:z(i),plotMode:V.plotMode,metadata:V.metadata,isLotStack:!1,dataCoverage:W(l),yield:N(l,e.passBins,e.edgeDieYieldMode),reticles:v,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs,metadataFields:e.metadataFields}}const d=f.map(l=>({x:l.x,y:l.y})),a=oe(d,e.dieOpts,e.waferOpts?.diameter);i.diePitch={confidence:a.confidence,units:a.units};const{pitchX:s,pitchY:c}=a,p=a.units,u=le(f,e.dieOpts),g=re(d);i.grid={confidence:g.confidence};const{offsetX:m,offsetY:x}=ue(d,u,g),{flipX:O,flipY:S}=me(e.dieOpts,u),{colMidX:R,colMidY:Y,anchored:E}=he(d,m,x,s,c,e.waferOpts?.center);let D=e.waferOpts?.diameter;const y=d.map(l=>({x:(Math.round(l.x)-m)*s-R,y:(Math.round(l.y)-x)*c-Y})),C=y.length>0?Math.max(...y.map(({x:l,y:M})=>Math.hypot(Math.abs(l)+s/2,Math.abs(M)+c/2))):0;if(D===void 0)if(d.length>0)if(p==="mm"){const l=se(y,{minRadius:C});D=l.diameter,i.wafer={confidence:l.confidence,method:l.method}}else D=C*2,i.wafer={confidence:a.confidence*.8,method:"extent"};else D=p==="mm"?300:30,i.wafer={confidence:0,method:"default"};if(!E&&d.length>0&&pe(d,m,x)&&(i.wafer.method="inferred-partial",(i.warnings??=[]).push("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){const l=e.dieOpts?.width!==void 0&&e.dieOpts?.height!==void 0;if(l&&C>D/2+1e-9){const M=y.filter(({x:b,y:v})=>Math.hypot(Math.abs(b)+s/2,Math.abs(v)+c/2)>D/2+1e-9).length;(i.warnings??=[]).push(`${M} of ${y.length} probed die positions ${j} ${D} mm wafer at the supplied die pitch of ${s} \xD7 ${c} 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 ${(C*2).toFixed(1)} mm. Check waferConfig.diameter and dieConfig.width/height against the real device.`)}else l||(i.warnings??=[]).push(`${H} ${s.toFixed(3)} \xD7 ${c.toFixed(3)} mm by assuming the die grid fits within the ${D} mm wafer. The assumed aspect ratio may not match the true die shape whenever edge dies are absent from the data (a reticle-complete map, or partial dies filtered out upstream). Supply dieConfig.width and dieConfig.height \u2014 the die pitch, not the diameter, is what fixes placement.`)}const k=$({diameter:D,notch:e.waferOpts?.notch,orientation:e.waferOpts?.orientation??0,metadata:e.waferOpts?.metadata}),Me={width:s,height:c};let w=f.map(l=>{const M=Math.round(l.x)-m,b=Math.round(l.y)-x,v={id:`${M}_${b}`,x:M,y:b,physX:M*s-R,physY:b*c-Y,width:s,height:c,insideWafer:!0,partial:!1};return G(v,l)});(m!==0||x!==0)&&(w=w.map(l=>({...l,x:l.x+m,y:l.y+x,id:`${l.x+m}_${l.y+x}`}))),w=A(w,k),(O||S)&&(w=Z(w,{flipX:O,flipY:S},k.center)),e.waferOpts?.edgeExclusion&&e.waferOpts.edgeExclusion>0&&(w=ge(w,k,e.waferOpts.edgeExclusion));const B=_(e.reticleOpts,k,w,s,c,m,x,R,Y,k.orientation,O,S),U=r.showReticle??e.reticleOpts!==void 0,F=T(k,w,{...r,reticles:B,showReticle:U,plotMode:K(f,r),testDefs:e.testDefs,dataAxisFlip:{x:O,y:S},isLotStack:e.lotStackOpts!==void 0,aggregationMethod:e.lotStackOpts?.method,lotSize:e.lotStackOpts?.results.length},{hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,metadataFields:e.metadataFields});return{wafer:k,dies:w,view:F,reticleConfig:e.reticleOpts,units:p,inference:i,warnings:z(i),plotMode:F.plotMode,metadata:F.metadata,isLotStack:e.lotStackOpts!==void 0,dataCoverage:W(w),yield:N(w,e.passBins,e.edgeDieYieldMode),reticles:B,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs,metadataFields:e.metadataFields,aggrMethod:F.aggrMethod,lotSize:F.lotSize}}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { buildView,
|
|
1
|
+
export { buildView, getDieKey, buildHoverText, buildMapTitle, } from './buildView.js';
|
|
2
2
|
export type { View, MapTitleParts, PlotMode, ViewRect, ViewText, ViewHoverPoint, ViewOverlay, ViewOptions, } from './buildView.js';
|
|
3
3
|
export * from './buildWaferMap.js';
|
|
4
4
|
export type { WaferMetadata, DieMetadata } from '../core/metadata.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export{buildView,
|
|
1
|
+
export{buildView,getDieKey,buildHoverText,buildMapTitle}from"./buildView.js";export*from"./buildWaferMap.js";export{hardBinColor,hardBinGreyscale,valueToViridis,valueToGreyscale,softBinColor,contrastTextColor}from"./colorMap.js";export*from"./colorSchemes.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{analyzeWaferMap as m}from"./analyzeWaferMap.js";import{median as u}from"../core/utils.js";const y=1.3,h=2;function g(o,s){const
|
|
1
|
+
import{analyzeWaferMap as m}from"./analyzeWaferMap.js";import{median as u}from"../core/utils.js";const y=1.3,h=2;function g(o,s){const i={info:0,notable:1,unusual:2};return i[o]>=i[s]?o:s}function b(o){const s=new Map;for(const r of o){const a=new Set,f=new Set(r.summary.findings.flatMap(e=>e.absorbedIds??[]));for(const e of r.summary.findings){if(f.has(e.id))continue;const n=[e.variable.kind,e.variable.index??"",e.variable.bin??"",e.comparison.family,e.comparison.left,e.effect.direction].join("|");if(a.has(n))continue;a.add(n);const t=s.get(n)??{finding:e,waferIndices:[],severity:e.severity};t.waferIndices.push(r.waferIndex),t.severity=g(t.severity,e.severity),s.set(n,t)}}const i=[];for(const[r,a]of s){if(a.waferIndices.length<h)continue;const f=a.waferIndices.length/Math.max(1,o.length),e=f>=.6||a.severity==="unusual"?"unusual":"notable",n=a.finding;i.push({id:`lot-repeat:${r}`,level:"lot",severity:e,variable:{...n.variable},comparison:{...n.comparison},effect:{direction:n.effect.direction,absoluteDelta:n.effect.absoluteDelta,relativeDelta:n.effect.relativeDelta,effectSize:n.effect.effectSize},stats:{method:"wafer-finding-frequency",sampleSizeLeft:a.waferIndices.length,sampleSizeRight:o.length-a.waferIndices.length},summary:`${n.summary} \u2014 seen on ${a.waferIndices.length}/${o.length} wafers (${Math.round(f*100)}%)`,highlight:{kind:"wafer",waferIndices:a.waferIndices}})}return i}function w(o){const s=o.map(e=>({waferIndex:e.waferIndex,yieldPercent:e.summary.stats.yieldPercent})).filter(e=>e.yieldPercent!==null);if(s.length<3)return[];const i=s.map(e=>e.yieldPercent),r=u(i),a=u(i.map(e=>Math.abs(e-r)));if(!Number.isFinite(a)||a===0)return[];const f=[];for(const e of s){const n=.6745*(e.yieldPercent-r)/a;if(Math.abs(n)<y)continue;const t=e.yieldPercent-r;f.push({id:`inter-wafer:yield:${e.waferIndex}`,level:"inter-wafer",severity:Math.abs(n)>=2?"unusual":"notable",variable:{kind:"yield",label:"Yield"},comparison:{family:"wafer",left:`Wafer ${e.waferIndex+1}`,right:"Lot median"},effect:{direction:t>0?"higher":"lower",absoluteDelta:t,relativeDelta:r===0?void 0:t/r,effectSize:n},stats:{method:"mad-z-score",sampleSizeLeft:1,sampleSizeRight:s.length-1},summary:`Wafer ${e.waferIndex+1} yield is ${Math.abs(t).toFixed(1)} percentage points ${t>0?"higher":"lower"} than the lot median`,highlight:{kind:"wafer",waferIndices:[e.waferIndex]}})}return f}export function analyzeWaferLot(o,s={}){const i=o.map((t,l)=>({waferIndex:l,summary:s.perWaferSummaries?.[l]??m(t,s)})),r=[...b(i),...w(i)].sort((t,l)=>{const c=t.severity==="unusual"?2:t.severity==="notable"?1:0,d=l.severity==="unusual"?2:l.severity==="notable"?1:0;return c!==d?d-c:t.summary.localeCompare(l.summary,"en")}),a=i.find(t=>t.summary.wafer)?.summary.wafer,f=a?Object.fromEntries(Object.entries(a).filter(([t])=>t!=="wafer"&&t!=="waferId")):void 0,e=i.map(({waferIndex:t,summary:l})=>({waferIndex:t,yieldPercent:l.stats.yieldPercent})),n=i.map(({waferIndex:t,summary:l})=>l.stats.perTestStats?.length?{waferIndex:t,tests:l.stats.perTestStats}:null).filter(t=>t!==null);return{level:"lot",hasNotableFindings:r.some(t=>t.severity!=="info")||i.some(t=>t.summary.hasNotableFindings),findings:r,lot:Object.keys(f??{}).length>0?f:void 0,stats:{waferCount:o.length},lotYieldSeries:e,perWafer:i,...n.length>0?{perWaferTestStats:n}:{}}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{isYieldEligibleDie as X,getDieKey as T}from"../core/dies.js";import{buildWaferMap as De,getTestPassStatus as ue,isParametricTest as q}from"../renderer/buildWaferMap.js";import{buildQuadrantRegions as $e,buildReticlePositionRegions as ke,buildRingRegions as ze,buildSectorRegions as ve,buildTestSiteRegions as Me,sectorCompassNames as de,areQuadrantsAdjacent as Re,parseRegionKey as j}from"./regions.js";import{buildClusterFindings as Be}from"./clusterDetection.js";import{classifyPattern as Ve}from"./patternClassification.js";import{normalCdf as ge}from"./math.js";import{quantile as Z}from"./math.js";import{mean as G,clamp01 as he}from"../core/utils.js";const Fe={ringCount:4,passBins:[1],significanceLevel:.05,minimumEffectSize:.2,minimumRelativeEffect:1,minimumSampleSize:5,includePartial:!1,includeEdgeExcluded:!1,enableYieldAnalysis:!0,enableHardBinAnalysis:!0,enableSoftBinAnalysis:!0,enableTestValueAnalysis:!1,computePerTestStats:!1,enableReticlePositionAnalysis:!0,enableClusterAnalysis:!0,enableAngularAnalysis:!0,sectorCount:8,enablePatternClassification:!0,minimumClusterSize:5};function Pe(s,r){const o={...s};return o.minimumClusterSize=Math.max(5,Math.round(r*.003)),o}function Le(s){return"wafer"in s&&"dies"in s&&"view"in s?s:De(s)}function Ne(s,r){return X(s,r)?s.hbin!==void 0||s.sbin!==void 0||s.testValues!==void 0&&Object.keys(s.testValues).length>0:!1}function Ce(s,r,o){if(!s||r)return;const e=(o??[]).filter(n=>q(n)&&(n.limitLow!==void 0||n.limitHigh!==void 0));if(e.length!==0)return n=>{for(const l of e){const a=l.testNumber,i=n.testValues?.[a];if(i!==void 0&&(l.limitLow!==void 0&&i<l.limitLow||l.limitHigh!==void 0&&i>l.limitHigh))return!0}return!1}}function Ae(s,r,o){const e=new Set,n=new Set,l=new Set,a=new Map,i=new Map;for(const t of s){if(t.testValues)for(const c of Object.keys(t.testValues))e.add(Number(c));if(t.testPass)for(const c of Object.keys(t.testPass))e.add(Number(c));t.hbin!==void 0&&n.add(t.hbin),t.sbin!==void 0&&l.add(t.sbin),X(t)&&(t.hbin!==void 0&&a.set(t.hbin,(a.get(t.hbin)??0)+1),t.sbin!==void 0&&i.set(t.sbin,(i.get(t.sbin)??0)+1))}return{totalDies:s.length,analyzedDies:r,excludedDies:s.length-r,yieldPercent:o,testsConsidered:[...e].sort((t,c)=>t-c),hardBinsConsidered:[...n].sort((t,c)=>t-c),softBinsConsidered:[...l].sort((t,c)=>t-c),...a.size?{hardBinCounts:Object.fromEntries(a)}:{},...i.size?{softBinCounts:Object.fromEntries(i)}:{}}}function Ke(s,r){if(!r?.length)return;const o=r.filter(n=>q(n)&&(n.limitLow!==void 0||n.limitHigh!==void 0));if(!o.length)return;const e=[];for(const n of o){const l=n.testNumber;let a=0,i=0,t=0,c=0;for(const u of s){if(u.partial||u.edgeExcluded)continue;const b=u.testValues?.[l];b!==void 0&&(c++,n.limitLow!==void 0&&b<n.limitLow?i++:n.limitHigh!==void 0&&b>n.limitHigh?t++:a++)}e.push({testNumber:l,label:n.name,passDies:a,failLowDies:i,failHighDies:t,totalDies:c,yieldPercent:c>0?a/c*100:null})}return e.length?e:void 0}export function computeFunctionalYield(s,r){if(!r?.length)return;const o=r.filter(n=>!q(n));if(!o.length)return;const e=[];for(const n of o){const l=n.testNumber;let a=0,i=0;for(const c of s){if(c.partial||c.edgeExcluded)continue;const u=ue(c,l,n);u!==void 0&&(u?a++:i++)}const t=a+i;e.push({testNumber:l,label:n.name,passDies:a,failDies:i,totalDies:t,passRatePercent:t>0?a/t*100:null})}return e.length?e:void 0}function Ee(s,r,o,e){const n=[];for(const l of r){const a=[];for(const b of s){if(b.partial||b.edgeExcluded)continue;const g=b.testValues?.[l];g!==void 0&&a.push(g)}if(a.length<e)continue;const i=G(a),t=Math.sqrt(re(a,i)),c=a.slice().sort((b,g)=>b-g),u=o?.find(b=>b.testNumber===l)?.name??String(l);n.push({testNumber:l,label:u,count:a.length,min:c[0],max:c[c.length-1],mean:i,stddev:t,median:Z(c,.5),q1:Z(c,.25),q3:Z(c,.75)})}return n.length?n:void 0}function H(s,r,o,e){const n=(s+o)/(r+e),l=n*(1-n)*(1/r+1/e);if(!Number.isFinite(l)||l<=0)return 1;const a=(s/r-o/e)/Math.sqrt(l);return he(2*(1-ge(Math.abs(a))))}function ee(s){const r=new Map;for(const o of s){const e=`${o.variable.kind}:${o.comparison.family}`,n=r.get(e)??[];n.push(o),r.set(e,n)}for(const o of r.values()){const e=[...o].filter(a=>a.stats.pValue!==void 0).sort((a,i)=>(a.stats.pValue??1)-(i.stats.pValue??1)),n=e.length;let l=1;for(let a=n-1;a>=0;a--){const i=e[a],t=i.stats.pValue??1,c=Math.min(1,Math.min(l,t*n/(a+1)));i.stats.adjustedPValue=c,l=c}}return s}function te(s,r,o){const e=Math.abs(r),n=o!==void 0?Math.abs(o):0;return s<=.01&&(e>=.3||n>=2.5)?"unusual":s<=.05&&(e>=.2||n>=1.5)?"notable":"info"}function ne(s,r){return s<=.01&&Math.abs(r)>=.5?"unusual":s<=.05&&Math.abs(r)>=.15?"notable":"info"}function J(s){return s==="reticle-position"?"other reticle positions":s==="test-site"?"other test sites":"the rest of the map"}function O(s){return s==="reticle-position"?"Other reticle positions":s==="test-site"?"Other test sites":"Rest of map"}function me(s,r,o){const e=(Math.abs(r)*100).toFixed(1);return`${s} yield is ${e} percentage points ${r>0?"higher":"lower"} than ${J(o)}`}function ie(s,r){return r==="quadrant"&&!s.startsWith("Quadrant")?`quadrant ${s}`:s}function pe(s,r,o,e){const n=ie(s,e),l=(Math.abs(o)*100).toFixed(1);return`${n} has ${r} occurrence ${l} percentage points ${o>0?"higher":"lower"} than ${J(e)}`}function xe(s,r,o,e){const n=ie(s,e),l=(Math.abs(o)*100).toFixed(1);return`${n} has ${r} pass rate ${l} percentage points ${o>0?"higher":"lower"} than ${J(e)}`}function be(s,r,o,e,n,l){const a=ie(s,n),i=J(n),t=o>0?"higher":"lower";if(e!==void 0&&Number.isFinite(e)){const u=(Math.abs(e)*100).toFixed(1);return`${a} mean ${r} is ${u}% ${t} than ${i}`}const c=l?` ${l}`:"";return`${a} mean ${r} is ${Math.abs(o).toPrecision(3)}${c} ${t} than ${i}`}function ye(s,r,o){const e=r?.find(n=>n.bin===s);return e?.name?`${o} ${s} (${e.name})`:`${o} ${s}`}function Te(s,r){const o=r?.find(e=>e.testNumber===s);return{label:o?.name??`Test ${s}`,unit:o?.unit}}function se(s,r){const o=new Map(s.map(n=>[T(n),n])),e=new Map;for(const n of r){const l=[];for(const a of n.dieKeys){const i=o.get(a);i&&l.push(i)}e.set(n.key,l)}return e}function oe(s,r){return ee(s),s.filter(o=>{const e=o.stats.adjustedPValue??o.stats.pValue??1,n=Math.abs(o.effect.absoluteDelta??0),l=Math.abs(o.effect.relativeDelta??0);return e<=r.significanceLevel&&(n>=r.minimumEffectSize||l>=r.minimumRelativeEffect)}).map(o=>({...o,severity:te(o.stats.adjustedPValue??o.stats.pValue??1,o.effect.absoluteDelta??0,o.effect.relativeDelta)}))}function Y(s,r,o,e){const n=new Set(o),l=se(s,r),a=new Map,i=new Map;for(const[c,u]of l){let b=0,g=0;for(const d of u)d.hbin!==void 0&&(g++,n.has(d.hbin)&&b++);a.set(c,b),i.set(c,g)}const t=[];for(const c of r){const u=i.get(c.key)??0,b=a.get(c.key)??0;let g=0,d=0;for(const[$,p]of i)$!==c.key&&(g+=p,d+=a.get($)??0);if(u<e.minimumSampleSize||g<e.minimumSampleSize)continue;const h=b/u,m=d/g,S=h-m,z=H(b,u,d,g);t.push({id:`yield:${c.key}`,level:"wafer",severity:"info",variable:{kind:"yield",label:"Yield"},comparison:{family:c.family,left:c.label,right:O(c.family)},effect:{direction:S===0?"different":S>0?"higher":"lower",absoluteDelta:S,relativeDelta:m===0?void 0:S/m,effectSize:S},stats:{method:"two-proportion-z",pValue:z,sampleSizeLeft:u,sampleSizeRight:g},summary:me(c.label,S,c.family),highlight:{kind:"region",regionFamily:c.family,regionKeys:[c.key],dieKeys:[...c.dieKeys]}})}return oe(t,e)}function L(s,r,o,e,n,l){const a=d=>o==="soft"?d.sbin:d.hbin,i=[...new Set(s.map(a).filter(d=>d!==void 0))].sort((d,h)=>d-h),t=se(s,r),c=new Map,u=new Map;for(const[d,h]of t){const m=new Map;for(const S of h){const z=a(S);z!==void 0&&m.set(z,(m.get(z)??0)+1)}c.set(d,m),u.set(d,h.length)}const b=[],g=n==="hardBin"?"HBin":"SBin";for(const d of r){const h=u.get(d.key)??0,m=c.get(d.key);let S=0;const z=new Map;for(const[$,p]of c)if($!==d.key){S+=u.get($)??0;for(const[w,k]of p)z.set(w,(z.get(w)??0)+k)}if(!(h<l.minimumSampleSize||S<l.minimumSampleSize))for(const $ of i){const p=m.get($)??0,w=z.get($)??0,k=p/h,f=w/S,y=k-f,v=H(p,h,w,S),D=ye($,e,g);b.push({id:`${n}:${$}:${d.key}`,level:"wafer",severity:"info",variable:{kind:n,bin:$,label:D},comparison:{family:d.family,left:d.label,right:O(d.family)},effect:{direction:y===0?"different":y>0?"higher":"lower",absoluteDelta:y,relativeDelta:f===0?void 0:y/f,effectSize:y},stats:{method:"two-proportion-z",pValue:v,sampleSizeLeft:h,sampleSizeRight:S},summary:pe(d.label,D,y,d.family),highlight:{kind:"bin",bin:$,regionKeys:[d.key],dieKeys:[...d.dieKeys]}})}}return oe(b,l)}function Q(s,r,o,e){const n=(o??[]).filter(u=>!q(u));if(!n.length)return[];const l=n.map(u=>u.testNumber),a=se(s,r),i=new Map,t=new Map;for(const[u,b]of a){const g=new Uint32Array(n.length),d=new Uint32Array(n.length);for(const h of b)for(let m=0;m<n.length;m++){const S=ue(h,l[m],n[m]);S!==void 0&&(d[m]++,S&&g[m]++)}i.set(u,g),t.set(u,d)}const c=[];for(const u of r){const b=i.get(u.key),g=t.get(u.key);for(let d=0;d<n.length;d++){let h=0,m=0;for(const[y,v]of t)y!==u.key&&(m+=v[d],h+=i.get(y)[d]);const S=g[d],z=b[d];if(S<e.minimumSampleSize||m<e.minimumSampleSize)continue;const $=z/S,p=h/m,w=$-p,k=H(z,S,h,m),f=l[d];c.push({id:`functional:${f}:${u.key}`,level:"wafer",severity:"info",variable:{kind:"functionalTest",index:f,label:`${n[d].name} pass rate`},comparison:{family:u.family,left:u.label,right:O(u.family)},effect:{direction:w===0?"different":w>0?"higher":"lower",absoluteDelta:w,relativeDelta:p===0?void 0:w/p,effectSize:w},stats:{method:"two-proportion-z",pValue:k,sampleSizeLeft:S,sampleSizeRight:m},summary:xe(u.label,n[d].name,w,u.family),highlight:{kind:"region",regionFamily:u.family,regionKeys:[u.key],dieKeys:[...u.dieKeys]}})}}return oe(c,e)}function re(s,r){return s.length<2?0:s.reduce((o,e)=>o+(e-r)**2,0)/(s.length-1)}function Se(s,r,o,e,n,l){const a=Math.sqrt(o/s+l/e),i=r-n;if(!Number.isFinite(a)||a===0)return{pValue:1,effectSize:0,delta:i};const t=i/a,c=Math.sqrt(Math.max(0,(o+l)/2)),u=c===0?i:i/c;return{pValue:he(2*(1-ge(Math.abs(t)))),effectSize:u,delta:i}}function qe(s,r){const o=G(s),e=G(r);return Se(s.length,o,re(s,o),r.length,e,re(r,e))}const ae=250;function we(s,r,o){const e=a=>a.filter(i=>q(o?.find(t=>t.testNumber===i)));if(r)return{testNumbers:e(r.slice().sort((a,i)=>a-i))};const n=new Set;let l=!1;e:for(const a of s)for(const i of Object.keys(a.testValues??{})){const t=Number(i);if(!n.has(t)&&(n.add(t),n.size>ae)){l=!0;break e}}if(l){const a=`[wafermap] analyzeWaferMap: more than ${ae} tests found in die data. Pass testNumbers: [...] in options to enable test value analysis for specific tests. Auto-cap threshold is ${ae}.`;return console.warn(a),{testNumbers:[],warning:a}}return{testNumbers:e([...n].sort((a,i)=>a-i))}}function _(s,r,o,e){const n=we(s,e.testNumbers,o);if(n.warning)return{findings:[],warning:n.warning};const l=n.testNumbers,a=new Map;for(let h=0;h<r.length;h++)for(const m of r[h].dieKeys)a.set(m,h);const i=r.length,t=[],c=[];for(const h of s){const m=a.get(T(h));m!==void 0&&(t.push(h),c.push(m))}const u=[],b=new Float64Array(i),g=new Float64Array(i),d=new Float64Array(i);for(const h of l){b.fill(0),g.fill(0),d.fill(0);let m=0,S=0,z=0,$;for(let p=0;p<t.length;p++){const k=t[p].testValues?.[h];if(k===void 0)continue;$===void 0&&($=k);const f=k-$,y=c[p];b[y]+=1,g[y]+=f,d[y]+=f*f,m+=1,S+=f,z+=f*f}if($!==void 0)for(let p=0;p<i;p++){const w=b[p],k=m-w;if(w<e.minimumSampleSize||k<e.minimumSampleSize)continue;const f=g[p]/w,v=(S-g[p])/k,D=f+$,M=v+$,V=w>1?Math.max(0,d[p]-w*f*f)/(w-1):0,R=k>1?Math.max(0,z-d[p]-k*v*v)/(k-1):0,{pValue:C,effectSize:P,delta:A}=Se(w,D,V,k,M,R),F=r[p],{label:W,unit:U}=Te(h,o),x=M!==0?A/Math.abs(M):void 0;u.push({id:`test:${h}:${F.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:h,label:W,unit:U},comparison:{family:F.family,left:F.label,right:O(F.family)},effect:{direction:A===0?"different":A>0?"higher":"lower",absoluteDelta:A,relativeDelta:x,effectSize:P},stats:{method:"welch-z-approx",pValue:C,sampleSizeLeft:w,sampleSizeRight:k},summary:be(F.label,W,A,x,F.family,U),highlight:{kind:"region",regionFamily:F.family,regionKeys:[F.key],dieKeys:[...F.dieKeys]}})}}return ee(u),{activeTestNumbers:l,findings:u.filter(h=>{const m=h.stats.adjustedPValue??h.stats.pValue??1,S=Math.abs(h.effect.effectSize??0);return m<=e.significanceLevel&&S>=e.minimumEffectSize}).map(h=>({...h,severity:ne(h.stats.adjustedPValue??h.stats.pValue??1,h.effect.effectSize??0)}))}}function je(s,r,o,e){if(!o?.length)return[];const n=o.filter(i=>q(i)&&(i.limitLow!==void 0||i.limitHigh!==void 0));if(!n.length)return[];const l=[],a=new Map(s.map(i=>[T(i),i]));for(const i of n){const t=i.testNumber;for(const c of r){const u=new Map;for(const g of c){const d=[];for(const h of g.dieKeys){const m=a.get(h);m&&d.push(m)}u.set(g.key,d)}const b=[];for(const g of c){const d=u.get(g.key),h=[];for(const[D,M]of u)if(D!==g.key)for(const V of M)h.push(V);const m=D=>D.testValues?.[t]!==void 0,S=D=>{const M=D.testValues?.[t];return M===void 0?!1:i.limitLow!==void 0&&M<i.limitLow||i.limitHigh!==void 0&&M>i.limitHigh},z=d.filter(m),$=h.filter(m);if(z.length<e.minimumSampleSize||$.length<e.minimumSampleSize)continue;const p=z.filter(S).length,w=$.filter(S).length,k=p/z.length,f=w/$.length,y=k-f,v=H(p,z.length,w,$.length);b.push({id:`specLimit:${t}:${g.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:t,label:i.name,unit:i.unit},comparison:{family:g.family,left:g.label,right:O(g.family)},effect:{direction:y===0?"different":y>0?"higher":"lower",absoluteDelta:y,relativeDelta:f===0?void 0:y/f,effectSize:y},stats:{method:"two-proportion-z",pValue:v,sampleSizeLeft:z.length,sampleSizeRight:$.length},summary:`${g.label} spec-fail rate for ${i.name} is ${(Math.abs(y)*100).toFixed(1)} pp ${y>0?"higher":"lower"} than the rest of the wafer`,highlight:{kind:"region",regionFamily:g.family,regionKeys:[g.key],dieKeys:[...g.dieKeys]}})}ee(b),l.push(...b.filter(g=>{const d=g.stats.adjustedPValue??g.stats.pValue??1,h=Math.abs(g.effect.absoluteDelta??0),m=Math.abs(g.effect.relativeDelta??0);return d<=e.significanceLevel&&(h>=e.minimumEffectSize||m>=e.minimumRelativeEffect)}).map(g=>({...g,severity:ne(g.stats.adjustedPValue??g.stats.pValue??1,g.effect.effectSize??0)})))}}return l}function E(s){return s.highlight.regionKeys??[]}function He(s){const r=s.variable.bin??s.variable.index??"";return`${s.comparison.family}\0${s.variable.kind}\0${r}\0${s.effect.direction}`}function I(s,r){const o=E(s)[0]??"",e=j(o);return e.family==="ring"?e.ring??0:e.family==="sector"?de(r).indexOf(e.sector??""):0}function Oe(s,r,o){if(r==="quadrant"){const a=new Map;for(const u of s){const b=j(E(u)[0]??"").quadrant;b&&a.set(b,u)}const i=[...a.keys()],t=new Set,c=[];for(const u of i){if(t.has(u))continue;const b=[],g=[u];for(t.add(u);g.length;){const d=g.pop();b.push(d);for(const h of i)!t.has(h)&&Re(d,h)&&(t.add(h),g.push(h))}c.push(b.map(d=>a.get(d)))}return c}const e=[...s].sort((a,i)=>I(a,o)-I(i,o)),n=[];let l=[];for(const a of e){if(l.length===0){l.push(a);continue}const i=I(l[l.length-1],o);I(a,o)-i===1?l.push(a):(n.push(l),l=[a])}if(l.length&&n.push(l),r==="sector"&&n.length>1){const i=de(o).length,t=I(n[0][0],o),c=I(n[n.length-1][n[n.length-1].length-1],o);if(t===0&&c===i-1){const u=n.pop();n[0]=[...u,...n[0]]}}return n}function Ie(s,r){const o=s.map(a=>j(E(a)[0]??"").ring).filter(a=>a!==void 0).sort((a,i)=>a-i),e=o[0],n=o[o.length-1];let l="";return e===n&&e===1&&r>1?l=" (core)":e===n&&e===r&&r>1&&(l=" (edge)"),e===n?`Ring ${e}${l}`:`Rings ${e}\u2013${n}`}function Ye(s,r){const o=s.map(e=>j(E(e)[0]??"").sector??"");return o.length===1?`Sector ${o[0]}`:`Sectors ${o[0]}\u2013${o[o.length-1]}`}function Qe(s){const r=s.map(o=>j(E(o)[0]??"").quadrant??"");return r.length===1?r[0]:r.length===2?`Quadrants ${r[0]} & ${r[1]}`:`Quadrants ${r.slice(0,-1).join(", ")} & ${r[r.length-1]}`}function le(s){return[...new Set(s)]}function _e(s,r){const o=s[0],e=o.comparison.family,n=o.variable.kind,l=le(s.flatMap(p=>p.highlight.dieKeys??[])),a=le(s.flatMap(E)),i=new Set(l),t=e==="ring"?Ie(s,r.ringCount):e==="sector"?Ye(s,r.sectorCount):Qe(s),c=n==="softBin"?r.softEligibleDies:n==="test"?r.testDies:r.eligibleDies,u=c.filter(p=>i.has(T(p))),b=c.filter(p=>!i.has(T(p)));let g,d,h,m,S;if(n==="test"){const p=o.variable.index,w=R=>R.testValues?.[p],k=u.map(w).filter(R=>R!==void 0),f=b.map(w).filter(R=>R!==void 0),{pValue:y,effectSize:v,delta:D}=qe(k,f),M=G(f),V=M!==0?D/Math.abs(M):void 0;g={direction:D===0?"different":D>0?"higher":"lower",absoluteDelta:D,relativeDelta:V,effectSize:v},d={method:"welch-z-approx",pValue:y,sampleSizeLeft:k.length,sampleSizeRight:f.length},h=ne(y,v),m=be(t,o.variable.label,D,V,e,o.variable.unit),S=`test:${p}`}else if(n==="yield"){const p=new Set(r.passBins),w=u.filter(P=>P.hbin!==void 0),k=b.filter(P=>P.hbin!==void 0),f=w.length,y=k.length,v=w.filter(P=>p.has(P.hbin)).length,D=k.filter(P=>p.has(P.hbin)).length,M=f>0?v/f:0,V=y>0?D/y:0,R=M-V,C=H(v,f,D,y);g={direction:R===0?"different":R>0?"higher":"lower",absoluteDelta:R,relativeDelta:V===0?void 0:R/V,effectSize:R},d={method:"two-proportion-z",pValue:C,sampleSizeLeft:f,sampleSizeRight:y},h=te(C,R,g.relativeDelta),m=me(t,R,e),S="yield"}else{const p=o.variable.bin,w=R=>n==="softBin"?R.sbin:R.hbin,k=u.filter(R=>w(R)===p).length,f=b.filter(R=>w(R)===p).length,y=k/u.length,v=f/b.length,D=y-v,M=H(k,u.length,f,b.length);g={direction:D===0?"different":D>0?"higher":"lower",absoluteDelta:D,relativeDelta:v===0?void 0:D/v,effectSize:D},d={method:"two-proportion-z",pValue:M,sampleSizeLeft:u.length,sampleSizeRight:b.length},h=te(M,D,g.relativeDelta);const V=n==="softBin"?r.sbinDefs:r.hbinDefs;m=pe(t,ye(p,V,n==="softBin"?"SBin":"HBin"),D,e),S=`${n}:${p}`}const z=le(a.map(p=>p.replace(`${e}:`,""))).join("-"),$=n==="hardBin"||n==="softBin"?{kind:"bin",bin:o.variable.bin,regionKeys:a,dieKeys:l}:{kind:"region",regionFamily:e,regionKeys:a,dieKeys:l};return{...o,id:`${S}:${e}:${z}`,severity:h,comparison:{family:e,left:t,right:O(e)},effect:g,stats:d,summary:m,highlight:$,relatedIds:s.map(p=>p.id)}}function We(s,r){const o=new Set(["ring","quadrant","sector"]),e=[],n=new Map;for(const a of s){if(!o.has(a.comparison.family)){e.push(a);continue}const i=He(a),t=n.get(i)??[];t.push(a),n.set(i,t)}const l=[];for(const a of n.values()){if(a.length===1){l.push(a[0]);continue}const i=a[0].comparison.family;for(const t of Oe(a,i,r.sectorCount))l.push(t.length===1?t[0]:_e(t,r))}return[...e,...l]}export function analyzeWaferMap(s,r={}){const o={...Fe,...r},e=Le(s),n=e.isLotStack,l=e.aggrMethod,a=!n||l==="mode"||l==="countBin"||l==="percent",i=e.dies.filter(f=>Ne(f,o)),t=Pe(o,i.length),c=e.dies.filter(f=>X(f,t)),u=ze(c,e.wafer,t.ringCount),b=$e(c,e.wafer,t.ringCount),g=t.enableReticlePositionAnalysis?ke(c,e.reticleConfig):[],d=t.enableTestSiteAnalysis===!1?[]:Me(c,t.enableTestSiteAnalysis===!0),h=t.enableAngularAnalysis?ve(c,e.wafer,t.sectorCount):[],m=[];if(t.enableYieldAnalysis&&a&&m.push(...Y(i,u,t.passBins,t),...Y(i,b,t.passBins,t),...Y(i,g,t.passBins,t),...Y(i,d,t.passBins,t),...Y(i,h,t.passBins,t)),t.enableHardBinAnalysis&&a&&m.push(...L(i,u,"hard",e.hbinDefs,"hardBin",t),...L(i,b,"hard",e.hbinDefs,"hardBin",t),...L(i,g,"hard",e.hbinDefs,"hardBin",t),...L(i,d,"hard",e.hbinDefs,"hardBin",t),...L(i,h,"hard",e.hbinDefs,"hardBin",t)),t.enableSoftBinAnalysis){const f=i.filter(y=>y.sbin!==void 0);m.push(...L(f,u,"soft",e.sbinDefs,"softBin",t),...L(f,b,"soft",e.sbinDefs,"softBin",t),...L(f,g,"soft",e.sbinDefs,"softBin",t),...L(f,d,"soft",e.sbinDefs,"softBin",t),...L(f,h,"soft",e.sbinDefs,"softBin",t))}const S=[];let z;if(t.enableTestValueAnalysis){const f=_(i,u,e.testDefs,t),y=_(i,b,e.testDefs,t),v=_(i,g,e.testDefs,t),D=_(i,d,e.testDefs,t),M=_(i,h,e.testDefs,t);m.push(...f.findings,...y.findings,...v.findings,...D.findings,...M.findings),f.warning&&S.push(f.warning),z=f.activeTestNumbers,m.push(...je(i,[u,b,g,d,h],e.testDefs,t)),m.push(...Q(i,u,e.testDefs,t),...Q(i,b,e.testDefs,t),...Q(i,g,e.testDefs,t),...Q(i,d,e.testDefs,t),...Q(i,h,e.testDefs,t))}if(t.enableClusterAnalysis){const f=Ce(n,a,e.testDefs);(!n||a||f!==void 0)&&m.push(...Be(i,e.wafer,{...t,isFailingDie:f}))}const $=We(m,{eligibleDies:i,softEligibleDies:i.filter(f=>f.sbin!==void 0),testDies:i,passBins:t.passBins,ringCount:t.ringCount,sectorCount:t.sectorCount,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs});if(m.length=0,m.push(...$),t.enablePatternClassification&&a){const f=Ve(i,e.wafer,{passBins:t.passBins,ringCount:t.ringCount});if(f!==null&&f.pattern!=="random"&&f.pattern!=="none"){let U=function(B){const N=E(B).map(K=>j(K).ring).filter(K=>K!==void 0);if(N.length===0)return!0;const fe=N.some(K=>K===W),ce=N.some(K=>K===1);return F==="edge-ring"||F==="edge-local"?fe:F==="center"?ce:F==="donut"?!fe&&!ce:!0};const v={center:"Center cluster",donut:"Donut","edge-ring":"Edge-ring","edge-local":"Edge-local",scratch:"Scratch","near-full":"Near-full"}[f.pattern]??f.pattern,D=f.confidence==="high"?"unusual":f.confidence==="medium"?"notable":"info",M=f.features,V=B=>`${(B*100).toFixed(0)}%`,R=f.pattern==="edge-ring"||f.pattern==="edge-local"?`${V(M.edgeRdd)} of edge dies failing`:f.pattern==="center"||f.pattern==="donut"?`centroid at ${V(M.centroidDistNorm)} of wafer radius from centre`:f.pattern==="scratch"?`linear score ${M.linearScore.toFixed(2)}, eccentricity ${M.eccentricity.toFixed(2)}`:`${V(M.globalRdd)} of dies failing`,C=i.filter(B=>{const N=B.hbin??B.sbin;return N!==void 0&&!new Set(t.passBins).has(N)}),P={"edge-ring":["ring","edge-arc"],"edge-local":["edge-arc","sector","quadrant"],center:["ring","cluster"],donut:["ring"],scratch:["cluster","sector","quadrant"],"near-full":["ring"]},A=new Set(P[f.pattern]??[]),F=f.pattern,W=t.ringCount,x=[];for(const B of m){if(!A.has(B.comparison.family)||B.comparison.family==="ring"&&!U(B))continue;x.push(B.id);const N=B.comparison.family==="ring"||B.comparison.family==="quadrant"||B.comparison.family==="sector";D!=="info"&&N&&(B.severity="info")}m.push({id:`spatial-pattern:${f.pattern}`,level:"wafer",severity:D,variable:{kind:"spatialPattern",label:v},comparison:{family:"spatial-pattern",left:v,right:"Wafer"},effect:{direction:"different",effectSize:M.globalRdd},stats:{method:"geometry",sampleSizeLeft:C.length,sampleSizeRight:i.length},summary:`Spatial pattern: ${v.toLowerCase()} (${f.confidence} confidence) \u2014 ${R}${f.note?` [${f.note}]`:""}`,highlight:{kind:"dies",dieKeys:C.map(B=>T(B))},relatedIds:x.length>0?x:void 0})}}m.sort((f,y)=>{const v=f.stats.adjustedPValue??f.stats.pValue??1,D=y.stats.adjustedPValue??y.stats.pValue??1;return v!==D?v-D:Math.abs(y.effect.absoluteDelta??0)-Math.abs(f.effect.absoluteDelta??0)});const p=Ae(e.dies,i.length,e.yield.yieldPercent);n&&(p.isLotStack=!0,l&&(p.aggregationMethod=l));const w=Ke(e.dies,e.testDefs);w&&(p.testSpecYield=w);const k=computeFunctionalYield(e.dies,e.testDefs);if(k&&(p.functionalYield=k),z===void 0&&t.computePerTestStats){const f=we(i,t.testNumbers,e.testDefs);f.warning&&S.push(f.warning),z=f.testNumbers}if(z?.length){const f=Ee(e.dies,z,e.testDefs,t.minimumSampleSize);f&&(p.perTestStats=f)}return S.length>0&&(p.warnings=S),{level:"wafer",hasNotableFindings:m.some(f=>f.severity==="notable"||f.severity==="unusual"),findings:m,wafer:e.wafer.metadata??void 0,stats:p}}
|
|
1
|
+
import{isYieldEligibleDie as X,getDieKey as E}from"../core/dies.js";import{buildWaferMap as ve,getTestPassStatus as ce,isParametricTest as q}from"../renderer/buildWaferMap.js";import{buildQuadrantRegions as $e,buildReticlePositionRegions as ke,buildRingRegions as De,buildSectorRegions as ze,buildTestSiteRegions as Me,sectorCompassNames as ue,areQuadrantsAdjacent as Re,parseRegionKey as j}from"./regions.js";import{buildClusterFindings as Be}from"./clusterDetection.js";import{classifyPattern as Fe}from"./patternClassification.js";import{normalCdf as de}from"./math.js";import{quantile as Z}from"./math.js";import{mean as G,clamp01 as ge}from"../core/utils.js";const Pe={ringCount:4,passBins:[1],significanceLevel:.05,minimumEffectSize:.2,minimumRelativeEffect:1,minimumSampleSize:5,includePartial:!1,includeEdgeExcluded:!1,enableYieldAnalysis:!0,enableHardBinAnalysis:!0,enableSoftBinAnalysis:!0,enableTestValueAnalysis:!1,computePerTestStats:!1,enableReticlePositionAnalysis:!0,enableClusterAnalysis:!0,enableAngularAnalysis:!0,sectorCount:8,enablePatternClassification:!0,minimumClusterSize:5};function Ve(s,a){const o={...s};return o.minimumClusterSize=Math.max(5,Math.round(a*.003)),o}function Le(s){return"wafer"in s&&"dies"in s&&"view"in s?s:ve(s)}function Ne(s,a){return X(s,a)?s.hbin!==void 0||s.sbin!==void 0||s.testValues!==void 0&&Object.keys(s.testValues).length>0:!1}function Ce(s,a,o){if(!s||a)return;const e=(o??[]).filter(n=>q(n)&&(n.limitLow!==void 0||n.limitHigh!==void 0));if(e.length!==0)return n=>{for(const f of e){const r=f.testNumber,i=n.testValues?.[r];if(i!==void 0&&(f.limitLow!==void 0&&i<f.limitLow||f.limitHigh!==void 0&&i>f.limitHigh))return!0}return!1}}function Ke(s,a,o){const e=new Set,n=new Set,f=new Set,r=new Map,i=new Map;for(const t of s){if(t.testValues)for(const l of Object.keys(t.testValues))e.add(Number(l));if(t.testPass)for(const l of Object.keys(t.testPass))e.add(Number(l));t.hbin!==void 0&&n.add(t.hbin),t.sbin!==void 0&&f.add(t.sbin),X(t)&&(t.hbin!==void 0&&r.set(t.hbin,(r.get(t.hbin)??0)+1),t.sbin!==void 0&&i.set(t.sbin,(i.get(t.sbin)??0)+1))}return{totalDies:s.length,analyzedDies:a,excludedDies:s.length-a,yieldPercent:o,testsConsidered:[...e].sort((t,l)=>t-l),hardBinsConsidered:[...n].sort((t,l)=>t-l),softBinsConsidered:[...f].sort((t,l)=>t-l),...r.size?{hardBinCounts:Object.fromEntries(r)}:{},...i.size?{softBinCounts:Object.fromEntries(i)}:{}}}function Ae(s,a){if(!a?.length)return;const o=a.filter(n=>q(n)&&(n.limitLow!==void 0||n.limitHigh!==void 0));if(!o.length)return;const e=[];for(const n of o){const f=n.testNumber;let r=0,i=0,t=0,l=0;for(const u of s){if(u.partial||u.edgeExcluded)continue;const b=u.testValues?.[f];b!==void 0&&(l++,n.limitLow!==void 0&&b<n.limitLow?i++:n.limitHigh!==void 0&&b>n.limitHigh?t++:r++)}e.push({testNumber:f,label:n.name,passDies:r,failLowDies:i,failHighDies:t,totalDies:l,yieldPercent:l>0?r/l*100:null})}return e.length?e:void 0}export function computeFunctionalYield(s,a){if(!a?.length)return;const o=a.filter(n=>!q(n));if(!o.length)return;const e=[];for(const n of o){const f=n.testNumber;let r=0,i=0;for(const l of s){if(l.partial||l.edgeExcluded)continue;const u=ce(l,f,n);u!==void 0&&(u?r++:i++)}const t=r+i;e.push({testNumber:f,label:n.name,passDies:r,failDies:i,totalDies:t,passRatePercent:t>0?r/t*100:null})}return e.length?e:void 0}function Ee(s,a,o,e){const n=[];for(const f of a){const r=[];for(const b of s){if(b.partial||b.edgeExcluded)continue;const g=b.testValues?.[f];g!==void 0&&r.push(g)}if(r.length<e)continue;const i=G(r),t=Math.sqrt(ae(r,i)),l=r.slice().sort((b,g)=>b-g),u=o?.find(b=>b.testNumber===f)?.name??String(f);n.push({testNumber:f,label:u,count:r.length,min:l[0],max:l[l.length-1],mean:i,stddev:t,median:Z(l,.5),q1:Z(l,.25),q3:Z(l,.75)})}return n.length?n:void 0}function H(s,a,o,e){const n=(s+o)/(a+e),f=n*(1-n)*(1/a+1/e);if(!Number.isFinite(f)||f<=0)return 1;const r=(s/a-o/e)/Math.sqrt(f);return ge(2*(1-de(Math.abs(r))))}function ee(s){const a=new Map;for(const o of s){const e=`${o.variable.kind}:${o.comparison.family}`,n=a.get(e)??[];n.push(o),a.set(e,n)}for(const o of a.values()){const e=[...o].filter(r=>r.stats.pValue!==void 0).sort((r,i)=>(r.stats.pValue??1)-(i.stats.pValue??1)),n=e.length;let f=1;for(let r=n-1;r>=0;r--){const i=e[r],t=i.stats.pValue??1,l=Math.min(1,Math.min(f,t*n/(r+1)));i.stats.adjustedPValue=l,f=l}}return s}function te(s,a,o){const e=Math.abs(a),n=o!==void 0?Math.abs(o):0;return s<=.01&&(e>=.3||n>=2.5)?"unusual":s<=.05&&(e>=.2||n>=1.5)?"notable":"info"}function ne(s,a){return s<=.01&&Math.abs(a)>=.5?"unusual":s<=.05&&Math.abs(a)>=.15?"notable":"info"}function J(s){return s==="reticle-position"?"other reticle positions":s==="test-site"?"other test sites":"the rest of the map"}function O(s){return s==="reticle-position"?"Other reticle positions":s==="test-site"?"Other test sites":"Rest of map"}function he(s,a,o){const e=(Math.abs(a)*100).toFixed(1);return`${s} yield is ${e} percentage points ${a>0?"higher":"lower"} than ${J(o)}`}function ie(s,a){return a==="quadrant"&&!s.startsWith("Quadrant")?`quadrant ${s}`:s}function me(s,a,o,e){const n=ie(s,e),f=(Math.abs(o)*100).toFixed(1);return`${n} has ${a} occurrence ${f} percentage points ${o>0?"higher":"lower"} than ${J(e)}`}function xe(s,a,o,e){const n=ie(s,e),f=(Math.abs(o)*100).toFixed(1);return`${n} has ${a} pass rate ${f} percentage points ${o>0?"higher":"lower"} than ${J(e)}`}function be(s,a,o,e,n,f){const r=ie(s,n),i=J(n),t=o>0?"higher":"lower";if(e!==void 0&&Number.isFinite(e)){const u=(Math.abs(e)*100).toFixed(1);return`${r} mean ${a} is ${u}% ${t} than ${i}`}const l=f?` ${f}`:"";return`${r} mean ${a} is ${Math.abs(o).toPrecision(3)}${l} ${t} than ${i}`}function pe(s,a,o){const e=a?.find(n=>n.bin===s);return e?.name?`${o} ${s} (${e.name})`:`${o} ${s}`}function Te(s,a){const o=a?.find(e=>e.testNumber===s);return{label:o?.name??`Test ${s}`,unit:o?.unit}}function se(s,a){const o=new Map(s.map(n=>[E(n),n])),e=new Map;for(const n of a){const f=[];for(const r of n.dieKeys){const i=o.get(r);i&&f.push(i)}e.set(n.key,f)}return e}function oe(s,a){return ee(s),s.filter(o=>{const e=o.stats.adjustedPValue??o.stats.pValue??1,n=Math.abs(o.effect.absoluteDelta??0),f=Math.abs(o.effect.relativeDelta??0);return e<=a.significanceLevel&&(n>=a.minimumEffectSize||f>=a.minimumRelativeEffect)}).map(o=>({...o,severity:te(o.stats.adjustedPValue??o.stats.pValue??1,o.effect.absoluteDelta??0,o.effect.relativeDelta)}))}function Y(s,a,o,e){const n=new Set(o),f=se(s,a),r=new Map,i=new Map;for(const[l,u]of f){let b=0,g=0;for(const d of u)d.hbin!==void 0&&(g++,n.has(d.hbin)&&b++);r.set(l,b),i.set(l,g)}const t=[];for(const l of a){const u=i.get(l.key)??0,b=r.get(l.key)??0;let g=0,d=0;for(const[$,p]of i)$!==l.key&&(g+=p,d+=r.get($)??0);if(u<e.minimumSampleSize||g<e.minimumSampleSize)continue;const h=b/u,m=d/g,S=h-m,D=H(b,u,d,g);t.push({id:`yield:${l.key}`,level:"wafer",severity:"info",variable:{kind:"yield",label:"Yield"},comparison:{family:l.family,left:l.label,right:O(l.family)},effect:{direction:S===0?"different":S>0?"higher":"lower",absoluteDelta:S,relativeDelta:m===0?void 0:S/m,effectSize:S},stats:{method:"two-proportion-z",pValue:D,sampleSizeLeft:u,sampleSizeRight:g},summary:he(l.label,S,l.family),highlight:{kind:"region",regionFamily:l.family,regionKeys:[l.key],dieKeys:[...l.dieKeys]}})}return oe(t,e)}function L(s,a,o,e,n,f){const r=d=>o==="soft"?d.sbin:d.hbin,i=[...new Set(s.map(r).filter(d=>d!==void 0))].sort((d,h)=>d-h),t=se(s,a),l=new Map,u=new Map;for(const[d,h]of t){const m=new Map;for(const S of h){const D=r(S);D!==void 0&&m.set(D,(m.get(D)??0)+1)}l.set(d,m),u.set(d,h.length)}const b=[],g=n==="hardBin"?"HBin":"SBin";for(const d of a){const h=u.get(d.key)??0,m=l.get(d.key);let S=0;const D=new Map;for(const[$,p]of l)if($!==d.key){S+=u.get($)??0;for(const[w,k]of p)D.set(w,(D.get(w)??0)+k)}if(!(h<f.minimumSampleSize||S<f.minimumSampleSize))for(const $ of i){const p=m.get($)??0,w=D.get($)??0,k=p/h,c=w/S,y=k-c,z=H(p,h,w,S),v=pe($,e,g);b.push({id:`${n}:${$}:${d.key}`,level:"wafer",severity:"info",variable:{kind:n,bin:$,label:v},comparison:{family:d.family,left:d.label,right:O(d.family)},effect:{direction:y===0?"different":y>0?"higher":"lower",absoluteDelta:y,relativeDelta:c===0?void 0:y/c,effectSize:y},stats:{method:"two-proportion-z",pValue:z,sampleSizeLeft:h,sampleSizeRight:S},summary:me(d.label,v,y,d.family),highlight:{kind:"bin",bin:$,regionKeys:[d.key],dieKeys:[...d.dieKeys]}})}}return oe(b,f)}function Q(s,a,o,e){const n=(o??[]).filter(u=>!q(u));if(!n.length)return[];const f=n.map(u=>u.testNumber),r=se(s,a),i=new Map,t=new Map;for(const[u,b]of r){const g=new Uint32Array(n.length),d=new Uint32Array(n.length);for(const h of b)for(let m=0;m<n.length;m++){const S=ce(h,f[m],n[m]);S!==void 0&&(d[m]++,S&&g[m]++)}i.set(u,g),t.set(u,d)}const l=[];for(const u of a){const b=i.get(u.key),g=t.get(u.key);for(let d=0;d<n.length;d++){let h=0,m=0;for(const[y,z]of t)y!==u.key&&(m+=z[d],h+=i.get(y)[d]);const S=g[d],D=b[d];if(S<e.minimumSampleSize||m<e.minimumSampleSize)continue;const $=D/S,p=h/m,w=$-p,k=H(D,S,h,m),c=f[d];l.push({id:`functional:${c}:${u.key}`,level:"wafer",severity:"info",variable:{kind:"functionalTest",index:c,label:`${n[d].name} pass rate`},comparison:{family:u.family,left:u.label,right:O(u.family)},effect:{direction:w===0?"different":w>0?"higher":"lower",absoluteDelta:w,relativeDelta:p===0?void 0:w/p,effectSize:w},stats:{method:"two-proportion-z",pValue:k,sampleSizeLeft:S,sampleSizeRight:m},summary:xe(u.label,n[d].name,w,u.family),highlight:{kind:"region",regionFamily:u.family,regionKeys:[u.key],dieKeys:[...u.dieKeys]}})}}return oe(l,e)}function ae(s,a){return s.length<2?0:s.reduce((o,e)=>o+(e-a)**2,0)/(s.length-1)}function ye(s,a,o,e,n,f){const r=Math.sqrt(o/s+f/e),i=a-n;if(!Number.isFinite(r)||r===0)return{pValue:1,effectSize:0,delta:i};const t=i/r,l=Math.sqrt(Math.max(0,(o+f)/2)),u=l===0?i:i/l;return{pValue:ge(2*(1-de(Math.abs(t)))),effectSize:u,delta:i}}function qe(s,a){const o=G(s),e=G(a);return ye(s.length,o,ae(s,o),a.length,e,ae(a,e))}const Se=250;function we(s,a,o){const e=r=>r.filter(i=>q(o?.find(t=>t.testNumber===i)));if(a)return{testNumbers:e(a.slice().sort((r,i)=>r-i))};const n=new Set;let f=!1;e:for(const r of s)for(const i of Object.keys(r.testValues??{})){const t=Number(i);if(!n.has(t)&&(n.add(t),n.size>Se)){f=!0;break e}}if(f){const r=`Test-value analysis was skipped: more than ${Se} tests were found in the die data, so no test findings were computed. Pass testNumbers: [...] to analyse specific tests.`;return console.warn(`[wafermap] analyzeWaferMap: ${r}`),{testNumbers:[],warning:{code:"test-count-capped",message:r,severity:"warning"}}}return{testNumbers:e([...n].sort((r,i)=>r-i))}}function _(s,a,o,e){const n=we(s,e.testNumbers,o);if(n.warning)return{findings:[],warning:n.warning};const f=n.testNumbers,r=new Map;for(let h=0;h<a.length;h++)for(const m of a[h].dieKeys)r.set(m,h);const i=a.length,t=[],l=[];for(const h of s){const m=r.get(E(h));m!==void 0&&(t.push(h),l.push(m))}const u=[],b=new Float64Array(i),g=new Float64Array(i),d=new Float64Array(i);for(const h of f){b.fill(0),g.fill(0),d.fill(0);let m=0,S=0,D=0,$;for(let p=0;p<t.length;p++){const k=t[p].testValues?.[h];if(k===void 0)continue;$===void 0&&($=k);const c=k-$,y=l[p];b[y]+=1,g[y]+=c,d[y]+=c*c,m+=1,S+=c,D+=c*c}if($!==void 0)for(let p=0;p<i;p++){const w=b[p],k=m-w;if(w<e.minimumSampleSize||k<e.minimumSampleSize)continue;const c=g[p]/w,z=(S-g[p])/k,v=c+$,M=z+$,F=w>1?Math.max(0,d[p]-w*c*c)/(w-1):0,R=k>1?Math.max(0,D-d[p]-k*z*z)/(k-1):0,{pValue:C,effectSize:V,delta:K}=ye(w,v,F,k,M,R),P=a[p],{label:W,unit:U}=Te(h,o),T=M!==0?K/Math.abs(M):void 0;u.push({id:`test:${h}:${P.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:h,label:W,unit:U},comparison:{family:P.family,left:P.label,right:O(P.family)},effect:{direction:K===0?"different":K>0?"higher":"lower",absoluteDelta:K,relativeDelta:T,effectSize:V},stats:{method:"welch-z-approx",pValue:C,sampleSizeLeft:w,sampleSizeRight:k},summary:be(P.label,W,K,T,P.family,U),highlight:{kind:"region",regionFamily:P.family,regionKeys:[P.key],dieKeys:[...P.dieKeys]}})}}return ee(u),{activeTestNumbers:f,findings:u.filter(h=>{const m=h.stats.adjustedPValue??h.stats.pValue??1,S=Math.abs(h.effect.effectSize??0);return m<=e.significanceLevel&&S>=e.minimumEffectSize}).map(h=>({...h,severity:ne(h.stats.adjustedPValue??h.stats.pValue??1,h.effect.effectSize??0)}))}}function je(s,a,o,e){if(!o?.length)return[];const n=o.filter(i=>q(i)&&(i.limitLow!==void 0||i.limitHigh!==void 0));if(!n.length)return[];const f=[],r=new Map(s.map(i=>[E(i),i]));for(const i of n){const t=i.testNumber;for(const l of a){const u=new Map;for(const g of l){const d=[];for(const h of g.dieKeys){const m=r.get(h);m&&d.push(m)}u.set(g.key,d)}const b=[];for(const g of l){const d=u.get(g.key),h=[];for(const[v,M]of u)if(v!==g.key)for(const F of M)h.push(F);const m=v=>v.testValues?.[t]!==void 0,S=v=>{const M=v.testValues?.[t];return M===void 0?!1:i.limitLow!==void 0&&M<i.limitLow||i.limitHigh!==void 0&&M>i.limitHigh},D=d.filter(m),$=h.filter(m);if(D.length<e.minimumSampleSize||$.length<e.minimumSampleSize)continue;const p=D.filter(S).length,w=$.filter(S).length,k=p/D.length,c=w/$.length,y=k-c,z=H(p,D.length,w,$.length);b.push({id:`specLimit:${t}:${g.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:t,label:i.name,unit:i.unit},comparison:{family:g.family,left:g.label,right:O(g.family)},effect:{direction:y===0?"different":y>0?"higher":"lower",absoluteDelta:y,relativeDelta:c===0?void 0:y/c,effectSize:y},stats:{method:"two-proportion-z",pValue:z,sampleSizeLeft:D.length,sampleSizeRight:$.length},summary:`${g.label} spec-fail rate for ${i.name} is ${(Math.abs(y)*100).toFixed(1)} pp ${y>0?"higher":"lower"} than the rest of the wafer`,highlight:{kind:"region",regionFamily:g.family,regionKeys:[g.key],dieKeys:[...g.dieKeys]}})}ee(b),f.push(...b.filter(g=>{const d=g.stats.adjustedPValue??g.stats.pValue??1,h=Math.abs(g.effect.absoluteDelta??0),m=Math.abs(g.effect.relativeDelta??0);return d<=e.significanceLevel&&(h>=e.minimumEffectSize||m>=e.minimumRelativeEffect)}).map(g=>({...g,severity:ne(g.stats.adjustedPValue??g.stats.pValue??1,g.effect.effectSize??0)})))}}return f}function x(s){return s.highlight.regionKeys??[]}function He(s){const a=s.variable.bin??s.variable.index??"";return`${s.comparison.family}\0${s.variable.kind}\0${a}\0${s.effect.direction}`}function I(s,a){const o=x(s)[0]??"",e=j(o);return e.family==="ring"?e.ring??0:e.family==="sector"?ue(a).indexOf(e.sector??""):0}function Oe(s,a,o){if(a==="quadrant"){const r=new Map;for(const u of s){const b=j(x(u)[0]??"").quadrant;b&&r.set(b,u)}const i=[...r.keys()],t=new Set,l=[];for(const u of i){if(t.has(u))continue;const b=[],g=[u];for(t.add(u);g.length;){const d=g.pop();b.push(d);for(const h of i)!t.has(h)&&Re(d,h)&&(t.add(h),g.push(h))}l.push(b.map(d=>r.get(d)))}return l}const e=[...s].sort((r,i)=>I(r,o)-I(i,o)),n=[];let f=[];for(const r of e){if(f.length===0){f.push(r);continue}const i=I(f[f.length-1],o);I(r,o)-i===1?f.push(r):(n.push(f),f=[r])}if(f.length&&n.push(f),a==="sector"&&n.length>1){const i=ue(o).length,t=I(n[0][0],o),l=I(n[n.length-1][n[n.length-1].length-1],o);if(t===0&&l===i-1){const u=n.pop();n[0]=[...u,...n[0]]}}return n}function Ie(s,a){const o=s.map(r=>j(x(r)[0]??"").ring).filter(r=>r!==void 0).sort((r,i)=>r-i),e=o[0],n=o[o.length-1];let f="";return e===n&&e===1&&a>1?f=" (core)":e===n&&e===a&&a>1&&(f=" (edge)"),e===n?`Ring ${e}${f}`:`Rings ${e}\u2013${n}`}function Ye(s,a){const o=s.map(e=>j(x(e)[0]??"").sector??"");return o.length===1?`Sector ${o[0]}`:`Sectors ${o[0]}\u2013${o[o.length-1]}`}function Qe(s){const a=s.map(o=>j(x(o)[0]??"").quadrant??"");return a.length===1?a[0]:a.length===2?`Quadrants ${a[0]} & ${a[1]}`:`Quadrants ${a.slice(0,-1).join(", ")} & ${a[a.length-1]}`}function re(s){return[...new Set(s)]}function _e(s,a){const o=s[0],e=o.comparison.family,n=o.variable.kind,f=re(s.flatMap(p=>p.highlight.dieKeys??[])),r=re(s.flatMap(x)),i=new Set(f),t=e==="ring"?Ie(s,a.ringCount):e==="sector"?Ye(s,a.sectorCount):Qe(s),l=n==="softBin"?a.softEligibleDies:n==="test"?a.testDies:a.eligibleDies,u=l.filter(p=>i.has(E(p))),b=l.filter(p=>!i.has(E(p)));let g,d,h,m,S;if(n==="test"){const p=o.variable.index,w=R=>R.testValues?.[p],k=u.map(w).filter(R=>R!==void 0),c=b.map(w).filter(R=>R!==void 0),{pValue:y,effectSize:z,delta:v}=qe(k,c),M=G(c),F=M!==0?v/Math.abs(M):void 0;g={direction:v===0?"different":v>0?"higher":"lower",absoluteDelta:v,relativeDelta:F,effectSize:z},d={method:"welch-z-approx",pValue:y,sampleSizeLeft:k.length,sampleSizeRight:c.length},h=ne(y,z),m=be(t,o.variable.label,v,F,e,o.variable.unit),S=`test:${p}`}else if(n==="yield"){const p=new Set(a.passBins),w=u.filter(V=>V.hbin!==void 0),k=b.filter(V=>V.hbin!==void 0),c=w.length,y=k.length,z=w.filter(V=>p.has(V.hbin)).length,v=k.filter(V=>p.has(V.hbin)).length,M=c>0?z/c:0,F=y>0?v/y:0,R=M-F,C=H(z,c,v,y);g={direction:R===0?"different":R>0?"higher":"lower",absoluteDelta:R,relativeDelta:F===0?void 0:R/F,effectSize:R},d={method:"two-proportion-z",pValue:C,sampleSizeLeft:c,sampleSizeRight:y},h=te(C,R,g.relativeDelta),m=he(t,R,e),S="yield"}else{const p=o.variable.bin,w=R=>n==="softBin"?R.sbin:R.hbin,k=u.filter(R=>w(R)===p).length,c=b.filter(R=>w(R)===p).length,y=k/u.length,z=c/b.length,v=y-z,M=H(k,u.length,c,b.length);g={direction:v===0?"different":v>0?"higher":"lower",absoluteDelta:v,relativeDelta:z===0?void 0:v/z,effectSize:v},d={method:"two-proportion-z",pValue:M,sampleSizeLeft:u.length,sampleSizeRight:b.length},h=te(M,v,g.relativeDelta);const F=n==="softBin"?a.sbinDefs:a.hbinDefs;m=me(t,pe(p,F,n==="softBin"?"SBin":"HBin"),v,e),S=`${n}:${p}`}const D=re(r.map(p=>p.replace(`${e}:`,""))).join("-"),$=n==="hardBin"||n==="softBin"?{kind:"bin",bin:o.variable.bin,regionKeys:r,dieKeys:f}:{kind:"region",regionFamily:e,regionKeys:r,dieKeys:f};return{...o,id:`${S}:${e}:${D}`,severity:h,comparison:{family:e,left:t,right:O(e)},effect:g,stats:d,summary:m,highlight:$,relatedIds:s.map(p=>p.id)}}function We(s,a){const o=new Set(["ring","quadrant","sector"]),e=[],n=new Map;for(const r of s){if(!o.has(r.comparison.family)){e.push(r);continue}const i=He(r),t=n.get(i)??[];t.push(r),n.set(i,t)}const f=[];for(const r of n.values()){if(r.length===1){f.push(r[0]);continue}const i=r[0].comparison.family;for(const t of Oe(r,i,a.sectorCount))f.push(t.length===1?t[0]:_e(t,a))}return[...e,...f]}function Ue(s){const a=new Map,o=new Map;for(const n of s){const f=E(n);n.hbin!==void 0&&n.hbin!==null&&(a.get(n.hbin)??a.set(n.hbin,new Set).get(n.hbin)).add(f),n.sbin!==void 0&&n.sbin!==null&&(o.get(n.sbin)??o.set(n.sbin,new Set).get(n.sbin)).add(f)}const e=new Set;for(const[n,f]of a)for(const[r,i]of o){if(f.size!==i.size)continue;let t=!0;for(const l of f)if(!i.has(l)){t=!1;break}t&&e.add(`${n}:${r}`)}return e}function Ge(s){return`${s.comparison.family}\0${s.comparison.left}`}function Je(s,a,o){const e=(i,t)=>{i.absorbedIds=[...i.absorbedIds??[],t.id]},n=new Map;for(const i of s){const t=Ge(i);(n.get(t)??n.set(t,[]).get(t)).push(i)}const f=Ue(a),r=new Set;for(const i of n.values()){for(const l of i)if(!(l.variable.kind!=="hardBin"||l.variable.bin===void 0))for(const u of i){if(u.variable.kind!=="softBin"||u.variable.bin===void 0||r.has(u.id)||!f.has(`${l.variable.bin}:${u.variable.bin}`)||u.effect.direction!==l.effect.direction)continue;e(l,u),r.add(u.id);const b=`${l.variable.label} and ${u.variable.label} (same dies)`;l.summary=l.summary.replace(l.variable.label,b),l.variable.label=b;break}if(o.length!==1)continue;const t=i.find(l=>l.variable.kind==="yield");if(t){for(const l of i)if(!(l===t||r.has(l.id)||!((l.variable.kind==="hardBin"||l.variable.kind==="softBin")&&l.variable.bin===o[0]))){e(t,l),r.add(l.id);for(const b of l.absorbedIds??[])t.absorbedIds=[...t.absorbedIds??[],b]}}}}export function analyzeWaferMap(s,a={}){const o={...Pe,...a},e=Le(s),n=e.isLotStack,f=e.aggrMethod,r=!n||f==="mode"||f==="countBin"||f==="percent",i=e.dies.filter(c=>Ne(c,o)),t=Ve(o,i.length),l=e.dies.filter(c=>X(c,t)),u=De(l,e.wafer,t.ringCount),b=$e(l,e.wafer,t.ringCount),g=t.enableReticlePositionAnalysis?ke(l,e.reticleConfig):[],d=t.enableTestSiteAnalysis===!1?[]:Me(l,t.enableTestSiteAnalysis===!0),h=t.enableAngularAnalysis?ze(l,e.wafer,t.sectorCount):[],m=[];if(t.enableYieldAnalysis&&r&&m.push(...Y(i,u,t.passBins,t),...Y(i,b,t.passBins,t),...Y(i,g,t.passBins,t),...Y(i,d,t.passBins,t),...Y(i,h,t.passBins,t)),t.enableHardBinAnalysis&&r&&m.push(...L(i,u,"hard",e.hbinDefs,"hardBin",t),...L(i,b,"hard",e.hbinDefs,"hardBin",t),...L(i,g,"hard",e.hbinDefs,"hardBin",t),...L(i,d,"hard",e.hbinDefs,"hardBin",t),...L(i,h,"hard",e.hbinDefs,"hardBin",t)),t.enableSoftBinAnalysis){const c=i.filter(y=>y.sbin!==void 0);m.push(...L(c,u,"soft",e.sbinDefs,"softBin",t),...L(c,b,"soft",e.sbinDefs,"softBin",t),...L(c,g,"soft",e.sbinDefs,"softBin",t),...L(c,d,"soft",e.sbinDefs,"softBin",t),...L(c,h,"soft",e.sbinDefs,"softBin",t))}const S=[];let D;if(t.enableTestValueAnalysis){const c=_(i,u,e.testDefs,t),y=_(i,b,e.testDefs,t),z=_(i,g,e.testDefs,t),v=_(i,d,e.testDefs,t),M=_(i,h,e.testDefs,t);m.push(...c.findings,...y.findings,...z.findings,...v.findings,...M.findings),c.warning&&S.push(c.warning),D=c.activeTestNumbers,m.push(...je(i,[u,b,g,d,h],e.testDefs,t)),m.push(...Q(i,u,e.testDefs,t),...Q(i,b,e.testDefs,t),...Q(i,g,e.testDefs,t),...Q(i,d,e.testDefs,t),...Q(i,h,e.testDefs,t))}if(t.enableClusterAnalysis){const c=Ce(n,r,e.testDefs);(!n||r||c!==void 0)&&m.push(...Be(i,e.wafer,{...t,isFailingDie:c}))}const $=We(m,{eligibleDies:i,softEligibleDies:i.filter(c=>c.sbin!==void 0),testDies:i,passBins:t.passBins,ringCount:t.ringCount,sectorCount:t.sectorCount,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs});if(m.length=0,m.push(...$),t.enablePatternClassification&&r){const c=Fe(i,e.wafer,{passBins:t.passBins,ringCount:t.ringCount});if(c!==null&&c.pattern!=="random"&&c.pattern!=="none"){let U=function(B){const N=x(B).map(A=>j(A).ring).filter(A=>A!==void 0);if(N.length===0)return!0;const le=N.some(A=>A===W),fe=N.some(A=>A===1);return P==="edge-ring"||P==="edge-local"?le:P==="center"?fe:P==="donut"?!le&&!fe:!0};const z={center:"Center cluster",donut:"Donut","edge-ring":"Edge-ring","edge-local":"Edge-local",scratch:"Scratch","near-full":"Near-full"}[c.pattern]??c.pattern,v=c.confidence==="high"?"unusual":c.confidence==="medium"?"notable":"info",M=c.features,F=B=>`${(B*100).toFixed(0)}%`,R=c.pattern==="edge-ring"||c.pattern==="edge-local"?`${F(M.edgeRdd)} of edge dies failing`:c.pattern==="center"||c.pattern==="donut"?`centroid at ${F(M.centroidDistNorm)} of wafer radius from centre`:c.pattern==="scratch"?`linear score ${M.linearScore.toFixed(2)}, eccentricity ${M.eccentricity.toFixed(2)}`:`${F(M.globalRdd)} of dies failing`,C=i.filter(B=>{const N=B.hbin??B.sbin;return N!==void 0&&!new Set(t.passBins).has(N)}),V={"edge-ring":["ring","edge-arc"],"edge-local":["edge-arc","sector","quadrant"],center:["ring","cluster"],donut:["ring"],scratch:["cluster","sector","quadrant"],"near-full":["ring"]},K=new Set(V[c.pattern]??[]),P=c.pattern,W=t.ringCount,T=[];for(const B of m){if(!K.has(B.comparison.family)||B.comparison.family==="ring"&&!U(B))continue;T.push(B.id);const N=B.comparison.family==="ring"||B.comparison.family==="quadrant"||B.comparison.family==="sector";v!=="info"&&N&&(B.severity="info")}m.push({id:`spatial-pattern:${c.pattern}`,level:"wafer",severity:v,variable:{kind:"spatialPattern",label:z},comparison:{family:"spatial-pattern",left:z,right:"Wafer"},effect:{direction:"different",effectSize:M.globalRdd},stats:{method:"geometry",sampleSizeLeft:C.length,sampleSizeRight:i.length},summary:`Spatial pattern: ${z.toLowerCase()} (${c.confidence} confidence) \u2014 ${R}${c.note?` [${c.note}]`:""}`,highlight:{kind:"dies",dieKeys:C.map(B=>E(B))},relatedIds:T.length>0?T:void 0})}}m.sort((c,y)=>{const z=c.stats.adjustedPValue??c.stats.pValue??1,v=y.stats.adjustedPValue??y.stats.pValue??1;return z!==v?z-v:Math.abs(y.effect.absoluteDelta??0)-Math.abs(c.effect.absoluteDelta??0)});const p=Ke(e.dies,i.length,e.yield.yieldPercent);n&&(p.isLotStack=!0,f&&(p.aggregationMethod=f));const w=Ae(e.dies,e.testDefs);w&&(p.testSpecYield=w);const k=computeFunctionalYield(e.dies,e.testDefs);if(k&&(p.functionalYield=k),D===void 0&&t.computePerTestStats){const c=we(i,t.testNumbers,e.testDefs);c.warning&&S.push(c.warning),D=c.testNumbers}if(D?.length){const c=Ee(e.dies,D,e.testDefs,t.minimumSampleSize);c&&(p.perTestStats=c)}return S.length>0&&(p.warnings=S),Je(m,i,t.passBins),{level:"wafer",hasNotableFindings:m.some(c=>c.severity==="notable"||c.severity==="unusual"),findings:m,wafer:e.wafer.metadata??void 0,stats:p}}
|