SPB Git forge

spb/cancerindex

Public
37commits 1branches 0releases
2.9 MBsize
maindefault branch
10 days agolast push
TypeScript 97.2% SQL 1.5% CSS 0.6% JavaScript 0.5%
5.3 KB · 79 lines tsx
Raw Blame History
1import Link from 'next/link';2import { Section } from '@/components/ui/section';3import { EmptyState } from '@/components/ui/empty-state';4import { Freshness } from '@/components/ui/freshness';5import { ClaimBadge } from '@/components/ui/badge';6import { SourceBadge } from '@/components/ui/source-badge';7import { ExplorerChart } from '@/components/charts/explorer-chart';8import { resolveGeographyRef, topCancersByLatest, yearRangeFor, explorerObservations, explorerOptions } from '@/lib/queries/explorer';9import { EPI_METRIC_LABEL } from '@/lib/queries/epidemiology';10import { groupComparable } from '@/lib/explorer-series';11import { serializeExplorerParams, type ExplorerState } from '@/lib/explorer-params';12import { fmtDate, fmtInt, humanize, toDate, unitLabel } from '@/lib/format';1314/**15 * Home module: the explorer's default chart — the five top-level cancers with the highest annual deaths16 * in the latest year (computed, not curated), one comparable group only (a single source and standard),17 * with the source line and a link that opens the same selection in /explore.18 */19export async function ExplorerModule({ metric = 'mortality_count', geographySlug = 'united-states', n = 5 }: { metric?: string; geographySlug?: string; n?: number }) {20  const [geo, options] = await Promise.all([resolveGeographyRef(geographySlug), explorerOptions()]);21  const hasMetric = options.metrics.some((m) => m.metric === metric);22  const top = geo && hasMetric ? await topCancersByLatest(metric, geo.id, 'all', 'all', n) : null;23  const range = geo && hasMetric ? await yearRangeFor(metric, geo.id) : null;24  const obs = geo && top && top.cancers.length > 0 ? await explorerObservations({ metric, cancerIds: top.cancers.map((c) => c.id), geographyId: geo.id, sex: 'all', age: 'all', from: range?.min ?? null, to: range?.max ?? null }) : [];25  const groups = groupComparable(obs);26  const g = groups[0];27  const metricLabel = EPI_METRIC_LABEL[metric] ?? humanize(metric);28  const state: ExplorerState | null =29    geo && top30      ? { metric, cancers: top.cancers.map((c) => c.slug), geography: geo.slug, sex: 'all', age: 'all', from: range?.min ?? null, to: range?.max ?? null, view: 'lines', normalize: 'none', page: 1 }31      : null;32  const href = state ? `/explore${serializeExplorerParams(state)}` : '/explore';33  const prov = g ? obs.filter((o) => o.source_slug === g.source_slug).sort((a, b) => String(b.retrieved_at ?? '') .localeCompare(String(a.retrieved_at ?? '')))[0] : undefined;34  const freshest = obs.map((o) => toDate(o.updated_at)).filter((d): d is Date => !!d).sort((a, b) => b.getTime() - a.getTime())[0] ?? null;3536  return (37    <Section38      id="explorer"39      kicker="Data explorer"40      title={g ? `${metricLabel} · ${g.geography_name} · ${g.year_min}–${g.year_max}` : 'Data explorer'}41      description={g && top ? `The ${top.cancers.length} top-level cancers with the highest ${metricLabel.toLowerCase()} in ${top.year}, both sexes, all ages, ${unitLabel(g.unit)} per site group as published by ${g.source_name ?? g.source_slug}. Selection computed from the observations; every metric, cancer, geography, sex, age group and year is one click away.` : 'Chart and download registry observations by metric, cancer, geography, sex, age group and year.'}42      actions={43        <Link href={href} className="ci-link">44          Open in the Data explorer →45        </Link>46      }47    >48      {!g ? (49        <EmptyState title="Data not yet available">50          The explorer chart appears once a licensed registry connector has ingested {metricLabel.toLowerCase()} observations{geo ? ` for ${geo.name}` : ''}.{' '}51          <Link className="ci-link" href="/explore/coverage">52            Coverage matrix53          </Link>54        </EmptyState>55      ) : (56        <>57          <ExplorerChart series={g.series.map((s, i) => ({ name: s.name, points: s.points, dashed: s.dashed, slot: i }))} unit={g.unit} ariaLabel={`${metricLabel} in ${g.geography_name}, ${g.year_min}–${g.year_max}, ${g.series.length} cancers, source ${g.source_slug}`} />58          {/* div, not p: the SourceBadge popover contains a <dl>. */}59          <div className="mt-1.5 flex flex-wrap items-center gap-x-2 gap-y-1 text-[12px] text-ink-3">60            <SourceBadge p={{ sourceSlug: g.source_slug, sourceName: g.source_name, dataset: prov?.dataset ?? null, datasetVersion: prov?.dataset_version ?? null, retrievedAt: prov?.retrieved_at ?? null, sourceUrl: prov?.source_url ?? null, license: prov?.source_license ?? null, layer: 'normalized', evidenceType: 'observed_data' }} />61            <ClaimBadge kind="observed" />62            <span>63              {g.source_name ?? g.source_slug}64              {prov?.retrieved_at ? ` (retrieved ${fmtDate(prov.retrieved_at)})` : ''} · {fmtInt(g.n_obs)} observations · {unitLabel(g.unit)} · both sexes · all ages65              {g.standard_population ? ` · standard: ${g.standard_population}` : ''}66            </span>67            {groups.length > 1 ? (68              <span>69                · {groups.length - 1} other source{groups.length > 2 ? 's' : ''} publish{groups.length > 2 ? '' : 'es'} this metric — shown separately in the explorer, never overlaid70              </span>71            ) : null}72          </div>73          <Freshness dataUpdatedAt={freshest} sourceVersion={prov?.dataset_version ?? null} />74        </>75      )}76    </Section>77  );78}79