SPB Git forge
38commits 1branches 0releases
338.7 MBsize
maindefault branch
2 h agolast push
HTML 53.9% TypeScript 44.5% JavaScript 0.6% SQL 0.5%

Home page tolerates an older dashboard payload (image builds prerender against the live API); post-upgrade runbook

Simon-Pierre Boucher committed 20 days ago (Sep 12, 2026) parent 95c1c4f

2 changed files +65 −3

modified apps/web/src/app/(site)/(home)/page.tsx +5 −3
@@ -53,7 +53,9 @@ function Cell({ label, value, unit, note, href }: { label: string; value: string
53 53 }
54 54
55 55 export default async function HomePage() {
56 − const r = await api.dashboard();
56 + const r0 = await api.dashboard();
57 + // tolerate an older API payload (image builds prerender against the live API): every 2.0 block is optional
58 + const r = r0.ok && r0.data ? { ...r0, data: { ...r0.data, pulse: r0.data.pulse ?? null, fastestGrowingMetros: r0.data.fastestGrowingMetros ?? [], majorProjects: r0.data.majorProjects ?? [], operatorExpansion: r0.data.operatorExpansion ?? [], powerEvents: r0.data.powerEvents ?? [], gridConstraints: r0.data.gridConstraints ?? [], coverage: r0.data.coverage ?? null, ingestion: r0.data.ingestion ?? null } as typeof r0.data } : r0;
57 59 const d: Dashboard | null = r.ok ? r.data : null;
58 60 const stats = d?.stats ?? null;
59 61 const cov = d?.coverage ?? null;
@@ -95,8 +97,8 @@ export default async function HomePage() {
95 97
96 98 <div className="flex flex-col gap-10 pt-8">
97 99 {/* ---------------------------------------------------------------- what changed today */}
98 − <Section title="What changed today" description={d ? `Deterministic counts over the last ${d.pulse.window === "24h" ? "24 hours" : d.pulse.window}, from ${fmtInt(d.pulse.eventsTotal)} deduplicated events.` : "Deterministic counts from the deduplicated event feed."} aside={<MoreLink href={routes.pulse()}>Full pulse</MoreLink>}>
99 − {d ? <PulseBlock pulse={d.pulse} /> : <EmptyState compact title="Pulse unavailable" />}
100 + <Section title="What changed today" description={d?.pulse ? `Deterministic counts over the last ${d.pulse.window === "24h" ? "24 hours" : d.pulse.window}, from ${fmtInt(d.pulse.eventsTotal)} deduplicated events.` : "Deterministic counts from the deduplicated event feed."} aside={<MoreLink href={routes.pulse()}>Full pulse</MoreLink>}>
101 + {d?.pulse ? <PulseBlock pulse={d.pulse} /> : <EmptyState compact title="Pulse unavailable" />}
100 102 </Section>
101 103
102 104 <div className="grid gap-x-8 gap-y-10 lg:grid-cols-2 [&>*]:min-w-0">
added deploy/bin/post-upgrade.sh +60 −0
@@ -0,0 +1,60 @@
1 +#!/usr/bin/env bash
2 +# post-upgrade.sh [step…] — data repair after the 2026-09-12 claim-first upgrade, run from the laptop against the data node.
3 +# steps (default: all, in order):
4 +# baseline dci quality + dci snapshot (flags + snapshot BEFORE repairs)
5 +# repair scripts/quality-apply.ts --hide-false-positives --fix-slugs --link-campuses
6 +# reprocess dci reprocess <every connector> --stale (offline, from archived bodies; new parser versions)
7 +# unbacked scripts/quality-apply.ts --null-unbacked-mw (after reprocess: figures with no site-scoped current claim)
8 +# refresh dci refresh-stats + dci rank + dci quality + dci snapshot
9 +# audit largest values (section 120 of the brief) printed for review
10 +# Everything is idempotent; projects are hidden, never deleted; the pre-upgrade pg_dump is in /srv/dci/backups/pg.
11 +set -euo pipefail
12 +source "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
13 +steps=("$@"); [[ ${#steps[@]} -eq 0 ]] && steps=(baseline repair reprocess unbacked refresh audit)
14 +has() { local s; for s in "${steps[@]}"; do [[ $s == "$1" ]] && return 0; done; return 1; }
15 +
16 +# `cli` service runs the worker image; scripts/ is not baked into the image → bind-mount it
17 +CLI="$(compose_prefix data) run --rm -T"
18 +CLI_SCRIPTS="$(compose_prefix data) run --rm -T -v $REMOTE_DIR/scripts:/app/scripts cli node /app/node_modules/tsx/dist/cli.mjs /app/scripts"
19 +dci() { remote data "$CLI cli $*"; }
20 +script() { remote data "$CLI_SCRIPTS/$*"; }
21 +psqlq() { remote data "docker exec -i dci-postgres-1 psql -U dci -d dci -Atc \"$1\""; }
22 +
23 +if has baseline; then
24 + log "baseline: quality sweep + snapshot"
25 + dci quality | tail -20
26 + dci snapshot | tail -8
27 +fi
28 +if has repair; then
29 + log "repair: hide vetoed projects, fix slugs, link campuses"
30 + script quality-apply.ts --hide-false-positives --fix-slugs --link-campuses | tail -40
31 +fi
32 +if has reprocess; then
33 + log "reprocess: every connector, stale documents only (offline)"
34 + ids=$(psqlq "select string_agg(id, ' ' order by id) from connectors where enabled")
35 + for id in $ids; do
36 + remote data "$CLI cli reprocess $id --stale 2>&1 | grep -E 'reprocess → |selected' | tail -2" || true
37 + done
38 +fi
39 +if has unbacked; then
40 + log "unbacked: null project figures no site-scoped current claim backs"
41 + script quality-apply.ts --null-unbacked-mw | tail -20
42 +fi
43 +if has refresh; then
44 + log "refresh: stats, rankings, quality, snapshot"
45 + dci refresh-stats | tail -1
46 + dci rank | tail -1
47 + dci quality | tail -20
48 + dci snapshot | tail -8
49 +fi
50 +if has audit; then
51 + log "audit: largest values"
52 + psqlq "select 'FAC-OPER' k, f.name, f.it_capacity_mw, f.total_power_mw, f.capacity_scope, f.capacity_semantics, f.record_scope from facilities f where f.merged_into is null and f.status in ('operational','partially_operational','expansion') order by coalesce(f.it_capacity_mw, f.total_power_mw) desc nulls last limit 12"
53 + psqlq "select 'FAC-CONSTR' k, f.name, coalesce(f.planned_power_mw, f.it_capacity_mw, f.total_power_mw) mw, f.capacity_scope from facilities f where f.merged_into is null and f.status = 'under_construction' order by 3 desc nulls last limit 8"
54 + psqlq "select 'PRJ-MW' k, p.name, p.planned_mw, p.capacity_scope, p.project_class, p.evidence_level, left(p.source_url, 80) from projects p where p.merged_into is null and not p.hidden order by p.planned_mw desc nulls last limit 12"
55 + psqlq "select 'PRJ-INV' k, p.name, p.investment_usd/1e9 inv_b, p.investment_scope, p.project_class from projects p where p.merged_into is null and not p.hidden order by p.investment_usd desc nulls last limit 8"
56 + psqlq "select 'OP-PIPE' k, o.name, o.stats->>'plannedMw' planned, o.stats->>'constructionMw' construction, o.stats->>'projectPlannedMw' project_mw from operators o order by coalesce((o.stats->>'projectPlannedMw')::float,0) + coalesce((o.stats->>'plannedMw')::float,0) desc limit 8"
57 + psqlq "select 'METRO-PIPE' k, m.name, m.stats->>'plannedMw' planned, m.stats->>'constructionMw' construction, m.stats->>'projectPlannedMw' project_mw from metros m order by coalesce((m.stats->>'projectPlannedMw')::float,0) + coalesce((m.stats->>'plannedMw')::float,0) desc limit 8"
58 + psqlq "select 'TOTALS' k, (select count(*) from facilities where merged_into is null), (select count(*) from projects where merged_into is null and not hidden), (select count(*) from projects where hidden), (select count(*) from claims), (select count(*) from claims where status='unscoped'), (select count(*) from quality_flags where status='open')"
59 +fi
60 +log "post-upgrade done"
61