@wafertools/wafermap 0.31.0 → 0.32.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 (65) hide show
  1. package/AGENTS.md +12 -12
  2. package/CHANGELOG.md +172 -0
  3. package/README.md +3 -3
  4. package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
  5. package/dist/packages/canvas-adapter/dieList.js +7 -7
  6. package/dist/packages/canvas-adapter/drilldown.js +1 -1
  7. package/dist/packages/canvas-adapter/insightsTab.js +1 -1
  8. package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
  9. package/dist/packages/canvas-adapter/renderWaferMap.d.ts +17 -4
  10. package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
  11. package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
  12. package/dist/packages/canvas-adapter/toCanvas.d.ts +23 -2
  13. package/dist/packages/canvas-adapter/toCanvas.js +1 -1
  14. package/dist/packages/canvas-adapter/toolbar.js +2 -2
  15. package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
  16. package/dist/packages/canvas-adapter/userGuideHtml.js +25 -15
  17. package/dist/packages/canvas-adapter/version.d.ts +2 -2
  18. package/dist/packages/canvas-adapter/version.js +1 -1
  19. package/dist/packages/core/aggregates.js +1 -1
  20. package/dist/packages/core/dieTable.d.ts +137 -0
  21. package/dist/packages/core/dieTable.js +1 -0
  22. package/dist/packages/core/dies.d.ts +22 -0
  23. package/dist/packages/core/dies.js +1 -1
  24. package/dist/packages/core/utils.d.ts +14 -0
  25. package/dist/packages/core/utils.js +1 -1
  26. package/dist/packages/renderer/buildView.d.ts +3 -3
  27. package/dist/packages/renderer/buildView.js +1 -1
  28. package/dist/packages/renderer/buildWaferMap.d.ts +29 -11
  29. package/dist/packages/renderer/buildWaferMap.js +1 -1
  30. package/dist/packages/renderer/columnarInput.d.ts +87 -0
  31. package/dist/packages/renderer/columnarInput.js +1 -0
  32. package/dist/packages/renderer/derivedTests/apply.d.ts +8 -6
  33. package/dist/packages/renderer/derivedTests/apply.js +1 -1
  34. package/dist/packages/renderer/index.d.ts +1 -0
  35. package/dist/packages/renderer/spec.d.ts +3 -7
  36. package/dist/packages/renderer/spec.js +1 -1
  37. package/dist/packages/stats/analyzeWaferLot.js +1 -1
  38. package/dist/packages/stats/analyzeWaferMap.d.ts +134 -2
  39. package/dist/packages/stats/analyzeWaferMap.js +1 -1
  40. package/dist/packages/stats/boxplot.js +1 -1
  41. package/dist/packages/stats/capability.d.ts +7 -7
  42. package/dist/packages/stats/capability.js +1 -1
  43. package/dist/packages/stats/clusterDetection.js +1 -1
  44. package/dist/packages/stats/connectedComponents.js +1 -1
  45. package/dist/packages/stats/correlation.js +1 -1
  46. package/dist/packages/stats/filterFindings.d.ts +5 -0
  47. package/dist/packages/stats/filterFindings.js +1 -1
  48. package/dist/packages/stats/histogram.js +1 -1
  49. package/dist/packages/stats/math.d.ts +22 -6
  50. package/dist/packages/stats/math.js +1 -1
  51. package/dist/packages/stats/normalizeInput.js +1 -1
  52. package/dist/packages/stats/patternClassification.d.ts +3 -3
  53. package/dist/packages/stats/patternClassification.js +1 -1
  54. package/dist/packages/stats/regions.d.ts +7 -0
  55. package/dist/packages/stats/regions.js +1 -1
  56. package/dist/packages/stats/renderSummaryReport.js +18 -18
  57. package/dist/packages/stats/scatter.js +1 -1
  58. package/dist/packages/stats/sweep.js +1 -1
  59. package/dist/packages/stats/testPassRate.js +1 -1
  60. package/dist/packages/stats/trend.js +1 -1
  61. package/dist/packages/stats/types.d.ts +5 -0
  62. package/dist/packages/worker/index.js +1 -1
  63. package/dist/packages/worker/wafermap.worker.d.ts +3 -0
  64. package/dist/packages/worker/wafermap.worker.js +1 -1
  65. package/package.json +1 -1
