@wafertools/wafermap 0.32.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 +7 -7
- package/CHANGELOG.md +374 -1
- 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 +57 -15
- 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 +51 -2
- package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
- package/dist/packages/canvas-adapter/summaryPanel.d.ts +49 -11
- package/dist/packages/canvas-adapter/summaryPanel.js +2 -3
- 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/toCanvas.js +1 -1
- package/dist/packages/canvas-adapter/toolbar.d.ts +74 -93
- 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 +409 -51
- 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/aggregates.d.ts +6 -0
- package/dist/packages/core/aggregates.js +1 -1
- package/dist/packages/core/classify.d.ts +3 -2
- package/dist/packages/core/classify.js +1 -1
- package/dist/packages/core/compact.d.ts +205 -0
- package/dist/packages/core/compact.js +3 -0
- package/dist/packages/core/passBins.d.ts +23 -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/axisTicks.d.ts +14 -0
- package/dist/packages/renderer/axisTicks.js +1 -1
- package/dist/packages/renderer/binColors.d.ts +6 -6
- package/dist/packages/renderer/binColors.js +1 -1
- package/dist/packages/renderer/buildView.d.ts +27 -2
- package/dist/packages/renderer/buildView.js +1 -1
- package/dist/packages/renderer/buildWaferMap.js +1 -1
- package/dist/packages/renderer/compactView.d.ts +22 -0
- package/dist/packages/renderer/compactView.js +1 -0
- package/dist/packages/renderer/xyIndicator.d.ts +27 -0
- package/dist/packages/renderer/xyIndicator.js +1 -0
- package/dist/packages/stats/analyzeWaferLot.d.ts +28 -0
- package/dist/packages/stats/analyzeWaferLot.js +1 -1
- package/dist/packages/stats/analyzeWaferMap.d.ts +36 -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/binRows.d.ts +38 -0
- package/dist/packages/stats/binRows.js +1 -0
- package/dist/packages/stats/filterFindings.d.ts +25 -0
- package/dist/packages/stats/filterFindings.js +1 -1
- package/dist/packages/stats/findingText.d.ts +9 -0
- package/dist/packages/stats/findingText.js +2 -0
- 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.d.ts +16 -0
- package/dist/packages/stats/lotDrift.js +1 -0
- package/dist/packages/stats/lotPattern.d.ts +33 -0
- package/dist/packages/stats/lotPattern.js +1 -0
- package/dist/packages/stats/math.d.ts +60 -6
- package/dist/packages/stats/math.js +1 -1
- package/dist/packages/stats/patternClassification.d.ts +32 -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 +44 -0
- package/dist/packages/stats/presentation.js +1 -0
- package/dist/packages/stats/regions.d.ts +15 -0
- package/dist/packages/stats/regions.js +1 -1
- package/dist/packages/stats/renderSummaryReport.d.ts +23 -5
- package/dist/packages/stats/renderSummaryReport.js +32 -27
- package/dist/packages/stats/reportHtml.d.ts +54 -9
- package/dist/packages/stats/reportHtml.js +145 -55
- 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 +76 -0
- package/dist/packages/stats/synthesis.js +2 -0
- 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 +2 -2
- package/dist/packages/stats/yield.d.ts +15 -6
- package/dist/packages/stats/yield.js +1 -1
- package/dist/packages/stats/yieldRows.d.ts +31 -0
- package/dist/packages/stats/yieldRows.js +1 -0
- package/llms.txt +16 -4
- package/package.json +3 -3
- package/dist/packages/stats/findingsNarrative.d.ts +0 -3
- package/dist/packages/stats/findingsNarrative.js +0 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Die } from '../core/dies.js';
|
|
2
2
|
import type { Wafer } from '../core/wafer.js';
|
|
3
|
+
import { type BinColors } from '../renderer/binColors.js';
|
|
3
4
|
import { type BinDef, type TestDef, type YieldSummary, type WaferMapResult } from '../renderer/buildWaferMap.js';
|
|
4
5
|
import type { StatsSummary, AnalyzeWaferMapOptions } from './types.js';
|
|
5
6
|
import { openHtmlReport } from './renderFindingsReport.js';
|
|
@@ -17,8 +18,14 @@ export interface SummaryReportParams {
|
|
|
17
18
|
sbinDefs?: BinDef[];
|
|
18
19
|
testDefs?: TestDef[];
|
|
19
20
|
statsSummary?: StatsSummary;
|
|
20
|
-
passBins
|
|
21
|
-
|
|
21
|
+
/** The map's own pass bins (`WaferMapResult.passBins`): required, there is no default to judge by instead. */
|
|
22
|
+
passBins: number[];
|
|
23
|
+
/** The map's own ring count (`WaferMapResult.ringCount`): required, there is no default. */
|
|
24
|
+
ringCount: number;
|
|
25
|
+
/** @internal The map's own bin colours, so a bar here is the colour of that bin on the map. Resolved with the default palette when omitted. */
|
|
26
|
+
binColors?: BinColors;
|
|
27
|
+
/** @internal Shown inside the app: a click on a finding's row selects it on the map (see `LIVE_FINDINGS_SCRIPT`). */
|
|
28
|
+
live?: boolean;
|
|
22
29
|
}
|
|
23
30
|
/** Generate a full wafer summary as a standalone HTML string suitable for `openHtmlReport()`. */
|
|
24
31
|
export declare function renderSummaryReportHtml(params: SummaryReportParams, options?: {
|
|
@@ -34,6 +41,8 @@ export interface LotSummaryReportParams {
|
|
|
34
41
|
dies?: Die[];
|
|
35
42
|
/** This wafer's own pass bins (`WaferMapResult.passBins`). Omitted ⇒ the top-level `passBins`. */
|
|
36
43
|
passBins?: number[];
|
|
44
|
+
/** This wafer's own ring count (`WaferMapResult.ringCount`). Omitted ⇒ the top-level `ringCount`. */
|
|
45
|
+
ringCount?: number;
|
|
37
46
|
/** Reused directly as `analyzeWaferLot`'s `perWaferSummaries` — the expensive
|
|
38
47
|
* per-wafer pass (`analyzeWaferMap`) is never re-run here. */
|
|
39
48
|
statsSummary?: StatsSummary;
|
|
@@ -49,11 +58,16 @@ export interface LotSummaryReportParams {
|
|
|
49
58
|
hbinDefs?: BinDef[];
|
|
50
59
|
sbinDefs?: BinDef[];
|
|
51
60
|
testDefs?: TestDef[];
|
|
52
|
-
/**
|
|
61
|
+
/** For items that carry no `passBins` of their own (a hand-built map). No default: an item with neither is refused. */
|
|
53
62
|
passBins?: number[];
|
|
63
|
+
/** For items that carry no `ringCount` of their own (a hand-built map); none ⇒ the first item's. No default. */
|
|
54
64
|
ringCount?: number;
|
|
55
65
|
/** Passthrough to the internal per-group `analyzeWaferLot` call, e.g. `{ enableTestValueAnalysis: true }`. */
|
|
56
66
|
analyzeOptions?: AnalyzeWaferMapOptions;
|
|
67
|
+
/** @internal The gallery's bin colours, so a bar here is the colour of that bin on the cards. Resolved per group with the default palette when omitted. */
|
|
68
|
+
binColors?: BinColors;
|
|
69
|
+
/** @internal Shown inside the app: a click on a finding's row selects it on the map. */
|
|
70
|
+
live?: boolean;
|
|
57
71
|
}
|
|
58
72
|
/**
|
|
59
73
|
* Generate a full lot summary as a standalone HTML string suitable for
|
|
@@ -86,7 +100,9 @@ export declare function renderWaferReportHtml(map: ReportMap & {
|
|
|
86
100
|
yield: YieldSummary;
|
|
87
101
|
dataCoverage: SummaryReportParams['dataCoverage'];
|
|
88
102
|
}, summary?: StatsSummary, options?: {
|
|
89
|
-
title?: string;
|
|
103
|
+
title?: string; /** @internal */
|
|
104
|
+
binColors?: BinColors; /** @internal */
|
|
105
|
+
live?: boolean;
|
|
90
106
|
}): string;
|
|
91
107
|
/**
|
|
92
108
|
* The lot summary report for several built maps, as a standalone HTML document.
|
|
@@ -96,6 +112,8 @@ export declare function renderWaferReportHtml(map: ReportMap & {
|
|
|
96
112
|
*/
|
|
97
113
|
export declare function renderLotReportHtml(maps: readonly ReportMap[], options?: {
|
|
98
114
|
title?: string;
|
|
99
|
-
analyzeOptions?: AnalyzeWaferMapOptions;
|
|
115
|
+
analyzeOptions?: AnalyzeWaferMapOptions; /** @internal */
|
|
116
|
+
binColors?: BinColors; /** @internal */
|
|
117
|
+
live?: boolean;
|
|
100
118
|
}): string;
|
|
101
119
|
//# sourceMappingURL=renderSummaryReport.d.ts.map
|
|
@@ -1,62 +1,67 @@
|
|
|
1
|
-
import{waferDisplayLabel as
|
|
2
|
-
`)),d
|
|
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(`
|
|
3
4
|
`);return`<!DOCTYPE html>
|
|
4
5
|
<html lang="en">
|
|
5
6
|
<head>
|
|
6
7
|
<meta charset="UTF-8">
|
|
7
|
-
<title>${y
|
|
8
|
+
<title>${S(y)}</title>
|
|
8
9
|
<style>
|
|
9
|
-
${
|
|
10
|
+
${j()}
|
|
10
11
|
</style>
|
|
11
12
|
</head>
|
|
12
13
|
<body>
|
|
13
14
|
<main class="report">
|
|
14
15
|
<header class="report-header">
|
|
15
|
-
<h1>${y
|
|
16
|
-
<p class="report-subtitle">Generated ${
|
|
16
|
+
<h1>${S(y)}</h1>
|
|
17
|
+
<p class="report-subtitle">Generated ${S(l)}</p>
|
|
17
18
|
</header>
|
|
18
|
-
${
|
|
19
|
-
<p class="footer">Generated ${
|
|
19
|
+
${te(x)}
|
|
20
|
+
<p class="footer">Generated ${S(l)}</p>
|
|
20
21
|
</main>
|
|
22
|
+
${t.live?k:""}
|
|
21
23
|
</body>
|
|
22
|
-
</html>`}function
|
|
23
|
-
`))}const
|
|
24
|
-
`)}`)}if(!
|
|
25
|
-
`)+
|
|
26
|
-
`);return{
|
|
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
|
+
`)}`)}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 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">
|
|
27
30
|
<header class="report-header">
|
|
28
|
-
<h1>${
|
|
29
|
-
<p class="report-subtitle">Generated ${
|
|
31
|
+
<h1>${S(t)}</h1>
|
|
32
|
+
<p class="report-subtitle">Generated ${S(i)}</p>
|
|
30
33
|
</header>
|
|
31
|
-
${
|
|
32
|
-
<p class="footer">Generated ${
|
|
33
|
-
</main>`}export function renderLotSummaryReportHtml(t,
|
|
34
|
+
${te(a,n)}
|
|
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,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>
|
|
34
37
|
<html lang="en">
|
|
35
38
|
<head>
|
|
36
39
|
<meta charset="UTF-8">
|
|
37
|
-
<title>${
|
|
40
|
+
<title>${S(h)}</title>
|
|
38
41
|
<style>
|
|
39
|
-
${
|
|
42
|
+
${j()}
|
|
40
43
|
</style>
|
|
41
44
|
</head>
|
|
42
45
|
<body>
|
|
43
|
-
${
|
|
46
|
+
${pe(h,l,m)}
|
|
47
|
+
${t.live?k:""}
|
|
44
48
|
</body>
|
|
45
|
-
</html>`}const
|
|
46
|
-
${
|
|
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
|
+
${y}`).join(`
|
|
47
51
|
`);return`<!DOCTYPE html>
|
|
48
52
|
<html lang="en">
|
|
49
53
|
<head>
|
|
50
54
|
<meta charset="UTF-8">
|
|
51
|
-
<title>${
|
|
55
|
+
<title>${S(p)}</title>
|
|
52
56
|
<style>
|
|
53
|
-
${
|
|
57
|
+
${j()}
|
|
54
58
|
</style>
|
|
55
59
|
</head>
|
|
56
60
|
<body>
|
|
57
61
|
<div class="report" style="padding-top:24px;">
|
|
58
62
|
${b}
|
|
59
63
|
</div>
|
|
60
|
-
${
|
|
64
|
+
${w}
|
|
65
|
+
${t.live?k:""}
|
|
61
66
|
</body>
|
|
62
|
-
</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,4 +1,7 @@
|
|
|
1
1
|
import type { StatsFinding, StatsSeverity } from './types.js';
|
|
2
|
+
import { type Synthesis } from './synthesis.js';
|
|
3
|
+
import { type MeterTier } from './presentation.js';
|
|
4
|
+
export type { MeterTier };
|
|
2
5
|
import { type FacetItem } from './facets.js';
|
|
3
6
|
export interface MetricItem {
|
|
4
7
|
label: string;
|
|
@@ -31,19 +34,38 @@ export declare function buildMetadataRows(items: FacetItem[]): Array<{
|
|
|
31
34
|
/** `buildMetadataRows` wrapped in a titled section. Returns `''` when there's nothing to show. */
|
|
32
35
|
export declare function renderMetadataSection(items: FacetItem[]): string;
|
|
33
36
|
export declare function renderMetricGrid(items: MetricItem[]): string;
|
|
34
|
-
|
|
37
|
+
/** A table cell: raw HTML (the caller escapes), or HTML with a class for the `<td>`. */
|
|
38
|
+
export type TableCell = string | {
|
|
39
|
+
html: string;
|
|
40
|
+
className?: string;
|
|
41
|
+
};
|
|
42
|
+
export declare function renderTable(headers: string[], rows: TableCell[][], options?: {
|
|
35
43
|
className?: string;
|
|
36
44
|
emptyMessage?: string;
|
|
37
45
|
}): string;
|
|
38
|
-
export declare function renderSeverityBadge(severity: StatsSeverity): string;
|
|
39
|
-
export declare function formatFindingDelta(finding: Pick<StatsFinding, 'effect' | 'variable'>): string;
|
|
40
46
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
|
|
47
|
+
* Three dots filled by level, with the word beneath. Colour only reinforces it: the dots and the
|
|
48
|
+
* word survive a black-and-white print and colour-blind reading (WCAG 1.4.1), which a bare coloured
|
|
49
|
+
* badge did not. The dots are decoration, hidden from a screen reader that reads the word.
|
|
50
|
+
*/
|
|
51
|
+
export declare function renderMeter(tier: MeterTier, word: string): string;
|
|
52
|
+
export declare function renderSeverityMeter(severity: StatsSeverity): string;
|
|
53
|
+
export declare const severityTier: (severity: StatsSeverity) => MeterTier;
|
|
54
|
+
/**
|
|
55
|
+
* A value with a bar behind it, for scanning a column of yields or shares. The figure is always
|
|
56
|
+
* printed; the bar is a second way to read it, scaled 0–100 (so a yield bar is never stretched
|
|
57
|
+
* to its own column's range).
|
|
45
58
|
*/
|
|
46
|
-
export declare function
|
|
59
|
+
export declare function barCell(percent: number, text: string, className?: string, color?: string): TableCell;
|
|
60
|
+
/** A signed difference in yield points, tinted by `shortfallStep` (only a shortfall of a point or more). */
|
|
61
|
+
export declare function deltaCell(points: number): TableCell;
|
|
62
|
+
/**
|
|
63
|
+
* Section ids and a contents line for a report body. Every section is produced by `renderSection`,
|
|
64
|
+
* so its `<h2>` is the title; the ids carry `scope` so several reports in one document cannot clash.
|
|
65
|
+
* Under four sections there is nothing to navigate and the body is returned unchanged.
|
|
66
|
+
*/
|
|
67
|
+
export declare function withSectionNav(sectionsHtml: string, scope?: string): string;
|
|
68
|
+
export declare function formatFindingDelta(finding: Pick<StatsFinding, 'effect' | 'variable'>): string;
|
|
47
69
|
/**
|
|
48
70
|
* The `†` key under a findings table, as HTML — `''` when no finding in it is
|
|
49
71
|
* about a derived test. A printed report has no hover, so unlike the on-screen
|
|
@@ -64,7 +86,30 @@ export declare function derivedFindingsKeyHtml(findings: StatsFinding[]): string
|
|
|
64
86
|
* `totalWafers` switches the coverage column from "N (region/rest)" to a count
|
|
65
87
|
* of wafers, for lot-level findings.
|
|
66
88
|
*/
|
|
67
|
-
|
|
89
|
+
/**
|
|
90
|
+
* The anchor a finding's table row carries when the table is the page's one table for that
|
|
91
|
+
* summary. Injective (every character outside A–Z a–z 0–9 becomes `_` and its hex code), so two
|
|
92
|
+
* finding ids can never share an anchor, and valid in an `id` attribute whatever the id holds.
|
|
93
|
+
*/
|
|
94
|
+
export declare function findingAnchor(id: string, scope?: string): string;
|
|
95
|
+
/**
|
|
96
|
+
* `anchorScope`: give each row an `id` so the synthesis can link to it, prefixed so two summaries in
|
|
97
|
+
* one document cannot share one. Only for the page's one table per summary — a per-wafer table
|
|
98
|
+
* repeats ids that another wafer's table also carries — so it is left out there.
|
|
99
|
+
*/
|
|
100
|
+
export declare function findingsTableHtml(findings: StatsFinding[], totalWafers?: number, anchorScope?: string, live?: boolean): string;
|
|
101
|
+
/**
|
|
102
|
+
* The synthesis as a section: the headline, the loss items each linked to the finding rows that
|
|
103
|
+
* carry their figures, and what was compared. A part links only when its row is on the page
|
|
104
|
+
* (`anchorIds`), so a sentence never points at nothing.
|
|
105
|
+
*/
|
|
106
|
+
export declare function synthesisSectionHtml(synthesis: Synthesis, anchorIds: ReadonlySet<string>, scope?: string): string;
|
|
107
|
+
/**
|
|
108
|
+
* For a report shown inside the app (the panel's modal): a click on a finding's row tells the page that
|
|
109
|
+
* holds the report, which selects that finding on the map. Inert when the report is opened on its own
|
|
110
|
+
* (no parent window) or where scripts are blocked, and the rows stay a plain table either way.
|
|
111
|
+
*/
|
|
112
|
+
export declare const LIVE_FINDINGS_SCRIPT = "<script>\ndocument.addEventListener('click', function (e) {\n var row = e.target.closest && e.target.closest('[data-finding]');\n if (row && window.parent !== window) window.parent.postMessage({ wmapFinding: row.getAttribute('data-finding') }, '*');\n});\n</script>";
|
|
68
113
|
export declare function formatFindingCoverage(finding: Pick<StatsFinding, 'stats'>, total?: number): string;
|
|
69
114
|
export declare function reportStyles(): string;
|
|
70
115
|
//# sourceMappingURL=reportHtml.d.ts.map
|
|
@@ -1,27 +1,28 @@
|
|
|
1
|
-
import{fmt as
|
|
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>${
|
|
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
17
|
<tbody>${l}</tbody>
|
|
18
|
-
</table>`}export function
|
|
19
|
-
${e}
|
|
20
|
-
|
|
21
|
-
<td class="tight">${
|
|
22
|
-
<td>${
|
|
23
|
-
<td
|
|
24
|
-
<td class="numeric">${
|
|
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
|
+
Click to show it on the map`:""}">
|
|
21
|
+
<td class="tight">${renderSeverityMeter(i.severity)}</td>
|
|
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>
|
|
25
26
|
</tr>`).join(`
|
|
26
27
|
`):'<tr><td colspan="5" class="no-data">No significant findings</td></tr>';return`<table class="report-table findings-table compact">
|
|
27
28
|
<thead>
|
|
@@ -34,9 +35,25 @@ ${e}`:t.summary}export function derivedFindingsKeyHtml(t){const e=u(t.map(r=>({l
|
|
|
34
35
|
</tr>
|
|
35
36
|
</thead>
|
|
36
37
|
<tbody>
|
|
37
|
-
${
|
|
38
|
+
${l}
|
|
38
39
|
</tbody>
|
|
39
|
-
</table>${derivedFindingsKeyHtml(t)}`}export function
|
|
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}
|
|
49
|
+
${p}
|
|
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>
|
|
52
|
+
document.addEventListener('click', function (e) {
|
|
53
|
+
var row = e.target.closest && e.target.closest('[data-finding]');
|
|
54
|
+
if (row && window.parent !== window) window.parent.postMessage({ wmapFinding: row.getAttribute('data-finding') }, '*');
|
|
55
|
+
});
|
|
56
|
+
<\/script>`;export function formatFindingCoverage(t,e){return e!==void 0?`${t.stats.sampleSizeLeft}/${e}`:`${t.stats.sampleSizeLeft}/${t.stats.sampleSizeRight}`}export function reportStyles(){return`
|
|
40
57
|
:root {
|
|
41
58
|
--report-font: Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
42
59
|
--report-text: #1f2328;
|
|
@@ -46,9 +63,18 @@ ${e}`:t.summary}export function derivedFindingsKeyHtml(t){const e=u(t.map(r=>({l
|
|
|
46
63
|
--report-line-strong: #c7ced8;
|
|
47
64
|
--report-surface: #f7f8fa;
|
|
48
65
|
--report-surface-alt: #fbfcfd;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
--report-
|
|
66
|
+
/* A tint says "look here"; the dots and the word say what. Text on a tint is always full ink:
|
|
67
|
+
the muted grey is 3.3:1 on the strongest pastel, below AA. */
|
|
68
|
+
--report-tier-high-bg: #fbe4e2;
|
|
69
|
+
--report-tier-high-edge: #b4372f;
|
|
70
|
+
--report-tier-medium-bg: #fdf0d5;
|
|
71
|
+
--report-tier-medium-edge: #a8741a;
|
|
72
|
+
--report-tier-low-bg: #edf2f7;
|
|
73
|
+
--report-tier-low-edge: #5f7f9f;
|
|
74
|
+
--report-low-1: ${g(1)};
|
|
75
|
+
--report-low-2: ${g(2)};
|
|
76
|
+
--report-low-3: ${g(3)};
|
|
77
|
+
--report-bar: #9db8d3;
|
|
52
78
|
}
|
|
53
79
|
|
|
54
80
|
html {
|
|
@@ -95,6 +121,32 @@ ${e}`:t.summary}export function derivedFindingsKeyHtml(t){const e=u(t.map(r=>({l
|
|
|
95
121
|
|
|
96
122
|
/* Sub-headings and notes inside a section \u2014 used by the split comparison,
|
|
97
123
|
which nests one block per facet under a single section heading. */
|
|
124
|
+
.synthesis-headline { font-weight: 600; margin: 0 0 8px; }
|
|
125
|
+
.synthesis-items { list-style: none; margin: 0 0 8px; padding: 0; }
|
|
126
|
+
.synthesis-items li {
|
|
127
|
+
display: grid;
|
|
128
|
+
grid-template-columns: 96px 1fr;
|
|
129
|
+
gap: 10px;
|
|
130
|
+
align-items: start;
|
|
131
|
+
margin: 0 0 8px;
|
|
132
|
+
padding: 9px 12px;
|
|
133
|
+
border-left: 4px solid var(--report-tier-low-edge);
|
|
134
|
+
border-radius: 0 4px 4px 0;
|
|
135
|
+
background: var(--report-tier-low-bg);
|
|
136
|
+
line-height: 1.45;
|
|
137
|
+
}
|
|
138
|
+
.synthesis-items li.tier-high { border-color: var(--report-tier-high-edge); background: var(--report-tier-high-bg); }
|
|
139
|
+
.synthesis-items li.tier-medium { border-color: var(--report-tier-medium-edge); background: var(--report-tier-medium-bg); }
|
|
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); }
|
|
144
|
+
.synthesis-watch { list-style: none; margin: 0 0 6px; padding: 0; }
|
|
145
|
+
.synthesis-watch li { margin: 0 0 3px; }
|
|
146
|
+
.synthesis-watch strong { margin-right: 6px; font-size: 11px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--report-muted); }
|
|
147
|
+
.synthesis-checked, .synthesis-none { color: var(--report-muted); margin: 0; }
|
|
148
|
+
.synthesis a { color: inherit; text-decoration: underline dotted; }
|
|
149
|
+
|
|
98
150
|
.report-subheading {
|
|
99
151
|
margin: 14px 0 6px;
|
|
100
152
|
font-size: 13px;
|
|
@@ -139,14 +191,6 @@ ${e}`:t.summary}export function derivedFindingsKeyHtml(t){const e=u(t.map(r=>({l
|
|
|
139
191
|
font-style: italic;
|
|
140
192
|
}
|
|
141
193
|
|
|
142
|
-
.findings-narrative {
|
|
143
|
-
margin: 0 0 14px;
|
|
144
|
-
color: var(--report-muted);
|
|
145
|
-
font-size: 13px;
|
|
146
|
-
font-style: italic;
|
|
147
|
-
line-height: 1.6;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
194
|
.definition-item {
|
|
151
195
|
display: contents;
|
|
152
196
|
}
|
|
@@ -268,39 +312,83 @@ ${e}`:t.summary}export function derivedFindingsKeyHtml(t){const e=u(t.map(r=>({l
|
|
|
268
312
|
font-size: 12px;
|
|
269
313
|
}
|
|
270
314
|
|
|
271
|
-
.
|
|
315
|
+
/* Tiles for the key figures, so they read as a row of facts and not a line of the list. */
|
|
316
|
+
.metric-grid .metric {
|
|
317
|
+
padding: 10px 12px;
|
|
318
|
+
background: var(--report-surface);
|
|
319
|
+
border: 1px solid var(--report-line);
|
|
320
|
+
border-radius: 6px;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.report-toc {
|
|
324
|
+
display: flex;
|
|
325
|
+
flex-wrap: wrap;
|
|
326
|
+
gap: 4px 16px;
|
|
327
|
+
margin: 0 0 18px;
|
|
328
|
+
padding: 8px 0;
|
|
329
|
+
border-top: 1px solid var(--report-line);
|
|
330
|
+
border-bottom: 1px solid var(--report-line);
|
|
331
|
+
font-size: 12px;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.report-toc a { color: var(--report-muted); text-decoration: none; }
|
|
335
|
+
.report-toc a:hover { color: var(--report-text); text-decoration: underline; }
|
|
336
|
+
|
|
337
|
+
/* The value is always printed; the bar behind it is for scanning a column. */
|
|
338
|
+
table.report-table td.barcell { min-width: 150px; }
|
|
339
|
+
.cellbar { position: relative; display: block; }
|
|
340
|
+
.cellbar i {
|
|
341
|
+
position: absolute;
|
|
342
|
+
left: 0;
|
|
343
|
+
top: 50%;
|
|
344
|
+
height: 12px;
|
|
345
|
+
transform: translateY(-50%);
|
|
346
|
+
background: var(--report-bar);
|
|
347
|
+
border-radius: 0 2px 2px 0;
|
|
348
|
+
}
|
|
349
|
+
.cellbar span { position: relative; padding-left: 4px; }
|
|
350
|
+
|
|
351
|
+
/* Shortfall below the reference, light to strong. Full ink on every tint (see the tier tokens). */
|
|
352
|
+
table.report-table td.low-1 { background: var(--report-low-1); }
|
|
353
|
+
table.report-table td.low-2 { background: var(--report-low-2); }
|
|
354
|
+
table.report-table td.low-3 { background: var(--report-low-3); }
|
|
355
|
+
table.report-table td.muted-row, table.report-table td.muted-row .cellbar { color: var(--report-muted); }
|
|
356
|
+
.report-legend { margin: 6px 0 0; color: var(--report-muted); font-size: 11px; }
|
|
357
|
+
|
|
358
|
+
.meter {
|
|
272
359
|
display: inline-flex;
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
border: 1px solid transparent;
|
|
278
|
-
border-radius: 999px;
|
|
279
|
-
font-size: 11px; /* print floor \u2014 see the table-header note above */
|
|
280
|
-
font-weight: 600;
|
|
281
|
-
line-height: 1.35;
|
|
282
|
-
letter-spacing: 0.04em;
|
|
283
|
-
text-transform: uppercase;
|
|
360
|
+
flex-direction: column;
|
|
361
|
+
align-items: flex-start;
|
|
362
|
+
gap: 1px;
|
|
363
|
+
line-height: 1.2;
|
|
284
364
|
white-space: nowrap;
|
|
285
365
|
}
|
|
286
366
|
|
|
287
|
-
.
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
border-color: #d9c0bb;
|
|
367
|
+
.meter-dots {
|
|
368
|
+
font-size: 12px;
|
|
369
|
+
letter-spacing: 2px;
|
|
291
370
|
}
|
|
292
371
|
|
|
293
|
-
.
|
|
294
|
-
color: var(--report-
|
|
295
|
-
|
|
296
|
-
|
|
372
|
+
.meter-word {
|
|
373
|
+
color: var(--report-text);
|
|
374
|
+
font-size: 11px;
|
|
375
|
+
font-weight: 600;
|
|
376
|
+
letter-spacing: 0.04em;
|
|
297
377
|
}
|
|
298
378
|
|
|
299
|
-
.
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
379
|
+
/* In the app, a row shows its finding on the map. */
|
|
380
|
+
table.findings-table tr.live { cursor: pointer; }
|
|
381
|
+
table.findings-table tr.live:hover td { box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.04); }
|
|
382
|
+
|
|
383
|
+
/* A finding a pattern explains sits beneath it, indented. */
|
|
384
|
+
table.findings-table tr.nested td:first-child { padding-left: 22px; }
|
|
385
|
+
table.findings-table tr.nested td { font-size: 12px; }
|
|
386
|
+
|
|
387
|
+
/* Findings table: the row carries the tier as a tint and a left edge; the meter carries it in words. */
|
|
388
|
+
table.findings-table tr.tier-high td { background: var(--report-tier-high-bg); }
|
|
389
|
+
table.findings-table tr.tier-medium td { background: var(--report-tier-medium-bg); }
|
|
390
|
+
table.findings-table tr.tier-high td:first-child { box-shadow: inset 4px 0 0 var(--report-tier-high-edge); }
|
|
391
|
+
table.findings-table tr.tier-medium td:first-child { box-shadow: inset 4px 0 0 var(--report-tier-medium-edge); }
|
|
304
392
|
|
|
305
393
|
.footer {
|
|
306
394
|
margin-top: 14px;
|
|
@@ -325,6 +413,8 @@ ${e}`:t.summary}export function derivedFindingsKeyHtml(t){const e=u(t.map(r=>({l
|
|
|
325
413
|
padding: 0;
|
|
326
414
|
}
|
|
327
415
|
|
|
416
|
+
.report-toc { display: none; }
|
|
417
|
+
|
|
328
418
|
section.report-section,
|
|
329
419
|
tr {
|
|
330
420
|
break-inside: avoid;
|