@wafertools/wafermap 0.33.0 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +6 -6
- package/CHANGELOG.md +212 -0
- package/README.md +5 -5
- package/dist/packages/canvas-adapter/chartPopulation.d.ts +15 -10
- package/dist/packages/canvas-adapter/chartPopulation.js +1 -1
- package/dist/packages/canvas-adapter/charts/barPanel.d.ts +7 -0
- package/dist/packages/canvas-adapter/charts/barPanel.js +1 -1
- package/dist/packages/canvas-adapter/charts/binCluster.d.ts +7 -1
- package/dist/packages/canvas-adapter/charts/binCluster.js +1 -1
- package/dist/packages/canvas-adapter/charts/boxplot.js +1 -1
- package/dist/packages/canvas-adapter/charts/capability.js +1 -1
- package/dist/packages/canvas-adapter/charts/chartShell.d.ts +55 -3
- package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
- package/dist/packages/canvas-adapter/charts/correlation.js +1 -2
- package/dist/packages/canvas-adapter/charts/groupedBarPlot.d.ts +2 -0
- package/dist/packages/canvas-adapter/charts/groupedBarPlot.js +1 -1
- package/dist/packages/canvas-adapter/charts/histogram.d.ts +16 -0
- package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
- package/dist/packages/canvas-adapter/charts/palette.d.ts +2 -0
- package/dist/packages/canvas-adapter/charts/palette.js +1 -1
- package/dist/packages/canvas-adapter/charts/plotChart.d.ts +27 -0
- package/dist/packages/canvas-adapter/charts/plotChart.js +1 -0
- package/dist/packages/canvas-adapter/charts/pointInteractions.d.ts +45 -0
- package/dist/packages/canvas-adapter/charts/pointInteractions.js +1 -0
- package/dist/packages/canvas-adapter/charts/regionYieldDiagram.d.ts +2 -0
- package/dist/packages/canvas-adapter/charts/regionYieldDiagram.js +1 -1
- package/dist/packages/canvas-adapter/charts/scatter.d.ts +19 -0
- package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
- package/dist/packages/canvas-adapter/charts/sweep.d.ts +12 -0
- package/dist/packages/canvas-adapter/charts/sweep.js +1 -1
- package/dist/packages/canvas-adapter/charts/testPassRate.d.ts +7 -0
- package/dist/packages/canvas-adapter/charts/testPassRate.js +1 -1
- package/dist/packages/canvas-adapter/charts/trend.d.ts +11 -0
- package/dist/packages/canvas-adapter/charts/trend.js +1 -1
- package/dist/packages/canvas-adapter/confirmDialog.d.ts +13 -0
- package/dist/packages/canvas-adapter/confirmDialog.js +1 -0
- package/dist/packages/canvas-adapter/dataTab.d.ts +131 -0
- package/dist/packages/canvas-adapter/dataTab.js +1 -0
- package/dist/packages/canvas-adapter/dieList.d.ts +39 -4
- package/dist/packages/canvas-adapter/dieList.js +6 -7
- package/dist/packages/canvas-adapter/drilldown.d.ts +20 -3
- package/dist/packages/canvas-adapter/drilldown.js +1 -1
- package/dist/packages/canvas-adapter/guideWindow.d.ts +96 -0
- package/dist/packages/canvas-adapter/guideWindow.js +1 -0
- package/dist/packages/canvas-adapter/identityHeader.d.ts +16 -0
- package/dist/packages/canvas-adapter/identityHeader.js +1 -1
- package/dist/packages/canvas-adapter/index.d.ts +3 -3
- package/dist/packages/canvas-adapter/index.js +1 -1
- package/dist/packages/canvas-adapter/insightsTab.d.ts +73 -54
- package/dist/packages/canvas-adapter/insightsTab.js +1 -1
- package/dist/packages/canvas-adapter/maplessSummary.d.ts +2 -2
- package/dist/packages/canvas-adapter/plotEditor.d.ts +29 -0
- package/dist/packages/canvas-adapter/plotEditor.js +1 -0
- package/dist/packages/canvas-adapter/plotItems.d.ts +32 -0
- package/dist/packages/canvas-adapter/plotItems.js +1 -0
- package/dist/packages/canvas-adapter/plotModal.d.ts +30 -0
- package/dist/packages/canvas-adapter/plotModal.js +1 -0
- package/dist/packages/canvas-adapter/plotStore.d.ts +52 -0
- package/dist/packages/canvas-adapter/plotStore.js +1 -0
- package/dist/packages/canvas-adapter/plotTab.d.ts +30 -0
- package/dist/packages/canvas-adapter/plotTab.js +1 -0
- package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +3 -2
- package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferMap.d.ts +21 -2
- package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
- package/dist/packages/canvas-adapter/summaryPanel.d.ts +27 -8
- package/dist/packages/canvas-adapter/summaryPanel.js +2 -4
- package/dist/packages/canvas-adapter/summaryRail.d.ts +7 -0
- package/dist/packages/canvas-adapter/summaryRail.js +1 -0
- package/dist/packages/canvas-adapter/sweepEditor.d.ts +17 -0
- package/dist/packages/canvas-adapter/sweepEditor.js +1 -0
- package/dist/packages/canvas-adapter/tableExport.d.ts +22 -0
- package/dist/packages/canvas-adapter/tableExport.js +3 -0
- package/dist/packages/canvas-adapter/toolbar.d.ts +25 -97
- package/dist/packages/canvas-adapter/toolbar.js +2 -2
- package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
- package/dist/packages/canvas-adapter/userGuideHtml.js +337 -42
- package/dist/packages/canvas-adapter/version.d.ts +2 -2
- package/dist/packages/canvas-adapter/version.js +1 -1
- package/dist/packages/canvas-adapter/viewSwitch.d.ts +13 -0
- package/dist/packages/canvas-adapter/viewSwitch.js +1 -0
- package/dist/packages/canvas-adapter/virtualTable.d.ts +44 -0
- package/dist/packages/canvas-adapter/virtualTable.js +1 -0
- package/dist/packages/core/classify.d.ts +3 -2
- package/dist/packages/core/classify.js +1 -1
- package/dist/packages/core/passBins.d.ts +17 -4
- package/dist/packages/core/passBins.js +1 -1
- package/dist/packages/core/ringCount.d.ts +22 -0
- package/dist/packages/core/ringCount.js +1 -0
- package/dist/packages/core/tableCsv.d.ts +43 -0
- package/dist/packages/core/tableCsv.js +3 -0
- package/dist/packages/renderer/binColors.d.ts +6 -6
- package/dist/packages/renderer/binColors.js +1 -1
- package/dist/packages/renderer/buildView.d.ts +1 -1
- package/dist/packages/renderer/buildView.js +1 -1
- package/dist/packages/renderer/buildWaferMap.js +1 -1
- package/dist/packages/stats/analyzeWaferLot.js +1 -1
- package/dist/packages/stats/analyzeWaferMap.js +1 -1
- package/dist/packages/stats/binPareto.d.ts +1 -1
- package/dist/packages/stats/binPareto.js +1 -1
- package/dist/packages/stats/histogram.d.ts +27 -0
- package/dist/packages/stats/histogram.js +1 -1
- package/dist/packages/stats/index.d.ts +2 -0
- package/dist/packages/stats/index.js +1 -1
- package/dist/packages/stats/lotDrift.js +1 -1
- package/dist/packages/stats/lotPattern.js +1 -1
- package/dist/packages/stats/math.d.ts +9 -6
- package/dist/packages/stats/math.js +1 -1
- package/dist/packages/stats/patternClassification.d.ts +1 -1
- package/dist/packages/stats/patternClassification.js +1 -1
- package/dist/packages/stats/plotData.d.ts +258 -0
- package/dist/packages/stats/plotData.js +1 -0
- package/dist/packages/stats/plotId.d.ts +3 -0
- package/dist/packages/stats/plotId.js +1 -0
- package/dist/packages/stats/plotSpec.d.ts +119 -0
- package/dist/packages/stats/plotSpec.js +4 -0
- package/dist/packages/stats/presentation.d.ts +2 -0
- package/dist/packages/stats/presentation.js +1 -1
- package/dist/packages/stats/regions.d.ts +6 -0
- package/dist/packages/stats/regions.js +1 -1
- package/dist/packages/stats/renderSummaryReport.d.ts +8 -3
- package/dist/packages/stats/renderSummaryReport.js +15 -15
- package/dist/packages/stats/reportHtml.d.ts +1 -1
- package/dist/packages/stats/reportHtml.js +36 -29
- package/dist/packages/stats/scatter.d.ts +6 -0
- package/dist/packages/stats/scatter.js +1 -1
- package/dist/packages/stats/sweep.d.ts +37 -1
- package/dist/packages/stats/sweep.js +1 -1
- package/dist/packages/stats/sweepText.d.ts +14 -0
- package/dist/packages/stats/sweepText.js +1 -0
- package/dist/packages/stats/sweepXFromName.d.ts +14 -0
- package/dist/packages/stats/sweepXFromName.js +1 -1
- package/dist/packages/stats/synthesis.d.ts +14 -4
- package/dist/packages/stats/synthesis.js +1 -1
- package/dist/packages/stats/testPassRate.d.ts +6 -0
- package/dist/packages/stats/testPassRate.js +1 -1
- package/dist/packages/stats/trend.js +1 -1
- package/dist/packages/stats/types.d.ts +1 -1
- package/dist/packages/stats/yield.d.ts +15 -6
- package/dist/packages/stats/yield.js +1 -1
- package/llms.txt +15 -3
- package/package.json +3 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import{waferDisplayLabel as
|
|
2
|
-
`)}function
|
|
3
|
-
`)),m?
|
|
1
|
+
import{waferDisplayLabel as _}from"../core/waferLabel.js";import{binPassSets as Re,binPassSetsByWafer as We,mergeBinDefs as Q,resolveBinColors as Ne,resolveBinColorsByWafer as Ie}from"../renderer/binColors.js";import{commonPassBins as Me,itemPassBins as M,passBinsLabel as Le}from"../core/passBins.js";import{itemRingCount as Ee}from"../core/ringCount.js";import{isParametricTest as ke,isBuiltMap as V}from"../renderer/buildWaferMap.js";import{buildRingRegions as U,buildQuadrantRegions as K,buildRegionYieldData as He}from"./regions.js";import{openHtmlReport as je}from"./renderFindingsReport.js";import{analyzeWaferLot as Ae}from"./analyzeWaferLot.js";import{computeFunctionalYield as qe,analyzeWaferMap as ze}from"./analyzeWaferMap.js";import{mergeTestDefs as Ge}from"./mergeTestDefs.js";import{buildFacetTable as Oe,facetValueOf as _e,FACET_NONE_VALUE as Qe}from"./facets.js";import{visibleFindings as L}from"./filterFindings.js";import{buildYieldDataCombined as Ve}from"./yield.js";import{describeWaferPopulation as J,populationLabel as Ue}from"./population.js";import{buildTestPassRateData as Ke,hasJudgeableTests as Je,poolFunctionalYield as Xe}from"./testPassRate.js";import{buildCapabilityData as Ze}from"./capability.js";import{buildSynthesis as et}from"./synthesis.js";import{binBreakdownRows as tt,binBreakdownTitle as nt,binCountsFrom as st,pooledBinCounts as at,totalOf as it}from"./binRows.js";import{MEAN_WAFER_YIELD_LABEL as ot,shortfallLegend as E}from"./presentation.js";import{regionYieldRows as X,waferYieldRows as rt}from"./yieldRows.js";import{describeValues as lt}from"./math.js";import{fmt as C}from"../renderer/fmt.js";import{getDieKey as ct,isPositionedDie as dt,diePassStatus as Z}from"../core/dies.js";import{testValue as ut}from"../core/dieTable.js";import{findingsTableHtml as ee,synthesisSectionHtml as ft,LIVE_FINDINGS_SCRIPT as k,barCell as R,deltaCell as H,withSectionNav as te,renderMetadataSection as ne,renderMetricGrid as se,renderSection as D,renderTable as P,reportStyles as j}from"./reportHtml.js";import{escHtml as S}from"../core/utils.js";function ae(t,a){return a===0?"\u2014":`${(t/a*100).toFixed(1)}%`}function mt(t){if(!t)return"";const a=t.lot??t.lotId,i=t.wafer??t.waferId,n=[a,i].filter(Boolean).map(String);return n.length?` \u2014 ${n.map(S).join(" \xB7 ")}`:""}function ie(t,a,i,n,r){if(!t.size)return"";const e=tt(t,a,n).map(c=>{const s=c.passing?"muted-row":"",d=m=>({html:S(m),className:s});return[d(String(c.bin)),d(c.name??"\u2014"),d(String(c.count)),R(c.percent,`${c.percent.toFixed(1)}%`,s,r?.get(c.bin))]});return D(nt(i,it(t)),P(["Bin","Name","Dies","Share"],e,{className:"compact"}))}function pt(t,a,i,n,r,e){const c=Re(t,r);return["hard","soft"].map(s=>ie(st(t,s,s==="hard"?n.hard:n.soft),s==="hard"?a:i,s,c[s],e[s])).join(`
|
|
2
|
+
`)}function oe(t,a,i,n){const r=new Set(n),e=new Map(i.map(o=>[ct(o),o]));if(!i.some(o=>o.hbin!=null||o.sbin!=null)||!a.length)return"";const s=[];for(const o of a){let p=0,g=0;for(const b of o.dieKeys){const w=e.get(b);if(!w||w.partial||w.edgeExcluded)continue;const y=Z(w,r);y!==void 0&&(g++,y&&p++)}g&&s.push({label:o.label,pass:p,total:g})}if(!s.length)return"";const{rows:d}=X(s.map(o=>({key:o.label,label:o.label,n:o.total,passDies:o.pass,yieldPercent:o.pass/o.total*100}))),m=d.map(o=>[S(o.label),String(o.passDies),String(o.n),R(o.yieldPercent,ae(o.passDies,o.n)),H(o.delta)]);return D(t,P(["Region","Pass","Total","Yield","vs wafer"],m,{className:"compact"})+`<p class="report-legend">${E("the wafer")}</p>`)}function gt(t,a){const i=new Float64Array(t.length);let n=0;for(const g of t){const b=ut(g,a);b!==void 0&&Number.isFinite(b)&&(i[n++]=b)}if(n===0)return;const{count:r,min:e,mean:c,max:s,q1:d,median:m,q3:o,stddev:p}=lt(i.subarray(0,n));return{testNumber:a,count:r,min:e,mean:c,max:s,q1:d,median:m,q3:o,stddev:p}}function re(t,a,i,n,r){if(!a.length)return"";const e=t.filter(l=>!l.partial&&!l.edgeExcluded),c=new Map((i??[]).map(l=>[l.testNumber,l])),s=new Map((n??[]).map(l=>[l.testNumber,l.yieldPercent])),d=[];for(const l of a.filter(ke)){const u=c.get(l.testNumber)??gt(e,l.testNumber);u&&d.push({name:l.name,unit:l.unit||void 0,stats:u,limitYield:s.get(l.testNumber)})}if(!d.length)return"";const m=l=>d.every(u=>u.stats[l]!==void 0),o=m("q1")&&m("median")&&m("q3"),p=m("stddev"),g=d.some(l=>l.limitYield!==void 0&&l.limitYield!==null),b=(l,u)=>l===void 0?"\u2014":C(l,u),w=["Test","N","Min",...o?["Q1","Median"]:[],"Mean",...o?["Q3"]:[],"Max",...p?["StdDev"]:[],...g?["Limit yield"]:[]],y=d.map(({name:l,unit:u,stats:h,limitYield:$})=>[S(l),{html:String(h.count),className:"numeric"},b(h.min,u),...o?[b(h.q1,u),b(h.median,u)]:[],b(h.mean,u),...o?[b(h.q3,u)]:[],b(h.max,u),...p?[b(h.stddev,u)]:[],...g?[$==null?"\u2014":`${$.toFixed(1)}%`]:[]]);return D("Test Values",P(w,y,{className:"compact"})+`<p class="report-legend">${p?"StdDev is the sample standard deviation (n\u22121). ":""}${r??""}</p>`)}function le(t,a,i){const n=i??qe(t,a);if(!n?.length)return"";const r=n.map(e=>[S(e.label),String(e.totalDies),String(e.passDies),String(e.failDies),e.passRatePercent!==null?`${e.passRatePercent.toFixed(1)}%`:"\u2014"]);return D("Functional Tests",P(["Test","N","Pass","Fail","Pass Rate"],r,{className:"compact"}))}function ce(t,a){const i=Ze(t,a);if(!i.length)return"";const n=e=>e===null?"\u2014":e.toFixed(2),r=i.map(e=>[S(e.label),e.hasSpec?e.limitBasis==="spec"?`LSL ${C(e.lsl,e.unit)} \u2013 USL ${C(e.usl,e.unit)}`:`Lo limit ${C(e.lsl,e.unit)} \u2013 Hi limit ${C(e.usl,e.unit)}`:"\u2014",C(e.mean,e.unit),n(e.cp),n(e.cpk),n(e.pp),n(e.ppk)]);return D("Process Capability",P(["Test","Limits","Mean","Cp","Cpk","Pp","Ppk"],r,{className:"compact"}))}function de(t,a,i="",n=!1){const r=L(t);return r.length?D("Findings",ee(r,a,i,n)):""}function ue(t,a,i=""){const n=new Set(L(t.findings).map(r=>r.id));return ft(et(t,{passBins:a?[...a]:void 0}),n,i)}export function renderSummaryReportHtml(t,a={}){const{wafer:i,dies:n,yieldSummary:r,dataCoverage:e,hbinDefs:c,sbinDefs:s,testDefs:d=[],statsSummary:m,passBins:o,ringCount:p}=t,g=t.binColors??Ne(n,{passBins:o,hbinDefs:c,sbinDefs:s}),b=i.metadata,w=mt(b),y=a.title??`Wafer Summary${w}`,l=new Date().toLocaleString(),u=n.filter(dt),h=U(u,i,p),$=K(u,i,p),v=[{label:"Total dies",value:String(r.totalDies)},{label:"Filled dies",value:String(e.filledDies),hint:`${ae(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: ${Le([o])})`,value:`${r.yieldPercent.toFixed(1)}%`}]:[],...r.edgeExcludedDies>0?[{label:"Edge excluded (outer zone)",value:String(r.edgeExcludedDies)}]:[]],x=[ne([{metadata:i.metadata}]),D("Summary",[se(v)].filter(Boolean).join(`
|
|
3
|
+
`)),m?ue(m,o):"",pt(n,c,s,{hard:m?.stats.hardBinCounts,soft:m?.stats.softBinCounts},o,g),oe("Ring Yield",h,n,o),oe("Quadrant Yield",$,n,o),re(n,d,m?.stats.perTestStats,m?.stats.testSpecYield),le(n,d,m?.stats.functionalYield),ce([{dies:n}],d),m?de(m.findings,void 0,"",t.live):""].filter(Boolean).join(`
|
|
4
4
|
`);return`<!DOCTYPE html>
|
|
5
5
|
<html lang="en">
|
|
6
6
|
<head>
|
|
@@ -16,37 +16,37 @@ ${j()}
|
|
|
16
16
|
<h1>${S(y)}</h1>
|
|
17
17
|
<p class="report-subtitle">Generated ${S(l)}</p>
|
|
18
18
|
</header>
|
|
19
|
-
${
|
|
19
|
+
${te(x)}
|
|
20
20
|
<p class="footer">Generated ${S(l)}</p>
|
|
21
21
|
</main>
|
|
22
22
|
${t.live?k:""}
|
|
23
23
|
</body>
|
|
24
|
-
</html>`}function
|
|
25
|
-
`))}const
|
|
24
|
+
</html>`}function ht(t,a){const i=t.perWafer.flatMap(e=>{const c=e.summary.stats.yieldPercent;return c===null?[]:[{waferIndex:e.waferIndex,yieldPercent:c}]}),n=new Map(rt(i).rows.map(e=>[e.waferIndex,e])),r=t.perWafer.map(e=>{const c=S(_(a[e.waferIndex],e.waferIndex)),s=n.get(e.waferIndex);return s?[s.outlier?`${c} \xB7 ${s.outlier} outlier`:c,R(s.yieldPercent,`${s.yieldPercent.toFixed(1)}%`),H(s.delta)]:[c,"N/A",""]});return P(["Wafer","Yield","vs lot median"],r,{className:"compact"})+`<p class="report-legend">${E("the lot median")}</p>`}function bt(t){const a=[];for(const e of t.perWafer){const c=L(e.summary.findings);if(!c.length)continue;const s=e.summary.wafer?.waferId??`Wafer ${e.waferIndex+1}`;a.push(`<h3 class="report-subheading">${S(s)}</h3>`+ee(c))}if(!a.length)return"";const i=a.length,n=t.perWafer.length,r=`<p class="report-note">${i} of ${n} wafer${n===1?"":"s"} have per-wafer findings; the rest had none.</p>`;return D("Findings by Wafer",r+a.join(`
|
|
25
|
+
`))}const yt=3;function St(t,a,i){const n=t.map(d=>({metadata:d.wafer?.metadata})),r=Oe(n,{facetableOnly:!0}).filter(d=>d.splittable);if(!r.length)return"";const e=r.slice(0,yt),c=[];for(const d of e){const m=new Map;t.forEach((l,u)=>{const h=_e(l.wafer?.metadata,d.key)??Qe;(m.get(h)??m.set(h,[]).get(h)).push(u)});const o=[...m.entries()].map(([l,u])=>({key:l,items:u.map(h=>t[h])}));if(o.length<2)continue;const p=o.map(l=>[S(l.key),S(l.items.map(u=>u.label).join(", "))]),g=Ve(o.map(l=>({key:l.key,items:l.items.map(u=>({label:u.label,dies:u.dies,passBins:M(u,i)}))}))).map(l=>[S(l.label),String(l.itemCount),`${l.percent.toFixed(1)}%`]),b=[P([d.label,"Wafers"],p,{className:"compact"}),P([d.label,"Wafers","Yield"],g,{className:"compact"})],w=t.flatMap(l=>l.dies??[]);let y=0;for(const l of["spec","testFlag","functional"]){if(!Je(a,l,w))continue;const u=Ke(o.map(v=>({key:v.key,items:v.items.map(x=>({dies:x.dies,testSpecYield:x.statsSummary?.stats.testSpecYield,functionalYield:x.statsSummary?.stats.functionalYield}))})),a,l);if(!u.rows.length)continue;const h=u.rows.map(v=>[S(v.label),...v.byGroup.map(x=>x.passRatePercent===null?"\u2014":`${x.passRatePercent.toFixed(1)}%`),v.overall.passRatePercent===null?"\u2014":`${v.overall.passRatePercent.toFixed(1)}%`]),$=l==="spec"?"Pass rate \xB7 test limits":l==="testFlag"?"Pass rate \xB7 tester flag":"Pass rate \xB7 functional";u.disagreementDies&&(y=u.disagreementDies),b.push(`<h4 class="report-subheading">${S($)}</h4>`+P(["Test",...u.groups,"All"],h,{className:"compact"}))}y&&b.push(`<p class="report-note">${y.toLocaleString()} dies judged differently by limits and tester flag.</p>`),c.push(`<h3 class="report-subheading">${S(d.label)}</h3>${b.join(`
|
|
26
26
|
`)}`)}if(!c.length)return"";const s=r.length>e.length?`<p class="report-note">${r.length-e.length} further splittable field(s) not shown.</p>`:"";return D("Split Comparison",c.join(`
|
|
27
|
-
`)+s)}function
|
|
28
|
-
`)}function
|
|
29
|
-
`);return{lotSummary:o,sections:
|
|
27
|
+
`)+s)}function wt(t,a,i,n,r,e){const c=We(r);return["hard","soft"].map(s=>ie(at(n,t,s),s==="hard"?a:i,s,c[s],e[s])).join(`
|
|
28
|
+
`)}function fe(t,a,i,n,r,e){const c=He(i,n,r,d=>e[d],a);if(!c.length)return"";const s=X(c).rows.map(d=>[S(d.label),R(d.yieldPercent,`${d.yieldPercent.toFixed(1)}%`),H(d.delta)]);return D(t,P(["Region","Yield","vs lot"],s,{className:"compact"})+`<p class="report-legend">${E("the lot")}</p>`)}function $t(t,a,i,n){let r;if(i?.length&&i.every(e=>e.stats.perTestStats!==void 0)){const e=new Map;for(const c of i)for(const s of c.stats.perTestStats??[]){const d=e.get(s.testNumber)??{n:0,sum:0,min:1/0,max:-1/0,parts:[]};d.n+=s.count,d.sum+=s.mean*s.count,d.min=Math.min(d.min,s.min),d.max=Math.max(d.max,s.max),d.parts.push({n:s.count,mean:s.mean,sd:s.stddev}),e.set(s.testNumber,d)}r=[...e.entries()].map(([c,s])=>{const d=s.sum/s.n,m=s.parts.every(p=>p.sd!==void 0),o=m&&s.n>1?s.parts.reduce((p,g)=>p+Math.max(0,g.n-1)*g.sd**2+g.n*(g.mean-d)**2,0)/(s.n-1):m?0:void 0;return{testNumber:c,count:s.n,min:s.min,max:s.max,mean:d,...o!==void 0?{stddev:Math.sqrt(o)}:{}}})}return re(t,a,r,n,r?"Quartiles are not shown for a lot: they cannot be combined from each wafer\u2019s own.":void 0)}function vt(t,a,i){const n=Xe(i);return le(t,a,n)}const A=["lot","product","testProgram","temperature"];function xt(t){const a=new Map(A.map(e=>[e,new Set]));for(const e of t)for(const c of A){const s=e.wafer?.metadata?.[c];s!=null&&s!==""&&a.get(c).add(String(s))}const i=A.filter(e=>a.get(e).size>1);if(i.length===0)return[{label:"",items:t,varying:i}];const n=new Map,r=[];for(const e of t){const c=i.map(s=>String(e.wafer?.metadata?.[s]??"(none)")).join(" \xB7 ");n.has(c)||(n.set(c,[]),r.push(c)),n.get(c).push(e)}return r.map(e=>({label:e,items:n.get(e),varying:i}))}function me(t,a,i,n,r,e,c,s,d="",m=!1){const o=Ae(t.map(f=>f.source??f),{perWaferSummaries:t.map(f=>f.statsSummary),...c}),p=[],g=[],b=[],w=[];for(const f of t){const T=f.dies??[];f.wafer&&(p.push(f.wafer),b.push(T),w.push(M(f,r)));for(const F of T)g.push(F)}const y=t.map(f=>({dies:f.dies??[],passBins:M(f,r)})),l=s??Ie(y,{hbinDefs:a,sbinDefs:i}).colors,u=g.some(f=>f.hbin!=null),h=g.some(f=>f.sbin!=null),$=u||h,v=o.perWafer.map(f=>f.summary.stats.yieldPercent).filter(f=>f!==null),x=v.length?v.reduce((f,T)=>f+T,0)/v.length:null;let q=0,B=0,Y=0,W=0,N=0;for(const f of y){const T=new Set(f.passBins);for(const F of f.dies){if(q++,F.edgeExcluded&&W++,F.partial&&N++,F.partial||F.edgeExcluded)continue;const O=Z(F,T);O!==void 0&&(B++,O&&Y++)}}const ge=B-Y,z=B>0?Y/B*100:null,he=[{label:"Wafers",value:String(o.stats.waferCount)},{label:"Total dies",value:String(q)},...$?[{label:"Good dies",value:String(Y)},{label:"Bad dies",value:String(ge)}]:[],...W>0?[{label:"Edge excluded",value:String(W)}]:[],...N>0?[{label:"Partial dies",value:String(N)}]:[],...x!==null?[{label:ot,value:`${x.toFixed(1)}%`}]:[],...z!==null?[{label:"Total yield",value:`${z.toFixed(1)}%`}]:[]],be=J(o.perWafer.map(f=>f.summary.wafer)),ye=D(be.lotId!==void 0?"Lot Summary":"Summary",se(he)),Se=ne(t.map(f=>({metadata:f.wafer?.metadata}))),we=D("Per-Wafer Yield",ht(o,t)),G=e??Ee(t[0]),$e=St(t,n,r),I=o.perWafer.map(f=>f.summary),ve=$?wt(g,a,i,I,y,l):"",xe=$&&p.length?fe("Ring Yield (All Wafers)",U,b,p,G,w):"",De=$&&p.length?fe("Quadrant Yield (All Wafers)",K,b,p,G,w):"",Pe=n.length?$t(g,n,I,o.stats.testSpecYield):"",Fe=n.length?vt(g,n,I):"",Te=n.length?ce(t,n):"",Ce=o.findings.length?de(o.findings,o.stats.waferCount,d,m):"",Be=bt(o),Ye=[ye,ue(o,Me(y.map(f=>f.passBins)),d),Se,we,$e,ve,xe,De,Pe,Fe,Te,Ce,Be].filter(Boolean).join(`
|
|
29
|
+
`);return{lotSummary:o,sections:Ye}}function pe(t,a,i,n=""){return`<main class="report">
|
|
30
30
|
<header class="report-header">
|
|
31
31
|
<h1>${S(t)}</h1>
|
|
32
32
|
<p class="report-subtitle">Generated ${S(i)}</p>
|
|
33
33
|
</header>
|
|
34
|
-
${
|
|
34
|
+
${te(a,n)}
|
|
35
35
|
<p class="footer">Generated ${S(i)}</p>
|
|
36
|
-
</main>`}export function renderLotSummaryReportHtml(t,a={}){const{items:i,hbinDefs:n,sbinDefs:r,testDefs:e=[],passBins:c
|
|
36
|
+
</main>`}export function renderLotSummaryReportHtml(t,a={}){const{items:i,hbinDefs:n,sbinDefs:r,testDefs:e=[],passBins:c,ringCount:s,analyzeOptions:d}=t,m=new Date().toLocaleString(),o=xt(i);if(o.length===1){const{lotSummary:y,sections:l}=me(o[0].items,n,r,e,c,s,d,t.binColors,"",t.live),u=J(y.perWafer.map($=>$.summary.wafer)),h=a.title??(u.lotId!==void 0?`Lot Summary \u2014 ${u.lotId}`:`Summary \u2014 ${Ue(u)}`);return`<!DOCTYPE html>
|
|
37
37
|
<html lang="en">
|
|
38
38
|
<head>
|
|
39
39
|
<meta charset="UTF-8">
|
|
40
|
-
<title>${S(
|
|
40
|
+
<title>${S(h)}</title>
|
|
41
41
|
<style>
|
|
42
42
|
${j()}
|
|
43
43
|
</style>
|
|
44
44
|
</head>
|
|
45
45
|
<body>
|
|
46
|
-
${
|
|
46
|
+
${pe(h,l,m)}
|
|
47
47
|
${t.live?k:""}
|
|
48
48
|
</body>
|
|
49
|
-
</html>`}const p=a.title??"Summary",
|
|
49
|
+
</html>`}const p=a.title??"Summary",g=o.map((y,l)=>{const u=`g${l+1}-`,{lotSummary:h,sections:$}=me(y.items,n,r,e,c,s,d,t.binColors,u,t.live),v=y.varying.includes("lot")?void 0:h.lot?.lot??h.lot?.lotId,x=v?`${y.label} \xB7 Lot ${String(v)}`:y.label;return pe(`${p} \u2014 ${x}`,$,m,u)}),b=`<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 ${o.length} groups by identity \u2014 shown separately below so stats are never pooled across them.</p>`,w=g.map((y,l)=>`${l===0?"":'<hr style="max-width:1080px;margin:32px auto 0;border:none;border-top:3px solid var(--report-line-strong);">'}
|
|
50
50
|
${y}`).join(`
|
|
51
51
|
`);return`<!DOCTYPE html>
|
|
52
52
|
<html lang="en">
|
|
@@ -64,4 +64,4 @@ ${b}
|
|
|
64
64
|
${w}
|
|
65
65
|
${t.live?k:""}
|
|
66
66
|
</body>
|
|
67
|
-
</html>`}export{
|
|
67
|
+
</html>`}export{je as openHtmlReport};export function renderWaferReportHtml(t,a,i={}){return renderSummaryReportHtml({wafer:t.wafer,dies:t.dies,yieldSummary:t.yield,dataCoverage:t.dataCoverage,hbinDefs:t.hbinDefs,sbinDefs:t.sbinDefs,testDefs:t.testDefs,statsSummary:a??t.statsSummary??(V(t)?ze(t):void 0),passBins:[...t.passBins],ringCount:t.ringCount,binColors:i.binColors,live:i.live},i)}export function renderLotReportHtml(t,a={}){const i=Q(t.map(e=>e.hbinDefs)),n=Q(t.map(e=>e.sbinDefs)),r=t.some(e=>e.testDefs?.length)?Ge([...t]).defs:void 0;return renderLotSummaryReportHtml({items:t.map((e,c)=>({label:_(e,c),wafer:e.wafer,dies:e.dies,passBins:[...e.passBins],statsSummary:e.statsSummary,source:V(e)?e:void 0})),hbinDefs:i.length?i:void 0,sbinDefs:n.length?n:void 0,testDefs:r,ringCount:t[0]?.ringCount,analyzeOptions:a.analyzeOptions,binColors:a.binColors,live:a.live},{title:a.title})}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { StatsFinding, StatsSeverity } from './types.js';
|
|
2
|
-
import type
|
|
2
|
+
import { type Synthesis } from './synthesis.js';
|
|
3
3
|
import { type MeterTier } from './presentation.js';
|
|
4
4
|
export type { MeterTier };
|
|
5
5
|
import { type FacetItem } from './facets.js';
|
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
import{
|
|
2
|
-
<h2>${
|
|
1
|
+
import{SYNTHESIS_SMALLER_HEADING as f,synthesisRowShare as u}from"./synthesis.js";import{formatFindingTooltip as x}from"./findingText.js";import{arrangeFindings as v}from"./filterFindings.js";import{SEVERITY_MARK as h,METER_DOTS as $,filledDots as w,impactWord as y,impactShortWord as k,shortfallColor as g,shortfallStep as S,formatPoints as z,barPercent as M}from"./presentation.js";import{fmt as b,plainBinTerms as j}from"../renderer/fmt.js";import{buildFacetTable as T,prettyKey as F}from"./facets.js";import{escHtml as a}from"../core/utils.js";import{derivedKeyText as N,derivedFields as A}from"../renderer/testLabel.js";export function renderSection(t,e,r=""){return`<section class="${["report-section",r].filter(Boolean).join(" ")}">
|
|
2
|
+
<h2>${a(t)}</h2>
|
|
3
3
|
${e}
|
|
4
|
-
</section>`}export function renderDefinitionList(t,e="meta-list"){if(!t.length)return"";const r=t.map(
|
|
4
|
+
</section>`}export function renderDefinitionList(t,e="meta-list"){if(!t.length)return"";const r=t.map(o=>`<div class="definition-item"><dt>${a(o.label)}</dt><dd>${a(o.value)}</dd></div>`).join(`
|
|
5
5
|
`);return`<dl class="${e}">
|
|
6
6
|
${r}
|
|
7
|
-
</dl>`}export function buildMetadataRows(t){return
|
|
7
|
+
</dl>`}export function buildMetadataRows(t){return T(t,{facetableOnly:t.length>1}).map(r=>({label:F(r.key),value:r.values.map(o=>o.value).join(", ")}))}export function renderMetadataSection(t){const e=buildMetadataRows(t);return e.length?renderSection("Wafer Info",renderDefinitionList(e)):""}export function renderMetricGrid(t){return t.length?`<dl class="metric-grid">
|
|
8
8
|
${t.map(r=>`<div class="metric">
|
|
9
|
-
<dt>${
|
|
10
|
-
<dd>${
|
|
11
|
-
${r.hint?`<span class="metric-hint">${
|
|
9
|
+
<dt>${a(r.label)}</dt>
|
|
10
|
+
<dd>${a(r.value)}</dd>
|
|
11
|
+
${r.hint?`<span class="metric-hint">${a(r.hint)}</span>`:""}
|
|
12
12
|
</div>`).join(`
|
|
13
13
|
`)}
|
|
14
|
-
</dl>`:""}export function renderTable(t,e,r={}){if(!e.length)return`<p class="no-data">${
|
|
15
|
-
`);return`<table class="${
|
|
16
|
-
<thead><tr>${
|
|
17
|
-
<tbody>${
|
|
18
|
-
</table>`}export function renderMeter(t,e){const r=
|
|
19
|
-
${
|
|
14
|
+
</dl>`:""}export function renderTable(t,e,r={}){if(!e.length)return`<p class="no-data">${a(r.emptyMessage??"No data")}</p>`;const o=["report-table",r.className].filter(Boolean).join(" "),s=t.map(n=>`<th>${a(n)}</th>`).join(""),m=n=>typeof n=="string"?`<td>${n}</td>`:`<td${n.className?` class="${n.className}"`:""}>${n.html}</td>`,l=e.map(n=>`<tr>${n.map(m).join("")}</tr>`).join(`
|
|
15
|
+
`);return`<table class="${o}">
|
|
16
|
+
<thead><tr>${s}</tr></thead>
|
|
17
|
+
<tbody>${l}</tbody>
|
|
18
|
+
</table>`}export function renderMeter(t,e){const r=w(t);return`<span class="meter"><span class="meter-dots" aria-hidden="true">${"\u25CF".repeat(r)}${"\u25CB".repeat($-r)}</span><span class="meter-word">${a(e)}</span></span>`}export function renderSeverityMeter(t){const{tier:e,word:r}=h[t];return renderMeter(e,r)}export const severityTier=t=>h[t].tier;export function barCell(t,e,r="",o){const s=o?`;background:${a(o)}`:"";return{html:`<span class="cellbar"><i style="width:${M(t).toFixed(1)}%${s}"></i><span>${a(e)}</span></span>`,className:["barcell",r].filter(Boolean).join(" ")}}export function deltaCell(t){const e=S(t);return{html:a(z(t)),className:["numeric",e?`low-${e}`:""].filter(Boolean).join(" ")}}export function withSectionNav(t,e=""){const r=[];let o=0;const s=t.replace(/<section class="(report-section[^"]*)">(\s*<h2>)(.*?)(<\/h2>)/g,(l,n,i,p,d)=>{const c=`sec-${e}${++o}`;return r.push({id:c,title:p}),`<section class="${n}" id="${c}">${i}${p}${d}`});return r.length<4?t:`<nav class="report-toc" aria-label="Sections">${r.map(l=>`<a href="#${l.id}">${l.title}</a>`).join("")}</nav>
|
|
19
|
+
${s}`}export function formatFindingDelta(t){const e=t.effect.absoluteDelta??0,r=e>0?"+":e<0?"\u2212":"";if(t.variable.kind==="yield"||t.variable.kind==="hardBin"||t.variable.kind==="softBin")return`${r}${Math.abs(e*100).toFixed(1)} pp`;if(t.variable.kind==="test"){if(t.effect.relativeDelta!==void 0&&Number.isFinite(t.effect.relativeDelta)){const o=Math.abs(t.effect.relativeDelta*100).toFixed(1);return`${r}${o}%`}return`${r}${b(Math.abs(e),t.variable.unit)}`}return`${r}${b(Math.abs(e),t.variable.unit)}`}export function derivedFindingsKeyHtml(t){const e=N(t.map(r=>({label:r.variable.label,...A(r.variable)})));return e?`<p class="report-note">${a(e)}</p>`:""}export function findingAnchor(t,e=""){return`finding-${e}`+t.replace(/[^A-Za-z0-9]/g,r=>`_${r.charCodeAt(0).toString(16)}_`)}export function findingsTableHtml(t,e,r,o=!1){const s=v(t),m=[...s.patterns.flatMap(i=>[{f:i.finding,nested:!1},...i.children.map(p=>({f:p,nested:!0}))]),...s.groups.flatMap(i=>i.findings.map(p=>({f:p,nested:!1})))],l=m.length?m.map(({f:i,nested:p})=>`<tr class="tier-${severityTier(i.severity)}${p?" nested":""}${o?" live":""}"${r!==void 0?` id="${findingAnchor(i.id,r)}"`:""}${o?` data-finding="${a(i.id)}"`:""} title="${a(x(i))}${o?`
|
|
20
20
|
Click to show it on the map`:""}">
|
|
21
21
|
<td class="tight">${renderSeverityMeter(i.severity)}</td>
|
|
22
|
-
<td class="tight">${
|
|
23
|
-
<td>${
|
|
24
|
-
<td class="numeric">${
|
|
25
|
-
<td class="numeric">${
|
|
22
|
+
<td class="tight">${a(i.comparison.left)}</td>
|
|
23
|
+
<td>${a(j(i.variable.label))}</td>
|
|
24
|
+
<td class="numeric">${a(formatFindingDelta(i))}</td>
|
|
25
|
+
<td class="numeric">${a(formatFindingCoverage(i,e))}</td>
|
|
26
26
|
</tr>`).join(`
|
|
27
27
|
`):'<tr><td colspan="5" class="no-data">No significant findings</td></tr>';return`<table class="report-table findings-table compact">
|
|
28
28
|
<thead>
|
|
@@ -35,16 +35,20 @@ Click to show it on the map`:""}">
|
|
|
35
35
|
</tr>
|
|
36
36
|
</thead>
|
|
37
37
|
<tbody>
|
|
38
|
-
${
|
|
38
|
+
${l}
|
|
39
39
|
</tbody>
|
|
40
|
-
</table>${derivedFindingsKeyHtml(t)}`}export function synthesisSectionHtml(t,e,r=""){const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
</table>${derivedFindingsKeyHtml(t)}`}export function synthesisSectionHtml(t,e,r=""){const o=d=>d.map(c=>c.target&&e.has(c.target.id)?`<a href="#${findingAnchor(c.target.id,r)}">${a(c.text)}</a>`:a(c.text)).join(""),[s,...m]=t.items,l=[...m,...t.also?.items??[]],n=t.also?.more??0,i=s?`<ol class="synthesis-items">
|
|
41
|
+
<li class="tier-${s.impact}">${renderMeter(s.impact,y(s.impact))}<span>${o(s.parts)}</span></li>
|
|
42
|
+
</ol>`:`<p class="synthesis-none">${a(t.nothing??"Nothing stands out.")}</p>`,p=l.length?`<p class="synthesis-smaller">${a(f)}</p>
|
|
43
|
+
<ul class="synthesis-more">
|
|
44
|
+
${l.map(d=>`<li>${renderMeter(d.impact,k(d.impact))}<span>${o([d.brief])}</span><span class="synthesis-share">${a(u(d))}</span></li>`).join(`
|
|
45
|
+
`)}${n?`
|
|
46
|
+
<li class="synthesis-rest"><span></span><span>and ${n} more</span><span></span></li>`:""}
|
|
47
|
+
</ul>`:"";return renderSection("What stands out",`<p class="synthesis-headline">${o(t.headline)}</p>
|
|
48
|
+
${i}
|
|
44
49
|
${p}
|
|
45
|
-
${t.
|
|
46
|
-
|
|
47
|
-
<p class="synthesis-checked">${o(t.checked)}</p>`,"synthesis")}export const LIVE_FINDINGS_SCRIPT=`<script>
|
|
50
|
+
${t.watch?`<ul class="synthesis-watch">${t.watch.map(d=>`<li><strong>Watch</strong> ${o(d.parts)}</li>`).join("")}</ul>`:""}
|
|
51
|
+
<p class="synthesis-checked">${a(t.checked)}</p>`,"synthesis")}export const LIVE_FINDINGS_SCRIPT=`<script>
|
|
48
52
|
document.addEventListener('click', function (e) {
|
|
49
53
|
var row = e.target.closest && e.target.closest('[data-finding]');
|
|
50
54
|
if (row && window.parent !== window) window.parent.postMessage({ wmapFinding: row.getAttribute('data-finding') }, '*');
|
|
@@ -67,9 +71,9 @@ document.addEventListener('click', function (e) {
|
|
|
67
71
|
--report-tier-medium-edge: #a8741a;
|
|
68
72
|
--report-tier-low-bg: #edf2f7;
|
|
69
73
|
--report-tier-low-edge: #5f7f9f;
|
|
70
|
-
--report-low-1: ${
|
|
71
|
-
--report-low-2: ${
|
|
72
|
-
--report-low-3: ${
|
|
74
|
+
--report-low-1: ${g(1)};
|
|
75
|
+
--report-low-2: ${g(2)};
|
|
76
|
+
--report-low-3: ${g(3)};
|
|
73
77
|
--report-bar: #9db8d3;
|
|
74
78
|
}
|
|
75
79
|
|
|
@@ -133,7 +137,10 @@ document.addEventListener('click', function (e) {
|
|
|
133
137
|
}
|
|
134
138
|
.synthesis-items li.tier-high { border-color: var(--report-tier-high-edge); background: var(--report-tier-high-bg); }
|
|
135
139
|
.synthesis-items li.tier-medium { border-color: var(--report-tier-medium-edge); background: var(--report-tier-medium-bg); }
|
|
136
|
-
.synthesis-
|
|
140
|
+
.synthesis-smaller { margin: 0 0 4px; color: var(--report-muted); }
|
|
141
|
+
.synthesis-more { list-style: none; margin: 0 0 8px; padding: 0; }
|
|
142
|
+
.synthesis-more li { display: grid; grid-template-columns: 96px 1fr auto; gap: 10px; align-items: baseline; margin: 0 0 3px; }
|
|
143
|
+
.synthesis-share { font-variant-numeric: tabular-nums; color: var(--report-muted); }
|
|
137
144
|
.synthesis-watch { list-style: none; margin: 0 0 6px; padding: 0; }
|
|
138
145
|
.synthesis-watch li { margin: 0 0 3px; }
|
|
139
146
|
.synthesis-watch strong { margin-right: 6px; font-size: 11px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--report-muted); }
|
|
@@ -5,9 +5,15 @@ export interface ScatterPoint {
|
|
|
5
5
|
hbin: number | undefined;
|
|
6
6
|
/** Facet group this point belongs to — set only by the grouped builder. */
|
|
7
7
|
group?: string;
|
|
8
|
+
/** The die this point is, so a point can name it and its wafer. */
|
|
9
|
+
die?: Die;
|
|
10
|
+
/** The wafer the die is on — the item's `waferIndex`, when it has one. */
|
|
11
|
+
waferIndex?: number;
|
|
8
12
|
}
|
|
9
13
|
export interface ScatterItem {
|
|
10
14
|
dies?: Die[];
|
|
15
|
+
/** Carried onto each point, so a click on a point can open its wafer. */
|
|
16
|
+
waferIndex?: number;
|
|
11
17
|
}
|
|
12
18
|
/** One point per die with valid values for both tests, across `items`. */
|
|
13
19
|
export declare function buildScatterData(items: ScatterItem[], xTest: number, yTest: number): ScatterPoint[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{testValue as
|
|
1
|
+
import{testValue as c}from"../core/dieTable.js";function a(e,i,o,n,t){const f=e.waferIndex;for(const r of e.dies??[]){const s=c(r,i),d=c(r,o);if(s!==void 0&&d!==void 0&&Number.isFinite(s)&&Number.isFinite(d)){const u={x:s,y:d,hbin:r.hbin,die:r};f!==void 0&&(u.waferIndex=f),n!==void 0&&(u.group=n),t.push(u)}}}export function buildScatterData(e,i,o){const n=[];for(const t of e)a(t,i,o,void 0,n);return n}export function buildScatterDataGrouped(e,i,o){const n=[];for(const t of e)for(const f of t.items)a(f,i,o,t.key,n);return n}
|
|
@@ -34,7 +34,7 @@ export interface SweepSeriesSpec {
|
|
|
34
34
|
* Read each test's x value from its NAME instead — for programs that record
|
|
35
35
|
* the swept quantity only in the test text. A placeholder pattern, not a
|
|
36
36
|
* regular expression: `{x}` reads a number, `*` matches any run of
|
|
37
|
-
* characters, everything else is literal. `"LRS_STATS_{x}"` reads 12,000 from
|
|
37
|
+
* characters, `?` matches exactly one character, everything else is literal. `"LRS_STATS_{x}"` reads 12,000 from
|
|
38
38
|
* a test named `"Normalized_LRS= LRS_STATS_12K / …"`.
|
|
39
39
|
*
|
|
40
40
|
* - The pattern is found ANYWHERE in the name; no leading or trailing `*`.
|
|
@@ -52,6 +52,14 @@ export interface SweepSeriesSpec {
|
|
|
52
52
|
xFromName?: string;
|
|
53
53
|
/** Optional explicit colour. Omit to take one from the chart's palette. */
|
|
54
54
|
color?: string;
|
|
55
|
+
/**
|
|
56
|
+
* What each test was called when the sweep was set up, by test number (`{ "3000": "Iread 0.1V" }`). The number is
|
|
57
|
+
* the key and the name is a check, as in a plot's fields: a lot from another test program can reuse the numbers for
|
|
58
|
+
* other measurements, and a sweep read from them would draw a plausible curve of the wrong quantity. A test whose
|
|
59
|
+
* name differs from the one recorded here is not drawn and the sweep says so. Filled in by the sweep editor;
|
|
60
|
+
* absent from a sweep written by hand, which is then not checked.
|
|
61
|
+
*/
|
|
62
|
+
testNames?: Record<string, string>;
|
|
55
63
|
}
|
|
56
64
|
export interface SweepSpec {
|
|
57
65
|
/** Stable identity, so a host can persist which sweep was selected. */
|
|
@@ -173,6 +181,34 @@ export interface SweepData {
|
|
|
173
181
|
* sweep card say so themselves.
|
|
174
182
|
*/
|
|
175
183
|
export declare function sweepAppliesTo(spec: SweepSpec, testDefs: readonly TestDef[] | undefined): boolean;
|
|
184
|
+
/** Whether two test names are the same test's name: case and surrounding space do not count. */
|
|
185
|
+
export declare const sameTestName: (a: string, b: string) => boolean;
|
|
186
|
+
/**
|
|
187
|
+
* `sweep` with each series' `testNames` set to the names its tests have in `testDefs`, for the check in `buildSweepData`.
|
|
188
|
+
* A test the data does not hold is left out, so a sweep written for a lot with more tests stays checkable on those it has.
|
|
189
|
+
*/
|
|
190
|
+
export declare function recordSweepTestNames<T extends {
|
|
191
|
+
series: SweepSeriesSpec[];
|
|
192
|
+
}>(sweep: T, testDefs: readonly TestDef[] | undefined): T;
|
|
193
|
+
/** One test's line in the name-pattern preview. `x` is what the pattern reads from the name. */
|
|
194
|
+
export interface NamePreviewRow {
|
|
195
|
+
test: number;
|
|
196
|
+
name?: string;
|
|
197
|
+
x: number | null | {
|
|
198
|
+
ambiguous: string;
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* What `series.xFromName` reads from the names of the series' first `max` tests, for the editor to show while the
|
|
203
|
+
* pattern is being written: the names as they are in the data, and the X each yields. The tests are expanded as the
|
|
204
|
+
* chart expands them (ranges included), so the preview and the chart cannot disagree about which tests there are.
|
|
205
|
+
* `error` is the pattern's own problem (none yet, or no `{x}`); the rows then carry names only.
|
|
206
|
+
*/
|
|
207
|
+
export declare function previewXFromName(series: SweepSeriesSpec, testDefs: readonly TestDef[] | undefined, max?: number): {
|
|
208
|
+
rows: NamePreviewRow[];
|
|
209
|
+
total: number;
|
|
210
|
+
error?: string;
|
|
211
|
+
};
|
|
176
212
|
/**
|
|
177
213
|
* Build the plottable data for one sweep over a die population.
|
|
178
214
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{isYieldEligibleDie as
|
|
1
|
+
import{isYieldEligibleDie as W}from"../core/dies.js";import{testValue as Z}from"../core/dieTable.js";import{isParametricTest as L}from"../renderer/buildWaferMap.js";import{markedTestLabel as ee,derivedFields as ne}from"../renderer/testLabel.js";import{parseTestReference as te}from"../renderer/derivedTests/parser.js";import{quantile as V}from"./math.js";import{compileXNamePattern as O,readXFromName as Y}from"./sweepXFromName.js";function y(r,a,t){const e=[];let n=!1;for(const i of r.tests){if(typeof i=="number"){Number.isInteger(i)?e.push(i):t.push(`"${r.label}": ${i} is not a test number.`);continue}const o=te(String(i),a);if(!o.ok){t.push(`"${r.label}": "${i}" \u2014 ${o.message}.`);continue}o.range&&(n=!0),e.push(...o.tests)}return{spec:r,tests:e,usedRange:n}}export function sweepAppliesTo(r,a){const t=new Map((a??[]).map(e=>[e.testNumber,e]));return r.series.some(e=>y(e,t,[]).tests.some(n=>{const i=t.get(n);return i!==void 0&&L(i)}))}export const sameTestName=(r,a)=>r.trim().toLowerCase()===a.trim().toLowerCase();export function recordSweepTestNames(r,a){const t=new Map((a??[]).map(e=>[e.testNumber,e]));return{...r,series:r.series.map(e=>{const n={};for(const u of y(e,t,[]).tests){const l=t.get(u)?.name;l&&(n[String(u)]=l)}const{testNames:i,...o}=e;return Object.keys(n).length>0?{...o,testNames:n}:o})}}function E(r,a=16){return r.length<=a?r.join(", "):`${r.slice(0,a).join(", ")}, \u2026 (${r.length-a} more)`}function se(r,a,t=3){const e=i=>i.length>70?`${i.slice(0,69)}\u2026`:i;return r.slice(0,t).map(i=>{const o=a.get(i);return o===void 0?`${i} (not in this data)`:`${i} "${e(o.name)}"`}).join(", ")+(r.length>t?`, \u2026 (${r.length-t} more)`:"")}export function previewXFromName(r,a,t=4){const e=new Map((a??[]).map(u=>[u.testNumber,u])),n=y(r,e,[]).tests,i=O(r.xFromName??"");return{rows:n.slice(0,t).map(u=>{const l=e.get(u)?.name;return{test:u,name:l,x:"error"in i||l===void 0?null:Y(i,l)}}),total:n.length,..."error"in i?{error:i.error}:{}}}function oe(r,a){const t=r?.[a];return t!==void 0&&Number.isFinite(t)?t:a}function ie(r,a,t){const e=r.spec;if(e.xValues!==void 0&&e.xFromName!==void 0)return t.push(`"${e.label}": gives both xValues and xFromName \u2014 use one.`),{asked:!0};if(e.xValues!==void 0){const n=e.xValues.length;return n===r.tests.length?{xs:e.xValues,asked:!0}:(t.push(r.usedRange?`"${e.label}": ${n} x values for ${r.tests.length} tests \u2014 the ranges matched ${E(r.tests)}.`:`"${e.label}": ${n} x values for ${r.tests.length} tests.`),{asked:!0})}if(e.xFromName!==void 0){const n=O(e.xFromName);if("error"in n)return t.push(`"${e.label}": xFromName "${e.xFromName}" \u2014 ${n.error}.`),{asked:!0};const i=[],o=[],u=[];for(const l of r.tests){const c=a.get(l)?.name,m=c===void 0?null:Y(n,c);typeof m=="number"?i.push(m):(o.push(l),m!==null&&u.push(m.ambiguous))}return o.length>0?(t.push(`"${e.label}": xFromName "${e.xFromName}" found no x value in the name of ${o.length===1?"test":"tests"} ${E(o)}`+(u.length>0?` \u2014 "${u[0]}" is written in capitals, where M could be milli or mega; put the letter in the pattern ("\u2026{x}M\u2026") and the unit in xLabel.`:".")+` The names are: ${se(o,a)}.`),{asked:!0}):{xs:i,asked:!0}}return{asked:!1}}function re(r,a,t){const e=new Map;for(let n=0;n<t.length;n++){const i=e.get(t[n]);if(i!==void 0)return`"${r}": tests ${a[i]} and ${a[n]} have the same x value (${t[n]}) \u2014 a curve cannot be measured where it revisits an x.`;e.set(t[n],n)}return null}function _(r,a){const t=r.filter(e=>e.count>0);for(let e=1;e<t.length;e++){const n=t[e-1],i=t[e],o=Math.min(n.median,i.median),u=Math.max(n.median,i.median);if(a<o||a>u)continue;if(n.median===i.median)return n.x;const l=(a-n.median)/(i.median-n.median);return n.x+l*(i.x-n.x)}return null}function ae(r,a){const t=new Map(a.filter(o=>o.count>0).map(o=>[o.x,o])),e=[];for(const o of r){if(o.count===0)continue;const u=t.get(o.x);u!==void 0&&e.push({x:o.x,d:o.median-u.median,y:o.median})}if(e.length<2)return null;const n=[];for(let o=0;o<e.length;o++){const u=e[o];if(u.d===0){n.push({x:u.x,y:u.y});continue}if(o===0)continue;const l=e[o-1];if(l.d===0||l.d<0==u.d<0)continue;const c=l.d/(l.d-u.d);n.push({x:l.x+c*(u.x-l.x),y:l.y+c*(u.y-l.y)})}const i=n[0];return i===void 0?null:{x:i.x,y:i.y,multiple:n.length>1}}export function buildSweepData(r,a,t){const e=[],n=new Map((a??[]).map(s=>[s.testNumber,s])),i=r.filter(s=>W(s)),o=t.series.map(s=>y(s,n,e)),u=o.map(s=>ie(s,n,e)),l=o.length>0&&u.every(s=>s.xs!==void 0),c=!l&&u.some(s=>s.asked);c&&u.some(s=>!s.asked)&&e.push("x values are given for some series but not all.");const m=l?o.map((s,d)=>re(s.spec.label,s.tests,u[d].xs)).find(s=>s!==null)??null:null;m!==null&&e.push(m);const C=t.xScale==="log",X=l&&u.every(s=>s.xs.every(d=>d>0)),$=C&&X?"log":"linear";C&&!X&&e.push(l?"A log x axis needs every x value to be positive \u2014 drawn on a linear axis.":"A log x axis needs x values (xValues or xFromName) \u2014 drawn in test order.");let v=0;const j=o.map(({spec:s,tests:d},g)=>{const N=l?u[g].xs:void 0,f=[],K=d.map((x,Q)=>{const h=n.get(x),S=s.testNames?.[String(x)],D=h!==void 0&&!!S&&!!h.name&&!sameTestName(S,h.name);D?(f.push(`${x} is "${S}" in this sweep and "${h.name}" here`),v++):h===void 0?e.push(`"${s.label}": test ${x} is not in this program's test definitions.`):L(h)||e.push(`"${s.label}": test ${x} is functional and has no measured value.`);const p=[];if(h!==void 0&&L(h)&&!D)for(const T of i){const b=Z(T,x);b!==void 0&&Number.isFinite(b)&&p.push(b)}return p.sort((T,b)=>T-b),{testNumber:x,label:ee(h,x),...ne(h),x:oe(N,Q),median:p.length?V(p,.5):NaN,p10:p.length?V(p,.1):NaN,p90:p.length?V(p,.9):NaN,count:p.length}});return f.length>0&&e.push(`"${s.label}": ${f.length} test${f.length!==1?"s are":" is"} not the one this sweep was set up on, so ${f.length!==1?"they are":"it is"} not drawn (${f.slice(0,3).join("; ")}${f.length>3?"; \u2026":""}). It may belong to another test program.`),{label:s.label,color:s.color,points:K}}),[P,R]=j,w=P!==void 0&&R!==void 0&&!c&&m===null&&v===0,z=t.crossing??!0,G=s=>$==="log"?Math.log10(s):s,M=s=>$==="log"?10**s:s,q=s=>s.map(d=>({...d,x:G(d.x)})),I=w?q(P.points):[],U=w?q(R.points):[],H=(c||m!==null||v>0?[]:t.separationAt??[]).map(s=>{if(!w)return{y:s,distance:null};const d=_(I,s),g=_(U,s);if(d===null)return{y:s,distance:null,reason:"first-series-never-reaches"};if(g===null)return{y:s,distance:null,reason:"second-series-never-reaches"};const N=M(d),f=M(g);return{y:s,distance:Math.abs(f-N),from:N,to:f,...$==="log"?{ratio:10**Math.abs(d-g)}:{}}}),k=w&&z?ae(I,U):null,J=k&&{...k,x:M(k.x)},B=s=>s===void 0?void 0:n.get(s.tests[0]??-1)?.unit,F=B(o[0]),A=B(o[1]);return F!==void 0&&A!==void 0&&F!==A&&e.push(`Series measure different units (${F} and ${A}) \u2014 crossing and separation compare unlike quantities.`),{title:t.title,xLabel:c?"Test":t.xLabel??(l?"Sweep value":"Test"),yLabel:t.yLabel??"Value",series:j,xIsPhysical:l,...l&&t.xUnit?{xUnit:t.xUnit}:{},xScale:$,crossing:J,separations:H,dieCount:i.length,warnings:e,...v>0?{notMeasured:"some tests are not the ones this sweep was set up on, so the crossing and widths are not measured"}:c&&t.series.length>=2?{notMeasured:"x values do not match the tests, so the sweep is drawn in test order and the crossing and widths are not measured"}:m!==null&&t.series.length>=2?{notMeasured:"a series revisits an x value, so its points cannot be paired with the other series"}:{}}}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type TextResult<T> = {
|
|
2
|
+
ok: true;
|
|
3
|
+
value: T;
|
|
4
|
+
} | {
|
|
5
|
+
ok: false;
|
|
6
|
+
error: string;
|
|
7
|
+
};
|
|
8
|
+
/** A series' tests: whole numbers stay numbers, `a..b` stays a range string for the sweep to expand against the lot. */
|
|
9
|
+
export declare function parseTestList(text: string): TextResult<Array<number | string>>;
|
|
10
|
+
export declare const formatTestList: (tests: ReadonlyArray<number | string>) => string;
|
|
11
|
+
/** Numbers separated by commas or spaces. */
|
|
12
|
+
export declare function parseNumberList(text: string): TextResult<number[]>;
|
|
13
|
+
export declare const formatNumberList: (values: readonly number[]) => string;
|
|
14
|
+
//# sourceMappingURL=sweepText.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const n=/^(\d+)\s*\.\.\s*(\d+)$/,o=r=>r.replace(/\s*\.\.\s*/g,"..").split(/[,\s]+/).filter(t=>t!=="");export function parseTestList(r){const t=[];for(const e of o(r)){if(/^\d+$/.test(e)){t.push(Number(e));continue}const s=n.exec(e);if(s){if(Number(s[1])>Number(s[2]))return{ok:!1,error:`"${e}" runs backwards: write the lower test number first`};t.push(`${s[1]}..${s[2]}`);continue}return{ok:!1,error:`"${e}" is not a test number or a range such as 1010..1030`}}return{ok:!0,value:t}}export const formatTestList=r=>r.join(", ");export function parseNumberList(r){const t=[];for(const e of o(r)){const s=Number(e);if(!Number.isFinite(s))return{ok:!1,error:`"${e}" is not a number`};t.push(s)}return{ok:!0,value:t}}export const formatNumberList=r=>r.join(", ");
|
|
@@ -3,6 +3,8 @@ type Part = {
|
|
|
3
3
|
text: string;
|
|
4
4
|
} | {
|
|
5
5
|
kind: 'star';
|
|
6
|
+
} | {
|
|
7
|
+
kind: 'any';
|
|
6
8
|
} | {
|
|
7
9
|
kind: 'x';
|
|
8
10
|
};
|
|
@@ -11,6 +13,18 @@ export interface XNamePattern {
|
|
|
11
13
|
source: string;
|
|
12
14
|
parts: Part[];
|
|
13
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Worked examples of name patterns, shown in the editor and the guide. Held as data and read back through the matcher
|
|
18
|
+
* by a test, so an example that stops being true fails the build instead of misleading a reader.
|
|
19
|
+
*/
|
|
20
|
+
export declare const NAME_PATTERN_EXAMPLES: ReadonlyArray<{
|
|
21
|
+
name: string;
|
|
22
|
+
pattern: string;
|
|
23
|
+
reads: number;
|
|
24
|
+
note?: string;
|
|
25
|
+
}>;
|
|
26
|
+
/** What a pattern cannot do, stated beside the examples because a reader will try it. */
|
|
27
|
+
export declare const NAME_PATTERN_LIMIT: string;
|
|
14
28
|
/** Compile a pattern, or return why it cannot be used. */
|
|
15
29
|
export declare function compileXNamePattern(source: string): XNamePattern | {
|
|
16
30
|
error: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{SI_PREFIX_SCALE as
|
|
1
|
+
import{SI_PREFIX_SCALE as _,KNOWN_BASE_UNITS as p}from"../renderer/fmt.js";const c=Object.fromEntries(Object.entries({..._,K:1e3}).map(([e,t])=>[e,Math.round(Math.log10(t))])),m=e=>e!==void 0&&/\p{L}/u.test(e),b=[...new Set(p.map(e=>e.toLowerCase()))];function g(e,t){const n=e[t];if(n===void 0||!m(n))return null;const o=t+1;if(!m(e[o]))return c[n]??null;const a=e.slice(o),s=b.find(r=>a.toLowerCase().startsWith(r)&&!m(a[r.length]));if(s===void 0)return null;const l=a.slice(0,s.length);return!p.includes(l)||!/\p{Ll}/u.test(e)?n.toLowerCase()==="m"?"ambiguous":c[n]??c[n.toLowerCase()]??c[n.toUpperCase()]??null:c[n]??null}export const NAME_PATTERN_EXAMPLES=[{name:"Fmax @ 0.55 V",pattern:"@ {x}",reads:.55,note:"the text before the number picks it out; the unit after it is ignored"},{name:"1234-5",pattern:"-{x}",reads:5,note:"the number after the dash"},{name:"1234-5",pattern:"{x}",reads:1234,note:"with nothing around it, {x} takes the first number"},{name:"A-1-5",pattern:"A-1-{x}",reads:5,note:"a pattern fits the first place it can, so add more of the name"},{name:"VDD=1.2V_IDS",pattern:"VDD={x}",reads:1.2},{name:"Idsat Vg=0.8 Vd=1.0",pattern:"Vd={x}",reads:1,note:"the second of two levels"},{name:"Tj-40C_Vth",pattern:"Tj{x}C",reads:-40,note:"a minus sign belongs to the number"},{name:"Step 03 of 12",pattern:"Step {x}",reads:3},{name:"PAT_7_RUN2",pattern:"PAT_{x}_",reads:7,note:"text after {x} must follow it, so RUN2 is not read"},{name:"LRS_STATS_12K",pattern:"LRS_STATS_{x}",reads:12e3,note:"{x} reads the K as kilo"},{name:"LRS_STATS_12K",pattern:"LRS_STATS_{x}K",reads:12,note:"write the K in the pattern to keep the number as the name shows it"},{name:"Fmax_25C_0.55V",pattern:"C_{x}",reads:.55,note:"the text just before the number, here the end of the temperature"},{name:"VCC_1.8V_TEMP_125",pattern:"TEMP_{x}",reads:125,note:"other numbers in the name are ignored"},{name:"LRS_RUN3_STATS_12K",pattern:"LRS*STATS_{x}",reads:12e3,note:"* skips any length of text between the two parts"},{name:"fmax @ 0.55 v",pattern:"FMAX @ {x}",reads:.55,note:"text matches in any case"},{name:"Vth @ 50mV",pattern:"@ {x}",reads:.05,note:"the m of mV is read as milli, so 50 mV is 0.05"},{name:"Delay 5ns @ 25C",pattern:"Delay {x}",reads:5e-9,note:"a unit with a prefix is scaled to its base unit"},{name:"Leak I=1e-6A",pattern:"I={x}",reads:1e-6,note:"exponent notation is read"},{name:"12314",pattern:"123?{x}",reads:4,note:"? is any one character, so names of one shape can be read by position: here the digit after 123 and one more"},{name:"12336",pattern:"123?{x}",reads:6,note:"the same pattern on the next name"}],NAME_PATTERN_LIMIT="A pattern reads a whole number or nothing: it cannot take part of a number, so reading the last digit of 12314 needs the characters before it spelled out or skipped with ? (123?{x}), which suits names of one length. It cannot count back from the end of a name. For names of uneven length, choose Values and type the X values instead.";export function compileXNamePattern(e){const t=[];let n="",o=0;const a=()=>{n&&(t.push({kind:"lit",text:n.toLowerCase()}),n="")};for(let s=0;s<e.length;s++){if(e.startsWith("{x}",s)){a(),t.push({kind:"x"}),o++,s+=2;continue}if(e[s]==="*"){a(),t.at(-1)?.kind!=="star"&&t.push({kind:"star"});continue}if(e[s]==="?"){a(),t.push({kind:"any"});continue}n+=e[s]}return a(),o!==1?{error:`the pattern must contain {x} exactly once (it has ${o})`}:{source:e,parts:t}}const S=/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?/;function T(e,t){const n=S.exec(e.slice(t));if(!n)return[];const o=n[0],a=t+o.length,s=g(e,a);if(s==="ambiguous"){const u=e.slice(t,a+1+(/^\p{L}+/u.exec(e.slice(a+1))?.[0].length??0));return[{value:Number(o),end:a,literalMustFollow:u}]}const l=[];if(s!==null){const[u,r="0"]=o.split(/[eE]/);l.push({value:+`${u}e${Number(r)+s}`,end:a+1})}return l.push({value:Number(o),end:a}),l}export function readXFromName(e,t){const{parts:n}=e;let o;const a=t.toLowerCase(),s=new Set,l=(r,i)=>r*(t.length+1)+i;function u(r,i){if(r===n.length)return NaN;if(s.has(l(r,i)))return;const h=n[r];let f;if(h.kind==="lit")a.startsWith(h.text,i)&&(f=u(r+1,i+h.text.length));else if(h.kind==="any")i<t.length&&(f=u(r+1,i+(t.codePointAt(i)>65535?2:1)));else if(h.kind==="star")for(let d=i;d<=t.length&&f===void 0;d++)f=u(r+1,d);else for(const d of T(t,i)){if(d.literalMustFollow!==void 0){const x=n[r+1];if(x?.kind!=="lit"||!a.startsWith(x.text,d.end)){o??=d.literalMustFollow;continue}}if(u(r+1,d.end)!==void 0){f=d.value;break}}return f===void 0&&s.add(l(r,i)),f}for(let r=0;r<=t.length;r++){const i=u(0,r);if(i!==void 0)return Number.isFinite(i)?i:null}return o!==void 0?{ambiguous:o}:null}
|
|
@@ -15,13 +15,15 @@ export type ImpactTier = 'high' | 'medium' | 'low';
|
|
|
15
15
|
export interface SynthesisItem {
|
|
16
16
|
/** From the share of analysed dies lost, the same quantity the items are ranked by. */
|
|
17
17
|
impact: ImpactTier;
|
|
18
|
+
/** Dies lost as a fraction (0–1) of the analysed dies; set once the items are costed. */
|
|
19
|
+
shareOfDies: number;
|
|
18
20
|
/** Estimated dies lost to this item (pass-rate shortfall × dies in the region). */
|
|
19
21
|
diesLost: number;
|
|
20
22
|
severity: StatsSeverity;
|
|
21
23
|
parts: SynthesisPart[];
|
|
22
24
|
/** The leading finding, then the others that stand on the same dies. */
|
|
23
25
|
findingIds: string[];
|
|
24
|
-
/** The item in a few words, for the
|
|
26
|
+
/** The item in a few words, for the compact list: `Ring 2: hard bin 4 (Short), 62 dies`. */
|
|
25
27
|
brief: SynthesisPart;
|
|
26
28
|
}
|
|
27
29
|
/**
|
|
@@ -37,10 +39,14 @@ export interface Synthesis {
|
|
|
37
39
|
headline: SynthesisPart[];
|
|
38
40
|
items: SynthesisItem[];
|
|
39
41
|
/**
|
|
40
|
-
* Items that clear the floor but not the cap,
|
|
41
|
-
*
|
|
42
|
+
* Items that clear the floor but not the cap, so nothing material goes unmentioned: the ones named
|
|
43
|
+
* (each shown in a few words, from its `brief`) and how many more were left out. Absent when the cap
|
|
44
|
+
* shows them all.
|
|
42
45
|
*/
|
|
43
|
-
also?:
|
|
46
|
+
also?: {
|
|
47
|
+
items: SynthesisItem[];
|
|
48
|
+
more: number;
|
|
49
|
+
};
|
|
44
50
|
/** At most two things to keep an eye on, after the items. Absent when there are none. */
|
|
45
51
|
watch?: SynthesisWatch[];
|
|
46
52
|
/** Said instead of the items when none is material, so a good lot reads as one rather than as an empty list. */
|
|
@@ -61,6 +67,10 @@ export interface SynthesisContext {
|
|
|
61
67
|
* lost, and a line saying what was compared.
|
|
62
68
|
*/
|
|
63
69
|
export declare function buildSynthesis(summary: StatsSummary | LotStatsSummary, ctx?: SynthesisContext): Synthesis;
|
|
70
|
+
/** The heading over the compact list of smaller items: the floor is the same one that admits an item at all. */
|
|
71
|
+
export declare const SYNTHESIS_SMALLER_HEADING: string;
|
|
72
|
+
/** A compact-list row: the item in a few words, then its share of the analysed dies. Plain text. */
|
|
73
|
+
export declare const synthesisRowShare: (it: SynthesisItem) => string;
|
|
64
74
|
/** A synthesis as plain text, one line per part, for tests and for any surface that is not HTML. */
|
|
65
75
|
export declare function synthesisText(s: Synthesis): string;
|
|
66
76
|
//# sourceMappingURL=synthesis.d.ts.map
|