spb/company-atlas
Public
Python 66.3%
TypeScript 22.7%
JavaScript 8.6%
HTML 1.4%
CSS 0.7%
1import type { Metadata } from 'next';2import { ConfidenceBadge, EventTypeBadge, SignificanceBadge } from '@/components/ui/badges';3import { Container, Note, PageHeader, Section, Unavailable } from '@/components/ui/section';4import { api, safe } from '@/lib/api';5import { METRIC_LABELS } from '@/lib/site';67export const metadata: Metadata = { title: 'Methodology', description: 'How Company Atlas computes Activity Score, Hiring Momentum, Product Velocity, AI Adoption, the Corporate Change Index and significance bands — with formula versions and inputs.' };8export const revalidate = 3600;910export default async function MethodologyPage() {11 const m = await safe(api.methodology());12 const bands = Array.isArray(m?.significance_bands) ? m.significance_bands : m?.significance_bands ? Object.entries(m.significance_bands).map(([label, [min, max]]) => ({ label, min, max })) : [];13 const labels: ReadonlyArray<readonly [string, string]> = m?.confidence_labels14 ? Array.isArray(m.confidence_labels)15 ? m.confidence_labels.map((l) => (typeof l === 'string' ? ([l, ''] as const) : ([l.label, l.min_confidence != null ? `confidence ≥ ${Math.round(l.min_confidence * 100)} %` : ''] as const)))16 : Object.entries(m.confidence_labels).map(([k, v]) => [k, String(v)] as const)17 : [];18 return (19 <Container>20 <PageHeader eyebrow="Methodology" title="Reproducible metrics over observed public pages" lede="Every metric value on the site carries a formula version, its inputs and a computation time. Formula versions bump whenever weights or normalisation change, so historical values remain reproducible." />21 {!m ? (22 <Unavailable what="Methodology" />23 ) : (24 <>25 <Section eyebrow="Metrics" title="Definitions" hairline={false}>26 <div className="divide-y divide-rule border-y border-rule">27 {m.metrics.map((x) => (28 <div key={x.metric} id={x.metric} className="scroll-mt-24 grid gap-2 py-4 md:grid-cols-[14rem_minmax(0,1fr)]">29 <div>30 <p className="font-medium text-ink">{METRIC_LABELS[x.metric] ?? x.metric}</p>31 <p className="mono text-[11px] text-ink-3">32 {x.metric} · {x.formula_version}33 </p>34 </div>35 <div>36 <p className="text-sm text-ink-2">{x.description}</p>37 {x.inputs.length > 0 && (38 <p className="mt-1.5 flex flex-wrap gap-1">39 {x.inputs.map((i) => (40 <span key={i} className="mono rounded-[3px] bg-surface-2 px-1 text-[11px] text-ink-2">41 {i}42 </span>43 ))}44 </p>45 )}46 </div>47 </div>48 ))}49 </div>50 </Section>51 <Section eyebrow="Change detection" title="Significance bands">52 <div className="grid gap-2 sm:grid-cols-5">53 {bands.map((b) => (54 <div key={b.label} className="border border-rule p-3">55 <SignificanceBadge value={(b.min + b.max) / 2} />56 <p className="tnum mt-1 text-sm text-ink-2">57 {b.min.toFixed(2)} – {b.max.toFixed(2)}58 </p>59 </div>60 ))}61 </div>62 <Note className="mt-3">Inputs: percentage of text changed, semantic similarity, page importance, affected structured entities (jobs, plans, people, locations), novelty against the sensor’s history and cross-source confirmation. Only changes above the meaningful band produce events; lower bands are archived, never discarded.</Note>63 </Section>64 <Section eyebrow="Confidence" title="Labels on every inferred value">65 <ul className="divide-y divide-rule border-y border-rule">66 {labels.map(([l, d]) => (67 <li key={l} className="flex flex-wrap items-baseline gap-3 py-2 text-sm">68 <ConfidenceBadge label={l} />69 <span className="text-ink-2">{d}</span>70 </li>71 ))}72 </ul>73 </Section>74 <Section eyebrow="Taxonomy" title="Event types">75 <p className="flex flex-wrap gap-1.5">76 {m.event_types.map((t) => (77 <EventTypeBadge key={t} type={t} />78 ))}79 </p>80 <Note className="mt-3">Subtypes include product launch / no longer listed / renamed, price increase / decrease / new tier, job count increase / decrease, new executive / executive no longer listed, new office / office no longer listed / country expansion, new partnership, acquisition, divestiture, API launch, documentation change, terms change and brand repositioning.</Note>81 </Section>82 <Section eyebrow="Coverage normalisation" title="Why long-term indices are adjusted">83 <p className="max-w-3xl text-sm text-ink-2">As the network grows, raw counts of observations and events rise regardless of what companies do. The Global Corporate Activity Index and industry/country series are normalised by the number of active sensors, the company population, crawl frequency and industry/country coverage in each window, so a rising index means more change per monitored surface — not more surfaces. Baselines are computed per sensor and per company (changes per week, job counts, announcement frequency, volatility) and drive the anomaly score.</p>84 </Section>85 </>86 )}87 </Container>88 );89}90