Skip to content

Commit 670bd28

Browse files
committed
feat(site): add a Verification section to the homepage
Surfaces the verification layer the dataset now ships with. A new "05 — Verification" section shows a live snapshot — the verified ratio (currently ~9.8%) and the green/yellow/red band distribution — alongside a short T0–T3 explainer (offline trust score → source liveness → Wikidata cross-reference → reality-based promotion). The snapshot is exposed at build time: build-verification.mjs copies a trimmed view of data/_verify/status.json into site/public/v1/verification.json (build-only, gitignored). The page reads that one static file. Adds a "verified" nav link and a backward-compatible decimals/suffix option to countUp. Refs #1
1 parent 07be490 commit 670bd28

6 files changed

Lines changed: 178 additions & 4 deletions

File tree

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,9 @@ env/
3131
# Note: data/_staging/ (raw collected candidate pool) is intentionally tracked —
3232
# comprehensive data collection is a purpose of this repo.
3333

34-
# Build-only: regenerated from full git history on every Pages deploy (site/scripts/build-history.mjs)
34+
# Build-only: regenerated on every Pages deploy (site/scripts/build-*.mjs)
3535
site/public/v1/history.json
36+
site/public/v1/verification.json
3637

3738
# Testing / coverage
3839
.pytest_cache/

site/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
"scripts": {
77
"dev": "astro dev",
88
"build:history": "node scripts/build-history.mjs",
9-
"prebuild": "node scripts/build-history.mjs",
9+
"build:verification": "node scripts/build-verification.mjs",
10+
"prebuild": "node scripts/build-history.mjs && node scripts/build-verification.mjs",
1011
"build": "astro build",
1112
"preview": "astro preview"
1213
},
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Expose the verification snapshot to the homepage at build time.
2+
//
3+
// The verification aggregate lives at data/_verify/status.json (kept in sync by
4+
// TechEngine's verify-status workflow). The site only serves site/public/v1/**,
5+
// so we copy a trimmed, render-ready view into site/public/v1/verification.json
6+
// during the Pages build. Build-only + gitignored: always reflects the committed
7+
// status.json, never hand-edited, no extra churn in data PRs.
8+
9+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
10+
import { dirname, resolve } from "node:path";
11+
import { fileURLToPath } from "node:url";
12+
13+
const SITE_DIR = resolve(dirname(fileURLToPath(import.meta.url)), "..");
14+
const REPO_ROOT = resolve(SITE_DIR, "..");
15+
const SRC = resolve(REPO_ROOT, "data/_verify/status.json");
16+
const OUT = resolve(SITE_DIR, "public/v1/verification.json");
17+
18+
function main() {
19+
let status = null;
20+
try {
21+
status = JSON.parse(readFileSync(SRC, "utf8"));
22+
} catch (err) {
23+
console.warn(`[build-verification] status.json unavailable: ${err.message}`);
24+
}
25+
26+
const out = status
27+
? {
28+
generated_at: status.generated_at || null,
29+
schema: 1,
30+
totals: status.totals || {},
31+
by_category: status.by_category || {},
32+
}
33+
: { generated_at: null, schema: 1, totals: {}, by_category: {} };
34+
35+
mkdirSync(dirname(OUT), { recursive: true });
36+
writeFileSync(OUT, JSON.stringify(out, null, 2));
37+
const v = out.totals.verified_pct;
38+
console.log(`[build-verification] wrote ${OUT}${v != null ? ` (verified ${v}%)` : " (empty)"}`);
39+
}
40+
41+
main();

