@wafertools/wafermap 0.21.1

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 (138) hide show
  1. package/CHANGELOG.md +751 -0
  2. package/LICENSE +21 -0
  3. package/README.md +79 -0
  4. package/dist/index.d.ts +4 -0
  5. package/dist/index.js +1 -0
  6. package/dist/packages/canvas-adapter/canvasTheme.d.ts +28 -0
  7. package/dist/packages/canvas-adapter/canvasTheme.js +1 -0
  8. package/dist/packages/canvas-adapter/charts/barPanel.d.ts +52 -0
  9. package/dist/packages/canvas-adapter/charts/barPanel.js +1 -0
  10. package/dist/packages/canvas-adapter/charts/binCluster.d.ts +17 -0
  11. package/dist/packages/canvas-adapter/charts/binCluster.js +1 -0
  12. package/dist/packages/canvas-adapter/charts/boxplot.d.ts +45 -0
  13. package/dist/packages/canvas-adapter/charts/boxplot.js +1 -0
  14. package/dist/packages/canvas-adapter/charts/capability.d.ts +41 -0
  15. package/dist/packages/canvas-adapter/charts/capability.js +1 -0
  16. package/dist/packages/canvas-adapter/charts/chartShell.d.ts +156 -0
  17. package/dist/packages/canvas-adapter/charts/chartShell.js +1 -0
  18. package/dist/packages/canvas-adapter/charts/correlation.d.ts +35 -0
  19. package/dist/packages/canvas-adapter/charts/correlation.js +1 -0
  20. package/dist/packages/canvas-adapter/charts/histogram.d.ts +30 -0
  21. package/dist/packages/canvas-adapter/charts/histogram.js +1 -0
  22. package/dist/packages/canvas-adapter/charts/index.d.ts +16 -0
  23. package/dist/packages/canvas-adapter/charts/index.js +1 -0
  24. package/dist/packages/canvas-adapter/charts/palette.d.ts +26 -0
  25. package/dist/packages/canvas-adapter/charts/palette.js +1 -0
  26. package/dist/packages/canvas-adapter/charts/regionYieldDiagram.d.ts +18 -0
  27. package/dist/packages/canvas-adapter/charts/regionYieldDiagram.js +1 -0
  28. package/dist/packages/canvas-adapter/charts/scatter.d.ts +37 -0
  29. package/dist/packages/canvas-adapter/charts/scatter.js +1 -0
  30. package/dist/packages/canvas-adapter/icons.d.ts +2 -0
  31. package/dist/packages/canvas-adapter/icons.js +42 -0
  32. package/dist/packages/canvas-adapter/index.d.ts +12 -0
  33. package/dist/packages/canvas-adapter/index.js +1 -0
  34. package/dist/packages/canvas-adapter/insightsTab.d.ts +76 -0
  35. package/dist/packages/canvas-adapter/insightsTab.js +1 -0
  36. package/dist/packages/canvas-adapter/metadataBadge.d.ts +26 -0
  37. package/dist/packages/canvas-adapter/metadataBadge.js +1 -0
  38. package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +181 -0
  39. package/dist/packages/canvas-adapter/renderWaferGallery.js +20 -0
  40. package/dist/packages/canvas-adapter/renderWaferMap.d.ts +316 -0
  41. package/dist/packages/canvas-adapter/renderWaferMap.js +1 -0
  42. package/dist/packages/canvas-adapter/summaryPanel.d.ts +262 -0
  43. package/dist/packages/canvas-adapter/summaryPanel.js +3 -0
  44. package/dist/packages/canvas-adapter/toCanvas.d.ts +106 -0
  45. package/dist/packages/canvas-adapter/toCanvas.js +1 -0
  46. package/dist/packages/canvas-adapter/toolbar.d.ts +496 -0
  47. package/dist/packages/canvas-adapter/toolbar.js +9 -0
  48. package/dist/packages/canvas-adapter/userGuideHtml.d.ts +2 -0
  49. package/dist/packages/canvas-adapter/userGuideHtml.js +968 -0
  50. package/dist/packages/canvas-adapter/version.d.ts +3 -0
  51. package/dist/packages/canvas-adapter/version.js +1 -0
  52. package/dist/packages/core/aggregates.d.ts +54 -0
  53. package/dist/packages/core/aggregates.js +1 -0
  54. package/dist/packages/core/classify.d.ts +24 -0
  55. package/dist/packages/core/classify.js +1 -0
  56. package/dist/packages/core/dies.d.ts +92 -0
  57. package/dist/packages/core/dies.js +1 -0
  58. package/dist/packages/core/index.d.ts +13 -0
  59. package/dist/packages/core/index.js +1 -0
  60. package/dist/packages/core/inference/grid.d.ts +35 -0
  61. package/dist/packages/core/inference/grid.js +1 -0
  62. package/dist/packages/core/inference/index.d.ts +7 -0
  63. package/dist/packages/core/inference/index.js +1 -0
  64. package/dist/packages/core/inference/pitch.d.ts +26 -0
  65. package/dist/packages/core/inference/pitch.js +1 -0
  66. package/dist/packages/core/inference/wafer.d.ts +30 -0
  67. package/dist/packages/core/inference/wafer.js +1 -0
  68. package/dist/packages/core/metadata.d.ts +31 -0
  69. package/dist/packages/core/metadata.js +1 -0
  70. package/dist/packages/core/probe.d.ts +16 -0
  71. package/dist/packages/core/probe.js +1 -0
  72. package/dist/packages/core/reticle.d.ts +68 -0
  73. package/dist/packages/core/reticle.js +1 -0
  74. package/dist/packages/core/transforms.d.ts +142 -0
  75. package/dist/packages/core/transforms.js +1 -0
  76. package/dist/packages/core/utils.d.ts +60 -0
  77. package/dist/packages/core/utils.js +1 -0
  78. package/dist/packages/core/wafer.d.ts +45 -0
  79. package/dist/packages/core/wafer.js +1 -0
  80. package/dist/packages/renderer/buildView.d.ts +420 -0
  81. package/dist/packages/renderer/buildView.js +1 -0
  82. package/dist/packages/renderer/buildWaferMap.d.ts +543 -0
  83. package/dist/packages/renderer/buildWaferMap.js +1 -0
  84. package/dist/packages/renderer/colorMap.d.ts +46 -0
  85. package/dist/packages/renderer/colorMap.js +1 -0
  86. package/dist/packages/renderer/colorSchemes.d.ts +40 -0
  87. package/dist/packages/renderer/colorSchemes.js +1 -0
  88. package/dist/packages/renderer/fmt.d.ts +30 -0
  89. package/dist/packages/renderer/fmt.js +1 -0
  90. package/dist/packages/renderer/index.d.ts +7 -0
  91. package/dist/packages/renderer/index.js +1 -0
  92. package/dist/packages/stats/analyzeWaferLot.d.ts +5 -0
  93. package/dist/packages/stats/analyzeWaferLot.js +1 -0
  94. package/dist/packages/stats/analyzeWaferMap.d.ts +13 -0
  95. package/dist/packages/stats/analyzeWaferMap.js +1 -0
  96. package/dist/packages/stats/binPareto.d.ts +49 -0
  97. package/dist/packages/stats/binPareto.js +1 -0
  98. package/dist/packages/stats/boxplot.d.ts +39 -0
  99. package/dist/packages/stats/boxplot.js +1 -0
  100. package/dist/packages/stats/capability.d.ts +55 -0
  101. package/dist/packages/stats/capability.js +1 -0
  102. package/dist/packages/stats/clusterDetection.d.ts +18 -0
  103. package/dist/packages/stats/clusterDetection.js +1 -0
  104. package/dist/packages/stats/correlation.d.ts +55 -0
  105. package/dist/packages/stats/correlation.js +1 -0
  106. package/dist/packages/stats/facets.d.ts +74 -0
  107. package/dist/packages/stats/facets.js +1 -0
  108. package/dist/packages/stats/filterFindings.d.ts +9 -0
  109. package/dist/packages/stats/filterFindings.js +1 -0
  110. package/dist/packages/stats/findingsNarrative.d.ts +3 -0
  111. package/dist/packages/stats/findingsNarrative.js +1 -0
  112. package/dist/packages/stats/histogram.d.ts +42 -0
  113. package/dist/packages/stats/histogram.js +1 -0
  114. package/dist/packages/stats/index.d.ts +27 -0
  115. package/dist/packages/stats/index.js +1 -0
  116. package/dist/packages/stats/math.d.ts +10 -0
  117. package/dist/packages/stats/math.js +1 -0
  118. package/dist/packages/stats/patternClassification.d.ts +107 -0
  119. package/dist/packages/stats/patternClassification.js +1 -0
  120. package/dist/packages/stats/regions.d.ts +57 -0
  121. package/dist/packages/stats/regions.js +1 -0
  122. package/dist/packages/stats/renderFindingsReport.d.ts +7 -0
  123. package/dist/packages/stats/renderFindingsReport.js +41 -0
  124. package/dist/packages/stats/renderSummaryReport.d.ts +60 -0
  125. package/dist/packages/stats/renderSummaryReport.js +68 -0
  126. package/dist/packages/stats/reportHtml.d.ts +42 -0
  127. package/dist/packages/stats/reportHtml.js +301 -0
  128. package/dist/packages/stats/scatter.d.ts +24 -0
  129. package/dist/packages/stats/scatter.js +1 -0
  130. package/dist/packages/stats/types.d.ts +269 -0
  131. package/dist/packages/stats/types.js +1 -0
  132. package/dist/packages/stats/yield.d.ts +56 -0
  133. package/dist/packages/stats/yield.js +1 -0
  134. package/dist/packages/worker/index.d.ts +44 -0
  135. package/dist/packages/worker/index.js +1 -0
  136. package/dist/packages/worker/wafermap.worker.d.ts +44 -0
  137. package/dist/packages/worker/wafermap.worker.js +1 -0
  138. package/package.json +122 -0
