Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ env/
# Note: data/_staging/ (raw collected candidate pool) is intentionally tracked —
# comprehensive data collection is a purpose of this repo.

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

# Testing / coverage
.pytest_cache/
Expand Down
3 changes: 2 additions & 1 deletion site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"scripts": {
"dev": "astro dev",
"build:history": "node scripts/build-history.mjs",
"prebuild": "node scripts/build-history.mjs",
"build:verification": "node scripts/build-verification.mjs",
"prebuild": "node scripts/build-history.mjs && node scripts/build-verification.mjs",
"build": "astro build",
"preview": "astro preview"
},
Expand Down
41 changes: 41 additions & 0 deletions site/scripts/build-verification.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Expose the verification snapshot to the homepage at build time.
//
// The verification aggregate lives at data/_verify/status.json (kept in sync by
// TechEngine's verify-status workflow). The site only serves site/public/v1/**,
// so we copy a trimmed, render-ready view into site/public/v1/verification.json
// during the Pages build. Build-only + gitignored: always reflects the committed
// status.json, never hand-edited, no extra churn in data PRs.

import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";

const SITE_DIR = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const REPO_ROOT = resolve(SITE_DIR, "..");
const SRC = resolve(REPO_ROOT, "data/_verify/status.json");
const OUT = resolve(SITE_DIR, "public/v1/verification.json");

function main() {
let status = null;
try {
status = JSON.parse(readFileSync(SRC, "utf8"));
} catch (err) {
console.warn(`[build-verification] status.json unavailable: ${err.message}`);
}

const out = status
? {
generated_at: status.generated_at || null,
schema: 1,
totals: status.totals || {},
by_category: status.by_category || {},
}
: { generated_at: null, schema: 1, totals: {}, by_category: {} };

mkdirSync(dirname(OUT), { recursive: true });
writeFileSync(OUT, JSON.stringify(out, null, 2));
const v = out.totals.verified_pct;
console.log(`[build-verification] wrote ${OUT}${v != null ? ` (verified ${v}%)` : " (empty)"}`);
}

main();
42 changes: 42 additions & 0 deletions site/src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ const endpoints = [
<a href="#history">History</a>
<a href="#playground">Playground</a>
<a href="#featured">devices</a>
<a href="#verification">verified</a>
<a href="#endpoints">endpoints</a>
<a href={`${base}docs`}>docs</a>
<button id="theme-toggle" class="icon-btn" aria-label="Toggle theme" title="Toggle theme">
Expand Down Expand Up @@ -247,6 +248,47 @@ const endpoints = [
</div>
</section>

<!-- ─────────────── VERIFICATION ─────────────── -->
<section class="block" id="verification">
<div class="wrap">
<div class="sec-head">
<div>
<span class="kicker">05 — Verification</span>
<h2 style="margin-top:14px">Checked against reality, not just well-formed</h2>
<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>
</div>
<span class="num">data/_verify/status.json</span>
</div>

<div class="verify" data-reveal>
<div class="verify-panel">
<div class="verify-headline">
<div class="verify-pct" id="verify-pct">—</div>
<div class="verify-pct-l">records verified<br /><span id="verify-count">loading…</span></div>
</div>
<div class="verify-bar" id="verify-bar" role="img" aria-label="Verification band distribution">
<span class="vb green" style="width:0%"></span>
<span class="vb yellow" style="width:0%"></span>
<span class="vb red" style="width:0%"></span>
</div>
<div class="verify-legend">
<span><i class="vdot green"></i>green <b id="verify-green">—</b></span>
<span><i class="vdot yellow"></i>yellow <b id="verify-yellow">—</b></span>
<span><i class="vdot red"></i>red <b id="verify-red">—</b></span>
</div>
<div class="verify-updated" id="verify-updated"></div>
</div>

<ol class="verify-tiers">
<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>
<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>
<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>
<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>
</ol>
</div>
</div>
</section>

<!-- ─────────────── END CTA ─────────────── -->
<section class="block" id="docs">
<div class="wrap">
Expand Down
57 changes: 55 additions & 2 deletions site/src/scripts/techapi.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,11 +185,13 @@ async function loadList(resource) {
});
})();

function countUp(node, target) {
function countUp(node, target, opts = {}) {
const { decimals = 0, suffix = "" } = opts;
const dur = 1100, t0 = performance.now();
(function tick(t) {
const p = Math.min(1, (t - t0) / dur);
node.textContent = Math.round(target * (1 - Math.pow(1 - p, 3))).toLocaleString();
const v = target * (1 - Math.pow(1 - p, 3));
node.textContent = (decimals ? v.toFixed(decimals) : Math.round(v).toLocaleString()) + suffix;
if (p < 1) requestAnimationFrame(tick);
})(performance.now());
}
Expand Down Expand Up @@ -385,6 +387,57 @@ function countUp(node, target) {
});
})();

