SPB Git forge
28commits 1branches 0releases
7.7 MBsize
maindefault branch
10 days agolast push
Python 66.3% TypeScript 22.7% JavaScript 8.6% HTML 1.4% CSS 0.7%
5.5 KB · 90 lines tsx
Raw Blame History
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