@@ -0,0 +1 @@
1
+ export*from"./types.js";export*from"./regions.js";export*from"./analyzeWaferMap.js";export*from"./analyzeWaferLot.js";export*from"./filterFindings.js";export{classifyPattern}from"./patternClassification.js";export{renderFindingsReportHtml,openHtmlReport,setReportOpener}from"./renderFindingsReport.js";export{renderSummaryReportHtml,renderLotSummaryReportHtml}from"./renderSummaryReport.js";export{buildFacetTable,facetValueOf,FACET_NONE_VALUE,DEFAULT_FACET_CURATION}from"./facets.js";export{buildCapabilityData}from"./capability.js";export{buildCorrelationMatrix,filterCorrelationMatrix}from"./correlation.js";export{buildTestBoxplotData}from"./boxplot.js";export{buildTestHistogramData,buildTestHistogramSeries}from"./histogram.js";export{buildScatterData,buildScatterDataGrouped}from"./scatter.js";export{buildYieldData,buildYieldDataCombined}from"./yield.js";export{buildBinParetoData,buildBinClusterData}from"./binPareto.js";
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Abramowitz & Stegun 7.1.26 rational approximation of the error function.
3
+ * Max absolute error ~1.5e-7 — ample for p-value gating.
4
+ */
5
+ export declare function errorFunction(value: number): number;
6
+ /** Standard normal cumulative distribution function Φ(value). */
7
+ export declare function normalCdf(value: number): number;
8
+ /** Linear-interpolation quantile of a pre-sorted array (`q` in [0, 1]). */
9
+ export declare function quantile(sorted: number[], q: number): number;
10
+ //# sourceMappingURL=math.d.ts.map
@@ -0,0 +1 @@
1
+ export function errorFunction(n){const a=n<0?-1:1,t=Math.abs(n),o=.254829592,r=-.284496736,s=1.421413741,i=-1.453152027,e=1.061405429,c=1/(1+.3275911*t),h=1-((((e*c+i)*c+s)*c+r)*c+o)*c*Math.exp(-t*t);return a*h}export function normalCdf(n){return .5*(1+errorFunction(n/Math.sqrt(2)))}export function quantile(n,a){if(n.length===0)return NaN;const t=a*(n.length-1),o=Math.floor(t),r=Math.ceil(t);return n[o]+(n[r]-n[o])*(t-o)}
@@ -0,0 +1,107 @@
1
+ import type { Die, Wafer } from '../core/index.js';
2
+ export type PatternLabel = 'center' | 'donut' | 'edge-ring' | 'edge-local' | 'scratch' | 'near-full' | 'random' | 'none';
3
+ export interface PatternFeatures {
4
+ /** Ratio of failing dies to total eligible dies (whole-wafer). */
5
+ globalRdd: number;
6
+ /** Ratio of failing dies in the outermost ring to total outermost-ring dies (whole-wafer). */
7
+ edgeRdd: number;
8
+ /** Distance from wafer centre to centroid of the salient region, normalised by wafer radius. */
9
+ centroidDistNorm: number;
10
+ /** Minimum distance from wafer centre among salient-region dies, normalised by radius. */
11
+ minDistNorm: number;
12
+ /** Maximum distance from wafer centre among salient-region dies, normalised by radius. */
13
+ maxDistNorm: number;
14
+ /**
15
+ * 25th-percentile radial distance among ALL failing dies, normalised by radius.
16
+ * Used for donut detection: a true donut has most fails away from the centre
17
+ * even when the salient component incidentally touches the centre.
18
+ */
19
+ p25DistNorm: number;
20
+ /**
21
+ * Ratio of failure density in the inner half (r < 0.5) vs outer half (r >= 0.5).
22
+ * Center: inner > outer (ratio > 1). Donut: outer > inner (ratio < 1).
23
+ */
24
+ innerOuterRatio: number;
25
+ /**
26
+ * Eccentricity of the bounding ellipse around the salient region (0 = circle, 1 = line).
27
+ * Derived from the 2nd-order central moments of the salient-region die positions.
28
+ */
29
+ eccentricity: number;
30
+ /**
31
+ * Linear score: fraction of salient-region dies in the single best collinear run
32
+ * (along row or column), normalised to [0, 1].
33
+ */
34
+ linearScore: number;
35
+ /** Number of dies in the most salient (largest) connected component of failing dies. */
36
+ salienceSize: number;
37
+ /** Fraction of all failing dies that belong to the salient region. */
38
+ salienceFraction: number;
39
+ /**
40
+ * Fraction of the full circumference (0–1) covered by edge-zone failing dies,
41
+ * measured in 16 angular sectors. 1.0 = all sectors occupied (full ring),
42
+ * low value = concentrated arc (edge-local).
43
+ */
44
+ edgeAngularSpread: number;
45
+ }
46
+ export interface PatternClassification {
47
+ pattern: PatternLabel;
48
+ confidence: 'high' | 'medium' | 'low';
49
+ features: PatternFeatures;
50
+ /** Human-readable note about classification uncertainty, when applicable. */
51
+ note?: string;
52
+ }
53
+ export interface PatternThresholds {
54
+ nearFullGlobalRdd: number;
55
+ edgeRingEdgeRdd: number;
56
+ edgeRingEdgeRddHigh: number;
57
+ edgeRingRadialSpread: number;
58
+ /** Minimum angular spread (0–1) for edge-ring vs edge-local classification. */
59
+ edgeRingAngularSpread: number;
60
+ edgeLocalEdgeRdd: number;
61
+ /** p25DistNorm separator: ≥ this → edge-ring; [0.55, this) → edge-local. */
62
+ edgeLocalCentroidDist: number;
63
+ /** p25DistNorm separator: &lt; this → center; [this, 0.55) → donut. */
64
+ donutP25Dist: number;
65
+ centerCentroidDist: number;
66
+ centerCentroidDistHigh: number;
67
+ centerEdgeRdd: number;
68
+ donutEdgeRdd: number;
69
+ scratchLinearScore: number;
70
+ scratchLinearScoreHigh: number;
71
+ scratchEccentricity: number;
72
+ minimumFailingDies: number;
73
+ }
74
+ /**
75
+ * Default thresholds for the spatial pattern classifier.
76
+ *
77
+ * Calibrated against WM-811K — 25,519 labelled real-world wafers from TSMC
78
+ * 300mm fabrication (Wu et al. 2015). Benchmark results on that dataset:
79
+ *
80
+ * | Pattern | Recall | Notes |
81
+ * |------------|--------|--------------------------------------------|
82
+ * | Near-full | 100% | |
83
+ * | Edge-ring | 74% | |
84
+ * | Edge-local | 65% | |
85
+ * | Center | 60% | |
86
+ * | Random | 59% | |
87
+ * | Scratch | 26% | Fragmented patterns harder to detect |
88
+ * | Donut | 15% | Geometrically similar to center with noise |
89
+ *
90
+ * Overall accuracy: 64% exact match, 86% detection rate (any pattern flagged).
91
+ */
92
+ export declare const DEFAULT_PATTERN_THRESHOLDS: PatternThresholds;
93
+ /**
94
+ * Classify the spatial failure pattern of a wafer from its die data.
95
+ *
96
+ * Uses connected-component analysis to identify the most salient (largest)
97
+ * failing region, then computes shape and position features on that region.
98
+ * Global statistics (RDD, edge RDD) are computed over all failing dies.
99
+ *
100
+ * Returns `null` when the failing-die count is below the minimum threshold
101
+ * (`thresholds.minimumFailingDies`, default 5) — too few failures to classify.
102
+ */
103
+ export declare function classifyPattern(dies: Die[], wafer: Wafer, options: {
104
+ passBins: number[];
105
+ ringCount?: number;
106
+ }): PatternClassification | null;
107
+ //# sourceMappingURL=patternClassification.d.ts.map
@@ -0,0 +1 @@
1
+ import{getDieKey as F}from"../core/dies.js";import{clamp01 as j}from"../core/utils.js";export const DEFAULT_PATTERN_THRESHOLDS={nearFullGlobalRdd:.6,edgeRingEdgeRdd:.18,edgeRingEdgeRddHigh:.3,edgeRingRadialSpread:.45,edgeRingAngularSpread:.6,edgeLocalEdgeRdd:.1,edgeLocalCentroidDist:.76,donutP25Dist:.4,centerCentroidDist:.22,centerCentroidDistHigh:.1,centerEdgeRdd:.35,donutEdgeRdd:.22,scratchLinearScore:.25,scratchLinearScoreHigh:.45,scratchEccentricity:.75,minimumFailingDies:5};function J(t){if(t.length===0)return[];const e=new Map;for(const s of t)e.set(F(s),s);const c=new Set,i=[];for(const s of t){const d=F(s);if(c.has(d))continue;const r=[],n=[s];for(c.add(d);n.length>0;){const a=n.pop();r.push(a);for(let g=-1;g<=1;g++)for(let m=-1;m<=1;m++){if(g===0&&m===0)continue;const u=`${a.x+g},${a.y+m}`;!c.has(u)&&e.has(u)&&(c.add(u),n.push(e.get(u)))}}i.push(r)}return i}function O(t,e,c){const i=t.physX-e.center.x,s=t.physY-e.center.y,d=Math.sqrt(i*i+s*s)/e.radius;return Math.min(c,Math.max(1,Math.floor(d*c)+1))===c}function Q(t,e,c){if(t.length<3)return 0;let i=0,s=0,d=0;for(const p of t){const l=p.physX-e,D=p.physY-c;i+=l*l,s+=D*D,d+=l*D}i/=t.length,s/=t.length,d/=t.length;const r=i+s,n=i*s-d*d,a=Math.max(0,(r/2)**2-n),g=r/2+Math.sqrt(a),m=r/2-Math.sqrt(a);if(g<=0)return 0;const u=j(m/g);return Math.sqrt(1-u)}function V(t,e){if(e===0)return 0;const c=new Map,i=new Map,s=new Map,d=new Map;for(const n of t)c.set(n.y,(c.get(n.y)??0)+1),i.set(n.x,(i.get(n.x)??0)+1),s.set(n.x-n.y,(s.get(n.x-n.y)??0)+1),d.set(n.x+n.y,(d.get(n.x+n.y)??0)+1);let r=0;for(const n of c.values())n>r&&(r=n);for(const n of i.values())n>r&&(r=n);for(const n of s.values())n>r&&(r=n);for(const n of d.values())n>r&&(r=n);return r/e}function W(t,e,c,i){const s=e.length,d=t.length,r=s>0?d/s:0,n=e.filter(o=>O(o,c,i)),a=t.filter(o=>O(o,c,i)),g=n.length>0?a.length/n.length:0,m=J(t),u=m.length>0?[...m].sort((o,h)=>h.length-o.length):[t],p=u[0],l=u.slice(0,5).flat(),D=p.length,H=d>0?D/d:0,f=c.center.x,y=c.center.y,R=c.radius;let E=0,N=0,M=1/0,x=0;for(const o of p){E+=o.physX,N+=o.physY;const h=Math.sqrt((o.physX-f)**2+(o.physY-y)**2);h<M&&(M=h),h>x&&(x=h)}const P=E/p.length,T=N/p.length,w=Math.sqrt((P-f)**2+(T-y)**2)/R,I=M===1/0?0:M/R,v=Math.min(x/R,1),C=t.map(o=>Math.sqrt((o.physX-f)**2+(o.physY-y)**2)/R).sort((o,h)=>o-h),k=C[Math.floor(C.length*.25)]??0,b=e.filter(o=>Math.sqrt((o.physX-f)**2+(o.physY-y)**2)/R<.5).length,X=e.filter(o=>Math.sqrt((o.physX-f)**2+(o.physY-y)**2)/R>=.5).length,z=t.filter(o=>Math.sqrt((o.physX-f)**2+(o.physY-y)**2)/R<.5).length,A=t.filter(o=>Math.sqrt((o.physX-f)**2+(o.physY-y)**2)/R>=.5).length,Y=b>0?z/b:0,q=X>0?A/X:0,_=q>0?Y/q:Y>0?2:1,G=l.reduce((o,h)=>o+h.physX,0)/l.length,K=l.reduce((o,h)=>o+h.physY,0)/l.length,U=Q(l,G,K),$=V(l,l.length),S=16,L=new Set;for(const o of a){const h=(Math.atan2(o.physY-y,o.physX-f)+2*Math.PI)%(2*Math.PI);L.add(Math.floor(h/(2*Math.PI)*S)%S)}const B=a.length>0?L.size/S:0;return{globalRdd:r,edgeRdd:g,centroidDistNorm:w,minDistNorm:I,maxDistNorm:v,p25DistNorm:k,eccentricity:U,linearScore:$,salienceSize:D,salienceFraction:H,edgeAngularSpread:B,innerOuterRatio:_}}function Z(t,e,c){const i=Math.max(5,Math.round(c*.003)),s=t.salienceSize>=i;if(t.globalRdd<e.nearFullGlobalRdd&&t.salienceFraction<.1)return{pattern:"random",confidence:"low"};if(t.globalRdd>=e.nearFullGlobalRdd)return{pattern:"near-full",confidence:t.globalRdd>=.8?"high":"medium"};if(s&&t.linearScore>=e.scratchLinearScore&&t.eccentricity>=e.scratchEccentricity){const a=t.linearScore>=e.scratchLinearScoreHigh?"high":"medium";return{pattern:"scratch",confidence:a,note:a==="medium"?"Scratch detection is less reliable for fragmented or diagonal patterns":void 0}}const d=s&&t.edgeRdd>=e.edgeLocalEdgeRdd&&t.maxDistNorm>=.7;if(d&&t.p25DistNorm>=e.edgeLocalCentroidDist&&t.maxDistNorm-t.minDistNorm<=e.edgeRingRadialSpread)return{pattern:"edge-ring",confidence:t.edgeRdd>=e.edgeRingEdgeRddHigh?"high":"medium"};if(d&&t.p25DistNorm>=.55&&t.p25DistNorm<e.edgeLocalCentroidDist)return{pattern:"edge-local",confidence:t.edgeRdd>=e.edgeRingEdgeRdd?"medium":"low"};const r=s&&t.centroidDistNorm<=e.centerCentroidDist&&t.edgeRdd<=e.centerEdgeRdd,n="Center and donut patterns have similar geometry; classification may be imprecise";if(r&&t.p25DistNorm<e.donutP25Dist){const a=t.centroidDistNorm<=e.centerCentroidDistHigh?"high":"medium";return{pattern:"center",confidence:a,note:a!=="high"?n:void 0}}return r&&t.p25DistNorm>=e.donutP25Dist&&t.p25DistNorm<.55?{pattern:"donut",confidence:"medium",note:n}:{pattern:"random",confidence:"low"}}export function classifyPattern(t,e,c){const i={...DEFAULT_PATTERN_THRESHOLDS},s=c.ringCount??4,d=new Set(c.passBins),r=t.filter(p=>{const l=p.hbin??p.sbin;return l!==void 0&&!d.has(l)}),n=Math.max(i.minimumFailingDies,Math.round(t.length*.003));if(r.length<n)return null;const a=W(r,t,e,s),{pattern:g,confidence:m,note:u}=Z(a,i,t.length);return{pattern:g,confidence:m,features:a,...u?{note:u}:{}}}
@@ -0,0 +1,57 @@
1
+ import { type Die, type Wafer } from '../core/index.js';
2
+ import type { ReticleConfig } from '../renderer/buildWaferMap.js';
3
+ export interface StatsRegion {
4
+ family: 'ring' | 'quadrant' | 'reticle-position' | 'test-site' | 'sector';
5
+ key: string;
6
+ label: string;
7
+ dieKeys: string[];
8
+ }
9
+ export interface RegionYieldDatum {
10
+ /** Region identity, e.g. `ring:2` or `quadrant:NE` — parse with `parseRegionKey`. */
11
+ key: string;
12
+ label: string;
13
+ /** `(pass / n) × 100`, in [0, 100]. Only present for regions with n > 0 (see filter below). */
14
+ yieldPercent: number;
15
+ /** Yield-eligible, binned die count in this region. */
16
+ n: number;
17
+ }
18
+ /**
19
+ * Pass/fail yield per region (ring, quadrant, or any other `StatsRegion`
20
+ * family), pooled across one or more wafers. Single source of truth for this
21
+ * computation — consumed by both the summary panel's progress-bar rows and
22
+ * the ring/quadrant yield diagrams, which previously each recomputed the
23
+ * same pass/total tally independently.
24
+ */
25
+ export declare function buildRegionYieldData(diesByWafer: Die[][], allWafers: Wafer[], ringCount: number, passBins: number[], regionBuilder: (dies: Die[], wafer: Wafer, ringCount: number) => StatsRegion[]): RegionYieldDatum[];
26
+ /** Compass bearing names for a given sector count, ordered CCW from East. */
27
+ export declare function sectorCompassNames(sectorCount: number): string[];
28
+ /** True when two quadrants share an edge (NE–NW, NE–SE, NW–SW, SE–SW); diagonals are not adjacent. */
29
+ export declare function areQuadrantsAdjacent(a: string, b: string): boolean;
30
+ export interface ParsedRegionKey {
31
+ family: StatsRegion['family'] | 'unknown';
32
+ ring?: number;
33
+ quadrant?: string;
34
+ sector?: string;
35
+ }
36
+ /**
37
+ * Parse a region key (e.g. `ring:2`, `quadrant:NE`, `sector:NNE`) into its
38
+ * structured parts. Always parse identity from the key, never from the label.
39
+ */
40
+ export declare function parseRegionKey(key: string): ParsedRegionKey;
41
+ export declare function buildRingRegions(dies: Die[], wafer: Wafer, ringCount: number): StatsRegion[];
42
+ export declare function buildQuadrantRegions(dies: Die[], wafer: Wafer, ringCount: number): StatsRegion[];
43
+ export declare function buildReticlePositionRegions(dies: Die[], reticleConfig: ReticleConfig | undefined): StatsRegion[];
44
+ /**
45
+ * Group dies by siteNum for parallel test site analysis.
46
+ *
47
+ * Returns an empty array (suppressing analysis) unless at least 2 distinct
48
+ * siteNum values each appear on MIN_DIES_PER_SITE or more dies — this prevents
49
+ * spurious regions when siteNum is used as a monotonically-increasing counter
50
+ * rather than a true parallel-site identifier.
51
+ *
52
+ * Pass `forceEnable: true` to bypass the guard (e.g. when the caller has already
53
+ * validated the data).
54
+ */
55
+ export declare function buildTestSiteRegions(dies: Die[], forceEnable?: boolean): StatsRegion[];
56
+ export declare function buildSectorRegions(dies: Die[], wafer: Wafer, sectorCount: number): StatsRegion[];
57
+ //# sourceMappingURL=regions.d.ts.map
@@ -0,0 +1 @@
1
+ import{classifyDie as E,getReticleCell as W,getRingLabel as b,getDieKey as M}from"../core/index.js";function y(t){return M(t)}export function buildRegionYieldData(t,s,o,e,n){const i=new Set(e),r=new Map,a=[];for(let l=0;l<s.length;l++){const c=t[l];if(!c?.length)continue;const d=n(c,s[l],o),S=new Map(c.map(u=>[y(u),u]));for(const u of d){a.includes(u.key)||a.push(u.key);const g=r.get(u.key)??{label:u.label,pass:0,total:0};for(const h of u.dieKeys){const f=S.get(h);if(!f||f.partial||f.edgeExcluded)continue;const p=f.hbin??f.sbin;p!=null&&(g.total++,i.has(p)&&g.pass++)}r.set(u.key,g)}}return a.map(l=>{const c=r.get(l);return c.total===0?null:{key:l,label:c.label,n:c.total,yieldPercent:c.pass/c.total*100}}).filter(l=>l!==null)}const k=["E","ENE","NE","NNE","N","NNW","NW","WNW","W","WSW","SW","SSW","S","SSE","SE","ESE"],x=["E","NE","N","NW","W","SW","S","SE"],R=["E","N","W","S"];export function sectorCompassNames(t){const s=[4,8,16,32].includes(t)?t:16;return s===4?R:s===8?x:k}const K={NE:["NW","SE"],NW:["NE","SW"],SE:["NE","SW"],SW:["NW","SE"]};export function areQuadrantsAdjacent(t,s){return K[t]?.includes(s)??!1}export function parseRegionKey(t){return t.startsWith("ring:")?{family:"ring",ring:Number(t.slice(5))}:t.startsWith("quadrant:")?{family:"quadrant",quadrant:t.slice(9)}:t.startsWith("sector:")?{family:"sector",sector:t.slice(7)}:{family:"unknown"}}export function buildRingRegions(t,s,o){const e=new Map;for(const n of t){const{ring:i}=E(n,s,{ringCount:o}),r=`ring:${i}`,a=e.get(r)??{family:"ring",key:r,label:b(i,o),dieKeys:[]};a.dieKeys.push(y(n)),e.set(r,a)}return[...e.values()].sort((n,i)=>n.key.localeCompare(i.key))}export function buildQuadrantRegions(t,s,o){const e=new Map;for(const i of t){const{quadrant:r}=E(i,s,{ringCount:o}),a=`quadrant:${r}`,l=e.get(a)??{family:"quadrant",key:a,label:r,dieKeys:[]};l.dieKeys.push(y(i)),e.set(a,l)}const n=new Map([["quadrant:NE",0],["quadrant:NW",1],["quadrant:SE",2],["quadrant:SW",3]]);return[...e.values()].sort((i,r)=>(n.get(i.key)??4)-(n.get(r.key)??4))}export function buildReticlePositionRegions(t,s){if(!s)return[];const o=new Map;for(const e of t){const{column:n,row:i}=W(e,s),r=`reticle-position:cell:${n},${i}`,a=o.get(r)??{family:"reticle-position",key:r,label:`Reticle cell (${n}, ${i})`,dieKeys:[]};a.dieKeys.push(y(e)),o.set(r,a)}return[...o.values()].sort((e,n)=>e.key.localeCompare(n.key))}const q=3;export function buildTestSiteRegions(t,s=!1){const o=new Map;for(const e of t){if(e.siteNum===void 0)continue;const n=o.get(e.siteNum)??[];n.push(y(e)),o.set(e.siteNum,n)}if(!s){let e=0;for(const n of o.values())n.length>=q&&e++;if(e<2)return[]}return[...o.entries()].sort(([e],[n])=>e-n).map(([e,n])=>({family:"test-site",key:`test-site:${e}`,label:`Site ${e}`,dieKeys:n}))}export function buildSectorRegions(t,s,o){const e=[4,8,16,32].includes(o)?o:16,n=sectorCompassNames(e),i=new Map,r=s.center.x,a=s.center.y,l=s.radius;for(const c of t){const d=c.physX-r,S=c.physY-a;if(Math.hypot(d,S)/l<.2)continue;const g=Math.PI/e,h=(Math.atan2(S,d)+2*Math.PI+g)%(2*Math.PI),f=Math.floor(h/(2*Math.PI)*e)%e,p=n[f],N=`sector:${p}`,m=i.get(N)??{family:"sector",key:N,label:`Sector ${p}`,dieKeys:[]};m.dieKeys.push(y(c)),i.set(N,m)}return[...i.values()].sort((c,d)=>c.key.localeCompare(d.key))}
@@ -0,0 +1,7 @@
1
+ import type { StatsSummary, LotStatsSummary } from './types.js';
2
+ export declare function renderFindingsReportHtml(summary: StatsSummary | LotStatsSummary, options?: {
3
+ title?: string;
4
+ }): string;
5
+ export declare function openHtmlReport(html: string): void;
6
+ export declare function setReportOpener(opener: (html: string) => void): void;
7
+ //# sourceMappingURL=renderFindingsReport.d.ts.map
@@ -0,0 +1,41 @@
1
+ import{formatFindingDelta as g,formatFindingCoverage as h,formatFindingTooltip as b,escHtml as a,buildMetadataRows as d,renderDefinitionList as c,renderSection as p,renderSeverityBadge as w,reportStyles as v}from"./reportHtml.js";import{buildFindingsNarrative as y}from"./findingsNarrative.js";import{plainBinTerms as f}from"../renderer/fmt.js";function $(t,n){if(t.level==="lot"){const o=t,l=o.perWafer.map(r=>r.summary.wafer?.wafer??r.summary.wafer?.waferId).filter(r=>r!=null).map(String).join(", ");return c([...d(o.perWafer.map(r=>({metadata:r.summary.wafer}))),...l?[{label:"Wafers",value:l}]:[],{label:"Wafer count",value:String(o.stats.waferCount)},{label:"Generated",value:n}])}const e=t,i=e.stats.yieldPercent!==null?`${e.stats.yieldPercent.toFixed(1)}%`:"N/A";return c([...d([{metadata:e.wafer}]),{label:"Total dies",value:String(e.stats.totalDies)},{label:"Analysed dies",value:String(e.stats.analyzedDies)},{label:"Yield",value:i},{label:"Generated",value:n}])}function R(t,n){return t.length?t.map(e=>`<tr title="${a(b(e))}">
2
+ <td class="tight">${w(e.severity)}</td>
3
+ <td class="tight">${a(e.comparison.left)}</td>
4
+ <td>${a(f(e.variable.label))}</td>
5
+ <td class="numeric">${a(g(e))}</td>
6
+ <td class="numeric">${a(h(e,n))}</td>
7
+ </tr>`).join(`
8
+ `):'<tr><td colspan="5" class="no-data">No significant findings</td></tr>'}function F(t,n){return`<table class="report-table findings-table compact">
9
+ <thead>
10
+ <tr>
11
+ <th>Severity</th>
12
+ <th>Region</th>
13
+ <th>Metric</th>
14
+ <th class="numeric">Delta</th>
15
+ <th class="numeric">${n!==void 0?"Wafers":"N (region/rest)"}</th>
16
+ </tr>
17
+ </thead>
18
+ <tbody>
19
+ ${R(t,n)}
20
+ </tbody>
21
+ </table>`}export function renderFindingsReportHtml(t,n={}){const e=t.level==="lot",i=n.title??(e?"Lot Findings Report":"Wafer Findings Report"),o=new Date().toLocaleString(),l=t.findings,r=t.level==="lot"?t.stats.waferCount:void 0,s=f(y(l)??""),u=s?`<p class="findings-narrative">${a(s)}</p>
22
+ `:"",m=[p("Summary",$(t,o)),p("Findings",u+F(l,r))].join(`
23
+ `);return`<!DOCTYPE html>
24
+ <html lang="en">
25
+ <head>
26
+ <meta charset="UTF-8">
27
+ <title>${a(i)}</title>
28
+ <style>
29
+ ${v()}
30
+ </style>
31
+ </head>
32
+ <body>
33
+ <main class="report">
34
+ <header class="report-header">
35
+ <h1>${a(i)}</h1>
36
+ <p class="report-subtitle">Generated ${a(o)}</p>
37
+ </header>
38
+ ${m}
39
+ </main>
40
+ </body>
41
+ </html>`}export function openHtmlReport(t){if(typeof window.__openHtmlReport=="function"){window.__openHtmlReport(t);return}const n=window.open("","_blank");n&&(n.document.write(t),n.document.close())}export function setReportOpener(t){window.__openHtmlReport=t}
@@ -0,0 +1,60 @@
1
+ import type { Die } from '../core/dies.js';
2
+ import type { Wafer } from '../core/wafer.js';
3
+ import { type BinDef, type TestDef, type YieldSummary } from '../renderer/buildWaferMap.js';
4
+ import type { StatsSummary, AnalyzeWaferMapOptions } from './types.js';
5
+ import { openHtmlReport } from './renderFindingsReport.js';
6
+ export interface SummaryReportParams {
7
+ wafer: Wafer;
8
+ dies: Die[];
9
+ yieldSummary: YieldSummary;
10
+ dataCoverage: {
11
+ filledDies: number;
12
+ totalDies: number;
13
+ edgeExcludedDies: number;
14
+ ratio: number;
15
+ };
16
+ hbinDefs?: BinDef[];
17
+ sbinDefs?: BinDef[];
18
+ testDefs?: TestDef[];
19
+ statsSummary?: StatsSummary;
20
+ passBins?: number[];
21
+ ringCount?: number;
22
+ }
23
+ /** Generate a full wafer summary as a standalone HTML string suitable for `openHtmlReport()`. */
24
+ export declare function renderSummaryReportHtml(params: SummaryReportParams, options?: {
25
+ title?: string;
26
+ }): string;
27
+ export interface LotSummaryReportParams {
28
+ /** One entry per wafer/item — grouping, per-group analysis (`analyzeWaferLot`),
29
+ * and rendering all happen internally; callers never pre-compute a lotSummary
30
+ * or pre-partition by lot identity themselves. */
31
+ items: Array<{
32
+ label: string;
33
+ wafer?: Wafer;
34
+ dies?: Die[];
35
+ /** Reused directly as `analyzeWaferLot`'s `perWaferSummaries` — the expensive
36
+ * per-wafer pass (`analyzeWaferMap`) is never re-run here. */
37
+ statsSummary?: StatsSummary;
38
+ }>;
39
+ hbinDefs?: BinDef[];
40
+ sbinDefs?: BinDef[];
41
+ testDefs?: TestDef[];
42
+ passBins?: number[];
43
+ ringCount?: number;
44
+ /** Passthrough to the internal per-group `analyzeWaferLot` call, e.g. `{ enableTestValueAnalysis: true }`. */
45
+ analyzeOptions?: AnalyzeWaferMapOptions;
46
+ }
47
+ /**
48
+ * Generate a full lot summary as a standalone HTML string suitable for
49
+ * `openHtmlReport()`. Grouping, per-group analysis, and rendering all
50
+ * happen internally — see `groupByIdentity` — so a caller never needs to
51
+ * pre-partition a multi-lot/multi-product/multi-temperature load itself:
52
+ * a single call always returns one complete document, whether that's one
53
+ * `<main>` (the common case) or several side by side with a banner
54
+ * explaining the split.
55
+ */
56
+ export declare function renderLotSummaryReportHtml(params: LotSummaryReportParams, options?: {
57
+ title?: string;
58
+ }): string;
59
+ export { openHtmlReport };
60
+ //# sourceMappingURL=renderSummaryReport.d.ts.map
@@ -0,0 +1,68 @@
1
+ import{isParametricTest as rt}from"../renderer/buildWaferMap.js";import{buildRingRegions as H,buildQuadrantRegions as L}from"./regions.js";import{openHtmlReport as ct}from"./renderFindingsReport.js";import{analyzeWaferLot as dt}from"./analyzeWaferLot.js";import{computeFunctionalYield as ut}from"./analyzeWaferMap.js";import{buildCapabilityData as ft}from"./capability.js";import{fmt as B}from"../renderer/fmt.js";import{getDieKey as W}from"../core/dies.js";import{formatFindingDelta as mt,formatFindingCoverage as pt,formatFindingTooltip as gt,escHtml as h,renderMetadataSection as k,renderMetricGrid as N,renderSection as $,renderSeverityBadge as ht,renderTable as D,reportStyles as E}from"./reportHtml.js";function P(o,r){return r===0?"\u2014":`${(o/r*100).toFixed(1)}%`}function bt(o){if(!o)return"";const r=o.lot??o.lotId,i=o.wafer??o.waferId,e=[r,i].filter(Boolean).map(String);return e.length?` \u2014 ${e.map(h).join(" \xB7 ")}`:""}function Et(o,r){const i=[{label:"Total dies",value:String(r.totalDies)},{label:"Filled dies",value:String(r.filledDies),hint:`${P(r.filledDies,r.totalDies)} fill`},{label:"Pass dies",value:String(o.passDies)},{label:"Fail dies",value:String(o.failDies)},...o.edgeExcludedDies>0?[{label:"Edge excluded",value:String(o.edgeExcludedDies)}]:[],...o.partialDies>0?[{label:"Partial dies",value:String(o.partialDies)}]:[],{label:"Yield",value:o.yieldPercent!==null?`${o.yieldPercent.toFixed(1)}%`:"N/A"}];return $("Yield",N(i))}function I(o,r,i,e){const l=new Map;if(e)for(const[n,s]of Object.entries(e))l.set(Number(n),s);else for(const n of o){if(n.partial||n.edgeExcluded)continue;const s=i==="hard"?n.hbin:n.sbin;s!=null&&l.set(s,(l.get(s)??0)+1)}if(!l.size)return"";const t=[...l.values()].reduce((n,s)=>n+s,0),a=r?new Map(r.map(n=>[n.bin,n])):null,c=[...l.entries()].sort((n,s)=>n[0]-s[0]).map(([n,s])=>{const p=a?.get(n)?.name??"\u2014";return[String(n),p,String(s),P(s,t)]});return $(i==="hard"?"Hard Bin Breakdown":"Soft Bin Breakdown",D(["Bin","Name","Count","%"],c,{className:"compact"}))}function j(o,r,i,e){const l=new Set(e),t=new Map(i.map(d=>[W(d),d]));if(!i.some(d=>d.hbin!=null||d.sbin!=null)||!r.length)return"";const c=[];for(const d of r){let n=0,s=0;for(const m of d.dieKeys){const p=t.get(m);if(!p||p.partial||p.edgeExcluded)continue;const g=p.hbin??p.sbin;g!=null&&(s++,l.has(g)&&n++)}s&&c.push([d.label,String(n),String(s),P(n,s)])}return c.length?$(o,D(["Region","Pass","Total","Yield"],c,{className:"compact"})):""}function z(o,r,i){if(!r.length)return"";const e=o.filter(a=>!a.partial&&!a.edgeExcluded),l=new Map((i??[]).map(a=>[a.testNumber,a])),t=[];for(const a of r.filter(rt)){const c=a.testNumber,d=a.unit||void 0,n=l.get(c);let s,m,p;if(n)({min:s,max:m,mean:p}=n);else{const g=e.map(b=>b.testValues?.[c]).filter(b=>b!==void 0&&isFinite(b));if(!g.length)continue;g.sort((b,u)=>b-u),s=g[0],m=g[g.length-1],p=g.reduce((b,u)=>b+u,0)/g.length}t.push([h(a.name),B(s,d),B(p,d),B(m,d)])}return t.length?$("Test Values",D(["Test","Min","Mean","Max"],t,{className:"compact"})):""}function A(o,r,i){const e=i??ut(o,r);if(!e?.length)return"";const l=e.map(t=>[h(t.label),String(t.totalDies),String(t.passDies),String(t.failDies),t.passRatePercent!==null?`${t.passRatePercent.toFixed(1)}%`:"\u2014"]);return $("Functional Tests",D(["Test","N","Pass","Fail","Pass Rate"],l,{className:"compact"}))}function G(o,r){const i=ft(o,r);if(!i.length)return"";const e=t=>t===null?"\u2014":t.toFixed(2),l=i.map(t=>[h(t.label),t.hasSpec?`${B(t.lsl,t.unit)} \u2013 ${B(t.usl,t.unit)}`:"\u2014",B(t.mean,t.unit),e(t.cp),e(t.cpk),e(t.pp),e(t.ppk)]);return $("Process Capability",D(["Test","Spec (LSL\u2013USL)","Mean","Cp","Cpk","Pp","Ppk"],l,{className:"compact"}))}function O(o,r){if(!o.length)return"";const i=o.map(t=>`<tr title="${h(gt(t))}">
2
+ <td class="tight">${ht(t.severity)}</td>
3
+ <td class="tight">${h(t.comparison.left)}</td>
4
+ <td>${h(t.variable.label)}</td>
5
+ <td class="numeric">${h(mt(t))}</td>
6
+ <td class="numeric">${h(pt(t,r))}</td>
7
+ </tr>`).join(`
8
+ `),l=`<table class="report-table findings-table compact"><thead><tr>
9
+ <th>Severity</th><th>Region</th><th>Metric</th><th class="numeric">Delta</th><th class="numeric">${r!==void 0?"Wafers":"N (region/rest)"}</th>
10
+ </tr></thead><tbody>${i}</tbody></table>`;return $("Findings",l)}export function renderSummaryReportHtml(o,r={}){const{wafer:i,dies:e,yieldSummary:l,dataCoverage:t,hbinDefs:a,sbinDefs:c,testDefs:d=[],statsSummary:n,passBins:s=[1],ringCount:m=4}=o,p=i.metadata,g=bt(p),b=r.title??`Wafer Summary${g}`,u=new Date().toLocaleString(),w=e.some(Y=>Y.hbin!=null),x=e.some(Y=>Y.sbin!=null),S=H(e,i,m),y=L(e,i,m),v=[{label:"Total dies",value:String(t.totalDies)},...l.partialDies>0?[{label:"Partial",value:String(l.partialDies)}]:[],...l.yieldPercent!==null?[{label:`Yield (pass: ${s.length===1?`bin ${s[0]}`:`bins ${s.join(", ")}`})`,value:`${l.yieldPercent.toFixed(1)}%`}]:[],...l.edgeExcludedDies>0?[{label:"Edge excluded (outer zone)",value:String(l.edgeExcludedDies)}]:[]],M=[k([{metadata:i.metadata}]),$("Summary",[N(v)].filter(Boolean).join(`
11
+ `)),w?I(e,a,"hard",n?.stats.hardBinCounts):x?I(e,c,"soft",n?.stats.softBinCounts):"",j("Ring Yield",S,e,s),j("Quadrant Yield",y,e,s),z(e,d,n?.stats.perTestStats),A(e,d,n?.stats.functionalYield),G([{dies:e}],d),n?O(n.findings):""].filter(Boolean).join(`
12
+ `);return`<!DOCTYPE html>
13
+ <html lang="en">
14
+ <head>
15
+ <meta charset="UTF-8">
16
+ <title>${h(b)}</title>
17
+ <style>
18
+ ${E()}
19
+ </style>
20
+ </head>
21
+ <body>
22
+ <main class="report">
23
+ <header class="report-header">
24
+ <h1>${h(b)}</h1>
25
+ <p class="report-subtitle">Generated ${h(u)}</p>
26
+ </header>
27
+ ${M}
28
+ <p class="footer">Generated ${h(u)}</p>
29
+ </main>
30
+ </body>
31
+ </html>`}function St(o,r){const i=o.perWafer.map(e=>{const l=r[e.waferIndex]?.label??`W${e.waferIndex+1}`,t=e.summary.stats.yieldPercent;return[l,t!==null?`${t.toFixed(1)}%`:"N/A"]});return D(["Wafer","Yield"],i,{className:"compact"})}function yt(o){const r=o.map(i=>[i.label,i.wafer?.metadata?.split]).filter(i=>!!i[1]).map(([i,e])=>[h(i),h(e)]);return r.length?$("Splits",D(["Wafer","Split"],r,{className:"compact"})):""}function K(o,r,i,e){const l=new Map,t=i==="hard"?"hardBinCounts":"softBinCounts";if(e?.length&&e.every(n=>n.stats[t]!==void 0))for(const n of e)for(const[s,m]of Object.entries(n.stats[t])){const p=Number(s);l.set(p,(l.get(p)??0)+m)}else for(const n of o){if(n.partial||n.edgeExcluded)continue;const s=i==="hard"?n.hbin:n.sbin;s!=null&&l.set(s,(l.get(s)??0)+1)}if(!l.size)return"";const a=[...l.values()].reduce((n,s)=>n+s,0),c=r?new Map(r.map(n=>[n.bin,n])):null,d=[...l.entries()].sort((n,s)=>n[0]-s[0]).map(([n,s])=>{const m=c?.get(n);return[m?.name?`Bin ${n} \xB7 ${m.name} (${s})`:`Bin ${n} (${s})`,P(s,a)]});return $(i==="hard"?"Hard Bin Breakdown (All Wafers)":"Soft Bin Breakdown (All Wafers)",D(["Bin","Yield"],d,{className:"compact"}))}function U(o,r,i,e,l,t){const a=new Set(t),c=new Map,d=[];for(let s=0;s<e.length;s++){const m=e[s],p=i[s]??[];if(!p.length)continue;const g=r(p,m,l),b=new Map(p.map(u=>[W(u),u]));for(const u of g){d.includes(u.label)||d.push(u.label);const w=c.get(u.label)??{pass:0,total:0};for(const x of u.dieKeys){const S=b.get(x);if(!S||S.partial||S.edgeExcluded)continue;const y=S.hbin??S.sbin;y!=null&&(w.total++,a.has(y)&&w.pass++)}c.set(u.label,w)}}if(!c.size)return"";const n=d.filter(s=>c.get(s)?.total).map(s=>{const m=c.get(s);return[s,P(m.pass,m.total)]});return n.length?$(o,D(["Region","Yield"],n,{className:"compact"})):""}function wt(o,r,i){let e;if(i?.length&&i.every(l=>l.stats.perTestStats!==void 0)){const l=new Map;for(const t of i)for(const a of t.stats.perTestStats??[]){const c=l.get(a.testNumber);c?(c.n+=a.count,c.sum+=a.mean*a.count,c.min=Math.min(c.min,a.min),c.max=Math.max(c.max,a.max)):l.set(a.testNumber,{n:a.count,sum:a.mean*a.count,min:a.min,max:a.max})}e=[...l.entries()].map(([t,a])=>({testNumber:t,min:a.min,max:a.max,mean:a.sum/a.n}))}return z(o,r,e)}function $t(o,r,i){let e;if(i?.length&&i.every(l=>l.stats.functionalYield!==void 0)){const l=new Map;for(const t of i)for(const a of t.stats.functionalYield??[]){const c=l.get(a.testNumber)??{label:a.label,passDies:0,failDies:0,totalDies:0};c.passDies+=a.passDies,c.failDies+=a.failDies,c.totalDies+=a.totalDies,l.set(a.testNumber,c)}e=[...l.entries()].map(([t,a])=>({testNumber:t,...a,passRatePercent:a.totalDies>0?a.passDies/a.totalDies*100:null}))}return A(o,r,e?.length?e:void 0)}const R=["lot","product","testProgram","temperature"];function xt(o){const r=new Map(R.map(t=>[t,new Set]));for(const t of o)for(const a of R){const c=t.wafer?.metadata?.[a];c!=null&&c!==""&&r.get(a).add(String(c))}const i=R.filter(t=>r.get(t).size>1);if(i.length===0)return[{label:"",items:o,varying:i}];const e=new Map,l=[];for(const t of o){const a=i.map(c=>String(t.wafer?.metadata?.[c]??"(none)")).join(" \xB7 ");e.has(a)||(e.set(a,[]),l.push(a)),e.get(a).push(t)}return l.map(t=>({label:t,items:e.get(t),varying:i}))}function Q(o,r,i,e,l,t,a){const c=dt(o,{perWaferSummaries:o.map(f=>f.statsSummary),...a}),d=[],n=[],s=[];for(const f of o){const T=f.dies??[];f.wafer&&(d.push(f.wafer),s.push(T)),T.length&&n.push(...T)}const m=n.some(f=>f.hbin!=null),p=n.some(f=>f.sbin!=null),g=m||p,b=c.perWafer.map(f=>f.summary.stats.yieldPercent).filter(f=>f!==null),u=b.length?b.reduce((f,T)=>f+T,0)/b.length:null,w=new Set(l);let x=0,S=0,y=0,v=0,M=0;for(const f of n){if(x++,f.edgeExcluded&&v++,f.partial&&M++,f.partial||f.edgeExcluded)continue;const T=f.hbin??f.sbin;T!=null&&(S++,w.has(T)&&y++)}const Y=S-y,C=S>0?y/S*100:null,V=[{label:"Wafers",value:String(c.stats.waferCount)},{label:"Total dies",value:String(x)},...g?[{label:"Good dies",value:String(y)},{label:"Bad dies",value:String(Y)}]:[],...v>0?[{label:"Edge excluded",value:String(v)}]:[],...M>0?[{label:"Partial dies",value:String(M)}]:[],...u!==null?[{label:"Mean wafer yield",value:`${u.toFixed(1)}%`}]:[],...C!==null?[{label:"Total yield",value:`${C.toFixed(1)}%`}]:[]],_=$("Lot Summary",N(V)),J=k(o.map(f=>({metadata:f.wafer?.metadata}))),X=$("Per-Wafer Yield",St(c,o)),Z=yt(o),F=c.perWafer.map(f=>f.summary),tt=g?m?K(n,r,"hard",F):K(n,i,"soft",F):"",et=g&&d.length?U("Ring Yield (All Wafers)",H,s,d,t,l):"",nt=g&&d.length?U("Quadrant Yield (All Wafers)",L,s,d,t,l):"",at=e.length?wt(n,e,F):"",st=e.length?$t(n,e,F):"",ot=e.length?G(o,e):"",it=c.findings.length?O(c.findings,c.stats.waferCount):"",lt=[_,J,X,Z,tt,et,nt,at,st,ot,it].filter(Boolean).join(`
32
+ `);return{lotSummary:c,sections:lt}}function q(o,r,i){return`<main class="report">
33
+ <header class="report-header">
34
+ <h1>${h(o)}</h1>
35
+ <p class="report-subtitle">Generated ${h(i)}</p>
36
+ </header>
37
+ ${r}
38
+ <p class="footer">Generated ${h(i)}</p>
39
+ </main>`}export function renderLotSummaryReportHtml(o,r={}){const{items:i,hbinDefs:e,sbinDefs:l,testDefs:t=[],passBins:a=[1],ringCount:c=4,analyzeOptions:d}=o,n=new Date().toLocaleString(),s=xt(i);if(s.length===1){const{lotSummary:u,sections:w}=Q(s[0].items,e,l,t,a,c,d),x=(()=>{const y=u.lot;if(!y)return"";const v=y.lot??y.lotId;return v?` \u2014 ${h(String(v))}`:""})(),S=r.title??`Lot Summary${x}`;return`<!DOCTYPE html>
40
+ <html lang="en">
41
+ <head>
42
+ <meta charset="UTF-8">
43
+ <title>${h(S)}</title>
44
+ <style>
45
+ ${E()}
46
+ </style>
47
+ </head>
48
+ <body>
49
+ ${q(S,w,n)}
50
+ </body>
51
+ </html>`}const m=r.title??"Lot Summary",p=s.map(u=>{const{lotSummary:w,sections:x}=Q(u.items,e,l,t,a,c,d),S=u.varying.includes("lot")?void 0:w.lot?.lot??w.lot?.lotId,y=S?`${u.label} \xB7 Lot ${String(S)}`:u.label;return q(`${m} \u2014 ${y}`,x,n)}),g=`<p style="max-width:1080px;margin:0 auto 8px;padding:8px 12px;background:var(--report-surface,#f7f8fa);border:1px solid var(--report-line,#d8dee6);border-radius:4px;font-size:12px;color:var(--report-muted,#5c6570);">This load spans ${s.length} groups by identity \u2014 shown separately below so stats are never pooled across them.</p>`,b=p.map((u,w)=>`${w===0?"":'<hr style="max-width:1080px;margin:32px auto 0;border:none;border-top:3px solid var(--report-line-strong,#c7ced8);">'}
52
+ ${u}`).join(`
53
+ `);return`<!DOCTYPE html>
54
+ <html lang="en">
55
+ <head>
56
+ <meta charset="UTF-8">
57
+ <title>${h(m)}</title>
58
+ <style>
59
+ ${E()}
60
+ </style>
61
+ </head>
62
+ <body>
63
+ <div class="report" style="padding-top:24px;">
64
+ ${g}
65
+ </div>
66
+ ${b}
67
+ </body>
68
+ </html>`}export{ct as openHtmlReport};
@@ -0,0 +1,42 @@
1
+ import type { StatsFinding, StatsSeverity } from './types.js';
2
+ import { type FacetItem } from './facets.js';
3
+ export interface MetricItem {
4
+ label: string;
5
+ value: string;
6
+ hint?: string;
7
+ }
8
+ export declare function escHtml(value: string): string;
9
+ export declare function renderSection(title: string, body: string, className?: string): string;
10
+ export declare function renderDefinitionList(entries: Array<{
11
+ label: string;
12
+ value: string;
13
+ }>, className?: string): string;
14
+ /**
15
+ * The single source of metadata content for every report and panel surface
16
+ * — built from `buildFacetTable` over the actual item(s), never a
17
+ * first-wafer-wins shortcut (e.g. `LotStatsSummary.lot`) or a per-file
18
+ * known-key list, both of which can silently drop a field that varies
19
+ * across the population and can drift from what the live Summary panel
20
+ * shows (`buildMetadataInfoSection` in canvas-adapter/summaryPanel.ts,
21
+ * which calls the same `buildFacetTable`). A population of one wafer is
22
+ * just a facet table where every field has exactly one value, so the
23
+ * wafer- and lot-level reports render identical content for the same
24
+ * underlying metadata — never two independently-derived renderings.
25
+ */
26
+ export declare function buildMetadataRows(items: FacetItem[]): Array<{
27
+ label: string;
28
+ value: string;
29
+ }>;
30
+ /** `buildMetadataRows` wrapped in a titled section. Returns `''` when there's nothing to show. */
31
+ export declare function renderMetadataSection(items: FacetItem[]): string;
32
+ export declare function renderMetricGrid(items: MetricItem[]): string;
33
+ export declare function renderTable(headers: string[], rows: string[][], options?: {
34
+ className?: string;
35
+ emptyMessage?: string;
36
+ }): string;
37
+ export declare function renderSeverityBadge(severity: StatsSeverity): string;
38
+ export declare function formatFindingDelta(finding: Pick<StatsFinding, 'effect' | 'variable'>): string;
39
+ export declare function formatFindingTooltip(finding: StatsFinding): string;
40
+ export declare function formatFindingCoverage(finding: Pick<StatsFinding, 'stats'>, total?: number): string;
41
+ export declare function reportStyles(): string;
42
+ //# sourceMappingURL=reportHtml.d.ts.map