@wafertools/wafermap 0.23.0 → 0.24.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 (29) hide show
  1. package/CHANGELOG.md +109 -1
  2. package/README.md +1 -1
  3. package/dist/packages/canvas-adapter/charts/chartShell.d.ts +1 -0
  4. package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
  5. package/dist/packages/canvas-adapter/dieList.d.ts +83 -17
  6. package/dist/packages/canvas-adapter/dieList.js +13 -2
  7. package/dist/packages/canvas-adapter/index.d.ts +1 -1
  8. package/dist/packages/canvas-adapter/insightsTab.js +1 -1
  9. package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +10 -0
  10. package/dist/packages/canvas-adapter/renderWaferGallery.js +2 -2
  11. package/dist/packages/canvas-adapter/renderWaferMap.d.ts +13 -0
  12. package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
  13. package/dist/packages/canvas-adapter/summaryPanel.d.ts +36 -3
  14. package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
  15. package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
  16. package/dist/packages/canvas-adapter/userGuideHtml.js +1 -1
  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/inference/pitch.js +1 -1
  20. package/dist/packages/core/metadata.d.ts +29 -0
  21. package/dist/packages/core/metadata.js +1 -1
  22. package/dist/packages/renderer/buildView.d.ts +8 -0
  23. package/dist/packages/renderer/buildView.js +1 -1
  24. package/dist/packages/stats/facets.js +1 -1
  25. package/dist/packages/stats/index.d.ts +2 -0
  26. package/dist/packages/stats/index.js +1 -1
  27. package/dist/packages/stats/metadataColumns.d.ts +70 -0
  28. package/dist/packages/stats/metadataColumns.js +1 -0
  29. package/package.json +11 -6
