@wafertools/wafermap 0.32.0 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/AGENTS.md +7 -7
  2. package/CHANGELOG.md +374 -1
  3. package/README.md +5 -5
  4. package/dist/packages/canvas-adapter/chartPopulation.d.ts +15 -10
  5. package/dist/packages/canvas-adapter/chartPopulation.js +1 -1
  6. package/dist/packages/canvas-adapter/charts/barPanel.d.ts +7 -0
  7. package/dist/packages/canvas-adapter/charts/barPanel.js +1 -1
  8. package/dist/packages/canvas-adapter/charts/binCluster.d.ts +7 -1
  9. package/dist/packages/canvas-adapter/charts/binCluster.js +1 -1
  10. package/dist/packages/canvas-adapter/charts/boxplot.js +1 -1
  11. package/dist/packages/canvas-adapter/charts/capability.js +1 -1
  12. package/dist/packages/canvas-adapter/charts/chartShell.d.ts +57 -15
  13. package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
  14. package/dist/packages/canvas-adapter/charts/correlation.js +1 -2
  15. package/dist/packages/canvas-adapter/charts/groupedBarPlot.d.ts +2 -0
  16. package/dist/packages/canvas-adapter/charts/groupedBarPlot.js +1 -1
  17. package/dist/packages/canvas-adapter/charts/histogram.d.ts +16 -0
  18. package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
  19. package/dist/packages/canvas-adapter/charts/palette.d.ts +2 -0
  20. package/dist/packages/canvas-adapter/charts/palette.js +1 -1
  21. package/dist/packages/canvas-adapter/charts/plotChart.d.ts +27 -0
  22. package/dist/packages/canvas-adapter/charts/plotChart.js +1 -0
  23. package/dist/packages/canvas-adapter/charts/pointInteractions.d.ts +45 -0
  24. package/dist/packages/canvas-adapter/charts/pointInteractions.js +1 -0
  25. package/dist/packages/canvas-adapter/charts/regionYieldDiagram.d.ts +2 -0
  26. package/dist/packages/canvas-adapter/charts/regionYieldDiagram.js +1 -1
  27. package/dist/packages/canvas-adapter/charts/scatter.d.ts +19 -0
  28. package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
  29. package/dist/packages/canvas-adapter/charts/sweep.d.ts +12 -0
  30. package/dist/packages/canvas-adapter/charts/sweep.js +1 -1
  31. package/dist/packages/canvas-adapter/charts/testPassRate.d.ts +7 -0
  32. package/dist/packages/canvas-adapter/charts/testPassRate.js +1 -1
  33. package/dist/packages/canvas-adapter/charts/trend.d.ts +11 -0
  34. package/dist/packages/canvas-adapter/charts/trend.js +1 -1
  35. package/dist/packages/canvas-adapter/confirmDialog.d.ts +13 -0
  36. package/dist/packages/canvas-adapter/confirmDialog.js +1 -0
  37. package/dist/packages/canvas-adapter/dataTab.d.ts +131 -0
  38. package/dist/packages/canvas-adapter/dataTab.js +1 -0
  39. package/dist/packages/canvas-adapter/dieList.d.ts +39 -4
  40. package/dist/packages/canvas-adapter/dieList.js +6 -7
  41. package/dist/packages/canvas-adapter/drilldown.d.ts +20 -3
  42. package/dist/packages/canvas-adapter/drilldown.js +1 -1
  43. package/dist/packages/canvas-adapter/guideWindow.d.ts +96 -0
  44. package/dist/packages/canvas-adapter/guideWindow.js +1 -0
  45. package/dist/packages/canvas-adapter/identityHeader.d.ts +16 -0
  46. package/dist/packages/canvas-adapter/identityHeader.js +1 -1
  47. package/dist/packages/canvas-adapter/index.d.ts +3 -3
  48. package/dist/packages/canvas-adapter/index.js +1 -1
  49. package/dist/packages/canvas-adapter/insightsTab.d.ts +73 -54
  50. package/dist/packages/canvas-adapter/insightsTab.js +1 -1
  51. package/dist/packages/canvas-adapter/maplessSummary.d.ts +2 -2
  52. package/dist/packages/canvas-adapter/plotEditor.d.ts +29 -0
  53. package/dist/packages/canvas-adapter/plotEditor.js +1 -0
  54. package/dist/packages/canvas-adapter/plotItems.d.ts +32 -0
  55. package/dist/packages/canvas-adapter/plotItems.js +1 -0
  56. package/dist/packages/canvas-adapter/plotModal.d.ts +30 -0
  57. package/dist/packages/canvas-adapter/plotModal.js +1 -0
  58. package/dist/packages/canvas-adapter/plotStore.d.ts +52 -0
  59. package/dist/packages/canvas-adapter/plotStore.js +1 -0
  60. package/dist/packages/canvas-adapter/plotTab.d.ts +30 -0
  61. package/dist/packages/canvas-adapter/plotTab.js +1 -0
  62. package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +3 -2
  63. package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
  64. package/dist/packages/canvas-adapter/renderWaferMap.d.ts +51 -2
  65. package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
  66. package/dist/packages/canvas-adapter/summaryPanel.d.ts +49 -11
  67. package/dist/packages/canvas-adapter/summaryPanel.js +2 -3
  68. package/dist/packages/canvas-adapter/summaryRail.d.ts +7 -0
  69. package/dist/packages/canvas-adapter/summaryRail.js +1 -0
  70. package/dist/packages/canvas-adapter/sweepEditor.d.ts +17 -0
  71. package/dist/packages/canvas-adapter/sweepEditor.js +1 -0
  72. package/dist/packages/canvas-adapter/tableExport.d.ts +22 -0
  73. package/dist/packages/canvas-adapter/tableExport.js +3 -0
  74. package/dist/packages/canvas-adapter/toCanvas.js +1 -1
  75. package/dist/packages/canvas-adapter/toolbar.d.ts +74 -93
  76. package/dist/packages/canvas-adapter/toolbar.js +2 -2
  77. package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
  78. package/dist/packages/canvas-adapter/userGuideHtml.js +409 -51
  79. package/dist/packages/canvas-adapter/version.d.ts +2 -2
  80. package/dist/packages/canvas-adapter/version.js +1 -1
  81. package/dist/packages/canvas-adapter/viewSwitch.d.ts +13 -0
  82. package/dist/packages/canvas-adapter/viewSwitch.js +1 -0
  83. package/dist/packages/canvas-adapter/virtualTable.d.ts +44 -0
  84. package/dist/packages/canvas-adapter/virtualTable.js +1 -0
  85. package/dist/packages/core/aggregates.d.ts +6 -0
  86. package/dist/packages/core/aggregates.js +1 -1
  87. package/dist/packages/core/classify.d.ts +3 -2
  88. package/dist/packages/core/classify.js +1 -1
  89. package/dist/packages/core/compact.d.ts +205 -0
  90. package/dist/packages/core/compact.js +3 -0
  91. package/dist/packages/core/passBins.d.ts +23 -4
  92. package/dist/packages/core/passBins.js +1 -1
  93. package/dist/packages/core/ringCount.d.ts +22 -0
  94. package/dist/packages/core/ringCount.js +1 -0
  95. package/dist/packages/core/tableCsv.d.ts +43 -0
  96. package/dist/packages/core/tableCsv.js +3 -0
  97. package/dist/packages/renderer/axisTicks.d.ts +14 -0
  98. package/dist/packages/renderer/axisTicks.js +1 -1
  99. package/dist/packages/renderer/binColors.d.ts +6 -6
  100. package/dist/packages/renderer/binColors.js +1 -1
  101. package/dist/packages/renderer/buildView.d.ts +27 -2
  102. package/dist/packages/renderer/buildView.js +1 -1
  103. package/dist/packages/renderer/buildWaferMap.js +1 -1
  104. package/dist/packages/renderer/compactView.d.ts +22 -0
  105. package/dist/packages/renderer/compactView.js +1 -0
  106. package/dist/packages/renderer/xyIndicator.d.ts +27 -0
  107. package/dist/packages/renderer/xyIndicator.js +1 -0
  108. package/dist/packages/stats/analyzeWaferLot.d.ts +28 -0
  109. package/dist/packages/stats/analyzeWaferLot.js +1 -1
  110. package/dist/packages/stats/analyzeWaferMap.d.ts +36 -1
  111. package/dist/packages/stats/analyzeWaferMap.js +1 -1
  112. package/dist/packages/stats/binPareto.d.ts +1 -1
  113. package/dist/packages/stats/binPareto.js +1 -1
  114. package/dist/packages/stats/binRows.d.ts +38 -0
  115. package/dist/packages/stats/binRows.js +1 -0
  116. package/dist/packages/stats/filterFindings.d.ts +25 -0
  117. package/dist/packages/stats/filterFindings.js +1 -1
  118. package/dist/packages/stats/findingText.d.ts +9 -0
  119. package/dist/packages/stats/findingText.js +2 -0
  120. package/dist/packages/stats/histogram.d.ts +27 -0
  121. package/dist/packages/stats/histogram.js +1 -1
  122. package/dist/packages/stats/index.d.ts +2 -0
  123. package/dist/packages/stats/index.js +1 -1
  124. package/dist/packages/stats/lotDrift.d.ts +16 -0
  125. package/dist/packages/stats/lotDrift.js +1 -0
  126. package/dist/packages/stats/lotPattern.d.ts +33 -0
  127. package/dist/packages/stats/lotPattern.js +1 -0
  128. package/dist/packages/stats/math.d.ts +60 -6
  129. package/dist/packages/stats/math.js +1 -1
  130. package/dist/packages/stats/patternClassification.d.ts +32 -1
  131. package/dist/packages/stats/patternClassification.js +1 -1
  132. package/dist/packages/stats/plotData.d.ts +258 -0
  133. package/dist/packages/stats/plotData.js +1 -0
  134. package/dist/packages/stats/plotId.d.ts +3 -0
  135. package/dist/packages/stats/plotId.js +1 -0
  136. package/dist/packages/stats/plotSpec.d.ts +119 -0
  137. package/dist/packages/stats/plotSpec.js +4 -0
  138. package/dist/packages/stats/presentation.d.ts +44 -0
  139. package/dist/packages/stats/presentation.js +1 -0
  140. package/dist/packages/stats/regions.d.ts +15 -0
  141. package/dist/packages/stats/regions.js +1 -1
  142. package/dist/packages/stats/renderSummaryReport.d.ts +23 -5
  143. package/dist/packages/stats/renderSummaryReport.js +32 -27
  144. package/dist/packages/stats/reportHtml.d.ts +54 -9
  145. package/dist/packages/stats/reportHtml.js +145 -55
  146. package/dist/packages/stats/scatter.d.ts +6 -0
  147. package/dist/packages/stats/scatter.js +1 -1
  148. package/dist/packages/stats/sweep.d.ts +37 -1
  149. package/dist/packages/stats/sweep.js +1 -1
  150. package/dist/packages/stats/sweepText.d.ts +14 -0
  151. package/dist/packages/stats/sweepText.js +1 -0
  152. package/dist/packages/stats/sweepXFromName.d.ts +14 -0
  153. package/dist/packages/stats/sweepXFromName.js +1 -1
  154. package/dist/packages/stats/synthesis.d.ts +76 -0
  155. package/dist/packages/stats/synthesis.js +2 -0
  156. package/dist/packages/stats/testPassRate.d.ts +6 -0
  157. package/dist/packages/stats/testPassRate.js +1 -1
  158. package/dist/packages/stats/trend.js +1 -1
  159. package/dist/packages/stats/types.d.ts +2 -2
  160. package/dist/packages/stats/yield.d.ts +15 -6
  161. package/dist/packages/stats/yield.js +1 -1
  162. package/dist/packages/stats/yieldRows.d.ts +31 -0
  163. package/dist/packages/stats/yieldRows.js +1 -0
  164. package/llms.txt +16 -4
  165. package/package.json +3 -3
  166. package/dist/packages/stats/findingsNarrative.d.ts +0 -3
  167. package/dist/packages/stats/findingsNarrative.js +0 -1
