Skip to content

Commit 1c3dc2f

Browse files
committed
Normalize dashboard to lower-is-better and move chart above controls
1 parent a4d5104 commit 1c3dc2f

File tree

2 files changed

+138
-115
lines changed

2 files changed

+138
-115
lines changed

docs/posts/2026-02-14-tech-progress-dashboard.html

Lines changed: 71 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@
113113
<meta name="twitter:card" content="summary">
114114
</head>
115115

116-
<body class="nav-fixed quarto-light">
116+
<body class="nav-fixed fullcontent quarto-light">
117117

118118
<div id="quarto-search-results"></div>
119119
<header id="quarto-header" class="headroom fixed-top quarto-banner"><div id="quarto-draft-alert" class="alert alert-warning"><i class="bi bi-pencil-square"></i>Draft</div>
@@ -188,41 +188,14 @@ <h1 class="title">MVP: Technical Progress Dashboard for AI Impact Tracking</h1>
188188
</header><div id="quarto-content" class="quarto-container page-columns page-rows-contents page-layout-article page-navbar">
189189
<!-- sidebar -->
190190
<!-- margin-sidebar -->
191-
<div id="quarto-margin-sidebar" class="sidebar margin-sidebar">
192-
<nav id="TOC" role="doc-toc" class="toc-active">
193-
<h2 id="toc-title">On this page</h2>
194-
195-
<ul class="collapse">
196-
<li><a href="#scope-choices-for-mvp" id="toc-scope-choices-for-mvp" class="nav-link active" data-scroll-target="#scope-choices-for-mvp">Scope choices for MVP</a></li>
197-
<li><a href="#dashboard" id="toc-dashboard" class="nav-link" data-scroll-target="#dashboard">Dashboard</a></li>
198-
<li><a href="#provenance-visible-series" id="toc-provenance-visible-series" class="nav-link" data-scroll-target="#provenance-visible-series">Provenance (visible series)</a></li>
199-
<li><a href="#notes-on-data-quality" id="toc-notes-on-data-quality" class="nav-link" data-scroll-target="#notes-on-data-quality">Notes on data quality</a></li>
200-
</ul>
201-
</nav>
202-
</div>
191+
203192
<!-- main -->
204193
<main class="content quarto-banner-title-block" id="quarto-document-content">
205194

206195

207196

208197

209198

