@wafertools/wafermap 0.30.3 → 0.30.4

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 (47) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.md +2 -2
  3. package/dist/packages/canvas-adapter/charts/boxplot.js +1 -1
  4. package/dist/packages/canvas-adapter/charts/capability.js +1 -1
  5. package/dist/packages/canvas-adapter/charts/chartShell.d.ts +1 -1
  6. package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
  7. package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
  8. package/dist/packages/canvas-adapter/charts/scatter.d.ts +5 -1
  9. package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
  10. package/dist/packages/canvas-adapter/charts/sweep.js +1 -1
  11. package/dist/packages/canvas-adapter/charts/testPassRate.js +1 -1
  12. package/dist/packages/canvas-adapter/charts/trend.js +1 -1
  13. package/dist/packages/canvas-adapter/insightsTab.d.ts +11 -1
  14. package/dist/packages/canvas-adapter/insightsTab.js +1 -1
  15. package/dist/packages/canvas-adapter/maplessSummary.js +1 -1
  16. package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
  17. package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
  18. package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
  19. package/dist/packages/canvas-adapter/toCanvas.js +1 -1
  20. package/dist/packages/canvas-adapter/toolbar.js +1 -1
  21. package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
  22. package/dist/packages/canvas-adapter/userGuideHtml.js +38 -36
  23. package/dist/packages/canvas-adapter/version.d.ts +2 -2
  24. package/dist/packages/canvas-adapter/version.js +1 -1
  25. package/dist/packages/canvas-adapter/warnings.js +1 -1
  26. package/dist/packages/core/dies.d.ts +1 -1
  27. package/dist/packages/renderer/binColors.js +1 -1
  28. package/dist/packages/renderer/buildView.js +1 -1
  29. package/dist/packages/renderer/buildWaferMap.d.ts +40 -12
  30. package/dist/packages/renderer/buildWaferMap.js +1 -1
  31. package/dist/packages/renderer/derivedTests/apply.js +1 -1
  32. package/dist/packages/renderer/derivedTests/parser.js +1 -1
  33. package/dist/packages/renderer/spec.d.ts +17 -0
  34. package/dist/packages/renderer/spec.js +1 -1
  35. package/dist/packages/stats/analyzeWaferMap.js +1 -1
  36. package/dist/packages/stats/capability.d.ts +4 -2
  37. package/dist/packages/stats/capability.js +1 -1
  38. package/dist/packages/stats/correlation.d.ts +1 -1
  39. package/dist/packages/stats/deprecated.d.ts +1 -1
  40. package/dist/packages/stats/deprecated.js +1 -1
  41. package/dist/packages/stats/mergeTestDefs.d.ts +1 -1
  42. package/dist/packages/stats/mergeTestDefs.js +1 -1
  43. package/dist/packages/stats/renderSummaryReport.js +11 -11
  44. package/dist/packages/stats/sweep.d.ts +8 -0
  45. package/dist/packages/stats/sweep.js +1 -1
  46. package/dist/packages/stats/types.d.ts +9 -3
  47. package/package.json +1 -1
