@wafertools/wafermap 0.22.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +16 -5
- package/CHANGELOG.md +101 -0
- package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
- package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
- package/dist/packages/canvas-adapter/dieList.d.ts +41 -0
- package/dist/packages/canvas-adapter/dieList.js +2 -0
- package/dist/packages/canvas-adapter/index.d.ts +2 -0
- package/dist/packages/canvas-adapter/index.js +1 -1
- package/dist/packages/canvas-adapter/maplessSummary.d.ts +34 -0
- package/dist/packages/canvas-adapter/maplessSummary.js +1 -0
- package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +2 -18
- package/dist/packages/canvas-adapter/renderWaferGallery.js +8 -8
- package/dist/packages/canvas-adapter/renderWaferMap.d.ts +17 -1
- package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
- package/dist/packages/canvas-adapter/summaryPanel.d.ts +2 -0
- package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
- package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
- package/dist/packages/canvas-adapter/userGuideHtml.js +1 -1
- package/dist/packages/canvas-adapter/version.d.ts +2 -2
- package/dist/packages/canvas-adapter/version.js +1 -1
- package/dist/packages/canvas-adapter/warnings.d.ts +6 -1
- package/dist/packages/core/classify.d.ts +6 -2
- package/dist/packages/core/dies.d.ts +67 -7
- package/dist/packages/core/dies.js +1 -1
- package/dist/packages/core/probe.d.ts +5 -0
- package/dist/packages/core/probe.js +1 -1
- package/dist/packages/core/transforms.d.ts +4 -4
- package/dist/packages/core/transforms.js +1 -1
- package/dist/packages/renderer/buildView.d.ts +3 -3
- package/dist/packages/renderer/buildView.js +1 -1
- package/dist/packages/renderer/buildWaferMap.d.ts +25 -8
- package/dist/packages/renderer/buildWaferMap.js +1 -1
- package/dist/packages/stats/analyzeWaferMap.js +1 -1
- package/dist/packages/stats/clusterDetection.d.ts +2 -2
- package/dist/packages/stats/patternClassification.d.ts +2 -2
- package/dist/packages/stats/regions.d.ts +6 -6
- package/dist/packages/stats/regions.js +1 -1
- package/dist/packages/stats/renderSummaryReport.js +17 -17
- package/llms.txt +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Die, type Wafer } from '../core/index.js';
|
|
1
|
+
import { type Die, type PositionedDie, type Wafer } from '../core/index.js';
|
|
2
2
|
import type { ReticleConfig } from '../renderer/buildWaferMap.js';
|
|
3
3
|
export interface StatsRegion {
|
|
4
4
|
family: 'ring' | 'quadrant' | 'reticle-position' | 'test-site' | 'sector';
|
|
@@ -22,7 +22,7 @@ export interface RegionYieldDatum {
|
|
|
22
22
|
* the ring/quadrant yield diagrams, which previously each recomputed the
|
|
23
23
|
* same pass/total tally independently.
|
|
24
24
|
*/
|
|
25
|
-
export declare function buildRegionYieldData(diesByWafer: Die[][], allWafers: Wafer[], ringCount: number, passBins: number[], regionBuilder: (dies:
|
|
25
|
+
export declare function buildRegionYieldData(diesByWafer: Die[][], allWafers: Wafer[], ringCount: number, passBins: number[], regionBuilder: (dies: PositionedDie[], wafer: Wafer, ringCount: number) => StatsRegion[]): RegionYieldDatum[];
|
|
26
26
|
/** Compass bearing names for a given sector count, ordered CCW from East. */
|
|
27
27
|
export declare function sectorCompassNames(sectorCount: number): string[];
|
|
28
28
|
/** True when two quadrants share an edge (NE–NW, NE–SE, NW–SW, SE–SW); diagonals are not adjacent. */
|
|
@@ -38,9 +38,9 @@ export interface ParsedRegionKey {
|
|
|
38
38
|
* structured parts. Always parse identity from the key, never from the label.
|
|
39
39
|
*/
|
|
40
40
|
export declare function parseRegionKey(key: string): ParsedRegionKey;
|
|
41
|
-
export declare function buildRingRegions(dies:
|
|
42
|
-
export declare function buildQuadrantRegions(dies:
|
|
43
|
-
export declare function buildReticlePositionRegions(dies:
|
|
41
|
+
export declare function buildRingRegions(dies: PositionedDie[], wafer: Wafer, ringCount: number): StatsRegion[];
|
|
42
|
+
export declare function buildQuadrantRegions(dies: PositionedDie[], wafer: Wafer, ringCount: number): StatsRegion[];
|
|
43
|
+
export declare function buildReticlePositionRegions(dies: PositionedDie[], reticleConfig: ReticleConfig | undefined): StatsRegion[];
|
|
44
44
|
/**
|
|
45
45
|
* Group dies by siteNum for parallel test site analysis.
|
|
46
46
|
*
|
|
@@ -53,5 +53,5 @@ export declare function buildReticlePositionRegions(dies: Die[], reticleConfig:
|
|
|
53
53
|
* validated the data).
|
|
54
54
|
*/
|
|
55
55
|
export declare function buildTestSiteRegions(dies: Die[], forceEnable?: boolean): StatsRegion[];
|
|
56
|
-
export declare function buildSectorRegions(dies:
|
|
56
|
+
export declare function buildSectorRegions(dies: PositionedDie[], wafer: Wafer, sectorCount: number): StatsRegion[];
|
|
57
57
|
//# sourceMappingURL=regions.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{classifyDie as E,getReticleCell as W,getRingLabel as b,getDieKey as
|
|
1
|
+
import{classifyDie as E,getReticleCell as W,getRingLabel as b,isPositionedDie as M,getDieKey as k}from"../core/index.js";function y(t){return k(t)}export function buildRegionYieldData(t,s,o,e,n){const i=new Set(e),r=new Map,a=[];for(let l=0;l<s.length;l++){const c=t[l];if(!c?.length)continue;const d=n(c.filter(M),s[l],o),S=new Map(c.map(u=>[y(u),u]));for(const u of d){a.includes(u.key)||a.push(u.key);const g=r.get(u.key)??{label:u.label,pass:0,total:0};for(const h of u.dieKeys){const f=S.get(h);if(!f||f.partial||f.edgeExcluded)continue;const p=f.hbin??f.sbin;p!=null&&(g.total++,i.has(p)&&g.pass++)}r.set(u.key,g)}}return a.map(l=>{const c=r.get(l);return c.total===0?null:{key:l,label:c.label,n:c.total,yieldPercent:c.pass/c.total*100}}).filter(l=>l!==null)}const x=["E","ENE","NE","NNE","N","NNW","NW","WNW","W","WSW","SW","SSW","S","SSE","SE","ESE"],R=["E","NE","N","NW","W","SW","S","SE"],K=["E","N","W","S"];export function sectorCompassNames(t){const s=[4,8,16,32].includes(t)?t:16;return s===4?K:s===8?R:x}const q={NE:["NW","SE"],NW:["NE","SW"],SE:["NE","SW"],SW:["NW","SE"]};export function areQuadrantsAdjacent(t,s){return q[t]?.includes(s)??!1}export function parseRegionKey(t){return t.startsWith("ring:")?{family:"ring",ring:Number(t.slice(5))}:t.startsWith("quadrant:")?{family:"quadrant",quadrant:t.slice(9)}:t.startsWith("sector:")?{family:"sector",sector:t.slice(7)}:{family:"unknown"}}export function buildRingRegions(t,s,o){const e=new Map;for(const n of t){const{ring:i}=E(n,s,{ringCount:o}),r=`ring:${i}`,a=e.get(r)??{family:"ring",key:r,label:b(i,o),dieKeys:[]};a.dieKeys.push(y(n)),e.set(r,a)}return[...e.values()].sort((n,i)=>n.key.localeCompare(i.key))}export function buildQuadrantRegions(t,s,o){const e=new Map;for(const i of t){const{quadrant:r}=E(i,s,{ringCount:o}),a=`quadrant:${r}`,l=e.get(a)??{family:"quadrant",key:a,label:r,dieKeys:[]};l.dieKeys.push(y(i)),e.set(a,l)}const n=new Map([["quadrant:NE",0],["quadrant:NW",1],["quadrant:SE",2],["quadrant:SW",3]]);return[...e.values()].sort((i,r)=>(n.get(i.key)??4)-(n.get(r.key)??4))}export function buildReticlePositionRegions(t,s){if(!s)return[];const o=new Map;for(const e of t){const{column:n,row:i}=W(e,s),r=`reticle-position:cell:${n},${i}`,a=o.get(r)??{family:"reticle-position",key:r,label:`Reticle cell (${n}, ${i})`,dieKeys:[]};a.dieKeys.push(y(e)),o.set(r,a)}return[...o.values()].sort((e,n)=>e.key.localeCompare(n.key))}const w=3;export function buildTestSiteRegions(t,s=!1){const o=new Map;for(const e of t){if(e.siteNum===void 0)continue;const n=o.get(e.siteNum)??[];n.push(y(e)),o.set(e.siteNum,n)}if(!s){let e=0;for(const n of o.values())n.length>=w&&e++;if(e<2)return[]}return[...o.entries()].sort(([e],[n])=>e-n).map(([e,n])=>({family:"test-site",key:`test-site:${e}`,label:`Site ${e}`,dieKeys:n}))}export function buildSectorRegions(t,s,o){const e=[4,8,16,32].includes(o)?o:16,n=sectorCompassNames(e),i=new Map,r=s.center.x,a=s.center.y,l=s.radius;for(const c of t){const d=c.physX-r,S=c.physY-a;if(Math.hypot(d,S)/l<.2)continue;const g=Math.PI/e,h=(Math.atan2(S,d)+2*Math.PI+g)%(2*Math.PI),f=Math.floor(h/(2*Math.PI)*e)%e,p=n[f],N=`sector:${p}`,m=i.get(N)??{family:"sector",key:N,label:`Sector ${p}`,dieKeys:[]};m.dieKeys.push(y(c)),i.set(N,m)}return[...i.values()].sort((c,d)=>c.key.localeCompare(d.key))}
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import{isParametricTest as
|
|
2
|
-
<td class="tight">${
|
|
1
|
+
import{isParametricTest as ct}from"../renderer/buildWaferMap.js";import{buildRingRegions as H,buildQuadrantRegions as L}from"./regions.js";import{openHtmlReport as dt}from"./renderFindingsReport.js";import{analyzeWaferLot as ut}from"./analyzeWaferLot.js";import{computeFunctionalYield as ft}from"./analyzeWaferMap.js";import{buildCapabilityData as mt}from"./capability.js";import{fmt as M}from"../renderer/fmt.js";import{getDieKey as W,isPositionedDie as k}from"../core/dies.js";import{formatFindingDelta as pt,formatFindingCoverage as gt,formatFindingTooltip as ht,escHtml as h,renderMetadataSection as I,renderMetricGrid as E,renderSection as $,renderSeverityBadge as bt,renderTable as D,reportStyles as R}from"./reportHtml.js";function Y(a,r){return r===0?"\u2014":`${(a/r*100).toFixed(1)}%`}function St(a){if(!a)return"";const r=a.lot??a.lotId,i=a.wafer??a.waferId,n=[r,i].filter(Boolean).map(String);return n.length?` \u2014 ${n.map(h).join(" \xB7 ")}`:""}function Rt(a,r){const i=[{label:"Total dies",value:String(a.totalDies)},{label:"Filled dies",value:String(r.filledDies),hint:`${Y(r.filledDies,r.totalDies)} fill`},{label:"Pass dies",value:String(a.passDies)},{label:"Fail dies",value:String(a.failDies)},...a.edgeExcludedDies>0?[{label:"Edge excluded",value:String(a.edgeExcludedDies)}]:[],...a.partialDies>0?[{label:"Partial dies",value:String(a.partialDies)}]:[],{label:"Yield",value:a.yieldPercent!==null?`${a.yieldPercent.toFixed(1)}%`:"N/A"}];return $("Yield",E(i))}function j(a,r,i,n){const l=new Map;if(n)for(const[e,o]of Object.entries(n))l.set(Number(e),o);else for(const e of a){if(e.partial||e.edgeExcluded)continue;const o=i==="hard"?e.hbin:e.sbin;o!=null&&l.set(o,(l.get(o)??0)+1)}if(!l.size)return"";const t=[...l.values()].reduce((e,o)=>e+o,0),s=r?new Map(r.map(e=>[e.bin,e])):null,c=[...l.entries()].sort((e,o)=>e[0]-o[0]).map(([e,o])=>{const p=s?.get(e)?.name??"\u2014";return[String(e),p,String(o),Y(o,t)]});return $(i==="hard"?"Hard Bin Breakdown":"Soft Bin Breakdown",D(["Bin","Name","Count","%"],c,{className:"compact"}))}function z(a,r,i,n){const l=new Set(n),t=new Map(i.map(d=>[W(d),d]));if(!i.some(d=>d.hbin!=null||d.sbin!=null)||!r.length)return"";const c=[];for(const d of r){let e=0,o=0;for(const m of d.dieKeys){const p=t.get(m);if(!p||p.partial||p.edgeExcluded)continue;const g=p.hbin??p.sbin;g!=null&&(o++,l.has(g)&&e++)}o&&c.push([d.label,String(e),String(o),Y(e,o)])}return c.length?$(a,D(["Region","Pass","Total","Yield"],c,{className:"compact"})):""}function A(a,r,i){if(!r.length)return"";const n=a.filter(s=>!s.partial&&!s.edgeExcluded),l=new Map((i??[]).map(s=>[s.testNumber,s])),t=[];for(const s of r.filter(ct)){const c=s.testNumber,d=s.unit||void 0,e=l.get(c);let o,m,p;if(e)({min:o,max:m,mean:p}=e);else{const g=n.map(b=>b.testValues?.[c]).filter(b=>b!==void 0&&isFinite(b));if(!g.length)continue;g.sort((b,u)=>b-u),o=g[0],m=g[g.length-1],p=g.reduce((b,u)=>b+u,0)/g.length}t.push([h(s.name),M(o,d),M(p,d),M(m,d)])}return t.length?$("Test Values",D(["Test","Min","Mean","Max"],t,{className:"compact"})):""}function G(a,r,i){const n=i??ft(a,r);if(!n?.length)return"";const l=n.map(t=>[h(t.label),String(t.totalDies),String(t.passDies),String(t.failDies),t.passRatePercent!==null?`${t.passRatePercent.toFixed(1)}%`:"\u2014"]);return $("Functional Tests",D(["Test","N","Pass","Fail","Pass Rate"],l,{className:"compact"}))}function O(a,r){const i=mt(a,r);if(!i.length)return"";const n=t=>t===null?"\u2014":t.toFixed(2),l=i.map(t=>[h(t.label),t.hasSpec?`${M(t.lsl,t.unit)} \u2013 ${M(t.usl,t.unit)}`:"\u2014",M(t.mean,t.unit),n(t.cp),n(t.cpk),n(t.pp),n(t.ppk)]);return $("Process Capability",D(["Test","Spec (LSL\u2013USL)","Mean","Cp","Cpk","Pp","Ppk"],l,{className:"compact"}))}function K(a,r){if(!a.length)return"";const i=a.map(t=>`<tr title="${h(ht(t))}">
|
|
2
|
+
<td class="tight">${bt(t.severity)}</td>
|
|
3
3
|
<td class="tight">${h(t.comparison.left)}</td>
|
|
4
4
|
<td>${h(t.variable.label)}</td>
|
|
5
|
-
<td class="numeric">${h(
|
|
6
|
-
<td class="numeric">${h(
|
|
5
|
+
<td class="numeric">${h(pt(t))}</td>
|
|
6
|
+
<td class="numeric">${h(gt(t,r))}</td>
|
|
7
7
|
</tr>`).join(`
|
|
8
8
|
`),l=`<table class="report-table findings-table compact"><thead><tr>
|
|
9
9
|
<th>Severity</th><th>Region</th><th>Metric</th><th class="numeric">Delta</th><th class="numeric">${r!==void 0?"Wafers":"N (region/rest)"}</th>
|
|
10
|
-
</tr></thead><tbody>${i}</tbody></table>`;return $("Findings",l)}export function renderSummaryReportHtml(
|
|
11
|
-
`)),w?
|
|
10
|
+
</tr></thead><tbody>${i}</tbody></table>`;return $("Findings",l)}export function renderSummaryReportHtml(a,r={}){const{wafer:i,dies:n,yieldSummary:l,dataCoverage:t,hbinDefs:s,sbinDefs:c,testDefs:d=[],statsSummary:e,passBins:o=[1],ringCount:m=4}=a,p=i.metadata,g=St(p),b=r.title??`Wafer Summary${g}`,u=new Date().toLocaleString(),w=n.some(B=>B.hbin!=null),x=n.some(B=>B.sbin!=null),S=n.filter(k),y=H(S,i,m),v=L(S,i,m),P=[{label:"Total dies",value:String(l.totalDies)},...l.partialDies>0?[{label:"Partial",value:String(l.partialDies)}]:[],...l.yieldPercent!==null?[{label:`Yield (pass: ${o.length===1?`bin ${o[0]}`:`bins ${o.join(", ")}`})`,value:`${l.yieldPercent.toFixed(1)}%`}]:[],...l.edgeExcludedDies>0?[{label:"Edge excluded (outer zone)",value:String(l.edgeExcludedDies)}]:[]],N=[I([{metadata:i.metadata}]),$("Summary",[E(P)].filter(Boolean).join(`
|
|
11
|
+
`)),w?j(n,s,"hard",e?.stats.hardBinCounts):x?j(n,c,"soft",e?.stats.softBinCounts):"",z("Ring Yield",y,n,o),z("Quadrant Yield",v,n,o),A(n,d,e?.stats.perTestStats),G(n,d,e?.stats.functionalYield),O([{dies:n}],d),e?K(e.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(b)}</title>
|
|
17
17
|
<style>
|
|
18
|
-
${
|
|
18
|
+
${R()}
|
|
19
19
|
</style>
|
|
20
20
|
</head>
|
|
21
21
|
<body>
|
|
@@ -24,31 +24,31 @@ ${E()}
|
|
|
24
24
|
<h1>${h(b)}</h1>
|
|
25
25
|
<p class="report-subtitle">Generated ${h(u)}</p>
|
|
26
26
|
</header>
|
|
27
|
-
${
|
|
27
|
+
${N}
|
|
28
28
|
<p class="footer">Generated ${h(u)}</p>
|
|
29
29
|
</main>
|
|
30
30
|
</body>
|
|
31
|
-
</html>`}function
|
|
32
|
-
`);return{lotSummary:c,sections:
|
|
31
|
+
</html>`}function yt(a,r){const i=a.perWafer.map(n=>{const l=r[n.waferIndex]?.label??`W${n.waferIndex+1}`,t=n.summary.stats.yieldPercent;return[l,t!==null?`${t.toFixed(1)}%`:"N/A"]});return D(["Wafer","Yield"],i,{className:"compact"})}function wt(a){const r=a.map(i=>[i.label,i.wafer?.metadata?.split]).filter(i=>!!i[1]).map(([i,n])=>[h(i),h(n)]);return r.length?$("Splits",D(["Wafer","Split"],r,{className:"compact"})):""}function U(a,r,i,n){const l=new Map,t=i==="hard"?"hardBinCounts":"softBinCounts";if(n?.length&&n.every(e=>e.stats[t]!==void 0))for(const e of n)for(const[o,m]of Object.entries(e.stats[t])){const p=Number(o);l.set(p,(l.get(p)??0)+m)}else for(const e of a){if(e.partial||e.edgeExcluded)continue;const o=i==="hard"?e.hbin:e.sbin;o!=null&&l.set(o,(l.get(o)??0)+1)}if(!l.size)return"";const s=[...l.values()].reduce((e,o)=>e+o,0),c=r?new Map(r.map(e=>[e.bin,e])):null,d=[...l.entries()].sort((e,o)=>e[0]-o[0]).map(([e,o])=>{const m=c?.get(e);return[m?.name?`Bin ${e} \xB7 ${m.name} (${o})`:`Bin ${e} (${o})`,Y(o,s)]});return $(i==="hard"?"Hard Bin Breakdown (All Wafers)":"Soft Bin Breakdown (All Wafers)",D(["Bin","Yield"],d,{className:"compact"}))}function Q(a,r,i,n,l,t){const s=new Set(t),c=new Map,d=[];for(let o=0;o<n.length;o++){const m=n[o],p=i[o]??[];if(!p.length)continue;const g=r(p.filter(k),m,l),b=new Map(p.map(u=>[W(u),u]));for(const u of g){d.includes(u.label)||d.push(u.label);const w=c.get(u.label)??{pass:0,total:0};for(const x of u.dieKeys){const S=b.get(x);if(!S||S.partial||S.edgeExcluded)continue;const y=S.hbin??S.sbin;y!=null&&(w.total++,s.has(y)&&w.pass++)}c.set(u.label,w)}}if(!c.size)return"";const e=d.filter(o=>c.get(o)?.total).map(o=>{const m=c.get(o);return[o,Y(m.pass,m.total)]});return e.length?$(a,D(["Region","Yield"],e,{className:"compact"})):""}function $t(a,r,i){let n;if(i?.length&&i.every(l=>l.stats.perTestStats!==void 0)){const l=new Map;for(const t of i)for(const s of t.stats.perTestStats??[]){const c=l.get(s.testNumber);c?(c.n+=s.count,c.sum+=s.mean*s.count,c.min=Math.min(c.min,s.min),c.max=Math.max(c.max,s.max)):l.set(s.testNumber,{n:s.count,sum:s.mean*s.count,min:s.min,max:s.max})}n=[...l.entries()].map(([t,s])=>({testNumber:t,min:s.min,max:s.max,mean:s.sum/s.n}))}return A(a,r,n)}function xt(a,r,i){let n;if(i?.length&&i.every(l=>l.stats.functionalYield!==void 0)){const l=new Map;for(const t of i)for(const s of t.stats.functionalYield??[]){const c=l.get(s.testNumber)??{label:s.label,passDies:0,failDies:0,totalDies:0};c.passDies+=s.passDies,c.failDies+=s.failDies,c.totalDies+=s.totalDies,l.set(s.testNumber,c)}n=[...l.entries()].map(([t,s])=>({testNumber:t,...s,passRatePercent:s.totalDies>0?s.passDies/s.totalDies*100:null}))}return G(a,r,n?.length?n:void 0)}const C=["lot","product","testProgram","temperature"];function Dt(a){const r=new Map(C.map(t=>[t,new Set]));for(const t of a)for(const s of C){const c=t.wafer?.metadata?.[s];c!=null&&c!==""&&r.get(s).add(String(c))}const i=C.filter(t=>r.get(t).size>1);if(i.length===0)return[{label:"",items:a,varying:i}];const n=new Map,l=[];for(const t of a){const s=i.map(c=>String(t.wafer?.metadata?.[c]??"(none)")).join(" \xB7 ");n.has(s)||(n.set(s,[]),l.push(s)),n.get(s).push(t)}return l.map(t=>({label:t,items:n.get(t),varying:i}))}function q(a,r,i,n,l,t,s){const c=ut(a,{perWaferSummaries:a.map(f=>f.statsSummary),...s}),d=[],e=[],o=[];for(const f of a){const T=f.dies??[];f.wafer&&(d.push(f.wafer),o.push(T)),T.length&&e.push(...T)}const m=e.some(f=>f.hbin!=null),p=e.some(f=>f.sbin!=null),g=m||p,b=c.perWafer.map(f=>f.summary.stats.yieldPercent).filter(f=>f!==null),u=b.length?b.reduce((f,T)=>f+T,0)/b.length:null,w=new Set(l);let x=0,S=0,y=0,v=0,P=0;for(const f of e){if(x++,f.edgeExcluded&&v++,f.partial&&P++,f.partial||f.edgeExcluded)continue;const T=f.hbin??f.sbin;T!=null&&(S++,w.has(T)&&y++)}const N=S-y,B=S>0?y/S*100:null,_=[{label:"Wafers",value:String(c.stats.waferCount)},{label:"Total dies",value:String(x)},...g?[{label:"Good dies",value:String(y)},{label:"Bad dies",value:String(N)}]:[],...v>0?[{label:"Edge excluded",value:String(v)}]:[],...P>0?[{label:"Partial dies",value:String(P)}]:[],...u!==null?[{label:"Mean wafer yield",value:`${u.toFixed(1)}%`}]:[],...B!==null?[{label:"Total yield",value:`${B.toFixed(1)}%`}]:[]],J=$("Lot Summary",E(_)),X=I(a.map(f=>({metadata:f.wafer?.metadata}))),Z=$("Per-Wafer Yield",yt(c,a)),tt=wt(a),F=c.perWafer.map(f=>f.summary),et=g?m?U(e,r,"hard",F):U(e,i,"soft",F):"",nt=g&&d.length?Q("Ring Yield (All Wafers)",H,o,d,t,l):"",st=g&&d.length?Q("Quadrant Yield (All Wafers)",L,o,d,t,l):"",at=n.length?$t(e,n,F):"",ot=n.length?xt(e,n,F):"",it=n.length?O(a,n):"",lt=c.findings.length?K(c.findings,c.stats.waferCount):"",rt=[J,X,Z,tt,et,nt,st,at,ot,it,lt].filter(Boolean).join(`
|
|
32
|
+
`);return{lotSummary:c,sections:rt}}function V(a,r,i){return`<main class="report">
|
|
33
33
|
<header class="report-header">
|
|
34
|
-
<h1>${h(
|
|
34
|
+
<h1>${h(a)}</h1>
|
|
35
35
|
<p class="report-subtitle">Generated ${h(i)}</p>
|
|
36
36
|
</header>
|
|
37
37
|
${r}
|
|
38
38
|
<p class="footer">Generated ${h(i)}</p>
|
|
39
|
-
</main>`}export function renderLotSummaryReportHtml(
|
|
39
|
+
</main>`}export function renderLotSummaryReportHtml(a,r={}){const{items:i,hbinDefs:n,sbinDefs:l,testDefs:t=[],passBins:s=[1],ringCount:c=4,analyzeOptions:d}=a,e=new Date().toLocaleString(),o=Dt(i);if(o.length===1){const{lotSummary:u,sections:w}=q(o[0].items,n,l,t,s,c,d),x=(()=>{const y=u.lot;if(!y)return"";const v=y.lot??y.lotId;return v?` \u2014 ${h(String(v))}`:""})(),S=r.title??`Lot Summary${x}`;return`<!DOCTYPE html>
|
|
40
40
|
<html lang="en">
|
|
41
41
|
<head>
|
|
42
42
|
<meta charset="UTF-8">
|
|
43
43
|
<title>${h(S)}</title>
|
|
44
44
|
<style>
|
|
45
|
-
${
|
|
45
|
+
${R()}
|
|
46
46
|
</style>
|
|
47
47
|
</head>
|
|
48
48
|
<body>
|
|
49
|
-
${
|
|
49
|
+
${V(S,w,e)}
|
|
50
50
|
</body>
|
|
51
|
-
</html>`}const m=r.title??"Lot Summary",p=
|
|
51
|
+
</html>`}const m=r.title??"Lot Summary",p=o.map(u=>{const{lotSummary:w,sections:x}=q(u.items,n,l,t,s,c,d),S=u.varying.includes("lot")?void 0:w.lot?.lot??w.lot?.lotId,y=S?`${u.label} \xB7 Lot ${String(S)}`:u.label;return V(`${m} \u2014 ${y}`,x,e)}),g=`<p style="max-width:1080px;margin:0 auto 8px;padding:8px 12px;background:var(--report-surface,#f7f8fa);border:1px solid var(--report-line,#d8dee6);border-radius:4px;font-size:12px;color:var(--report-muted,#5c6570);">This load spans ${o.length} groups by identity \u2014 shown separately below so stats are never pooled across them.</p>`,b=p.map((u,w)=>`${w===0?"":'<hr style="max-width:1080px;margin:32px auto 0;border:none;border-top:3px solid var(--report-line-strong,#c7ced8);">'}
|
|
52
52
|
${u}`).join(`
|
|
53
53
|
`);return`<!DOCTYPE html>
|
|
54
54
|
<html lang="en">
|
|
@@ -56,7 +56,7 @@ ${u}`).join(`
|
|
|
56
56
|
<meta charset="UTF-8">
|
|
57
57
|
<title>${h(m)}</title>
|
|
58
58
|
<style>
|
|
59
|
-
${
|
|
59
|
+
${R()}
|
|
60
60
|
</style>
|
|
61
61
|
</head>
|
|
62
62
|
<body>
|
|
@@ -65,4 +65,4 @@ ${g}
|
|
|
65
65
|
</div>
|
|
66
66
|
${b}
|
|
67
67
|
</body>
|
|
68
|
-
</html>`}export{
|
|
68
|
+
</html>`}export{dt as openHtmlReport};
|
package/llms.txt
CHANGED
|
@@ -22,7 +22,7 @@ where repo-relative paths do not resolve.
|
|
|
22
22
|
|
|
23
23
|
## Important notes
|
|
24
24
|
- `x` and `y` in `DieResult` are die grid positions (prober step integers), not millimetres.
|
|
25
|
-
- Use `testValues: Record<number, number>` (keyed by stable test number)
|
|
25
|
+
- Use `testValues: Record<number, number>` (keyed by stable test number). Example: `testValues: { 1050: 0.95 }`. The positional `values: number[]` array was **removed** in 0.21.0 — passing it is a type error and is ignored at runtime; do not add a compatibility shim.
|
|
26
26
|
- Hard bins go in `hbin`; soft bins go in `sbin`. Their number spaces are independent. Never merge them, and never fall back to `?? 0` — a missing bin is not bin 0.
|
|
27
27
|
- All coordinates shown to the user (axis ticks, tooltips) must be original die grid positions (`die.x` / `die.y`), not post-transform display coordinates.
|
|
28
28
|
- `buildWaferMap()` is pure and server-safe; renderers require the DOM.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wafertools/wafermap",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.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",
|