@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.
- package/CHANGELOG.md +48 -0
- package/README.md +2 -2
- 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/chartShell.d.ts +1 -1
- package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
- package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
- package/dist/packages/canvas-adapter/charts/scatter.d.ts +5 -1
- package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
- package/dist/packages/canvas-adapter/charts/sweep.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/insightsTab.d.ts +11 -1
- package/dist/packages/canvas-adapter/insightsTab.js +1 -1
- package/dist/packages/canvas-adapter/maplessSummary.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
- package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
- package/dist/packages/canvas-adapter/toCanvas.js +1 -1
- package/dist/packages/canvas-adapter/toolbar.js +1 -1
- package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
- package/dist/packages/canvas-adapter/userGuideHtml.js +38 -36
- 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/dies.d.ts +1 -1
- package/dist/packages/renderer/binColors.js +1 -1
- package/dist/packages/renderer/buildView.js +1 -1
- package/dist/packages/renderer/buildWaferMap.d.ts +40 -12
- package/dist/packages/renderer/buildWaferMap.js +1 -1
- package/dist/packages/renderer/derivedTests/apply.js +1 -1
- package/dist/packages/renderer/derivedTests/parser.js +1 -1
- package/dist/packages/renderer/spec.d.ts +17 -0
- package/dist/packages/renderer/spec.js +1 -1
- package/dist/packages/stats/analyzeWaferMap.js +1 -1
- package/dist/packages/stats/capability.d.ts +4 -2
- package/dist/packages/stats/capability.js +1 -1
- package/dist/packages/stats/correlation.d.ts +1 -1
- package/dist/packages/stats/deprecated.d.ts +1 -1
- package/dist/packages/stats/deprecated.js +1 -1
- package/dist/packages/stats/mergeTestDefs.d.ts +1 -1
- package/dist/packages/stats/mergeTestDefs.js +1 -1
- package/dist/packages/stats/renderSummaryReport.js +11 -11
- package/dist/packages/stats/sweep.d.ts +8 -0
- package/dist/packages/stats/sweep.js +1 -1
- package/dist/packages/stats/types.d.ts +9 -3
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{diePassStatus as P}from"../../core/dies.js";import{getTestPassStatus as D,isParametricTest as S}from"../buildWaferMap.js";import{classifySpec as V}from"../spec.js";import{parseExpression as k}from"./parser.js";import{evaluate as O}from"./evaluate.js";function p(m){return{code:"derived-test-invalid",message:m,severity:"warning"}}function M(m,l){const w=[],o=n=>{switch(n.k){case"acc":{if(n.kind!=="t")return;const i=l.get(n.test)?.unit;return i?new Set([i]):void 0}case"unary":return n.op==="-"?o(n.arg):void 0;case"binary":{const i=o(n.left),a=o(n.right);if(n.op==="+"||n.op==="-"){if(i&&a){const d=new Set([...i,...a]);return d.size>1&&w.push(`${[...d].join(" and ")} are combined by "${n.op}"`),d}return i??a}return}case"call":{if(n.name==="min"||n.name==="max"||n.name==="mean"||n.name==="sum"){const i=n.args[0];if(i?.k==="vec"&&i.kind==="t"){const a=new Set(i.tests.map(d=>l.get(d)?.unit).filter(d=>!!d));return a.size>1&&w.push(`${[...a].join(" and ")} are combined by "${n.name}"`),a}}return}default:return}};return o(m),w}function T(m,l,w){const o=[],n=new Map((l??[]).map(e=>[e.testNumber,e])),i=e=>`${JSON.stringify(e.name??"")} (test ${e.testNumber})`,a=[],d=new Set;for(const e of m){const t=i(e);if(!Number.isInteger(e.testNumber)){o.push(p(`Derived test ${t} was dropped: \`testNumber\` must be an integer.`));continue}if(w.has(e.testNumber)||n.has(e.testNumber)){o.push(p(`Derived test ${t} was dropped: test number ${e.testNumber} already exists in the data. Measured values are never replaced by a derived test \u2014 give it an unused number.`));continue}if(d.has(e.testNumber)){o.push(p(`Derived test ${t} was dropped: test number ${e.testNumber} is used by an earlier derived test.`));continue}if(typeof e.expression!="string"||e.expression.trim()===""){o.push(p(`Derived test ${t} was dropped: \`expression\` is empty.`));continue}d.add(e.testNumber),a.push(e)}const y=new Map(n);for(const e of a){const{expression:t,constants:u,...c}=e;y.set(e.testNumber,{...c,testType:e.testType??"P"})}const s=new Map,r=new Set;for(const e of a){const t=i(e),u={testDefs:y,constants:e.constants??{}},c=k(e.expression,u);if(!c.ok){o.push(p(`Derived test ${t} was dropped: ${c.message}`)),r.add(e.testNumber);continue}const f=c.type==="boolean",g=!S(e);if(f!==g){o.push(p(f?`Derived test ${t} was dropped: its expression produces a true/false verdict, so it must declare \`testType: 'F'\`.`:`Derived test ${t} was dropped: it declares \`testType: 'F'\` but its expression produces a number. Functional tests carry a verdict, not a measurement.`)),r.add(e.testNumber);continue}if(f&&(e.limitLow!==void 0||e.limitHigh!==void 0)&&o.push(p(`Derived test ${t}:
|
|
1
|
+
import{diePassStatus as P}from"../../core/dies.js";import{getTestPassStatus as D,isParametricTest as S}from"../buildWaferMap.js";import{classifySpec as V}from"../spec.js";import{parseExpression as k}from"./parser.js";import{evaluate as O}from"./evaluate.js";function p(m){return{code:"derived-test-invalid",message:m,severity:"warning"}}function M(m,l){const w=[],o=n=>{switch(n.k){case"acc":{if(n.kind!=="t")return;const i=l.get(n.test)?.unit;return i?new Set([i]):void 0}case"unary":return n.op==="-"?o(n.arg):void 0;case"binary":{const i=o(n.left),a=o(n.right);if(n.op==="+"||n.op==="-"){if(i&&a){const d=new Set([...i,...a]);return d.size>1&&w.push(`${[...d].join(" and ")} are combined by "${n.op}"`),d}return i??a}return}case"call":{if(n.name==="min"||n.name==="max"||n.name==="mean"||n.name==="sum"){const i=n.args[0];if(i?.k==="vec"&&i.kind==="t"){const a=new Set(i.tests.map(d=>l.get(d)?.unit).filter(d=>!!d));return a.size>1&&w.push(`${[...a].join(" and ")} are combined by "${n.name}"`),a}}return}default:return}};return o(m),w}function T(m,l,w){const o=[],n=new Map((l??[]).map(e=>[e.testNumber,e])),i=e=>`${JSON.stringify(e.name??"")} (test ${e.testNumber})`,a=[],d=new Set;for(const e of m){const t=i(e);if(!Number.isInteger(e.testNumber)){o.push(p(`Derived test ${t} was dropped: \`testNumber\` must be an integer.`));continue}if(w.has(e.testNumber)||n.has(e.testNumber)){o.push(p(`Derived test ${t} was dropped: test number ${e.testNumber} already exists in the data. Measured values are never replaced by a derived test \u2014 give it an unused number.`));continue}if(d.has(e.testNumber)){o.push(p(`Derived test ${t} was dropped: test number ${e.testNumber} is used by an earlier derived test.`));continue}if(typeof e.expression!="string"||e.expression.trim()===""){o.push(p(`Derived test ${t} was dropped: \`expression\` is empty.`));continue}d.add(e.testNumber),a.push(e)}const y=new Map(n);for(const e of a){const{expression:t,constants:u,...c}=e;y.set(e.testNumber,{...c,testType:e.testType??"P"})}const s=new Map,r=new Set;for(const e of a){const t=i(e),u={testDefs:y,constants:e.constants??{}},c=k(e.expression,u);if(!c.ok){o.push(p(`Derived test ${t} was dropped: ${c.message}`)),r.add(e.testNumber);continue}const f=c.type==="boolean",g=!S(e);if(f!==g){o.push(p(f?`Derived test ${t} was dropped: its expression produces a true/false verdict, so it must declare \`testType: 'F'\`.`:`Derived test ${t} was dropped: it declares \`testType: 'F'\` but its expression produces a number. Functional tests carry a verdict, not a measurement.`)),r.add(e.testNumber);continue}if(f&&(e.limitLow!==void 0||e.limitHigh!==void 0)&&o.push(p(`Derived test ${t}: test limits are ignored on a true/false derived test.`)),e.testNumber===void 0)continue;const N=e.testNumber,x=[...new Set(c.reads.map(h=>h.test))].filter(h=>h!==N&&d.has(h));if(c.reads.some(h=>h.test===N)){o.push(p(`Derived test ${t} was dropped: its expression reads itself.`)),r.add(N);continue}for(const h of M(c.ast,y))o.push(p(`Derived test ${t} combines different units \u2014 ${h}. The result is computed as written; set \`unit\` on the derived test to say what it is.`));s.set(N,{raw:e,compiled:{def:{...e,testType:f?"F":"P",derived:!0},ast:c.ast,boolean:f},deps:x})}const b=[],v=new Map,$=(e,t)=>{if(v.get(e)==="done")return!r.has(e);if(v.get(e)==="visiting"){const f=[...t.slice(t.indexOf(e)),e].join(" \u2192 ");for(const g of t.slice(t.indexOf(e)))r.has(g)||(r.add(g),o.push(p(`Derived test ${i(s.get(g).raw)} was dropped: its expression depends on itself through ${f}.`)));return!1}const u=s.get(e);if(u===void 0)return!1;v.set(e,"visiting");let c=!0;for(const f of u.deps)if(!$(f,[...t,e])){if(!r.has(e)){r.add(e);const g=s.get(f)?.raw.name??`test ${f}`;o.push(p(`Derived test ${i(u.raw)} was dropped: it reads ${JSON.stringify(g)} (test ${f}), which was itself dropped.`))}c=!1;break}return v.set(e,"done"),!c||r.has(e)?!1:(b.push(u.compiled),!0)};for(const e of a)$(e.testNumber,[]);return{compiled:b,warnings:o}}export function applyDerivedTests(m,l,w,o){if(w===void 0||w.length===0)return{results:m,testDefs:l,warnings:[]};const n=new Set;for(const s of m){for(const r of Object.keys(s.testValues??{}))n.add(Number(r));for(const r of Object.keys(s.testPass??{}))n.add(Number(r))}const{compiled:i,warnings:a}=T(w,l,n);if(i.length===0)return{results:m,testDefs:l,warnings:a};const d=new Map((l??[]).map(s=>[s.testNumber,s]));for(const s of i)d.set(s.def.testNumber,s.def);return{results:m.map(s=>{let r,b;const v=t=>r?.[t]??s.testValues?.[t],e={value:v,testPass:t=>b?.[t]??D({testValues:r??s.testValues,testPass:b??s.testPass},t,d.get(t)),specPass:t=>{const u=V(v(t),d.get(t));return u===null?void 0:u==="pass"},diePass:()=>P(s,o)};for(const t of i){const u=O(t.ast,e);u!==void 0&&(t.boolean?(b??={...s.testPass},b[t.def.testNumber]=u):(r??={...s.testValues},r[t.def.testNumber]=u))}return r===void 0&&b===void 0?s:{...s,...r!==void 0?{testValues:r}:{},...b!==void 0?{testPass:b}:{}}}),testDefs:[...l??[],...i.map(s=>s.def)],warnings:a}}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import{isParametricTest as c}from"../buildWaferMap.js";import{hasSpecLimits as l}from"../spec.js";export const FUNCTIONS={abs:[{arity:1,returns:"number"}],sqrt:[{arity:1,returns:"number"}],ln:[{arity:1,returns:"number"}],log10:[{arity:1,returns:"number"}],exp:[{arity:1,returns:"number"}],floor:[{arity:1,returns:"number"}],ceil:[{arity:1,returns:"number"}],round:[{arity:1,returns:"number"}],sign:[{arity:1,returns:"number"}],pow:[{arity:2,returns:"number"}],min:[{vecOf:"numberVec",returns:"number"},{variadic:!0,returns:"number"}],max:[{vecOf:"numberVec",returns:"number"},{variadic:!0,returns:"number"}],if:[{arity:3,returns:"number"}],all:[{vecOf:"boolVec",returns:"boolean"}],any:[{vecOf:"boolVec",returns:"boolean"}],none:[{vecOf:"boolVec",returns:"boolean"}],countTrue:[{vecOf:"boolVec",returns:"number"}],countFalse:[{vecOf:"boolVec",returns:"number"}],mean:[{vecOf:"numberVec",returns:"number"}],sum:[{vecOf:"numberVec",returns:"number"}],countKnown:[{vecOf:"any",returns:"number"}]};const d=["t","testPass","specPass"],m=["<=",">=","==","!=","..","+","-","*","/","%","^","<",">"],b=["(",")","[","]",","];class o extends Error{position;constructor(t,e){super(t),this.position=e}}function h(r){const t=[];let e=0;for(;e<r.length;){const n=r[e];if(n===" "||n===" "||n===`
|
|
2
|
-
`||n==="\r"){e++;continue}if(n>="0"&&n<="9"){const s=e;for(;e<r.length&&r[e]>="0"&&r[e]<="9";)e++;if(r[e]==="."&&r[e+1]!==".")for(e++;e<r.length&&r[e]>="0"&&r[e]<="9";)e++;if(r[e]==="e"||r[e]==="E"){const a=e;if(e++,(r[e]==="+"||r[e]==="-")&&e++,r[e]>="0"&&r[e]<="9")for(;e<r.length&&r[e]>="0"&&r[e]<="9";)e++;else e=a}const u=r.slice(s,e);t.push({kind:"num",text:u,pos:s,value:Number(u)});continue}if(/[A-Za-z_]/.test(n)){const s=e;for(;e<r.length&&/[A-Za-z0-9_]/.test(r[e]);)e++;t.push({kind:"ident",text:r.slice(s,e),pos:s});continue}const i=m.find(s=>r.startsWith(s,e));if(i){t.push({kind:"op",text:i,pos:e}),e+=i.length;continue}if(b.includes(n)){t.push({kind:"punc",text:n,pos:e}),e++;continue}throw new o(`Unexpected character ${JSON.stringify(n)} at position ${e}`,e)}return t.push({kind:"eof",text:"",pos:r.length}),t}class f{toks;ctx;p=0;reads=[];usesDiePass=!1;constructor(t,e){this.toks=t,this.ctx=e}peek(){return this.toks[this.p]}next(){return this.toks[this.p++]}at(t){const e=this.peek();return(e.kind==="op"||e.kind==="punc"||e.kind==="ident")&&e.text===t}eat(t){return this.at(t)?(this.p++,!0):!1}expect(t){if(!this.at(t)){const e=this.peek();throw new o(`Expected ${JSON.stringify(t)} but found ${e.kind==="eof"?"end of expression":JSON.stringify(e.text)} at position ${e.pos}`,e.pos)}return this.next()}parse(){const t=this.parseOr();return this.expectEnd(),t}parseTestRefOnly(){const t=this.parseTestRef(this.peek().pos);return this.expectEnd(),t}expectEnd(){const t=this.peek();if(t.kind!=="eof")throw new o(`Unexpected ${JSON.stringify(t.text)} at position ${t.pos}`,t.pos)}requireScalar(t,e,n){if(t.type==="numberVec"||t.type==="boolVec")throw new o(`${e} cannot be applied to a test range at position ${n} \u2014 a range must be reduced first, e.g. mean(t[1010..1015]) or all(testPass[1010..1015])`,n);return t.type}parseOr(){let t=this.parseAnd();for(;this.at("or");){const e=this.next().pos,n=this.parseAnd();this.requireBool(t,"or",e),this.requireBool(n,"or",e),t={node:{k:"binary",op:"or",left:t.node,right:n.node},type:"boolean"}}return t}parseAnd(){let t=this.parseNot();for(;this.at("and");){const e=this.next().pos,n=this.parseNot();this.requireBool(t,"and",e),this.requireBool(n,"and",e),t={node:{k:"binary",op:"and",left:t.node,right:n.node},type:"boolean"}}return t}requireBool(t,e,n){if(this.requireScalar(t,`Operator ${JSON.stringify(e)}`,n),t.type!=="boolean")throw new o(`Operator ${JSON.stringify(e)} needs a true/false operand at position ${n}, but got a number \u2014 compare it first, e.g. t[1020] > 5`,n)}parseNot(){if(this.at("not")){const t=this.next().pos,e=this.parseNot();return this.requireBool(e,"not",t),{node:{k:"unary",op:"not",arg:e.node},type:"boolean"}}return this.parseCompare()}parseCompare(){const t=this.parseAdd(),e=this.peek();if(e.kind==="op"&&["<","<=",">",">=","==","!="].includes(e.text)){this.next();const n=this.parseAdd(),i=this.requireScalar(t,`Operator ${JSON.stringify(e.text)}`,e.pos),s=this.requireScalar(n,`Operator ${JSON.stringify(e.text)}`,e.pos);if(i!==s)throw new o(`Cannot compare a ${i} with a ${s} at position ${e.pos}`,e.pos);if(i==="boolean"&&e.text!=="=="&&e.text!=="!=")throw new o(`Operator ${JSON.stringify(e.text)} cannot order true/false values at position ${e.pos}`,e.pos);return{node:{k:"binary",op:e.text,left:t.node,right:n.node},type:"boolean"}}return t}parseAdd(){let t=this.parseMul();for(;;){const e=this.peek();if(e.kind!=="op"||e.text!=="+"&&e.text!=="-")return t;this.next();const n=this.parseMul();this.requireNum(t,e.text,e.pos),this.requireNum(n,e.text,e.pos),t={node:{k:"binary",op:e.text,left:t.node,right:n.node},type:"number"}}}parseMul(){let t=this.parsePow();for(;;){const e=this.peek();if(e.kind!=="op"||!["*","/","%"].includes(e.text))return t;this.next();const n=this.parsePow();this.requireNum(t,e.text,e.pos),this.requireNum(n,e.text,e.pos),t={node:{k:"binary",op:e.text,left:t.node,right:n.node},type:"number"}}}parsePow(){const t=this.parseUnary(),e=this.peek();if(e.kind==="op"&&e.text==="^"){this.next();const n=this.parsePow();return this.requireNum(t,"^",e.pos),this.requireNum(n,"^",e.pos),{node:{k:"binary",op:"^",left:t.node,right:n.node},type:"number"}}return t}requireNum(t,e,n){if(this.requireScalar(t,`Operator ${JSON.stringify(e)}`,n),t.type!=="number")throw new o(`Operator ${JSON.stringify(e)} needs a numeric operand at position ${n}, but got a true/false value`,n)}parseUnary(){const t=this.peek();if(t.kind==="op"&&t.text==="-"){this.next();const e=this.parseUnary();return this.requireNum(e,"-",t.pos),{node:{k:"unary",op:"-",arg:e.node},type:"number"}}return this.parsePrimary()}parsePrimary(){const t=this.next();if(t.kind==="num")return{node:{k:"num",value:t.value},type:"number"};if(t.kind==="punc"&&t.text==="("){const e=this.parseOr();return this.expect(")"),e}if(t.kind==="ident"){if(d.includes(t.text))return this.parseAccessor(t.text,t.pos);if(t.text==="diePass")return this.expect("("),this.expect(")"),this.usesDiePass=!0,{node:{k:"diePass"},type:"boolean"};if(this.at("("))return this.parseCall(t.text,t.pos);if(Object.prototype.hasOwnProperty.call(this.ctx.constants,t.text)){const e=this.ctx.constants[t.text];if(typeof e!="number"||!Number.isFinite(e))throw new o(`Constant ${JSON.stringify(t.text)} at position ${t.pos} is not a finite number`,t.pos);return{node:{k:"num",value:e},type:"number"}}throw new o(`Unknown name ${JSON.stringify(t.text)} at position ${t.pos}. Test values are read as t[1020], verdicts as testPass[1020] or specPass[1020]; other names must be declared in \`constants\`.`,t.pos)}throw new o(`Unexpected ${t.kind==="eof"?"end of expression":JSON.stringify(t.text)} at position ${t.pos}`,t.pos)}parseAccessor(t,e){this.expect("[");const{tests:n,range:i}=this.parseTestRef(e);this.expect("]");for(const u of n)this.checkAccessible(t,u,e);for(const u of n)this.reads.push({kind:t,test:u});const s=t==="t"?"number":"boolean";return i?{node:{k:"vec",kind:t,tests:n},type:s==="number"?"numberVec":"boolVec"}:{node:{k:"acc",kind:t,test:n[0]},type:s}}checkAccessible(t,e,n){const i=this.ctx.testDefs.get(e);if(i===void 0)throw new o(`Test ${e} at position ${n} is not declared in \`testDefs\``,n);if(t==="t"&&!c(i))throw new o(`t[${e}] at position ${n} reads a measured value, but test ${e} is functional (testType 'F') and has no measured value \u2014 use testPass[${e}] for its verdict`,n);if(t==="specPass"){if(!c(i))throw new o(`specPass[${e}] at position ${n} needs
|
|
2
|
+
`||n==="\r"){e++;continue}if(n>="0"&&n<="9"){const s=e;for(;e<r.length&&r[e]>="0"&&r[e]<="9";)e++;if(r[e]==="."&&r[e+1]!==".")for(e++;e<r.length&&r[e]>="0"&&r[e]<="9";)e++;if(r[e]==="e"||r[e]==="E"){const a=e;if(e++,(r[e]==="+"||r[e]==="-")&&e++,r[e]>="0"&&r[e]<="9")for(;e<r.length&&r[e]>="0"&&r[e]<="9";)e++;else e=a}const u=r.slice(s,e);t.push({kind:"num",text:u,pos:s,value:Number(u)});continue}if(/[A-Za-z_]/.test(n)){const s=e;for(;e<r.length&&/[A-Za-z0-9_]/.test(r[e]);)e++;t.push({kind:"ident",text:r.slice(s,e),pos:s});continue}const i=m.find(s=>r.startsWith(s,e));if(i){t.push({kind:"op",text:i,pos:e}),e+=i.length;continue}if(b.includes(n)){t.push({kind:"punc",text:n,pos:e}),e++;continue}throw new o(`Unexpected character ${JSON.stringify(n)} at position ${e}`,e)}return t.push({kind:"eof",text:"",pos:r.length}),t}class f{toks;ctx;p=0;reads=[];usesDiePass=!1;constructor(t,e){this.toks=t,this.ctx=e}peek(){return this.toks[this.p]}next(){return this.toks[this.p++]}at(t){const e=this.peek();return(e.kind==="op"||e.kind==="punc"||e.kind==="ident")&&e.text===t}eat(t){return this.at(t)?(this.p++,!0):!1}expect(t){if(!this.at(t)){const e=this.peek();throw new o(`Expected ${JSON.stringify(t)} but found ${e.kind==="eof"?"end of expression":JSON.stringify(e.text)} at position ${e.pos}`,e.pos)}return this.next()}parse(){const t=this.parseOr();return this.expectEnd(),t}parseTestRefOnly(){const t=this.parseTestRef(this.peek().pos);return this.expectEnd(),t}expectEnd(){const t=this.peek();if(t.kind!=="eof")throw new o(`Unexpected ${JSON.stringify(t.text)} at position ${t.pos}`,t.pos)}requireScalar(t,e,n){if(t.type==="numberVec"||t.type==="boolVec")throw new o(`${e} cannot be applied to a test range at position ${n} \u2014 a range must be reduced first, e.g. mean(t[1010..1015]) or all(testPass[1010..1015])`,n);return t.type}parseOr(){let t=this.parseAnd();for(;this.at("or");){const e=this.next().pos,n=this.parseAnd();this.requireBool(t,"or",e),this.requireBool(n,"or",e),t={node:{k:"binary",op:"or",left:t.node,right:n.node},type:"boolean"}}return t}parseAnd(){let t=this.parseNot();for(;this.at("and");){const e=this.next().pos,n=this.parseNot();this.requireBool(t,"and",e),this.requireBool(n,"and",e),t={node:{k:"binary",op:"and",left:t.node,right:n.node},type:"boolean"}}return t}requireBool(t,e,n){if(this.requireScalar(t,`Operator ${JSON.stringify(e)}`,n),t.type!=="boolean")throw new o(`Operator ${JSON.stringify(e)} needs a true/false operand at position ${n}, but got a number \u2014 compare it first, e.g. t[1020] > 5`,n)}parseNot(){if(this.at("not")){const t=this.next().pos,e=this.parseNot();return this.requireBool(e,"not",t),{node:{k:"unary",op:"not",arg:e.node},type:"boolean"}}return this.parseCompare()}parseCompare(){const t=this.parseAdd(),e=this.peek();if(e.kind==="op"&&["<","<=",">",">=","==","!="].includes(e.text)){this.next();const n=this.parseAdd(),i=this.requireScalar(t,`Operator ${JSON.stringify(e.text)}`,e.pos),s=this.requireScalar(n,`Operator ${JSON.stringify(e.text)}`,e.pos);if(i!==s)throw new o(`Cannot compare a ${i} with a ${s} at position ${e.pos}`,e.pos);if(i==="boolean"&&e.text!=="=="&&e.text!=="!=")throw new o(`Operator ${JSON.stringify(e.text)} cannot order true/false values at position ${e.pos}`,e.pos);return{node:{k:"binary",op:e.text,left:t.node,right:n.node},type:"boolean"}}return t}parseAdd(){let t=this.parseMul();for(;;){const e=this.peek();if(e.kind!=="op"||e.text!=="+"&&e.text!=="-")return t;this.next();const n=this.parseMul();this.requireNum(t,e.text,e.pos),this.requireNum(n,e.text,e.pos),t={node:{k:"binary",op:e.text,left:t.node,right:n.node},type:"number"}}}parseMul(){let t=this.parsePow();for(;;){const e=this.peek();if(e.kind!=="op"||!["*","/","%"].includes(e.text))return t;this.next();const n=this.parsePow();this.requireNum(t,e.text,e.pos),this.requireNum(n,e.text,e.pos),t={node:{k:"binary",op:e.text,left:t.node,right:n.node},type:"number"}}}parsePow(){const t=this.parseUnary(),e=this.peek();if(e.kind==="op"&&e.text==="^"){this.next();const n=this.parsePow();return this.requireNum(t,"^",e.pos),this.requireNum(n,"^",e.pos),{node:{k:"binary",op:"^",left:t.node,right:n.node},type:"number"}}return t}requireNum(t,e,n){if(this.requireScalar(t,`Operator ${JSON.stringify(e)}`,n),t.type!=="number")throw new o(`Operator ${JSON.stringify(e)} needs a numeric operand at position ${n}, but got a true/false value`,n)}parseUnary(){const t=this.peek();if(t.kind==="op"&&t.text==="-"){this.next();const e=this.parseUnary();return this.requireNum(e,"-",t.pos),{node:{k:"unary",op:"-",arg:e.node},type:"number"}}return this.parsePrimary()}parsePrimary(){const t=this.next();if(t.kind==="num")return{node:{k:"num",value:t.value},type:"number"};if(t.kind==="punc"&&t.text==="("){const e=this.parseOr();return this.expect(")"),e}if(t.kind==="ident"){if(d.includes(t.text))return this.parseAccessor(t.text,t.pos);if(t.text==="diePass")return this.expect("("),this.expect(")"),this.usesDiePass=!0,{node:{k:"diePass"},type:"boolean"};if(this.at("("))return this.parseCall(t.text,t.pos);if(Object.prototype.hasOwnProperty.call(this.ctx.constants,t.text)){const e=this.ctx.constants[t.text];if(typeof e!="number"||!Number.isFinite(e))throw new o(`Constant ${JSON.stringify(t.text)} at position ${t.pos} is not a finite number`,t.pos);return{node:{k:"num",value:e},type:"number"}}throw new o(`Unknown name ${JSON.stringify(t.text)} at position ${t.pos}. Test values are read as t[1020], verdicts as testPass[1020] or specPass[1020]; other names must be declared in \`constants\`.`,t.pos)}throw new o(`Unexpected ${t.kind==="eof"?"end of expression":JSON.stringify(t.text)} at position ${t.pos}`,t.pos)}parseAccessor(t,e){this.expect("[");const{tests:n,range:i}=this.parseTestRef(e);this.expect("]");for(const u of n)this.checkAccessible(t,u,e);for(const u of n)this.reads.push({kind:t,test:u});const s=t==="t"?"number":"boolean";return i?{node:{k:"vec",kind:t,tests:n},type:s==="number"?"numberVec":"boolVec"}:{node:{k:"acc",kind:t,test:n[0]},type:s}}checkAccessible(t,e,n){const i=this.ctx.testDefs.get(e);if(i===void 0)throw new o(`Test ${e} at position ${n} is not declared in \`testDefs\``,n);if(t==="t"&&!c(i))throw new o(`t[${e}] at position ${n} reads a measured value, but test ${e} is functional (testType 'F') and has no measured value \u2014 use testPass[${e}] for its verdict`,n);if(t==="specPass"){if(!c(i))throw new o(`specPass[${e}] at position ${n} needs test limits, but test ${e} is functional (testType 'F') \u2014 use testPass[${e}] for its recorded verdict`,n);if(!l(i))throw new o(`specPass[${e}] at position ${n} needs \`limitLow\` or \`limitHigh\` on test ${e}, which declares neither`,n)}}parseTestRef(t){const e=this.expectInt();if(!this.eat(".."))return{tests:[e],range:!1};const n=this.expectInt();if(n<e)throw new o(`Test range ${e}..${n} at position ${t} runs backwards`,t);const i=[...this.ctx.testDefs.keys()].filter(s=>s>=e&&s<=n).sort((s,u)=>s-u);if(i.length===0)throw new o(`Test range ${e}..${n} at position ${t} matches no declared test`,t);return{tests:i,range:!0}}expectInt(){const t=this.peek();if(t.kind!=="num"||!Number.isInteger(t.value))throw new o(`Expected a test number at position ${t.pos}`,t.pos);return this.next(),t.value}parseCall(t,e){const n=FUNCTIONS[t];if(n===void 0)throw new o(`Unknown function ${JSON.stringify(t)} at position ${e}. Available: ${Object.keys(FUNCTIONS).sort().join(", ")}`,e);this.expect("(");const i=[];if(!this.at(")"))do i.push(this.parseOr());while(this.eat(","));this.expect(")");const s=i.map(a=>a.type),u={k:"call",name:t,args:i.map(a=>a.node)};for(const a of n){if(a.vecOf!==void 0){if(s.length!==1)continue;const p=s[0];if(!(p==="numberVec"||p==="boolVec")||a.vecOf!=="any"&&a.vecOf!==p)continue;return{node:u,type:a.returns}}if(a.variadic){if(s.length<2||!s.every(p=>p==="number"))continue;return{node:u,type:a.returns}}if(a.arity!==void 0){if(s.length!==a.arity)continue;if(t==="if"){if(s[0]!=="boolean"||s[1]!=="number"||s[2]!=="number")continue;return{node:u,type:"number"}}if(!s.every(p=>p==="number"))continue;return{node:u,type:a.returns}}}throw new o(`${t}(${s.join(", ")}) at position ${e} is not a valid call \u2014 ${y(t,n)}`,e)}}function y(r,t){return`expected ${t.map(n=>n.vecOf==="boolVec"?`${r}(<true/false test range>)`:n.vecOf==="numberVec"?`${r}(<value test range>)`:n.vecOf==="any"?`${r}(<test range>)`:n.variadic?`${r}(number, number, \u2026)`:r==="if"?"if(<true/false>, number, number)":`${r}(${Array.from({length:n.arity??0},()=>"number").join(", ")})`).join(" or ")}`}export function parseExpression(r,t){try{if(r.length>MAX_EXPRESSION_LENGTH)return{ok:!1,message:`Expression is ${r.length} characters, over the ${MAX_EXPRESSION_LENGTH} limit`,position:MAX_EXPRESSION_LENGTH};const e=new f(h(r),t),{node:n,type:i}=e.parse();return i==="numberVec"||i==="boolVec"?{ok:!1,message:"Expression results in a test range, not a single value \u2014 reduce it, e.g. mean(t[1010..1015]) or all(testPass[1010..1015])",position:0}:{ok:!0,ast:n,type:i,reads:e.reads,usesDiePass:e.usesDiePass}}catch(e){if(e instanceof o)return{ok:!1,message:e.message,position:e.position};throw e}}export function parseTestReference(r,t){try{if(r.length>MAX_EXPRESSION_LENGTH)return{ok:!1,message:`Test reference is ${r.length} characters, over the ${MAX_EXPRESSION_LENGTH} limit`,position:MAX_EXPRESSION_LENGTH};const{tests:e,range:n}=new f(h(r),{testDefs:t,constants:{}}).parseTestRefOnly();return{ok:!0,tests:e,range:n}}catch(e){if(e instanceof o)return{ok:!1,message:e.message,position:e.position};throw e}}export const MAX_EXPRESSION_LENGTH=2e3;
|
|
@@ -22,6 +22,10 @@
|
|
|
22
22
|
* `null` means no verdict and must be treated as no-data by every caller, never
|
|
23
23
|
* as a pass and never as a fail.
|
|
24
24
|
*
|
|
25
|
+
* A value exactly equal to a limit passes unless the test says otherwise
|
|
26
|
+
* (`limitLowInclusive`/`limitHighInclusive: false`, which STDF's `PARM_FLG` bits
|
|
27
|
+
* 6/7 and ATDF's Limit Compare letters state per test).
|
|
28
|
+
*
|
|
25
29
|
* Out-of-spec *classification* depends ONLY on whether limits are defined — never on
|
|
26
30
|
* `colorbarRangeMode`. An out-of-spec die is always flagged when limits exist, regardless
|
|
27
31
|
* of how the colorbar is scaled. The *form* of the indication, decided in
|
|
@@ -36,12 +40,25 @@ export type SpecCategory = 'pass' | 'failHigh' | 'failLow';
|
|
|
36
40
|
export interface SpecLimits {
|
|
37
41
|
limitLow?: number;
|
|
38
42
|
limitHigh?: number;
|
|
43
|
+
/** A value equal to `limitLow` passes. Default `true`; STDF `PARM_FLG` bit 6. */
|
|
44
|
+
limitLowInclusive?: boolean;
|
|
45
|
+
/** A value equal to `limitHigh` passes. Default `true`; STDF `PARM_FLG` bit 7. */
|
|
46
|
+
limitHighInclusive?: boolean;
|
|
39
47
|
}
|
|
40
48
|
/**
|
|
41
49
|
* Classify `value` against `limits`. Returns `null` when no verdict is possible:
|
|
42
50
|
* no value, a non-finite value, or a test with neither limit defined.
|
|
43
51
|
*/
|
|
44
52
|
export declare function classifySpec(value: number | undefined, limits: SpecLimits | undefined): SpecCategory | null;
|
|
53
|
+
/** True for a verdict that is outside the limits. */
|
|
54
|
+
export declare function isOutOfSpec(category: SpecCategory | null): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* How many values in an ascending, finite array `classifySpec` would judge out of
|
|
57
|
+
* spec — the same rule, counted by binary search instead of one call per value,
|
|
58
|
+
* for callers that already hold the values sorted. `tests/specLimits.test.mjs`
|
|
59
|
+
* holds the two in agreement.
|
|
60
|
+
*/
|
|
61
|
+
export declare function countOutOfSpecSorted(sorted: ArrayLike<number>, limits: SpecLimits): number;
|
|
45
62
|
/** True when `limits` can produce a verdict at all — i.e. at least one limit is
|
|
46
63
|
* defined. Used to reject `specPass[n]` at parse time rather than silently
|
|
47
64
|
* yielding no-data for every die. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export function classifySpec(n
|
|
1
|
+
export function classifySpec(i,n){return i===void 0||!Number.isFinite(i)||n===void 0||n.limitLow===void 0&&n.limitHigh===void 0?null:n.limitLow!==void 0&&(n.limitLowInclusive===!1?i<=n.limitLow:i<n.limitLow)?"failLow":n.limitHigh!==void 0&&(n.limitHighInclusive===!1?i>=n.limitHigh:i>n.limitHigh)?"failHigh":"pass"}export function isOutOfSpec(i){return i==="failLow"||i==="failHigh"}export function countOutOfSpecSorted(i,n){let u=0,h=0;if(n.limitLow!==void 0){const l=n.limitLow,o=n.limitLowInclusive===!1?e=>e<=l:e=>e<l;let f=0,t=i.length;for(;f<t;){const e=f+t>>1;o(i[e])?f=e+1:t=e}u=f}if(n.limitHigh!==void 0){const l=n.limitHigh,o=n.limitHighInclusive===!1?e=>e<l:e=>e<=l;let f=0,t=i.length;for(;f<t;){const e=f+t>>1;o(i[e])?f=e+1:t=e}h=i.length-Math.max(f,u)}return u+h}export function hasSpecLimits(i){return i!==void 0&&(i.limitLow!==void 0||i.limitHigh!==void 0)}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{normalizeInput as Le}from"./normalizeInput.js";import{computeCapability as Ee,computeTestFlagYield as Ke,computeRegionYield as Te}from"./summaryFigures.js";import{isYieldEligibleDie as ae,getDieKey as T,isPositionedDie as be}from"../core/dies.js";import{getTestPassStatus as ee,isParametricTest as q}from"../renderer/buildWaferMap.js";import{markedTestLabel as x,derivedFields as te}from"../renderer/testLabel.js";import{buildQuadrantRegions as xe,buildReticlePositionRegions as je,buildRingRegions as Ae,buildSectorRegions as Oe,buildTestSiteRegions as He,sectorCompassNames as ye,areQuadrantsAdjacent as qe,parseRegionKey as I}from"./regions.js";import{buildClusterFindings as Ie}from"./clusterDetection.js";import{classifyPattern as Ye}from"./patternClassification.js";import{normalCdf as Se}from"./math.js";import{quantile as re}from"./math.js";import{mean as ne,clamp01 as we}from"../core/utils.js";const _e={significanceLevel:{min:1e-6,max:1},minimumEffectSize:{min:0,max:1},minimumRelativeEffect:{min:0,max:Number.MAX_SAFE_INTEGER}},ve=[4,8,16,32];function We(s){const a=Object.fromEntries(Object.entries(s).filter(([,t])=>t!==void 0)),i={...j,...a},o=[],e=[];for(const t of["significanceLevel","minimumEffectSize","minimumRelativeEffect"]){const{min:f,max:n}=_e[t],u=i[t];if(typeof u!="number"||!Number.isFinite(u)){e.push(`${t}=${String(u)} is not a finite number (using ${j[t]})`),i[t]=j[t];continue}const l=Math.min(n,Math.max(f,u));l!==u&&(e.push(`${t}=${u} is outside ${f}\u2013${n} (using ${l})`),i[t]=l)}if(ve.includes(i.sectorCount)||(e.push(`sectorCount=${i.sectorCount} is not one of ${ve.join(", ")} (using ${j.sectorCount})`),i.sectorCount=j.sectorCount),e.length){const t=`analyzeWaferMap: ${e.join("; ")}. The analysis ran with the corrected values.`;console.warn(`[wafermap] ${t}`),o.push({code:"analysis-option-corrected",message:t,severity:"warning"})}const c=["passBins","ringCount"].filter(t=>a[t]!==void 0);if(c.length){const t=`analyzeWaferMap: ${c.join(" and ")} ${c.length>1?"are":"is"} no longer an analysis option and was ignored. Set ${c.length>1?"them":"it"} on buildWaferMap; the analysis used the value the map was built with.`;console.warn(`[wafermap] ${t}`),o.push({code:"analysis-option-corrected",message:t,severity:"warning"})}const r=["enableYieldAnalysis","enableHardBinAnalysis","enableSoftBinAnalysis","enableReticlePositionAnalysis","enableTestSiteAnalysis","enableClusterAnalysis","enableAngularAnalysis","enablePatternClassification"].filter(t=>a[t]!==void 0);if(r.length){const t=`analyzeWaferMap: ${r.join(", ")} ${r.length>1?"are":"is"} no longer an option and was ignored: every analysis now runs. Filter the findings (filterFindings) to show fewer.`;console.warn(`[wafermap] ${t}`),o.push({code:"analysis-option-corrected",message:t,severity:"warning"})}return{resolved:i,warnings:o}}const j={ringCount:4,passBins:[1],significanceLevel:.05,minimumEffectSize:.2,minimumRelativeEffect:1,minimumSampleSize:5,enableTestValueAnalysis:!1,computePerTestStats:!1,sectorCount:8,minimumClusterSize:5};function Qe(s,a){const i={...s};return i.minimumClusterSize=Math.max(5,Math.round(a*.003)),i}function Ue(s){return ae(s)?s.hbin!==void 0||s.sbin!==void 0||s.testValues!==void 0&&Object.keys(s.testValues).length>0:!1}function Ge(s,a,i){if(!s||a)return;const o=(i??[]).filter(e=>q(e)&&(e.limitLow!==void 0||e.limitHigh!==void 0));if(o.length!==0)return e=>{for(const c of o){const r=c.testNumber,t=e.testValues?.[r];if(t!==void 0&&(c.limitLow!==void 0&&t<c.limitLow||c.limitHigh!==void 0&&t>c.limitHigh))return!0}return!1}}function Xe(s,a,i){const o=new Set,e=new Set,c=new Set,r=new Map,t=new Map;for(const f of s){if(f.testValues)for(const n of Object.keys(f.testValues))o.add(Number(n));if(f.testPass)for(const n of Object.keys(f.testPass))o.add(Number(n));f.hbin!==void 0&&e.add(f.hbin),f.sbin!==void 0&&c.add(f.sbin),ae(f)&&(f.hbin!==void 0&&r.set(f.hbin,(r.get(f.hbin)??0)+1),f.sbin!==void 0&&t.set(f.sbin,(t.get(f.sbin)??0)+1))}return{totalDies:s.length,analyzedDies:a,excludedDies:s.length-a,yieldPercent:i,testsConsidered:[...o].sort((f,n)=>f-n),hardBinsConsidered:[...e].sort((f,n)=>f-n),softBinsConsidered:[...c].sort((f,n)=>f-n),...r.size?{hardBinCounts:Object.fromEntries(r)}:{},...t.size?{softBinCounts:Object.fromEntries(t)}:{}}}function Je(s,a){if(!a?.length)return;const i=a.filter(e=>q(e)&&(e.limitLow!==void 0||e.limitHigh!==void 0));if(!i.length)return;const o=[];for(const e of i){const c=e.testNumber;let r=0,t=0,f=0,n=0;for(const u of s){if(u.partial||u.edgeExcluded)continue;const l=u.testValues?.[c];l!==void 0&&(n++,e.limitLow!==void 0&&l<e.limitLow?t++:e.limitHigh!==void 0&&l>e.limitHigh?f++:r++)}o.push({testNumber:c,label:e.name,passDies:r,failLowDies:t,failHighDies:f,totalDies:n,yieldPercent:n>0?r/n*100:null})}return o.length?o:void 0}export function computeFunctionalYield(s,a){if(!a?.length)return;const i=a.filter(e=>!q(e));if(!i.length)return;const o=[];for(const e of i){const c=e.testNumber;let r=0,t=0;for(const n of s){if(n.partial||n.edgeExcluded)continue;const u=ee(n,c,e);u!==void 0&&(u?r++:t++)}const f=r+t;o.push({testNumber:c,label:x(e,c),...te(e),passDies:r,failDies:t,totalDies:f,passRatePercent:f>0?r/f*100:null})}return o.length?o:void 0}function Ze(s,a,i,o){const e=[];for(const c of a){const r=[];for(const l of s){if(l.partial||l.edgeExcluded)continue;const h=l.testValues?.[c];h!==void 0&&Number.isFinite(h)&&r.push(h)}if(r.length<o)continue;const t=ne(r),f=Math.sqrt(ge(r,t)),n=Float64Array.from(r);n.sort();const u=i?.find(l=>l.testNumber===c)?.name??String(c);e.push({testNumber:c,label:u,count:r.length,min:n[0],max:n[n.length-1],mean:t,stddev:f,median:re(n,.5),q1:re(n,.25),q3:re(n,.75)})}return e.length?e:void 0}function A(s,a,i,o){const e=(s+i)/(a+o),c=e*(1-e)*(1/a+1/o);if(!Number.isFinite(c)||c<=0)return 1;const r=(s/a-i/o)/Math.sqrt(c);return we(2*(1-Se(Math.abs(r))))}function fe(s){const a=new Map;for(const i of s){const o=`${i.variable.kind}:${i.comparison.family}`,e=a.get(o)??[];e.push(i),a.set(o,e)}for(const i of a.values()){const o=[...i].filter(r=>r.stats.pValue!==void 0).sort((r,t)=>(r.stats.pValue??1)-(t.stats.pValue??1)),e=o.length;let c=1;for(let r=e-1;r>=0;r--){const t=o[r],f=t.stats.pValue??1,n=Math.min(1,Math.min(c,f*e/(r+1)));t.stats.adjustedPValue=n,c=n}}return s}function ie(s,a,i){const o=Math.abs(a),e=i!==void 0?Math.abs(i):0;return s<=.01&&(o>=.3||e>=2.5)?"unusual":s<=.05&&(o>=.2||e>=1.5)?"notable":"info"}function le(s,a){return s<=.01&&Math.abs(a)>=.5?"unusual":s<=.05&&Math.abs(a)>=.15?"notable":"info"}function se(s){return s==="reticle-position"?"other reticle positions":s==="test-site"?"other test sites":"the rest of the map"}function Y(s){return s==="reticle-position"?"Other reticle positions":s==="test-site"?"Other test sites":"Rest of map"}function $e(s,a,i){const o=(Math.abs(a)*100).toFixed(1);return`${s} yield is ${o} percentage points ${a>0?"higher":"lower"} than ${se(i)}`}function ce(s,a){return a==="quadrant"&&!s.startsWith("Quadrant")?`quadrant ${s}`:s}function De(s){return/^(Rings|Sectors|Quadrants)\b/.test(s)?"have":"has"}function ke(s,a,i,o){const e=ce(s,o),c=(Math.abs(i)*100).toFixed(1);return`${e} ${De(e)} ${a} occurrence ${c} percentage points ${i>0?"higher":"lower"} than ${se(o)}`}function ze(s,a,i,o){const e=ce(s,o),c=(Math.abs(i)*100).toFixed(1);return`${e} ${De(e)} ${a} pass rate ${c} percentage points ${i>0?"higher":"lower"} than ${se(o)}`}function Me(s,a,i,o,e,c){const r=ce(s,e),t=se(e),f=i>0?"higher":"lower";if(o!==void 0&&Number.isFinite(o)){const u=(Math.abs(o)*100).toFixed(1);return`${r} mean ${a} is ${u}% ${f} than ${t}`}const n=c?` ${c}`:"";return`${r} mean ${a} is ${Math.abs(i).toPrecision(3)}${n} ${f} than ${t}`}function Re(s,a,i){const o=a?.find(e=>e.bin===s);return o?.name?`${i} ${s} (${o.name})`:`${i} ${s}`}function et(s,a){const i=a?.find(o=>o.testNumber===s);return{label:x(i,s),unit:i?.unit,...te(i)}}function ue(s,a){const i=new Map(s.map(e=>[T(e),e])),o=new Map;for(const e of a){const c=[];for(const r of e.dieKeys){const t=i.get(r);t&&c.push(t)}o.set(e.key,c)}return o}function de(s,a){return fe(s),s.filter(i=>{const o=i.stats.adjustedPValue??i.stats.pValue??1,e=Math.abs(i.effect.absoluteDelta??0),c=Math.abs(i.effect.relativeDelta??0);return o<=a.significanceLevel&&(e>=a.minimumEffectSize||c>=a.minimumRelativeEffect)}).map(i=>({...i,severity:ie(i.stats.adjustedPValue??i.stats.pValue??1,i.effect.absoluteDelta??0,i.effect.relativeDelta)}))}function W(s,a,i,o){const e=new Set(i),c=ue(s,a),r=new Map,t=new Map;for(const[n,u]of c){let l=0,h=0;for(const g of u)g.hbin!==void 0&&(h++,e.has(g.hbin)&&l++);r.set(n,l),t.set(n,h)}const f=[];for(const n of a){const u=t.get(n.key)??0,l=r.get(n.key)??0;let h=0,g=0;for(const[k,m]of t)k!==n.key&&(h+=m,g+=r.get(k)??0);if(u<o.minimumSampleSize||h<o.minimumSampleSize)continue;const p=l/u,b=g/h,y=p-b,M=A(l,u,g,h);f.push({id:`yield:${n.key}`,level:"wafer",severity:"info",variable:{kind:"yield",label:"Yield"},comparison:{family:n.family,left:n.label,right:Y(n.family)},effect:{direction:y===0?"different":y>0?"higher":"lower",absoluteDelta:y,relativeDelta:b===0?void 0:y/b,effectSize:y},stats:{method:"two-proportion-z",pValue:M,sampleSizeLeft:u,sampleSizeRight:h},summary:$e(n.label,y,n.family),highlight:{kind:"region",regionFamily:n.family,regionKeys:[n.key],dieKeys:[...n.dieKeys]}})}return de(f,o)}function L(s,a,i,o,e,c){const r=g=>i==="soft"?g.sbin:g.hbin,t=[...new Set(s.map(r).filter(g=>g!==void 0))].sort((g,p)=>g-p),f=ue(s,a),n=new Map,u=new Map;for(const[g,p]of f){const b=new Map;for(const y of p){const M=r(y);M!==void 0&&b.set(M,(b.get(M)??0)+1)}n.set(g,b),u.set(g,p.length)}const l=[],h=e==="hardBin"?"HBin":"SBin";for(const g of a){const p=u.get(g.key)??0,b=n.get(g.key);let y=0;const M=new Map;for(const[k,m]of n)if(k!==g.key){y+=u.get(k)??0;for(const[S,$]of m)M.set(S,(M.get(S)??0)+$)}if(!(p<c.minimumSampleSize||y<c.minimumSampleSize))for(const k of t){const m=b.get(k)??0,S=M.get(k)??0,$=m/p,w=S/y,v=$-w,D=A(m,p,S,y),z=Re(k,o,h);l.push({id:`${e}:${k}:${g.key}`,level:"wafer",severity:"info",variable:{kind:e,bin:k,label:z},comparison:{family:g.family,left:g.label,right:Y(g.family)},effect:{direction:v===0?"different":v>0?"higher":"lower",absoluteDelta:v,relativeDelta:w===0?void 0:v/w,effectSize:v},stats:{method:"two-proportion-z",pValue:D,sampleSizeLeft:p,sampleSizeRight:y},summary:ke(g.label,z,v,g.family),highlight:{kind:"bin",bin:k,regionKeys:[g.key],dieKeys:[...g.dieKeys]}})}}return de(l,c)}function Q(s,a,i,o){const e=(i??[]).filter(u=>!q(u));if(!e.length)return[];const c=e.map(u=>u.testNumber),r=ue(s,a),t=new Map,f=new Map;for(const[u,l]of r){const h=new Uint32Array(e.length),g=new Uint32Array(e.length);for(const p of l)for(let b=0;b<e.length;b++){const y=ee(p,c[b],e[b]);y!==void 0&&(g[b]++,y&&h[b]++)}t.set(u,h),f.set(u,g)}const n=[];for(const u of a){const l=t.get(u.key),h=f.get(u.key);for(let g=0;g<e.length;g++){let p=0,b=0;for(const[v,D]of f)v!==u.key&&(b+=D[g],p+=t.get(v)[g]);const y=h[g],M=l[g];if(y<o.minimumSampleSize||b<o.minimumSampleSize)continue;const k=M/y,m=p/b,S=k-m,$=A(M,y,p,b),w=c[g];n.push({id:`functional:${w}:${u.key}`,level:"wafer",severity:"info",variable:{kind:"functionalTest",index:w,label:`${x(e[g],w)} pass rate`,...te(e[g])},comparison:{family:u.family,left:u.label,right:Y(u.family)},effect:{direction:S===0?"different":S>0?"higher":"lower",absoluteDelta:S,relativeDelta:m===0?void 0:S/m,effectSize:S},stats:{method:"two-proportion-z",pValue:$,sampleSizeLeft:y,sampleSizeRight:b},summary:ze(u.label,x(e[g],w),S,u.family),highlight:{kind:"region",regionFamily:u.family,regionKeys:[u.key],dieKeys:[...u.dieKeys]}})}}return de(n,o)}function ge(s,a){return s.length<2?0:s.reduce((i,o)=>i+(o-a)**2,0)/(s.length-1)}function Be(s,a,i,o,e,c){const r=Math.sqrt(i/s+c/o),t=a-e;if(!Number.isFinite(r)||r===0)return{pValue:1,effectSize:0,delta:t};const f=t/r,n=Math.sqrt(Math.max(0,(i+c)/2)),u=n===0?t:t/n;return{pValue:we(2*(1-Se(Math.abs(f)))),effectSize:u,delta:t}}function tt(s,a){const i=ne(s),o=ne(a);return Be(s.length,i,ge(s,i),a.length,o,ge(a,o))}const Fe=250;function Ne(s,a,i){const o=r=>r.filter(t=>q(i?.find(f=>f.testNumber===t)));if(a)return{testNumbers:o(a.slice().sort((r,t)=>r-t))};const e=new Set;let c=!1;e:for(const r of s)for(const t of Object.keys(r.testValues??{})){const f=Number(t);if(!e.has(f)&&(e.add(f),e.size>Fe)){c=!0;break e}}if(c){const r=`Test-value analysis was skipped: more than ${Fe} tests were found in the die data, so no test findings were computed. Pass testNumbers: [...] to analyse specific tests.`;return console.warn(`[wafermap] analyzeWaferMap: ${r}`),{testNumbers:[],warning:{code:"test-count-capped",message:r,severity:"warning"}}}return{testNumbers:o([...e].sort((r,t)=>r-t))}}function U(s,a,i,o){const e=Ne(s,o.testNumbers,i);if(e.warning)return{findings:[],warning:e.warning};const c=e.testNumbers,r=new Map;for(let p=0;p<a.length;p++)for(const b of a[p].dieKeys)r.set(b,p);const t=a.length,f=[],n=[];for(const p of s){const b=r.get(T(p));b!==void 0&&(f.push(p),n.push(b))}const u=[],l=new Float64Array(t),h=new Float64Array(t),g=new Float64Array(t);for(const p of c){l.fill(0),h.fill(0),g.fill(0);let b=0,y=0,M=0,k;for(let m=0;m<f.length;m++){const $=f[m].testValues?.[p];if($===void 0)continue;k===void 0&&(k=$);const w=$-k,v=n[m];l[v]+=1,h[v]+=w,g[v]+=w*w,b+=1,y+=w,M+=w*w}if(k!==void 0)for(let m=0;m<t;m++){const S=l[m],$=b-S;if(S<o.minimumSampleSize||$<o.minimumSampleSize)continue;const w=h[m]/S,D=(y-h[m])/$,z=w+k,R=D+k,F=S>1?Math.max(0,g[m]-S*w*w)/(S-1):0,d=$>1?Math.max(0,M-g[m]-$*D*D)/($-1):0,{pValue:N,effectSize:B,delta:V}=Be(S,z,F,$,R,d),P=a[m],{label:H,unit:G,...X}=et(p,i),J=R!==0?V/Math.abs(R):void 0;u.push({id:`test:${p}:${P.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:p,label:H,unit:G,...X},comparison:{family:P.family,left:P.label,right:Y(P.family)},effect:{direction:V===0?"different":V>0?"higher":"lower",absoluteDelta:V,relativeDelta:J,effectSize:B},stats:{method:"welch-z-approx",pValue:N,sampleSizeLeft:S,sampleSizeRight:$},summary:Me(P.label,H,V,J,P.family,G),highlight:{kind:"region",regionFamily:P.family,regionKeys:[P.key],dieKeys:[...P.dieKeys]}})}}return fe(u),{activeTestNumbers:c,findings:u.filter(p=>{const b=p.stats.adjustedPValue??p.stats.pValue??1,y=Math.abs(p.effect.effectSize??0);return b<=o.significanceLevel&&y>=o.minimumEffectSize}).map(p=>({...p,severity:le(p.stats.adjustedPValue??p.stats.pValue??1,p.effect.effectSize??0)}))}}function nt(s,a,i,o){if(!i?.length)return[];const e=i.filter(t=>q(t)&&(t.limitLow!==void 0||t.limitHigh!==void 0));if(!e.length)return[];const c=[],r=new Map(s.map(t=>[T(t),t]));for(const t of e){const f=t.testNumber;for(const n of a){const u=new Map;for(const h of n){const g=[];for(const p of h.dieKeys){const b=r.get(p);b&&g.push(b)}u.set(h.key,g)}const l=[];for(const h of n){const g=u.get(h.key),p=[];for(const[z,R]of u)if(z!==h.key)for(const F of R)p.push(F);const b=z=>z.testValues?.[f]!==void 0,y=z=>{const R=z.testValues?.[f];return R===void 0?!1:t.limitLow!==void 0&&R<t.limitLow||t.limitHigh!==void 0&&R>t.limitHigh},M=g.filter(b),k=p.filter(b);if(M.length<o.minimumSampleSize||k.length<o.minimumSampleSize)continue;const m=M.filter(y).length,S=k.filter(y).length,$=m/M.length,w=S/k.length,v=$-w,D=A(m,M.length,S,k.length);l.push({id:`specLimit:${f}:${h.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:f,label:x(t,f),unit:t.unit,...te(t)},comparison:{family:h.family,left:h.label,right:Y(h.family)},effect:{direction:v===0?"different":v>0?"higher":"lower",absoluteDelta:v,relativeDelta:w===0?void 0:v/w,effectSize:v},stats:{method:"two-proportion-z",pValue:D,sampleSizeLeft:M.length,sampleSizeRight:k.length},summary:`${h.label} spec-fail rate for ${x(t,f)} is ${(Math.abs(v)*100).toFixed(1)} pp ${v>0?"higher":"lower"} than the rest of the wafer`,highlight:{kind:"region",regionFamily:h.family,regionKeys:[h.key],dieKeys:[...h.dieKeys]}})}fe(l),c.push(...l.filter(h=>{const g=h.stats.adjustedPValue??h.stats.pValue??1,p=Math.abs(h.effect.absoluteDelta??0),b=Math.abs(h.effect.relativeDelta??0);return g<=o.significanceLevel&&(p>=o.minimumEffectSize||b>=o.minimumRelativeEffect)}).map(h=>({...h,severity:le(h.stats.adjustedPValue??h.stats.pValue??1,h.effect.effectSize??0)})))}}return c}function O(s){return s.highlight.regionKeys??[]}function it(s){const a=s.variable.bin??s.variable.index??"";return`${s.comparison.family}\0${s.variable.kind}\0${a}\0${s.effect.direction}`}function _(s,a){const i=O(s)[0]??"",o=I(i);return o.family==="ring"?o.ring??0:o.family==="sector"?ye(a).indexOf(o.sector??""):0}function st(s,a,i){if(a==="quadrant"){const r=new Map;for(const u of s){const l=I(O(u)[0]??"").quadrant;l&&r.set(l,u)}const t=[...r.keys()],f=new Set,n=[];for(const u of t){if(f.has(u))continue;const l=[],h=[u];for(f.add(u);h.length;){const g=h.pop();l.push(g);for(const p of t)!f.has(p)&&qe(g,p)&&(f.add(p),h.push(p))}n.push(l.map(g=>r.get(g)))}return n}const o=[...s].sort((r,t)=>_(r,i)-_(t,i)),e=[];let c=[];for(const r of o){if(c.length===0){c.push(r);continue}const t=_(c[c.length-1],i);_(r,i)-t===1?c.push(r):(e.push(c),c=[r])}if(c.length&&e.push(c),a==="sector"&&e.length>1){const t=ye(i).length,f=_(e[0][0],i),n=_(e[e.length-1][e[e.length-1].length-1],i);if(f===0&&n===t-1){const u=e.pop();e[0]=[...u,...e[0]]}}return e}function ot(s,a){const i=s.map(r=>I(O(r)[0]??"").ring).filter(r=>r!==void 0).sort((r,t)=>r-t),o=i[0],e=i[i.length-1];let c="";return o===e&&o===1&&a>1?c=" (core)":o===e&&o===a&&a>1&&(c=" (edge)"),o===e?`Ring ${o}${c}`:`Rings ${o}\u2013${e}`}function at(s,a){const i=s.map(o=>I(O(o)[0]??"").sector??"");return i.length===1?`Sector ${i[0]}`:`Sectors ${i[0]}\u2013${i[i.length-1]}`}function rt(s){const a=s.map(i=>I(O(i)[0]??"").quadrant??"");return a.length===1?a[0]:a.length===2?`Quadrants ${a[0]} & ${a[1]}`:`Quadrants ${a.slice(0,-1).join(", ")} & ${a[a.length-1]}`}function me(s){return[...new Set(s)]}function ft(s,a){const i=s[0],o=i.comparison.family,e=i.variable.kind,c=me(s.flatMap(m=>m.highlight.dieKeys??[])),r=me(s.flatMap(O)),t=new Set(c),f=o==="ring"?ot(s,a.ringCount):o==="sector"?at(s,a.sectorCount):rt(s),n=e==="softBin"?a.softEligibleDies:e==="test"?a.testDies:a.eligibleDies,u=n.filter(m=>t.has(T(m))),l=n.filter(m=>!t.has(T(m)));let h,g,p,b,y;if(e==="test"){const m=i.variable.index,S=d=>d.testValues?.[m],$=u.map(S).filter(d=>d!==void 0),w=l.map(S).filter(d=>d!==void 0),{pValue:v,effectSize:D,delta:z}=tt($,w),R=ne(w),F=R!==0?z/Math.abs(R):void 0;h={direction:z===0?"different":z>0?"higher":"lower",absoluteDelta:z,relativeDelta:F,effectSize:D},g={method:"welch-z-approx",pValue:v,sampleSizeLeft:$.length,sampleSizeRight:w.length},p=le(v,D),b=Me(f,i.variable.label,z,F,o,i.variable.unit),y=`test:${m}`}else if(e==="yield"){const m=new Set(a.passBins),S=u.filter(B=>B.hbin!==void 0),$=l.filter(B=>B.hbin!==void 0),w=S.length,v=$.length,D=S.filter(B=>m.has(B.hbin)).length,z=$.filter(B=>m.has(B.hbin)).length,R=w>0?D/w:0,F=v>0?z/v:0,d=R-F,N=A(D,w,z,v);h={direction:d===0?"different":d>0?"higher":"lower",absoluteDelta:d,relativeDelta:F===0?void 0:d/F,effectSize:d},g={method:"two-proportion-z",pValue:N,sampleSizeLeft:w,sampleSizeRight:v},p=ie(N,d,h.relativeDelta),b=$e(f,d,o),y="yield"}else if(e==="functionalTest"){const m=i.variable.index,S=a.testDefs?.find(N=>N.testNumber===m);let $=0,w=0,v=0,D=0;for(const N of u){const B=ee(N,m,S);B!==void 0&&(w++,B&&$++)}for(const N of l){const B=ee(N,m,S);B!==void 0&&(D++,B&&v++)}const z=w>0?$/w:0,R=D>0?v/D:0,F=z-R,d=A($,w,v,D);h={direction:F===0?"different":F>0?"higher":"lower",absoluteDelta:F,relativeDelta:R===0?void 0:F/R,effectSize:F},g={method:"two-proportion-z",pValue:d,sampleSizeLeft:w,sampleSizeRight:D},p=ie(d,F,h.relativeDelta),b=ze(f,x(S,m),F,o),y=`functional:${m}`}else{const m=i.variable.bin,S=d=>e==="softBin"?d.sbin:d.hbin,$=u.filter(d=>S(d)===m).length,w=l.filter(d=>S(d)===m).length,v=$/u.length,D=w/l.length,z=v-D,R=A($,u.length,w,l.length);h={direction:z===0?"different":z>0?"higher":"lower",absoluteDelta:z,relativeDelta:D===0?void 0:z/D,effectSize:z},g={method:"two-proportion-z",pValue:R,sampleSizeLeft:u.length,sampleSizeRight:l.length},p=ie(R,z,h.relativeDelta);const F=e==="softBin"?a.sbinDefs:a.hbinDefs;b=ke(f,Re(m,F,e==="softBin"?"SBin":"HBin"),z,o),y=`${e}:${m}`}const M=me(r.map(m=>m.replace(`${o}:`,""))).join("-"),k=e==="hardBin"||e==="softBin"?{kind:"bin",bin:i.variable.bin,regionKeys:r,dieKeys:c}:{kind:"region",regionFamily:o,regionKeys:r,dieKeys:c};return{...i,id:`${y}:${o}:${M}`,severity:p,comparison:{family:o,left:f,right:Y(o)},effect:h,stats:g,summary:b,highlight:k,relatedIds:s.map(m=>m.id)}}function lt(s,a){const i=new Set(["ring","quadrant","sector"]),o=[],e=new Map;for(const r of s){if(!i.has(r.comparison.family)){o.push(r);continue}const t=it(r),f=e.get(t)??[];f.push(r),e.set(t,f)}const c=[];for(const r of e.values()){if(r.length===1){c.push(r[0]);continue}const t=r[0].comparison.family;for(const f of st(r,t,a.sectorCount))c.push(f.length===1?f[0]:ft(f,a))}return[...o,...c]}function ct(s){const a=new Map,i=new Map;for(const e of s){const c=T(e);e.hbin!==void 0&&e.hbin!==null&&(a.get(e.hbin)??a.set(e.hbin,new Set).get(e.hbin)).add(c),e.sbin!==void 0&&e.sbin!==null&&(i.get(e.sbin)??i.set(e.sbin,new Set).get(e.sbin)).add(c)}const o=new Set;for(const[e,c]of a)for(const[r,t]of i){if(c.size!==t.size)continue;let f=!0;for(const n of c)if(!t.has(n)){f=!1;break}f&&o.add(`${e}:${r}`)}return o}function ut(s){return`${s.comparison.family}\0${s.comparison.left}`}function dt(s,a,i){const o=(t,f)=>{t.absorbedIds=[...t.absorbedIds??[],f.id]},e=new Map;for(const t of s){const f=ut(t);(e.get(f)??e.set(f,[]).get(f)).push(t)}const c=ct(a),r=new Set;for(const t of e.values()){for(const n of t)if(!(n.variable.kind!=="hardBin"||n.variable.bin===void 0))for(const u of t){if(u.variable.kind!=="softBin"||u.variable.bin===void 0||r.has(u.id)||!c.has(`${n.variable.bin}:${u.variable.bin}`)||u.effect.direction!==n.effect.direction)continue;o(n,u),r.add(u.id);const l=n.variable.label.replace(/^HBin\s+/,""),h=u.variable.label.replace(/^SBin\s+/,""),g=l===h?`HBin and SBin ${l} (same dies)`:`${n.variable.label} and ${u.variable.label} (same dies)`;n.summary=n.summary.replace(n.variable.label,g),n.variable.label=g;break}if(i.length!==1)continue;const f=t.find(n=>n.variable.kind==="yield");if(f){for(const n of t)if(!(n===f||r.has(n.id)||!((n.variable.kind==="hardBin"||n.variable.kind==="softBin")&&n.variable.bin===i[0]))){o(f,n),r.add(n.id);for(const l of n.absorbedIds??[])f.absorbedIds=[...f.absorbedIds??[],l]}}}}export function analyzeWaferMap(s,a={}){const{resolved:i,warnings:o}=We(a),e=Le(s),c={...i,passBins:e.passBins??j.passBins,ringCount:e.ringCount??j.ringCount},r=e.isLotStack,t=e.aggrMethod,f=!r||t==="mode"||t==="countBin"||t==="percent",n=e.dies.filter(d=>Ue(d)),u=n.filter(be),l=Qe(c,n.length),h=e.dies.filter(d=>ae(d)),g=h.filter(be),p=Ae(g,e.wafer,l.ringCount),b=xe(g,e.wafer,l.ringCount),y=je(g,e.reticleConfig),M=He(h,!1),k=Oe(g,e.wafer,l.sectorCount),m=[];f&&m.push(...W(n,p,l.passBins,l),...W(n,b,l.passBins,l),...W(n,y,l.passBins,l),...W(n,M,l.passBins,l),...W(n,k,l.passBins,l)),f&&m.push(...L(n,p,"hard",e.hbinDefs,"hardBin",l),...L(n,b,"hard",e.hbinDefs,"hardBin",l),...L(n,y,"hard",e.hbinDefs,"hardBin",l),...L(n,M,"hard",e.hbinDefs,"hardBin",l),...L(n,k,"hard",e.hbinDefs,"hardBin",l));{const d=n.filter(N=>N.sbin!==void 0);m.push(...L(d,p,"soft",e.sbinDefs,"softBin",l),...L(d,b,"soft",e.sbinDefs,"softBin",l),...L(d,y,"soft",e.sbinDefs,"softBin",l),...L(d,M,"soft",e.sbinDefs,"softBin",l),...L(d,k,"soft",e.sbinDefs,"softBin",l))}const S=[...o];let $;if(l.enableTestValueAnalysis){const d=U(n,p,e.testDefs,l),N=U(n,b,e.testDefs,l),B=U(n,y,e.testDefs,l),V=U(n,M,e.testDefs,l),P=U(n,k,e.testDefs,l);m.push(...d.findings,...N.findings,...B.findings,...V.findings,...P.findings),d.warning&&S.push(d.warning),$=d.activeTestNumbers,m.push(...nt(n,[p,b,y,M,k],e.testDefs,l)),m.push(...Q(n,p,e.testDefs,l),...Q(n,b,e.testDefs,l),...Q(n,y,e.testDefs,l),...Q(n,M,e.testDefs,l),...Q(n,k,e.testDefs,l))}{const d=Ge(r,f,e.testDefs);(!r||f||d!==void 0)&&m.push(...Ie(u,e.wafer,{...l,isFailingDie:d}))}const w=lt(m,{eligibleDies:n,softEligibleDies:n.filter(d=>d.sbin!==void 0),testDies:n,passBins:l.passBins,ringCount:l.ringCount,sectorCount:l.sectorCount,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs});m.length=0,m.push(...w);let v;if(f){const d=Ye(u,e.wafer,{passBins:l.passBins,ringCount:l.ringCount});if(v=d??void 0,d!==null&&d.pattern!=="random"&&d.pattern!=="none"){let Ve=function(C){const E=O(C).map(K=>I(K).ring).filter(K=>K!==void 0);if(E.length===0)return!0;const he=E.some(K=>K===Ce),pe=E.some(K=>K===1);return Z==="edge-ring"||Z==="edge-local"?he:Z==="center"?pe:Z==="donut"?!he&&!pe:!0};const B={center:"Center cluster",donut:"Donut","edge-ring":"Edge-ring","edge-local":"Edge-local",scratch:"Scratch","near-full":"Near-full"}[d.pattern]??d.pattern,V=d.confidence==="high"?"unusual":d.confidence==="medium"?"notable":"info",P=d.features,H=C=>`${(C*100).toFixed(0)}%`,G=d.pattern==="edge-ring"||d.pattern==="edge-local"?`${H(P.edgeRdd)} of edge dies failing`:d.pattern==="center"||d.pattern==="donut"?`centroid at ${H(P.centroidDistNorm)} of wafer radius from centre`:d.pattern==="scratch"?`linear score ${P.linearScore.toFixed(2)}, eccentricity ${P.eccentricity.toFixed(2)}`:`${H(P.globalRdd)} of dies failing`,X=n.filter(C=>{const E=C.hbin??C.sbin;return E!==void 0&&!new Set(l.passBins).has(E)}),J={"edge-ring":["ring","edge-arc"],"edge-local":["edge-arc","sector","quadrant"],center:["ring","cluster"],donut:["ring"],scratch:["cluster","sector","quadrant"],"near-full":["ring"]},Pe=new Set(J[d.pattern]??[]),Z=d.pattern,Ce=l.ringCount,oe=[];for(const C of m){if(!Pe.has(C.comparison.family)||C.comparison.family==="ring"&&!Ve(C))continue;oe.push(C.id);const E=C.comparison.family==="ring"||C.comparison.family==="quadrant"||C.comparison.family==="sector";V!=="info"&&E&&(C.severity="info")}m.push({id:`spatial-pattern:${d.pattern}`,level:"wafer",severity:V,variable:{kind:"spatialPattern",label:B},comparison:{family:"spatial-pattern",left:B,right:"Wafer"},effect:{direction:"different",effectSize:P.globalRdd},stats:{method:"geometry",sampleSizeLeft:X.length,sampleSizeRight:n.length},summary:`Spatial pattern: ${B.toLowerCase()} (${d.confidence} confidence) \u2014 ${G}${d.note?` [${d.note}]`:""}`,highlight:{kind:"dies",dieKeys:X.map(C=>T(C))},relatedIds:oe.length>0?oe:void 0})}}m.sort((d,N)=>{const B=d.stats.adjustedPValue??d.stats.pValue??1,V=N.stats.adjustedPValue??N.stats.pValue??1;return B!==V?B-V:Math.abs(N.effect.absoluteDelta??0)-Math.abs(d.effect.absoluteDelta??0)});const D=Xe(e.dies,n.length,e.yield.yieldPercent);r&&(D.isLotStack=!0,t&&(D.aggregationMethod=t));const z=Je(e.dies,e.testDefs);z&&(D.testSpecYield=z);const R=computeFunctionalYield(e.dies,e.testDefs);if(R&&(D.functionalYield=R),v&&(D.spatialPattern=v),$===void 0&&l.computePerTestStats){const d=Ne(n,l.testNumbers,e.testDefs);d.warning&&S.push(d.warning),$=d.testNumbers}if($?.length){const d=Ze(e.dies,$,e.testDefs,l.minimumSampleSize);d&&(D.perTestStats=d);const N=Ee([{dies:e.dies}],e.testDefs,$);N&&(D.capability=N)}Object.assign(D,Ke(e.dies,e.testDefs));const F=Te([{dies:e.dies,wafer:e.wafer,passBins:l.passBins,ringCount:l.ringCount}]);return F&&(D.regionYield=F),S.length>0&&(D.warnings=S),dt(m,n,l.passBins),{level:"wafer",hasNotableFindings:m.some(d=>d.severity==="notable"||d.severity==="unusual"),findings:m,wafer:e.wafer.metadata??void 0,stats:D}}
|
|
1
|
+
import{normalizeInput as Ke}from"./normalizeInput.js";import{computeCapability as Te,computeTestFlagYield as xe,computeRegionYield as je}from"./summaryFigures.js";import{isYieldEligibleDie as ae,getDieKey as T,isPositionedDie as ye}from"../core/dies.js";import{getTestPassStatus as ee,isParametricTest as H}from"../renderer/buildWaferMap.js";import{markedTestLabel as x,derivedFields as te}from"../renderer/testLabel.js";import{buildQuadrantRegions as Ae,buildReticlePositionRegions as Oe,buildRingRegions as qe,buildSectorRegions as He,buildTestSiteRegions as Ie,sectorCompassNames as Se,areQuadrantsAdjacent as Ye,parseRegionKey as I}from"./regions.js";import{buildClusterFindings as _e}from"./clusterDetection.js";import{classifyPattern as We}from"./patternClassification.js";import{normalCdf as we}from"./math.js";import{quantile as re}from"./math.js";import{mean as ne,clamp01 as $e}from"../core/utils.js";import{classifySpec as le,isOutOfSpec as ve}from"../renderer/spec.js";const Qe={significanceLevel:{min:1e-6,max:1},minimumEffectSize:{min:0,max:1},minimumRelativeEffect:{min:0,max:Number.MAX_SAFE_INTEGER}},De=[4,8,16,32];function Ue(s){const a=Object.fromEntries(Object.entries(s).filter(([,n])=>n!==void 0)),i={...j,...a},o=[],e=[];for(const n of["significanceLevel","minimumEffectSize","minimumRelativeEffect"]){const{min:l,max:t}=Qe[n],u=i[n];if(typeof u!="number"||!Number.isFinite(u)){e.push(`${n}=${String(u)} is not a finite number (using ${j[n]})`),i[n]=j[n];continue}const f=Math.min(t,Math.max(l,u));f!==u&&(e.push(`${n}=${u} is outside ${l}\u2013${t} (using ${f})`),i[n]=f)}if(De.includes(i.sectorCount)||(e.push(`sectorCount=${i.sectorCount} is not one of ${De.join(", ")} (using ${j.sectorCount})`),i.sectorCount=j.sectorCount),e.length){const n=`analyzeWaferMap: ${e.join("; ")}. The analysis ran with the corrected values.`;console.warn(`[wafermap] ${n}`),o.push({code:"analysis-option-corrected",message:n,severity:"warning"})}const c=["passBins","ringCount"].filter(n=>a[n]!==void 0);if(c.length){const n=`analyzeWaferMap: ${c.join(" and ")} ${c.length>1?"are":"is"} no longer an analysis option and was ignored. Set ${c.length>1?"them":"it"} on buildWaferMap; the analysis used the value the map was built with.`;console.warn(`[wafermap] ${n}`),o.push({code:"analysis-option-corrected",message:n,severity:"warning"})}const r=["enableYieldAnalysis","enableHardBinAnalysis","enableSoftBinAnalysis","enableReticlePositionAnalysis","enableTestSiteAnalysis","enableClusterAnalysis","enableAngularAnalysis","enablePatternClassification"].filter(n=>a[n]!==void 0);if(r.length){const n=`analyzeWaferMap: ${r.join(", ")} ${r.length>1?"are":"is"} no longer an option and was ignored: every analysis now runs. Filter the findings (filterFindings) to show fewer.`;console.warn(`[wafermap] ${n}`),o.push({code:"analysis-option-corrected",message:n,severity:"warning"})}return{resolved:i,warnings:o}}const j={ringCount:4,passBins:[1],significanceLevel:.05,minimumEffectSize:.2,minimumRelativeEffect:1,minimumSampleSize:5,enableTestValueAnalysis:!1,computePerTestStats:!1,sectorCount:8,minimumClusterSize:5};function Ge(s,a){const i={...s};return i.minimumClusterSize=Math.max(5,Math.round(a*.003)),i}function Xe(s){return ae(s)?s.hbin!==void 0||s.sbin!==void 0||s.testValues!==void 0&&Object.keys(s.testValues).length>0:!1}function Je(s,a,i){if(!s||a)return;const o=(i??[]).filter(e=>H(e)&&(e.limitLow!==void 0||e.limitHigh!==void 0));if(o.length!==0)return e=>{for(const c of o)if(ve(le(e.testValues?.[c.testNumber],c)))return!0;return!1}}function Ze(s,a,i){const o=new Set,e=new Set,c=new Set,r=new Map,n=new Map;for(const l of s){if(l.testValues)for(const t of Object.keys(l.testValues))o.add(Number(t));if(l.testPass)for(const t of Object.keys(l.testPass))o.add(Number(t));l.hbin!==void 0&&e.add(l.hbin),l.sbin!==void 0&&c.add(l.sbin),ae(l)&&(l.hbin!==void 0&&r.set(l.hbin,(r.get(l.hbin)??0)+1),l.sbin!==void 0&&n.set(l.sbin,(n.get(l.sbin)??0)+1))}return{totalDies:s.length,analyzedDies:a,excludedDies:s.length-a,yieldPercent:i,testsConsidered:[...o].sort((l,t)=>l-t),hardBinsConsidered:[...e].sort((l,t)=>l-t),softBinsConsidered:[...c].sort((l,t)=>l-t),...r.size?{hardBinCounts:Object.fromEntries(r)}:{},...n.size?{softBinCounts:Object.fromEntries(n)}:{}}}function et(s,a){if(!a?.length)return;const i=a.filter(e=>H(e)&&(e.limitLow!==void 0||e.limitHigh!==void 0));if(!i.length)return;const o=[];for(const e of i){const c=e.testNumber;let r=0,n=0,l=0,t=0;for(const u of s){if(u.partial||u.edgeExcluded)continue;const f=le(u.testValues?.[c],e);f!==null&&(t++,f==="failLow"?n++:f==="failHigh"?l++:r++)}o.push({testNumber:c,label:e.name,passDies:r,failLowDies:n,failHighDies:l,totalDies:t,yieldPercent:t>0?r/t*100:null})}return o.length?o:void 0}export function computeFunctionalYield(s,a){if(!a?.length)return;const i=a.filter(e=>!H(e));if(!i.length)return;const o=[];for(const e of i){const c=e.testNumber;let r=0,n=0;for(const t of s){if(t.partial||t.edgeExcluded)continue;const u=ee(t,c,e);u!==void 0&&(u?r++:n++)}const l=r+n;o.push({testNumber:c,label:x(e,c),...te(e),passDies:r,failDies:n,totalDies:l,passRatePercent:l>0?r/l*100:null})}return o.length?o:void 0}function tt(s,a,i,o){const e=[];for(const c of a){const r=[];for(const f of s){if(f.partial||f.edgeExcluded)continue;const m=f.testValues?.[c];m!==void 0&&Number.isFinite(m)&&r.push(m)}if(r.length<o)continue;const n=ne(r),l=Math.sqrt(he(r,n)),t=Float64Array.from(r);t.sort();const u=i?.find(f=>f.testNumber===c)?.name??String(c);e.push({testNumber:c,label:u,count:r.length,min:t[0],max:t[t.length-1],mean:n,stddev:l,median:re(t,.5),q1:re(t,.25),q3:re(t,.75)})}return e.length?e:void 0}function A(s,a,i,o){const e=(s+i)/(a+o),c=e*(1-e)*(1/a+1/o);if(!Number.isFinite(c)||c<=0)return 1;const r=(s/a-i/o)/Math.sqrt(c);return $e(2*(1-we(Math.abs(r))))}function fe(s){const a=new Map;for(const i of s){const o=`${i.variable.kind}:${i.comparison.family}`,e=a.get(o)??[];e.push(i),a.set(o,e)}for(const i of a.values()){const o=[...i].filter(r=>r.stats.pValue!==void 0).sort((r,n)=>(r.stats.pValue??1)-(n.stats.pValue??1)),e=o.length;let c=1;for(let r=e-1;r>=0;r--){const n=o[r],l=n.stats.pValue??1,t=Math.min(1,Math.min(c,l*e/(r+1)));n.stats.adjustedPValue=t,c=t}}return s}function ie(s,a,i){const o=Math.abs(a),e=i!==void 0?Math.abs(i):0;return s<=.01&&(o>=.3||e>=2.5)?"unusual":s<=.05&&(o>=.2||e>=1.5)?"notable":"info"}function ce(s,a){return s<=.01&&Math.abs(a)>=.5?"unusual":s<=.05&&Math.abs(a)>=.15?"notable":"info"}function se(s){return s==="reticle-position"?"other reticle positions":s==="test-site"?"other test sites":"the rest of the map"}function Y(s){return s==="reticle-position"?"Other reticle positions":s==="test-site"?"Other test sites":"Rest of map"}function ke(s,a,i){const o=(Math.abs(a)*100).toFixed(1);return`${s} yield is ${o} percentage points ${a>0?"higher":"lower"} than ${se(i)}`}function ue(s,a){return a==="quadrant"&&!s.startsWith("Quadrant")?`quadrant ${s}`:s}function ze(s){return/^(Rings|Sectors|Quadrants)\b/.test(s)?"have":"has"}function Me(s,a,i,o){const e=ue(s,o),c=(Math.abs(i)*100).toFixed(1);return`${e} ${ze(e)} ${a} occurrence ${c} percentage points ${i>0?"higher":"lower"} than ${se(o)}`}function Re(s,a,i,o){const e=ue(s,o),c=(Math.abs(i)*100).toFixed(1);return`${e} ${ze(e)} ${a} pass rate ${c} percentage points ${i>0?"higher":"lower"} than ${se(o)}`}function Be(s,a,i,o,e,c){const r=ue(s,e),n=se(e),l=i>0?"higher":"lower";if(o!==void 0&&Number.isFinite(o)){const u=(Math.abs(o)*100).toFixed(1);return`${r} mean ${a} is ${u}% ${l} than ${n}`}const t=c?` ${c}`:"";return`${r} mean ${a} is ${Math.abs(i).toPrecision(3)}${t} ${l} than ${n}`}function Fe(s,a,i){const o=a?.find(e=>e.bin===s);return o?.name?`${i} ${s} (${o.name})`:`${i} ${s}`}function nt(s,a){const i=a?.find(o=>o.testNumber===s);return{label:x(i,s),unit:i?.unit,...te(i)}}function de(s,a){const i=new Map(s.map(e=>[T(e),e])),o=new Map;for(const e of a){const c=[];for(const r of e.dieKeys){const n=i.get(r);n&&c.push(n)}o.set(e.key,c)}return o}function ge(s,a){return fe(s),s.filter(i=>{const o=i.stats.adjustedPValue??i.stats.pValue??1,e=Math.abs(i.effect.absoluteDelta??0),c=Math.abs(i.effect.relativeDelta??0);return o<=a.significanceLevel&&(e>=a.minimumEffectSize||c>=a.minimumRelativeEffect)}).map(i=>({...i,severity:ie(i.stats.adjustedPValue??i.stats.pValue??1,i.effect.absoluteDelta??0,i.effect.relativeDelta)}))}function W(s,a,i,o){const e=new Set(i),c=de(s,a),r=new Map,n=new Map;for(const[t,u]of c){let f=0,m=0;for(const g of u)g.hbin!==void 0&&(m++,e.has(g.hbin)&&f++);r.set(t,f),n.set(t,m)}const l=[];for(const t of a){const u=n.get(t.key)??0,f=r.get(t.key)??0;let m=0,g=0;for(const[k,h]of n)k!==t.key&&(m+=h,g+=r.get(k)??0);if(u<o.minimumSampleSize||m<o.minimumSampleSize)continue;const p=f/u,b=g/m,y=p-b,M=A(f,u,g,m);l.push({id:`yield:${t.key}`,level:"wafer",severity:"info",variable:{kind:"yield",label:"Yield"},comparison:{family:t.family,left:t.label,right:Y(t.family)},effect:{direction:y===0?"different":y>0?"higher":"lower",absoluteDelta:y,relativeDelta:b===0?void 0:y/b,effectSize:y},stats:{method:"two-proportion-z",pValue:M,sampleSizeLeft:u,sampleSizeRight:m},summary:ke(t.label,y,t.family),highlight:{kind:"region",regionFamily:t.family,regionKeys:[t.key],dieKeys:[...t.dieKeys]}})}return ge(l,o)}function L(s,a,i,o,e,c){const r=g=>i==="soft"?g.sbin:g.hbin,n=[...new Set(s.map(r).filter(g=>g!==void 0))].sort((g,p)=>g-p),l=de(s,a),t=new Map,u=new Map;for(const[g,p]of l){const b=new Map;for(const y of p){const M=r(y);M!==void 0&&b.set(M,(b.get(M)??0)+1)}t.set(g,b),u.set(g,p.length)}const f=[],m=e==="hardBin"?"HBin":"SBin";for(const g of a){const p=u.get(g.key)??0,b=t.get(g.key);let y=0;const M=new Map;for(const[k,h]of t)if(k!==g.key){y+=u.get(k)??0;for(const[S,v]of h)M.set(S,(M.get(S)??0)+v)}if(!(p<c.minimumSampleSize||y<c.minimumSampleSize))for(const k of n){const h=b.get(k)??0,S=M.get(k)??0,v=h/p,w=S/y,$=v-w,D=A(h,p,S,y),z=Fe(k,o,m);f.push({id:`${e}:${k}:${g.key}`,level:"wafer",severity:"info",variable:{kind:e,bin:k,label:z},comparison:{family:g.family,left:g.label,right:Y(g.family)},effect:{direction:$===0?"different":$>0?"higher":"lower",absoluteDelta:$,relativeDelta:w===0?void 0:$/w,effectSize:$},stats:{method:"two-proportion-z",pValue:D,sampleSizeLeft:p,sampleSizeRight:y},summary:Me(g.label,z,$,g.family),highlight:{kind:"bin",bin:k,regionKeys:[g.key],dieKeys:[...g.dieKeys]}})}}return ge(f,c)}function Q(s,a,i,o){const e=(i??[]).filter(u=>!H(u));if(!e.length)return[];const c=e.map(u=>u.testNumber),r=de(s,a),n=new Map,l=new Map;for(const[u,f]of r){const m=new Uint32Array(e.length),g=new Uint32Array(e.length);for(const p of f)for(let b=0;b<e.length;b++){const y=ee(p,c[b],e[b]);y!==void 0&&(g[b]++,y&&m[b]++)}n.set(u,m),l.set(u,g)}const t=[];for(const u of a){const f=n.get(u.key),m=l.get(u.key);for(let g=0;g<e.length;g++){let p=0,b=0;for(const[$,D]of l)$!==u.key&&(b+=D[g],p+=n.get($)[g]);const y=m[g],M=f[g];if(y<o.minimumSampleSize||b<o.minimumSampleSize)continue;const k=M/y,h=p/b,S=k-h,v=A(M,y,p,b),w=c[g];t.push({id:`functional:${w}:${u.key}`,level:"wafer",severity:"info",variable:{kind:"functionalTest",index:w,label:`${x(e[g],w)} pass rate`,...te(e[g])},comparison:{family:u.family,left:u.label,right:Y(u.family)},effect:{direction:S===0?"different":S>0?"higher":"lower",absoluteDelta:S,relativeDelta:h===0?void 0:S/h,effectSize:S},stats:{method:"two-proportion-z",pValue:v,sampleSizeLeft:y,sampleSizeRight:b},summary:Re(u.label,x(e[g],w),S,u.family),highlight:{kind:"region",regionFamily:u.family,regionKeys:[u.key],dieKeys:[...u.dieKeys]}})}}return ge(t,o)}function he(s,a){return s.length<2?0:s.reduce((i,o)=>i+(o-a)**2,0)/(s.length-1)}function Ne(s,a,i,o,e,c){const r=Math.sqrt(i/s+c/o),n=a-e;if(!Number.isFinite(r)||r===0)return{pValue:1,effectSize:0,delta:n};const l=n/r,t=Math.sqrt(Math.max(0,(i+c)/2)),u=t===0?n:n/t;return{pValue:$e(2*(1-we(Math.abs(l)))),effectSize:u,delta:n}}function it(s,a){const i=ne(s),o=ne(a);return Ne(s.length,i,he(s,i),a.length,o,he(a,o))}const Pe=250;function Ce(s,a,i){const o=r=>r.filter(n=>H(i?.find(l=>l.testNumber===n)));if(a)return{testNumbers:o(a.slice().sort((r,n)=>r-n))};const e=new Set;let c=!1;e:for(const r of s)for(const n of Object.keys(r.testValues??{})){const l=Number(n);if(!e.has(l)&&(e.add(l),e.size>Pe)){c=!0;break e}}if(c){const r=`Test-value analysis was skipped: more than ${Pe} tests were found in the die data, so no test findings were computed. Pass testNumbers: [...] to analyse specific tests.`;return console.warn(`[wafermap] analyzeWaferMap: ${r}`),{testNumbers:[],warning:{code:"test-count-capped",message:r,severity:"warning"}}}return{testNumbers:o([...e].sort((r,n)=>r-n))}}function U(s,a,i,o){const e=Ce(s,o.testNumbers,i);if(e.warning)return{findings:[],warning:e.warning};const c=e.testNumbers,r=new Map;for(let p=0;p<a.length;p++)for(const b of a[p].dieKeys)r.set(b,p);const n=a.length,l=[],t=[];for(const p of s){const b=r.get(T(p));b!==void 0&&(l.push(p),t.push(b))}const u=[],f=new Float64Array(n),m=new Float64Array(n),g=new Float64Array(n);for(const p of c){f.fill(0),m.fill(0),g.fill(0);let b=0,y=0,M=0,k;for(let h=0;h<l.length;h++){const v=l[h].testValues?.[p];if(v===void 0)continue;k===void 0&&(k=v);const w=v-k,$=t[h];f[$]+=1,m[$]+=w,g[$]+=w*w,b+=1,y+=w,M+=w*w}if(k!==void 0)for(let h=0;h<n;h++){const S=f[h],v=b-S;if(S<o.minimumSampleSize||v<o.minimumSampleSize)continue;const w=m[h]/S,D=(y-m[h])/v,z=w+k,N=D+k,B=S>1?Math.max(0,g[h]-S*w*w)/(S-1):0,d=v>1?Math.max(0,M-g[h]-v*D*D)/(v-1):0,{pValue:F,effectSize:R,delta:V}=Ne(S,z,B,v,N,d),P=a[h],{label:q,unit:G,...X}=nt(p,i),J=N!==0?V/Math.abs(N):void 0;u.push({id:`test:${p}:${P.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:p,label:q,unit:G,...X},comparison:{family:P.family,left:P.label,right:Y(P.family)},effect:{direction:V===0?"different":V>0?"higher":"lower",absoluteDelta:V,relativeDelta:J,effectSize:R},stats:{method:"welch-z-approx",pValue:F,sampleSizeLeft:S,sampleSizeRight:v},summary:Be(P.label,q,V,J,P.family,G),highlight:{kind:"region",regionFamily:P.family,regionKeys:[P.key],dieKeys:[...P.dieKeys]}})}}return fe(u),{activeTestNumbers:c,findings:u.filter(p=>{const b=p.stats.adjustedPValue??p.stats.pValue??1,y=Math.abs(p.effect.effectSize??0);return b<=o.significanceLevel&&y>=o.minimumEffectSize}).map(p=>({...p,severity:ce(p.stats.adjustedPValue??p.stats.pValue??1,p.effect.effectSize??0)}))}}function st(s,a,i,o){if(!i?.length)return[];const e=i.filter(n=>H(n)&&(n.limitLow!==void 0||n.limitHigh!==void 0));if(!e.length)return[];const c=[],r=new Map(s.map(n=>[T(n),n]));for(const n of e){const l=n.testNumber;for(const t of a){const u=new Map;for(const m of t){const g=[];for(const p of m.dieKeys){const b=r.get(p);b&&g.push(b)}u.set(m.key,g)}const f=[];for(const m of t){const g=u.get(m.key),p=[];for(const[z,N]of u)if(z!==m.key)for(const B of N)p.push(B);const b=z=>z.testValues?.[l]!==void 0,y=z=>ve(le(z.testValues?.[l],n)),M=g.filter(b),k=p.filter(b);if(M.length<o.minimumSampleSize||k.length<o.minimumSampleSize)continue;const h=M.filter(y).length,S=k.filter(y).length,v=h/M.length,w=S/k.length,$=v-w,D=A(h,M.length,S,k.length);f.push({id:`specLimit:${l}:${m.key}`,level:"wafer",severity:"info",variable:{kind:"test",index:l,label:x(n,l),unit:n.unit,...te(n)},comparison:{family:m.family,left:m.label,right:Y(m.family)},effect:{direction:$===0?"different":$>0?"higher":"lower",absoluteDelta:$,relativeDelta:w===0?void 0:$/w,effectSize:$},stats:{method:"two-proportion-z",pValue:D,sampleSizeLeft:M.length,sampleSizeRight:k.length},summary:`${m.label} limit fail rate for ${x(n,l)} is ${(Math.abs($)*100).toFixed(1)} pp ${$>0?"higher":"lower"} than the rest of the wafer`,highlight:{kind:"region",regionFamily:m.family,regionKeys:[m.key],dieKeys:[...m.dieKeys]}})}fe(f),c.push(...f.filter(m=>{const g=m.stats.adjustedPValue??m.stats.pValue??1,p=Math.abs(m.effect.absoluteDelta??0),b=Math.abs(m.effect.relativeDelta??0);return g<=o.significanceLevel&&(p>=o.minimumEffectSize||b>=o.minimumRelativeEffect)}).map(m=>({...m,severity:ce(m.stats.adjustedPValue??m.stats.pValue??1,m.effect.effectSize??0)})))}}return c}function O(s){return s.highlight.regionKeys??[]}function ot(s){const a=s.variable.bin??s.variable.index??"";return`${s.comparison.family}\0${s.variable.kind}\0${a}\0${s.effect.direction}`}function _(s,a){const i=O(s)[0]??"",o=I(i);return o.family==="ring"?o.ring??0:o.family==="sector"?Se(a).indexOf(o.sector??""):0}function at(s,a,i){if(a==="quadrant"){const r=new Map;for(const u of s){const f=I(O(u)[0]??"").quadrant;f&&r.set(f,u)}const n=[...r.keys()],l=new Set,t=[];for(const u of n){if(l.has(u))continue;const f=[],m=[u];for(l.add(u);m.length;){const g=m.pop();f.push(g);for(const p of n)!l.has(p)&&Ye(g,p)&&(l.add(p),m.push(p))}t.push(f.map(g=>r.get(g)))}return t}const o=[...s].sort((r,n)=>_(r,i)-_(n,i)),e=[];let c=[];for(const r of o){if(c.length===0){c.push(r);continue}const n=_(c[c.length-1],i);_(r,i)-n===1?c.push(r):(e.push(c),c=[r])}if(c.length&&e.push(c),a==="sector"&&e.length>1){const n=Se(i).length,l=_(e[0][0],i),t=_(e[e.length-1][e[e.length-1].length-1],i);if(l===0&&t===n-1){const u=e.pop();e[0]=[...u,...e[0]]}}return e}function rt(s,a){const i=s.map(r=>I(O(r)[0]??"").ring).filter(r=>r!==void 0).sort((r,n)=>r-n),o=i[0],e=i[i.length-1];let c="";return o===e&&o===1&&a>1?c=" (core)":o===e&&o===a&&a>1&&(c=" (edge)"),o===e?`Ring ${o}${c}`:`Rings ${o}\u2013${e}`}function lt(s,a){const i=s.map(o=>I(O(o)[0]??"").sector??"");return i.length===1?`Sector ${i[0]}`:`Sectors ${i[0]}\u2013${i[i.length-1]}`}function ft(s){const a=s.map(i=>I(O(i)[0]??"").quadrant??"");return a.length===1?a[0]:a.length===2?`Quadrants ${a[0]} & ${a[1]}`:`Quadrants ${a.slice(0,-1).join(", ")} & ${a[a.length-1]}`}function me(s){return[...new Set(s)]}function ct(s,a){const i=s[0],o=i.comparison.family,e=i.variable.kind,c=me(s.flatMap(h=>h.highlight.dieKeys??[])),r=me(s.flatMap(O)),n=new Set(c),l=o==="ring"?rt(s,a.ringCount):o==="sector"?lt(s,a.sectorCount):ft(s),t=e==="softBin"?a.softEligibleDies:e==="test"?a.testDies:a.eligibleDies,u=t.filter(h=>n.has(T(h))),f=t.filter(h=>!n.has(T(h)));let m,g,p,b,y;if(e==="test"){const h=i.variable.index,S=d=>d.testValues?.[h],v=u.map(S).filter(d=>d!==void 0),w=f.map(S).filter(d=>d!==void 0),{pValue:$,effectSize:D,delta:z}=it(v,w),N=ne(w),B=N!==0?z/Math.abs(N):void 0;m={direction:z===0?"different":z>0?"higher":"lower",absoluteDelta:z,relativeDelta:B,effectSize:D},g={method:"welch-z-approx",pValue:$,sampleSizeLeft:v.length,sampleSizeRight:w.length},p=ce($,D),b=Be(l,i.variable.label,z,B,o,i.variable.unit),y=`test:${h}`}else if(e==="yield"){const h=new Set(a.passBins),S=u.filter(R=>R.hbin!==void 0),v=f.filter(R=>R.hbin!==void 0),w=S.length,$=v.length,D=S.filter(R=>h.has(R.hbin)).length,z=v.filter(R=>h.has(R.hbin)).length,N=w>0?D/w:0,B=$>0?z/$:0,d=N-B,F=A(D,w,z,$);m={direction:d===0?"different":d>0?"higher":"lower",absoluteDelta:d,relativeDelta:B===0?void 0:d/B,effectSize:d},g={method:"two-proportion-z",pValue:F,sampleSizeLeft:w,sampleSizeRight:$},p=ie(F,d,m.relativeDelta),b=ke(l,d,o),y="yield"}else if(e==="functionalTest"){const h=i.variable.index,S=a.testDefs?.find(F=>F.testNumber===h);let v=0,w=0,$=0,D=0;for(const F of u){const R=ee(F,h,S);R!==void 0&&(w++,R&&v++)}for(const F of f){const R=ee(F,h,S);R!==void 0&&(D++,R&&$++)}const z=w>0?v/w:0,N=D>0?$/D:0,B=z-N,d=A(v,w,$,D);m={direction:B===0?"different":B>0?"higher":"lower",absoluteDelta:B,relativeDelta:N===0?void 0:B/N,effectSize:B},g={method:"two-proportion-z",pValue:d,sampleSizeLeft:w,sampleSizeRight:D},p=ie(d,B,m.relativeDelta),b=Re(l,x(S,h),B,o),y=`functional:${h}`}else{const h=i.variable.bin,S=d=>e==="softBin"?d.sbin:d.hbin,v=u.filter(d=>S(d)===h).length,w=f.filter(d=>S(d)===h).length,$=v/u.length,D=w/f.length,z=$-D,N=A(v,u.length,w,f.length);m={direction:z===0?"different":z>0?"higher":"lower",absoluteDelta:z,relativeDelta:D===0?void 0:z/D,effectSize:z},g={method:"two-proportion-z",pValue:N,sampleSizeLeft:u.length,sampleSizeRight:f.length},p=ie(N,z,m.relativeDelta);const B=e==="softBin"?a.sbinDefs:a.hbinDefs;b=Me(l,Fe(h,B,e==="softBin"?"SBin":"HBin"),z,o),y=`${e}:${h}`}const M=me(r.map(h=>h.replace(`${o}:`,""))).join("-"),k=e==="hardBin"||e==="softBin"?{kind:"bin",bin:i.variable.bin,regionKeys:r,dieKeys:c}:{kind:"region",regionFamily:o,regionKeys:r,dieKeys:c};return{...i,id:`${y}:${o}:${M}`,severity:p,comparison:{family:o,left:l,right:Y(o)},effect:m,stats:g,summary:b,highlight:k,relatedIds:s.map(h=>h.id)}}function ut(s,a){const i=new Set(["ring","quadrant","sector"]),o=[],e=new Map;for(const r of s){if(!i.has(r.comparison.family)){o.push(r);continue}const n=ot(r),l=e.get(n)??[];l.push(r),e.set(n,l)}const c=[];for(const r of e.values()){if(r.length===1){c.push(r[0]);continue}const n=r[0].comparison.family;for(const l of at(r,n,a.sectorCount))c.push(l.length===1?l[0]:ct(l,a))}return[...o,...c]}function dt(s){const a=new Map,i=new Map;for(const e of s){const c=T(e);e.hbin!==void 0&&e.hbin!==null&&(a.get(e.hbin)??a.set(e.hbin,new Set).get(e.hbin)).add(c),e.sbin!==void 0&&e.sbin!==null&&(i.get(e.sbin)??i.set(e.sbin,new Set).get(e.sbin)).add(c)}const o=new Set;for(const[e,c]of a)for(const[r,n]of i){if(c.size!==n.size)continue;let l=!0;for(const t of c)if(!n.has(t)){l=!1;break}l&&o.add(`${e}:${r}`)}return o}function gt(s){return`${s.comparison.family}\0${s.comparison.left}`}function ht(s,a,i){const o=(n,l)=>{n.absorbedIds=[...n.absorbedIds??[],l.id]},e=new Map;for(const n of s){const l=gt(n);(e.get(l)??e.set(l,[]).get(l)).push(n)}const c=dt(a),r=new Set;for(const n of e.values()){for(const t of n)if(!(t.variable.kind!=="hardBin"||t.variable.bin===void 0))for(const u of n){if(u.variable.kind!=="softBin"||u.variable.bin===void 0||r.has(u.id)||!c.has(`${t.variable.bin}:${u.variable.bin}`)||u.effect.direction!==t.effect.direction)continue;o(t,u),r.add(u.id);const f=t.variable.label.replace(/^HBin\s+/,""),m=u.variable.label.replace(/^SBin\s+/,""),g=f===m?`HBin and SBin ${f} (same dies)`:`${t.variable.label} and ${u.variable.label} (same dies)`;t.summary=t.summary.replace(t.variable.label,g),t.variable.label=g;break}if(i.length!==1)continue;const l=n.find(t=>t.variable.kind==="yield");if(l){for(const t of n)if(!(t===l||r.has(t.id)||!((t.variable.kind==="hardBin"||t.variable.kind==="softBin")&&t.variable.bin===i[0]))){o(l,t),r.add(t.id);for(const f of t.absorbedIds??[])l.absorbedIds=[...l.absorbedIds??[],f]}}}}export function analyzeWaferMap(s,a={}){const{resolved:i,warnings:o}=Ue(a),e=Ke(s),c={...i,passBins:e.passBins??j.passBins,ringCount:e.ringCount??j.ringCount},r=e.isLotStack,n=e.aggrMethod,l=!r||n==="mode"||n==="countBin"||n==="percent",t=e.dies.filter(d=>Xe(d)),u=t.filter(ye),f=Ge(c,t.length),m=e.dies.filter(d=>ae(d)),g=m.filter(ye),p=qe(g,e.wafer,f.ringCount),b=Ae(g,e.wafer,f.ringCount),y=Oe(g,e.reticleConfig),M=Ie(m,!1),k=He(g,e.wafer,f.sectorCount),h=[];l&&h.push(...W(t,p,f.passBins,f),...W(t,b,f.passBins,f),...W(t,y,f.passBins,f),...W(t,M,f.passBins,f),...W(t,k,f.passBins,f)),l&&h.push(...L(t,p,"hard",e.hbinDefs,"hardBin",f),...L(t,b,"hard",e.hbinDefs,"hardBin",f),...L(t,y,"hard",e.hbinDefs,"hardBin",f),...L(t,M,"hard",e.hbinDefs,"hardBin",f),...L(t,k,"hard",e.hbinDefs,"hardBin",f));{const d=t.filter(F=>F.sbin!==void 0);h.push(...L(d,p,"soft",e.sbinDefs,"softBin",f),...L(d,b,"soft",e.sbinDefs,"softBin",f),...L(d,y,"soft",e.sbinDefs,"softBin",f),...L(d,M,"soft",e.sbinDefs,"softBin",f),...L(d,k,"soft",e.sbinDefs,"softBin",f))}const S=[...o];let v;if(f.enableTestValueAnalysis){const d=U(t,p,e.testDefs,f),F=U(t,b,e.testDefs,f),R=U(t,y,e.testDefs,f),V=U(t,M,e.testDefs,f),P=U(t,k,e.testDefs,f);h.push(...d.findings,...F.findings,...R.findings,...V.findings,...P.findings),d.warning&&S.push(d.warning),v=d.activeTestNumbers,h.push(...st(t,[p,b,y,M,k],e.testDefs,f)),h.push(...Q(t,p,e.testDefs,f),...Q(t,b,e.testDefs,f),...Q(t,y,e.testDefs,f),...Q(t,M,e.testDefs,f),...Q(t,k,e.testDefs,f))}{const d=Je(r,l,e.testDefs);(!r||l||d!==void 0)&&h.push(..._e(u,e.wafer,{...f,isFailingDie:d}))}const w=ut(h,{eligibleDies:t,softEligibleDies:t.filter(d=>d.sbin!==void 0),testDies:t,passBins:f.passBins,ringCount:f.ringCount,sectorCount:f.sectorCount,hbinDefs:e.hbinDefs,sbinDefs:e.sbinDefs,testDefs:e.testDefs});h.length=0,h.push(...w);let $;if(l){const d=We(u,e.wafer,{passBins:f.passBins,ringCount:f.ringCount});if($=d??void 0,d!==null&&d.pattern!=="random"&&d.pattern!=="none"){let Ee=function(C){const E=O(C).map(K=>I(K).ring).filter(K=>K!==void 0);if(E.length===0)return!0;const pe=E.some(K=>K===Le),be=E.some(K=>K===1);return Z==="edge-ring"||Z==="edge-local"?pe:Z==="center"?be:Z==="donut"?!pe&&!be:!0};const R={center:"Center cluster",donut:"Donut","edge-ring":"Edge-ring","edge-local":"Edge-local",scratch:"Scratch","near-full":"Near-full"}[d.pattern]??d.pattern,V=d.confidence==="high"?"unusual":d.confidence==="medium"?"notable":"info",P=d.features,q=C=>`${(C*100).toFixed(0)}%`,G=d.pattern==="edge-ring"||d.pattern==="edge-local"?`${q(P.edgeRdd)} of edge dies failing`:d.pattern==="center"||d.pattern==="donut"?`centroid at ${q(P.centroidDistNorm)} of wafer radius from centre`:d.pattern==="scratch"?`linear score ${P.linearScore.toFixed(2)}, eccentricity ${P.eccentricity.toFixed(2)}`:`${q(P.globalRdd)} of dies failing`,X=t.filter(C=>{const E=C.hbin??C.sbin;return E!==void 0&&!new Set(f.passBins).has(E)}),J={"edge-ring":["ring","edge-arc"],"edge-local":["edge-arc","sector","quadrant"],center:["ring","cluster"],donut:["ring"],scratch:["cluster","sector","quadrant"],"near-full":["ring"]},Ve=new Set(J[d.pattern]??[]),Z=d.pattern,Le=f.ringCount,oe=[];for(const C of h){if(!Ve.has(C.comparison.family)||C.comparison.family==="ring"&&!Ee(C))continue;oe.push(C.id);const E=C.comparison.family==="ring"||C.comparison.family==="quadrant"||C.comparison.family==="sector";V!=="info"&&E&&(C.severity="info")}h.push({id:`spatial-pattern:${d.pattern}`,level:"wafer",severity:V,variable:{kind:"spatialPattern",label:R},comparison:{family:"spatial-pattern",left:R,right:"Wafer"},effect:{direction:"different",effectSize:P.globalRdd},stats:{method:"geometry",sampleSizeLeft:X.length,sampleSizeRight:t.length},summary:`Spatial pattern: ${R.toLowerCase()} (${d.confidence} confidence) \u2014 ${G}${d.note?` [${d.note}]`:""}`,highlight:{kind:"dies",dieKeys:X.map(C=>T(C))},relatedIds:oe.length>0?oe:void 0})}}h.sort((d,F)=>{const R=d.stats.adjustedPValue??d.stats.pValue??1,V=F.stats.adjustedPValue??F.stats.pValue??1;return R!==V?R-V:Math.abs(F.effect.absoluteDelta??0)-Math.abs(d.effect.absoluteDelta??0)});const D=Ze(e.dies,t.length,e.yield.yieldPercent);r&&(D.isLotStack=!0,n&&(D.aggregationMethod=n));const z=et(e.dies,e.testDefs);z&&(D.testSpecYield=z);const N=computeFunctionalYield(e.dies,e.testDefs);if(N&&(D.functionalYield=N),$&&(D.spatialPattern=$),v===void 0&&f.computePerTestStats){const d=Ce(t,f.testNumbers,e.testDefs);d.warning&&S.push(d.warning),v=d.testNumbers}if(v?.length){const d=tt(e.dies,v,e.testDefs,f.minimumSampleSize);d&&(D.perTestStats=d);const F=Te([{dies:e.dies}],e.testDefs,v);F&&(D.capability=F)}Object.assign(D,xe(e.dies,e.testDefs));const B=je([{dies:e.dies,wafer:e.wafer,passBins:f.passBins,ringCount:f.ringCount}]);return B&&(D.regionYield=B),S.length>0&&(D.warnings=S),ht(h,t,f.passBins),{level:"wafer",hasNotableFindings:h.some(d=>d.severity==="notable"||d.severity==="unusual"),findings:h,wafer:e.wafer.metadata??void 0,stats:D}}
|
|
@@ -21,6 +21,8 @@ export interface CapabilityDatum {
|
|
|
21
21
|
hasSpec: boolean;
|
|
22
22
|
lsl?: number;
|
|
23
23
|
usl?: number;
|
|
24
|
+
/** Which limits `lsl`/`usl` are — see `TestCapability.limitBasis`. */
|
|
25
|
+
limitBasis?: 'spec' | 'test';
|
|
24
26
|
mean: number;
|
|
25
27
|
/** Sample stddev, ddof=1, pooled across every die across all items. */
|
|
26
28
|
stdOverall: number;
|
|
@@ -80,8 +82,8 @@ export interface PooledTestStats {
|
|
|
80
82
|
/**
|
|
81
83
|
* Per test, how many pooled values fell outside the test's own spec limits —
|
|
82
84
|
* the spec-yield tally, counted off the sorted values this pass already held.
|
|
83
|
-
* Only tests carrying at least one limit appear. `fail`
|
|
84
|
-
*
|
|
85
|
+
* Only tests carrying at least one limit appear. `fail` is
|
|
86
|
+
* `countOutOfSpecSorted` — the same rule as the per-die `classifySpec`.
|
|
85
87
|
*
|
|
86
88
|
* This is here rather than the sorted arrays themselves because the result is
|
|
87
89
|
* memoised: a tally is a handful of numbers per test, whereas the values are
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{isYieldEligibleDie as
|
|
1
|
+
import{isYieldEligibleDie as v}from"../core/dies.js";import{drain as O}from"../core/utils.js";import{isParametricTest as B}from"../renderer/buildWaferMap.js";import{countOutOfSpecSorted as D,hasSpecLimits as H}from"../renderer/spec.js";import{testLabel as E,derivedFields as P}from"../renderer/testLabel.js";import{describeSorted as V,quantile as k}from"./math.js";const _=25e4;function z(n){if(n.specLow!==void 0&&n.specHigh!==void 0&&n.specHigh>n.specLow)return{lsl:n.specLow,usl:n.specHigh,basis:"spec"};if(n.limitLow!==void 0&&n.limitHigh!==void 0&&n.limitHigh>n.limitLow)return{lsl:n.limitLow,usl:n.limitHigh,basis:"test"}}function x(n){const s=new Map,t=new Map;for(const p of n){const e=p.testNumber;if(e===void 0||!B(p))continue;t.set(e,p);const i=z(p);i&&s.set(e,i)}return{defByTestNumber:t,specByTest:s}}function*C(n,s,t){const p=new Map,e=[];for(const l of s.keys())p.set(l,e.length),e.push(l);const i=e.length,r=new Float64Array(i),o=new Float64Array(i),m=new Float64Array(i),h=new Float64Array(i),T=new Float64Array(i),b=new Float64Array(i),w=new Float64Array(i),g=new Float64Array(i),d=t?new Array(i):[],S=i<=64,A=Math.max(500,Math.floor(_/Math.max(1,i)));let f=0;for(const l of n){b.fill(0),w.fill(0),g.fill(0);for(const a of l.dies??[]){if(++f>=A&&(f=0,yield),!v(a))continue;const y=a.testValues;if(y){if(S){for(let u=0;u<i;u++){const c=y[e[u]];c===void 0||!Number.isFinite(c)||(r[u]++,o[u]+=c,m[u]+=c*c,b[u]++,w[u]+=c,g[u]+=c*c,t&&(d[u]??=[]).push(c))}continue}for(const u in y){const c=p.get(+u);if(c===void 0)continue;const N=y[+u];N===void 0||!Number.isFinite(N)||(r[c]++,o[c]+=N,m[c]+=N*N,b[c]++,w[c]+=N,g[c]+=N*N,t&&(d[c]??=[]).push(N))}}}for(let a=0;a<i;a++){const y=b[a];if(y<2)continue;const u=w[a]/y,c=(g[a]-y*u*u)/(y-1);h[a]+=(y-1)*Math.max(0,c),T[a]+=y-1}}const M=new Map;for(let l=0;l<i;l++){if(r[l]===0)continue;const a=e[l];M.set(a,{n:r[l],sum:o[l],sumSq:m[l],withinNumerator:h[l],withinDenominator:T[l]}),t&&d[l]&&t.set(a,d[l])}return M}function I(n,s,t){return O(C(n,s,t))}function L(n,s,t,p){const{n:e,sum:i,sumSq:r,withinNumerator:o,withinDenominator:m}=p,h=i/e,T=e>=2?Math.max(0,(r-e*h*h)/(e-1)):0,b=Math.sqrt(T),w=m>0?Math.sqrt(o/m):NaN;let g=null,d=null,S=null,A=null;if(t){const{lsl:f,usl:M}=t,l=M-f;S=b>0?l/(6*b):null,A=b>0?Math.min((M-h)/(3*b),(h-f)/(3*b)):null,g=Number.isFinite(w)&&w>0?l/(6*w):null,d=Number.isFinite(w)&&w>0?Math.min((M-h)/(3*w),(h-f)/(3*w)):null}return{testNumber:n,label:E(s,n),unit:s.unit,...P(s),hasSpec:t!==void 0,lsl:t?.lsl,usl:t?.usl,...t?{limitBasis:t.basis}:{},mean:h,stdOverall:b,stdWithin:w,n:e,cp:g,cpk:d,pp:S,ppk:A}}function q(n){return n.sort((s,t)=>s.hasSpec!==t.hasSpec?s.hasSpec?-1:1:s.hasSpec?s.ppk===null&&t.ppk===null?0:s.ppk===null?1:t.ppk===null?-1:s.ppk-t.ppk:t.stdOverall-s.stdOverall)}export function buildCapabilityData(n,s){return O(buildCapabilityDataSteps(n,s))}export function*buildCapabilityDataSteps(n,s){return(yield*pooledTestStatsSteps(n,s)).capability.slice()}const F=new Map,R=4,$=new WeakMap;let j=1;function W(n,s){const t=[];for(const e of n){const i=e.dies;if(!i){t.push(0);continue}let r=$.get(i);r===void 0&&(r=j++,$.set(i,r)),t.push(r)}const p=s.map(e=>`${e.testNumber}${e.testType??"P"}${e.limitLow??""}${e.limitHigh??""}${e.specLow??""}${e.specHigh??""}${e.name??""}${e.unit??""}`);return`${t.join(",")}${p.join("")}`}export function clearPooledTestStatsCache(){F.clear()}export function*pooledTestStatsSteps(n,s){const t=W(n,s),p=F.get(t);if(p)return F.delete(t),F.set(t,p),p;const e={stats:new Map,specTally:new Map,capability:[]},{defByTestNumber:i,specByTest:r}=x(s);if(i.size===0)return e;const o=new Map,m=yield*C(n,i,o),h=new Map,T=new Map,b=[];for(const g of s){yield;const d=g.testNumber;if(d===void 0)continue;const S=m.get(d);if(!S||S.n===0||!i.has(d))continue;const A=o.get(d);o.delete(d);const f=Float64Array.from(A);f.sort(),h.set(d,V(f)),H(g)&&T.set(d,{n:f.length,fail:D(f,g)});const M=r.get(d),l=L(d,g,M,S);let a;if(M){const y=M.usl-M.lsl;a=u=>(u-M.lsl)/y}else{const y=f[0],u=f[f.length-1]-y;a=u>0?c=>(c-y)/u:()=>.5}b.push({...l,min:a(f[0]),q1:a(k(f,.25)),median:a(k(f,.5)),q3:a(k(f,.75)),max:a(f[f.length-1])})}const w={stats:h,specTally:T,capability:q(b)};return F.set(t,w),F.size>R&&F.delete(F.keys().next().value),w}export function buildCapabilityFigures(n,s){const{defByTestNumber:t,specByTest:p}=x(s);if(t.size===0)return[];const e=I(n,t,null),i=[];for(const[r,o]of e)o.n!==0&&i.push(L(r,t.get(r),p.get(r),o));return q(i)}export function poolCapabilityFigures(n,s){const{defByTestNumber:t,specByTest:p}=x(s),e=new Map;for(const r of n)for(const o of r){if(!t.has(o.testNumber)||o.n===0)continue;let m=e.get(o.testNumber);m||(m={n:0,sum:0,sumSq:0,withinNumerator:0,withinDenominator:0},e.set(o.testNumber,m));const h=o.stdOverall*o.stdOverall;m.n+=o.n,m.sum+=o.n*o.mean,m.sumSq+=(o.n-1)*h+o.n*o.mean*o.mean,o.n>=2&&(m.withinNumerator+=(o.n-1)*h,m.withinDenominator+=o.n-1)}const i=[];for(const[r,o]of e)i.push(L(r,t.get(r),p.get(r),o));return q(i)}
|
|
@@ -64,7 +64,7 @@ export interface CorrelationMatrix {
|
|
|
64
64
|
* Correlation is the only computation here that is quadratic in tests *and*
|
|
65
65
|
* linear in dies: a 400k-die, 50-test lot is 1,225 pairs per die — 490 million
|
|
66
66
|
* pair updates, each touching six accumulators — which in a browser presents as
|
|
67
|
-
* a panel that never renders
|
|
67
|
+
* a panel that never renders.
|
|
68
68
|
*
|
|
69
69
|
* 25,000 is far past the point where more dies change a Pearson coefficient: the
|
|
70
70
|
* standard error of r is about `(1 - r²)/sqrt(n)`, so at n = 25,000 it is under
|
|
@@ -19,7 +19,7 @@ export declare const buildTestBoxplotData: typeof buildTestBoxplotDataImpl;
|
|
|
19
19
|
export declare const buildTestTrendData: typeof buildTestTrendDataImpl;
|
|
20
20
|
/** @deprecated Removed in 0.31.0. Compute the die-weighted mean from `perWaferTestStats` (mean × count) on `analyzeWaferLot`'s result. */
|
|
21
21
|
export declare const trendCentre: typeof trendCentreImpl;
|
|
22
|
-
/** @deprecated Removed in 0.31.0. `analyzeWaferMap` and `analyzeWaferLot` now return per-test pass rates as `stats.testSpecYield` (
|
|
22
|
+
/** @deprecated Removed in 0.31.0. `analyzeWaferMap` and `analyzeWaferLot` now return per-test pass rates as `stats.testSpecYield` (test limits), `stats.testFlagYield` (tester verdicts) and `stats.functionalYield`, with `stats.specVerdictDisagreementDies.` */
|
|
23
23
|
export declare const buildTestPassRateData: typeof buildTestPassRateDataImpl;
|
|
24
24
|
/** @deprecated Removed in 0.31.0. Read which pass rates exist from `stats.testSpecYield`, `stats.testFlagYield` and `stats.functionalYield` on `analyzeWaferMap`'s or `analyzeWaferLot`'s result. */
|
|
25
25
|
export declare const hasJudgeableTests: typeof hasJudgeableTestsImpl;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{deprecated as e}from"../renderer/deprecate.js";import{buildCapabilityData as s}from"./capability.js";import{buildCorrelationMatrix as r,filterCorrelationMatrix as i}from"./correlation.js";import{buildTestBoxplotData as o}from"./boxplot.js";import{buildTestTrendData as n,trendCentre as l}from"./trend.js";import{buildTestPassRateData as d,hasJudgeableTests as p}from"./testPassRate.js";import{buildTestHistogramData as m,buildTestHistogramSeries as u}from"./histogram.js";import{buildScatterData as c,buildScatterDataGrouped as f}from"./scatter.js";import{buildYieldData as b,buildYieldDataCombined as g}from"./yield.js";import{buildBinParetoData as h,buildBinClusterData as y}from"./binPareto.js";const t="If you depend on it, say so at https://github.com/wafertools/wafermap/issues.";export const buildCapabilityData=e(s,"buildCapabilityData",`analyzeWaferMap and analyzeWaferLot now return Cp/Cpk/Pp/Ppk as stats.capability (enable computePerTestStats); a lot's uses the pooled within-wafer stddev. ${t}`),buildCorrelationMatrix=e(r,"buildCorrelationMatrix",`It prepared data for the Insights correlation matrix, which wmap draws itself (insights: { enabled: true }); there is no data replacement yet. ${t}`),filterCorrelationMatrix=e(i,"filterCorrelationMatrix",`It prepared data for the Insights correlation matrix, which wmap draws itself (insights: { enabled: true }); there is no data replacement. ${t}`),buildTestBoxplotData=e(o,"buildTestBoxplotData",`Five-number summaries are stats.perTestStats (enable computePerTestStats), and perWaferTestStats on analyzeWaferLot's result. ${t}`),buildTestTrendData=e(n,"buildTestTrendData",`Per-wafer means and stddevs in slot order are perWaferTestStats on analyzeWaferLot's result (enable computePerTestStats). ${t}`),trendCentre=e(l,"trendCentre",`Compute the die-weighted mean from perWaferTestStats (mean \xD7 count) on analyzeWaferLot's result. ${t}`),buildTestPassRateData=e(d,"buildTestPassRateData",`analyzeWaferMap and analyzeWaferLot now return per-test pass rates as stats.testSpecYield (
|
|
1
|
+
import{deprecated as e}from"../renderer/deprecate.js";import{buildCapabilityData as s}from"./capability.js";import{buildCorrelationMatrix as r,filterCorrelationMatrix as i}from"./correlation.js";import{buildTestBoxplotData as o}from"./boxplot.js";import{buildTestTrendData as n,trendCentre as l}from"./trend.js";import{buildTestPassRateData as d,hasJudgeableTests as p}from"./testPassRate.js";import{buildTestHistogramData as m,buildTestHistogramSeries as u}from"./histogram.js";import{buildScatterData as c,buildScatterDataGrouped as f}from"./scatter.js";import{buildYieldData as b,buildYieldDataCombined as g}from"./yield.js";import{buildBinParetoData as h,buildBinClusterData as y}from"./binPareto.js";const t="If you depend on it, say so at https://github.com/wafertools/wafermap/issues.";export const buildCapabilityData=e(s,"buildCapabilityData",`analyzeWaferMap and analyzeWaferLot now return Cp/Cpk/Pp/Ppk as stats.capability (enable computePerTestStats); a lot's uses the pooled within-wafer stddev. ${t}`),buildCorrelationMatrix=e(r,"buildCorrelationMatrix",`It prepared data for the Insights correlation matrix, which wmap draws itself (insights: { enabled: true }); there is no data replacement yet. ${t}`),filterCorrelationMatrix=e(i,"filterCorrelationMatrix",`It prepared data for the Insights correlation matrix, which wmap draws itself (insights: { enabled: true }); there is no data replacement. ${t}`),buildTestBoxplotData=e(o,"buildTestBoxplotData",`Five-number summaries are stats.perTestStats (enable computePerTestStats), and perWaferTestStats on analyzeWaferLot's result. ${t}`),buildTestTrendData=e(n,"buildTestTrendData",`Per-wafer means and stddevs in slot order are perWaferTestStats on analyzeWaferLot's result (enable computePerTestStats). ${t}`),trendCentre=e(l,"trendCentre",`Compute the die-weighted mean from perWaferTestStats (mean \xD7 count) on analyzeWaferLot's result. ${t}`),buildTestPassRateData=e(d,"buildTestPassRateData",`analyzeWaferMap and analyzeWaferLot now return per-test pass rates as stats.testSpecYield (test limits), stats.testFlagYield (tester verdicts) and stats.functionalYield, with stats.specVerdictDisagreementDies. ${t}`),hasJudgeableTests=e(p,"hasJudgeableTests",`Read which pass rates exist from stats.testSpecYield, stats.testFlagYield and stats.functionalYield on analyzeWaferMap's or analyzeWaferLot's result. ${t}`),buildTestHistogramData=e(m,"buildTestHistogramData",`It prepared data for the Insights histogram, which wmap draws itself (insights: { enabled: true }); there is no data replacement. ${t}`),buildTestHistogramSeries=e(u,"buildTestHistogramSeries",`It prepared data for the Insights histogram, which wmap draws itself (insights: { enabled: true }); there is no data replacement. ${t}`),buildScatterData=e(c,"buildScatterData",`It prepared data for the Insights scatter plot, which wmap draws itself (insights: { enabled: true }); there is no data replacement. ${t}`),buildScatterDataGrouped=e(f,"buildScatterDataGrouped",`It prepared data for the Insights scatter plot, which wmap draws itself (insights: { enabled: true }); there is no data replacement. ${t}`),buildYieldData=e(b,"buildYieldData",`Per-wafer yield is lotYieldSeries on analyzeWaferLot's result, and stats.yieldPercent on each summary. ${t}`),buildYieldDataCombined=e(g,"buildYieldDataCombined",`Per-wafer yield is lotYieldSeries on analyzeWaferLot's result; weight it by each wafer's die count to combine groups. ${t}`),buildBinParetoData=e(h,"buildBinParetoData",`Bin counts are stats.hardBinCounts and stats.softBinCounts on analyzeWaferMap's result. ${t}`),buildBinClusterData=e(y,"buildBinClusterData",`Bin counts per wafer are stats.hardBinCounts and stats.softBinCounts on each perWafer summary of analyzeWaferLot's result. ${t}`);import{deprecatedValue as R}from"../renderer/deprecate.js";import{buildRingRegions as D,buildQuadrantRegions as I,buildSectorRegions as T,buildReticlePositionRegions as C,buildTestSiteRegions as x,buildRegionYieldData as S,areQuadrantsAdjacent as w,parseRegionKey as $,sectorCompassNames as M}from"./regions.js";import{classifyPattern as W}from"./patternClassification.js";import{computeFunctionalYield as Y}from"./analyzeWaferMap.js";import{resolveMetadataColumns as H,discoverDieMetadataKeys as P}from"./metadataColumns.js";import{openHtmlReport as z}from"./renderFindingsReport.js";import{renderSummaryReportHtml as L,renderLotSummaryReportHtml as A}from"./renderSummaryReport.js";import{DEFAULT_FACET_CURATION as F}from"./facets.js";const B="analyzeWaferMap returns the classification, with its geometry features, as stats.spatialPattern for every wafer, and a detected pattern as a finding (comparison.family 'spatial-pattern').",E="analyzeWaferMap returns the same figures as stats.functionalYield.",a="The die list resolves its metadata columns itself (the dieList.metadataColumns option).";export const buildRingRegions=e(D,"buildRingRegions",`Ring yield is stats.regionYield.ring on analyzeWaferMap's or analyzeWaferLot's result; ring findings are in findings. ${t}`),buildQuadrantRegions=e(I,"buildQuadrantRegions",`Quadrant yield is stats.regionYield.quadrant on analyzeWaferMap's or analyzeWaferLot's result; quadrant findings are in findings. ${t}`),buildSectorRegions=e(T,"buildSectorRegions",`Sector findings are in analyzeWaferMap's findings (comparison.family 'sector'). ${t}`),buildReticlePositionRegions=e(C,"buildReticlePositionRegions",`Reticle-position findings are in analyzeWaferMap's findings; a die's reticle cell is getReticleCell(die, reticleConfig). ${t}`),buildTestSiteRegions=e(x,"buildTestSiteRegions",`Test-site findings are in analyzeWaferMap's findings (comparison.family 'test-site'). ${t}`),buildRegionYieldData=e(S,"buildRegionYieldData",`analyzeWaferMap and analyzeWaferLot now return ring and quadrant yield as stats.regionYield. ${t}`),areQuadrantsAdjacent=e(w,"areQuadrantsAdjacent",`It is an internal helper of the quadrant findings, which analyzeWaferMap already reports. ${t}`),parseRegionKey=e($,"parseRegionKey",`Findings name their region in prose (comparison.left) and region yield in RegionYield.label; region keys are identities, not to be parsed. ${t}`),sectorCompassNames=e(M,"sectorCompassNames",`Sector findings name their sector in prose (comparison.left). ${t}`),classifyPattern=e(W,"classifyPattern",`${B} ${t}`),computeFunctionalYield=e(Y,"computeFunctionalYield",`${E} ${t}`),resolveMetadataColumns=e(H,"resolveMetadataColumns",`${a} ${t}`),discoverDieMetadataKeys=e(P,"discoverDieMetadataKeys",`${a} ${t}`),openHtmlReport=e(z,"openHtmlReport",`Open reports with openReportModal(html), or route them into your host with setReportOpener. ${t}`),renderSummaryReportHtml=e(L,"renderSummaryReportHtml",`Use renderWaferReportHtml(result, summary), which reads pass bins and ring count from the built map. ${t}`),renderLotSummaryReportHtml=e(A,"renderLotSummaryReportHtml",`Use renderLotReportHtml(results), which reads each wafer's pass bins and ring count from its built map. ${t}`),DEFAULT_FACET_CURATION=R(F,"DEFAULT_FACET_CURATION");import{renderFindingsReportHtml as v}from"./renderFindingsReport.js";export const renderFindingsReportHtml=e(v,"renderFindingsReportHtml",`Use renderWaferReportHtml(result, summary) or renderLotReportHtml(results): their Findings section is the same table, alongside the population and yield it was found in. ${t}`);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { TestDef, WaferWarning } from '../renderer/buildWaferMap.js';
|
|
2
2
|
/** Which field disagreed. `'limits'` is the soft tier; the rest are hard. */
|
|
3
|
-
export type TestDefConflictKind = 'name' | 'unit' | 'testType' | 'limits';
|
|
3
|
+
export type TestDefConflictKind = 'name' | 'unit' | 'testType' | 'limits' | 'specLimits';
|
|
4
4
|
/** One test number whose defs could not be reconciled across the population. */
|
|
5
5
|
export interface TestDefConflict {
|
|
6
6
|
testNumber: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const
|
|
1
|
+
const H=1e-6;function x(n,t){if(n===t)return!0;if(!Number.isFinite(n)||!Number.isFinite(t))return!1;const i=Math.max(Math.abs(n),Math.abs(t));return Math.abs(n-t)<=1e-6*i}function T(n){const t=n.name?.trim();return t||void 0}function I(n){const t=n.unit?.trim();return t||void 0}function S(n){return n.toLowerCase()}function h(n,t){const i=new Set,r=[];for(const l of n){const s=t(l);i.has(s)||(i.add(s),r.push(l))}return r}function v(n){const t=[];for(const i of n)t.some(r=>x(r,i))||t.push(i);return t}function N(n){return`${n.testNumber} (${n.values.join(" vs ")})`}function y(n,t=5){const i=n.slice(0,t).map(N).join(", "),r=n.length-Math.min(t,n.length);return r>0?`${i}, and ${r} more`:i}export function mergeTestDefs(n){const t=new Map,i=[];for(const s of n)for(const o of s?.testDefs??[]){const u=o.testNumber;let d=t.get(u);d||(d=[],t.set(u,d),i.push(u)),d.push(o)}const r=[],l=[];for(const s of i){const o=t.get(s),u=h(o.map(T).filter(e=>e!==void 0),S),d=h(o.map(I).filter(e=>e!==void 0),e=>e),g=h(o.map(e=>e.testType).filter(e=>e!==void 0),e=>e),$=u.length>1?{testNumber:s,kind:"name",excluded:!0,values:u}:d.length>1?{testNumber:s,kind:"unit",excluded:!0,values:d}:g.length>1?{testNumber:s,kind:"testType",excluded:!0,values:g}:void 0;if($){l.push($);continue}const f=v(o.map(e=>e.limitLow).filter(e=>e!==void 0)),a=v(o.map(e=>e.limitHigh).filter(e=>e!==void 0)),p=h(o.filter(e=>e.limitLow!==void 0).map(e=>e.limitLowInclusive!==!1),e=>String(e)),b=h(o.filter(e=>e.limitHigh!==void 0).map(e=>e.limitHighInclusive!==!1),e=>String(e)),c=f.length>1||a.length>1||p.length>1||b.length>1;c&&l.push({testNumber:s,kind:"limits",excluded:!1,values:f.length>1?f.map(e=>String(e)):a.length>1?a.map(e=>String(e)):p.length>1?p.map(e=>`${e?"\u2265":">"} ${f[0]}`):b.map(e=>`${e?"\u2264":"<"} ${a[0]}`)});const m=v(o.map(e=>e.specLow).filter(e=>e!==void 0)),w=v(o.map(e=>e.specHigh).filter(e=>e!==void 0)),L=m.length>1||w.length>1;L&&l.push({testNumber:s,kind:"specLimits",excluded:!1,values:(m.length>1?m:w).map(e=>String(e))}),r.push({testNumber:s,name:u[0]??"",...d[0]!==void 0?{unit:d[0]}:{},...g[0]!==void 0?{testType:g[0]}:{},...o.find(e=>e.logScale!==void 0)?{logScale:o.find(e=>e.logScale!==void 0).logScale}:{},...!c&&f[0]!==void 0?{limitLow:f[0]}:{},...!c&&a[0]!==void 0?{limitHigh:a[0]}:{},...!c&&p[0]===!1?{limitLowInclusive:!1}:{},...!c&&b[0]===!1?{limitHighInclusive:!1}:{},...!L&&m[0]!==void 0?{specLow:m[0]}:{},...!L&&w[0]!==void 0?{specHigh:w[0]}:{},...o.some(e=>e.derived)?{derived:!0}:{},...o.find(e=>e.expression!==void 0)?{expression:o.find(e=>e.expression!==void 0).expression}:{}})}return{defs:r,conflicts:l,warnings:k(l)}}function k(n){const t=[],i=n.filter(s=>s.excluded),r=n.filter(s=>s.kind==="limits"),l=n.filter(s=>s.kind==="specLimits");return i.length>0&&t.push({code:"test-def-collision",severity:"error",message:`${i.length} test ${i.length===1?"number describes":"numbers describe"} a different measurement in different wafers, so the same number cannot identify one test across this population. Affected: `+y(i)+". These tests are withheld from every cross-wafer chart, report and map mode \u2014 pooling them would mix unrelated measurements into one distribution. Each wafer's own view is unaffected. Load one test program at a time to see them."}),r.length>0&&t.push({code:"test-limit-conflict",severity:"warning",message:`${r.length} ${r.length===1?"test is":"tests are"} held to different test limits in different wafers. Affected: `+y(r)+". The measured values are still comparable, so distributions include them, but the limit yield, the limit lines, and capability measured against the test limits are withheld for these tests \u2014 there is no single pair of limits to judge the merged population against."}),l.length>0&&t.push({code:"test-limit-conflict",severity:"warning",message:`${l.length} ${l.length===1?"test has":"tests have"} different spec limits in different wafers. Affected: `+y(l)+". Their spec limits are withheld for the merged population, so capability uses the test limits where they agree."}),t}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{waferDisplayLabel as H}from"../core/waferLabel.js";import{binPassSets as we,binPassSetsByWafer as $e,mergeBinDefs as I}from"../renderer/binColors.js";import{itemPassBins as R,passBinsLabel as xe}from"../core/passBins.js";import{isParametricTest as De}from"../renderer/buildWaferMap.js";import{buildRingRegions as j,buildQuadrantRegions as z,buildRegionYieldData as Be}from"./regions.js";import{openHtmlReport as ve}from"./renderFindingsReport.js";import{analyzeWaferLot as Te}from"./analyzeWaferLot.js";import{computeFunctionalYield as Pe,analyzeWaferMap as Fe}from"./analyzeWaferMap.js";import{mergeTestDefs as Ye}from"./mergeTestDefs.js";import{sortBinsForDisplay as A}from"./binPareto.js";import{buildFacetTable as We,facetValueOf as Me,FACET_NONE_VALUE as Ce}from"./facets.js";import{visibleFindings as O}from"./filterFindings.js";import{buildYieldDataCombined as Re}from"./yield.js";import{describeWaferPopulation as G,populationLabel as Ne}from"./population.js";import{buildTestPassRateData as Ee,hasJudgeableTests as
|
|
2
|
-
`)),u?Q(n,
|
|
1
|
+
import{waferDisplayLabel as H}from"../core/waferLabel.js";import{binPassSets as we,binPassSetsByWafer as $e,mergeBinDefs as I}from"../renderer/binColors.js";import{itemPassBins as R,passBinsLabel as xe}from"../core/passBins.js";import{isParametricTest as De}from"../renderer/buildWaferMap.js";import{buildRingRegions as j,buildQuadrantRegions as z,buildRegionYieldData as Be}from"./regions.js";import{openHtmlReport as ve}from"./renderFindingsReport.js";import{analyzeWaferLot as Te}from"./analyzeWaferLot.js";import{computeFunctionalYield as Pe,analyzeWaferMap as Fe}from"./analyzeWaferMap.js";import{mergeTestDefs as Ye}from"./mergeTestDefs.js";import{sortBinsForDisplay as A}from"./binPareto.js";import{buildFacetTable as We,facetValueOf as Me,FACET_NONE_VALUE as Ce}from"./facets.js";import{visibleFindings as O}from"./filterFindings.js";import{buildYieldDataCombined as Re}from"./yield.js";import{describeWaferPopulation as G,populationLabel as Ne}from"./population.js";import{buildTestPassRateData as Ee,hasJudgeableTests as Le,poolFunctionalYield as ke}from"./testPassRate.js";import{buildCapabilityData as He}from"./capability.js";import{fmt as T}from"../renderer/fmt.js";import{getDieKey as Ie,isPositionedDie as je,diePassStatus as U}from"../core/dies.js";import{findingsTableHtml as V,renderMetadataSection as _,renderMetricGrid as K,renderSection as x,renderTable as v,reportStyles as N}from"./reportHtml.js";import{escHtml as y}from"../core/utils.js";function C(t,o){return o===0?"\u2014":`${(t/o*100).toFixed(1)}%`}function ze(t){if(!t)return"";const o=t.lot??t.lotId,a=t.wafer??t.waferId,n=[o,a].filter(Boolean).map(String);return n.length?` \u2014 ${n.map(y).join(" \xB7 ")}`:""}function Q(t,o,a,n,r=[1]){const e=new Map;if(n)for(const[s,c]of Object.entries(n))e.set(Number(s),c);else for(const s of t){if(s.partial||s.edgeExcluded)continue;const c=a==="hard"?s.hbin:s.sbin;c!=null&&e.set(c,(e.get(c)??0)+1)}if(!e.size)return"";const i=[...e.values()].reduce((s,c)=>s+c,0),l=o?new Map(o.map(s=>[s.bin,s])):null,d=A(e.entries(),we(t,r)[a]).map(([s,c])=>{const p=l?.get(s)?.name??"\u2014";return[String(s),p,String(c),C(c,i)]});return x(a==="hard"?"Hard Bin Breakdown":"Soft Bin Breakdown",v(["Bin","Name","Count","%"],d,{className:"compact"}))}function q(t,o,a,n){const r=new Set(n),e=new Map(a.map(d=>[Ie(d),d]));if(!a.some(d=>d.hbin!=null||d.sbin!=null)||!o.length)return"";const l=[];for(const d of o){let f=0,s=0;for(const c of d.dieKeys){const h=e.get(c);if(!h||h.partial||h.edgeExcluded)continue;const p=U(h,r);p!==void 0&&(s++,p&&f++)}s&&l.push([d.label,String(f),String(s),C(f,s)])}return l.length?x(t,v(["Region","Pass","Total","Yield"],l,{className:"compact"})):""}function J(t,o,a){if(!o.length)return"";const n=t.filter(i=>!i.partial&&!i.edgeExcluded),r=new Map((a??[]).map(i=>[i.testNumber,i])),e=[];for(const i of o.filter(De)){const l=i.testNumber,d=i.unit||void 0,f=r.get(l);let s,c,h;if(f)({min:s,max:c,mean:h}=f);else{const p=n.map(w=>w.testValues?.[l]).filter(w=>w!==void 0&&isFinite(w));if(!p.length)continue;p.sort((w,b)=>w-b),s=p[0],c=p[p.length-1],h=p.reduce((w,b)=>w+b,0)/p.length}e.push([y(i.name),T(s,d),T(h,d),T(c,d)])}return e.length?x("Test Values",v(["Test","Min","Mean","Max"],e,{className:"compact"})):""}function X(t,o,a){const n=a??Pe(t,o);if(!n?.length)return"";const r=n.map(e=>[y(e.label),String(e.totalDies),String(e.passDies),String(e.failDies),e.passRatePercent!==null?`${e.passRatePercent.toFixed(1)}%`:"\u2014"]);return x("Functional Tests",v(["Test","N","Pass","Fail","Pass Rate"],r,{className:"compact"}))}function Z(t,o){const a=He(t,o);if(!a.length)return"";const n=e=>e===null?"\u2014":e.toFixed(2),r=a.map(e=>[y(e.label),e.hasSpec?e.limitBasis==="spec"?`LSL ${T(e.lsl,e.unit)} \u2013 USL ${T(e.usl,e.unit)}`:`Lo limit ${T(e.lsl,e.unit)} \u2013 Hi limit ${T(e.usl,e.unit)}`:"\u2014",T(e.mean,e.unit),n(e.cp),n(e.cpk),n(e.pp),n(e.ppk)]);return x("Process Capability",v(["Test","Limits","Mean","Cp","Cpk","Pp","Ppk"],r,{className:"compact"}))}function ee(t,o){const a=O(t);return a.length?x("Findings",V(a,o)):""}export function renderSummaryReportHtml(t,o={}){const{wafer:a,dies:n,yieldSummary:r,dataCoverage:e,hbinDefs:i,sbinDefs:l,testDefs:d=[],statsSummary:f,passBins:s=[1],ringCount:c=4}=t,h=a.metadata,p=ze(h),w=o.title??`Wafer Summary${p}`,b=new Date().toLocaleString(),u=n.some(W=>W.hbin!=null),g=n.some(W=>W.sbin!=null),S=n.filter(je),D=j(S,a,c),$=z(S,a,c),B=[{label:"Total dies",value:String(r.totalDies)},{label:"Filled dies",value:String(e.filledDies),hint:`${C(e.filledDies,e.totalDies)} fill`},{label:"Pass dies",value:String(r.passDies)},{label:"Fail dies",value:String(r.failDies)},...r.partialDies>0?[{label:"Partial",value:String(r.partialDies)}]:[],...r.yieldPercent!==null?[{label:`Yield (pass: ${xe([s])})`,value:`${r.yieldPercent.toFixed(1)}%`}]:[],...r.edgeExcludedDies>0?[{label:"Edge excluded (outer zone)",value:String(r.edgeExcludedDies)}]:[]],Y=[_([{metadata:a.metadata}]),x("Summary",[K(B)].filter(Boolean).join(`
|
|
2
|
+
`)),u?Q(n,i,"hard",f?.stats.hardBinCounts,s):g?Q(n,l,"soft",f?.stats.softBinCounts,s):"",q("Ring Yield",D,n,s),q("Quadrant Yield",$,n,s),J(n,d,f?.stats.perTestStats),X(n,d,f?.stats.functionalYield),Z([{dies:n}],d),f?ee(f.findings):""].filter(Boolean).join(`
|
|
3
3
|
`);return`<!DOCTYPE html>
|
|
4
4
|
<html lang="en">
|
|
5
5
|
<head>
|
|
@@ -19,18 +19,18 @@ ${N()}
|
|
|
19
19
|
<p class="footer">Generated ${y(b)}</p>
|
|
20
20
|
</main>
|
|
21
21
|
</body>
|
|
22
|
-
</html>`}function Ae(t,
|
|
23
|
-
`))}const Ge=3;function Ue(t,
|
|
24
|
-
`)}`)}if(!
|
|
25
|
-
`)+l)}function te(t,
|
|
26
|
-
`);return{lotSummary:l,sections:Se}}function ae(t,
|
|
22
|
+
</html>`}function Ae(t,o){const a=t.perWafer.map(n=>{const r=H(o[n.waferIndex],n.waferIndex),e=n.summary.stats.yieldPercent;return[r,e!==null?`${e.toFixed(1)}%`:"N/A"]});return v(["Wafer","Yield"],a,{className:"compact"})}function Oe(t){const o=[];for(const e of t.perWafer){const i=O(e.summary.findings);if(!i.length)continue;const l=e.summary.wafer?.waferId??`Wafer ${e.waferIndex+1}`;o.push(`<h3 class="report-subheading">${y(l)}</h3>`+V(i))}if(!o.length)return"";const a=o.length,n=t.perWafer.length,r=`<p class="report-note">${a} of ${n} wafer${n===1?"":"s"} have per-wafer findings; the rest had none.</p>`;return x("Findings by Wafer",r+o.join(`
|
|
23
|
+
`))}const Ge=3;function Ue(t,o,a){const n=t.map(d=>({metadata:d.wafer?.metadata})),r=We(n,{facetableOnly:!0}).filter(d=>d.splittable);if(!r.length)return"";const e=r.slice(0,Ge),i=[];for(const d of e){const f=new Map;t.forEach((u,g)=>{const S=Me(u.wafer?.metadata,d.key)??Ce;(f.get(S)??f.set(S,[]).get(S)).push(g)});const s=[...f.entries()].map(([u,g])=>({key:u,items:g.map(S=>t[S])}));if(s.length<2)continue;const c=s.map(u=>[y(u.key),y(u.items.map(g=>g.label).join(", "))]),h=Re(s.map(u=>({key:u.key,items:u.items.map(g=>({label:g.label,dies:g.dies,passBins:R(g,a)}))})),a).map(u=>[y(u.label),String(u.itemCount),`${u.percent.toFixed(1)}%`]),p=[v([d.label,"Wafers"],c,{className:"compact"}),v([d.label,"Wafers","Yield"],h,{className:"compact"})],w=t.flatMap(u=>u.dies??[]);let b=0;for(const u of["spec","testFlag","functional"]){if(!Le(o,u,w))continue;const g=Ee(s.map($=>({key:$.key,items:$.items.map(B=>({dies:B.dies,testSpecYield:B.statsSummary?.stats.testSpecYield,functionalYield:B.statsSummary?.stats.functionalYield}))})),o,u);if(!g.rows.length)continue;const S=g.rows.map($=>[y($.label),...$.byGroup.map(B=>B.passRatePercent===null?"\u2014":`${B.passRatePercent.toFixed(1)}%`),$.overall.passRatePercent===null?"\u2014":`${$.overall.passRatePercent.toFixed(1)}%`]),D=u==="spec"?"Pass rate \xB7 test limits":u==="testFlag"?"Pass rate \xB7 tester flag":"Pass rate \xB7 functional";g.disagreementDies&&(b=g.disagreementDies),p.push(`<h4 class="report-subheading">${y(D)}</h4>`+v(["Test",...g.groups,"All"],S,{className:"compact"}))}b&&p.push(`<p class="report-note">${b.toLocaleString()} dies judged differently by limits and tester flag.</p>`),i.push(`<h3 class="report-subheading">${y(d.label)}</h3>${p.join(`
|
|
24
|
+
`)}`)}if(!i.length)return"";const l=r.length>e.length?`<p class="report-note">${r.length-e.length} further splittable field(s) not shown.</p>`:"";return x("Split Comparison",i.join(`
|
|
25
|
+
`)+l)}function te(t,o,a,n,r){const e=new Map,i=a==="hard"?"hardBinCounts":"softBinCounts";if(n?.length&&n.every(s=>s.stats[i]!==void 0))for(const s of n)for(const[c,h]of Object.entries(s.stats[i])){const p=Number(c);e.set(p,(e.get(p)??0)+h)}else for(const s of t){if(s.partial||s.edgeExcluded)continue;const c=a==="hard"?s.hbin:s.sbin;c!=null&&e.set(c,(e.get(c)??0)+1)}if(!e.size)return"";const l=[...e.values()].reduce((s,c)=>s+c,0),d=o?new Map(o.map(s=>[s.bin,s])):null,f=A(e.entries(),$e(r)[a]).map(([s,c])=>{const h=d?.get(s);return[h?.name?`Bin ${s} \xB7 ${h.name} (${c})`:`Bin ${s} (${c})`,C(c,l)]});return x(a==="hard"?"Hard Bin Breakdown (All Wafers)":"Soft Bin Breakdown (All Wafers)",v(["Bin","Yield"],f,{className:"compact"}))}function ne(t,o,a,n,r,e){const i=Be(a,n,r,l=>e[l],o).map(l=>[l.label,`${l.yieldPercent.toFixed(1)}%`]);return i.length?x(t,v(["Region","Yield"],i,{className:"compact"})):""}function Ve(t,o,a){let n;if(a?.length&&a.every(r=>r.stats.perTestStats!==void 0)){const r=new Map;for(const e of a)for(const i of e.stats.perTestStats??[]){const l=r.get(i.testNumber);l?(l.n+=i.count,l.sum+=i.mean*i.count,l.min=Math.min(l.min,i.min),l.max=Math.max(l.max,i.max)):r.set(i.testNumber,{n:i.count,sum:i.mean*i.count,min:i.min,max:i.max})}n=[...r.entries()].map(([e,i])=>({testNumber:e,min:i.min,max:i.max,mean:i.sum/i.n}))}return J(t,o,n)}function _e(t,o,a){const n=ke(a);return X(t,o,n)}const E=["lot","product","testProgram","temperature"];function Ke(t){const o=new Map(E.map(e=>[e,new Set]));for(const e of t)for(const i of E){const l=e.wafer?.metadata?.[i];l!=null&&l!==""&&o.get(i).add(String(l))}const a=E.filter(e=>o.get(e).size>1);if(a.length===0)return[{label:"",items:t,varying:a}];const n=new Map,r=[];for(const e of t){const i=a.map(l=>String(e.wafer?.metadata?.[l]??"(none)")).join(" \xB7 ");n.has(i)||(n.set(i,[]),r.push(i)),n.get(i).push(e)}return r.map(e=>({label:e,items:n.get(e),varying:a}))}function se(t,o,a,n,r,e,i){const l=Te(t.map(m=>m.source??m),{perWaferSummaries:t.map(m=>m.statsSummary),...i}),d=[],f=[],s=[],c=[];for(const m of t){const F=m.dies??[];m.wafer&&(d.push(m.wafer),s.push(F),c.push(R(m,r)));for(const P of F)f.push(P)}const h=t.map(m=>({dies:m.dies??[],passBins:R(m,r)})),p=f.some(m=>m.hbin!=null),w=f.some(m=>m.sbin!=null),b=p||w,u=l.perWafer.map(m=>m.summary.stats.yieldPercent).filter(m=>m!==null),g=u.length?u.reduce((m,F)=>m+F,0)/u.length:null;let S=0,D=0,$=0,B=0,Y=0;for(const m of h){const F=new Set(m.passBins);for(const P of m.dies){if(S++,P.edgeExcluded&&B++,P.partial&&Y++,P.partial||P.edgeExcluded)continue;const k=U(P,F);k!==void 0&&(D++,k&&$++)}}const W=D-$,L=D>0?$/D*100:null,ie=[{label:"Wafers",value:String(l.stats.waferCount)},{label:"Total dies",value:String(S)},...b?[{label:"Good dies",value:String($)},{label:"Bad dies",value:String(W)}]:[],...B>0?[{label:"Edge excluded",value:String(B)}]:[],...Y>0?[{label:"Partial dies",value:String(Y)}]:[],...g!==null?[{label:"Mean wafer yield",value:`${g.toFixed(1)}%`}]:[],...L!==null?[{label:"Total yield",value:`${L.toFixed(1)}%`}]:[]],oe=G(l.perWafer.map(m=>m.summary.wafer)),re=x(oe.lotId!==void 0?"Lot Summary":"Summary",K(ie)),le=_(t.map(m=>({metadata:m.wafer?.metadata}))),ce=x("Per-Wafer Yield",Ae(l,t)),de=Ue(t,n,r),M=l.perWafer.map(m=>m.summary),ue=b?p?te(f,o,"hard",M,h):te(f,a,"soft",M,h):"",fe=b&&d.length?ne("Ring Yield (All Wafers)",j,s,d,e,c):"",me=b&&d.length?ne("Quadrant Yield (All Wafers)",z,s,d,e,c):"",pe=n.length?Ve(f,n,M):"",ge=n.length?_e(f,n,M):"",he=n.length?Z(t,n):"",be=l.findings.length?ee(l.findings,l.stats.waferCount):"",ye=Oe(l),Se=[re,le,ce,de,ue,fe,me,pe,ge,he,be,ye].filter(Boolean).join(`
|
|
26
|
+
`);return{lotSummary:l,sections:Se}}function ae(t,o,a){return`<main class="report">
|
|
27
27
|
<header class="report-header">
|
|
28
28
|
<h1>${y(t)}</h1>
|
|
29
29
|
<p class="report-subtitle">Generated ${y(a)}</p>
|
|
30
30
|
</header>
|
|
31
|
-
${
|
|
31
|
+
${o}
|
|
32
32
|
<p class="footer">Generated ${y(a)}</p>
|
|
33
|
-
</main>`}export function renderLotSummaryReportHtml(t,
|
|
33
|
+
</main>`}export function renderLotSummaryReportHtml(t,o={}){const{items:a,hbinDefs:n,sbinDefs:r,testDefs:e=[],passBins:i=[1],ringCount:l=4,analyzeOptions:d}=t,f=new Date().toLocaleString(),s=Ke(a);if(s.length===1){const{lotSummary:b,sections:u}=se(s[0].items,n,r,e,i,l,d),g=G(b.perWafer.map(D=>D.summary.wafer)),S=o.title??(g.lotId!==void 0?`Lot Summary \u2014 ${g.lotId}`:`Summary \u2014 ${Ne(g)}`);return`<!DOCTYPE html>
|
|
34
34
|
<html lang="en">
|
|
35
35
|
<head>
|
|
36
36
|
<meta charset="UTF-8">
|
|
@@ -42,7 +42,7 @@ ${N()}
|
|
|
42
42
|
<body>
|
|
43
43
|
${ae(S,u,f)}
|
|
44
44
|
</body>
|
|
45
|
-
</html>`}const c=
|
|
45
|
+
</html>`}const c=o.title??"Summary",h=s.map(b=>{const{lotSummary:u,sections:g}=se(b.items,n,r,e,i,l,d),S=b.varying.includes("lot")?void 0:u.lot?.lot??u.lot?.lotId,D=S?`${b.label} \xB7 Lot ${String(S)}`:b.label;return ae(`${c} \u2014 ${D}`,g,f)}),p=`<p style="max-width:1080px;margin:0 auto 8px;padding:8px 12px;background:var(--report-surface);border:1px solid var(--report-line);border-radius:4px;font-size:12px;color:var(--report-muted);">This load spans ${s.length} groups by identity \u2014 shown separately below so stats are never pooled across them.</p>`,w=h.map((b,u)=>`${u===0?"":'<hr style="max-width:1080px;margin:32px auto 0;border:none;border-top:3px solid var(--report-line-strong);">'}
|
|
46
46
|
${b}`).join(`
|
|
47
47
|
`);return`<!DOCTYPE html>
|
|
48
48
|
<html lang="en">
|
|
@@ -59,4 +59,4 @@ ${p}
|
|
|
59
59
|
</div>
|
|
60
60
|
${w}
|
|
61
61
|
</body>
|
|
62
|
-
</html>`}export{ve as openHtmlReport};export function renderWaferReportHtml(t,
|
|
62
|
+
</html>`}export{ve as openHtmlReport};export function renderWaferReportHtml(t,o,a={}){return renderSummaryReportHtml({wafer:t.wafer,dies:t.dies,yieldSummary:t.yield,dataCoverage:t.dataCoverage,hbinDefs:t.hbinDefs,sbinDefs:t.sbinDefs,testDefs:t.testDefs,statsSummary:o??t.statsSummary??("view"in t?Fe(t):void 0),passBins:[...t.passBins],ringCount:t.ringCount},a)}export function renderLotReportHtml(t,o={}){const a=I(t.map(e=>e.hbinDefs)),n=I(t.map(e=>e.sbinDefs)),r=t.some(e=>e.testDefs?.length)?Ye([...t]).defs:void 0;return renderLotSummaryReportHtml({items:t.map((e,i)=>({label:H(e,i),wafer:e.wafer,dies:e.dies,passBins:[...e.passBins],statsSummary:e.statsSummary,source:"view"in e?e:void 0})),hbinDefs:a.length?a:void 0,sbinDefs:n.length?n:void 0,testDefs:r,ringCount:t[0]?.ringCount,analyzeOptions:o.analyzeOptions},{title:o.title})}
|
|
@@ -165,6 +165,14 @@ export interface SweepData {
|
|
|
165
165
|
*/
|
|
166
166
|
notMeasured?: string;
|
|
167
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* Whether `spec` names at least one parametric test in `testDefs` — i.e.
|
|
170
|
+
* whether it can draw anything for data with these tests. False means none of
|
|
171
|
+
* its tests exist here: typically a sweep from another test program, still
|
|
172
|
+
* defined after a different lot was loaded. Internal: the Sweeps tab and the
|
|
173
|
+
* sweep card say so themselves.
|
|
174
|
+
*/
|
|
175
|
+
export declare function sweepAppliesTo(spec: SweepSpec, testDefs: readonly TestDef[] | undefined): boolean;
|
|
168
176
|
/**
|
|
169
177
|
* Build the plottable data for one sweep over a die population.
|
|
170
178
|
*
|