site/src/pages/index.astro

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ const endpoints = [
5454
<a href="#history">History</a>
5555
<a href="#playground">Playground</a>
5656
<a href="#featured">devices</a>
57+
<a href="#verification">verified</a>
5758
<a href="#endpoints">endpoints</a>
5859
<a href={`${base}docs`}>docs</a>
5960
<button id="theme-toggle" class="icon-btn" aria-label="Toggle theme" title="Toggle theme">
@@ -247,6 +248,47 @@ const endpoints = [
247248
</div>
248249
</section>
249250

251+
<!-- ─────────────── VERIFICATION ─────────────── -->
252+
<section class="block" id="verification">
253+
<div class="wrap">
254+
<div class="sec-head">
255+
<div>
256+
<span class="kicker">05 — Verification</span>
257+
<h2 style="margin-top:14px">Checked against reality, not just well-formed</h2>
258+
<p class="sec-sub">Passing validation only means a record is <em>shaped</em> right. A separate verification layer scores how confident we are that it describes a real, existing device — and only then sets its <code>verified</code> flag.</p>
259+
</div>
260+
<span class="num">data/_verify/status.json</span>
261+
</div>
262+
263+
<div class="verify" data-reveal>
264+
<div class="verify-panel">
265+
<div class="verify-headline">
266+
<div class="verify-pct" id="verify-pct">—</div>
267+
<div class="verify-pct-l">records verified<br /><span id="verify-count">loading…</span></div>
268+
</div>
269+
<div class="verify-bar" id="verify-bar" role="img" aria-label="Verification band distribution">
270+
<span class="vb green" style="width:0%"></span>
271+
<span class="vb yellow" style="width:0%"></span>
272+
<span class="vb red" style="width:0%"></span>
273+
</div>
274+
<div class="verify-legend">
275+
<span><i class="vdot green"></i>green <b id="verify-green">—</b></span>
276+
<span><i class="vdot yellow"></i>yellow <b id="verify-yellow">—</b></span>
277+
<span><i class="vdot red"></i>red <b id="verify-red">—</b></span>
278+
</div>
279+
<div class="verify-updated" id="verify-updated"></div>
280+
</div>
281+
282+
<ol class="verify-tiers">
283+
<li><span class="vt-n">T0</span><div><h4>Offline trust score</h4><p>Completeness, cross-field consistency and source authority score every record into a <b>green / yellow / red</b> band — deterministic, no network.</p></div></li>
284+
<li><span class="vt-n">T1</span><div><h4>Source liveness</h4><p>Each record's <code>source_urls</code> are checked for reachability.</p></div></li>
285+
<li><span class="vt-n">T2</span><div><h4>External cross-reference</h4><p>Claims are matched against Wikidata by exact title and release year.</p></div></li>
286+
<li><span class="vt-n">T3</span><div><h4>Promotion</h4><p>A green record becomes <code>verified</code>; an external contradiction vetoes it. Accuracy stays reality-based.</p></div></li>
287+
</ol>
288+
</div>
289+
</div>
290+
</section>
291+
250292
<!-- ─────────────── END CTA ─────────────── -->
251293
<section class="block" id="docs">
252294
<div class="wrap">

site/src/scripts/techapi.js

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,11 +185,13 @@ async function loadList(resource) {
185185
});
186186
})();
187187

188-
function countUp(node, target) {
188+
function countUp(node, target, opts = {}) {
189+
const { decimals = 0, suffix = "" } = opts;
189190
const dur = 1100, t0 = performance.now();
190191
(function tick(t) {
191192
const p = Math.min(1, (t - t0) / dur);
192-
node.textContent = Math.round(target * (1 - Math.pow(1 - p, 3))).toLocaleString();
193+
const v = target * (1 - Math.pow(1 - p, 3));
194+
node.textContent = (decimals ? v.toFixed(decimals) : Math.round(v).toLocaleString()) + suffix;
193195
if (p < 1) requestAnimationFrame(tick);
194196
})(performance.now());
195197
}
@@ -385,6 +387,57 @@ function countUp(node, target) {
385387
});
386388
})();
387389

390+
/* ============================================================
391+
VERIFICATION — live band distribution + verified ratio
392+
(from v1/verification.json, built from data/_verify/status.json)
393+
============================================================ */
394+
(function verification() {
395+
const pctEl = document.getElementById("verify-pct");
396+
if (!pctEl) return;
397+
const bar = document.getElementById("verify-bar");
398+
const countEl = document.getElementById("verify-count");
399+
const updatedEl = document.getElementById("verify-updated");
400+
const setText = (id, v) => { const el = document.getElementById(id); if (el) el.textContent = v; };
401+
402+
getJSON("v1/verification.json").then((d) => {
403+
const t = d.totals || {};
404+
const total = t.records || 0;
405+
if (!total) throw new Error("empty snapshot");
406+
const pct = t.verified_pct != null ? t.verified_pct : (t.verified || 0) / total * 100;
407+
408+
const obs = new IntersectionObserver((entries) => {
409+
entries.forEach((e) => {
410+
if (!e.isIntersecting) return;
411+
countUp(pctEl, pct, { decimals: 1, suffix: "%" });
412+
obs.disconnect();
413+
});
414+
}, { threshold: .4 });
415+
obs.observe(pctEl);
416+
pctEl.textContent = pct.toFixed(1) + "%";
417+
418+
countEl.textContent = `${(t.verified || 0).toLocaleString()} / ${total.toLocaleString()}`;
419+
420+
const g = t.green || 0, y = t.yellow || 0, r = t.red || 0;
421+
const sum = Math.max(1, g + y + r);
422+
const seg = bar.querySelectorAll(".vb");
423+
if (seg[0]) seg[0].style.width = (g / sum * 100).toFixed(2) + "%";
424+
if (seg[1]) seg[1].style.width = (y / sum * 100).toFixed(2) + "%";
425+
if (seg[2]) seg[2].style.width = (r / sum * 100).toFixed(2) + "%";
426+
setText("verify-green", g.toLocaleString());
427+
setText("verify-yellow", y.toLocaleString());
428+
setText("verify-red", r.toLocaleString());
429+
430+
if (d.generated_at) {
431+
const dt = new Date(d.generated_at);
432+
if (!isNaN(dt)) updatedEl.textContent = "snapshot updated " +
433+
dt.toLocaleDateString(undefined, { month: "short", day: "numeric", year: "numeric" });
434+
}
435+
}).catch(() => {
436+
pctEl.textContent = "—";
437+
if (countEl) countEl.textContent = "snapshot unavailable";
438+
});
439+
})();
440+
388441
/* ============================================================
389442
PLAYGROUND
390443
============================================================ */