@@ -1,10 +1,10 @@
1
- import{waferDisplayLabel as H}from"../core/waferLabel.js";import{binPassSets as we,binPassSetsByWafer as $e,mergeBinDefs as I}from"../renderer/binColors.js";import{itemPassBins as R,passBinsLabel as xe}from"../core/passBins.js";import{isParametricTest as De}from"../renderer/buildWaferMap.js";import{buildRingRegions as j,buildQuadrantRegions as z,buildRegionYieldData as Be}from"./regions.js";import{openHtmlReport as ve}from"./renderFindingsReport.js";import{analyzeWaferLot as Te}from"./analyzeWaferLot.js";import{computeFunctionalYield as Pe,analyzeWaferMap as Fe}from"./analyzeWaferMap.js";import{mergeTestDefs as Ye}from"./mergeTestDefs.js";import{sortBinsForDisplay as A}from"./binPareto.js";import{buildFacetTable as We,facetValueOf as Me,FACET_NONE_VALUE as Ce}from"./facets.js";import{visibleFindings as O}from"./filterFindings.js";import{buildYieldDataCombined as Re}from"./yield.js";import{describeWaferPopulation as G,populationLabel as Ne}from"./population.js";import{buildTestPassRateData as Ee,hasJudgeableTests as Le,poolFunctionalYield as ke}from"./testPassRate.js";import{buildCapabilityData as He}from"./capability.js";import{fmt as T}from"../renderer/fmt.js";import{getDieKey as Ie,isPositionedDie as je,diePassStatus as U}from"../core/dies.js";import{findingsTableHtml as V,renderMetadataSection as _,renderMetricGrid as K,renderSection as x,renderTable as v,reportStyles as N}from"./reportHtml.js";import{escHtml as y}from"../core/utils.js";function C(t,o){return o===0?"\u2014":`${(t/o*100).toFixed(1)}%`}function ze(t){if(!t)return"";const o=t.lot??t.lotId,a=t.wafer??t.waferId,n=[o,a].filter(Boolean).map(String);return n.length?` \u2014 ${n.map(y).join(" \xB7 ")}`:""}function Q(t,o,a,n,r=[1]){const e=new Map;if(n)for(const[s,c]of Object.entries(n))e.set(Number(s),c);else for(const s of t){if(s.partial||s.edgeExcluded)continue;const c=a==="hard"?s.hbin:s.sbin;c!=null&&e.set(c,(e.get(c)??0)+1)}if(!e.size)return"";const i=[...e.values()].reduce((s,c)=>s+c,0),l=o?new Map(o.map(s=>[s.bin,s])):null,d=A(e.entries(),we(t,r)[a]).map(([s,c])=>{const p=l?.get(s)?.name??"\u2014";return[String(s),p,String(c),C(c,i)]});return x(a==="hard"?"Hard Bin Breakdown":"Soft Bin Breakdown",v(["Bin","Name","Count","%"],d,{className:"compact"}))}function q(t,o,a,n){const r=new Set(n),e=new Map(a.map(d=>[Ie(d),d]));if(!a.some(d=>d.hbin!=null||d.sbin!=null)||!o.length)return"";const l=[];for(const d of o){let f=0,s=0;for(const c of d.dieKeys){const h=e.get(c);if(!h||h.partial||h.edgeExcluded)continue;const p=U(h,r);p!==void 0&&(s++,p&&f++)}s&&l.push([d.label,String(f),String(s),C(f,s)])}return l.length?x(t,v(["Region","Pass","Total","Yield"],l,{className:"compact"})):""}function J(t,o,a){if(!o.length)return"";const n=t.filter(i=>!i.partial&&!i.edgeExcluded),r=new Map((a??[]).map(i=>[i.testNumber,i])),e=[];for(const i of o.filter(De)){const l=i.testNumber,d=i.unit||void 0,f=r.get(l);let s,c,h;if(f)({min:s,max:c,mean:h}=f);else{const p=n.map(w=>w.testValues?.[l]).filter(w=>w!==void 0&&isFinite(w));if(!p.length)continue;p.sort((w,b)=>w-b),s=p[0],c=p[p.length-1],h=p.reduce((w,b)=>w+b,0)/p.length}e.push([y(i.name),T(s,d),T(h,d),T(c,d)])}return e.length?x("Test Values",v(["Test","Min","Mean","Max"],e,{className:"compact"})):""}function X(t,o,a){const n=a??Pe(t,o);if(!n?.length)return"";const r=n.map(e=>[y(e.label),String(e.totalDies),String(e.passDies),String(e.failDies),e.passRatePercent!==null?`${e.passRatePercent.toFixed(1)}%`:"\u2014"]);return x("Functional Tests",v(["Test","N","Pass","Fail","Pass Rate"],r,{className:"compact"}))}function Z(t,o){const a=He(t,o);if(!a.length)return"";const n=e=>e===null?"\u2014":e.toFixed(2),r=a.map(e=>[y(e.label),e.hasSpec?e.limitBasis==="spec"?`LSL ${T(e.lsl,e.unit)} \u2013 USL ${T(e.usl,e.unit)}`:`Lo limit ${T(e.lsl,e.unit)} \u2013 Hi limit ${T(e.usl,e.unit)}`:"\u2014",T(e.mean,e.unit),n(e.cp),n(e.cpk),n(e.pp),n(e.ppk)]);return x("Process Capability",v(["Test","Limits","Mean","Cp","Cpk","Pp","Ppk"],r,{className:"compact"}))}function ee(t,o){const a=O(t);return a.length?x("Findings",V(a,o)):""}export function renderSummaryReportHtml(t,o={}){const{wafer:a,dies:n,yieldSummary:r,dataCoverage:e,hbinDefs:i,sbinDefs:l,testDefs:d=[],statsSummary:f,passBins:s=[1],ringCount:c=4}=t,h=a.metadata,p=ze(h),w=o.title??`Wafer Summary${p}`,b=new Date().toLocaleString(),u=n.some(W=>W.hbin!=null),g=n.some(W=>W.sbin!=null),S=n.filter(je),D=j(S,a,c),$=z(S,a,c),B=[{label:"Total dies",value:String(r.totalDies)},{label:"Filled dies",value:String(e.filledDies),hint:`${C(e.filledDies,e.totalDies)} fill`},{label:"Pass dies",value:String(r.passDies)},{label:"Fail dies",value:String(r.failDies)},...r.partialDies>0?[{label:"Partial",value:String(r.partialDies)}]:[],...r.yieldPercent!==null?[{label:`Yield (pass: ${xe([s])})`,value:`${r.yieldPercent.toFixed(1)}%`}]:[],...r.edgeExcludedDies>0?[{label:"Edge excluded (outer zone)",value:String(r.edgeExcludedDies)}]:[]],Y=[_([{metadata:a.metadata}]),x("Summary",[K(B)].filter(Boolean).join(`
2
- `)),u?Q(n,i,"hard",f?.stats.hardBinCounts,s):g?Q(n,l,"soft",f?.stats.softBinCounts,s):"",q("Ring Yield",D,n,s),q("Quadrant Yield",$,n,s),J(n,d,f?.stats.perTestStats),X(n,d,f?.stats.functionalYield),Z([{dies:n}],d),f?ee(f.findings):""].filter(Boolean).join(`
1
+ import{waferDisplayLabel as H}from"../core/waferLabel.js";import{binPassSets as $e,binPassSetsByWafer as xe,mergeBinDefs as I}from"../renderer/binColors.js";import{itemPassBins as R,passBinsLabel as De}from"../core/passBins.js";import{isParametricTest as Be,isBuiltMap as j}from"../renderer/buildWaferMap.js";import{buildRingRegions as z,buildQuadrantRegions as A,buildRegionYieldData as ve}from"./regions.js";import{openHtmlReport as Te}from"./renderFindingsReport.js";import{analyzeWaferLot as Pe}from"./analyzeWaferLot.js";import{computeFunctionalYield as Fe,analyzeWaferMap as Ye}from"./analyzeWaferMap.js";import{mergeTestDefs as We}from"./mergeTestDefs.js";import{sortBinsForDisplay as O}from"./binPareto.js";import{buildFacetTable as Me,facetValueOf as Ce,FACET_NONE_VALUE as Re}from"./facets.js";import{visibleFindings as G}from"./filterFindings.js";import{buildYieldDataCombined as Ne}from"./yield.js";import{describeWaferPopulation as U,populationLabel as Ee}from"./population.js";import{buildTestPassRateData as Le,hasJudgeableTests as ke,poolFunctionalYield as He}from"./testPassRate.js";import{buildCapabilityData as Ie}from"./capability.js";import{fmt as T}from"../renderer/fmt.js";import{getDieKey as je,isPositionedDie as ze,diePassStatus as V}from"../core/dies.js";import{testValue as Ae}from"../core/dieTable.js";import{findingsTableHtml as _,renderMetadataSection as K,renderMetricGrid as Q,renderSection as $,renderTable as v,reportStyles as N}from"./reportHtml.js";import{escHtml as y}from"../core/utils.js";function C(t,o){return o===0?"\u2014":`${(t/o*100).toFixed(1)}%`}function Oe(t){if(!t)return"";const o=t.lot??t.lotId,a=t.wafer??t.waferId,n=[o,a].filter(Boolean).map(String);return n.length?` \u2014 ${n.map(y).join(" \xB7 ")}`:""}function q(t,o,a,n,r=[1]){const e=new Map;if(n)for(const[s,c]of Object.entries(n))e.set(Number(s),c);else for(const s of t){if(s.partial||s.edgeExcluded)continue;const c=a==="hard"?s.hbin:s.sbin;c!=null&&e.set(c,(e.get(c)??0)+1)}if(!e.size)return"";const i=[...e.values()].reduce((s,c)=>s+c,0),l=o?new Map(o.map(s=>[s.bin,s])):null,u=O(e.entries(),$e(t,r)[a]).map(([s,c])=>{const b=l?.get(s)?.name??"\u2014";return[String(s),b,String(c),C(c,i)]});return $(a==="hard"?"Hard Bin Breakdown":"Soft Bin Breakdown",v(["Bin","Name","Count","%"],u,{className:"compact"}))}function J(t,o,a,n){const r=new Set(n),e=new Map(a.map(u=>[je(u),u]));if(!a.some(u=>u.hbin!=null||u.sbin!=null)||!o.length)return"";const l=[];for(const u of o){let f=0,s=0;for(const c of u.dieKeys){const g=e.get(c);if(!g||g.partial||g.edgeExcluded)continue;const b=V(g,r);b!==void 0&&(s++,b&&f++)}s&&l.push([u.label,String(f),String(s),C(f,s)])}return l.length?$(t,v(["Region","Pass","Total","Yield"],l,{className:"compact"})):""}function X(t,o,a){if(!o.length)return"";const n=t.filter(i=>!i.partial&&!i.edgeExcluded),r=new Map((a??[]).map(i=>[i.testNumber,i])),e=[];for(const i of o.filter(Be)){const l=i.testNumber,u=i.unit||void 0,f=r.get(l);let s,c,g;if(f)({min:s,max:c,mean:g}=f);else{let b=0,B=0;s=1/0,c=-1/0;for(const h of n){const d=Ae(h,l);d===void 0||!isFinite(d)||(b++,B+=d,d<s&&(s=d),d>c&&(c=d))}if(b===0)continue;g=B/b}e.push([y(i.name),T(s,u),T(g,u),T(c,u)])}return e.length?$("Test Values",v(["Test","Min","Mean","Max"],e,{className:"compact"})):""}function Z(t,o,a){const n=a??Fe(t,o);if(!n?.length)return"";const r=n.map(e=>[y(e.label),String(e.totalDies),String(e.passDies),String(e.failDies),e.passRatePercent!==null?`${e.passRatePercent.toFixed(1)}%`:"\u2014"]);return $("Functional Tests",v(["Test","N","Pass","Fail","Pass Rate"],r,{className:"compact"}))}function ee(t,o){const a=Ie(t,o);if(!a.length)return"";const n=e=>e===null?"\u2014":e.toFixed(2),r=a.map(e=>[y(e.label),e.hasSpec?e.limitBasis==="spec"?`LSL ${T(e.lsl,e.unit)} \u2013 USL ${T(e.usl,e.unit)}`:`Lo limit ${T(e.lsl,e.unit)} \u2013 Hi limit ${T(e.usl,e.unit)}`:"\u2014",T(e.mean,e.unit),n(e.cp),n(e.cpk),n(e.pp),n(e.ppk)]);return $("Process Capability",v(["Test","Limits","Mean","Cp","Cpk","Pp","Ppk"],r,{className:"compact"}))}function te(t,o){const a=G(t);return a.length?$("Findings",_(a,o)):""}export function renderSummaryReportHtml(t,o={}){const{wafer:a,dies:n,yieldSummary:r,dataCoverage:e,hbinDefs:i,sbinDefs:l,testDefs:u=[],statsSummary:f,passBins:s=[1],ringCount:c=4}=t,g=a.metadata,b=Oe(g),B=o.title??`Wafer Summary${b}`,h=new Date().toLocaleString(),d=n.some(W=>W.hbin!=null),p=n.some(W=>W.sbin!=null),S=n.filter(ze),x=z(S,a,c),w=A(S,a,c),D=[{label:"Total dies",value:String(r.totalDies)},{label:"Filled dies",value:String(e.filledDies),hint:`${C(e.filledDies,e.totalDies)} fill`},{label:"Pass dies",value:String(r.passDies)},{label:"Fail dies",value:String(r.failDies)},...r.partialDies>0?[{label:"Partial",value:String(r.partialDies)}]:[],...r.yieldPercent!==null?[{label:`Yield (pass: ${De([s])})`,value:`${r.yieldPercent.toFixed(1)}%`}]:[],...r.edgeExcludedDies>0?[{label:"Edge excluded (outer zone)",value:String(r.edgeExcludedDies)}]:[]],Y=[K([{metadata:a.metadata}]),$("Summary",[Q(D)].filter(Boolean).join(`
2
+ `)),d?q(n,i,"hard",f?.stats.hardBinCounts,s):p?q(n,l,"soft",f?.stats.softBinCounts,s):"",J("Ring Yield",x,n,s),J("Quadrant Yield",w,n,s),X(n,u,f?.stats.perTestStats),Z(n,u,f?.stats.functionalYield),ee([{dies:n}],u),f?te(f.findings):""].filter(Boolean).join(`
3
3
  `);return`<!DOCTYPE html>
4
4
  <html lang="en">
5
5
  <head>
6
6
  <meta charset="UTF-8">
7
- <title>${y(w)}</title>
7
+ <title>${y(B)}</title>
8
8
  <style>
9
9
  ${N()}
10
10
  </style>
@@ -12,25 +12,25 @@ ${N()}
12
12
  <body>
13
13
  <main class="report">
14
14
  <header class="report-header">
15
- <h1>${y(w)}</h1>
16
- <p class="report-subtitle">Generated ${y(b)}</p>
15
+ <h1>${y(B)}</h1>
16
+ <p class="report-subtitle">Generated ${y(h)}</p>
17
17
  </header>
18
18
  ${Y}
19
- <p class="footer">Generated ${y(b)}</p>
19
+ <p class="footer">Generated ${y(h)}</p>
20
20
  </main>
21
21
  </body>
22
- </html>`}function Ae(t,o){const a=t.perWafer.map(n=>{const r=H(o[n.waferIndex],n.waferIndex),e=n.summary.stats.yieldPercent;return[r,e!==null?`${e.toFixed(1)}%`:"N/A"]});return v(["Wafer","Yield"],a,{className:"compact"})}function Oe(t){const o=[];for(const e of t.perWafer){const i=O(e.summary.findings);if(!i.length)continue;const l=e.summary.wafer?.waferId??`Wafer ${e.waferIndex+1}`;o.push(`<h3 class="report-subheading">${y(l)}</h3>`+V(i))}if(!o.length)return"";const a=o.length,n=t.perWafer.length,r=`<p class="report-note">${a} of ${n} wafer${n===1?"":"s"} have per-wafer findings; the rest had none.</p>`;return x("Findings by Wafer",r+o.join(`
23
- `))}const Ge=3;function Ue(t,o,a){const n=t.map(d=>({metadata:d.wafer?.metadata})),r=We(n,{facetableOnly:!0}).filter(d=>d.splittable);if(!r.length)return"";const e=r.slice(0,Ge),i=[];for(const d of e){const f=new Map;t.forEach((u,g)=>{const S=Me(u.wafer?.metadata,d.key)??Ce;(f.get(S)??f.set(S,[]).get(S)).push(g)});const s=[...f.entries()].map(([u,g])=>({key:u,items:g.map(S=>t[S])}));if(s.length<2)continue;const c=s.map(u=>[y(u.key),y(u.items.map(g=>g.label).join(", "))]),h=Re(s.map(u=>({key:u.key,items:u.items.map(g=>({label:g.label,dies:g.dies,passBins:R(g,a)}))})),a).map(u=>[y(u.label),String(u.itemCount),`${u.percent.toFixed(1)}%`]),p=[v([d.label,"Wafers"],c,{className:"compact"}),v([d.label,"Wafers","Yield"],h,{className:"compact"})],w=t.flatMap(u=>u.dies??[]);let b=0;for(const u of["spec","testFlag","functional"]){if(!Le(o,u,w))continue;const g=Ee(s.map($=>({key:$.key,items:$.items.map(B=>({dies:B.dies,testSpecYield:B.statsSummary?.stats.testSpecYield,functionalYield:B.statsSummary?.stats.functionalYield}))})),o,u);if(!g.rows.length)continue;const S=g.rows.map($=>[y($.label),...$.byGroup.map(B=>B.passRatePercent===null?"\u2014":`${B.passRatePercent.toFixed(1)}%`),$.overall.passRatePercent===null?"\u2014":`${$.overall.passRatePercent.toFixed(1)}%`]),D=u==="spec"?"Pass rate \xB7 test limits":u==="testFlag"?"Pass rate \xB7 tester flag":"Pass rate \xB7 functional";g.disagreementDies&&(b=g.disagreementDies),p.push(`<h4 class="report-subheading">${y(D)}</h4>`+v(["Test",...g.groups,"All"],S,{className:"compact"}))}b&&p.push(`<p class="report-note">${b.toLocaleString()} dies judged differently by limits and tester flag.</p>`),i.push(`<h3 class="report-subheading">${y(d.label)}</h3>${p.join(`
24
- `)}`)}if(!i.length)return"";const l=r.length>e.length?`<p class="report-note">${r.length-e.length} further splittable field(s) not shown.</p>`:"";return x("Split Comparison",i.join(`
25
- `)+l)}function te(t,o,a,n,r){const e=new Map,i=a==="hard"?"hardBinCounts":"softBinCounts";if(n?.length&&n.every(s=>s.stats[i]!==void 0))for(const s of n)for(const[c,h]of Object.entries(s.stats[i])){const p=Number(c);e.set(p,(e.get(p)??0)+h)}else for(const s of t){if(s.partial||s.edgeExcluded)continue;const c=a==="hard"?s.hbin:s.sbin;c!=null&&e.set(c,(e.get(c)??0)+1)}if(!e.size)return"";const l=[...e.values()].reduce((s,c)=>s+c,0),d=o?new Map(o.map(s=>[s.bin,s])):null,f=A(e.entries(),$e(r)[a]).map(([s,c])=>{const h=d?.get(s);return[h?.name?`Bin ${s} \xB7 ${h.name} (${c})`:`Bin ${s} (${c})`,C(c,l)]});return x(a==="hard"?"Hard Bin Breakdown (All Wafers)":"Soft Bin Breakdown (All Wafers)",v(["Bin","Yield"],f,{className:"compact"}))}function ne(t,o,a,n,r,e){const i=Be(a,n,r,l=>e[l],o).map(l=>[l.label,`${l.yieldPercent.toFixed(1)}%`]);return i.length?x(t,v(["Region","Yield"],i,{className:"compact"})):""}function Ve(t,o,a){let n;if(a?.length&&a.every(r=>r.stats.perTestStats!==void 0)){const r=new Map;for(const e of a)for(const i of e.stats.perTestStats??[]){const l=r.get(i.testNumber);l?(l.n+=i.count,l.sum+=i.mean*i.count,l.min=Math.min(l.min,i.min),l.max=Math.max(l.max,i.max)):r.set(i.testNumber,{n:i.count,sum:i.mean*i.count,min:i.min,max:i.max})}n=[...r.entries()].map(([e,i])=>({testNumber:e,min:i.min,max:i.max,mean:i.sum/i.n}))}return J(t,o,n)}function _e(t,o,a){const n=ke(a);return X(t,o,n)}const E=["lot","product","testProgram","temperature"];function Ke(t){const o=new Map(E.map(e=>[e,new Set]));for(const e of t)for(const i of E){const l=e.wafer?.metadata?.[i];l!=null&&l!==""&&o.get(i).add(String(l))}const a=E.filter(e=>o.get(e).size>1);if(a.length===0)return[{label:"",items:t,varying:a}];const n=new Map,r=[];for(const e of t){const i=a.map(l=>String(e.wafer?.metadata?.[l]??"(none)")).join(" \xB7 ");n.has(i)||(n.set(i,[]),r.push(i)),n.get(i).push(e)}return r.map(e=>({label:e,items:n.get(e),varying:a}))}function se(t,o,a,n,r,e,i){const l=Te(t.map(m=>m.source??m),{perWaferSummaries:t.map(m=>m.statsSummary),...i}),d=[],f=[],s=[],c=[];for(const m of t){const F=m.dies??[];m.wafer&&(d.push(m.wafer),s.push(F),c.push(R(m,r)));for(const P of F)f.push(P)}const h=t.map(m=>({dies:m.dies??[],passBins:R(m,r)})),p=f.some(m=>m.hbin!=null),w=f.some(m=>m.sbin!=null),b=p||w,u=l.perWafer.map(m=>m.summary.stats.yieldPercent).filter(m=>m!==null),g=u.length?u.reduce((m,F)=>m+F,0)/u.length:null;let S=0,D=0,$=0,B=0,Y=0;for(const m of h){const F=new Set(m.passBins);for(const P of m.dies){if(S++,P.edgeExcluded&&B++,P.partial&&Y++,P.partial||P.edgeExcluded)continue;const k=U(P,F);k!==void 0&&(D++,k&&$++)}}const W=D-$,L=D>0?$/D*100:null,ie=[{label:"Wafers",value:String(l.stats.waferCount)},{label:"Total dies",value:String(S)},...b?[{label:"Good dies",value:String($)},{label:"Bad dies",value:String(W)}]:[],...B>0?[{label:"Edge excluded",value:String(B)}]:[],...Y>0?[{label:"Partial dies",value:String(Y)}]:[],...g!==null?[{label:"Mean wafer yield",value:`${g.toFixed(1)}%`}]:[],...L!==null?[{label:"Total yield",value:`${L.toFixed(1)}%`}]:[]],oe=G(l.perWafer.map(m=>m.summary.wafer)),re=x(oe.lotId!==void 0?"Lot Summary":"Summary",K(ie)),le=_(t.map(m=>({metadata:m.wafer?.metadata}))),ce=x("Per-Wafer Yield",Ae(l,t)),de=Ue(t,n,r),M=l.perWafer.map(m=>m.summary),ue=b?p?te(f,o,"hard",M,h):te(f,a,"soft",M,h):"",fe=b&&d.length?ne("Ring Yield (All Wafers)",j,s,d,e,c):"",me=b&&d.length?ne("Quadrant Yield (All Wafers)",z,s,d,e,c):"",pe=n.length?Ve(f,n,M):"",ge=n.length?_e(f,n,M):"",he=n.length?Z(t,n):"",be=l.findings.length?ee(l.findings,l.stats.waferCount):"",ye=Oe(l),Se=[re,le,ce,de,ue,fe,me,pe,ge,he,be,ye].filter(Boolean).join(`
26
- `);return{lotSummary:l,sections:Se}}function ae(t,o,a){return`<main class="report">
22
+ </html>`}function Ge(t,o){const a=t.perWafer.map(n=>{const r=H(o[n.waferIndex],n.waferIndex),e=n.summary.stats.yieldPercent;return[r,e!==null?`${e.toFixed(1)}%`:"N/A"]});return v(["Wafer","Yield"],a,{className:"compact"})}function Ue(t){const o=[];for(const e of t.perWafer){const i=G(e.summary.findings);if(!i.length)continue;const l=e.summary.wafer?.waferId??`Wafer ${e.waferIndex+1}`;o.push(`<h3 class="report-subheading">${y(l)}</h3>`+_(i))}if(!o.length)return"";const a=o.length,n=t.perWafer.length,r=`<p class="report-note">${a} of ${n} wafer${n===1?"":"s"} have per-wafer findings; the rest had none.</p>`;return $("Findings by Wafer",r+o.join(`
23
+ `))}const Ve=3;function _e(t,o,a){const n=t.map(u=>({metadata:u.wafer?.metadata})),r=Me(n,{facetableOnly:!0}).filter(u=>u.splittable);if(!r.length)return"";const e=r.slice(0,Ve),i=[];for(const u of e){const f=new Map;t.forEach((d,p)=>{const S=Ce(d.wafer?.metadata,u.key)??Re;(f.get(S)??f.set(S,[]).get(S)).push(p)});const s=[...f.entries()].map(([d,p])=>({key:d,items:p.map(S=>t[S])}));if(s.length<2)continue;const c=s.map(d=>[y(d.key),y(d.items.map(p=>p.label).join(", "))]),g=Ne(s.map(d=>({key:d.key,items:d.items.map(p=>({label:p.label,dies:p.dies,passBins:R(p,a)}))})),a).map(d=>[y(d.label),String(d.itemCount),`${d.percent.toFixed(1)}%`]),b=[v([u.label,"Wafers"],c,{className:"compact"}),v([u.label,"Wafers","Yield"],g,{className:"compact"})],B=t.flatMap(d=>d.dies??[]);let h=0;for(const d of["spec","testFlag","functional"]){if(!ke(o,d,B))continue;const p=Le(s.map(w=>({key:w.key,items:w.items.map(D=>({dies:D.dies,testSpecYield:D.statsSummary?.stats.testSpecYield,functionalYield:D.statsSummary?.stats.functionalYield}))})),o,d);if(!p.rows.length)continue;const S=p.rows.map(w=>[y(w.label),...w.byGroup.map(D=>D.passRatePercent===null?"\u2014":`${D.passRatePercent.toFixed(1)}%`),w.overall.passRatePercent===null?"\u2014":`${w.overall.passRatePercent.toFixed(1)}%`]),x=d==="spec"?"Pass rate \xB7 test limits":d==="testFlag"?"Pass rate \xB7 tester flag":"Pass rate \xB7 functional";p.disagreementDies&&(h=p.disagreementDies),b.push(`<h4 class="report-subheading">${y(x)}</h4>`+v(["Test",...p.groups,"All"],S,{className:"compact"}))}h&&b.push(`<p class="report-note">${h.toLocaleString()} dies judged differently by limits and tester flag.</p>`),i.push(`<h3 class="report-subheading">${y(u.label)}</h3>${b.join(`
24
+ `)}`)}if(!i.length)return"";const l=r.length>e.length?`<p class="report-note">${r.length-e.length} further splittable field(s) not shown.</p>`:"";return $("Split Comparison",i.join(`
25
+ `)+l)}function ne(t,o,a,n,r){const e=new Map,i=a==="hard"?"hardBinCounts":"softBinCounts";if(n?.length&&n.every(s=>s.stats[i]!==void 0))for(const s of n)for(const[c,g]of Object.entries(s.stats[i])){const b=Number(c);e.set(b,(e.get(b)??0)+g)}else for(const s of t){if(s.partial||s.edgeExcluded)continue;const c=a==="hard"?s.hbin:s.sbin;c!=null&&e.set(c,(e.get(c)??0)+1)}if(!e.size)return"";const l=[...e.values()].reduce((s,c)=>s+c,0),u=o?new Map(o.map(s=>[s.bin,s])):null,f=O(e.entries(),xe(r)[a]).map(([s,c])=>{const g=u?.get(s);return[g?.name?`Bin ${s} \xB7 ${g.name} (${c})`:`Bin ${s} (${c})`,C(c,l)]});return $(a==="hard"?"Hard Bin Breakdown (All Wafers)":"Soft Bin Breakdown (All Wafers)",v(["Bin","Yield"],f,{className:"compact"}))}function se(t,o,a,n,r,e){const i=ve(a,n,r,l=>e[l],o).map(l=>[l.label,`${l.yieldPercent.toFixed(1)}%`]);return i.length?$(t,v(["Region","Yield"],i,{className:"compact"})):""}function Ke(t,o,a){let n;if(a?.length&&a.every(r=>r.stats.perTestStats!==void 0)){const r=new Map;for(const e of a)for(const i of e.stats.perTestStats??[]){const l=r.get(i.testNumber);l?(l.n+=i.count,l.sum+=i.mean*i.count,l.min=Math.min(l.min,i.min),l.max=Math.max(l.max,i.max)):r.set(i.testNumber,{n:i.count,sum:i.mean*i.count,min:i.min,max:i.max})}n=[...r.entries()].map(([e,i])=>({testNumber:e,min:i.min,max:i.max,mean:i.sum/i.n}))}return X(t,o,n)}function Qe(t,o,a){const n=He(a);return Z(t,o,n)}const E=["lot","product","testProgram","temperature"];function qe(t){const o=new Map(E.map(e=>[e,new Set]));for(const e of t)for(const i of E){const l=e.wafer?.metadata?.[i];l!=null&&l!==""&&o.get(i).add(String(l))}const a=E.filter(e=>o.get(e).size>1);if(a.length===0)return[{label:"",items:t,varying:a}];const n=new Map,r=[];for(const e of t){const i=a.map(l=>String(e.wafer?.metadata?.[l]??"(none)")).join(" \xB7 ");n.has(i)||(n.set(i,[]),r.push(i)),n.get(i).push(e)}return r.map(e=>({label:e,items:n.get(e),varying:a}))}function ae(t,o,a,n,r,e,i){const l=Pe(t.map(m=>m.source??m),{perWaferSummaries:t.map(m=>m.statsSummary),...i}),u=[],f=[],s=[],c=[];for(const m of t){const F=m.dies??[];m.wafer&&(u.push(m.wafer),s.push(F),c.push(R(m,r)));for(const P of F)f.push(P)}const g=t.map(m=>({dies:m.dies??[],passBins:R(m,r)})),b=f.some(m=>m.hbin!=null),B=f.some(m=>m.sbin!=null),h=b||B,d=l.perWafer.map(m=>m.summary.stats.yieldPercent).filter(m=>m!==null),p=d.length?d.reduce((m,F)=>m+F,0)/d.length:null;let S=0,x=0,w=0,D=0,Y=0;for(const m of g){const F=new Set(m.passBins);for(const P of m.dies){if(S++,P.edgeExcluded&&D++,P.partial&&Y++,P.partial||P.edgeExcluded)continue;const k=V(P,F);k!==void 0&&(x++,k&&w++)}}const W=x-w,L=x>0?w/x*100:null,oe=[{label:"Wafers",value:String(l.stats.waferCount)},{label:"Total dies",value:String(S)},...h?[{label:"Good dies",value:String(w)},{label:"Bad dies",value:String(W)}]:[],...D>0?[{label:"Edge excluded",value:String(D)}]:[],...Y>0?[{label:"Partial dies",value:String(Y)}]:[],...p!==null?[{label:"Mean wafer yield",value:`${p.toFixed(1)}%`}]:[],...L!==null?[{label:"Total yield",value:`${L.toFixed(1)}%`}]:[]],re=U(l.perWafer.map(m=>m.summary.wafer)),le=$(re.lotId!==void 0?"Lot Summary":"Summary",Q(oe)),ce=K(t.map(m=>({metadata:m.wafer?.metadata}))),de=$("Per-Wafer Yield",Ge(l,t)),ue=_e(t,n,r),M=l.perWafer.map(m=>m.summary),fe=h?b?ne(f,o,"hard",M,g):ne(f,a,"soft",M,g):"",me=h&&u.length?se("Ring Yield (All Wafers)",z,s,u,e,c):"",pe=h&&u.length?se("Quadrant Yield (All Wafers)",A,s,u,e,c):"",ge=n.length?Ke(f,n,M):"",be=n.length?Qe(f,n,M):"",he=n.length?ee(t,n):"",ye=l.findings.length?te(l.findings,l.stats.waferCount):"",Se=Ue(l),we=[le,ce,de,ue,fe,me,pe,ge,be,he,ye,Se].filter(Boolean).join(`
26
+ `);return{lotSummary:l,sections:we}}function ie(t,o,a){return`<main class="report">
27
27
  <header class="report-header">
28
28
  <h1>${y(t)}</h1>
29
29
  <p class="report-subtitle">Generated ${y(a)}</p>
30
30
  </header>
31
31
  ${o}
32
32
  <p class="footer">Generated ${y(a)}</p>
33
- </main>`}export function renderLotSummaryReportHtml(t,o={}){const{items:a,hbinDefs:n,sbinDefs:r,testDefs:e=[],passBins:i=[1],ringCount:l=4,analyzeOptions:d}=t,f=new Date().toLocaleString(),s=Ke(a);if(s.length===1){const{lotSummary:b,sections:u}=se(s[0].items,n,r,e,i,l,d),g=G(b.perWafer.map(D=>D.summary.wafer)),S=o.title??(g.lotId!==void 0?`Lot Summary \u2014 ${g.lotId}`:`Summary \u2014 ${Ne(g)}`);return`<!DOCTYPE html>
33
+ </main>`}export function renderLotSummaryReportHtml(t,o={}){const{items:a,hbinDefs:n,sbinDefs:r,testDefs:e=[],passBins:i=[1],ringCount:l=4,analyzeOptions:u}=t,f=new Date().toLocaleString(),s=qe(a);if(s.length===1){const{lotSummary:h,sections:d}=ae(s[0].items,n,r,e,i,l,u),p=U(h.perWafer.map(x=>x.summary.wafer)),S=o.title??(p.lotId!==void 0?`Lot Summary \u2014 ${p.lotId}`:`Summary \u2014 ${Ee(p)}`);return`<!DOCTYPE html>
34
34
  <html lang="en">
35
35
  <head>
36
36
  <meta charset="UTF-8">
@@ -40,10 +40,10 @@ ${N()}
40
40
  </style>
41
41
  </head>
42
42
  <body>
43
- ${ae(S,u,f)}
43
+ ${ie(S,d,f)}
44
44
  </body>
45
- </html>`}const c=o.title??"Summary",h=s.map(b=>{const{lotSummary:u,sections:g}=se(b.items,n,r,e,i,l,d),S=b.varying.includes("lot")?void 0:u.lot?.lot??u.lot?.lotId,D=S?`${b.label} \xB7 Lot ${String(S)}`:b.label;return ae(`${c} \u2014 ${D}`,g,f)}),p=`<p style="max-width:1080px;margin:0 auto 8px;padding:8px 12px;background:var(--report-surface);border:1px solid var(--report-line);border-radius:4px;font-size:12px;color:var(--report-muted);">This load spans ${s.length} groups by identity \u2014 shown separately below so stats are never pooled across them.</p>`,w=h.map((b,u)=>`${u===0?"":'<hr style="max-width:1080px;margin:32px auto 0;border:none;border-top:3px solid var(--report-line-strong);">'}
46
- ${b}`).join(`
45
+ </html>`}const c=o.title??"Summary",g=s.map(h=>{const{lotSummary:d,sections:p}=ae(h.items,n,r,e,i,l,u),S=h.varying.includes("lot")?void 0:d.lot?.lot??d.lot?.lotId,x=S?`${h.label} \xB7 Lot ${String(S)}`:h.label;return ie(`${c} \u2014 ${x}`,p,f)}),b=`<p style="max-width:1080px;margin:0 auto 8px;padding:8px 12px;background:var(--report-surface);border:1px solid var(--report-line);border-radius:4px;font-size:12px;color:var(--report-muted);">This load spans ${s.length} groups by identity \u2014 shown separately below so stats are never pooled across them.</p>`,B=g.map((h,d)=>`${d===0?"":'<hr style="max-width:1080px;margin:32px auto 0;border:none;border-top:3px solid var(--report-line-strong);">'}
46
+ ${h}`).join(`
47
47
  `);return`<!DOCTYPE html>
48
48
  <html lang="en">
49
49
  <head>
@@ -55,8 +55,8 @@ ${N()}
55
55
  </head>
56
56
  <body>
57
57
  <div class="report" style="padding-top:24px;">
58
- ${p}
58
+ ${b}
59
59
  </div>
60
- ${w}
60
+ ${B}
61
61
  </body>
62
- </html>`}export{ve as openHtmlReport};export function renderWaferReportHtml(t,o,a={}){return renderSummaryReportHtml({wafer:t.wafer,dies:t.dies,yieldSummary:t.yield,dataCoverage:t.dataCoverage,hbinDefs:t.hbinDefs,sbinDefs:t.sbinDefs,testDefs:t.testDefs,statsSummary:o??t.statsSummary??("view"in t?Fe(t):void 0),passBins:[...t.passBins],ringCount:t.ringCount},a)}export function renderLotReportHtml(t,o={}){const a=I(t.map(e=>e.hbinDefs)),n=I(t.map(e=>e.sbinDefs)),r=t.some(e=>e.testDefs?.length)?Ye([...t]).defs:void 0;return renderLotSummaryReportHtml({items:t.map((e,i)=>({label:H(e,i),wafer:e.wafer,dies:e.dies,passBins:[...e.passBins],statsSummary:e.statsSummary,source:"view"in e?e:void 0})),hbinDefs:a.length?a:void 0,sbinDefs:n.length?n:void 0,testDefs:r,ringCount:t[0]?.ringCount,analyzeOptions:o.analyzeOptions},{title:o.title})}
62
+ </html>`}export{Te as openHtmlReport};export function renderWaferReportHtml(t,o,a={}){return renderSummaryReportHtml({wafer:t.wafer,dies:t.dies,yieldSummary:t.yield,dataCoverage:t.dataCoverage,hbinDefs:t.hbinDefs,sbinDefs:t.sbinDefs,testDefs:t.testDefs,statsSummary:o??t.statsSummary??(j(t)?Ye(t):void 0),passBins:[...t.passBins],ringCount:t.ringCount},a)}export function renderLotReportHtml(t,o={}){const a=I(t.map(e=>e.hbinDefs)),n=I(t.map(e=>e.sbinDefs)),r=t.some(e=>e.testDefs?.length)?We([...t]).defs:void 0;return renderLotSummaryReportHtml({items:t.map((e,i)=>({label:H(e,i),wafer:e.wafer,dies:e.dies,passBins:[...e.passBins],statsSummary:e.statsSummary,source:j(e)?e:void 0})),hbinDefs:a.length?a:void 0,sbinDefs:n.length?n:void 0,testDefs:r,ringCount:t[0]?.ringCount,analyzeOptions:o.analyzeOptions},{title:o.title})}
@@ -1 +1 @@
1
- function u(i,o,s,t,n){for(const e of i??[]){const f=e.testValues?.[o],r=e.testValues?.[s];f!==void 0&&r!==void 0&&Number.isFinite(f)&&Number.isFinite(r)&&n.push(t===void 0?{x:f,y:r,hbin:e.hbin}:{x:f,y:r,hbin:e.hbin,group:t})}}export function buildScatterData(i,o,s){const t=[];for(const n of i)u(n.dies,o,s,void 0,t);return t}export function buildScatterDataGrouped(i,o,s){const t=[];for(const n of i)for(const e of n.items)u(e.dies,o,s,n.key,t);return t}
1
+ import{testValue as u}from"../core/dieTable.js";function c(e,o,s,t,n){for(const i of e??[]){const r=u(i,o),f=u(i,s);r!==void 0&&f!==void 0&&Number.isFinite(r)&&Number.isFinite(f)&&n.push(t===void 0?{x:r,y:f,hbin:i.hbin}:{x:r,y:f,hbin:i.hbin,group:t})}}export function buildScatterData(e,o,s){const t=[];for(const n of e)c(n.dies,o,s,void 0,t);return t}export function buildScatterDataGrouped(e,o,s){const t=[];for(const n of e)for(const i of n.items)c(i.dies,o,s,n.key,t);return t}
@@ -1 +1 @@
1
- import{isYieldEligibleDie as G}from"../core/dies.js";import{isParametricTest as M}from"../renderer/buildWaferMap.js";import{markedTestLabel as H,derivedFields as J}from"../renderer/testLabel.js";import{parseTestReference as K}from"../renderer/derivedTests/parser.js";import{quantile as V}from"./math.js";import{compileXNamePattern as Q,readXFromName as W}from"./sweepXFromName.js";function j(o,a,s){const e=[];let t=!1;for(const r of o.tests){if(typeof r=="number"){Number.isInteger(r)?e.push(r):s.push(`"${o.label}": ${r} is not a test number.`);continue}const i=K(String(r),a);if(!i.ok){s.push(`"${o.label}": "${r}" \u2014 ${i.message}.`);continue}i.range&&(t=!0),e.push(...i.tests)}return{spec:o,tests:e,usedRange:t}}export function sweepAppliesTo(o,a){const s=new Map((a??[]).map(e=>[e.testNumber,e]));return o.series.some(e=>j(e,s,[]).tests.some(t=>{const r=s.get(t);return r!==void 0&&M(r)}))}function B(o,a=16){return o.length<=a?o.join(", "):`${o.slice(0,a).join(", ")}, \u2026 (${o.length-a} more)`}function Z(o,a){const s=o?.[a];return s!==void 0&&Number.isFinite(s)?s:a}function _(o,a,s){const e=o.spec;if(e.xValues!==void 0&&e.xFromName!==void 0)return s.push(`"${e.label}": gives both xValues and xFromName \u2014 use one.`),{asked:!0};if(e.xValues!==void 0){const t=e.xValues.length;return t===o.tests.length?{xs:e.xValues,asked:!0}:(s.push(o.usedRange?`"${e.label}": ${t} x values for ${o.tests.length} tests \u2014 the ranges matched ${B(o.tests)}.`:`"${e.label}": ${t} x values for ${o.tests.length} tests.`),{asked:!0})}if(e.xFromName!==void 0){const t=Q(e.xFromName);if("error"in t)return s.push(`"${e.label}": xFromName "${e.xFromName}" \u2014 ${t.error}.`),{asked:!0};const r=[],i=[],u=[];for(const l of o.tests){const c=a.get(l)?.name,f=c===void 0?null:W(t,c);typeof f=="number"?r.push(f):(i.push(l),f!==null&&u.push(f.ambiguous))}return i.length>0?(s.push(`"${e.label}": xFromName "${e.xFromName}" found no x value in the name of ${i.length===1?"test":"tests"} ${B(i)}`+(u.length>0?` \u2014 "${u[0]}" is written in capitals, where M could be milli or mega; put the letter in the pattern ("\u2026{x}M\u2026") and the unit in xLabel.`:".")),{asked:!0}):{xs:r,asked:!0}}return{asked:!1}}function ee(o,a,s){const e=new Map;for(let t=0;t<s.length;t++){const r=e.get(s[t]);if(r!==void 0)return`"${o}": tests ${a[r]} and ${a[t]} have the same x value (${s[t]}) \u2014 a curve cannot be measured where it revisits an x.`;e.set(s[t],t)}return null}function D(o,a){const s=o.filter(e=>e.count>0);for(let e=1;e<s.length;e++){const t=s[e-1],r=s[e],i=Math.min(t.median,r.median),u=Math.max(t.median,r.median);if(a<i||a>u)continue;if(t.median===r.median)return t.x;const l=(a-t.median)/(r.median-t.median);return t.x+l*(r.x-t.x)}return null}function ne(o,a){const s=new Map(a.filter(i=>i.count>0).map(i=>[i.x,i])),e=[];for(const i of o){if(i.count===0)continue;const u=s.get(i.x);u!==void 0&&e.push({x:i.x,d:i.median-u.median,y:i.median})}if(e.length<2)return null;const t=[];for(let i=0;i<e.length;i++){const u=e[i];if(u.d===0){t.push({x:u.x,y:u.y});continue}if(i===0)continue;const l=e[i-1];if(l.d===0||l.d<0==u.d<0)continue;const c=l.d/(l.d-u.d);t.push({x:l.x+c*(u.x-l.x),y:l.y+c*(u.y-l.y)})}const r=t[0];return r===void 0?null:{x:r.x,y:r.y,multiple:t.length>1}}export function buildSweepData(o,a,s){const e=[],t=new Map((a??[]).map(n=>[n.testNumber,n])),r=o.filter(n=>G(n)),i=s.series.map(n=>j(n,t,e)),u=i.map(n=>_(n,t,e)),l=i.length>0&&u.every(n=>n.xs!==void 0),c=!l&&u.some(n=>n.asked);c&&u.some(n=>!n.asked)&&e.push("x values are given for some series but not all.");const f=l?i.map((n,d)=>ee(n.spec.label,n.tests,u[d].xs)).find(n=>n!==null)??null:null;f!==null&&e.push(f);const S=s.xScale==="log",T=l&&u.every(n=>n.xs.every(d=>d>0)),b=S&&T?"log":"linear";S&&!T&&e.push(l?"A log x axis needs every x value to be positive \u2014 drawn on a linear axis.":"A log x axis needs x values (xValues or xFromName) \u2014 drawn in test order.");const L=i.map(({spec:n,tests:d},p)=>{const v=l?u[p].xs:void 0,N=d.map((x,z)=>{const h=t.get(x);h===void 0?e.push(`"${n.label}": test ${x} is not in this program's test definitions.`):M(h)||e.push(`"${n.label}": test ${x} is functional and has no measured value.`);const m=[];if(h!==void 0&&M(h))for(const F of r){const g=F.testValues?.[x];g!==void 0&&Number.isFinite(g)&&m.push(g)}return m.sort((F,g)=>F-g),{testNumber:x,label:H(h,x),...J(h),x:Z(v,z),median:m.length?V(m,.5):NaN,p10:m.length?V(m,.1):NaN,p90:m.length?V(m,.9):NaN,count:m.length}});return{label:n.label,color:n.color,points:N}}),[X,C]=L,$=X!==void 0&&C!==void 0&&!c&&f===null,I=s.crossing??!0,Y=n=>b==="log"?Math.log10(n):n,k=n=>b==="log"?10**n:n,P=n=>n.map(d=>({...d,x:Y(d.x)})),q=$?P(X.points):[],R=$?P(C.points):[],E=(c||f!==null?[]:s.separationAt??[]).map(n=>{if(!$)return{y:n,distance:null};const d=D(q,n),p=D(R,n);if(d===null)return{y:n,distance:null,reason:"first-series-never-reaches"};if(p===null)return{y:n,distance:null,reason:"second-series-never-reaches"};const v=k(d),N=k(p);return{y:n,distance:Math.abs(N-v),from:v,to:N,...b==="log"?{ratio:10**Math.abs(d-p)}:{}}}),w=$&&I?ne(q,R):null,O=w&&{...w,x:k(w.x)},U=n=>n===void 0?void 0:t.get(n.tests[0]??-1)?.unit,y=U(i[0]),A=U(i[1]);return y!==void 0&&A!==void 0&&y!==A&&e.push(`Series measure different units (${y} and ${A}) \u2014 crossing and separation compare unlike quantities.`),{title:s.title,xLabel:c?"Test":s.xLabel??(l?"Sweep value":"Test"),yLabel:s.yLabel??"Value",series:L,xIsPhysical:l,...l&&s.xUnit?{xUnit:s.xUnit}:{},xScale:b,crossing:O,separations:E,dieCount:r.length,warnings:e,...c&&s.series.length>=2?{notMeasured:"x values do not match the tests, so the sweep is drawn in test order and the crossing and widths are not measured"}:f!==null&&s.series.length>=2?{notMeasured:"a series revisits an x value, so its points cannot be paired with the other series"}:{}}}
1
+ import{isYieldEligibleDie as G}from"../core/dies.js";import{testValue as H}from"../core/dieTable.js";import{isParametricTest as M}from"../renderer/buildWaferMap.js";import{markedTestLabel as J,derivedFields as K}from"../renderer/testLabel.js";import{parseTestReference as Q}from"../renderer/derivedTests/parser.js";import{quantile as V}from"./math.js";import{compileXNamePattern as W,readXFromName as Z}from"./sweepXFromName.js";function j(o,a,s){const e=[];let t=!1;for(const r of o.tests){if(typeof r=="number"){Number.isInteger(r)?e.push(r):s.push(`"${o.label}": ${r} is not a test number.`);continue}const i=Q(String(r),a);if(!i.ok){s.push(`"${o.label}": "${r}" \u2014 ${i.message}.`);continue}i.range&&(t=!0),e.push(...i.tests)}return{spec:o,tests:e,usedRange:t}}export function sweepAppliesTo(o,a){const s=new Map((a??[]).map(e=>[e.testNumber,e]));return o.series.some(e=>j(e,s,[]).tests.some(t=>{const r=s.get(t);return r!==void 0&&M(r)}))}function B(o,a=16){return o.length<=a?o.join(", "):`${o.slice(0,a).join(", ")}, \u2026 (${o.length-a} more)`}function _(o,a){const s=o?.[a];return s!==void 0&&Number.isFinite(s)?s:a}function ee(o,a,s){const e=o.spec;if(e.xValues!==void 0&&e.xFromName!==void 0)return s.push(`"${e.label}": gives both xValues and xFromName \u2014 use one.`),{asked:!0};if(e.xValues!==void 0){const t=e.xValues.length;return t===o.tests.length?{xs:e.xValues,asked:!0}:(s.push(o.usedRange?`"${e.label}": ${t} x values for ${o.tests.length} tests \u2014 the ranges matched ${B(o.tests)}.`:`"${e.label}": ${t} x values for ${o.tests.length} tests.`),{asked:!0})}if(e.xFromName!==void 0){const t=W(e.xFromName);if("error"in t)return s.push(`"${e.label}": xFromName "${e.xFromName}" \u2014 ${t.error}.`),{asked:!0};const r=[],i=[],u=[];for(const l of o.tests){const c=a.get(l)?.name,f=c===void 0?null:Z(t,c);typeof f=="number"?r.push(f):(i.push(l),f!==null&&u.push(f.ambiguous))}return i.length>0?(s.push(`"${e.label}": xFromName "${e.xFromName}" found no x value in the name of ${i.length===1?"test":"tests"} ${B(i)}`+(u.length>0?` \u2014 "${u[0]}" is written in capitals, where M could be milli or mega; put the letter in the pattern ("\u2026{x}M\u2026") and the unit in xLabel.`:".")),{asked:!0}):{xs:r,asked:!0}}return{asked:!1}}function ne(o,a,s){const e=new Map;for(let t=0;t<s.length;t++){const r=e.get(s[t]);if(r!==void 0)return`"${o}": tests ${a[r]} and ${a[t]} have the same x value (${s[t]}) \u2014 a curve cannot be measured where it revisits an x.`;e.set(s[t],t)}return null}function D(o,a){const s=o.filter(e=>e.count>0);for(let e=1;e<s.length;e++){const t=s[e-1],r=s[e],i=Math.min(t.median,r.median),u=Math.max(t.median,r.median);if(a<i||a>u)continue;if(t.median===r.median)return t.x;const l=(a-t.median)/(r.median-t.median);return t.x+l*(r.x-t.x)}return null}function te(o,a){const s=new Map(a.filter(i=>i.count>0).map(i=>[i.x,i])),e=[];for(const i of o){if(i.count===0)continue;const u=s.get(i.x);u!==void 0&&e.push({x:i.x,d:i.median-u.median,y:i.median})}if(e.length<2)return null;const t=[];for(let i=0;i<e.length;i++){const u=e[i];if(u.d===0){t.push({x:u.x,y:u.y});continue}if(i===0)continue;const l=e[i-1];if(l.d===0||l.d<0==u.d<0)continue;const c=l.d/(l.d-u.d);t.push({x:l.x+c*(u.x-l.x),y:l.y+c*(u.y-l.y)})}const r=t[0];return r===void 0?null:{x:r.x,y:r.y,multiple:t.length>1}}export function buildSweepData(o,a,s){const e=[],t=new Map((a??[]).map(n=>[n.testNumber,n])),r=o.filter(n=>G(n)),i=s.series.map(n=>j(n,t,e)),u=i.map(n=>ee(n,t,e)),l=i.length>0&&u.every(n=>n.xs!==void 0),c=!l&&u.some(n=>n.asked);c&&u.some(n=>!n.asked)&&e.push("x values are given for some series but not all.");const f=l?i.map((n,d)=>ne(n.spec.label,n.tests,u[d].xs)).find(n=>n!==null)??null:null;f!==null&&e.push(f);const S=s.xScale==="log",T=l&&u.every(n=>n.xs.every(d=>d>0)),b=S&&T?"log":"linear";S&&!T&&e.push(l?"A log x axis needs every x value to be positive \u2014 drawn on a linear axis.":"A log x axis needs x values (xValues or xFromName) \u2014 drawn in test order.");const L=i.map(({spec:n,tests:d},p)=>{const v=l?u[p].xs:void 0,N=d.map((x,z)=>{const h=t.get(x);h===void 0?e.push(`"${n.label}": test ${x} is not in this program's test definitions.`):M(h)||e.push(`"${n.label}": test ${x} is functional and has no measured value.`);const m=[];if(h!==void 0&&M(h))for(const F of r){const g=H(F,x);g!==void 0&&Number.isFinite(g)&&m.push(g)}return m.sort((F,g)=>F-g),{testNumber:x,label:J(h,x),...K(h),x:_(v,z),median:m.length?V(m,.5):NaN,p10:m.length?V(m,.1):NaN,p90:m.length?V(m,.9):NaN,count:m.length}});return{label:n.label,color:n.color,points:N}}),[X,C]=L,$=X!==void 0&&C!==void 0&&!c&&f===null,I=s.crossing??!0,Y=n=>b==="log"?Math.log10(n):n,k=n=>b==="log"?10**n:n,P=n=>n.map(d=>({...d,x:Y(d.x)})),q=$?P(X.points):[],R=$?P(C.points):[],E=(c||f!==null?[]:s.separationAt??[]).map(n=>{if(!$)return{y:n,distance:null};const d=D(q,n),p=D(R,n);if(d===null)return{y:n,distance:null,reason:"first-series-never-reaches"};if(p===null)return{y:n,distance:null,reason:"second-series-never-reaches"};const v=k(d),N=k(p);return{y:n,distance:Math.abs(N-v),from:v,to:N,...b==="log"?{ratio:10**Math.abs(d-p)}:{}}}),w=$&&I?te(q,R):null,O=w&&{...w,x:k(w.x)},U=n=>n===void 0?void 0:t.get(n.tests[0]??-1)?.unit,y=U(i[0]),A=U(i[1]);return y!==void 0&&A!==void 0&&y!==A&&e.push(`Series measure different units (${y} and ${A}) \u2014 crossing and separation compare unlike quantities.`),{title:s.title,xLabel:c?"Test":s.xLabel??(l?"Sweep value":"Test"),yLabel:s.yLabel??"Value",series:L,xIsPhysical:l,...l&&s.xUnit?{xUnit:s.xUnit}:{},xScale:b,crossing:O,separations:E,dieCount:r.length,warnings:e,...c&&s.series.length>=2?{notMeasured:"x values do not match the tests, so the sweep is drawn in test order and the crossing and widths are not measured"}:f!==null&&s.series.length>=2?{notMeasured:"a series revisits an x value, so its points cannot be paired with the other series"}:{}}}
@@ -1 +1 @@
1
- import{isYieldEligibleDie as y}from"../core/dies.js";import{isParametricTest as L,getTestPassStatus as P}from"../renderer/buildWaferMap.js";import{classifySpec as Y}from"../renderer/spec.js";import{testLabel as T,derivedFields as R}from"../renderer/testLabel.js";const F=(i,l)=>l===0?null:i/l*100;export function buildTestPassRateData(i,l,o){const n=i.map(e=>e.key),t={groups:n,rows:[],disagreementDies:null};if(!l?.length)return t;const r=l.filter(e=>e.testNumber!==void 0&&H(e,o));if(!r.length)return t;const b=()=>({pass:0,fail:0,failLow:0,failHigh:0}),g=new Map;for(const e of r){const s=e.testNumber;g.set(s,{label:T(e,s),overall:b(),perGroup:n.map(()=>b())})}const c=(e,s,f)=>{const a=g.get(e);if(a)for(const u of[a.overall,a.perGroup[s]])u.pass+=f.pass??0,u.fail+=f.fail??0,u.failLow+=f.failLow??0,u.failHigh+=f.failHigh??0},h=o==="spec"||o==="testFlag";let w=0,N=0;i.forEach((e,s)=>{for(const f of e.items){if(o==="spec"&&f.testSpecYield&&!G(f)){for(const a of f.testSpecYield)c(a.testNumber,s,{pass:a.passDies,fail:a.failLowDies+a.failHighDies,failLow:a.failLowDies,failHigh:a.failHighDies});continue}if(o==="functional"&&f.functionalYield){for(const a of f.functionalYield)c(a.testNumber,s,{pass:a.passDies,fail:a.failDies});continue}for(const a of f.dies??[])if(y(a))for(const u of r){const p=u.testNumber,m=Y(a.testValues?.[p],u),d=P(a,p,u);if(h&&m!==null&&d!==void 0&&(w++,m==="pass"!==d&&N++),o==="spec"){if(m===null)continue;m==="pass"?c(p,s,{pass:1}):m==="failLow"?c(p,s,{fail:1,failLow:1}):c(p,s,{fail:1,failHigh:1})}else{if(d===void 0)continue;c(p,s,d?{pass:1}:{fail:1})}}}});const v=e=>({passDies:e.pass,failDies:e.fail,totalDies:e.pass+e.fail,passRatePercent:F(e.pass,e.pass+e.fail)}),D=[];for(const[e,s]of g)s.overall.pass+s.overall.fail!==0&&D.push({testNumber:e,label:s.label,overall:v(s.overall),byGroup:s.perGroup.map(v),...o==="spec"?{failLowDies:s.overall.failLow,failHighDies:s.overall.failHigh}:{}});return D.sort((e,s)=>(e.overall.passRatePercent??101)-(s.overall.passRatePercent??101)||e.testNumber-s.testNumber),{groups:n,rows:D,disagreementDies:h&&w>0?N:null}}function G(i){return(i.dies??[]).some(l=>l.testPass!==void 0&&Object.keys(l.testPass).length>0)}function H(i,l){return l==="functional"?!L(i):L(i)?l==="spec"?i.limitLow!==void 0||i.limitHigh!==void 0:!0:!1}export function hasJudgeableTests(i,l,o){if(!i?.length)return!1;const n=i.filter(t=>t.testNumber!==void 0&&H(t,l));return n.length?l!=="testFlag"?!0:(o??[]).some(t=>n.some(r=>P(t,r.testNumber,r)!==void 0)):!1}export function poolFunctionalYield(i){if(!i?.length||!i.every(n=>n.stats.functionalYield!==void 0))return;const l=new Map;for(const n of i)for(const t of n.stats.functionalYield??[]){const r=l.get(t.testNumber)??{label:t.label,...R(t),passDies:0,failDies:0,totalDies:0};r.passDies+=t.passDies,r.failDies+=t.failDies,r.totalDies+=t.totalDies,l.set(t.testNumber,r)}const o=[...l.entries()].map(([n,t])=>({testNumber:n,...t,passRatePercent:t.totalDies>0?t.passDies/t.totalDies*100:null}));return o.length?o:void 0}
1
+ import{isYieldEligibleDie as x}from"../core/dies.js";import{dieHasVerdicts as M,testValuesReader as S}from"../core/dieTable.js";import{isParametricTest as R,getTestPassStatus as E,testPassStatusReader as z}from"../renderer/buildWaferMap.js";import{classifySpec as C}from"../renderer/spec.js";import{testLabel as I,derivedFields as J}from"../renderer/testLabel.js";const K=(s,o)=>o===0?null:s/o*100;export function buildTestPassRateData(s,o,n){const r=s.map(e=>e.key),t={groups:r,rows:[],disagreementDies:null};if(!o?.length)return t;const i=o.filter(e=>e.testNumber!==void 0&&G(e,n));if(!i.length)return t;const v=()=>({pass:0,fail:0,failLow:0,failHigh:0}),g=new Map;for(const e of i){const l=e.testNumber;g.set(l,{label:I(e,l),overall:v(),perGroup:r.map(()=>v())})}const N=(e,l,u)=>{const f=g.get(e);if(f)for(const p of[f.overall,f.perGroup[l]])p.pass+=u.pass??0,p.fail+=u.fail??0,p.failLow+=u.failLow??0,p.failHigh+=u.failHigh??0},T=S(i.map(e=>e.testNumber)),L=new Float64Array(i.length),V=z(i),h=new Int8Array(i.length),H=n==="spec"||n==="testFlag";let y=0,P=0;s.forEach(function(l,u){for(const f of l.items){if(n==="spec"&&f.testSpecYield&&!O(f)){for(const a of f.testSpecYield)N(a.testNumber,u,{pass:a.passDies,fail:a.failLowDies+a.failHighDies,failLow:a.failLowDies,failHigh:a.failHighDies});continue}if(n==="functional"&&f.functionalYield){for(const a of f.functionalYield)N(a.testNumber,u,{pass:a.passDies,fail:a.failDies});continue}const p=i.map(a=>g.get(a.testNumber).overall),F=i.map(a=>g.get(a.testNumber).perGroup[u]);for(const a of f.dies??[])if(x(a)){T(a,L),V(a,h);for(let c=0;c<i.length;c++){const A=i[c],D=C(L[c],A),b=h[c]===-1?void 0:h[c]===1;H&&D!==null&&b!==void 0&&(y++,D==="pass"!==b&&P++);const m=p[c],d=F[c];if(n==="spec"){if(D===null)continue;if(D==="pass"){m.pass++,d.pass++;continue}m.fail++,d.fail++,D==="failLow"?(m.failLow++,d.failLow++):(m.failHigh++,d.failHigh++)}else{if(b===void 0)continue;b?(m.pass++,d.pass++):(m.fail++,d.fail++)}}}}});const Y=e=>({passDies:e.pass,failDies:e.fail,totalDies:e.pass+e.fail,passRatePercent:K(e.pass,e.pass+e.fail)}),w=[];for(const[e,l]of g)l.overall.pass+l.overall.fail!==0&&w.push({testNumber:e,label:l.label,overall:Y(l.overall),byGroup:l.perGroup.map(Y),...n==="spec"?{failLowDies:l.overall.failLow,failHighDies:l.overall.failHigh}:{}});return w.sort((e,l)=>(e.overall.passRatePercent??101)-(l.overall.passRatePercent??101)||e.testNumber-l.testNumber),{groups:r,rows:w,disagreementDies:H&&y>0?P:null}}function O(s){return(s.dies??[]).some(M)}function G(s,o){return o==="functional"?!R(s):R(s)?o==="spec"?s.limitLow!==void 0||s.limitHigh!==void 0:!0:!1}export function hasJudgeableTests(s,o,n){if(!s?.length)return!1;const r=s.filter(t=>t.testNumber!==void 0&&G(t,o));return r.length?o!=="testFlag"?!0:(n??[]).some(t=>r.some(i=>E(t,i.testNumber,i)!==void 0)):!1}export function poolFunctionalYield(s){if(!s?.length||!s.every(r=>r.stats.functionalYield!==void 0))return;const o=new Map;for(const r of s)for(const t of r.stats.functionalYield??[]){const i=o.get(t.testNumber)??{label:t.label,...J(t),passDies:0,failDies:0,totalDies:0};i.passDies+=t.passDies,i.failDies+=t.failDies,i.totalDies+=t.totalDies,o.set(t.testNumber,i)}const n=[...o.entries()].map(([r,t])=>({testNumber:r,...t,passRatePercent:t.totalDies>0?t.passDies/t.totalDies*100:null}));return n.length?n:void 0}
@@ -1 +1 @@
1
- import{isYieldEligibleDie as f}from"../core/dies.js";export function buildTestTrendData(s,u){return s.map((n,t)=>{const c=n.label??`#${t}`,i=n.testStats?.find(e=>e.testNumber===u);if(i){const{mean:e,stddev:o,count:d}=i;return{label:c,mean:e,stddev:o,count:d,key:n.key}}const r=(n.dies??[]).filter(e=>f(e)).map(e=>e.testValues?.[u]).filter(e=>e!==void 0&&Number.isFinite(e));if(r.length===0)return{label:c,mean:NaN,stddev:0,count:0,key:n.key};const l=r.reduce((e,o)=>e+o,0)/r.length,a=r.length>1?r.reduce((e,o)=>e+(o-l)**2,0)/(r.length-1):0;return{label:c,mean:l,stddev:Math.sqrt(a),count:r.length,key:n.key}})}export function trendCentre(s){let u=0,n=0;for(const t of s)!t.count||!Number.isFinite(t.mean)||(u+=t.count,n+=t.mean*t.count);return u===0?null:n/u}
1
+ import{isYieldEligibleDie as f}from"../core/dies.js";import{testValue as m}from"../core/dieTable.js";export function buildTestTrendData(i,o){return i.map((t,n)=>{const s=t.label??`#${n}`,c=t.testStats?.find(e=>e.testNumber===o);if(c){const{mean:e,stddev:u,count:d}=c;return{label:s,mean:e,stddev:u,count:d,key:t.key}}const r=(t.dies??[]).filter(e=>f(e)).map(e=>m(e,o)).filter(e=>e!==void 0&&Number.isFinite(e));if(r.length===0)return{label:s,mean:NaN,stddev:0,count:0,key:t.key};const l=r.reduce((e,u)=>e+u,0)/r.length,a=r.length>1?r.reduce((e,u)=>e+(u-l)**2,0)/(r.length-1):0;return{label:s,mean:l,stddev:Math.sqrt(a),count:r.length,key:t.key}})}export function trendCentre(i){let o=0,t=0;for(const n of i)!n.count||!Number.isFinite(n.mean)||(o+=n.count,t+=n.mean*n.count);return o===0?null:t/o}
@@ -30,6 +30,11 @@ export interface HighlightBinTarget {
30
30
  export interface HighlightWaferTarget {
31
31
  kind: 'wafer';
32
32
  waferIndices: number[];
33
+ /**
34
+ * For a lot finding about a region, that region's dies on each counted wafer,
35
+ * keyed by wafer index — what to highlight on each wafer's map.
36
+ */
37
+ dieKeysByWafer?: Record<number, string[]>;
33
38
  }
34
39
  export interface HighlightDieTarget {
35
40
  kind: 'dies';
@@ -1 +1 @@
1
- export function createWafermapWorker(n){let i=0;const s=new Map;return n.onmessage=t=>{const e=t.data;if(e.type==="pong")return;const r=s.get(e.id);r&&(s.delete(e.id),e.type==="error"?r.reject(new Error(e.message)):e.type==="result"?r.resolve(e.result):e.type==="analyzed"?r.resolve({waferSummaries:e.waferSummaries,lotSummary:e.lotSummary}):e.type==="resultWithAnalysis"&&r.resolve({results:e.results,waferSummaries:e.waferSummaries,lotSummary:e.lotSummary}))},n.onerror=t=>{const e=new Error(t.message??"Worker error");for(const r of s.values())r.reject(e);s.clear()},{run(t){return new Promise((e,r)=>{const a=i++;s.set(a,{resolve:e,reject:r}),n.postMessage({type:"run",id:a,input:t})})},runAnalysis(t,e,r){return new Promise((a,l)=>{const o=i++;s.set(o,{resolve:a,reject:l}),n.postMessage({type:"analyze",id:o,results:t,options:e,hasMultiWafer:r})})},runWithAnalysis(t,e,r){return new Promise((a,l)=>{const o=i++;s.set(o,{resolve:a,reject:l}),n.postMessage({type:"runWithAnalysis",id:o,inputs:t,options:e,hasMultiWafer:r})})},terminate(){n.terminate();const t=new Error("Worker terminated");for(const e of s.values())e.reject(t);s.clear()}}}
1
+ import{attachTables as m}from"../core/dieTable.js";export function createWafermapWorker(a){let i=0;const s=new Map;return a.onmessage=t=>{const e=t.data;if(e.type==="pong")return;const r=s.get(e.id);r&&(s.delete(e.id),e.type==="error"?r.reject(new Error(e.message)):e.type==="result"?(m([e.result.dies],e.tables),r.resolve(e.result)):e.type==="analyzed"?r.resolve({waferSummaries:e.waferSummaries,lotSummary:e.lotSummary}):e.type==="resultWithAnalysis"&&(m(e.results.map(n=>n.dies),e.tables),r.resolve({results:e.results,waferSummaries:e.waferSummaries,lotSummary:e.lotSummary})))},a.onerror=t=>{const e=new Error(t.message??"Worker error");for(const r of s.values())r.reject(e);s.clear()},{run(t){return new Promise((e,r)=>{const n=i++;s.set(n,{resolve:e,reject:r}),a.postMessage({type:"run",id:n,input:t})})},runAnalysis(t,e,r){return new Promise((n,l)=>{const o=i++;s.set(o,{resolve:n,reject:l}),a.postMessage({type:"analyze",id:o,results:t,options:e,hasMultiWafer:r})})},runWithAnalysis(t,e,r){return new Promise((n,l)=>{const o=i++;s.set(o,{resolve:n,reject:l}),a.postMessage({type:"runWithAnalysis",id:o,inputs:t,options:e,hasMultiWafer:r})})},terminate(){a.terminate();const t=new Error("Worker terminated");for(const e of s.values())e.reject(t);s.clear()}}}
@@ -1,5 +1,6 @@
1
1
  import type { WaferMapInput, WaferMapResult } from '../renderer/buildWaferMap.js';
2
2
  import type { AnalyzeWaferMapOptions, StatsSummary, LotStatsSummary } from '../stats/index.js';
3
+ import type { DetachedTables } from '../core/dieTable.js';
3
4
  export type WorkerRequest = {
4
5
  type: 'run';
5
6
  id: number;
@@ -23,6 +24,7 @@ export type WorkerResponse = {
23
24
  type: 'result';
24
25
  id: number;
25
26
  result: WaferMapResult;
27
+ tables: DetachedTables;
26
28
  } | {
27
29
  type: 'analyzed';
28
30
  id: number;
@@ -34,6 +36,7 @@ export type WorkerResponse = {
34
36
  results: WaferMapResult[];
35
37
  waferSummaries: StatsSummary[];
36
38
  lotSummary: LotStatsSummary | null;
39
+ tables: DetachedTables;
37
40
  } | {
38
41
  type: 'error';
39
42
  id: number;
@@ -1 +1 @@
1
- import{buildWaferMap as o}from"../renderer/buildWaferMap.js";import{analyzeWaferMap as i,analyzeWaferLot as n}from"../stats/index.js";self.onmessage=p=>{const e=p.data;if(e.type==="ping"){self.postMessage({type:"pong"});return}if(e.type==="run")try{const s=o(e.input);self.postMessage({type:"result",id:e.id,result:s})}catch(s){const t=s instanceof Error?s.message:String(s);self.postMessage({type:"error",id:e.id,message:t})}if(e.type==="analyze")try{const s=e.results.map(r=>i(r,e.options)),t=e.hasMultiWafer?n(e.results,{...e.options,perWaferSummaries:s}):null;self.postMessage({type:"analyzed",id:e.id,waferSummaries:s,lotSummary:t})}catch(s){const t=s instanceof Error?s.message:String(s);self.postMessage({type:"error",id:e.id,message:t})}if(e.type==="runWithAnalysis")try{const s=e.inputs.map(a=>o(a)),t=s.map(a=>i(a,e.options)),r=e.hasMultiWafer?n(s,{...e.options,perWaferSummaries:t}):null;self.postMessage({type:"resultWithAnalysis",id:e.id,results:s,waferSummaries:t,lotSummary:r})}catch(s){const t=s instanceof Error?s.message:String(s);self.postMessage({type:"error",id:e.id,message:t})}};
1
+ import{buildWaferMap as o}from"../renderer/buildWaferMap.js";import{analyzeWaferMap as i,analyzeWaferLot as n}from"../stats/index.js";import{detachTables as p}from"../core/dieTable.js";self.onmessage=l=>{const s=l.data;if(s.type==="ping"){self.postMessage({type:"pong"});return}if(s.type==="run")try{const e=o(s.input),{transfer:t,...r}=p([e.dies]);self.postMessage({type:"result",id:s.id,result:e,tables:r},t)}catch(e){const t=e instanceof Error?e.message:String(e);self.postMessage({type:"error",id:s.id,message:t})}if(s.type==="analyze")try{const e=s.results.map(r=>i(r,s.options)),t=s.hasMultiWafer?n(s.results,{...s.options,perWaferSummaries:e}):null;self.postMessage({type:"analyzed",id:s.id,waferSummaries:e,lotSummary:t})}catch(e){const t=e instanceof Error?e.message:String(e);self.postMessage({type:"error",id:s.id,message:t})}if(s.type==="runWithAnalysis")try{const e=s.inputs.map(a=>o(a)),t=e.map(a=>i(a,s.options)),r=s.hasMultiWafer?n(e,{...s.options,perWaferSummaries:t}):null,{transfer:m,...f}=p(e.map(a=>a.dies));self.postMessage({type:"resultWithAnalysis",id:s.id,results:e,waferSummaries:t,lotSummary:r,tables:f},m)}catch(e){const t=e instanceof Error?e.message:String(e);self.postMessage({type:"error",id:s.id,message:t})}};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wafertools/wafermap",
3
- "version": "0.31.0",
3
+ "version": "0.32.0",
4
4
  "description": "Interactive wafer map visualization and yield analysis for semiconductor test data. Hard bins, soft bins, test values, retests, edge exclusion, reticle overlays, spatial statistics, failure clustering, and lot-level trend analysis — pure ES modules, no runtime dependencies, no server required.",
5
5
  "type": "module",
6
6
  "license": "MIT",