@@ -1 +1 @@
1
- import{isYieldEligibleDie as z}from"../core/dies.js";import{isParametricTest as U}from"../renderer/buildWaferMap.js";import{markedTestLabel as G,derivedFields as H}from"../renderer/testLabel.js";import{parseTestReference as J}from"../renderer/derivedTests/parser.js";import{quantile as M}from"./math.js";import{compileXNamePattern as K,readXFromName as Q}from"./sweepXFromName.js";function W(o,l,i){const e=[];let t=!1;for(const r of o.tests){if(typeof r=="number"){Number.isInteger(r)?e.push(r):i.push(`"${o.label}": ${r} is not a test number.`);continue}const s=J(String(r),l);if(!s.ok){i.push(`"${o.label}": "${r}" \u2014 ${s.message}.`);continue}s.range&&(t=!0),e.push(...s.tests)}return{spec:o,tests:e,usedRange:t}}function j(o,l=16){return o.length<=l?o.join(", "):`${o.slice(0,l).join(", ")}, \u2026 (${o.length-l} more)`}function Z(o,l){const i=o?.[l];return i!==void 0&&Number.isFinite(i)?i:l}function _(o,l,i){const e=o.spec;if(e.xValues!==void 0&&e.xFromName!==void 0)return i.push(`"${e.label}": gives both xValues and xFromName \u2014 use one.`),{asked:!0};if(e.xValues!==void 0){const t=e.xValues.length;return t===o.tests.length?{xs:e.xValues,asked:!0}:(i.push(o.usedRange?`"${e.label}": ${t} x values for ${o.tests.length} tests \u2014 the ranges matched ${j(o.tests)}.`:`"${e.label}": ${t} x values for ${o.tests.length} tests.`),{asked:!0})}if(e.xFromName!==void 0){const t=K(e.xFromName);if("error"in t)return i.push(`"${e.label}": xFromName "${e.xFromName}" \u2014 ${t.error}.`),{asked:!0};const r=[],s=[],a=[];for(const u of o.tests){const c=l.get(u)?.name,f=c===void 0?null:Q(t,c);typeof f=="number"?r.push(f):(s.push(u),f!==null&&a.push(f.ambiguous))}return s.length>0?(i.push(`"${e.label}": xFromName "${e.xFromName}" found no x value in the name of ${s.length===1?"test":"tests"} ${j(s)}`+(a.length>0?` \u2014 "${a[0]}" is written in capitals, where M could be milli or mega; put the letter in the pattern ("\u2026{x}M\u2026") and the unit in xLabel.`:".")),{asked:!0}):{xs:r,asked:!0}}return{asked:!1}}function ee(o,l,i){const e=new Map;for(let t=0;t<i.length;t++){const r=e.get(i[t]);if(r!==void 0)return`"${o}": tests ${l[r]} and ${l[t]} have the same x value (${i[t]}) \u2014 a curve cannot be measured where it revisits an x.`;e.set(i[t],t)}return null}function B(o,l){const i=o.filter(e=>e.count>0);for(let e=1;e<i.length;e++){const t=i[e-1],r=i[e],s=Math.min(t.median,r.median),a=Math.max(t.median,r.median);if(l<s||l>a)continue;if(t.median===r.median)return t.x;const u=(l-t.median)/(r.median-t.median);return t.x+u*(r.x-t.x)}return null}function ne(o,l){const i=new Map(l.filter(s=>s.count>0).map(s=>[s.x,s])),e=[];for(const s of o){if(s.count===0)continue;const a=i.get(s.x);a!==void 0&&e.push({x:s.x,d:s.median-a.median,y:s.median})}if(e.length<2)return null;const t=[];for(let s=0;s<e.length;s++){const a=e[s];if(a.d===0){t.push({x:a.x,y:a.y});continue}if(s===0)continue;const u=e[s-1];if(u.d===0||u.d<0==a.d<0)continue;const c=u.d/(u.d-a.d);t.push({x:u.x+c*(a.x-u.x),y:u.y+c*(a.y-u.y)})}const r=t[0];return r===void 0?null:{x:r.x,y:r.y,multiple:t.length>1}}export function buildSweepData(o,l,i){const e=[],t=new Map((l??[]).map(n=>[n.testNumber,n])),r=o.filter(n=>z(n)),s=i.series.map(n=>W(n,t,e)),a=s.map(n=>_(n,t,e)),u=s.length>0&&a.every(n=>n.xs!==void 0),c=!u&&a.some(n=>n.asked);c&&a.some(n=>!n.asked)&&e.push("x values are given for some series but not all.");const f=u?s.map((n,d)=>ee(n.spec.label,n.tests,a[d].xs)).find(n=>n!==null)??null:null;f!==null&&e.push(f);const V=i.xScale==="log",S=u&&a.every(n=>n.xs.every(d=>d>0)),b=V&&S?"log":"linear";V&&!S&&e.push(u?"A log x axis needs every x value to be positive \u2014 drawn on a linear axis.":"A log x axis needs x values (xValues or xFromName) \u2014 drawn in test order.");const L=s.map(({spec:n,tests:d},p)=>{const v=u?a[p].xs:void 0,N=d.map((x,O)=>{const h=t.get(x);h===void 0?e.push(`"${n.label}": test ${x} is not in this program's test definitions.`):U(h)||e.push(`"${n.label}": test ${x} is functional and has no measured value.`);const m=[];if(h!==void 0&&U(h))for(const A of r){const g=A.testValues?.[x];g!==void 0&&Number.isFinite(g)&&m.push(g)}return m.sort((A,g)=>A-g),{testNumber:x,label:G(h,x),...H(h),x:Z(v,O),median:m.length?M(m,.5):NaN,p10:m.length?M(m,.1):NaN,p90:m.length?M(m,.9):NaN,count:m.length}});return{label:n.label,color:n.color,points:N}}),[T,X]=L,$=T!==void 0&&X!==void 0&&!c&&f===null,D=i.crossing??!0,I=n=>b==="log"?Math.log10(n):n,k=n=>b==="log"?10**n:n,C=n=>n.map(d=>({...d,x:I(d.x)})),P=$?C(T.points):[],q=$?C(X.points):[],Y=(c||f!==null?[]:i.separationAt??[]).map(n=>{if(!$)return{y:n,distance:null};const d=B(P,n),p=B(q,n);if(d===null)return{y:n,distance:null,reason:"first-series-never-reaches"};if(p===null)return{y:n,distance:null,reason:"second-series-never-reaches"};const v=k(d),N=k(p);return{y:n,distance:Math.abs(N-v),from:v,to:N,...b==="log"?{ratio:10**Math.abs(d-p)}:{}}}),y=$&&D?ne(P,q):null,E=y&&{...y,x:k(y.x)},R=n=>n===void 0?void 0:t.get(n.tests[0]??-1)?.unit,F=R(s[0]),w=R(s[1]);return F!==void 0&&w!==void 0&&F!==w&&e.push(`Series measure different units (${F} and ${w}) \u2014 crossing and separation compare unlike quantities.`),{title:i.title,xLabel:c?"Test":i.xLabel??(u?"Sweep value":"Test"),yLabel:i.yLabel??"Value",series:L,xIsPhysical:u,...u&&i.xUnit?{xUnit:i.xUnit}:{},xScale:b,crossing:E,separations:Y,dieCount:r.length,warnings:e,...c&&i.series.length>=2?{notMeasured:"x values do not match the tests, so the sweep is drawn in test order and the crossing and widths are not measured"}:f!==null&&i.series.length>=2?{notMeasured:"a series revisits an x value, so its points cannot be paired with the other series"}:{}}}
1
+ import{isYieldEligibleDie as G}from"../core/dies.js";import{isParametricTest as M}from"../renderer/buildWaferMap.js";import{markedTestLabel as H,derivedFields as J}from"../renderer/testLabel.js";import{parseTestReference as K}from"../renderer/derivedTests/parser.js";import{quantile as V}from"./math.js";import{compileXNamePattern as Q,readXFromName as W}from"./sweepXFromName.js";function j(o,a,s){const e=[];let t=!1;for(const r of o.tests){if(typeof r=="number"){Number.isInteger(r)?e.push(r):s.push(`"${o.label}": ${r} is not a test number.`);continue}const i=K(String(r),a);if(!i.ok){s.push(`"${o.label}": "${r}" \u2014 ${i.message}.`);continue}i.range&&(t=!0),e.push(...i.tests)}return{spec:o,tests:e,usedRange:t}}export function sweepAppliesTo(o,a){const s=new Map((a??[]).map(e=>[e.testNumber,e]));return o.series.some(e=>j(e,s,[]).tests.some(t=>{const r=s.get(t);return r!==void 0&&M(r)}))}function B(o,a=16){return o.length<=a?o.join(", "):`${o.slice(0,a).join(", ")}, \u2026 (${o.length-a} more)`}function Z(o,a){const s=o?.[a];return s!==void 0&&Number.isFinite(s)?s:a}function _(o,a,s){const e=o.spec;if(e.xValues!==void 0&&e.xFromName!==void 0)return s.push(`"${e.label}": gives both xValues and xFromName \u2014 use one.`),{asked:!0};if(e.xValues!==void 0){const t=e.xValues.length;return t===o.tests.length?{xs:e.xValues,asked:!0}:(s.push(o.usedRange?`"${e.label}": ${t} x values for ${o.tests.length} tests \u2014 the ranges matched ${B(o.tests)}.`:`"${e.label}": ${t} x values for ${o.tests.length} tests.`),{asked:!0})}if(e.xFromName!==void 0){const t=Q(e.xFromName);if("error"in t)return s.push(`"${e.label}": xFromName "${e.xFromName}" \u2014 ${t.error}.`),{asked:!0};const r=[],i=[],u=[];for(const l of o.tests){const c=a.get(l)?.name,f=c===void 0?null:W(t,c);typeof f=="number"?r.push(f):(i.push(l),f!==null&&u.push(f.ambiguous))}return i.length>0?(s.push(`"${e.label}": xFromName "${e.xFromName}" found no x value in the name of ${i.length===1?"test":"tests"} ${B(i)}`+(u.length>0?` \u2014 "${u[0]}" is written in capitals, where M could be milli or mega; put the letter in the pattern ("\u2026{x}M\u2026") and the unit in xLabel.`:".")),{asked:!0}):{xs:r,asked:!0}}return{asked:!1}}function ee(o,a,s){const e=new Map;for(let t=0;t<s.length;t++){const r=e.get(s[t]);if(r!==void 0)return`"${o}": tests ${a[r]} and ${a[t]} have the same x value (${s[t]}) \u2014 a curve cannot be measured where it revisits an x.`;e.set(s[t],t)}return null}function D(o,a){const s=o.filter(e=>e.count>0);for(let e=1;e<s.length;e++){const t=s[e-1],r=s[e],i=Math.min(t.median,r.median),u=Math.max(t.median,r.median);if(a<i||a>u)continue;if(t.median===r.median)return t.x;const l=(a-t.median)/(r.median-t.median);return t.x+l*(r.x-t.x)}return null}function ne(o,a){const s=new Map(a.filter(i=>i.count>0).map(i=>[i.x,i])),e=[];for(const i of o){if(i.count===0)continue;const u=s.get(i.x);u!==void 0&&e.push({x:i.x,d:i.median-u.median,y:i.median})}if(e.length<2)return null;const t=[];for(let i=0;i<e.length;i++){const u=e[i];if(u.d===0){t.push({x:u.x,y:u.y});continue}if(i===0)continue;const l=e[i-1];if(l.d===0||l.d<0==u.d<0)continue;const c=l.d/(l.d-u.d);t.push({x:l.x+c*(u.x-l.x),y:l.y+c*(u.y-l.y)})}const r=t[0];return r===void 0?null:{x:r.x,y:r.y,multiple:t.length>1}}export function buildSweepData(o,a,s){const e=[],t=new Map((a??[]).map(n=>[n.testNumber,n])),r=o.filter(n=>G(n)),i=s.series.map(n=>j(n,t,e)),u=i.map(n=>_(n,t,e)),l=i.length>0&&u.every(n=>n.xs!==void 0),c=!l&&u.some(n=>n.asked);c&&u.some(n=>!n.asked)&&e.push("x values are given for some series but not all.");const f=l?i.map((n,d)=>ee(n.spec.label,n.tests,u[d].xs)).find(n=>n!==null)??null:null;f!==null&&e.push(f);const S=s.xScale==="log",T=l&&u.every(n=>n.xs.every(d=>d>0)),b=S&&T?"log":"linear";S&&!T&&e.push(l?"A log x axis needs every x value to be positive \u2014 drawn on a linear axis.":"A log x axis needs x values (xValues or xFromName) \u2014 drawn in test order.");const L=i.map(({spec:n,tests:d},p)=>{const v=l?u[p].xs:void 0,N=d.map((x,z)=>{const h=t.get(x);h===void 0?e.push(`"${n.label}": test ${x} is not in this program's test definitions.`):M(h)||e.push(`"${n.label}": test ${x} is functional and has no measured value.`);const m=[];if(h!==void 0&&M(h))for(const F of r){const g=F.testValues?.[x];g!==void 0&&Number.isFinite(g)&&m.push(g)}return m.sort((F,g)=>F-g),{testNumber:x,label:H(h,x),...J(h),x:Z(v,z),median:m.length?V(m,.5):NaN,p10:m.length?V(m,.1):NaN,p90:m.length?V(m,.9):NaN,count:m.length}});return{label:n.label,color:n.color,points:N}}),[X,C]=L,$=X!==void 0&&C!==void 0&&!c&&f===null,I=s.crossing??!0,Y=n=>b==="log"?Math.log10(n):n,k=n=>b==="log"?10**n:n,P=n=>n.map(d=>({...d,x:Y(d.x)})),q=$?P(X.points):[],R=$?P(C.points):[],E=(c||f!==null?[]:s.separationAt??[]).map(n=>{if(!$)return{y:n,distance:null};const d=D(q,n),p=D(R,n);if(d===null)return{y:n,distance:null,reason:"first-series-never-reaches"};if(p===null)return{y:n,distance:null,reason:"second-series-never-reaches"};const v=k(d),N=k(p);return{y:n,distance:Math.abs(N-v),from:v,to:N,...b==="log"?{ratio:10**Math.abs(d-p)}:{}}}),w=$&&I?ne(q,R):null,O=w&&{...w,x:k(w.x)},U=n=>n===void 0?void 0:t.get(n.tests[0]??-1)?.unit,y=U(i[0]),A=U(i[1]);return y!==void 0&&A!==void 0&&y!==A&&e.push(`Series measure different units (${y} and ${A}) \u2014 crossing and separation compare unlike quantities.`),{title:s.title,xLabel:c?"Test":s.xLabel??(l?"Sweep value":"Test"),yLabel:s.yLabel??"Value",series:L,xIsPhysical:l,...l&&s.xUnit?{xUnit:s.xUnit}:{},xScale:b,crossing:O,separations:E,dieCount:r.length,warnings:e,...c&&s.series.length>=2?{notMeasured:"x values do not match the tests, so the sweep is drawn in test order and the crossing and widths are not measured"}:f!==null&&s.series.length>=2?{notMeasured:"a series revisits an x value, so its points cannot be paired with the other series"}:{}}}
@@ -233,7 +233,7 @@ export interface StatsSummary {
233
233
  /** Number of wafers in the lot stack. Present only when isLotStack is true. */
234
234
  lotSize?: number;
235
235
  /**
236
- * Per-test spec yield for each test that has at least one limit defined.
236
+ * Per-test yield against the test limits, for each test that has at least one limit defined.
237
237
  * Only populated when testDefs with limitLow/limitHigh are provided.
238
238
  */
239
239
  testSpecYield?: Array<{
@@ -293,7 +293,7 @@ export interface StatsSummary {
293
293
  capability?: TestCapability[];
294
294
  /**
295
295
  * Per-test pass rate by the tester's recorded verdict, for parametric tests
296
- * with recorded verdicts — the counterpart of `testSpecYield` (spec limits)
296
+ * with recorded verdicts — the counterpart of `testSpecYield` (test limits)
297
297
  * and `functionalYield` (functional tests). Worst first.
298
298
  */
299
299
  testFlagYield?: TestVerdictYield[];
@@ -339,10 +339,16 @@ export interface TestCapability {
339
339
  derived?: true;
340
340
  /** The expression a `derived` test was computed from, for display. */
341
341
  expression?: string;
342
- /** Both `limitLow` and `limitHigh` defined. When false, `lsl`/`usl` are absent and every index is null. */
342
+ /**
343
+ * Limits to judge capability against exist: both spec limits (`specLow`/
344
+ * `specHigh`), or failing that both test limits (`limitLow`/`limitHigh`). When
345
+ * false, `lsl`/`usl` are absent and every index is null.
346
+ */
343
347
  hasSpec: boolean;
344
348
  lsl?: number;
345
349
  usl?: number;
350
+ /** `'spec'` when `lsl`/`usl` are the spec limits, `'test'` when they are the test limits. */
351
+ limitBasis?: 'spec' | 'test';
346
352
  mean: number;
347
353
  /** Sample stddev (ddof = 1) over every value. */
348
354
  stdOverall: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wafertools/wafermap",
3
- "version": "0.30.3",
3
+ "version": "0.30.4",
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",