site/src/styles/techapi.css

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,42 @@ section.block { padding: 76px 0; border-top: 1px solid var(--border); }
493493
.hiw-card p code { background: var(--surface-2); padding: 1px 6px; border-radius: 3px; border: 1px solid var(--border); font-size: .9em; color: var(--fg-2); }
494494
@media (max-width: 760px) { .hiw { grid-template-columns: 1fr; } .hiw-card { border-right: none; border-bottom: 1px solid var(--border); } .hiw-card:last-child { border-bottom: none; } }
495495

496+
/* ============================================================
497+
VERIFICATION
498+
============================================================ */
499+
.verify { display: grid; grid-template-columns: .95fr 1.05fr; gap: 16px; --vg: #3fb950; --vy: var(--warn); --vr: var(--err); }
500+
.verify-panel { border: 1px solid var(--border); border-radius: 8px; background: var(--surface); padding: 26px; display: flex; flex-direction: column; }
501+
.verify-headline { display: flex; align-items: baseline; gap: 16px; }
502+
.verify-pct { font-family: var(--mono); font-size: clamp(40px, 6vw, 60px); font-weight: 700; line-height: 1; letter-spacing: -.03em; color: var(--vg); }
503+
.verify-pct-l { color: var(--muted); font-size: 13px; line-height: 1.5; }
504+
.verify-pct-l span { color: var(--fg-2); font-family: var(--mono); }
505+
.verify-bar { display: flex; height: 14px; margin-top: 24px; border-radius: 4px; overflow: hidden; background: var(--surface-3); border: 1px solid var(--border); }
506+
.verify-bar .vb { height: 100%; transition: width .9s cubic-bezier(.2,.7,.2,1); }
507+
.verify-bar .vb.green { background: var(--vg); }
508+
.verify-bar .vb.yellow { background: var(--vy); }
509+
.verify-bar .vb.red { background: var(--vr); }
510+
.verify-legend { display: flex; flex-wrap: wrap; gap: 18px; margin-top: 16px; font-size: 13px; color: var(--muted); }
511+
.verify-legend b { color: var(--fg); font-family: var(--mono); margin-left: 2px; }
512+
.verify-legend .vdot { display: inline-block; width: 9px; height: 9px; border-radius: 2px; margin-right: 7px; vertical-align: baseline; }
513+
.verify-legend .vdot.green { background: var(--vg); }
514+
.verify-legend .vdot.yellow { background: var(--vy); }
515+
.verify-legend .vdot.red { background: var(--vr); }
516+
.verify-updated { margin-top: auto; padding-top: 18px; font-family: var(--mono); font-size: 11.5px; color: var(--faint); letter-spacing: .03em; }
517+
.verify-tiers { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: 1fr 1fr; gap: 0; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
518+
.verify-tiers li { display: flex; gap: 14px; padding: 22px; border-right: 1px solid var(--border); border-bottom: 1px solid var(--border); }
519+
.verify-tiers li:nth-child(2n) { border-right: none; }
520+
.verify-tiers li:nth-last-child(-n+2) { border-bottom: none; }
521+
.vt-n { flex: none; width: 34px; height: 34px; display: grid; place-items: center; border-radius: 5px; background: var(--surface-2); border: 1px solid var(--border); font-family: var(--mono); font-size: 13px; font-weight: 600; color: var(--accent-text); }
522+
.verify-tiers h4 { margin: 4px 0 7px; font-size: 16px; }
523+
.verify-tiers p { margin: 0; color: var(--muted); font-size: 13.5px; line-height: 1.55; }
524+
.verify-tiers p code, .sec-sub code { background: var(--surface-2); padding: 1px 6px; border-radius: 3px; border: 1px solid var(--border); font-size: .9em; color: var(--fg-2); font-family: var(--mono); }
525+
@media (max-width: 860px) {
526+
.verify { grid-template-columns: 1fr; }
527+
.verify-tiers { grid-template-columns: 1fr; }
528+
.verify-tiers li { border-right: none; }
529+
.verify-tiers li:nth-last-child(-n+2):not(:last-child) { border-bottom: 1px solid var(--border); }
530+
}
531+
496532
/* ============================================================
497533
CTA + FOOTER
498534
============================================================ */

0 commit comments

Comments
 (0)