@@ -1,3 +1,3 @@
1
- export declare const WMAP_VERSION = "0.32.0";
2
- export declare const WMAP_BUILD_TIME = "2026-09-27T14:45:45.383Z";
1
+ export declare const WMAP_VERSION = "0.34.0";
2
+ export declare const WMAP_BUILD_TIME = "2026-10-06T09:30:13.804Z";
3
3
  //# sourceMappingURL=version.d.ts.map
@@ -1 +1 @@
1
- export const WMAP_VERSION="0.32.0",WMAP_BUILD_TIME="2026-09-27T14:45:45.383Z";
1
+ export const WMAP_VERSION="0.34.0",WMAP_BUILD_TIME="2026-10-06T09:30:13.804Z";
@@ -0,0 +1,13 @@
1
+ export interface ViewSwitch {
2
+ el: HTMLDivElement;
3
+ /** Reflect the host's state; never calls `onChange`. */
4
+ setInsightsOpen(open: boolean): void;
5
+ }
6
+ /**
7
+ * `mapsLabel` is "Map" for a single wafer and "Maps" for a gallery. Built as a two-tab tablist
8
+ * (APG Tabs): arrow keys move and activate, only the selected tab is in the Tab order.
9
+ */
10
+ export declare function createViewSwitch(doc: Document, mapsLabel: string,
11
+ /** What Insights covers: "this wafer" or "this lot", so the tooltip names the population. */
12
+ scope: 'wafer' | 'lot', onChange: (insightsOpen: boolean) => void): ViewSwitch;
13
+ //# sourceMappingURL=viewSwitch.d.ts.map
@@ -0,0 +1 @@
1
+ import{CLR as s,FONT as m,RADIUS as w,SHADOW as f,SPACE as p,wireControlHover as A,wireTooltip as S}from"./toolbar.js";export function createViewSwitch(n,u,l,g){const i=n.createElement("div");i.setAttribute("role","tablist"),i.setAttribute("aria-label","View"),i.dataset.wmapViewSwitch="1",Object.assign(i.style,{order:"1",flexShrink:"0",display:"inline-flex",alignItems:"stretch",background:s.menuBg,border:`1px solid ${s.menuBorder}`,borderRadius:w.control,boxShadow:f.panel,overflow:"hidden"});const c=(t,e,h)=>{const r=n.createElement("button");return r.type="button",r.textContent=t,r.dataset.wmapView=e,r.setAttribute("role","tab"),Object.assign(r.style,{border:"none",cursor:"pointer",fontSize:m.body,padding:`${p.sm} ${p.lg}`,whiteSpace:"nowrap"}),A(r,"bare"),S(r,h),r.addEventListener("click",()=>{r.getAttribute("aria-selected")!=="true"&&g(e==="insights")}),r},a=c(u,"maps",l==="lot"?"The wafer maps":"The wafer map"),o=c("Insights","insights",`Charts and statistics for this ${l}`);i.append(a,o);const d=(t,e)=>{t.setAttribute("aria-selected",String(e)),t.dataset.on=String(e),t.tabIndex=e?0:-1,Object.assign(t.style,{background:e?s.bgActive:"transparent",color:e?s.iconActive:s.label,fontWeight:"600"})},b=t=>{d(a,!t),d(o,t)};return b(!1),i.addEventListener("keydown",t=>{if(t.key!=="ArrowLeft"&&t.key!=="ArrowRight")return;const e=n.activeElement===a?o:a;n.activeElement!==a&&n.activeElement!==o||(t.preventDefault(),e.focus(),e.click())}),{el:i,setInsightsOpen:b}}
@@ -0,0 +1,44 @@
1
+ export interface VirtualColumn<R> {
2
+ header: string;
3
+ /** The cell as shown. */
4
+ get: (row: R) => string;
5
+ /** What to sort by, when that is not the shown text (a number column). Omit to sort by `get`. */
6
+ sortKey?: (row: R) => number | string | undefined;
7
+ /** Right-align (numbers). */
8
+ numeric?: boolean;
9
+ }
10
+ export interface VirtualTableOptions<R> {
11
+ columns: VirtualColumn<R>[];
12
+ rows: ArrayLike<R>;
13
+ ariaLabel: string;
14
+ ownerDocument?: Document;
15
+ /** Pixel height of every row. Default 24. */
16
+ rowHeight?: number;
17
+ /** Rows drawn beyond the visible window, each side. Default 10. */
18
+ overscan?: number;
19
+ /** Viewport height assumed when the element has not been laid out (tests, hidden parents). Default 480. */
20
+ fallbackViewportHeight?: number;
21
+ onRowClick?: (row: R, rowIndex: number) => void;
22
+ }
23
+ export interface VirtualTable<R> {
24
+ /** The scroll container; mount it in a flex column or give it a height. */
25
+ el: HTMLElement;
26
+ /** Row count currently shown. */
27
+ readonly length: number;
28
+ /**
29
+ * The rows in the order shown NOW (after sorting). Lazily walks a snapshot of that order and of the rows: a sort or
30
+ * `setRows` made while the walk is under way (a large export yields to the page between slices) does not change it,
31
+ * so the file holds every row once, in the order it was started in.
32
+ */
33
+ orderedRows(): Iterable<R>;
34
+ /** Replace the rows, keeping the current sort. */
35
+ setRows(rows: ArrayLike<R>): void;
36
+ /** The index range currently drawn, for tests. */
37
+ drawnRange(): {
38
+ first: number;
39
+ last: number;
40
+ };
41
+ destroy(): void;
42
+ }
43
+ export declare function createVirtualTable<R>(opts: VirtualTableOptions<R>): VirtualTable<R>;
44
+ //# sourceMappingURL=virtualTable.d.ts.map
@@ -0,0 +1 @@
1
+ import{ensureStylesInjected as U}from"./dieList.js";import{CLR as _,RADIUS as K}from"./toolbar.js";const X=64,G=260,J=7.4;export function createVirtualTable(h){const l=h.ownerDocument??document;U(l);const b=h.rowHeight??24,L=h.overscan??10,w=h.columns;let c=h.rows,g=null,C=-1,x=1,M=0,k=-1;const i=l.createElement("div");Object.assign(i.style,{overflow:"auto",flex:"1",minHeight:"0",minWidth:"0",border:`1px solid ${_.menuBorder}`,borderRadius:K.control}),i.tabIndex=0,i.setAttribute("role","region"),i.setAttribute("aria-label",h.ariaLabel);const d=l.createElement("table");d.className="wmap-dielist-table",d.setAttribute("aria-label",h.ariaLabel),d.style.tableLayout="fixed";const N=l.createElement("colgroup"),j=Math.min(c.length,50);let H=0;w.forEach(t=>{let n=t.header.length+2;for(let o=0;o<j;o++)n=Math.max(n,t.get(c[o]).length);const e=Math.max(X,Math.min(G,Math.round(n*J+20)));H+=e;const a=l.createElement("col");a.style.width=`${e}px`,N.appendChild(a)}),d.style.width=`${H}px`,d.style.minWidth="100%",d.appendChild(N);const R=l.createElement("thead"),O=l.createElement("tr"),S=[];w.forEach((t,n)=>{const e=l.createElement("th");e.className="wmap-dielist-th",e.scope="col",e.textContent=t.header,e.style.cursor="pointer",e.style.overflow="hidden",e.style.textOverflow="ellipsis",t.numeric&&(e.style.textAlign="right"),e.setAttribute("aria-sort","none"),e.tabIndex=0;const a=()=>B(n);e.addEventListener("click",a),e.addEventListener("keydown",o=>{(o.key==="Enter"||o.key===" ")&&(o.preventDefault(),a())}),S.push(e),O.appendChild(e)}),R.appendChild(O),d.appendChild(R);const $=l.createElement("tbody");d.appendChild($),i.appendChild(d);const D=()=>{const t=l.createElement("tr");t.setAttribute("aria-hidden","true");const n=l.createElement("td");return n.colSpan=w.length,n.style.padding="0",n.style.border="none",t.appendChild(n),t},F=D(),I=D(),q=t=>c[g?g[t]:t];function z(){return i.clientHeight>0?i.clientHeight:h.fallbackViewportHeight??480}function E(t=!1){const n=c.length,e=R.offsetHeight||b,a=Math.max(0,i.scrollTop-e),o=Math.max(0,Math.floor(a/b)-L),r=Math.min(n-1,Math.ceil((a+z())/b)+L);if(!t&&o===M&&r===k)return;M=o,k=r;const p=l.createDocumentFragment();F.firstElementChild.style.height=`${o*b}px`,I.firstElementChild.style.height=`${Math.max(0,n-1-r)*b}px`,p.appendChild(F);for(let s=o;s<=r;s++){const u=q(s),m=l.createElement("tr");m.style.height=`${b}px`,m.setAttribute("aria-rowindex",String(s+2));for(const y of w){const f=l.createElement("td");f.className="wmap-dielist-td",f.textContent=y.get(u),f.style.overflow="hidden",f.style.textOverflow="ellipsis",y.numeric&&(f.style.textAlign="right"),m.appendChild(f)}if(h.onRowClick){m.style.cursor="pointer";const y=g?g[s]:s;m.addEventListener("click",()=>h.onRowClick(u,y))}p.appendChild(m)}p.appendChild(I),$.replaceChildren(p)}function B(t){C===t?x=x===1?-1:1:(C=t,x=1),S.forEach((n,e)=>n.setAttribute("aria-sort",e!==C?"none":x===1?"ascending":"descending")),S.forEach((n,e)=>{n.textContent=w[e].header+(e===C?x===1?" \u25B2":" \u25BC":"")}),T(),i.scrollTop=0,E(!0)}function T(){const t=c.length;if(C<0||t===0){g=null;return}const n=w[C],e=n.sortKey??n.get,a=new Array(t);for(let r=0;r<t;r++)a[r]=e(c[r]);const o=new Uint32Array(t);for(let r=0;r<t;r++)o[r]=r;o.sort((r,p)=>{const s=a[r],u=a[p],m=s===void 0||s===""||typeof s=="number"&&Number.isNaN(s),y=u===void 0||u===""||typeof u=="number"&&Number.isNaN(u);if(m||y)return m===y?r-p:m?1:-1;const f=typeof s=="number"&&typeof u=="number"?s-u:String(s).localeCompare(String(u),void 0,{numeric:!0});return f!==0?f*x:r-p}),g=o}let v=0;const A=l.defaultView,V=()=>{v||(A&&typeof A.requestAnimationFrame=="function"?v=A.requestAnimationFrame(()=>{v=0,E()}):E())};i.addEventListener("scroll",V);const W=typeof ResizeObserver<"u"?new ResizeObserver(()=>E()):null;return W?.observe(i),d.setAttribute("aria-rowcount",String(c.length+1)),E(!0),{el:i,get length(){return c.length},orderedRows(){const t=c,n=g;return(function*(){for(let e=0;e<t.length;e++)yield t[n?n[e]:e]})()},setRows(t){c=t,d.setAttribute("aria-rowcount",String(c.length+1)),T(),i.scrollTop=0,E(!0)},drawnRange:()=>({first:M,last:k}),destroy(){i.removeEventListener("scroll",V),W?.disconnect(),v&&A&&A.cancelAnimationFrame(v),i.remove()}}}
@@ -1,4 +1,10 @@
1
1
  export type AggregationMethod = 'mean' | 'median' | 'stddev' | 'min' | 'max' | 'count';
