@wafertools/wafermap 0.30.0 → 0.30.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.
- package/AGENTS.md +13 -3
- package/CHANGELOG.md +175 -0
- package/README.md +3 -3
- package/dist/packages/canvas-adapter/charts/barPanel.js +1 -1
- package/dist/packages/canvas-adapter/charts/binCluster.js +1 -1
- package/dist/packages/canvas-adapter/charts/boxplot.js +1 -1
- package/dist/packages/canvas-adapter/charts/capability.js +1 -1
- package/dist/packages/canvas-adapter/charts/correlation.js +2 -2
- package/dist/packages/canvas-adapter/charts/groupedBarPlot.d.ts +1 -0
- package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
- package/dist/packages/canvas-adapter/charts/testPassRate.js +1 -1
- package/dist/packages/canvas-adapter/charts/trend.js +1 -1
- package/dist/packages/canvas-adapter/deprecated.d.ts +0 -3
- package/dist/packages/canvas-adapter/deprecated.js +1 -1
- package/dist/packages/canvas-adapter/dieList.js +1 -1
- package/dist/packages/canvas-adapter/exportName.d.ts +68 -0
- package/dist/packages/canvas-adapter/exportName.js +1 -0
- package/dist/packages/canvas-adapter/icons.js +2 -2
- package/dist/packages/canvas-adapter/index.d.ts +2 -1
- package/dist/packages/canvas-adapter/index.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +25 -3
- package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferMap.d.ts +24 -2
- package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
- package/dist/packages/canvas-adapter/summaryPanel.d.ts +21 -6
- package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
- package/dist/packages/canvas-adapter/toCanvas.js +1 -1
- package/dist/packages/canvas-adapter/toolbar.d.ts +6 -0
- package/dist/packages/canvas-adapter/toolbar.js +2 -2
- package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
- package/dist/packages/canvas-adapter/userGuideHtml.js +15 -8
- package/dist/packages/canvas-adapter/version.d.ts +2 -2
- package/dist/packages/canvas-adapter/version.js +1 -1
- package/dist/packages/canvas-adapter/warnings.js +1 -1
- package/dist/packages/core/deprecated.d.ts +8 -12
- package/dist/packages/core/deprecated.js +1 -1
- package/dist/packages/core/index.d.ts +1 -1
- package/dist/packages/core/index.js +1 -1
- package/dist/packages/core/utils.d.ts +12 -0
- package/dist/packages/core/utils.js +1 -1
- package/dist/packages/core/waferLabel.d.ts +12 -2
- package/dist/packages/core/waferLabel.js +1 -1
- package/dist/packages/renderer/binColors.d.ts +38 -0
- package/dist/packages/renderer/binColors.js +1 -1
- package/dist/packages/renderer/buildView.js +1 -1
- package/dist/packages/renderer/buildWaferMap.d.ts +30 -5
- package/dist/packages/renderer/buildWaferMap.js +1 -1
- package/dist/packages/renderer/deprecate.d.ts +7 -0
- package/dist/packages/renderer/deprecate.js +1 -1
- package/dist/packages/renderer/deprecated.d.ts +2 -2
- package/dist/packages/renderer/deprecated.js +1 -1
- package/dist/packages/renderer/index.d.ts +2 -1
- package/dist/packages/renderer/index.js +1 -1
- package/dist/packages/stats/analyzeWaferLot.js +1 -1
- package/dist/packages/stats/analyzeWaferMap.js +1 -1
- package/dist/packages/stats/capability.d.ts +16 -1
- package/dist/packages/stats/capability.js +1 -1
- package/dist/packages/stats/deprecated.d.ts +30 -35
- package/dist/packages/stats/deprecated.js +1 -1
- package/dist/packages/stats/index.d.ts +4 -2
- package/dist/packages/stats/index.js +1 -1
- package/dist/packages/stats/normalizeInput.d.ts +10 -0
- package/dist/packages/stats/normalizeInput.js +1 -0
- package/dist/packages/stats/regions.d.ts +2 -0
- package/dist/packages/stats/regions.js +1 -1
- package/dist/packages/stats/renderFindingsReport.js +1 -1
- package/dist/packages/stats/renderSummaryReport.d.ts +39 -1
- package/dist/packages/stats/renderSummaryReport.js +26 -26
- package/dist/packages/stats/reportHtml.d.ts +0 -1
- package/dist/packages/stats/reportHtml.js +10 -10
- package/dist/packages/stats/summaryFigures.d.ts +47 -0
- package/dist/packages/stats/summaryFigures.js +1 -0
- package/dist/packages/stats/types.d.ts +118 -0
- package/package.json +1 -1
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import{waferDisplayLabel as
|
|
2
|
-
<td class="tight">${
|
|
3
|
-
<td class="tight">${h(
|
|
4
|
-
<td>${h(
|
|
5
|
-
<td class="numeric">${h(
|
|
6
|
-
<td class="numeric">${h(
|
|
1
|
+
import{waferDisplayLabel as L}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 O,buildRegionYieldData as ve}from"./regions.js";import{openHtmlReport as Be}from"./renderFindingsReport.js";import{analyzeWaferLot as Te}from"./analyzeWaferLot.js";import{computeFunctionalYield as Fe,analyzeWaferMap as Pe}from"./analyzeWaferMap.js";import{mergeTestDefs as Ye}from"./mergeTestDefs.js";import{sortBinsForDisplay as z}from"./binPareto.js";import{buildFacetTable as We,facetValueOf as Me,FACET_NONE_VALUE as Ce}from"./facets.js";import{visibleFindings as A}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 ke,poolFunctionalYield as He}from"./testPassRate.js";import{buildCapabilityData as Le}from"./capability.js";import{fmt as F}from"../renderer/fmt.js";import{getDieKey as Ie,isPositionedDie as je,diePassStatus as U}from"../core/dies.js";import{formatFindingDelta as Oe,formatFindingCoverage as ze,formatFindingTooltip as Ae,renderMetadataSection as V,renderMetricGrid as _,renderSection as x,renderSeverityBadge as Ge,renderTable as B,reportStyles as N}from"./reportHtml.js";import{escHtml as h}from"../core/utils.js";function C(t,o){return o===0?"\u2014":`${(t/o*100).toFixed(1)}%`}function Ue(t){if(!t)return"";const o=t.lot??t.lotId,s=t.wafer??t.waferId,n=[o,s].filter(Boolean).map(String);return n.length?` \u2014 ${n.map(h).join(" \xB7 ")}`:""}function K(t,o,s,n,r=[1]){const e=new Map;if(n)for(const[a,c]of Object.entries(n))e.set(Number(a),c);else for(const a of t){if(a.partial||a.edgeExcluded)continue;const c=s==="hard"?a.hbin:a.sbin;c!=null&&e.set(c,(e.get(c)??0)+1)}if(!e.size)return"";const i=[...e.values()].reduce((a,c)=>a+c,0),l=o?new Map(o.map(a=>[a.bin,a])):null,d=z(e.entries(),we(t,r)[s]).map(([a,c])=>{const p=l?.get(a)?.name??"\u2014";return[String(a),p,String(c),C(c,i)]});return x(s==="hard"?"Hard Bin Breakdown":"Soft Bin Breakdown",B(["Bin","Name","Count","%"],d,{className:"compact"}))}function Q(t,o,s,n){const r=new Set(n),e=new Map(s.map(d=>[Ie(d),d]));if(!s.some(d=>d.hbin!=null||d.sbin!=null)||!o.length)return"";const l=[];for(const d of o){let f=0,a=0;for(const c of d.dieKeys){const b=e.get(c);if(!b||b.partial||b.edgeExcluded)continue;const p=U(b,r);p!==void 0&&(a++,p&&f++)}a&&l.push([d.label,String(f),String(a),C(f,a)])}return l.length?x(t,B(["Region","Pass","Total","Yield"],l,{className:"compact"})):""}function q(t,o,s){if(!o.length)return"";const n=t.filter(i=>!i.partial&&!i.edgeExcluded),r=new Map((s??[]).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 a,c,b;if(f)({min:a,max:c,mean:b}=f);else{const p=n.map(w=>w.testValues?.[l]).filter(w=>w!==void 0&&isFinite(w));if(!p.length)continue;p.sort((w,y)=>w-y),a=p[0],c=p[p.length-1],b=p.reduce((w,y)=>w+y,0)/p.length}e.push([h(i.name),F(a,d),F(b,d),F(c,d)])}return e.length?x("Test Values",B(["Test","Min","Mean","Max"],e,{className:"compact"})):""}function J(t,o,s){const n=s??Fe(t,o);if(!n?.length)return"";const r=n.map(e=>[h(e.label),String(e.totalDies),String(e.passDies),String(e.failDies),e.passRatePercent!==null?`${e.passRatePercent.toFixed(1)}%`:"\u2014"]);return x("Functional Tests",B(["Test","N","Pass","Fail","Pass Rate"],r,{className:"compact"}))}function X(t,o){const s=Le(t,o);if(!s.length)return"";const n=e=>e===null?"\u2014":e.toFixed(2),r=s.map(e=>[h(e.label),e.hasSpec?`${F(e.lsl,e.unit)} \u2013 ${F(e.usl,e.unit)}`:"\u2014",F(e.mean,e.unit),n(e.cp),n(e.cpk),n(e.pp),n(e.ppk)]);return x("Process Capability",B(["Test","Spec (LSL\u2013USL)","Mean","Cp","Cpk","Pp","Ppk"],r,{className:"compact"}))}function Z(t,o){const s=t.map(r=>`<tr title="${h(Ae(r))}">
|
|
2
|
+
<td class="tight">${Ge(r.severity)}</td>
|
|
3
|
+
<td class="tight">${h(r.comparison.left)}</td>
|
|
4
|
+
<td>${h(r.variable.label)}</td>
|
|
5
|
+
<td class="numeric">${h(Oe(r))}</td>
|
|
6
|
+
<td class="numeric">${h(ze(r,o))}</td>
|
|
7
7
|
</tr>`).join(`
|
|
8
8
|
`);return`<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">${
|
|
10
|
-
</tr></thead><tbody>${
|
|
11
|
-
`)),u?
|
|
9
|
+
<th>Severity</th><th>Region</th><th>Metric</th><th class="numeric">Delta</th><th class="numeric">${o!==void 0?"Wafers":"N (region/rest)"}</th>
|
|
10
|
+
</tr></thead><tbody>${s}</tbody></table>`}function ee(t,o){const s=A(t);return s.length?x("Findings",Z(s,o)):""}export function renderSummaryReportHtml(t,o={}){const{wafer:s,dies:n,yieldSummary:r,dataCoverage:e,hbinDefs:i,sbinDefs:l,testDefs:d=[],statsSummary:f,passBins:a=[1],ringCount:c=4}=t,b=s.metadata,p=Ue(b),w=o.title??`Wafer Summary${p}`,y=new Date().toLocaleString(),u=n.some(Y=>Y.hbin!=null),g=n.some(Y=>Y.sbin!=null),S=n.filter(je),D=j(S,s,c),$=O(S,s,c),v=[{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([a])})`,value:`${r.yieldPercent.toFixed(1)}%`}]:[],...r.edgeExcludedDies>0?[{label:"Edge excluded (outer zone)",value:String(r.edgeExcludedDies)}]:[]],P=[V([{metadata:s.metadata}]),x("Summary",[_(v)].filter(Boolean).join(`
|
|
11
|
+
`)),u?K(n,i,"hard",f?.stats.hardBinCounts,a):g?K(n,l,"soft",f?.stats.softBinCounts,a):"",Q("Ring Yield",D,n,a),Q("Quadrant Yield",$,n,a),q(n,d,f?.stats.perTestStats),J(n,d,f?.stats.functionalYield),X([{dies:n}],d),f?ee(f.findings):""].filter(Boolean).join(`
|
|
12
12
|
`);return`<!DOCTYPE html>
|
|
13
13
|
<html lang="en">
|
|
14
14
|
<head>
|
|
15
15
|
<meta charset="UTF-8">
|
|
16
16
|
<title>${h(w)}</title>
|
|
17
17
|
<style>
|
|
18
|
-
${
|
|
18
|
+
${N()}
|
|
19
19
|
</style>
|
|
20
20
|
</head>
|
|
21
21
|
<body>
|
|
@@ -28,30 +28,30 @@ ${C()}
|
|
|
28
28
|
<p class="footer">Generated ${h(y)}</p>
|
|
29
29
|
</main>
|
|
30
30
|
</body>
|
|
31
|
-
</html>`}function
|
|
32
|
-
`))}const
|
|
33
|
-
`)}`)}if(!
|
|
34
|
-
`)+l)}function
|
|
35
|
-
`);return{lotSummary:l,sections:
|
|
31
|
+
</html>`}function Ve(t,o){const s=t.perWafer.map(n=>{const r=L(o[n.waferIndex],n.waferIndex),e=n.summary.stats.yieldPercent;return[r,e!==null?`${e.toFixed(1)}%`:"N/A"]});return B(["Wafer","Yield"],s,{className:"compact"})}function _e(t){const o=[];for(const e of t.perWafer){const i=A(e.summary.findings);if(!i.length)continue;const l=e.summary.wafer?.waferId??`Wafer ${e.waferIndex+1}`;o.push(`<h3 class="report-subheading">${h(l)}</h3>`+Z(i))}if(!o.length)return"";const s=o.length,n=t.perWafer.length,r=`<p class="report-note">${s} of ${n} wafer${n===1?"":"s"} have per-wafer findings; the rest had none.</p>`;return x("Findings by Wafer",r+o.join(`
|
|
32
|
+
`))}const Ke=3;function Qe(t,o,s){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,Ke),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 a=[...f.entries()].map(([u,g])=>({key:u,items:g.map(S=>t[S])}));if(a.length<2)continue;const c=a.map(u=>[h(u.key),h(u.items.map(g=>g.label).join(", "))]),b=Re(a.map(u=>({key:u.key,items:u.items.map(g=>({label:g.label,dies:g.dies,passBins:R(g,s)}))})),s).map(u=>[h(u.label),String(u.itemCount),`${u.percent.toFixed(1)}%`]),p=[B([d.label,"Wafers"],c,{className:"compact"}),B([d.label,"Wafers","Yield"],b,{className:"compact"})],w=t.flatMap(u=>u.dies??[]);let y=0;for(const u of["spec","testFlag","functional"]){if(!ke(o,u,w))continue;const g=Ee(a.map($=>({key:$.key,items:$.items.map(v=>({dies:v.dies,testSpecYield:v.statsSummary?.stats.testSpecYield,functionalYield:v.statsSummary?.stats.functionalYield}))})),o,u);if(!g.rows.length)continue;const S=g.rows.map($=>[h($.label),...$.byGroup.map(v=>v.passRatePercent===null?"\u2014":`${v.passRatePercent.toFixed(1)}%`),$.overall.passRatePercent===null?"\u2014":`${$.overall.passRatePercent.toFixed(1)}%`]),D=u==="spec"?"Pass rate \xB7 spec limits":u==="testFlag"?"Pass rate \xB7 tester flag":"Pass rate \xB7 functional";g.disagreementDies&&(y=g.disagreementDies),p.push(`<h4 class="report-subheading">${h(D)}</h4>`+B(["Test",...g.groups,"All"],S,{className:"compact"}))}y&&p.push(`<p class="report-note">${y.toLocaleString()} dies judged differently by limits and tester flag.</p>`),i.push(`<h3 class="report-subheading">${h(d.label)}</h3>${p.join(`
|
|
33
|
+
`)}`)}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(`
|
|
34
|
+
`)+l)}function te(t,o,s,n,r){const e=new Map,i=s==="hard"?"hardBinCounts":"softBinCounts";if(n?.length&&n.every(a=>a.stats[i]!==void 0))for(const a of n)for(const[c,b]of Object.entries(a.stats[i])){const p=Number(c);e.set(p,(e.get(p)??0)+b)}else for(const a of t){if(a.partial||a.edgeExcluded)continue;const c=s==="hard"?a.hbin:a.sbin;c!=null&&e.set(c,(e.get(c)??0)+1)}if(!e.size)return"";const l=[...e.values()].reduce((a,c)=>a+c,0),d=o?new Map(o.map(a=>[a.bin,a])):null,f=z(e.entries(),$e(r)[s]).map(([a,c])=>{const b=d?.get(a);return[b?.name?`Bin ${a} \xB7 ${b.name} (${c})`:`Bin ${a} (${c})`,C(c,l)]});return x(s==="hard"?"Hard Bin Breakdown (All Wafers)":"Soft Bin Breakdown (All Wafers)",B(["Bin","Yield"],f,{className:"compact"}))}function ne(t,o,s,n,r,e){const i=ve(s,n,r,l=>e[l],o).map(l=>[l.label,`${l.yieldPercent.toFixed(1)}%`]);return i.length?x(t,B(["Region","Yield"],i,{className:"compact"})):""}function qe(t,o,s){let n;if(s?.length&&s.every(r=>r.stats.perTestStats!==void 0)){const r=new Map;for(const e of s)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 q(t,o,n)}function Je(t,o,s){const n=He(s);return J(t,o,n)}const E=["lot","product","testProgram","temperature"];function Xe(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 s=E.filter(e=>o.get(e).size>1);if(s.length===0)return[{label:"",items:t,varying:s}];const n=new Map,r=[];for(const e of t){const i=s.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:s}))}function se(t,o,s,n,r,e,i){const l=Te(t.map(m=>m.source??m),{perWaferSummaries:t.map(m=>m.statsSummary),...i}),d=[],f=[],a=[],c=[];for(const m of t){const T=m.dies??[];m.wafer&&(d.push(m.wafer),a.push(T),c.push(R(m,r))),T.length&&f.push(...T)}const b=t.map(m=>({dies:m.dies??[],passBins:R(m,r)})),p=f.some(m=>m.hbin!=null),w=f.some(m=>m.sbin!=null),y=p||w,u=l.perWafer.map(m=>m.summary.stats.yieldPercent).filter(m=>m!==null),g=u.length?u.reduce((m,T)=>m+T,0)/u.length:null;let S=0,D=0,$=0,v=0,P=0;for(const m of b){const T=new Set(m.passBins);for(const W of m.dies){if(S++,W.edgeExcluded&&v++,W.partial&&P++,W.partial||W.edgeExcluded)continue;const H=U(W,T);H!==void 0&&(D++,H&&$++)}}const Y=D-$,k=D>0?$/D*100:null,ie=[{label:"Wafers",value:String(l.stats.waferCount)},{label:"Total dies",value:String(S)},...y?[{label:"Good dies",value:String($)},{label:"Bad dies",value:String(Y)}]:[],...v>0?[{label:"Edge excluded",value:String(v)}]:[],...P>0?[{label:"Partial dies",value:String(P)}]:[],...g!==null?[{label:"Mean wafer yield",value:`${g.toFixed(1)}%`}]:[],...k!==null?[{label:"Total yield",value:`${k.toFixed(1)}%`}]:[]],oe=G(l.perWafer.map(m=>m.summary.wafer)),re=x(oe.lotId!==void 0?"Lot Summary":"Summary",_(ie)),le=V(t.map(m=>({metadata:m.wafer?.metadata}))),ce=x("Per-Wafer Yield",Ve(l,t)),de=Qe(t,n,r),M=l.perWafer.map(m=>m.summary),ue=y?p?te(f,o,"hard",M,b):te(f,s,"soft",M,b):"",fe=y&&d.length?ne("Ring Yield (All Wafers)",j,a,d,e,c):"",me=y&&d.length?ne("Quadrant Yield (All Wafers)",O,a,d,e,c):"",pe=n.length?qe(f,n,M):"",ge=n.length?Je(f,n,M):"",he=n.length?X(t,n):"",be=l.findings.length?ee(l.findings,l.stats.waferCount):"",ye=_e(l),Se=[re,le,ce,de,ue,fe,me,pe,ge,he,be,ye].filter(Boolean).join(`
|
|
35
|
+
`);return{lotSummary:l,sections:Se}}function ae(t,o,s){return`<main class="report">
|
|
36
36
|
<header class="report-header">
|
|
37
|
-
<h1>${h(
|
|
38
|
-
<p class="report-subtitle">Generated ${h(
|
|
37
|
+
<h1>${h(t)}</h1>
|
|
38
|
+
<p class="report-subtitle">Generated ${h(s)}</p>
|
|
39
39
|
</header>
|
|
40
|
-
${
|
|
41
|
-
<p class="footer">Generated ${h(
|
|
42
|
-
</main>`}export function renderLotSummaryReportHtml(
|
|
40
|
+
${o}
|
|
41
|
+
<p class="footer">Generated ${h(s)}</p>
|
|
42
|
+
</main>`}export function renderLotSummaryReportHtml(t,o={}){const{items:s,hbinDefs:n,sbinDefs:r,testDefs:e=[],passBins:i=[1],ringCount:l=4,analyzeOptions:d}=t,f=new Date().toLocaleString(),a=Xe(s);if(a.length===1){const{lotSummary:y,sections:u}=se(a[0].items,n,r,e,i,l,d),g=G(y.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>
|
|
43
43
|
<html lang="en">
|
|
44
44
|
<head>
|
|
45
45
|
<meta charset="UTF-8">
|
|
46
46
|
<title>${h(S)}</title>
|
|
47
47
|
<style>
|
|
48
|
-
${
|
|
48
|
+
${N()}
|
|
49
49
|
</style>
|
|
50
50
|
</head>
|
|
51
51
|
<body>
|
|
52
|
-
${
|
|
52
|
+
${ae(S,u,f)}
|
|
53
53
|
</body>
|
|
54
|
-
</html>`}const c=
|
|
54
|
+
</html>`}const c=o.title??"Summary",b=a.map(y=>{const{lotSummary:u,sections:g}=se(y.items,n,r,e,i,l,d),S=y.varying.includes("lot")?void 0:u.lot?.lot??u.lot?.lotId,D=S?`${y.label} \xB7 Lot ${String(S)}`:y.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 ${a.length} groups by identity \u2014 shown separately below so stats are never pooled across them.</p>`,w=b.map((y,u)=>`${u===0?"":'<hr style="max-width:1080px;margin:32px auto 0;border:none;border-top:3px solid var(--report-line-strong);">'}
|
|
55
55
|
${y}`).join(`
|
|
56
56
|
`);return`<!DOCTYPE html>
|
|
57
57
|
<html lang="en">
|
|
@@ -59,13 +59,13 @@ ${y}`).join(`
|
|
|
59
59
|
<meta charset="UTF-8">
|
|
60
60
|
<title>${h(c)}</title>
|
|
61
61
|
<style>
|
|
62
|
-
${
|
|
62
|
+
${N()}
|
|
63
63
|
</style>
|
|
64
64
|
</head>
|
|
65
65
|
<body>
|
|
66
66
|
<div class="report" style="padding-top:24px;">
|
|
67
|
-
${
|
|
67
|
+
${p}
|
|
68
68
|
</div>
|
|
69
69
|
${w}
|
|
70
70
|
</body>
|
|
71
|
-
</html>`}export{
|
|
71
|
+
</html>`}export{Be as openHtmlReport};export function renderWaferReportHtml(t,o,s={}){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?Pe(t):void 0),passBins:[...t.passBins],ringCount:t.ringCount},s)}export function renderLotReportHtml(t,o={}){const s=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:L(e,i),wafer:e.wafer,dies:e.dies,passBins:[...e.passBins],statsSummary:e.statsSummary,source:"view"in e?e:void 0})),hbinDefs:s.length?s:void 0,sbinDefs:n.length?n:void 0,testDefs:r,ringCount:t[0]?.ringCount,analyzeOptions:o.analyzeOptions},{title:o.title})}
|
|
@@ -5,7 +5,6 @@ export interface MetricItem {
|
|
|
5
5
|
value: string;
|
|
6
6
|
hint?: string;
|
|
7
7
|
}
|
|
8
|
-
export declare function escHtml(value: string): string;
|
|
9
8
|
export declare function renderSection(title: string, body: string, className?: string): string;
|
|
10
9
|
export declare function renderDefinitionList(entries: Array<{
|
|
11
10
|
label: string;
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import{fmt as n}from"../renderer/fmt.js";import{buildFacetTable as
|
|
2
|
-
<h2>${
|
|
1
|
+
import{fmt as n}from"../renderer/fmt.js";import{buildFacetTable as d,prettyKey as c}from"./facets.js";import{escHtml as a}from"../core/utils.js";export function renderSection(e,t,r=""){return`<section class="${["report-section",r].filter(Boolean).join(" ")}">
|
|
2
|
+
<h2>${a(e)}</h2>
|
|
3
3
|
${t}
|
|
4
|
-
</section>`}export function renderDefinitionList(e,t="meta-list"){if(!e.length)return"";const r=e.map(o=>`<div class="definition-item"><dt>${
|
|
4
|
+
</section>`}export function renderDefinitionList(e,t="meta-list"){if(!e.length)return"";const r=e.map(o=>`<div class="definition-item"><dt>${a(o.label)}</dt><dd>${a(o.value)}</dd></div>`).join(`
|
|
5
5
|
`);return`<dl class="${t}">
|
|
6
6
|
${r}
|
|
7
|
-
</dl>`}export function buildMetadataRows(e){return
|
|
7
|
+
</dl>`}export function buildMetadataRows(e){return d(e,{facetableOnly:e.length>1}).map(r=>({label:c(r.key),value:r.values.map(o=>o.value).join(", ")}))}export function renderMetadataSection(e){const t=buildMetadataRows(e);return t.length?renderSection("Wafer Info",renderDefinitionList(t)):""}export function renderMetricGrid(e){return e.length?`<dl class="metric-grid">
|
|
8
8
|
${e.map(r=>`<div class="metric">
|
|
9
|
-
<dt>${
|
|
10
|
-
<dd>${
|
|
11
|
-
${r.hint?`<span class="metric-hint">${
|
|
9
|
+
<dt>${a(r.label)}</dt>
|
|
10
|
+
<dd>${a(r.value)}</dd>
|
|
11
|
+
${r.hint?`<span class="metric-hint">${a(r.hint)}</span>`:""}
|
|
12
12
|
</div>`).join(`
|
|
13
13
|
`)}
|
|
14
|
-
</dl>`:""}export function renderTable(e,t,r={}){if(!t.length)return`<p class="no-data">${
|
|
14
|
+
</dl>`:""}export function renderTable(e,t,r={}){if(!t.length)return`<p class="no-data">${a(r.emptyMessage??"No data")}</p>`;const o=["report-table",r.className].filter(Boolean).join(" "),p=e.map(i=>`<th>${a(i)}</th>`).join(""),l=t.map(i=>`<tr>${i.map(s=>`<td>${s}</td>`).join("")}</tr>`).join(`
|
|
15
15
|
`);return`<table class="${o}">
|
|
16
|
-
<thead><tr>${
|
|
17
|
-
<tbody>${
|
|
16
|
+
<thead><tr>${p}</tr></thead>
|
|
17
|
+
<tbody>${l}</tbody>
|
|
18
18
|
</table>`}export function renderSeverityBadge(e){const t=e.charAt(0).toUpperCase()+e.slice(1);return`<span class="badge badge-${e}">${t}</span>`}export function formatFindingDelta(e){const t=e.effect.absoluteDelta??0,r=t>0?"+":t<0?"\u2212":"";if(e.variable.kind==="yield"||e.variable.kind==="hardBin"||e.variable.kind==="softBin")return`${r}${Math.abs(t*100).toFixed(1)} pp`;if(e.variable.kind==="test"){if(e.effect.relativeDelta!==void 0&&Number.isFinite(e.effect.relativeDelta)){const o=Math.abs(e.effect.relativeDelta*100).toFixed(1);return`${r}${o}%`}return`${r}${n(Math.abs(t),e.variable.unit)}`}return`${r}${n(Math.abs(t),e.variable.unit)}`}export function formatFindingTooltip(e){return e.summary}export function formatFindingCoverage(e,t){return t!==void 0?`${e.stats.sampleSizeLeft}/${t}`:`${e.stats.sampleSizeLeft}/${e.stats.sampleSizeRight}`}export function reportStyles(){return`
|
|
19
19
|
:root {
|
|
20
20
|
--report-font: Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Die, Wafer } from '../core/index.js';
|
|
2
|
+
import type { TestDef } from '../renderer/buildWaferMap.js';
|
|
3
|
+
import type { TestCapability, TestVerdictYield, RegionYieldFigures, StatsSummary } from './types.js';
|
|
4
|
+
/** Capability over `items` (each one wafer — the subgroup), restricted to `testNumbers`. */
|
|
5
|
+
export declare function computeCapability(items: {
|
|
6
|
+
dies: Die[];
|
|
7
|
+
}[], testDefs: TestDef[] | undefined, testNumbers: readonly number[]): TestCapability[] | undefined;
|
|
8
|
+
/**
|
|
9
|
+
* Lot capability pooled from each wafer's own `stats.capability` — exact, and
|
|
10
|
+
* without revisiting a die. Absent unless every wafer has capability, like the
|
|
11
|
+
* other pooled lot figures.
|
|
12
|
+
*/
|
|
13
|
+
export declare function poolCapability(summaries: StatsSummary[], testDefs: TestDef[]): TestCapability[] | undefined;
|
|
14
|
+
/** Pass rates by the tester's recorded verdict, plus the spec/verdict disagreement count. */
|
|
15
|
+
export declare function computeTestFlagYield(dies: Die[], testDefs: TestDef[] | undefined): {
|
|
16
|
+
testFlagYield?: TestVerdictYield[];
|
|
17
|
+
specVerdictDisagreementDies?: number;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Ring and quadrant yield over one or more wafers, each die judged by its own
|
|
21
|
+
* wafer's pass bins and rings by its own ring count. Rings are pooled only when
|
|
22
|
+
* every wafer has the same ring count — otherwise there are no common rings
|
|
23
|
+
* (the gallery raises `ring-count-mixed` for the same case) and `ring` is absent.
|
|
24
|
+
*/
|
|
25
|
+
export declare function computeRegionYield(maps: {
|
|
26
|
+
dies: Die[];
|
|
27
|
+
wafer: Wafer;
|
|
28
|
+
passBins?: readonly number[];
|
|
29
|
+
ringCount: number;
|
|
30
|
+
}[]): RegionYieldFigures | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Lot region yield pooled from each wafer's own `regionYield`: pass and die
|
|
33
|
+
* counts summed per region key, first-appearance order. Pooled from the
|
|
34
|
+
* summaries rather than recomputed over every die, so a lot analysis given
|
|
35
|
+
* `perWaferSummaries` stays cheap. Same rule as the pass rates: absent unless
|
|
36
|
+
* every wafer has it, and rings pool only when every wafer has the same ring
|
|
37
|
+
* count (the gallery raises `ring-count-mixed` for the same case).
|
|
38
|
+
*/
|
|
39
|
+
export declare function poolRegionYield(summaries: StatsSummary[], ringCounts: readonly number[]): RegionYieldFigures | undefined;
|
|
40
|
+
/** The lot-level pass-rate figures: each wafer's counts, summed per test, only when every wafer has them. */
|
|
41
|
+
export declare function poolPassRates(summaries: StatsSummary[]): {
|
|
42
|
+
testSpecYield?: NonNullable<StatsSummary['stats']['testSpecYield']>;
|
|
43
|
+
functionalYield?: NonNullable<StatsSummary['stats']['functionalYield']>;
|
|
44
|
+
testFlagYield?: TestVerdictYield[];
|
|
45
|
+
specVerdictDisagreementDies?: number;
|
|
46
|
+
};
|
|
47
|
+
//# sourceMappingURL=summaryFigures.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{itemPassBins as D}from"../core/passBins.js";import{buildCapabilityFigures as m,poolCapabilityFigures as h}from"./capability.js";import{buildTestPassRateData as y,hasJudgeableTests as w,poolFunctionalYield as b}from"./testPassRate.js";import{buildRegionYieldData as c,buildRingRegions as Y,buildQuadrantRegions as R}from"./regions.js";export function computeCapability(t,i,n){if(!i?.length||!n.length)return;const o=new Set(n),r=m(t,i.filter(d=>o.has(d.testNumber)));return r.length?r:void 0}export function poolCapability(t,i){const n=t.map(r=>r.stats.capability);if(!n.length||n.some(r=>r===void 0))return;const o=h(n,i);return o.length?o:void 0}export function computeTestFlagYield(t,i){if(!w(i,"testFlag",t))return{};const n=y([{key:"",items:[{dies:t}]}],i,"testFlag"),o={};return n.rows.length&&(o.testFlagYield=n.rows.map(r=>({testNumber:r.testNumber,label:r.label,...r.overall}))),n.disagreementDies!==null&&(o.specVerdictDisagreementDies=n.disagreementDies),o}export function computeRegionYield(t){if(!t.length)return;const i=t.map(l=>l.dies),n=t.map(l=>l.wafer),o=l=>D(t[l]),r=new Set(t.map(l=>l.ringCount)),d=c(i,n,t[0].ringCount,o,R),s=r.size===1?c(i,n,t[0].ringCount,o,Y):void 0;if(!(!d.length&&!s?.length))return{...s?.length?{ring:s}:{},quadrant:d}}export function poolRegionYield(t,i){const n=t.map(s=>s.stats.regionYield);if(!n.length||n.some(s=>s===void 0))return;const o=s=>{if(s.some(a=>a===void 0))return;const l=new Map;for(const a of s)for(const u of a){const f=l.get(u.key);f?(f.n+=u.n,f.passDies+=u.passDies):l.set(u.key,{...u})}const e=[...l.values()];for(const a of e)a.yieldPercent=a.passDies/a.n*100;return e.length?e:void 0},r=o(n.map(s=>s.quadrant)),d=new Set(i).size===1?o(n.map(s=>s.ring)):void 0;if(!(!r&&!d))return{...d?{ring:d}:{},quadrant:r??[]}}function p(t,i,n){if(!t.length||t.some(d=>d===void 0))return;const o=new Map;for(const d of t)for(const s of d??[]){const l=o.get(s.testNumber);l?i(l,s):o.set(s.testNumber,{...s})}if(!o.size)return;const r=[...o.values()];return r.forEach(n),r}const g=(t,i)=>i===0?null:t/i*100;export function poolPassRates(t){const i={},n=p(t.map(e=>e.stats.testSpecYield),(e,a)=>{e.passDies+=a.passDies,e.failLowDies+=a.failLowDies,e.failHighDies+=a.failHighDies,e.totalDies+=a.totalDies},e=>{e.yieldPercent=g(e.passDies,e.totalDies)});n&&(i.testSpecYield=n);const o=(e,a)=>{e.passDies+=a.passDies,e.failDies+=a.failDies,e.totalDies+=a.totalDies},r=e=>{e.passRatePercent=g(e.passDies,e.totalDies)},d=b(t);d&&(i.functionalYield=d);const s=p(t.map(e=>e.stats.testFlagYield),o,r);s&&(i.testFlagYield=s.sort((e,a)=>(e.passRatePercent??1/0)-(a.passRatePercent??1/0)));const l=t.map(e=>e.stats.specVerdictDisagreementDies);return l.length&&l.every(e=>e!==void 0)&&(i.specVerdictDisagreementDies=l.reduce((e,a)=>e+a,0)),i}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { PatternClassification } from './patternClassification.js';
|
|
1
2
|
import type { WaferMapInput, WaferMapResult, WaferWarning } from '../renderer/buildWaferMap.js';
|
|
2
3
|
export type { WaferWarning };
|
|
3
4
|
export type StatsSeverity = 'info' | 'notable' | 'unusual';
|
|
@@ -266,8 +267,108 @@ export interface StatsSummary {
|
|
|
266
267
|
q1: number;
|
|
267
268
|
q3: number;
|
|
268
269
|
}>;
|
|
270
|
+
/**
|
|
271
|
+
* Cp/Cpk/Pp/Ppk for each parametric test with values, worst first: tests with
|
|
272
|
+
* both limits by Ppk, then tests without by variability. Populated under the
|
|
273
|
+
* same conditions as `perTestStats` and over the same tests.
|
|
274
|
+
*/
|
|
275
|
+
capability?: TestCapability[];
|
|
276
|
+
/**
|
|
277
|
+
* Per-test pass rate by the tester's recorded verdict, for parametric tests
|
|
278
|
+
* with recorded verdicts — the counterpart of `testSpecYield` (spec limits)
|
|
279
|
+
* and `functionalYield` (functional tests). Worst first.
|
|
280
|
+
*/
|
|
281
|
+
testFlagYield?: TestVerdictYield[];
|
|
282
|
+
/**
|
|
283
|
+
* Dies whose spec-limit judgement and recorded tester verdict disagree, across
|
|
284
|
+
* every parametric test that has both. Absent when no test has both sources;
|
|
285
|
+
* `0` means both are present and agree everywhere. Not an error — guard bands
|
|
286
|
+
* make disagreement expected — but worth a look.
|
|
287
|
+
*/
|
|
288
|
+
specVerdictDisagreementDies?: number;
|
|
289
|
+
/** Yield by ring and by quadrant (positioned dies only). */
|
|
290
|
+
regionYield?: RegionYieldFigures;
|
|
291
|
+
/**
|
|
292
|
+
* The spatial pattern classifier's result for this wafer — its label, confidence
|
|
293
|
+
* and the geometry features it measured (failure density overall and at the edge,
|
|
294
|
+
* the failing cluster's radial position, eccentricity, linearity, …). Present for
|
|
295
|
+
* every wafer the classifier could measure, including `'random'` and `'none'`,
|
|
296
|
+
* which raise no finding — so the features can train or feed a model of your own,
|
|
297
|
+
* negatives included. Absent when the map has no bin data, or too few failing dies
|
|
298
|
+
* to have a shape (fewer than 5, or 0.3% of the wafer).
|
|
299
|
+
*/
|
|
300
|
+
spatialPattern?: PatternClassification;
|
|
269
301
|
};
|
|
270
302
|
}
|
|
303
|
+
/**
|
|
304
|
+
* Process capability for one parametric test.
|
|
305
|
+
*
|
|
306
|
+
* Cp/Cpk use `stdWithin` — the pooled within-wafer sample stddev, each wafer
|
|
307
|
+
* being the short-term subgroup — and Pp/Ppk use `stdOverall`. For a single
|
|
308
|
+
* wafer there is one subgroup, so `stdWithin` equals `stdOverall` and Cp equals
|
|
309
|
+
* Pp. Partial and edge-excluded dies are excluded, as for yield.
|
|
310
|
+
*/
|
|
311
|
+
export interface TestCapability {
|
|
312
|
+
testNumber: number;
|
|
313
|
+
label: string;
|
|
314
|
+
unit?: string;
|
|
315
|
+
/** Both `limitLow` and `limitHigh` defined. When false, `lsl`/`usl` are absent and every index is null. */
|
|
316
|
+
hasSpec: boolean;
|
|
317
|
+
lsl?: number;
|
|
318
|
+
usl?: number;
|
|
319
|
+
mean: number;
|
|
320
|
+
/** Sample stddev (ddof = 1) over every value. */
|
|
321
|
+
stdOverall: number;
|
|
322
|
+
/** Pooled within-wafer sample stddev. NaN when no wafer contributed two or more values. */
|
|
323
|
+
stdWithin: number;
|
|
324
|
+
/** Values counted. */
|
|
325
|
+
n: number;
|
|
326
|
+
/** Null when `hasSpec` is false or `stdWithin` is NaN or 0. */
|
|
327
|
+
cp: number | null;
|
|
328
|
+
cpk: number | null;
|
|
329
|
+
/** Null when `hasSpec` is false or `stdOverall` is 0. */
|
|
330
|
+
pp: number | null;
|
|
331
|
+
ppk: number | null;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Pass rate for one test, judged by the tester's own recorded verdict
|
|
335
|
+
* (`die.testPass`, STDF PTR `TEST_FLG`) — which exists whether or not spec
|
|
336
|
+
* limits do, and can legitimately disagree with a spec-limit judgement (guard
|
|
337
|
+
* bands, dynamic or per-site limits). Read through `getTestPassStatus`; dies
|
|
338
|
+
* with no recorded verdict are not counted as fails.
|
|
339
|
+
*/
|
|
340
|
+
export interface TestVerdictYield {
|
|
341
|
+
testNumber: number;
|
|
342
|
+
label: string;
|
|
343
|
+
passDies: number;
|
|
344
|
+
failDies: number;
|
|
345
|
+
/** Dies with a recorded verdict for this test. */
|
|
346
|
+
totalDies: number;
|
|
347
|
+
/** `(passDies / totalDies) × 100`, or `null` when no die had a verdict. */
|
|
348
|
+
passRatePercent: number | null;
|
|
349
|
+
}
|
|
350
|
+
/** Yield of one wafer region, each die judged by its own wafer's pass bins. */
|
|
351
|
+
export interface RegionYield {
|
|
352
|
+
/** Stable identity, e.g. `"ring:2"` or `"quadrant:NE"`. Use it as a key; do not parse it. */
|
|
353
|
+
key: string;
|
|
354
|
+
/** Display name, e.g. `"Ring 4 (edge)"` or `"NE"`. */
|
|
355
|
+
label: string;
|
|
356
|
+
/** `(pass / n) × 100`, in [0, 100]. */
|
|
357
|
+
yieldPercent: number;
|
|
358
|
+
/** Yield-eligible dies with a bin in this region. */
|
|
359
|
+
n: number;
|
|
360
|
+
/** Of those, the dies that pass — so regions can be pooled exactly. */
|
|
361
|
+
passDies: number;
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Yield by ring and by quadrant — the Summary panel's region yield. Regions with
|
|
365
|
+
* no eligible dies are omitted. `ring` is absent from a lot summary whose wafers
|
|
366
|
+
* were built with different ring counts, which have no common rings to pool.
|
|
367
|
+
*/
|
|
368
|
+
export interface RegionYieldFigures {
|
|
369
|
+
ring?: RegionYield[];
|
|
370
|
+
quadrant: RegionYield[];
|
|
371
|
+
}
|
|
271
372
|
export interface LotStatsSummary {
|
|
272
373
|
/** Discriminant — always `'lot'`. Narrow on this to tell a lot summary from a {@link StatsSummary}. */
|
|
273
374
|
level: 'lot';
|
|
@@ -302,6 +403,23 @@ export interface LotStatsSummary {
|
|
|
302
403
|
/** Engine-computed analysis stats for this lot. */
|
|
303
404
|
stats: {
|
|
304
405
|
waferCount: number;
|
|
406
|
+
/**
|
|
407
|
+
* Cp/Cpk/Pp/Ppk over the whole lot, each wafer as a subgroup — so `stdWithin`
|
|
408
|
+
* is the pooled within-wafer stddev and Cp and Pp genuinely differ. Present
|
|
409
|
+
* when per-test statistics were computed (`computePerTestStats` or
|
|
410
|
+
* `enableTestValueAnalysis`). Worst first.
|
|
411
|
+
*/
|
|
412
|
+
capability?: TestCapability[];
|
|
413
|
+
/** `testSpecYield` pooled over the lot: counts summed per test. Absent unless every wafer has it. */
|
|
414
|
+
testSpecYield?: NonNullable<StatsSummary['stats']['testSpecYield']>;
|
|
415
|
+
/** `functionalYield` pooled over the lot: counts summed per test. Absent unless every wafer has it. */
|
|
416
|
+
functionalYield?: NonNullable<StatsSummary['stats']['functionalYield']>;
|
|
417
|
+
/** `testFlagYield` pooled over the lot: counts summed per test. Absent unless every wafer has it. */
|
|
418
|
+
testFlagYield?: TestVerdictYield[];
|
|
419
|
+
/** `specVerdictDisagreementDies` summed over the lot. Absent unless every wafer has it. */
|
|
420
|
+
specVerdictDisagreementDies?: number;
|
|
421
|
+
/** Ring and quadrant yield pooled over the lot: counts summed per region, each wafer judged by its own pass bins. Absent unless every wafer has it; `ring` also absent when ring counts differ. */
|
|
422
|
+
regionYield?: RegionYieldFigures;
|
|
305
423
|
};
|
|
306
424
|
/** Per-wafer yield as a flat series, ordered by waferIndex. `yieldPercent` is in [0, 100]; null when a wafer had no bin data. */
|
|
307
425
|
lotYieldSeries: Array<{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wafertools/wafermap",
|
|
3
|
-
"version": "0.30.
|
|
3
|
+
"version": "0.30.1",
|
|
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",
|