spb/cancerindex
Public
TypeScript 97.2%
SQL 1.5%
CSS 0.6%
JavaScript 0.5%
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 { Badge, ClaimBadge } from '@/components/ui/badge';6import { SourceBadge } from '@/components/ui/source-badge';7import { getGeographyBySlug, cagrFor, CAGR_FORMULA, CAGR_FORMULA_VERSION } from '@/lib/queries/geography';8import { fmtNum, fmtValue, fmtInt, toDate } from '@/lib/format';910/**11 * Home module "Fastest-rising incidence" (§105, §328): ASIR compound annual growth rate over the last ten12 * available years, computed on the fly from observations. Shown only when ≥ 10 years exist; labelled as13 * derived, with the formula and both endpoints on every row so the number can be reproduced.14 */15export async function RisingModule({ slug = 'united-states', limit = 8 }: { slug?: string; limit?: number }) {16 const geo = await getGeographyBySlug(slug);17 const res = geo ? await cagrFor(geo.id, 'as_incidence_rate', 'all', 10) : null;18 const rows = res?.rows.slice(0, limit) ?? [];19 const first = rows[0];20 return (21 <Section22 id="rising"23 kicker="Trend"24 title={`Fastest-rising incidence${geo ? ` · ${geo.name}` : ''}`}25 description={res ? `Age-standardized incidence rate, both sexes, all ages: compound annual growth rate between ${res.startYear} and ${res.endYear} (the last 10 years published). Derived by CancerIndex from registry observations; not a source figure.` : 'Age-standardized incidence rate: compound annual growth rate over the last 10 published years.'}26 actions={27 geo ? (28 <Link href={`/country/${slug}#trend`} className="ci-link">29 Trend chart →30 </Link>31 ) : undefined32 }33 >34 {!res || rows.length === 0 ? (35 <EmptyState title="Not enough years to compute a trend">36 A 10-year growth rate is computed only when at least ten distinct years of age-standardized incidence exist for the geography{res ? ` (${res.yearsAvailable} available)` : ''}. Nothing is extrapolated from shorter series.37 </EmptyState>38 ) : (39 <>40 <div className="ci-table-wrap">41 <table className="ci-table">42 <thead>43 <tr>44 <th>Cancer</th>45 <th className="num">CAGR (%/yr)</th>46 <th className="num">{res.startYear} (per 100,000)</th>47 <th className="num">{res.endYear} (per 100,000)</th>48 <th className="num">Years</th>49 </tr>50 </thead>51 <tbody>52 {rows.map((r) => (53 <tr key={r.cancer_id}>54 <td>55 <Link className="ci-link" href={`/cancer/${r.slug}/statistics`}>56 {r.canonical_name}57 </Link>58 {r.estimate_types.some((t) => t !== 'observed') ? <Badge tone="warn" className="ml-1.5">includes estimates</Badge> : null}59 </td>60 <td className={`num font-medium ${r.cagr > 0 ? 'text-danger' : r.cagr < 0 ? 'text-ok' : ''}`}>61 {r.cagr > 0 ? '+' : ''}62 {fmtNum(r.cagr * 100, 2)}63 </td>64 <td className="num">{fmtValue(r.start_value, r.unit)}</td>65 <td className="num">{fmtValue(r.end_value, r.unit)}</td>66 <td className="num text-ink-3">{fmtInt(r.n_years)}</td>67 </tr>68 ))}69 </tbody>70 </table>71 </div>72 <div className="mt-2 flex flex-wrap items-center gap-1.5 text-[12px] text-ink-3">73 <ClaimBadge kind="computed" />74 {first ? <SourceBadge p={{ sourceSlug: first.source_slug, layer: 'derived', note: 'Inputs are the two endpoint observations (start and end year) of the source series.' }} /> : null}75 <span>76 formula: <code className="ci-mono">{CAGR_FORMULA}</code>77 </span>78 <span className="ci-mono">{CAGR_FORMULA_VERSION}</span>79 {first?.standard_population ? <span>standard: {first.standard_population}</span> : null}80 </div>81 <p className="mt-1 text-[12px] text-ink-3">82 Rising incidence can reflect true risk changes, screening or diagnostic practice, coding changes or population ageing beyond what standardization removes; the rate says nothing about causes. Falling rates are listed on the <Link className="ci-link" href="/methodology#cagr">methodology page</Link> definition.83 </p>84 <Freshness dataUpdatedAt={rows.map((r) => toDate(r.updated_at)).filter((d): d is Date => !!d).sort((a, b) => b.getTime() - a.getTime())[0] ?? null} sourceVersion={`${res.startYear}–${res.endYear}`} extra={`${fmtInt(res.rows.length)} cancers with a complete 10-year series`} />85 </>86 )}87 </Section>88 );89}90