/* ============================================================
VERIFICATION — live band distribution + verified ratio
(from v1/verification.json, built from data/_verify/status.json)
============================================================ */
(function verification() {
const pctEl = document.getElementById("verify-pct");
if (!pctEl) return;
const bar = document.getElementById("verify-bar");
const countEl = document.getElementById("verify-count");
const updatedEl = document.getElementById("verify-updated");
const setText = (id, v) => { const el = document.getElementById(id); if (el) el.textContent = v; };

getJSON("v1/verification.json").then((d) => {
const t = d.totals || {};
const total = t.records || 0;
if (!total) throw new Error("empty snapshot");
const pct = t.verified_pct != null ? t.verified_pct : (t.verified || 0) / total * 100;

const obs = new IntersectionObserver((entries) => {
entries.forEach((e) => {
if (!e.isIntersecting) return;
countUp(pctEl, pct, { decimals: 1, suffix: "%" });
obs.disconnect();
});
}, { threshold: .4 });
obs.observe(pctEl);
pctEl.textContent = pct.toFixed(1) + "%";

countEl.textContent = `${(t.verified || 0).toLocaleString()} / ${total.toLocaleString()}`;

const g = t.green || 0, y = t.yellow || 0, r = t.red || 0;
const sum = Math.max(1, g + y + r);
const seg = bar.querySelectorAll(".vb");
if (seg[0]) seg[0].style.width = (g / sum * 100).toFixed(2) + "%";
if (seg[1]) seg[1].style.width = (y / sum * 100).toFixed(2) + "%";
if (seg[2]) seg[2].style.width = (r / sum * 100).toFixed(2) + "%";
setText("verify-green", g.toLocaleString());
setText("verify-yellow", y.toLocaleString());
setText("verify-red", r.toLocaleString());

if (d.generated_at) {
const dt = new Date(d.generated_at);
if (!isNaN(dt)) updatedEl.textContent = "snapshot updated " +
dt.toLocaleDateString(undefined, { month: "short", day: "numeric", year: "numeric" });
}
}).catch(() => {
pctEl.textContent = "—";
if (countEl) countEl.textContent = "snapshot unavailable";
});
})();

/* ============================================================
PLAYGROUND
============================================================ */
Expand Down
36 changes: 36 additions & 0 deletions site/src/styles/techapi.css
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,42 @@ section.block { padding: 76px 0; border-top: 1px solid var(--border); }
.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); }
@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; } }

/* ============================================================
VERIFICATION
============================================================ */
.verify { display: grid; grid-template-columns: .95fr 1.05fr; gap: 16px; --vg: #3fb950; --vy: var(--warn); --vr: var(--err); }
.verify-panel { border: 1px solid var(--border); border-radius: 8px; background: var(--surface); padding: 26px; display: flex; flex-direction: column; }
.verify-headline { display: flex; align-items: baseline; gap: 16px; }
.verify-pct { font-family: var(--mono); font-size: clamp(40px, 6vw, 60px); font-weight: 700; line-height: 1; letter-spacing: -.03em; color: var(--vg); }
.verify-pct-l { color: var(--muted); font-size: 13px; line-height: 1.5; }
.verify-pct-l span { color: var(--fg-2); font-family: var(--mono); }
.verify-bar { display: flex; height: 14px; margin-top: 24px; border-radius: 4px; overflow: hidden; background: var(--surface-3); border: 1px solid var(--border); }
.verify-bar .vb { height: 100%; transition: width .9s cubic-bezier(.2,.7,.2,1); }
.verify-bar .vb.green { background: var(--vg); }
.verify-bar .vb.yellow { background: var(--vy); }
.verify-bar .vb.red { background: var(--vr); }
.verify-legend { display: flex; flex-wrap: wrap; gap: 18px; margin-top: 16px; font-size: 13px; color: var(--muted); }
.verify-legend b { color: var(--fg); font-family: var(--mono); margin-left: 2px; }
.verify-legend .vdot { display: inline-block; width: 9px; height: 9px; border-radius: 2px; margin-right: 7px; vertical-align: baseline; }
.verify-legend .vdot.green { background: var(--vg); }
.verify-legend .vdot.yellow { background: var(--vy); }
.verify-legend .vdot.red { background: var(--vr); }
.verify-updated { margin-top: auto; padding-top: 18px; font-family: var(--mono); font-size: 11.5px; color: var(--faint); letter-spacing: .03em; }
.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; }
.verify-tiers li { display: flex; gap: 14px; padding: 22px; border-right: 1px solid var(--border); border-bottom: 1px solid var(--border); }
.verify-tiers li:nth-child(2n) { border-right: none; }
.verify-tiers li:nth-last-child(-n+2) { border-bottom: none; }
.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); }
.verify-tiers h4 { margin: 4px 0 7px; font-size: 16px; }
.verify-tiers p { margin: 0; color: var(--muted); font-size: 13.5px; line-height: 1.55; }
.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); }
@media (max-width: 860px) {
.verify { grid-template-columns: 1fr; }
.verify-tiers { grid-template-columns: 1fr; }
.verify-tiers li { border-right: none; }
.verify-tiers li:nth-last-child(-n+2):not(:last-child) { border-bottom: 1px solid var(--border); }
}

/* ============================================================
CTA + FOOTER
============================================================ */
Expand Down
Loading