SPB Git forge

spb/websensor

Public
33commits 1branches 0releases
3.4 MBsize
maindefault branch
10 days agolast push
TypeScript 55.4% Python 43.2% SQL 1.2%

web: /coverage and /coverage/[sector] pages (were hidden by the coverage/ gitignore rule, now anchored to the repo root)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Simon-Pierre Boucher committed 11 days ago (Sep 13, 2026) parent b31ffaf

3 changed files +171 −1

modified .gitignore +3 −1
@@ -7,7 +7,9 @@ dist/
7 7 !.env.example
8 8 logs/
9 9 tmp/
10 coverage/
10 +/coverage/
11 +apps/*/coverage/
12 +packages/*/coverage/
11 13 .DS_Store
12 14 .claude/
13 15 deploy/*.mld.json
added apps/web/src/app/coverage/[sector]/page.tsx +101 −0
@@ -0,0 +1,101 @@
1 +import Link from "next/link";
2 +import type { Metadata } from "next";
3 +import { notFound } from "next/navigation";
4 +import { Bar, Chip, Flag, PageHeader, Panel, Stat, Table, Td } from "@/components/ui";
5 +import { api } from "@/lib/api";
6 +import { fmtInt, fmtPct, relTime } from "@/lib/format";
7 +
8 +export const dynamic = "force-dynamic";
9 +
10 +export async function generateMetadata({ params }: { params: Promise<{ sector: string }> }): Promise<Metadata> {
11 + const { sector } = await params;
12 + const c = await api.coverageSector(sector);
13 + if (!c) return { title: "Coverage" };
14 + return { title: `${c.label} · coverage ${c.score.toFixed(1)}`, description: `${fmtInt(c.covered)} of ${fmtInt(c.members)} ${c.label.toLowerCase()} organizations observed by WebSensor (${c.universes.length} universes).`, alternates: { canonical: `/coverage/${c.sector}` } };
15 +}
16 +
17 +function band(score: number): string {
18 + return score >= 80 ? "text-signal" : score >= 60 ? "text-high" : score >= 40 ? "text-warn" : "text-fg-muted";
19 +}
20 +
21 +type Params = { u?: string; status?: string };
22 +
23 +export default async function CoverageSectorPage({ params, searchParams }: { params: Promise<{ sector: string }>; searchParams: Promise<Params> }) {
24 + const { sector } = await params;
25 + const sp = await searchParams;
26 + const c = await api.coverageSector(sector);
27 + if (!c) notFound();
28 + const universes = sp.u ? c.universes.filter((u) => u.key === sp.u) : c.universes;
29 + const filter = (m: { covered: boolean; shadow: number; seed_status: string | null }): boolean => {
30 + if (sp.status === "observed") return m.covered;
31 + if (sp.status === "shadow") return !m.covered && m.shadow > 0;
32 + if (sp.status === "queued") return !m.covered && m.shadow === 0 && (m.seed_status === "queued" || m.seed_status === "discovering");
33 + if (sp.status === "missing") return !m.covered && m.shadow === 0 && !(m.seed_status === "queued" || m.seed_status === "discovering");
34 + return true;
35 + };
36 + const href = (patch: Params): string => {
37 + const n = { ...sp, ...patch };
38 + const q = new URLSearchParams();
39 + if (n.u) q.set("u", n.u);
40 + if (n.status) q.set("status", n.status);
41 + const s = q.toString();
42 + return `/coverage/${c.sector}${s ? `?${s}` : ""}`;
43 + };
44 + return (
45 + <>
46 + <PageHeader
47 + compact
48 + kicker={<Link href="/coverage" className="hover:underline">Coverage</Link>}
49 + title={<span>{c.label} <span className={`font-mono ${band(c.score)}`}>{c.score.toFixed(1)}</span></span>}
50 + description={`${c.description ?? ""} ${fmtInt(c.covered)} of ${fmtInt(c.members)} organizations observed · breadth ${fmtPct(c.breadth)} · depth ${fmtPct(c.depth)} · ${fmtInt(c.sensors)} sensors matched · ${fmtInt(c.shadow_members)} in shadow · ${fmtInt(c.queued)} queued for discovery. Updated ${relTime(c.generated_at)}.`}
51 + />
52 + <div className="panel mb-4 grid grid-cols-2 divide-x divide-y divide-line sm:grid-cols-3 lg:grid-cols-6 lg:divide-y-0">
53 + <Stat label="Score" value={<span className={band(c.score)}>{c.score.toFixed(1)}</span>} hint={`weight ×${c.weight}`} />
54 + <Stat label="Breadth" value={fmtPct(c.breadth)} hint="importance-weighted share observed" />
55 + <Stat label="Depth" value={fmtPct(c.depth)} hint="sensors per organization / 5" />
56 + <Stat label="Observed" value={`${fmtInt(c.covered)} / ${fmtInt(c.members)}`} tone="signal" />
57 + <Stat label="Shadow" value={fmtInt(c.shadow_members)} tone="info" hint="validated, proving signal" />
58 + <Stat label="Countries" value={fmtInt(c.by_country.filter((x) => x.country !== "—").length)} hint={`${c.universes.length} universes`} />
59 + </div>
60 + <div className="mb-2 flex flex-wrap items-center gap-1">
61 + <span className="label mr-1">status</span>
62 + {[["", "all"], ["observed", "observed"], ["shadow", "shadow"], ["queued", "queued"], ["missing", "not yet seeded"]].map(([k, l]) => (
63 + <Chip key={k} href={href({ status: k || undefined })} tone={(sp.status ?? "") === k ? "signal" : "default"}>{l}</Chip>
64 + ))}
65 + <span className="label mx-1">·</span>
66 + <span className="label mr-1">universe</span>
67 + <Chip href={href({ u: undefined })} tone={!sp.u ? "signal" : "default"}>all</Chip>
68 + {c.universes.map((u) => (
69 + <Chip key={u.key} href={href({ u: u.key })} tone={sp.u === u.key ? "signal" : "default"}>{u.label} <span className="font-mono text-[10.5px] text-fg-subtle">{u.covered}/{u.members}</span></Chip>
70 + ))}
71 + </div>
72 + {universes.map((u) => {
73 + const items = u.items.filter(filter);
74 + return (
75 + <Panel key={u.key} dense className="mb-4" title={<span>{u.label} <span className={`font-mono text-[12px] ${band(u.score)}`}>{u.score.toFixed(1)}</span> <span className="font-mono text-[11px] text-fg-subtle">· {u.covered}/{u.members} observed · {fmtInt(u.sensors)} sensors</span></span>} action={u.provenance ? <span className="max-w-[40ch] truncate text-[10.5px] text-fg-subtle" title={u.provenance}>{u.provenance}</span> : undefined}>
76 + {items.length === 0 ? (
77 + <div className="p-3 text-[12px] text-fg-subtle">No member matches this filter.</div>
78 + ) : (
79 + <div className="max-h-[32rem] overflow-auto">
80 + <Table head={["Organization", "Domain", "Country", "Imp.", "Status", "Sensors", "Depth", ""]}>
81 + {items.map((m) => (
82 + <tr key={`${u.key}/${m.domain}`} className="hover:bg-panel-2/60">
83 + <Td>{m.source_id ? <Link href={`/source/${m.source_id}`} className="font-medium hover:underline">{m.name}</Link> : <span className="font-medium">{m.name}</span>}</Td>
84 + <Td mono><Link href={`/domain/${m.domain}`} className="text-fg-muted hover:underline">{m.domain}</Link></Td>
85 + <Td>{m.country ? <span className="inline-flex items-center gap-1 font-mono text-[11px] text-fg-muted"><Flag code={m.country} /> {m.country}</span> : <span className="text-fg-subtle">—</span>}</Td>
86 + <Td mono className="text-fg-subtle">{m.importance}</Td>
87 + <Td>{m.covered ? <Chip tone="ok">observed</Chip> : m.shadow > 0 ? <Chip tone="info">shadow ×{m.shadow}</Chip> : m.seed_status === "queued" || m.seed_status === "discovering" ? <Chip tone="warn">{m.seed_status}</Chip> : m.seed_status === "blocked" ? <Chip tone="danger">blocked</Chip> : m.seed_status ? <Chip>{m.seed_status}</Chip> : <Chip>not seeded</Chip>}</Td>
88 + <Td mono>{m.sensors}{m.shadow ? <span className="text-fg-subtle"> +{m.shadow}</span> : null}</Td>
89 + <Td><div className="w-16"><Bar value={m.depth * 100} tone={m.depth >= 0.8 ? "signal" : m.depth >= 0.4 ? "mid" : "info"} /></div></Td>
90 + <Td>{m.source_id && <Link href={`/source/${m.source_id}`} className="whitespace-nowrap text-[11px] text-fg-subtle hover:text-fg">source →</Link>}</Td>
91 + </tr>
92 + ))}
93 + </Table>
94 + </div>
95 + )}
96 + </Panel>
97 + );
98 + })}
99 + </>
100 + );
101 +}
added apps/web/src/app/coverage/page.tsx +67 −0
@@ -0,0 +1,67 @@
1 +import Link from "next/link";
2 +import type { Metadata } from "next";
3 +import { Bar, Empty, PageHeader, Panel, Stat, Table, Td } from "@/components/ui";
4 +import { api } from "@/lib/api";
5 +import { fmtInt, fmtPct, relTime } from "@/lib/format";
6 +
7 +export const dynamic = "force-dynamic";
8 +export const metadata: Metadata = { title: "Coverage", description: "Global Observation Coverage Score: how much of the world's high-value public web WebSensor observes, sector by sector — listed companies, governments, AI, cloud, cybersecurity, markets, science, healthcare, energy, transport, telecom, logistics, open source and commerce.", alternates: { canonical: "/coverage" } };
9 +
10 +function band(score: number): string {
11 + return score >= 80 ? "text-signal" : score >= 60 ? "text-high" : score >= 40 ? "text-warn" : "text-fg-muted";
12 +}
13 +
14 +export default async function CoveragePage() {
15 + const c = await api.coverage();
16 + if (!c || !c.sectors.length) {
17 + return (
18 + <>
19 + <PageHeader compact kicker="Global Observation Coverage Score" title="Coverage" description="How much of the world's high-value public web do we observe?" />
20 + <Panel><Empty>Coverage universes are not loaded yet.</Empty></Panel>
21 + </>
22 + );
23 + }
24 + const factoryQueued = (c.factory.queued ?? 0) + (c.factory.discovering ?? 0);
25 + return (
26 + <>
27 + <PageHeader
28 + compact
29 + kicker="Global Observation Coverage Score"
30 + title={<span>Coverage <span className={`font-mono ${band(c.global_score)}`}>{c.global_score.toFixed(1)}</span><span className="text-[13px] text-fg-subtle"> / 100</span></span>}
31 + description={`How much of the world's high-value public web do we observe? ${fmtInt(c.covered)} of ${fmtInt(c.members)} organizations in ${c.sectors.length} sector universes have at least one active sensor. The denominator is explicit (config/coverage), the score is explainable (breadth 70 % · depth 30 %), and the Source Factory expands it continuously: ${fmtInt(c.monitored.shadow)} sensors are in shadow monitoring, ${fmtInt(factoryQueued)} organizations are queued for discovery.`}
32 + />
33 + <div className="panel mb-4 grid grid-cols-2 divide-x divide-y divide-line sm:grid-cols-3 lg:grid-cols-6 lg:divide-y-0">
34 + <Stat label="Global score" value={<span className={band(c.global_score)}>{c.global_score.toFixed(1)}</span>} hint="sector-weighted" />
35 + <Stat label="Universe members" value={fmtInt(c.members)} hint={`${c.sectors.length} sectors`} />
36 + <Stat label="Observed" value={fmtInt(c.covered)} hint={fmtPct(c.members ? c.covered / c.members : null)} tone="signal" />
37 + <Stat label="Sensors matched" value={fmtInt(c.sensors_matched)} hint={`${fmtInt(c.monitored.sensors)} active in total`} />
38 + <Stat label="In shadow" value={fmtInt(c.monitored.shadow)} hint="observed, not yet published" tone="info" />
39 + <Stat label="Factory queue" value={fmtInt(factoryQueued)} hint={`${fmtInt(c.factory.discovered ?? 0)} discovered · ${fmtInt(c.factory.blocked ?? 0)} blocked`} />
40 + </div>
41 + <Panel dense title="Sectors" action={<span className="text-[11px] text-fg-subtle">updated {relTime(c.generated_at)}</span>}>
42 + <Table head={["Sector", "Score", "", "Breadth", "Depth", "Members", "Observed", "Shadow", "Queued", "Sensors", "Weight", ""]}>
43 + {c.sectors.map((s) => (
44 + <tr key={s.sector} className="hover:bg-panel-2/60">
45 + <Td><Link href={`/coverage/${s.sector}`} className="font-medium hover:underline">{s.label}</Link><div className="text-[11px] text-fg-subtle">{s.description ?? s.sector}</div></Td>
46 + <Td mono className={`font-semibold ${band(s.score)}`}>{s.score.toFixed(1)}</Td>
47 + <Td><div className="w-24 sm:w-40"><Bar value={s.score} tone={s.score >= 80 ? "signal" : s.score >= 60 ? "high" : s.score >= 40 ? "mid" : "info"} /></div></Td>
48 + <Td mono>{fmtPct(s.breadth)}</Td>
49 + <Td mono>{fmtPct(s.depth)}</Td>
50 + <Td mono>{fmtInt(s.members)}</Td>
51 + <Td mono className="text-signal">{fmtInt(s.covered)}</Td>
52 + <Td mono className={s.shadow_members ? "text-info" : "text-fg-subtle"}>{fmtInt(s.shadow_members)}</Td>
53 + <Td mono className="text-fg-subtle">{fmtInt(s.queued)}</Td>
54 + <Td mono>{fmtInt(s.sensors)}</Td>
55 + <Td mono className="text-fg-subtle">×{s.weight}</Td>
56 + <Td><Link href={`/coverage/${s.sector}`} className="whitespace-nowrap text-[11px] text-fg-subtle hover:text-fg">members →</Link></Td>
57 + </tr>
58 + ))}
59 + </Table>
60 + </Panel>
61 + <p className="mt-3 text-[11.5px] leading-relaxed text-fg-subtle">
62 + Method — {c.method}. A member counts as observed when at least one active (non-shadow) sensor lives on its registrable domain or on a host it declares. Shadow sensors are new sensors the Source Factory found and validated; they are polled and their evidence is kept, but nothing is published until they prove signal over noise. Universes live in <code>config/coverage/*.yaml</code> with provenance and retrieval dates; they are the denominator, not the registry.
63 + {c.issues.length > 0 && <span className="text-warn"> {c.issues.length} universe file{c.issues.length > 1 ? "s" : ""} could not be loaded.</span>}
64 + </p>
65 + </>
66 + );
67 +}
68