2
+ /**
3
+ * Whether a stack by this method holds measurements of the test, in its units.
4
+ * A mean, median, minimum or maximum does; a standard deviation is a spread and a
5
+ * count is a tally, so the test's limits say nothing about them.
6
+ */
7
+ export declare function stackHoldsMeasurements(method: string): boolean;
2
8
  /** Minimal die shape required by aggregation functions — satisfied by both Die and DieResult. */
3
9
  export interface DieLike {
4
10
  x: number;
@@ -1 +1 @@
1
- import{copyDie as p,testValue as m}from"./dieTable.js";import{minOf as M,maxOf as V}from"./utils.js";import{positionKey as h}from"./dies.js";export function aggregateValues(l,n,c=0){if(!l.length)return[];const a=new Map,o=new Map;for(const g of l)for(const t of g){const e=h(t),s=m(t,c);s!==void 0?(a.has(e)||(a.set(e,[]),o.set(e,t)),a.get(e).push(s)):o.has(e)||o.set(e,t)}const u=[];for(const[g,t]of o){const e=a.get(g);if(!e?.length){u.push(p(t,{testValues:void 0,testPass:void 0}));continue}let s;if(n==="mean")s=e.reduce((f,i)=>f+i,0)/e.length;else if(n==="median"){const f=[...e].sort((r,d)=>r-d),i=Math.floor(f.length/2);s=f.length%2?f[i]:(f[i-1]+f[i])/2}else if(n==="stddev"){if(e.length<2){u.push(p(t,{testValues:{0:0},testPass:void 0}));continue}const f=e.reduce((i,r)=>i+r,0)/e.length;s=Math.sqrt(e.reduce((i,r)=>i+(r-f)**2,0)/(e.length-1))}else n==="min"?s=M(e):n==="max"?s=V(e):s=e.length;u.push(p(t,{testValues:{0:s},testPass:void 0}))}return u}export function aggregateBinCounts(l,n,c="hard"){if(!l.length)return[];const a=new Map;for(const o of l)for(const u of o)if((c==="soft"?u.sbin:u.hbin)===n){const t=h(u);a.set(t,(a.get(t)??0)+1)}return(l[0]??[]).map(o=>p(o,{testValues:{0:a.get(h(o))??0},testPass:void 0,...c==="soft"?{sbin:n}:{hbin:n}}))}
1
+ import{copyDie as p,testValue as m}from"./dieTable.js";import{minOf as M,maxOf as V}from"./utils.js";import{positionKey as d}from"./dies.js";export function stackHoldsMeasurements(n){return n!=="stddev"&&n!=="count"}export function aggregateValues(n,o,c=0){if(!n.length)return[];const a=new Map,f=new Map;for(const g of n)for(const t of g){const e=d(t),s=m(t,c);s!==void 0?(a.has(e)||(a.set(e,[]),f.set(e,t)),a.get(e).push(s)):f.has(e)||f.set(e,t)}const r=[];for(const[g,t]of f){const e=a.get(g);if(!e?.length){r.push(p(t,{testValues:void 0,testPass:void 0}));continue}let s;if(o==="mean")s=e.reduce((i,u)=>i+u,0)/e.length;else if(o==="median"){const i=[...e].sort((l,h)=>l-h),u=Math.floor(i.length/2);s=i.length%2?i[u]:(i[u-1]+i[u])/2}else if(o==="stddev"){if(e.length<2){r.push(p(t,{testValues:{0:0},testPass:void 0}));continue}const i=e.reduce((u,l)=>u+l,0)/e.length;s=Math.sqrt(e.reduce((u,l)=>u+(l-i)**2,0)/(e.length-1))}else o==="min"?s=M(e):o==="max"?s=V(e):s=e.length;r.push(p(t,{testValues:{0:s},testPass:void 0}))}return r}export function aggregateBinCounts(n,o,c="hard"){if(!n.length)return[];const a=new Map;for(const f of n)for(const r of f)if((c==="soft"?r.sbin:r.hbin)===o){const t=d(r);a.set(t,(a.get(t)??0)+1)}return(n[0]??[]).map(f=>p(f,{testValues:{0:a.get(d(f))??0},testPass:void 0,...c==="soft"?{sbin:o}:{hbin:o}}))}
@@ -6,7 +6,8 @@ export interface DieClassification {
6
6
  quadrant: Quadrant;
7
7
  }
8
8
  export interface ClassifyOptions {
9
- ringCount?: number;
9
+ /** The map's own ring count (`WaferMapResult.ringCount`). Required: there is no default ring count to classify by. */
10
+ ringCount: number;
10
11
  }
11
12
  /**
12
13
  * Classify a die by its radial ring (1 = innermost) and physical wafer quadrant.
@@ -22,7 +23,7 @@ export interface ClassifyOptions {
22
23
  * first (region builders only ever classify positioned dies; an unpositioned
23
24
  * die has no ring/quadrant by definition).
24
25
  */
25
- export declare function classifyDie(die: PositionedDie, wafer: Wafer, options?: ClassifyOptions): DieClassification;
26
+ export declare function classifyDie(die: PositionedDie, wafer: Wafer, options: ClassifyOptions): DieClassification;
26
27
  /** Human-readable label for a ring index (1-based) given a total ring count. */
27
28
  export declare function getRingLabel(ring: number, ringCount: number): string;
28
29
  //# sourceMappingURL=classify.d.ts.map
@@ -1 +1 @@
1
- export function classifyDie(t,e,s={}){const o=Math.max(1,s.ringCount??4),n=t.physX-e.center.x,r=t.physY-e.center.y,a=Math.sqrt(n*n+r*r)/e.radius,c=Math.min(o,Math.max(1,Math.floor(a*o)+1));let i;return n>=0&&r>=0?i="NE":n<0&&r>=0?i="NW":n<0&&r<0?i="SW":i="SE",{ring:c,quadrant:i}}export function getRingLabel(t,e){return e===1?"Full Wafer":t===1?"Ring 1 (core)":t===e?`Ring ${t} (edge)`:`Ring ${t}`}
1
+ import{requireRingCount as u}from"./ringCount.js";export function classifyDie(t,e,s){const o=Math.max(1,u(s,"classifyDie")),n=t.physX-e.center.x,r=t.physY-e.center.y,a=Math.sqrt(n*n+r*r)/e.radius,c=Math.min(o,Math.max(1,Math.floor(a*o)+1));let i;return n>=0&&r>=0?i="NE":n<0&&r>=0?i="NW":n<0&&r<0?i="SW":i="SE",{ring:c,quadrant:i}}export function getRingLabel(t,e){return e===1?"Full Wafer":t===1?"Ring 1 (core)":t===e?`Ring ${t} (edge)`:`Ring ${t}`}
@@ -0,0 +1,205 @@
1
+ /**
2
+ * Detection of a repeating multi-project-wafer (MPW) layout, the gate for the compact view.
3
+ *
4
+ * On an MPW only a few parts sit in each reticle, so the occupied columns and rows form a
5
+ * pattern that repeats at the reticle pitch. Random missing data does not repeat, which is
6
+ * what separates the two: hiding the empty rows of a wafer that merely has holes could hide
7
+ * a real problem, so compaction is only offered when a clear period is found.
8
+ *
9
+ * "Occupied" means a die EXISTS at that index, whether or not it carries a bin: a die with
10
+ * only metadata still has a position, and the layout must not change with the colour mode.
11
+ */
12
+ export interface MpwDetectOptions {
13
+ /** Fewest whole repeats of the period the axis must span. Default 3. */
14
+ minRepeats?: number;
15
+ /**
16
+ * Lowest lag correlation of the occupancy signal that counts as a period. Default 0.75:
17
+ * random sparse layouts score under 0.5 at every lag, while a clean MPW with a few
18
+ * reticle columns missing outright still scores above it.
19
+ */
20
+ minScore?: number;
21
+ /** Occupied fraction above which an axis is dense: nothing worth compacting. Default 0.8. */
22
+ maxDensity?: number;
23
+ /**
24
+ * A unit the host already knows, such as a reticle width or height in dies. Only that unit and
25
+ * its multiples are tested (devices on every second reticle repeat at twice the reticle, a
26
+ * cluster that repeats within a reticle at a divisor of it, which the multiples reach too), so
27
+ * the check still has to pass (the occupied indices must actually repeat) but nothing else is
28
+ * inferred.
29
+ */
30
+ expectedPeriod?: number;
31
+ }
32
+ /** A period found on one axis. */
33
+ export interface MpwPeriod {
34
+ /** Indices between one repeat and the next. */
35
+ period: number;
36
+ /** Lag correlation of the occupancy signal at `period`, 0 to 1. */
37
+ score: number;
38
+ /** Whole repeats of the period within the occupied span. */
39
+ repeats: number;
40
+ /** Occupied indices as a fraction of the span between the first and last. */
41
+ occupiedFraction: number;
42
+ }
43
+ /**
44
+ * Find the smallest period at which the occupied indices of one axis repeat.
45
+ *
46
+ * `indices` are the distinct integer indices that hold at least one die, in any order.
47
+ * Returns `null` when the axis is nearly full (nothing to compact), spans too few repeats,
48
+ * or does not repeat: random sparse data scores near zero at every period.
49
+ */
50
+ export declare function detectMpwPeriod(indices: readonly number[], options?: MpwDetectOptions): MpwPeriod | null;
51
+ /** What one axis looks like for the purposes of the compact gate. */
52
+ export type AxisPattern = ({
53
+ kind: 'periodic';
54
+ } & MpwPeriod) | {
55
+ kind: 'dense';
56
+ occupiedFraction: number;
57
+ } | {
58
+ kind: 'none';
59
+ };
60
+ export interface MpwLayout {
61
+ x: AxisPattern;
62
+ y: AxisPattern;
63
+ /** True when compaction should be offered. */
64
+ offered: boolean;
65
+ }
66
+ /**
67
+ * Decide whether the compact view applies to a set of dies.
68
+ *
69
+ * Offered when both axes repeat, or one repeats and the other is dense (a layout that is
70
+ * sparse in one direction only still has empty rows or columns worth removing). Pass the
71
+ * dies of every wafer in the selection, so the answer, and the layout built from it, is
72
+ * the same for every card in a gallery. Unpositioned dies are ignored.
73
+ */
74
+ export declare function detectMpwLayout(dies: readonly {
75
+ x?: number | null;
76
+ y?: number | null;
77
+ }[], options?: Omit<MpwDetectOptions, 'expectedPeriod'> & {
78
+ reticle?: {
79
+ width: number;
80
+ height: number;
81
+ };
82
+ }): MpwLayout;
83
+ /**
84
+ * The compact layout of a set of dies: which original columns and rows survive, and the
85
+ * indices they take. Only the layout, never the dies themselves, so one map can be built
86
+ * from every wafer in a selection and applied to each, giving all gallery cards one grid.
87
+ */
88
+ export interface CompactMap {
89
+ /** Original die `x` of each compact column, ascending. All coordinates shown to a user are these. */
90
+ readonly columns: readonly number[];
91
+ /** Original die `y` of each compact row, ascending. */
92
+ readonly rows: readonly number[];
93
+ /** Compact columns that follow a skipped range of original columns (so a group of columns starts there). */
94
+ readonly columnBreaks: readonly number[];
95
+ /** Compact rows that follow a skipped range of original rows. */
96
+ readonly rowBreaks: readonly number[];
97
+ /**
98
+ * The blocks the layout falls into: each is a run of compact columns and a run of compact
99
+ * rows, `[start, end)`, with no skipped original index inside either, and at least one
100
+ * die of the layout in it. On an MPW these are the reticle groups the dies sit in, so
101
+ * drawing each as a box keeps them distinct once the gaps between them are gone.
102
+ */
103
+ readonly groups: readonly CompactGroup[];
104
+ /** Compact column of an original `x`, or `undefined` when no die of the layout sits in it. */
105
+ columnOf(x: number): number | undefined;
106
+ /** Compact row of an original `y`, or `undefined` when no die of the layout sits in it. */
107
+ rowOf(y: number): number | undefined;
108
+ }
109
+ /** A block of compact cells with no skipped original index inside it; see `CompactMap.groups`. */
110
+ export interface CompactGroup {
111
+ readonly columns: readonly [start: number, end: number];
112
+ readonly rows: readonly [start: number, end: number];
113
+ }
114
+ /**
115
+ * Build the compact layout for `dies`: the columns and rows that hold at least one die,
116
+ * renumbered 0..n-1 in order. A die EXISTS there whether or not it carries a bin (see the
117
+ * note at the top of this file), so the layout never depends on colour mode, filters or
118
+ * which wafer has data. Unpositioned dies are ignored.
119
+ */
120
+ export declare function buildCompactMap(dies: readonly {
121
+ x?: number | null;
122
+ y?: number | null;
123
+ }[]): CompactMap;
124
+ /** A die with the compact cell it is drawn in. */
125
+ export interface CompactPlacement<T> {
126
+ die: T;
127
+ column: number;
128
+ row: number;
129
+ }
130
+ /**
131
+ * Place `dies` on a compact grid. With no `basis` the layout is built from `dies`; pass the
132
+ * dies of every wafer in a selection as `basis` so each wafer lands on the same grid. Every
133
+ * positioned die is placed, none dropped: a die whose column or row is missing from the
134
+ * layout throws, since that is a layout built from the wrong dies, not data to hide.
135
+ */
136
+ export declare function compactDies<T extends {
137
+ x?: number | null;
138
+ y?: number | null;
139
+ }>(dies: readonly T[], basis?: readonly {
140
+ x?: number | null;
141
+ y?: number | null;
142
+ }[] | CompactMap): {
143
+ placements: CompactPlacement<T>[];
144
+ map: CompactMap;
145
+ };
146
+ /**
147
+ * Whether the compact layout should be offered for the dies of one map or of every map in a
148
+ * gallery. The single rule both renderers use: `reticles` holds one entry per map, and a
149
+ * reticle size fixes the period only when every map supplies the same one; otherwise the
150
+ * period is inferred from the dies.
151
+ */
152
+ export declare function compactLayoutOffered(dies: readonly {
153
+ x?: number | null;
154
+ y?: number | null;
155
+ }[], reticles: readonly ({
156
+ width: number;
157
+ height: number;
158
+ } | undefined)[], options?: Omit<MpwDetectOptions, 'expectedPeriod'>): boolean;
159
+ /** What the detector saw on one axis. Counts and scores only: no index or coordinate. */
160
+ export interface AxisDiagnostics {
161
+ /** Distinct indices that hold a die. */
162
+ occupied: number;
163
+ /** Indices from the first occupied to the last. */
164
+ span: number;
165
+ /** Maximal runs of consecutive occupied indices. */
166
+ runs: number;
167
+ pattern: AxisPattern;
168
+ /** The best-scoring candidate periods, whether or not any passed. */
169
+ bestPeriods: {
170
+ period: number;
171
+ score: number;
172
+ }[];
173
+ }
174
+ export interface MpwDiagnostics {
175
+ wafers: number;
176
+ positionedDies: number;
177
+ unpositionedDies: number;
178
+ /** Blocks of compact cells that hold dies. */
179
+ groups: number;
180
+ reticle: {
181
+ width: number;
182
+ height: number;
183
+ } | undefined;
184
+ offered: boolean;
185
+ thresholds: Required<Omit<MpwDetectOptions, 'expectedPeriod'>>;
186
+ columns: AxisDiagnostics;
187
+ rows: AxisDiagnostics;
188
+ }
189
+ /**
190
+ * Everything the compact gate saw, as numbers a host can show a user and ask them to send
191
+ * back. It exists because the detector's thresholds cannot be tuned on data nobody can
192
+ * share, so what it needs to report must carry no die position, bin, value or identity:
193
+ * counts, fractions, scores and periods only. `reticles` holds one entry per map, as for
194
+ * `compactLayoutOffered`, which this agrees with by construction.
195
+ */
196
+ export declare function diagnoseMpwLayout(dies: readonly {
197
+ x?: number | null;
198
+ y?: number | null;
199
+ }[], reticles: readonly ({
200
+ width: number;
201
+ height: number;
202
+ } | undefined)[], options?: Omit<MpwDetectOptions, 'expectedPeriod'>): MpwDiagnostics;
203
+ /** The text a user copies and sends back. Plain lines, so it can be read before it is sent. */
204
+ export declare function formatMpwDiagnostics(d: MpwDiagnostics, library: string): string;
205
+ //# sourceMappingURL=compact.d.ts.map
@@ -0,0 +1,3 @@
1
+ import{hasPosition as a}from"./dies.js";const h={minRepeats:3,minScore:.75,maxDensity:.8};function w(t,e){const s=t.length-e;if(s<2)return 0;let r=0,o=0,n=0,i=0,d=0;for(let u=0;u<s;u++){const p=t[u],l=t[u+e];r+=p,o+=l,n+=p*p,i+=l*l,d+=p*l}const f=n-r*r/s,c=i-o*o/s;return f<=0||c<=0?0:(d-r*o/s)/Math.sqrt(f*c)}function x(t,e,s){const r=Math.floor(t/e),o=[];if(s!==void 0&&s>=1){const n=Math.floor(s);for(let i=n;i<=r;i+=n)i>=2&&o.push(i)}else for(let n=2;n<=r;n++)o.push(n);return o}function g(t){if(t.length===0)return null;let e=1/0,s=-1/0;for(const n of t)n<e&&(e=n),n>s&&(s=n);const r=new Uint8Array(s-e+1);let o=0;for(const n of t)r[n-e]||(r[n-e]=1,o++);return{signal:r,count:o}}export function detectMpwPeriod(t,e={}){const{minRepeats:s,minScore:r,maxDensity:o}={...h,...e},{expectedPeriod:n}=e,i=g(t);if(!i)return null;const d=i.signal.length,f=i.count/d;if(f>o)return null;for(const c of x(d,s,n)){const u=w(i.signal,c);if(u>=r)return{period:c,score:u,repeats:Math.floor(d/c),occupiedFraction:f}}return null}function m(t,e){const{minRepeats:s,maxDensity:r}={...h,...e},o=detectMpwPeriod(t,e);if(o)return{kind:"periodic",...o};const n=g(t);return n&&n.count>=s&&n.count/n.signal.length>r?{kind:"dense",occupiedFraction:n.count/n.signal.length}:{kind:"none"}}export function detectMpwLayout(t,e={}){const{reticle:s,...r}=e,o=new Set,n=new Set;for(const c of t)a(c)&&(o.add(c.x),n.add(c.y));const i=m([...o],{...r,expectedPeriod:s?.width}),d=m([...n],{...r,expectedPeriod:s?.height}),f=i.kind==="periodic"&&(d.kind==="periodic"||d.kind==="dense")||d.kind==="periodic"&&i.kind==="dense";return{x:i,y:d,offered:f}}function y(t,e){const s=[0,...e,t],r=new Int32Array(t);for(let o=0;o+1<s.length;o++)r.fill(o,s[o],s[o+1]);return{ids:r,bounds:s}}function $(t){const e=[...t].sort((o,n)=>o-n),s=[],r=new Map;return e.forEach((o,n)=>{r.set(o,n),n>0&&o-e[n-1]>1&&s.push(n)}),{sorted:e,breaks:s,indexOf:r}}export function buildCompactMap(t){const e=new Set,s=new Set;for(const c of t)a(c)&&(e.add(c.x),s.add(c.y));const r=$(e),o=$(s),n=y(r.sorted.length,r.breaks),i=y(o.sorted.length,o.breaks),d=new Set;for(const c of t)a(c)&&d.add(n.ids[r.indexOf.get(c.x)]*i.bounds.length+i.ids[o.indexOf.get(c.y)]);return{groups:[...d].sort((c,u)=>c-u).map(c=>{const u=Math.floor(c/i.bounds.length),p=c%i.bounds.length;return{columns:[n.bounds[u],n.bounds[u+1]],rows:[i.bounds[p],i.bounds[p+1]]}}),columns:r.sorted,rows:o.sorted,columnBreaks:r.breaks,rowBreaks:o.breaks,columnOf:c=>r.indexOf.get(c),rowOf:c=>o.indexOf.get(c)}}export function compactDies(t,e){const s=e&&"columnOf"in e?e:buildCompactMap(e??t),r=[];for(const o of t){if(!a(o))continue;const n=s.columnOf(o.x),i=s.rowOf(o.y);if(n===void 0||i===void 0)throw new RangeError(`compactDies: die (${o.x}, ${o.y}) is outside the compact layout`);r.push({die:o,column:n,row:i})}return{placements:r,map:s}}export function compactLayoutOffered(t,e,s={}){return detectMpwLayout(t,{...s,reticle:b(e)}).offered}function b(t){const e=t[0];return e&&t.every(s=>s&&s.width===e.width&&s.height===e.height)?{width:e.width,height:e.height}:void 0}function k(t,e){const{minRepeats:s}={...h,...e},r=g(t);if(!r)return{occupied:0,span:0,runs:0,pattern:{kind:"none"},bestPeriods:[]};let o=0;r.signal.forEach((i,d)=>{i&&!r.signal[d-1]&&o++});const n=[];for(const i of x(r.signal.length,s,e.expectedPeriod))n.push({period:i,score:w(r.signal,i)});return n.sort((i,d)=>d.score-i.score||i.period-d.period),{occupied:r.count,span:r.signal.length,runs:o,pattern:m(t,e),bestPeriods:n.slice(0,5)}}export function diagnoseMpwLayout(t,e,s={}){const r=b(e),o=new Set,n=new Set;let i=0;for(const d of t)a(d)&&(i++,o.add(d.x),n.add(d.y));return{wafers:e.length,positionedDies:i,unpositionedDies:t.length-i,groups:buildCompactMap(t).groups.length,reticle:r,offered:detectMpwLayout(t,{...s,reticle:r}).offered,thresholds:{...h,...s},columns:k([...o],{...s,expectedPeriod:r?.width}),rows:k([...n],{...s,expectedPeriod:r?.height})}}function D(t,e){const s=e.span>0?` (${(100*e.occupied/e.span).toFixed(1)}%)`:"",r=e.pattern.kind==="periodic"?`periodic, period ${e.pattern.period}, score ${e.pattern.score.toFixed(3)}, ${e.pattern.repeats} repeats`:e.pattern.kind==="dense"?"dense, nothing to compact":"no repeating pattern found",o=e.bestPeriods.length?`
2
+ best periods: ${e.bestPeriods.map(n=>`${n.period} (${n.score.toFixed(3)})`).join(", ")}`:"";return`${t}: ${e.occupied} occupied of ${e.span}${s}, ${e.runs} runs; ${r}${o}`}export function formatMpwDiagnostics(t,e){return["wafermap compact-layout diagnostics (format 1)",`library: ${e}`,"Counts and scores only: no die coordinates, bins, test values or wafer identity.",`wafers: ${t.wafers}`,`dies: ${t.positionedDies} positioned, ${t.unpositionedDies} without a position`,`reticle size: ${t.reticle?`${t.reticle.width} x ${t.reticle.height} (supplied by every wafer)`:"not supplied"}`,`compact layout offered: ${t.offered?"yes":"no"}`,`groups of dies: ${t.groups}`,`thresholds: minRepeats ${t.thresholds.minRepeats}, minScore ${t.thresholds.minScore}, maxDensity ${t.thresholds.maxDensity}`,D("columns",t.columns),D("rows",t.rows)].join(`
3
+ `)}
@@ -12,17 +12,36 @@
12
12
  *
13
13
  * Deliberately internal (not re-exported from `core/index.ts`).
14
14
  */
15
- /** The industry convention, applied ONLY to input that states no pass bins. */
16
- export declare const INPUT_DEFAULT_PASS_BINS: readonly number[];
17
15
  /**
18
- * One wafer's pass bins: its own (carried from `buildWaferMap`), else the
19
- * caller's default for items built without any, else the input convention.
16
+ * The pass bins of a build: what the input states, or — only when it states
17
+ * none — the industry convention (bin 1). Called by `buildWaferMap` and nothing
18
+ * else; from there the value travels on the result.
19
+ */
20
+ export declare function normalizePassBins(stated: readonly number[] | undefined): number[];
21
+ /**
22
+ * The pass bins a source carries, or an error naming where they were missing.
23
+ * Downstream of the build there is no default to fall back on: a surface that
24
+ * cannot say which bins pass must not state a yield.
25
+ */
26
+ export declare function requirePassBins(source: {
27
+ passBins?: readonly number[];
28
+ } | null | undefined, where: string): readonly number[];
29
+ /**
30
+ * One wafer's pass bins: its own (carried from `buildWaferMap`), else
31
+ * `fallback` — a value the CALLER holds for items built without any (a report
32
+ * given `passBins` for hand-built maps) — else an error. Never a built-in default.
20
33
  */
21
34
  export declare function itemPassBins(item: {
22
35
  passBins?: readonly number[];
23
36
  } | null | undefined, fallback?: readonly number[]): readonly number[];
24
37
  /** Same bins, in any order. */
25
38
  export declare function sameBins(a: readonly number[], b: readonly number[]): boolean;
39
+ /**
40
+ * The pass bins every wafer is judged by, or `undefined` when they differ (or there are none to
41
+ * ask). For a surface that must name one set — a headline, or which bins count as failures — and
42
+ * so says nothing rather than naming one wafer's set for a population judged several ways.
43
+ */
44
+ export declare function commonPassBins(sets: Iterable<readonly number[] | undefined>): readonly number[] | undefined;
26
45
  /**
27
46
  * Names the pass bins in use, for a yield label: "bin 1", "bins 1, 2", or —
28
47
  * when the wafers shown disagree — "per wafer: bin 1 · bins 1, 2". A label that
@@ -1 +1 @@
1
- export const INPUT_DEFAULT_PASS_BINS=[1];export function itemPassBins(s,e=INPUT_DEFAULT_PASS_BINS){return s?.passBins??e}export function sameBins(s,e){const t=new Set(s),n=new Set(e);return t.size===n.size&&[...t].every(i=>n.has(i))}export function passBinsLabel(s){const e=[];for(const n of s)e.some(i=>sameBins(i,n))||e.push(n);const t=n=>n.length===0?"no bins":n.length===1?`bin ${n[0]}`:`bins ${n.join(", ")}`;return e.length<=1?t(e[0]??INPUT_DEFAULT_PASS_BINS):`per wafer: ${e.map(t).join(" \xB7 ")}`}
1
+ const r=[1];export function normalizePassBins(s){return[...s??r]}export function requirePassBins(s,n){const e=s?.passBins;if(!e)throw new Error(`${n}: no pass bins (WaferMapResult.passBins); a map not built by buildWaferMap must state them.`);return e}export function itemPassBins(s,n){return s?.passBins??n??requirePassBins(void 0,"itemPassBins")}export function sameBins(s,n){const e=new Set(s),t=new Set(n);return e.size===t.size&&[...e].every(i=>t.has(i))}export function commonPassBins(s){let n;for(const e of s){if(e===void 0)return;if(n===void 0)n=e;else if(!sameBins(n,e))return}return n}export function passBinsLabel(s){const n=[];for(const t of s)n.some(i=>sameBins(i,t))||n.push(t);const e=t=>t.length===0?"no bins":t.length===1?`bin ${t[0]}`:`bins ${t.join(", ")}`;return n.length===0?"no wafers":n.length===1?e(n[0]):`per wafer: ${n.map(e).join(" \xB7 ")}`}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The ring count a map's rings, ring findings, ring yields and ring plots all use — and the ONE place its default lives.
3
+ *
4
+ * Same rule as pass bins (core/passBins.ts): the default applies where data ENTERS the library, `buildWaferMap`
5
+ * (`WaferMapInput.ringCount`, default 4), and from there the value travels on the result (`WaferMapResult.ringCount`).
6
+ * Everywhere else it is read, never replaced: a surface that fell back to 4 for a wafer built with 6 rings would draw one
7
+ * set of rings and report findings for another. `scripts/check-pass-bin-defaults.mjs` and `tests/passBinsDefault.test.mjs`
8
+ * hold this too.
9
+ *
10
+ * Deliberately internal (not re-exported from `core/index.ts`).
11
+ */
12
+ /** The ring count of an input that states none. Called by `buildWaferMap` and nothing else. */
13
+ export declare function defaultRingCount(): number;
14
+ /** The ring count a source carries, or an error naming where it was missing. There is no default to fall back on. */
15
+ export declare function requireRingCount(source: {
16
+ ringCount?: number;
17
+ } | null | undefined, where: string): number;
18
+ /** One item's ring count: its own, else `fallback` — a value the CALLER holds — else an error. Never a built-in default. */
19
+ export declare function itemRingCount(item: {
20
+ ringCount?: number;
21
+ } | null | undefined, fallback?: number): number;
22
+ //# sourceMappingURL=ringCount.d.ts.map
@@ -0,0 +1 @@
1
+ const u=4;export function defaultRingCount(){return 4}export function requireRingCount(n,t){const r=n?.ringCount;if(typeof r!="number"||!Number.isFinite(r))throw new Error(`${t}: no ring count (WaferMapResult.ringCount); a map not built by buildWaferMap must state it.`);return r}export function itemRingCount(n,t){return n?.ringCount??t??requireRingCount(void 0,"itemRingCount")}
@@ -0,0 +1,43 @@
1
+ /** A cell: a number is written by {@link fileNumber}, a string is escaped by
2
+ * `csvField`, and null/undefined/non-finite are an empty cell. */
3
+ export type CsvCell = string | number | null | undefined;
4
+ /** One column: its header and how to read it from a row. */
5
+ export interface CsvColumn<R> {
6
+ header: string;
7
+ get: (row: R) => CsvCell;
8
+ }
9
+ /** Significant digits written for a number: a double's reliable precision. */
10
+ export declare const FILE_NUMBER_DIGITS = 15;
11
+ /**
12
+ * A number as it goes into a file, in plain or exponent notation (`0.000123`,
13
+ * `1.5e-7`) that every consumer parses. Non-finite → empty.
14
+ *
15
+ * - **A whole number** is written as is.
16
+ * - **A single-precision reading** (an STDF `R*4`, which wmap keeps as `f32`)
17
+ * is written as the shortest decimal that is still that same `f32`: `0.5123457`,
18
+ * not the 17-digit expansion of the double it was widened to
19
+ * (`0.51234567165374756`), which claims precision the tester never recorded and
20
+ * doubles the size of the file.
21
+ * - **Anything else** keeps {@link FILE_NUMBER_DIGITS} significant digits, which
22
+ * drops float noise (`0.1 + 0.2` → `0.3`) and nothing a measurement carried.
23
+ */
24
+ export declare function fileNumber(n: number): string;
25
+ /** One cell as CSV text, escaped. */
26
+ export declare function csvCell(value: CsvCell): string;
27
+ /** One CSV line from cells, without a line ending. */
28
+ export declare function csvLine(cells: readonly CsvCell[]): string;
29
+ /** A header row and one line per row, joined with `\n` (no trailing newline —
30
+ * the format every export here has always used). */
31
+ export declare function tableToCsv<R>(columns: readonly CsvColumn<R>[], rows: Iterable<R>): string;
32
+ /** The header line, then one line per row, as they are produced. The unit a
33
+ * streaming writer works in: it can stop between any two lines, which a joined
34
+ * string cannot. */
35
+ export declare function csvLines<R>(columns: readonly CsvColumn<R>[], rows: Iterable<R>): Generator<string>;
36
+ /**
37
+ * The same table as tab-separated text, for the clipboard: what a spreadsheet or
38
+ * JMP pastes into cells. Numbers are written as in a file; a text cell has its
39
+ * tabs and line breaks turned into spaces, since either would start a new cell
40
+ * or row. No quoting — a paste does not parse it.
41
+ */
42
+ export declare function tableToTsv<R>(columns: readonly CsvColumn<R>[], rows: Iterable<R>): string;
43
+ //# sourceMappingURL=tableCsv.d.ts.map
@@ -0,0 +1,3 @@
1
+ import{csvField as f}from"./utils.js";export const FILE_NUMBER_DIGITS=15;export function fileNumber(r){return Number.isFinite(r)?Number.isInteger(r)&&Math.abs(r)<1e15?String(r):Math.fround(r)===r?u(r):String(Number(r.toPrecision(FILE_NUMBER_DIGITS))):""}function u(r){const o=Math.floor(Math.log10(Math.abs(r)));for(let t=7;t<=9;t++){const n=t-1-o;if(n<0||n>22)break;const e=10**n,i=Math.round(r*e)/e;if(Math.fround(i)===r)return String(i)}for(let t=7;t<=9;t++){const n=r.toPrecision(t);if(Math.fround(Number(n))===r)return String(Number(n))}return String(Number(r.toPrecision(FILE_NUMBER_DIGITS)))}export function csvCell(r){return r==null?"":typeof r=="number"?fileNumber(r):f(r)}export function csvLine(r){return r.map(csvCell).join(",")}export function tableToCsv(r,o){return Array.from(csvLines(r,o)).join(`
2
+ `)}export function*csvLines(r,o){yield csvLine(r.map(t=>t.header));for(const t of o)yield csvLine(r.map(n=>n.get(t)))}export function tableToTsv(r,o){const t=e=>e==null?"":typeof e=="number"?fileNumber(e):e.replace(/[\t\r\n]+/g," "),n=[r.map(e=>t(e.header)).join(" ")];for(const e of o)n.push(r.map(i=>t(i.get(e))).join(" "));return n.join(`
3
+ `)}
@@ -1,3 +1,17 @@
1
+ /**
2
+ * Which columns (or rows) of a compact layout get an axis label.
3
+ *
4
+ * Group starts when the cells are too narrow to label every one: each is the first column of
5
+ * a reticle group, so the labels name the boundaries, there are few of them, and they are
6
+ * spread out. Every cell once there is room for that, so a zoomed-in map labels each die.
7
+ * A layout with a single group has no boundaries to name, so it is thinned evenly instead.
8
+ *
9
+ * @param count cells on the axis
10
+ * @param breaks compact indices that start a group after the first (see `CompactMap`)
11
+ * @param cellPx on-screen size of one cell along the axis
12
+ * @param minGapPx least distance between two labels
13
+ */
14
+ export declare function compactTickIndices(count: number, breaks: readonly number[], cellPx: number, minGapPx: number): number[];
1
15
  /**
2
16
  * Round a raw spacing to the nearest 1, 2 or 5 × 10ⁿ — THE rounding rule for
3
17
  * every tick grid in the library, the wafer map's own mm axes included.
@@ -1 +1 @@
1
- export function niceStep(i){if(!(i>0)||!Number.isFinite(i))return 0;const n=Math.pow(10,Math.floor(Math.log10(i))),t=i/n;return(t<1.5?1:t<3.5?2:t<7.5?5:10)*n}export function stepDecimals(i){if(!(i>0)||!Number.isFinite(i))return 0;for(let n=0;n<=9;n++){const t=i*10**n;if(Math.abs(t-Math.round(t))<1e-6*Math.max(1,t))return n}return 9}function M(i,n,t){const c=Math.ceil(i/t-1e-9)*t,e=[];for(let r=c;r<=n+t*1e-9;r+=t)e.push(Number((Math.round(r/t)*t).toFixed(stepDecimals(t)+2)));return e}export function fitTicks(i,n,t,c){const e=n-i;if(!(e>0)||!Number.isFinite(e)||!(t>0))return{ticks:Number.isFinite(i)?[i]:[],step:0};const r=[1,2,5];let f=Math.floor(Math.log10(e/Math.max(1,t/4))),u=0;for(let a=0;a<60;a++){const o=Number((r[u]*10**f).toPrecision(12)),s=M(i,n,o);if(s.length<=1||o/e*t>=c(o,s))return{ticks:s,step:o};++u===r.length&&(u=0,f++)}return{ticks:[i,n],step:e}}
1
+ export function compactTickIndices(e,n,t,s){if(e<=0)return[];const r=Math.max(1,Math.ceil(s/Math.max(t,1e-9)));if(r===1)return Array.from({length:e},(i,c)=>c);if(n.length===0){const i=[];for(let c=0;c<e;c+=r)i.push(c);return i}const o=[];let u=-1/0;for(const i of[0,...n])((i-u)*t>=s||o.length===0)&&(o.push(i),u=i);return o}export function niceStep(e){if(!(e>0)||!Number.isFinite(e))return 0;const n=Math.pow(10,Math.floor(Math.log10(e))),t=e/n;return(t<1.5?1:t<3.5?2:t<7.5?5:10)*n}export function stepDecimals(e){if(!(e>0)||!Number.isFinite(e))return 0;for(let n=0;n<=9;n++){const t=e*10**n;if(Math.abs(t-Math.round(t))<1e-6*Math.max(1,t))return n}return 9}function h(e,n,t){const s=Math.ceil(e/t-1e-9)*t,r=[];for(let o=s;o<=n+t*1e-9;o+=t)r.push(Number((Math.round(o/t)*t).toFixed(stepDecimals(t)+2)));return r}export function fitTicks(e,n,t,s){const r=n-e;if(!(r>0)||!Number.isFinite(r)||!(t>0))return{ticks:Number.isFinite(e)?[e]:[],step:0};const o=[1,2,5];let u=Math.floor(Math.log10(r/Math.max(1,t/4))),i=0;for(let c=0;c<60;c++){const f=Number((o[i]*10**u).toPrecision(12)),a=h(e,n,f);if(a.length<=1||f/r*t>=s(f,a))return{ticks:a,step:f};++i===o.length&&(i=0,u++)}return{ticks:[e,n],step:r}}
@@ -34,8 +34,8 @@ export interface BinColors {
34
34
  };
35
35
  }
36
36
  export interface BinColorOptions {
37
- /** Which bins pass. Default `[1]`. Must match the `passBins` behind the yield figure. */
38
- passBins?: readonly number[];
37
+ /** Which bins pass: the map's `passBins`. Required (no default); must match the `passBins` behind the yield figure. */
38
+ passBins: readonly number[];
39
39
  /** Registered bin palette name (`registerBinColorScheme`). Default `'default'`. */
40
40
  binColorScheme?: string;
41
41
  hbinDefs?: readonly BinDef[];
@@ -82,7 +82,7 @@ export interface BinColorOptions {
82
82
  * colours that bin on every card, and `shared` describes the whole gallery.
83
83
  * A lone map resolves over its own dies.
84
84
  */
85
- export declare function resolveBinColors(dies: Iterable<Die>, options?: BinColorOptions): BinColors;
85
+ export declare function resolveBinColors(dies: Iterable<Die>, options: BinColorOptions): BinColors;
86
86
  /** The inputs a built map carries for its bin colours. A `WaferMapResult` is one. */
87
87
  export interface BinColorSource {
88
88
  dies: Iterable<Die>;
@@ -124,8 +124,8 @@ export declare function mergeBinDefs(lists: Iterable<readonly BinDef[] | undefin
124
124
  /** One wafer's dies and the pass bins that wafer was built with. */
125
125
  export interface BinPassGroup {
126
126
  dies: Iterable<Die>;
127
- /** Omitted ⇒ the input convention `[1]` — pass `WaferMapResult.passBins`. */
128
- passBins?: readonly number[];
127
+ /** Required: `WaferMapResult.passBins`. There is no default to judge a wafer by instead. */
128
+ passBins: readonly number[];
129
129
  }
130
130
  /**
131
131
  * `resolveBinColors` over several wafers that may carry DIFFERENT pass bins — a
@@ -147,7 +147,7 @@ export declare function resolveBinColorsByWafer(groups: Iterable<BinPassGroup>,
147
147
  * caller that has dies but no resolved assignment (the summary report). The same
148
148
  * tally `resolveBinColors` uses, so the two cannot disagree about a verdict.
149
149
  */
150
- export declare function binPassSets(dies: Iterable<Die>, passBins?: readonly number[]): BinColors['pass'];
150
+ export declare function binPassSets(dies: Iterable<Die>, passBins: readonly number[]): BinColors['pass'];
151
151
  /** `binPassSets` over wafers that each carry their own pass bins. */
152
152
  export declare function binPassSetsByWafer(groups: Iterable<BinPassGroup>): BinColors['pass'];
153
153
  /** The `pass-bins-mixed` advisory for `resolveBinColorsByWafer`'s `mixedHardBins`, or null. */
@@ -1 +1 @@
1
- import{diePassStatus as w}from"../core/dies.js";import{INPUT_DEFAULT_PASS_BINS as S,itemPassBins as x}from"../core/passBins.js";import{getBinColorScheme as y}from"./colorSchemes.js";import{arrayEqual as u,mapEqual as p,setEqual as b}from"../core/utils.js";export function resolveBinColors(s,n={}){return resolveBinColorsByWafer([{dies:s,passBins:n.passBins}],n).colors}export function binColorsForMaps(s,n={}){return resolveBinColorsForMaps(Array.isArray(s)?s:[s],n).colors}export function resolveBinColorsForMaps(s,n={}){return resolveBinColorsByWafer(s.map(e=>({dies:e.dies,passBins:x(e)})),{...n,hbinDefs:mergeBinDefs(s.map(e=>e.hbinDefs)),sbinDefs:mergeBinDefs(s.map(e=>e.sbinDefs))})}export function mergeBinDefs(s){const n=new Set,e=[];for(const a of s)for(const r of a??[])n.has(r.bin)||(n.add(r.bin),e.push(r));return e}export function resolveBinColorsByWafer(s,n={}){const e=y(n.binColorScheme),a=n.useDefinedBinColors??!0,r=d(s),f=B(r.hardBins,i=>r.pass.hard.has(i),e,a?n.hbinDefs:void 0,!1),t=B(r.softBins,i=>r.pass.soft.has(i),e,a?n.sbinDefs:void 0,!0);return{colors:{hard:f.colors,soft:t.colors,shared:{hard:f.shared,soft:t.shared},pass:r.pass},mixedHardBins:r.mixedHardBins}}export function binPassSets(s,n){return d([{dies:s,passBins:n}]).pass}export function binPassSetsByWafer(s){return d(s).pass}function d(s){const n=new Map,e=new Map,a=new Set;for(const t of s){const i=new Set(t.passBins??S);for(const l of t.dies){const o=w(l,i)===!0;l.hbin!=null&&(n.set(l.hbin,(n.get(l.hbin)??!0)&&o),o&&a.add(l.hbin)),l.sbin!=null&&e.set(l.sbin,(e.get(l.sbin)??!0)&&o)}}const r=t=>new Set([...t].filter(([,i])=>i).map(([i])=>i)),f={hard:r(n),soft:r(e)};return{hardBins:[...n.keys()],softBins:[...e.keys()],pass:f,mixedHardBins:[...a].filter(t=>!f.hard.has(t)).sort((t,i)=>t-i)}}function g(s){return s.length>8?`${s.slice(0,8).join(", ")} and ${s.length-8} more`:s.join(", ")}export function mixedPassBinsWarning(s){if(!s.length)return null;const n=s.length===1;return{code:"pass-bins-mixed",severity:"warning",message:`Hard ${n?"bin":"bins"} ${g(s)} ${n?"passes":"pass"} on some wafers and ${n?"fails":"fail"} on others: these wafers were built with different pass bins. Each wafer's own yield and die verdicts are correct, but a bin has one colour and one legend row, so ${n?"it is":"they are"} shown as failing there.`}}export function binColorsCover(s,n){if(!s.pass)return!1;for(const e of n)if(e.hbin!=null&&!s.hard.has(e.hbin)||e.sbin!=null&&!s.soft.has(e.sbin))return!1;return!0}export function binColorWarning(s,n){const e=n==="softBin"?"soft":n==="hardBin"?"hard":null;if(!e)return null;const a=s.shared[e];if(!a.length)return null;const r=g(a);return{code:"bin-colors-shared",severity:"warning",message:`${e==="hard"?"Hard":"Soft"} bins ${r} are drawn in a colour another bin also has, so colour alone cannot tell them apart. There are more bins than the colour scheme has distinct colours (or a bin definition repeats one). Use the legend, the tooltip, or highlight one bin at a time.`}}const C=1,P=2;function m(s,n,e,a){const r=s.length,f=a?Math.floor(r/2):0;return s[((Math.floor(n)-e+f)%r+r)%r]}function B(s,n,e,a,r){const f=new Map;for(const o of a??[])o.color&&f.set(o.bin,o.color);const t=new Map;for(const o of s)t.set(o,f.get(o)??(n(o)?m(e.pass,o,C,r):m(e.fail,o,P,r)));const i=new Map;for(const[o,h]of t){const c=h.toLowerCase();i.set(c,[...i.get(c)??[],o])}const l=[...i.values()].filter(o=>o.length>1).flat().sort((o,h)=>o-h);return{colors:t,shared:l}}export function binColorsEqual(s,n){return s===n?!0:!s||!n?!1:p(s.hard,n.hard)&&p(s.soft,n.soft)&&u(s.shared.hard,n.shared.hard)&&u(s.shared.soft,n.shared.soft)&&b(s.pass.hard,n.pass.hard)&&b(s.pass.soft,n.pass.soft)}
1
+ import{diePassStatus as x}from"../core/dies.js";import{itemPassBins as C,requirePassBins as u}from"../core/passBins.js";import{getBinColorScheme as S}from"./colorSchemes.js";import{arrayEqual as p,mapEqual as b,setEqual as B}from"../core/utils.js";export function resolveBinColors(s,e){return resolveBinColorsByWafer([{dies:s,passBins:u(e,"resolveBinColors")}],e).colors}export function binColorsForMaps(s,e={}){return resolveBinColorsForMaps(Array.isArray(s)?s:[s],e).colors}export function resolveBinColorsForMaps(s,e={}){return resolveBinColorsByWafer(s.map(n=>({dies:n.dies,passBins:C(n)})),{...e,hbinDefs:mergeBinDefs(s.map(n=>n.hbinDefs)),sbinDefs:mergeBinDefs(s.map(n=>n.sbinDefs))})}export function mergeBinDefs(s){const e=new Set,n=[];for(const a of s)for(const r of a??[])e.has(r.bin)||(e.add(r.bin),n.push(r));return n}export function resolveBinColorsByWafer(s,e={}){const n=S(e.binColorScheme),a=e.useDefinedBinColors??!0,r=d(s),f=w(r.hardBins,i=>r.pass.hard.has(i),n,a?e.hbinDefs:void 0,!1),t=w(r.softBins,i=>r.pass.soft.has(i),n,a?e.sbinDefs:void 0,!0);return{colors:{hard:f.colors,soft:t.colors,shared:{hard:f.shared,soft:t.shared},pass:r.pass},mixedHardBins:r.mixedHardBins}}export function binPassSets(s,e){return d([{dies:s,passBins:e}]).pass}export function binPassSetsByWafer(s){return d(s).pass}function d(s){const e=new Map,n=new Map,a=new Set;for(const t of s){const i=new Set(u(t,"resolveBinColorsByWafer"));for(const l of t.dies){const o=x(l,i)===!0;l.hbin!=null&&(e.set(l.hbin,(e.get(l.hbin)??!0)&&o),o&&a.add(l.hbin)),l.sbin!=null&&n.set(l.sbin,(n.get(l.sbin)??!0)&&o)}}const r=t=>new Set([...t].filter(([,i])=>i).map(([i])=>i)),f={hard:r(e),soft:r(n)};return{hardBins:[...e.keys()],softBins:[...n.keys()],pass:f,mixedHardBins:[...a].filter(t=>!f.hard.has(t)).sort((t,i)=>t-i)}}function g(s){return s.length>8?`${s.slice(0,8).join(", ")} and ${s.length-8} more`:s.join(", ")}export function mixedPassBinsWarning(s){if(!s.length)return null;const e=s.length===1;return{code:"pass-bins-mixed",severity:"warning",message:`Hard ${e?"bin":"bins"} ${g(s)} ${e?"passes":"pass"} on some wafers and ${e?"fails":"fail"} on others: these wafers were built with different pass bins. Each wafer's own yield and die verdicts are correct, but a bin has one colour and one legend row, so ${e?"it is":"they are"} shown as failing there.`}}export function binColorsCover(s,e){if(!s.pass)return!1;for(const n of e)if(n.hbin!=null&&!s.hard.has(n.hbin)||n.sbin!=null&&!s.soft.has(n.sbin))return!1;return!0}export function binColorWarning(s,e){const n=e==="softBin"?"soft":e==="hardBin"?"hard":null;if(!n)return null;const a=s.shared[n];if(!a.length)return null;const r=g(a);return{code:"bin-colors-shared",severity:"warning",message:`${n==="hard"?"Hard":"Soft"} bins ${r} are drawn in a colour another bin also has, so colour alone cannot tell them apart. There are more bins than the colour scheme has distinct colours (or a bin definition repeats one). Use the legend, the tooltip, or highlight one bin at a time.`}}const y=1,v=2;function m(s,e,n,a){const r=s.length,f=a?Math.floor(r/2):0;return s[((Math.floor(e)-n+f)%r+r)%r]}function w(s,e,n,a,r){const f=new Map;for(const o of a??[])o.color&&f.set(o.bin,o.color);const t=new Map;for(const o of s)t.set(o,f.get(o)??(e(o)?m(n.pass,o,y,r):m(n.fail,o,v,r)));const i=new Map;for(const[o,h]of t){const c=h.toLowerCase();i.set(c,[...i.get(c)??[],o])}const l=[...i.values()].filter(o=>o.length>1).flat().sort((o,h)=>o-h);return{colors:t,shared:l}}export function binColorsEqual(s,e){return s===e?!0:!s||!e?!1:b(s.hard,e.hard)&&b(s.soft,e.soft)&&p(s.shared.hard,e.shared.hard)&&p(s.shared.soft,e.shared.soft)&&B(s.pass.hard,e.pass.hard)&&B(s.pass.soft,e.pass.soft)}