@@ -1 +1 @@
1
- import{hasPosition as Qt}from"../core/dies.js";import{getReticleCell as Ut}from"../core/reticle.js";import{affineRotation as ft,affineMirror as Vt,affineCompose as at,affinePoint as X,affineVector as dt,affineSwapsAxes as Jt}from"../core/transforms.js";import{contrastTextColor as Y,SPEC_PASS_FILL as ht,SPEC_FAIL_LOW as Zt,SPEC_FAIL_HIGH as pt}from"./colorMap.js";import{getColorScheme as te}from"./colorSchemes.js";import{getDieTestValue as R,getTestPassStatus as G,isParametricTest as mt}from"./buildWaferMap.js";import{fmt as yt,fmtColorbarAxis as gt,fmtAggregationMethod as Rt}from"./fmt.js";import{metadataValueColor as ee}from"./colorMap.js";import{compareNatural as ne,clamp01 as Ct}from"../core/utils.js";import{prettyKey as ae}from"../core/utils.js";export function resolveTestNumber(t,e){return e?.length?{testNumber:(findTestDef(e,t)??e[0]).testNumber}:{testNumber:t}}export function findTestDef(t,e){return t?.find(n=>n.testNumber===e)}export function getUniqueTestNumbers(t){return[...new Set(t.flatMap(e=>e.testValues?Object.keys(e.testValues).map(Number):[]))].sort((e,n)=>e-n)}const ie="#d3d6db",Nt="#e8e9ea",se="#eceef0",w="#d6d9dd";function it(t,e){const n=e?t.metadata?.[e]:void 0;return n!=null&&(typeof n=="string"||typeof n=="number"||typeof n=="boolean")?String(n):void 0}export function classifySpec(t,e){return t===void 0?null:e?.limitLow!==void 0&&t<e.limitLow?"failLow":e?.limitHigh!==void 0&&t>e.limitHigh?"failHigh":"pass"}function oe(t,e,n){const{x:i,y:s}=t.center,a=ft(t.orientation,i,s),o=at(Vt(e?.x??!1,e?.y??!1,i,s),ft(t.orientation,i,s)),l=at(Vt(n?.flipX??!1,n?.flipY??!1,i,s),ft(n?.rotation??0,i,s));return{bakedToScreen:l,physicalToScreen:at(l,a),gridToScreen:at(l,o)}}function _(t,e=!1){return{points:[t],closed:e}}function re(t,e){const{center:n,radius:i,notch:s}=t;let a=n.x+i*Math.cos(e),o=n.y+i*Math.sin(e);if(!s)return{x:a,y:o};const l=Math.sqrt(i**2-(s.length/2)**2),f=s.length/2,y=a-n.x,m=o-n.y;return s.type==="bottom"&&m<-l?(o=n.y-l,a=n.x+Math.max(-f,Math.min(f,y))):s.type==="top"&&m>l?(o=n.y+l,a=n.x+Math.max(-f,Math.min(f,y))):s.type==="left"&&y<-l?(a=n.x-l,o=n.y+Math.max(-f,Math.min(f,m))):s.type==="right"&&y>l&&(a=n.x+l,o=n.y+Math.max(-f,Math.min(f,m))),{x:a,y:o}}function Ht(t,e){return t.map(e).join(" / ")}function le(t,e=256){const n=Array.from({length:e},(i,s)=>t(s/(e-1)));return i=>n[Math.min(e-1,Math.max(0,Math.round(i*(e-1))))]}function ce(t,e){const n=Math.max(1,Math.min(t.width,t.height)),i=t.width/Math.max(e.length,1);return Math.max(8,Math.min(16,Math.round(Math.min(n*.55,i*1.8))))}function ue(t,e,n){if(e?.length){const i=e.flatMap(s=>{const a=s.testNumber;if(!mt(s)){const f=G(t,a,s);return f===void 0?[]:[{key:a,label:s.name,value:f?"Pass":"Fail"}]}const o=R(t,a);if(o===void 0)return[];const l=t.testPass?.[a]===!1?" <i>(recorded fail)</i>":"";return[{key:a,label:s.name,value:`${yt(o,s.unit,n)}${l}`}]});if(i.length)return i}return t.testValues&&Object.keys(t.testValues).length>0?Object.entries(t.testValues).map(([i,s])=>({key:Number(i),label:`Test ${i}`,value:yt(s,void 0,n)})):t.testPass&&Object.keys(t.testPass).length>0?Object.entries(t.testPass).map(([i,s])=>({key:Number(i),label:`Test ${i}`,value:s?"Pass":"Fail"})):[]}export function buildHoverText(t,e,n={}){const{testDefs:i,hbinDefs:s,sbinDefs:a,fallbackFormat:o,aggrMethod:l,lotSize:f,waferMeta:y,activeTest:m,reticleConfig:$}=n,P=s?new Map(s.map(r=>[r.bin,r])):null,v=a?new Map(a.map(r=>[r.bin,r])):null,x=[`Die (${t.x}, ${t.y})`];if($&&Qt(t)){const r=Ut(t,$);x.push(`Reticle (${r.column}, ${r.row})`)}if(e==="stackedValues"){const r=R(t,0);if(r!==void 0){const c=i?.[0],p=c?.testNumber,h=c?.name??(p!=null?`Test ${p}`:"Value"),M=l?` (${l})`:"";x.push(`${h}${M}: ${yt(r,c?.unit,o)}`)}}else if(e==="stackedBins"||e==="stackedSoftBins"){const r=R(t,0),c=e==="stackedSoftBins"?t.sbin:t.hbin;if(r!==void 0){const p=e==="stackedSoftBins"?v:P,h=c!==void 0?p?.get(c)?.name?`${c} \xB7 ${p.get(c).name}`:`Bin ${c}`:"Bin";let M;if(l==="percent")M=`${r.toFixed(0)}%`;else{const b=f?` (${(r/f*100).toFixed(0)}% of lot)`:"";M=`${r}${b}`}const T=l?` [${Rt(l)}]`:"";x.push(`${h}: ${M}${T}`)}}else{const r=ue(t,i,o);if(e==="value"&&r.length){const{testNumber:c}=resolveTestNumber(m??0,i),p=findTestDef(i,c),h=R(t,c);let M=r.findIndex(A=>A.key===c);M<0&&(M=0);const T=r[M];let b=`<b>${T.label}: ${T.value}</b>`;if(T.key===c&&p){const A=classifySpec(h,p);(A==="failLow"||A==="failHigh")&&(b+=" <i>(out of spec)</i>")}x.push(b);const O=r.length-1;O>0&&x.push(`<i>+${O} more test${O===1?"":"s"}</i>`)}else if(r.length){const c=r.length;x.push(`<i>${c} test result${c===1?"":"s"} recorded</i>`)}if(t.hbin!==void 0||t.sbin!==void 0){const c=[];if(t.hbin!==void 0){const p=P?.get(t.hbin)?.name,h=p?`${t.hbin} \xB7 ${p}`:String(t.hbin);c.push(`HBin: ${h}`)}if(t.sbin!==void 0){const p=v?.get(t.sbin)?.name,h=p?`${t.sbin} \xB7 ${p}`:String(t.sbin);c.push(`SBin: ${h}`)}x.push(c.join(" &nbsp;\xB7&nbsp; "))}}t.retestCount!==void 0&&x.push(`Retests: ${t.retestCount}`),t.siteNum!==void 0&&x.push(`Site: ${t.siteNum}`),t.partId!==void 0&&x.push(`Part ID: ${t.partId}`),t.partial&&x.push("<i>partial die</i>"),t.probeIndex!==void 0&&x.push(`Probe: #${t.probeIndex}`);const k={...y??{},...t.metadata??{}};for(const[r,c]of Object.entries(k))c!=null&&x.push(`${r}: ${String(c)}`);return x.join("<br>")}export function buildMapTitle(t,e="engineering",n){const i=t.isLotStack&&t.lotSize!==void 0?`stacked (${t.lotSize} ${t.lotSize===1?"wafer":"wafers"})`:"stacked",s=a=>{const o=n?.length===1?n[0]:void 0;return o===void 0?`${a} Bin`:o.name?`${a} Bin ${o.bin} \xB7 ${o.name}`:`${a} Bin ${o.bin}`};switch(t.plotMode){case"hardBin":return{primary:"Hard Bin",secondary:""};case"softBin":return{primary:"Soft Bin",secondary:""};case"stackedBins":return{primary:s("Hard"),secondary:i};case"stackedSoftBins":return{primary:s("Soft"),secondary:i};case"metadata":{const a=t.metadataFields?.find(l=>l.key===t.activeMetadataKey),o=t.activeMetadataKey?ae(t.activeMetadataKey):"Metadata";return{primary:a?.label??o,secondary:""}}case"stackedValues":{const a=t.testDefs?.[0],o=t.valueRange[1]||t.valueRange[0]||0,{axisLabel:l}=gt(o,a?.name,a?.unit,e),f=l||"Value",y=t.aggrMethod?` \xB7 ${Rt(t.aggrMethod)}`:"";return{primary:`${f}${y}`,secondary:i}}default:{const a=findTestDef(t.testDefs,t.activeTest),o=t.valueRange[1]||t.valueRange[0]||0,{axisLabel:l}=gt(o,a?.name,a?.unit,e),f=l||a?.name;if(t.passFailDisplay!=="off"){const y=a?.testNumber??t.activeTest,m=f?`${f} \xB7 #${y}`:`Test ${y}`,$=t.passFailDisplay==="spec"?"Spec pass/fail":a!==void 0&&!mt(a)?"Functional pass/fail":"Tester pass/fail";return{primary:m,secondary:$}}return{primary:f??`Test ${t.activeTest}`,secondary:""}}}}export function generateTextOverlay(t,e,n){const{plotMode:i,colorFns:s,normalize:a,activeTest:o,valueRange:l,testDefs:f,fallbackFormat:y,passFailDisplay:m="off",activeMetadataKey:$,metadataColorMap:P}=n,v=findTestDef(f,o),{tickFmt:x}=gt(l[1],v?.name,v?.unit,y),{testNumber:k}=resolveTestNumber(o,f);return t.flatMap((r,c)=>{let p="",h="#111111";if(i==="value"&&m==="test"){const b=G(r,k,v);if(b===void 0)return[];p=b?"P":"F",h=Y(b?ht:pt)}else if(i==="value"){const b=R(r,k);if(b===void 0)return[];p=Ht([b],x),h=Y(s.forValue(a(b)))}else if(i==="hardBin"||i==="softBin"){const b=i==="softBin"?r.sbin:r.hbin;if(b===void 0)return[];p=String(b),h=Y(s.forBin(b))}else if(i==="metadata"){const b=it(r,$);if(b===void 0)return[];p=b,h=Y(P?.get(p)??w)}else{const b=R(r,0);if(b===void 0)return[];p=Ht([b],x),h=Y(s.forValue(a(b)))}const M=e?e[c*2]:r.physX,T=e?e[c*2+1]:r.physY;return[{x:M,y:T,text:p,fontSize:ce(r,p),color:h,align:"center"}]})}function Ot(t){return t==="top"?Math.PI/2:t==="bottom"?-Math.PI/2:t==="left"?Math.PI:0}function fe(t,e,n=720){const{center:i,radius:s,notch:a}=t,o=k=>X(e,k.x,k.y);if(!(a!==void 0&&t.diameter>150)){const k=[];for(let r=0;r<=n;r++){const c=2*Math.PI*r/n;k.push(o(re(t,c)))}return[{kind:"wafer-boundary",..._(k,!0),lineColor:"#888888",lineWidth:1}]}const f=1.25,y=a.length,m=Ot(a.type),$=Math.atan2(y,s),P=s-f,v=[];let x=!1;for(let k=0;k<=n;k++){const r=2*Math.PI*k/n;let c=r-m;for(;c>Math.PI;)c-=2*Math.PI;for(;c<-Math.PI;)c+=2*Math.PI;if(Math.abs(c)<=$){if(!x){x=!0;const p={x:i.x+s*Math.cos(m-$),y:i.y+s*Math.sin(m-$)},h={x:i.x+P*Math.cos(m),y:i.y+P*Math.sin(m)},M={x:i.x+s*Math.cos(m+$),y:i.y+s*Math.sin(m+$)};v.push(o(p),o(h),o(M))}continue}v.push(o({x:i.x+s*Math.cos(r),y:i.y+s*Math.sin(r)}))}return[{kind:"wafer-boundary",..._(v,!0),lineColor:"#888888",lineWidth:1}]}function de(t,e,n,i=360){const s=[],a=Math.max(1,Math.floor(n));for(let o=1;o<a;o++){const l=t.radius*o/a,f=[];for(let y=0;y<=i;y++){const m=2*Math.PI*y/i,$={x:t.center.x+l*Math.cos(m),y:t.center.y+l*Math.sin(m)};f.push(X(e,$.x,$.y))}s.push({kind:"ring-boundary",..._(f,!0),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5})}return s}function he(t,e,n,i){const{x:s,y:a}=t.center,o=t.radius,l=(r,c)=>X(e,r,c),f=n-s,y=Math.sqrt(Math.max(0,o*o-f*f)),m=l(n,a-y),$=l(n,a+y),P=i-a,v=Math.sqrt(Math.max(0,o*o-P*P)),x=l(s-v,i),k=l(s+v,i);return[{kind:"quadrant-boundary",..._([m,$]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5},{kind:"quadrant-boundary",..._([x,k]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5}]}function pe(t,e){return t.map(n=>{const i=n.width/2,s=n.height/2;return{kind:"reticle",points:[[[n.x-i,n.y-s],[n.x+i,n.y-s],[n.x+i,n.y+s],[n.x-i,n.y+s]].map(([o,l])=>X(e,o,l))],closed:!0,lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5,fill:"rgba(0,0,0,0)"}})}function me(t){const e=t.filter(n=>n.probeIndex!==void 0).sort((n,i)=>(n.probeIndex??0)-(i.probeIndex??0));return e.length?[{kind:"probe-path",..._(e.map(n=>({x:n.physX,y:n.physY}))),lineColor:"rgba(220,80,0,0.55)",lineWidth:1}]:[]}function ye(t,e,n,i,s,a,o,l,f,y,m,$,P,v="off",x,k,r){const c=e.width-o,p=e.height-o,h=a?p:c,M=a?c:p,T=B=>s==="softBin"?B.sbin:B.hbin,b=B=>it(B,x);if(e.partial){t.push({x:n,y:i,width:h,height:M,fill:ie,type:"stacked",metadata:e.metadata});return}if(e.edgeExcluded){t.push({x:n,y:i,width:h,height:M,fill:se,type:"stacked",metadata:e.metadata});return}if(f!==void 0&&(s==="hardBin"||s==="softBin")&&T(e)!==f){t.push({x:n,y:i,width:h,height:M,fill:Nt,type:"hardBin",metadata:e.metadata});return}if(r!==void 0&&s==="metadata"&&b(e)!==r){t.push({x:n,y:i,width:h,height:M,fill:Nt,type:"metadata",metadata:e.metadata});return}if(s==="value"){if(v==="test"){const U=G(e,m,P),st=U===void 0?w:U?ht:pt;t.push({x:n,y:i,width:h,height:M,fill:st,type:"value",metadata:e.metadata});return}const B=R(e,m),F=classifySpec(B,P);let W,Q;B===void 0?W=w:v==="spec"?W=F==="failLow"?Zt:F==="failHigh"?pt:ht:(W=l.forValue(y(B)),(F==="failLow"||F==="failHigh")&&(Q=F)),t.push({x:n,y:i,width:h,height:M,fill:W,type:"value",specMark:Q,metadata:e.metadata});return}if(s==="hardBin"||s==="softBin"){const B=T(e),F=B!=null?l.forBin(B):w;t.push({x:n,y:i,width:h,height:M,fill:F,type:s,metadata:e.metadata});return}if(s==="metadata"){const B=b(e),F=B!==void 0?k?.get(B)??w:w;t.push({x:n,y:i,width:h,height:M,fill:F,type:"metadata",metadata:e.metadata});return}const O=R(e,0),A=O!==void 0?l.forValue(y(O)):w;t.push({x:n,y:i,width:h,height:M,fill:A,type:"value",metadata:e.metadata})}function ge(t,e,n){const i=t.radius*.15,s=dt(e,i,0),a=dt(e,0,i),o=s.x+a.x>=0?-1:1,l=s.y+a.y>=0?-1:1,f={x:t.center.x+o*t.radius*.9,y:t.center.y+l*t.radius*.9},y={x:f.x+s.x,y:f.y+s.y},m={x:f.x+a.x,y:f.y+a.y};return n.push({x:y.x+s.x*.35,y:y.y+s.y*.35,text:"+X",fontSize:10,color:"#cc3300",align:"center",role:"indicator"},{x:m.x+a.x*.35,y:m.y+a.y*.35,text:"+Y",fontSize:10,color:"#0044cc",align:"center",role:"indicator"}),[{kind:"xy-indicator",..._([f,y]),lineColor:"#cc3300",lineWidth:2},{kind:"xy-indicator",..._([f,m]),lineColor:"#0044cc",lineWidth:2}]}export function buildView(t,e,n={},i){const s=n.reticles??[],{plotMode:a="value",showDieLabels:o=!1,showReticle:l=!1,showProbePath:f=!1,ringCount:y=4,showRingBoundaries:m=!1,showQuadrantBoundaries:$=!1,showXYIndicator:P=!1,dieGap:v=1,colorScheme:x="default",highlightBin:k,highlightMetadataValue:r,interactiveTransform:c,valueRange:p,testDefs:h,activeTest:M=0,activeMetadataKey:T,fallbackFormat:b="engineering",aggregationMethod:O,lotSize:A,dataAxisFlip:B,isLotStack:F=!1,logScale:W,colorbarRangeMode:Q="spec",passFailDisplay:U,showPartialDies:st=!0}=n,xt=U??"off",bt=i?.hbinDefs,Mt=i?.sbinDefs,St=i?.metadataFields,At=St?.find(u=>u.key===T),Et={x:(B?.x??!1)!==(c?.flipX??!1),y:(B?.y??!1)!==(c?.flipY??!1)},_t=bt?new Map(bt.map(u=>[u.bin,u])):null,wt=Mt?new Map(Mt.map(u=>[u.bin,u])):null,ot=a==="softBin"?wt:_t,rt=te(x),$t={forValue:le(rt.forValue),forBin:x==="custom"&&ot?u=>ot.get(u)?.color??rt.forBin(u):rt.forBin};let{testNumber:H}=resolveTestNumber(M,h);if(!h?.length&&a==="value"&&!e.some(d=>d.testValues&&H in d.testValues)){const d=e.reduce((g,L)=>{if(!L.testValues)return g;const S=Object.keys(L.testValues).map(Number),K=S.length?Math.min(...S):void 0;return K!==void 0&&(g===void 0||K<g)?K:g},void 0);d!==void 0&&(H=d)}let J;if(Array.isArray(p))J=p;else if(p){const{testNumber:u}=resolveTestNumber(p.test,h);J=u===H?p.range:void 0}const I=findTestDef(h,H),kt=a==="value"&&I!==void 0&&!mt(I),Wt=I!==void 0&&(I.limitLow!==void 0||I.limitHigh!==void 0);let C="off";a==="value"&&(kt?C="test":xt==="spec"&&Wt?C="spec":xt==="test"&&e.some(u=>G(u,H,I)!==void 0)&&(C="test"));const Bt=C==="spec"?"spec":Q;let V,N,vt=!1;if(J)[V,N]=J;else{const u=a==="stackedValues"||a==="stackedBins"||a==="stackedSoftBins",d=!u&&a==="value"&&Bt==="spec"&&I&&(I.limitLow!==void 0||I.limitHigh!==void 0);let g=1/0,L=-1/0,S=!0;for(const K of e){const q=u?R(K,0):R(K,H);q!==void 0&&(q<g&&(g=q),q>L&&(L=q),Number.isInteger(q)||(S=!1))}isFinite(g)||(S=!1),vt=S,d?(V=I.limitLow!==void 0?I.limitLow:isFinite(g)?g:0,N=I.limitHigh!==void 0?I.limitHigh:isFinite(L)?L:1):(V=isFinite(g)?g:0,N=isFinite(L)?L:1)}V===N&&(N=V+1);const Tt=kt?!1:W??I?.logScale??!1,It=Tt&&V>0&&N>0,jt=It;let Z;if(It){const u=Math.log10(V),d=Math.log10(N)-u;Z=g=>g<=0?0:Ct((Math.log10(g)-u)/d)}else Z=u=>Ct((u-V)/(N-V));const D=oe(t,B,c);let Lt=null;if(t.notch){const u=Ot(t.notch.type),d=dt(D.physicalToScreen,Math.cos(u)*t.radius,Math.sin(u)*t.radius),g=Math.hypot(d.x,d.y);g>0&&(Lt={x:d.x/g,y:d.y/g})}let j=1/0;for(let u=0;u<e.length;u++){const d=e[u];d.width<j&&(j=d.width),d.height<j&&(j=d.height)}const zt=j!==1/0?Math.min(v,j*.12):v,Pt=[],lt=[],Kt=D.bakedToScreen.a===1&&D.bakedToScreen.b===0&&D.bakedToScreen.c===0&&D.bakedToScreen.d===1&&D.bakedToScreen.e===0&&D.bakedToScreen.f===0;let E=null;if(!Kt){E=new Float64Array(e.length*2);for(let u=0;u<e.length;u++){const d=X(D.bakedToScreen,e[u].physX,e[u].physY);E[u*2]=d.x,E[u*2+1]=d.y}}const qt=Jt(D.gridToScreen),Xt=a==="hardBin"||a==="softBin"||a==="stackedBins"||a==="stackedSoftBins",Yt=a==="softBin"||a==="stackedSoftBins";let tt=Xt?new Map:void 0;const ct=a==="value"&&C==="spec"?{pass:0,failHigh:0,failLow:0}:void 0,ut=a==="value"&&C==="test"?{pass:0,fail:0}:void 0,Dt=a==="metadata";let et=Dt?new Map:void 0,nt=null;if(Dt&&T){const u=new Set;for(const g of e){if(g.partial||g.edgeExcluded)continue;const L=it(g,T);L!==void 0&&u.add(L)}const d=[...u].sort(ne);nt=new Map(d.map((g,L)=>[g,At?.values?.find(S=>S.value===g)?.color??ee(L)]))}for(let u=0;u<e.length;u++){const d=e[u],g=E?E[u*2]:d.physX,L=E?E[u*2+1]:d.physY;if(lt.push({x:g,y:L}),tt&&!d.partial&&!d.edgeExcluded){const S=Yt?d.sbin:d.hbin;S!=null&&tt.set(S,(tt.get(S)??0)+1)}if(ct&&!d.partial&&!d.edgeExcluded){const S=classifySpec(R(d,H),I);S&&ct[S]++}if(ut&&!d.partial&&!d.edgeExcluded){const S=G(d,H,I);S!==void 0&&ut[S?"pass":"fail"]++}if(et&&!d.partial&&!d.edgeExcluded){const S=it(d,T);S!==void 0&&et.set(S,(et.get(S)??0)+1)}d.partial&&!st||ye(Pt,d,g,L,a,qt,zt,$t,k,Z,H,ot,I,C,T,nt,r)}const Ft=o?generateTextOverlay(e,E,{plotMode:a,colorFns:$t,normalize:Z,activeTest:M,valueRange:[V,N],testDefs:h,fallbackFormat:b,passFailDisplay:C,activeMetadataKey:T,metadataColorMap:nt}):[],z=fe(t,D.physicalToScreen);m&&z.push(...de(t,D.physicalToScreen,y)),$&&z.push(...he(t,D.physicalToScreen,t.center.x,t.center.y)),l&&z.push(...pe(s,D.gridToScreen)),f&&z.push(...me(e)),P&&z.push(...ge(t,D.gridToScreen,Ft));const Gt=lt.length>0?{minX:t.center.x-t.radius,maxX:t.center.x+t.radius,minY:t.center.y-t.radius,maxY:t.center.y+t.radius}:null;return{rectangles:Pt,hoverPoints:lt,texts:Ft,overlays:z,plotMode:a,colorScheme:x,metadata:t.metadata??null,dies:e,valueRange:[V,N],allIntegerValues:vt,testDefs:h,activeTest:M,logScale:jt,logScaleRequested:Tt,aggrMethod:O,lotSize:A,axisFlip:Et,dataAxisFlip:{x:B?.x??!1,y:B?.y??!1},gridToScreen:D.gridToScreen,rotation:((t.orientation+(c?.rotation??0))%360+360)%360,hasReticle:s.length>0,isLotStack:F,passFailDisplay:C,colorBySpec:C==="spec",colorbarRangeMode:Bt,waferCenter:t.center,waferRadius:t.radius,notchDir:Lt,binCounts:tt,metadataCounts:et,metadataColorMap:nt,activeMetadataKey:T,metadataFields:St,specCounts:ct,passFailCounts:ut,dieBounds:Gt}}export{getDieKey}from"../core/dies.js";
1
+ import{hasPosition as Ut}from"../core/dies.js";import{getReticleCell as Jt}from"../core/reticle.js";import{metadataCategoricalValue as Zt,metadataDisplayValue as te}from"../core/metadata.js";import{affineRotation as ft,affineMirror as Pt,affineCompose as at,affinePoint as X,affineVector as dt,affineSwapsAxes as ee}from"../core/transforms.js";import{contrastTextColor as Y,SPEC_PASS_FILL as ht,SPEC_FAIL_LOW as ne,SPEC_FAIL_HIGH as mt}from"./colorMap.js";import{getColorScheme as ae}from"./colorSchemes.js";import{getDieTestValue as R,getTestPassStatus as G,isParametricTest as pt}from"./buildWaferMap.js";import{fmt as yt,fmtColorbarAxis as gt,fmtAggregationMethod as Rt}from"./fmt.js";import{metadataValueColor as ie}from"./colorMap.js";import{compareNatural as se,clamp01 as Ct}from"../core/utils.js";import{prettyKey as Nt}from"../core/utils.js";export function resolveTestNumber(t,e){return e?.length?{testNumber:(findTestDef(e,t)??e[0]).testNumber}:{testNumber:t}}export function findTestDef(t,e){return t?.find(a=>a.testNumber===e)}export function getUniqueTestNumbers(t){return[...new Set(t.flatMap(e=>e.testValues?Object.keys(e.testValues).map(Number):[]))].sort((e,a)=>e-a)}const oe="#d3d6db",Ht="#e8e9ea",re="#eceef0",w="#d6d9dd";function it(t,e){return e?Zt(t.metadata?.[e]):void 0}export function classifySpec(t,e){return t===void 0?null:e?.limitLow!==void 0&&t<e.limitLow?"failLow":e?.limitHigh!==void 0&&t>e.limitHigh?"failHigh":"pass"}function le(t,e,a){const{x:i,y:s}=t.center,n=ft(t.orientation,i,s),o=at(Pt(e?.x??!1,e?.y??!1,i,s),ft(t.orientation,i,s)),r=at(Pt(a?.flipX??!1,a?.flipY??!1,i,s),ft(a?.rotation??0,i,s));return{bakedToScreen:r,physicalToScreen:at(r,n),gridToScreen:at(r,o)}}function E(t,e=!1){return{points:[t],closed:e}}function ce(t,e){const{center:a,radius:i,notch:s}=t;let n=a.x+i*Math.cos(e),o=a.y+i*Math.sin(e);if(!s)return{x:n,y:o};const r=Math.sqrt(i**2-(s.length/2)**2),u=s.length/2,y=n-a.x,p=o-a.y;return s.type==="bottom"&&p<-r?(o=a.y-r,n=a.x+Math.max(-u,Math.min(u,y))):s.type==="top"&&p>r?(o=a.y+r,n=a.x+Math.max(-u,Math.min(u,y))):s.type==="left"&&y<-r?(n=a.x-r,o=a.y+Math.max(-u,Math.min(u,p))):s.type==="right"&&y>r&&(n=a.x+r,o=a.y+Math.max(-u,Math.min(u,p))),{x:n,y:o}}function Ot(t,e){return t.map(e).join(" / ")}function ue(t,e=256){const a=Array.from({length:e},(i,s)=>t(s/(e-1)));return i=>a[Math.min(e-1,Math.max(0,Math.round(i*(e-1))))]}function fe(t,e){const a=Math.max(1,Math.min(t.width,t.height)),i=t.width/Math.max(e.length,1);return Math.max(8,Math.min(16,Math.round(Math.min(a*.55,i*1.8))))}function de(t,e,a){if(e?.length){const i=e.flatMap(s=>{const n=s.testNumber;if(!pt(s)){const u=G(t,n,s);return u===void 0?[]:[{key:n,label:s.name,value:u?"Pass":"Fail"}]}const o=R(t,n);if(o===void 0)return[];const r=t.testPass?.[n]===!1?" <i>(recorded fail)</i>":"";return[{key:n,label:s.name,value:`${yt(o,s.unit,a)}${r}`}]});if(i.length)return i}return t.testValues&&Object.keys(t.testValues).length>0?Object.entries(t.testValues).map(([i,s])=>({key:Number(i),label:`Test ${i}`,value:yt(s,void 0,a)})):t.testPass&&Object.keys(t.testPass).length>0?Object.entries(t.testPass).map(([i,s])=>({key:Number(i),label:`Test ${i}`,value:s?"Pass":"Fail"})):[]}export function buildHoverText(t,e,a={}){const{testDefs:i,hbinDefs:s,sbinDefs:n,fallbackFormat:o,aggrMethod:r,lotSize:u,waferMeta:y,activeTest:p,reticleConfig:B,metadataFields:D}=a,v=s?new Map(s.map(l=>[l.bin,l])):null,L=n?new Map(n.map(l=>[l.bin,l])):null,m=[`Die (${t.x}, ${t.y})`];if(B&&Ut(t)){const l=Jt(t,B);m.push(`Reticle (${l.column}, ${l.row})`)}if(e==="stackedValues"){const l=R(t,0);if(l!==void 0){const f=i?.[0],d=f?.testNumber,g=f?.name??(d!=null?`Test ${d}`:"Value"),$=r?` (${r})`:"";m.push(`${g}${$}: ${yt(l,f?.unit,o)}`)}}else if(e==="stackedBins"||e==="stackedSoftBins"){const l=R(t,0),f=e==="stackedSoftBins"?t.sbin:t.hbin;if(l!==void 0){const d=e==="stackedSoftBins"?L:v,g=f!==void 0?d?.get(f)?.name?`${f} \xB7 ${d.get(f).name}`:`Bin ${f}`:"Bin";let $;if(r==="percent")$=`${l.toFixed(0)}%`;else{const H=u?` (${(l/u*100).toFixed(0)}% of lot)`:"";$=`${l}${H}`}const x=r?` [${Rt(r)}]`:"";m.push(`${g}: ${$}${x}`)}}else{const l=de(t,i,o);if(e==="value"&&l.length){const{testNumber:f}=resolveTestNumber(p??0,i),d=findTestDef(i,f),g=R(t,f);let $=l.findIndex(S=>S.key===f);$<0&&($=0);const x=l[$];let H=`<b>${x.label}: ${x.value}</b>`;if(x.key===f&&d){const S=classifySpec(g,d);(S==="failLow"||S==="failHigh")&&(H+=" <i>(out of spec)</i>")}m.push(H);const _=l.length-1;_>0&&m.push(`<i>+${_} more test${_===1?"":"s"}</i>`)}else if(l.length){const f=l.length;m.push(`<i>${f} test result${f===1?"":"s"} recorded</i>`)}if(t.hbin!==void 0||t.sbin!==void 0){const f=[];if(t.hbin!==void 0){const d=v?.get(t.hbin)?.name,g=d?`${t.hbin} \xB7 ${d}`:String(t.hbin);f.push(`HBin: ${g}`)}if(t.sbin!==void 0){const d=L?.get(t.sbin)?.name,g=d?`${t.sbin} \xB7 ${d}`:String(t.sbin);f.push(`SBin: ${g}`)}m.push(f.join(" &nbsp;\xB7&nbsp; "))}}t.retestCount!==void 0&&m.push(`Retests: ${t.retestCount}`),t.siteNum!==void 0&&m.push(`Site: ${t.siteNum}`),t.partId!==void 0&&m.push(`Part ID: ${t.partId}`),t.partial&&m.push("<i>partial die</i>"),t.probeIndex!==void 0&&m.push(`Probe: #${t.probeIndex}`);const M={...y??{},...t.metadata??{}};for(const[l,f]of Object.entries(M)){const d=te(f);if(d===void 0)continue;const g=D?.find($=>$.key===l)?.label??Nt(l);m.push(`${g}: ${d}`)}return m.join("<br>")}export function buildMapTitle(t,e="engineering",a){const i=t.isLotStack&&t.lotSize!==void 0?`stacked (${t.lotSize} ${t.lotSize===1?"wafer":"wafers"})`:"stacked",s=n=>{const o=a?.length===1?a[0]:void 0;return o===void 0?`${n} Bin`:o.name?`${n} Bin ${o.bin} \xB7 ${o.name}`:`${n} Bin ${o.bin}`};switch(t.plotMode){case"hardBin":return{primary:"Hard Bin",secondary:""};case"softBin":return{primary:"Soft Bin",secondary:""};case"stackedBins":return{primary:s("Hard"),secondary:i};case"stackedSoftBins":return{primary:s("Soft"),secondary:i};case"metadata":{const n=t.metadataFields?.find(r=>r.key===t.activeMetadataKey),o=t.activeMetadataKey?Nt(t.activeMetadataKey):"Metadata";return{primary:n?.label??o,secondary:""}}case"stackedValues":{const n=t.testDefs?.[0],o=t.valueRange[1]||t.valueRange[0]||0,{axisLabel:r}=gt(o,n?.name,n?.unit,e),u=r||"Value",y=t.aggrMethod?` \xB7 ${Rt(t.aggrMethod)}`:"";return{primary:`${u}${y}`,secondary:i}}default:{const n=findTestDef(t.testDefs,t.activeTest),o=t.valueRange[1]||t.valueRange[0]||0,{axisLabel:r}=gt(o,n?.name,n?.unit,e),u=r||n?.name;if(t.passFailDisplay!=="off"){const y=n?.testNumber??t.activeTest,p=u?`${u} \xB7 #${y}`:`Test ${y}`,B=t.passFailDisplay==="spec"?"Spec pass/fail":n!==void 0&&!pt(n)?"Functional pass/fail":"Tester pass/fail";return{primary:p,secondary:B}}return{primary:u??`Test ${t.activeTest}`,secondary:""}}}}export function generateTextOverlay(t,e,a){const{plotMode:i,colorFns:s,normalize:n,activeTest:o,valueRange:r,testDefs:u,fallbackFormat:y,passFailDisplay:p="off",activeMetadataKey:B,metadataColorMap:D}=a,v=findTestDef(u,o),{tickFmt:L}=gt(r[1],v?.name,v?.unit,y),{testNumber:m}=resolveTestNumber(o,u);return t.flatMap((M,l)=>{let f="",d="#111111";if(i==="value"&&p==="test"){const x=G(M,m,v);if(x===void 0)return[];f=x?"P":"F",d=Y(x?ht:mt)}else if(i==="value"){const x=R(M,m);if(x===void 0)return[];f=Ot([x],L),d=Y(s.forValue(n(x)))}else if(i==="hardBin"||i==="softBin"){const x=i==="softBin"?M.sbin:M.hbin;if(x===void 0)return[];f=String(x),d=Y(s.forBin(x))}else if(i==="metadata"){const x=it(M,B);if(x===void 0)return[];f=x,d=Y(D?.get(f)??w)}else{const x=R(M,0);if(x===void 0)return[];f=Ot([x],L),d=Y(s.forValue(n(x)))}const g=e?e[l*2]:M.physX,$=e?e[l*2+1]:M.physY;return[{x:g,y:$,text:f,fontSize:fe(M,f),color:d,align:"center"}]})}function At(t){return t==="top"?Math.PI/2:t==="bottom"?-Math.PI/2:t==="left"?Math.PI:0}function he(t,e,a=720){const{center:i,radius:s,notch:n}=t,o=m=>X(e,m.x,m.y);if(!(n!==void 0&&t.diameter>150)){const m=[];for(let M=0;M<=a;M++){const l=2*Math.PI*M/a;m.push(o(ce(t,l)))}return[{kind:"wafer-boundary",...E(m,!0),lineColor:"#888888",lineWidth:1}]}const u=1.25,y=n.length,p=At(n.type),B=Math.atan2(y,s),D=s-u,v=[];let L=!1;for(let m=0;m<=a;m++){const M=2*Math.PI*m/a;let l=M-p;for(;l>Math.PI;)l-=2*Math.PI;for(;l<-Math.PI;)l+=2*Math.PI;if(Math.abs(l)<=B){if(!L){L=!0;const f={x:i.x+s*Math.cos(p-B),y:i.y+s*Math.sin(p-B)},d={x:i.x+D*Math.cos(p),y:i.y+D*Math.sin(p)},g={x:i.x+s*Math.cos(p+B),y:i.y+s*Math.sin(p+B)};v.push(o(f),o(d),o(g))}continue}v.push(o({x:i.x+s*Math.cos(M),y:i.y+s*Math.sin(M)}))}return[{kind:"wafer-boundary",...E(v,!0),lineColor:"#888888",lineWidth:1}]}function me(t,e,a,i=360){const s=[],n=Math.max(1,Math.floor(a));for(let o=1;o<n;o++){const r=t.radius*o/n,u=[];for(let y=0;y<=i;y++){const p=2*Math.PI*y/i,B={x:t.center.x+r*Math.cos(p),y:t.center.y+r*Math.sin(p)};u.push(X(e,B.x,B.y))}s.push({kind:"ring-boundary",...E(u,!0),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5})}return s}function pe(t,e,a,i){const{x:s,y:n}=t.center,o=t.radius,r=(M,l)=>X(e,M,l),u=a-s,y=Math.sqrt(Math.max(0,o*o-u*u)),p=r(a,n-y),B=r(a,n+y),D=i-n,v=Math.sqrt(Math.max(0,o*o-D*D)),L=r(s-v,i),m=r(s+v,i);return[{kind:"quadrant-boundary",...E([p,B]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5},{kind:"quadrant-boundary",...E([L,m]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5}]}function ye(t,e){return t.map(a=>{const i=a.width/2,s=a.height/2;return{kind:"reticle",points:[[[a.x-i,a.y-s],[a.x+i,a.y-s],[a.x+i,a.y+s],[a.x-i,a.y+s]].map(([o,r])=>X(e,o,r))],closed:!0,lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5,fill:"rgba(0,0,0,0)"}})}function ge(t){const e=t.filter(a=>a.probeIndex!==void 0).sort((a,i)=>(a.probeIndex??0)-(i.probeIndex??0));return e.length?[{kind:"probe-path",...E(e.map(a=>({x:a.physX,y:a.physY}))),lineColor:"rgba(220,80,0,0.55)",lineWidth:1}]:[]}function xe(t,e,a,i,s,n,o,r,u,y,p,B,D,v="off",L,m,M){const l=e.width-o,f=e.height-o,d=n?f:l,g=n?l:f,$=S=>s==="softBin"?S.sbin:S.hbin,x=S=>it(S,L);if(e.partial){t.push({x:a,y:i,width:d,height:g,fill:oe,type:"stacked",metadata:e.metadata});return}if(e.edgeExcluded){t.push({x:a,y:i,width:d,height:g,fill:re,type:"stacked",metadata:e.metadata});return}if(u!==void 0&&(s==="hardBin"||s==="softBin")&&$(e)!==u){t.push({x:a,y:i,width:d,height:g,fill:Ht,type:"hardBin",metadata:e.metadata});return}if(M!==void 0&&s==="metadata"&&x(e)!==M){t.push({x:a,y:i,width:d,height:g,fill:Ht,type:"metadata",metadata:e.metadata});return}if(s==="value"){if(v==="test"){const U=G(e,p,D),st=U===void 0?w:U?ht:mt;t.push({x:a,y:i,width:d,height:g,fill:st,type:"value",metadata:e.metadata});return}const S=R(e,p),F=classifySpec(S,D);let W,Q;S===void 0?W=w:v==="spec"?W=F==="failLow"?ne:F==="failHigh"?mt:ht:(W=r.forValue(y(S)),(F==="failLow"||F==="failHigh")&&(Q=F)),t.push({x:a,y:i,width:d,height:g,fill:W,type:"value",specMark:Q,metadata:e.metadata});return}if(s==="hardBin"||s==="softBin"){const S=$(e),F=S!=null?r.forBin(S):w;t.push({x:a,y:i,width:d,height:g,fill:F,type:s,metadata:e.metadata});return}if(s==="metadata"){const S=x(e),F=S!==void 0?m?.get(S)??w:w;t.push({x:a,y:i,width:d,height:g,fill:F,type:"metadata",metadata:e.metadata});return}const H=R(e,0),_=H!==void 0?r.forValue(y(H)):w;t.push({x:a,y:i,width:d,height:g,fill:_,type:"value",metadata:e.metadata})}function be(t,e,a){const i=t.radius*.15,s=dt(e,i,0),n=dt(e,0,i),o=s.x+n.x>=0?-1:1,r=s.y+n.y>=0?-1:1,u={x:t.center.x+o*t.radius*.9,y:t.center.y+r*t.radius*.9},y={x:u.x+s.x,y:u.y+s.y},p={x:u.x+n.x,y:u.y+n.y};return a.push({x:y.x+s.x*.35,y:y.y+s.y*.35,text:"+X",fontSize:10,color:"#cc3300",align:"center",role:"indicator"},{x:p.x+n.x*.35,y:p.y+n.y*.35,text:"+Y",fontSize:10,color:"#0044cc",align:"center",role:"indicator"}),[{kind:"xy-indicator",...E([u,y]),lineColor:"#cc3300",lineWidth:2},{kind:"xy-indicator",...E([u,p]),lineColor:"#0044cc",lineWidth:2}]}export function buildView(t,e,a={},i){const s=a.reticles??[],{plotMode:n="value",showDieLabels:o=!1,showReticle:r=!1,showProbePath:u=!1,ringCount:y=4,showRingBoundaries:p=!1,showQuadrantBoundaries:B=!1,showXYIndicator:D=!1,dieGap:v=1,colorScheme:L="default",highlightBin:m,highlightMetadataValue:M,interactiveTransform:l,valueRange:f,testDefs:d,activeTest:g=0,activeMetadataKey:$,fallbackFormat:x="engineering",aggregationMethod:H,lotSize:_,dataAxisFlip:S,isLotStack:F=!1,logScale:W,colorbarRangeMode:Q="spec",passFailDisplay:U,showPartialDies:st=!0}=a,xt=U??"off",bt=i?.hbinDefs,Mt=i?.sbinDefs,St=i?.metadataFields,Et=St?.find(c=>c.key===$),_t={x:(S?.x??!1)!==(l?.flipX??!1),y:(S?.y??!1)!==(l?.flipY??!1)},wt=bt?new Map(bt.map(c=>[c.bin,c])):null,Wt=Mt?new Map(Mt.map(c=>[c.bin,c])):null,ot=n==="softBin"?Wt:wt,rt=ae(L),$t={forValue:ue(rt.forValue),forBin:L==="custom"&&ot?c=>ot.get(c)?.color??rt.forBin(c):rt.forBin};let{testNumber:O}=resolveTestNumber(g,d);if(!d?.length&&n==="value"&&!e.some(h=>h.testValues&&O in h.testValues)){const h=e.reduce((b,I)=>{if(!I.testValues)return b;const k=Object.keys(I.testValues).map(Number),K=k.length?Math.min(...k):void 0;return K!==void 0&&(b===void 0||K<b)?K:b},void 0);h!==void 0&&(O=h)}let J;if(Array.isArray(f))J=f;else if(f){const{testNumber:c}=resolveTestNumber(f.test,d);J=c===O?f.range:void 0}const T=findTestDef(d,O),kt=n==="value"&&T!==void 0&&!pt(T),jt=T!==void 0&&(T.limitLow!==void 0||T.limitHigh!==void 0);let C="off";n==="value"&&(kt?C="test":xt==="spec"&&jt?C="spec":xt==="test"&&e.some(c=>G(c,O,T)!==void 0)&&(C="test"));const Bt=C==="spec"?"spec":Q;let P,N,vt=!1;if(J)[P,N]=J;else{const c=n==="stackedValues"||n==="stackedBins"||n==="stackedSoftBins",h=!c&&n==="value"&&Bt==="spec"&&T&&(T.limitLow!==void 0||T.limitHigh!==void 0);let b=1/0,I=-1/0,k=!0;for(const K of e){const q=c?R(K,0):R(K,O);q!==void 0&&(q<b&&(b=q),q>I&&(I=q),Number.isInteger(q)||(k=!1))}isFinite(b)||(k=!1),vt=k,h?(P=T.limitLow!==void 0?T.limitLow:isFinite(b)?b:0,N=T.limitHigh!==void 0?T.limitHigh:isFinite(I)?I:1):(P=isFinite(b)?b:0,N=isFinite(I)?I:1)}P===N&&(N=P+1);const Tt=kt?!1:W??T?.logScale??!1,It=Tt&&P>0&&N>0,zt=It;let Z;if(It){const c=Math.log10(P),h=Math.log10(N)-c;Z=b=>b<=0?0:Ct((Math.log10(b)-c)/h)}else Z=c=>Ct((c-P)/(N-P));const V=le(t,S,l);let Lt=null;if(t.notch){const c=At(t.notch.type),h=dt(V.physicalToScreen,Math.cos(c)*t.radius,Math.sin(c)*t.radius),b=Math.hypot(h.x,h.y);b>0&&(Lt={x:h.x/b,y:h.y/b})}let j=1/0;for(let c=0;c<e.length;c++){const h=e[c];h.width<j&&(j=h.width),h.height<j&&(j=h.height)}const Kt=j!==1/0?Math.min(v,j*.12):v,Vt=[],lt=[],qt=V.bakedToScreen.a===1&&V.bakedToScreen.b===0&&V.bakedToScreen.c===0&&V.bakedToScreen.d===1&&V.bakedToScreen.e===0&&V.bakedToScreen.f===0;let A=null;if(!qt){A=new Float64Array(e.length*2);for(let c=0;c<e.length;c++){const h=X(V.bakedToScreen,e[c].physX,e[c].physY);A[c*2]=h.x,A[c*2+1]=h.y}}const Xt=ee(V.gridToScreen),Yt=n==="hardBin"||n==="softBin"||n==="stackedBins"||n==="stackedSoftBins",Gt=n==="softBin"||n==="stackedSoftBins";let tt=Yt?new Map:void 0;const ct=n==="value"&&C==="spec"?{pass:0,failHigh:0,failLow:0}:void 0,ut=n==="value"&&C==="test"?{pass:0,fail:0}:void 0,Dt=n==="metadata";let et=Dt?new Map:void 0,nt=null;if(Dt&&$){const c=new Set;for(const b of e){if(b.partial||b.edgeExcluded)continue;const I=it(b,$);I!==void 0&&c.add(I)}const h=[...c].sort(se);nt=new Map(h.map((b,I)=>[b,Et?.values?.find(k=>k.value===b)?.color??ie(I)]))}for(let c=0;c<e.length;c++){const h=e[c],b=A?A[c*2]:h.physX,I=A?A[c*2+1]:h.physY;if(lt.push({x:b,y:I}),tt&&!h.partial&&!h.edgeExcluded){const k=Gt?h.sbin:h.hbin;k!=null&&tt.set(k,(tt.get(k)??0)+1)}if(ct&&!h.partial&&!h.edgeExcluded){const k=classifySpec(R(h,O),T);k&&ct[k]++}if(ut&&!h.partial&&!h.edgeExcluded){const k=G(h,O,T);k!==void 0&&ut[k?"pass":"fail"]++}if(et&&!h.partial&&!h.edgeExcluded){const k=it(h,$);k!==void 0&&et.set(k,(et.get(k)??0)+1)}h.partial&&!st||xe(Vt,h,b,I,n,Xt,Kt,$t,m,Z,O,ot,T,C,$,nt,M)}const Ft=o?generateTextOverlay(e,A,{plotMode:n,colorFns:$t,normalize:Z,activeTest:g,valueRange:[P,N],testDefs:d,fallbackFormat:x,passFailDisplay:C,activeMetadataKey:$,metadataColorMap:nt}):[],z=he(t,V.physicalToScreen);p&&z.push(...me(t,V.physicalToScreen,y)),B&&z.push(...pe(t,V.physicalToScreen,t.center.x,t.center.y)),r&&z.push(...ye(s,V.gridToScreen)),u&&z.push(...ge(e)),D&&z.push(...be(t,V.gridToScreen,Ft));const Qt=lt.length>0?{minX:t.center.x-t.radius,maxX:t.center.x+t.radius,minY:t.center.y-t.radius,maxY:t.center.y+t.radius}:null;return{rectangles:Vt,hoverPoints:lt,texts:Ft,overlays:z,plotMode:n,colorScheme:L,metadata:t.metadata??null,dies:e,valueRange:[P,N],allIntegerValues:vt,testDefs:d,activeTest:g,logScale:zt,logScaleRequested:Tt,aggrMethod:H,lotSize:_,axisFlip:_t,dataAxisFlip:{x:S?.x??!1,y:S?.y??!1},gridToScreen:V.gridToScreen,rotation:((t.orientation+(l?.rotation??0))%360+360)%360,hasReticle:s.length>0,isLotStack:F,passFailDisplay:C,colorBySpec:C==="spec",colorbarRangeMode:Bt,waferCenter:t.center,waferRadius:t.radius,notchDir:Lt,binCounts:tt,metadataCounts:et,metadataColorMap:nt,activeMetadataKey:$,metadataFields:St,specCounts:ct,passFailCounts:ut,dieBounds:Qt}}export{getDieKey}from"../core/dies.js";
@@ -1 +1 @@
1
- import{compareNatural as m}from"../core/utils.js";export const FACET_NONE_VALUE="(none)";export{prettyKey}from"../core/utils.js";export const DEFAULT_FACET_CURATION={split:{label:"Split"},lot:{label:"Lot"},product:{label:"Product"},testProgram:{label:"Program"},temperature:{label:"Temperature"},testDate:{label:"Test date",date:!0},operator:{label:"Operator"},waferId:{label:"Wafer",facet:!1}};function C(e){const n=e.indexOf("T");return n>0?e.slice(0,n):e}export function facetValueOf(e,n,r=DEFAULT_FACET_CURATION){const a=e?.[n];if(a==null||a==="")return;const t=String(a);return r[n]?.date?C(t):t}export function commonMetadata(e){if(!e.length)return{};const n=e[0].metadata??{},r={};for(const[a,t]of Object.entries(n))t==null||t===""||e.every(d=>(d.metadata??{})[a]===t)&&(r[a]=t);return r}export function buildFacetTable(e,n={}){const r={...DEFAULT_FACET_CURATION,...n.curation},a=n.facetableOnly??!0,t=new Set;for(const o of e)for(const f of Object.keys(o.metadata??{}))t.add(f);const b=[...Object.keys(r).filter(o=>t.has(o)),...[...t].filter(o=>!(o in r))],p=[];for(const o of b){const f=r[o];if(a&&f&&f.facet===!1)continue;const u=new Map;for(const c of e){const l=facetValueOf(c.metadata,o,r)??FACET_NONE_VALUE,i=u.get(l)??{waferCount:0,dieCount:0};i.waferCount+=1,i.dieCount+=c.dieCount??0,u.set(l,i)}if(u.size===0)continue;const s=Array.from(u,([c,l])=>({value:c,waferCount:l.waferCount,dieCount:l.dieCount}));s.sort((c,l)=>(c.value===FACET_NONE_VALUE?1:0)-(l.value===FACET_NONE_VALUE?1:0)||l.waferCount-c.waferCount||m(c.value,l.value)),p.push({key:o,label:f?.label??o,values:s,splittable:s.length>1})}return p}
1
+ import{metadataDisplayValue as b}from"../core/metadata.js";import{compareNatural as C}from"../core/utils.js";export const FACET_NONE_VALUE="(none)";export{prettyKey}from"../core/utils.js";export const DEFAULT_FACET_CURATION={split:{label:"Split"},lot:{label:"Lot"},product:{label:"Product"},testProgram:{label:"Program"},temperature:{label:"Temperature"},testDate:{label:"Test date",date:!0},operator:{label:"Operator"},waferId:{label:"Wafer",facet:!1}};function y(e){const t=e.indexOf("T");return t>0?e.slice(0,t):e}export function facetValueOf(e,t,o=DEFAULT_FACET_CURATION){const l=b(e?.[t]);if(l!==void 0)return o[t]?.date?y(l):l}export function commonMetadata(e){if(!e.length)return{};const t=e[0].metadata??{},o={};for(const[l,r]of Object.entries(t))r==null||r===""||e.every(d=>(d.metadata??{})[l]===r)&&(o[l]=r);return o}export function buildFacetTable(e,t={}){const o={...DEFAULT_FACET_CURATION,...t.curation},l=t.facetableOnly??!0,r=new Set;for(const n of e)for(const c of Object.keys(n.metadata??{}))r.add(c);const m=[...Object.keys(o).filter(n=>r.has(n)),...[...r].filter(n=>!(n in o))],p=[];for(const n of m){const c=o[n];if(l&&c&&c.facet===!1)continue;const u=new Map;for(const f of e){const a=facetValueOf(f.metadata,n,o)??FACET_NONE_VALUE,i=u.get(a)??{waferCount:0,dieCount:0};i.waferCount+=1,i.dieCount+=f.dieCount??0,u.set(a,i)}if(u.size===0)continue;const s=Array.from(u,([f,a])=>({value:f,waferCount:a.waferCount,dieCount:a.dieCount}));s.sort((f,a)=>(f.value===FACET_NONE_VALUE?1:0)-(a.value===FACET_NONE_VALUE?1:0)||a.waferCount-f.waferCount||C(f.value,a.value)),p.push({key:n,label:c?.label??n,values:s,splittable:s.length>1})}return p}
@@ -10,6 +10,8 @@ export type { SummaryReportParams, LotSummaryReportParams } from './renderSummar
10
10
  export { renderSummaryReportHtml, renderLotSummaryReportHtml } from './renderSummaryReport.js';
11
11
  export { buildFacetTable, facetValueOf, FACET_NONE_VALUE, DEFAULT_FACET_CURATION } from './facets.js';
12
12
  export type { FacetField, FacetValue, FacetCuration, FacetItem, BuildFacetTableOptions } from './facets.js';
13
+ export { resolveMetadataColumns, discoverDieMetadataKeys } from './metadataColumns.js';
14
+ export type { MetadataColumn, MetadataColumnScope, MetadataColumnSet, MetadataKeySelection, ResolveMetadataColumnsOptions, } from './metadataColumns.js';
13
15
  export { buildCapabilityData } from './capability.js';
14
16
  export type { CapabilityDatum, CapabilityItem } from './capability.js';
15
17
  export { buildCorrelationMatrix, filterCorrelationMatrix } from './correlation.js';
@@ -1 +1 @@
1
- export*from"./types.js";export*from"./regions.js";export*from"./analyzeWaferMap.js";export*from"./analyzeWaferLot.js";export*from"./filterFindings.js";export{classifyPattern}from"./patternClassification.js";export{renderFindingsReportHtml,openHtmlReport,setReportOpener}from"./renderFindingsReport.js";export{renderSummaryReportHtml,renderLotSummaryReportHtml}from"./renderSummaryReport.js";export{buildFacetTable,facetValueOf,FACET_NONE_VALUE,DEFAULT_FACET_CURATION}from"./facets.js";export{buildCapabilityData}from"./capability.js";export{buildCorrelationMatrix,filterCorrelationMatrix}from"./correlation.js";export{buildTestBoxplotData}from"./boxplot.js";export{buildTestHistogramData,buildTestHistogramSeries}from"./histogram.js";export{buildScatterData,buildScatterDataGrouped}from"./scatter.js";export{buildYieldData,buildYieldDataCombined}from"./yield.js";export{buildBinParetoData,buildBinClusterData}from"./binPareto.js";
1
+ export*from"./types.js";export*from"./regions.js";export*from"./analyzeWaferMap.js";export*from"./analyzeWaferLot.js";export*from"./filterFindings.js";export{classifyPattern}from"./patternClassification.js";export{renderFindingsReportHtml,openHtmlReport,setReportOpener}from"./renderFindingsReport.js";export{renderSummaryReportHtml,renderLotSummaryReportHtml}from"./renderSummaryReport.js";export{buildFacetTable,facetValueOf,FACET_NONE_VALUE,DEFAULT_FACET_CURATION}from"./facets.js";export{resolveMetadataColumns,discoverDieMetadataKeys}from"./metadataColumns.js";export{buildCapabilityData}from"./capability.js";export{buildCorrelationMatrix,filterCorrelationMatrix}from"./correlation.js";export{buildTestBoxplotData}from"./boxplot.js";export{buildTestHistogramData,buildTestHistogramSeries}from"./histogram.js";export{buildScatterData,buildScatterDataGrouped}from"./scatter.js";export{buildYieldData,buildYieldDataCombined}from"./yield.js";export{buildBinParetoData,buildBinClusterData}from"./binPareto.js";
@@ -0,0 +1,70 @@
1
+ import type { Die } from '../core/dies.js';
2
+ import type { WaferMetadata } from '../core/metadata.js';
3
+ import type { MetadataFieldDef } from '../renderer/buildWaferMap.js';
4
+ /** Where a column's value comes from. A `'wafer'` column is constant down
5
+ * every row; a `'die'` column genuinely varies die to die. */
6
+ export type MetadataColumnScope = 'die' | 'wafer';
7
+ /** How metadata keys are chosen. `'auto'` = every key present in the data. */
8
+ export type MetadataKeySelection = 'auto' | 'none' | string[];
9
+ export interface MetadataColumn {
10
+ /** The metadata key this column reads. */
11
+ key: string;
12
+ /** Column header, already de-duplicated against every other column. */
13
+ label: string;
14
+ scope: MetadataColumnScope;
15
+ /** Value for one die. Always a string — `''` for absent, never `'undefined'`. */
16
+ get: (die: Die) => string;
17
+ /** Wafer scope only: the die-independent value, for exports with no die rows. */
18
+ constant?: string;
19
+ /** True when the column belongs in the CSV but not the on-screen table. */
20
+ csvOnly: boolean;
21
+ }
22
+ export interface ResolveMetadataColumnsOptions {
23
+ /** Population to discover die keys from. Scanned in full — never sampled. */
24
+ dies?: Die[];
25
+ waferMetadata?: WaferMetadata;
26
+ /** Supplies label and ordering for keys the host has already declared. */
27
+ metadataFields?: MetadataFieldDef[];
28
+ /** Die-level key selection. Default `'auto'`. */
29
+ dieKeys?: MetadataKeySelection;
30
+ /** Wafer-level key selection. Default `'auto'`. */
31
+ waferKeys?: MetadataKeySelection;
32
+ /** Where wafer-level columns appear. Default `'csv'`. */
33
+ waferPlacement?: 'csv' | 'both' | 'none';
34
+ /** Headers already taken by built-in columns, for collision resolution. */
35
+ reservedLabels?: string[];
36
+ /**
37
+ * Cap on auto-discovered die keys. Default 64. Exceeding it is a data shape
38
+ * no table can usefully show; the excess is reported in `truncatedKeys` so
39
+ * the caller can state the omission rather than hide it.
40
+ */
41
+ maxDieKeys?: number;
42
+ }
43
+ export interface MetadataColumnSet {
44
+ columns: MetadataColumn[];
45
+ /** Die keys dropped by `maxDieKeys`, in the order they would have appeared. */
46
+ truncatedKeys: string[];
47
+ }
48
+ /**
49
+ * Union of `die.metadata` keys across the whole population, deterministically
50
+ * ordered: keys named in `metadataFields` first (declaration order), then the
51
+ * rest by `compareNatural`. Never object insertion order — two runs over the
52
+ * same data must produce byte-identical exports.
53
+ *
54
+ * The scan is deliberately exhaustive rather than sampled: a key appearing
55
+ * only on late dies would otherwise vanish from the export with no signal.
56
+ */
57
+ export declare function discoverDieMetadataKeys(dies: Die[], metadataFields?: MetadataFieldDef[], limit?: number): {
58
+ keys: string[];
59
+ truncated: string[];
60
+ };
61
+ /**
62
+ * Build the metadata column set for a population.
63
+ *
64
+ * A key present on both a die and its wafer yields exactly ONE column, scope
65
+ * `'die'`, carrying the die value — the same shadowing rule the hover tooltip
66
+ * applies with `{ ...waferMeta, ...die.metadata }`. Two columns for one key
67
+ * could otherwise disagree in the same row.
68
+ */
69
+ export declare function resolveMetadataColumns(o: ResolveMetadataColumnsOptions): MetadataColumnSet;
70
+ //# sourceMappingURL=metadataColumns.d.ts.map
@@ -0,0 +1 @@
1
+ import{metadataDisplayValue as k}from"../core/metadata.js";import{compareNatural as w,prettyKey as h}from"../core/utils.js";import{DEFAULT_FACET_CURATION as v}from"./facets.js";const K=64;export function discoverDieMetadataKeys(e,n,c=K){const d=new Set;for(const a of e)if(a.metadata)for(const i of Object.keys(a.metadata))d.add(i);const t=[];for(const a of n??[])d.has(a.key)&&!t.includes(a.key)&&t.push(a.key);const y=[...d].filter(a=>!t.includes(a)).sort(w),r=[...t,...y];return{keys:r.slice(0,c),truncated:r.slice(c)}}function A(e){const n=Object.keys(e).filter(t=>k(e[t])!==void 0),c=Object.keys(v).filter(t=>n.includes(t)),d=n.filter(t=>!c.includes(t)).sort(w);return[...c,...d]}const b=(e,n)=>e==="none"?[]:Array.isArray(e)?e.filter(c=>n.includes(c)):n;export function resolveMetadataColumns(e){const n=e.waferMetadata??{},c=e.waferPlacement??"csv",d=discoverDieMetadataKeys(e.dies??[],e.metadataFields,e.maxDieKeys??K),t=b(e.dieKeys,d.keys),y=c==="none"?[]:b(e.waferKeys,A(n)).filter(s=>!t.includes(s)),r=new Set(e.reservedLabels??[]),a=(s,o)=>{const f=e.metadataFields?.find(u=>u.key===s)?.label??h(s);if(!r.has(f))return r.add(f),f;const m=o==="die"?`${f} (metadata)`:`${f} (wafer metadata)`;if(!r.has(m))return r.add(m),m;const l=`${f} (${s})`;if(!r.has(l))return r.add(l),l;for(let u=2;;u++){const p=`${l} (${u})`;if(!r.has(p))return r.add(p),p}},i=[];for(const s of t)i.push({key:s,label:a(s,"die"),scope:"die",get:o=>k(o.metadata?.[s])??"",csvOnly:!1});for(const s of y){const o=k(n[s])??"";i.push({key:s,label:a(s,"wafer"),scope:"wafer",get:()=>o,constant:o,csvOnly:c==="csv"})}return{columns:i,truncatedKeys:d.truncated}}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@wafertools/wafermap",
3
- "version": "0.23.0",
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.",
3
+ "version": "0.24.0",
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 \u2014 pure ES modules, no runtime dependencies, no server required.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": {
@@ -71,20 +71,25 @@
71
71
  "node": ">=18"
72
72
  },
73
73
  "scripts": {
74
+ "help": "node scripts/help.mjs",
74
75
  "build": "node scripts/build-user-guide.mjs && node scripts/sync-icons.mjs && node scripts/sync-version.mjs && rm -rf dist && tsc -p tsconfig.build.json",
75
76
  "build:guide": "node scripts/build-user-guide.mjs",
76
77
  "clean": "rm -rf dist",
77
- "check": "node scripts/sync-version.mjs && tsc --noEmit -p tsconfig.json && node scripts/check-examples-manifest.mjs && node scripts/build-agents-page.mjs --check && node scripts/check-agents-guide.mjs && node scripts/check-toolbar-docs.mjs",
78
+ "check": "node scripts/build-user-guide.mjs && node scripts/sync-version.mjs && tsc --noEmit -p tsconfig.json && node scripts/check-examples-manifest.mjs && node scripts/build-agents-page.mjs --check && node scripts/check-agents-guide.mjs && node scripts/check-toolbar-docs.mjs && node scripts/check-changelog.mjs && node scripts/help.mjs --check",
78
79
  "build:examples-index": "node scripts/build-examples-index.mjs",
79
80
  "check:drift": "node scripts/check-drift.mjs",
80
81
  "dev": "rm -rf docs/dist && cp -r dist docs/dist && ./.venv/bin/zensical serve --dev-addr localhost:8001",
81
82
  "test": "npm run build && node --test tests/*.test.mjs",
82
- "build:site": "rm -rf docs/dist && cp -r dist docs/ && ${ZENSICAL:-.venv/bin/zensical} build --clean && cp -r docs/data site/data && rm -rf docs/dist && node scripts/bundle-docs.mjs && node scripts/build-examples-archive.mjs",
83
- "preview:site": "npm run build:site && echo \"\\nServing bundled site at http://localhost:8002/examples/ — Ctrl-C to stop\\n\" && cd site && python3 -m http.server 8002",
83
+ "build:site": "rm -rf docs/dist && cp -r dist docs/ && ${ZENSICAL:-.venv/bin/zensical} build --clean && cp -r docs/data site/data && cp llms.txt site/llms.txt && rm -rf docs/dist && node scripts/bundle-docs.mjs && node scripts/build-examples-archive.mjs",
84
+ "preview:site": "npm run build:site && echo \"\\nServing bundled site at http://localhost:8002/examples/ \u2014 Ctrl-C to stop\\n\" && cd site && python3 -m http.server 8002",
84
85
  "prepack": "npm run build && node scripts/minify-dist.mjs",
85
86
  "pack:check": "npm pack --dry-run",
86
87
  "prepublishOnly": "npm test",
87
- "postpublish": "echo \"\\ndist/ was minified for publish — run 'npm run build' to restore the readable dev build.\\n\"",
88
+ "verify": "npm run check && npm test",
89
+ "preversion": "npm run verify",
90
+ "version": "node scripts/check-changelog.mjs",
91
+ "postversion": "echo \"\\nTagged v$npm_package_version. Next:\\n git push && git push origin v$npm_package_version\\n npm publish\\n\"",
92
+ "postpublish": "echo \"\\ndist/ was minified for publish \u2014 run 'npm run build' to restore the readable dev build.\\n\"",
88
93
  "screenshots": "node scripts/capture-screenshots.mjs",
89
94
  "screenshots:list": "node scripts/capture-screenshots.mjs --list",
90
95
  "build:images": "npm run build && node scripts/capture-screenshots.mjs",