210-
<p>This MVP is built to support <strong>forecasting and measuring AI’s impact on technological progress</strong> using a frontier-first lens.</p>
211-
<section id="scope-choices-for-mvp" class="level2">
212-
<h2 class="anchored" data-anchor-id="scope-choices-for-mvp">Scope choices for MVP</h2>
213-
<ol type="1">
214-
<li>Goal: support forecasting and monitoring of AI-driven acceleration in technical efficiency trends.</li>
215-
<li>Included domains: compute hardware, compute storage, energy, energy storage, biotech, agriculture.</li>
216-
<li>Included metric classes: cost efficiency, physical productivity, and energy efficiency.</li>
217-
<li>Frontier rule: include frontier series by default; include average series when they are informative or frontier data is sparse.</li>
218-
<li>Data inclusion threshold: at least 4 time points, direction-of-improvement defined, source URL recorded.</li>
219-
<li>Fitting method: per-series log-linear fit on historical data (<code>log(value) = a + b * year</code>).</li>
220-
<li>v1 excludes causal attribution and forecasting beyond observed history.</li>
221-
<li>v1 data are seed-series and should be upgraded to automated ingestion in a follow-up.</li>
222-
</ol>
223-
</section>
224-
<section id="dashboard" class="level2">
225-
<h2 class="anchored" data-anchor-id="dashboard">Dashboard</h2>
226199
<script src="https://cdn.plot.ly/plotly-2.35.2.min.js"></script>
227200
<style>
228201
#tpd-controls {
@@ -272,6 +245,12 @@ <h2 class="anchored" data-anchor-id="dashboard">Dashboard</h2>
272245
background: #f3f3f3;
273246
}
274247
</style>
248+
<div id="tpd-plot">
249+
250+
</div>
251+
<div id="tpd-notes">
252+
253+
</div>
275254
<div id="tpd-controls">
276255
<div class="tpd-control">
277256
<p><label for="tpd-domain">Domain</label> <select id="tpd-domain" multiple="" size="6"></select></p>
@@ -294,13 +273,6 @@ <h2 class="anchored" data-anchor-id="dashboard">Dashboard</h2>
294273
<p><label style="margin-top:10px;"> <input id="tpd-show-fit" type="checkbox" checked=""> Show log-linear trend fit </label></p>
295274
</div>
296275
</div>
297-
<div id="tpd-notes">
298-
299-
</div>
300-
<div id="tpd-plot">
301-
302-
</div>
303-
</section>
304276
<section id="provenance-visible-series" class="level2">
305277
<h2 class="anchored" data-anchor-id="provenance-visible-series">Provenance (visible series)</h2>
306278
<table id="tpd-provenance">
@@ -319,7 +291,7 @@ <h2 class="anchored" data-anchor-id="provenance-visible-series">Provenance (visi
319291
Frontier class
320292
</th>
321293
<th>
322-
Direction
294+
Direction (displayed)
323295
</th>
324296
<th>
325297
Points shown
@@ -483,12 +455,17 @@ <h2 class="anchored" data-anchor-id="provenance-visible-series">Provenance (visi
483455
return grouped;
484456
}
485457

458+
function toLowerBetterValue(row) {
459+
if (row.value <= 0) return null;
460+
return row.direction === "higher_better" ? 1 / row.value : row.value;
461+
}
462+
486463
function fitLogLinear(points) {
487-
const filtered = points.filter((p) => p.value > 0);
464+
const filtered = points.filter((p) => p.value_lb > 0);
488465
if (filtered.length < 2) return null;
489466

490467
const xs = filtered.map((p) => p.year);
491-
const ys = filtered.map((p) => Math.log(p.value));
468+
const ys = filtered.map((p) => Math.log(p.value_lb));
492469

493470
const n = xs.length;
494471
const xMean = xs.reduce((a, b) => a + b, 0) / n;
@@ -507,18 +484,15 @@ <h2 class="anchored" data-anchor-id="provenance-visible-series">Provenance (visi
507484
return { a, b };
508485
}
509486

510-
function annualImprovementPercent(slope, direction) {
511-
if (direction === "higher_better") {
512-
return (Math.exp(slope) - 1) * 100;
513-
}
487+
function annualImprovementPercent(slope) {
514488
return (1 - Math.exp(slope)) * 100;
515489
}
516490

517491
function makeDisplayPoints(points, yMode) {
518-
if (yMode === "raw") return points.map((p) => ({ year: p.year, y: p.value }));
519-
const base = points[0]?.value;
492+
if (yMode === "raw") return points.map((p) => ({ year: p.year, y: p.value_lb }));
493+
const base = points[0]?.value_lb;
520494
if (!base || base <= 0) return [];
521-
return points.map((p) => ({ year: p.year, y: p.value / base }));
495+
return points.map((p) => ({ year: p.year, y: p.value_lb / base }));
522496
}
523497

524498
function render() {
@@ -535,17 +509,25 @@ <h2 class="anchored" data-anchor-id="provenance-visible-series">Provenance (visi
535509
const search = controls.search.value.trim().toLowerCase();
536510
const showFit = controls.showFit.checked;
537511

538-
const filteredRows = TPD_DATA.filter((row) => {
539-
if (!domainSel.includes(row.domain)) return false;
540-
if (!typeSel.includes(row.metric_type)) return false;
541-
if (row.year < yearMin || row.year > yearMax) return false;
542-
if (search && !row.name.toLowerCase().includes(search)) return false;
543-
544-
if (frontierSel === "frontier_only" && row.frontier_class !== "frontier") return false;
545-
if (frontierSel === "average_only" && row.frontier_class !== "average") return false;
546-
547-
return true;
548-
});
512+
const filteredRows = TPD_DATA
513+
.filter((row) => {
514+
if (!domainSel.includes(row.domain)) return false;
515+
if (!typeSel.includes(row.metric_type)) return false;
516+
if (row.year < yearMin || row.year > yearMax) return false;
517+
if (search && !row.name.toLowerCase().includes(search)) return false;
518+
519+
if (frontierSel === "frontier_only" && row.frontier_class !== "frontier") return false;
520+
if (frontierSel === "average_only" && row.frontier_class !== "average") return false;
521+
522+
return true;
523+
})
524+
.map((row) => ({
525+
...row,
526+
value_lb: toLowerBetterValue(row),
527+
unit_lb: row.direction === "higher_better" ? `1 / (${row.unit})` : row.unit,
528+
direction_lb: "lower_better"
529+
}))
530+
.filter((row) => row.value_lb !== null);
549531

550532
const bySeries = groupBySeries(filteredRows);
551533

@@ -557,7 +539,12 @@ <h2 class="anchored" data-anchor-id="provenance-visible-series">Provenance (visi
557539

558540
seriesToPlot.forEach((rows) => {
559541
const first = rows[0];
560-
rawUnitSet.add(first.unit);
542+
rawUnitSet.add(first.unit_lb);
543+
const fit = fitLogLinear(rows);
544+
const annualPct = fit ? annualImprovementPercent(fit.b) : null;
545+
const seriesLabel = annualPct === null
546+
? first.name
547+
: `${first.name} (${annualPct.toFixed(1)}%/yr)`;
561548

562549
const displayPoints = makeDisplayPoints(rows, yMode);
563550
if (!displayPoints.length) return;
@@ -567,27 +554,26 @@ <h2 class="anchored" data-anchor-id="provenance-visible-series">Provenance (visi
567554
mode: "lines+markers",
568555
x: displayPoints.map((d) => d.year),
569556
y: displayPoints.map((d) => d.y),
570-
name: first.name,
557+
name: seriesLabel,
571558
hovertemplate:
572559
"<b>%{fullData.name}</b><br>Year: %{x}<br>Value: %{y:.4g}<extra></extra>"
573560
});
574561

575562
if (showFit) {
576-
const fit = fitLogLinear(rows);
577563
if (fit) {
578564
const xFit = rows.map((p) => p.year);
579565
const yFitRaw = xFit.map((x) => Math.exp(fit.a + fit.b * x));
580566
const yFit = yMode === "raw"
581567
? yFitRaw
582568
: yFitRaw.map((v) => v / yFitRaw[0]);
583569

584-
const annualPct = annualImprovementPercent(fit.b, first.direction);
585570
traces.push({
586571
type: "scatter",
587572
mode: "lines",
588573
x: xFit,
589574
y: yFit,
590-
name: `${first.name} trend (${annualPct.toFixed(1)}%/yr)`,
575+
name: `${seriesLabel} trend`,
576+
showlegend: false,
591577
line: { dash: "dot", width: 2 },
592578
hovertemplate:
593579
"<b>%{fullData.name}</b><br>Year: %{x}<br>Fitted: %{y:.4g}<extra></extra>"
@@ -601,18 +587,23 @@ <h2 class="anchored" data-anchor-id="provenance-visible-series">Provenance (visi
601587
metricType: first.metric_type,
602588
frontierClass: first.frontier_class,
603589
direction: first.direction,
590+
transformed: first.direction === "higher_better",
604591
points: rows.length,
605592
sourceName: first.source_name,
606593
sourceUrl: first.source_url,
607-
note: first.provenance_note
594+
note: first.direction === "higher_better"
595+
? `${first.provenance_note} Display transformed as 1/value so lower is better.`
596+
: first.provenance_note
608597
});
609598
});
610599

611600
const layout = {
612601
title: "Technical Progress Over Time",
613602
xaxis: { title: "Year" },
614603
yaxis: {
615-
title: yMode === "indexed" ? "Indexed value (first visible year = 1)" : "Raw value",
604+
title: yMode === "indexed"
605+
? "Indexed value (first visible year = 1, lower is better)"
606+
: "Raw value (lower-is-better transformed)",
616607
type: yScale
617608
},
618609
legend: { orientation: "h", y: -0.25 },
@@ -642,7 +633,7 @@ <h2 class="anchored" data-anchor-id="provenance-visible-series">Provenance (visi
642633
<td>${row.domain}</td>
643634
<td>${row.metricType}</td>
644635
<td>${row.frontierClass}</td>
645-
<td>${row.direction}</td>
636+
<td>lower_better</td>
646637
<td>${row.points}</td>
647638
<td><a href="${row.sourceUrl}" target="_blank" rel="noopener noreferrer">${row.sourceName}</a></td>
648639
<td>${row.note}</td>
@@ -684,8 +675,23 @@ <h2 class="anchored" data-anchor-id="notes-on-data-quality">Notes on data qualit
684675
<ul>
685676
<li>This is a <strong>seed MVP dataset</strong> intended to validate taxonomy + interaction design.</li>
686677
<li>Several series above are marked as seed points from published charts; next step is scripted ingestion from source APIs/files.</li>
678+
<li>All displayed series are transformed to <strong>lower-is-better</strong> (<code>1/value</code> for originally higher-is-better metrics).</li>
687679
<li>Because units differ across technologies, indexed mode is the main comparison mode.</li>
688680
</ul>
681+
<p>This MVP is built to support <strong>forecasting and measuring AI’s impact on technological progress</strong> using a frontier-first lens.</p>
682+
</section>
683+
<section id="scope-choices-for-mvp" class="level2">
684+
<h2 class="anchored" data-anchor-id="scope-choices-for-mvp">Scope choices for MVP</h2>
685+
<ol type="1">
686+
<li>Goal: support forecasting and monitoring of AI-driven acceleration in technical efficiency trends.</li>
687+
<li>Included domains: compute hardware, compute storage, energy, energy storage, biotech, agriculture.</li>
688+
<li>Included metric classes: cost efficiency, physical productivity, and energy efficiency.</li>
689+
<li>Frontier rule: include frontier series by default; include average series when they are informative or frontier data is sparse.</li>
690+
<li>Data inclusion threshold: at least 4 time points, direction-of-improvement defined, source URL recorded.</li>
691+
<li>Fitting method: per-series log-linear fit on historical data (<code>log(value) = a + b * year</code>).</li>
692+
<li>v1 excludes causal attribution and forecasting beyond observed history.</li>
693+
<li>v1 data are seed-series and should be upgraded to automated ingestion in a follow-up.</li>
694+
</ol>
689695

690696

691697
</section>

0 commit comments

Comments
 (0)