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.4 KB · 90 lines tsx
Raw Blame History
1import { Section } from '@/components/ui/section';2import { EmptyState } from '@/components/ui/empty-state';3import { Freshness } from '@/components/ui/freshness';4import { ClaimBadge } from '@/components/ui/badge';5import { SourceBadge, TableProvenance } from '@/components/ui/source-badge';6import { Pager } from '@/components/ui/pager';7import { PublicationList } from '@/components/data/publication-list';8import { literatureCountsFor, recentPublicationsFor, recentPublicationsForCount, PUBLICATION_PAGE_SIZE } from '@/lib/queries/publications';9import { loadProvenance, toInfo } from '@/lib/queries/provenance';10import { fmtInt, isoDate } from '@/lib/format';11import { pageInfo } from '@/lib/pagination';12import type { CancerBundle } from '../load';1314const WINDOW_LABEL: Record<string, string> = { all: 'All time', '10y': 'Last 10 years', '5y': 'Last 5 years', '5y_prior': 'Preceding 5-year window', '12m': 'Last 12 months' };1516export async function ResearchTab({ b, pPage = 1 }: { b: CancerBundle; pPage?: number }) {17  const [counts, pubTotal] = await Promise.all([literatureCountsFor(b.cancer.id), recentPublicationsForCount('cancer', b.descendants)]);18  const info = pageInfo(pPage, PUBLICATION_PAGE_SIZE, pubTotal);19  const [pubs, prov] = await Promise.all([pubTotal > 0 ? recentPublicationsFor('cancer', b.descendants, { page: info.page, pageSize: info.pageSize }) : Promise.resolve([]), loadProvenance(counts.map((c) => c.provenance_id))]);20  const countsProv = counts[0] ? toInfo(prov.get(counts[0].provenance_id), 'derived') : null;21  const hrefFor = (p: number) => `/cancer/${b.cancer.slug}/research${p > 1 ? `?pPage=${p}` : ''}#recent-publications`;22  return (23    <div className="space-y-8">24      <Section id="literature-counts" kicker="Research activity" title="PubMed record counts" description="Each count is the number of PubMed records returned by the exact query shown, at the time it was run. Counts are research-activity signals, not quality measures.">25        {counts.length ? (26          <>27            <TableProvenance p={countsProv ?? { sourceSlug: 'pubmed', sourceName: 'PubMed', layer: 'derived' }} claim={<ClaimBadge kind="computed" />}>28              {counts.length} window{counts.length === 1 ? '' : 's'} · counts computed by CancerIndex from PubMed E-utilities; the exact query is stored with each count.29            </TableProvenance>30            <div className="ci-table-wrap">31              <table className="ci-table">32                <thead>33                  <tr>34                    <th scope="col">Window</th>35                    <th scope="col">Period</th>36                    <th scope="col" className="num">37                      Records (count)38                    </th>39                    <th scope="col">Exact query</th>40                    <th scope="col">Computed</th>41                    <th scope="col">Source</th>42                  </tr>43                </thead>44                <tbody>45                  {counts.map((c) => (46                    <tr key={c.id}>47                      <td>{WINDOW_LABEL[c.window_key] ?? c.window_key}</td>48                      <td className="whitespace-nowrap text-[12.5px] text-ink-3">{c.window_start || c.window_end ? `${c.window_start ?? '…'} → ${c.window_end ?? '…'}` : '—'}</td>49                      <td className="num font-medium">{fmtInt(c.count)}</td>50                      <td>51                        <code className="ci-mono block max-w-[520px] whitespace-pre-wrap break-words text-[11.5px] text-ink-2">{c.query}</code>52                      </td>53                      <td className="whitespace-nowrap text-[12.5px]">{isoDate(c.updated_at)}</td>54                      <td>55                        <SourceBadge compact title={null} p={toInfo(prov.get(c.provenance_id), 'derived') ?? { sourceSlug: 'pubmed', sourceName: 'PubMed' }} />56                      </td>57                    </tr>58                  ))}59                </tbody>60              </table>61            </div>62            <Freshness dataUpdatedAt={counts[0]!.updated_at} extra="source: pubmed E-utilities" />63          </>64        ) : (65          <EmptyState knows={[{ label: 'Trials tab', href: `/cancer/${b.cancer.slug}/trials` }, { label: 'Methodology: publication counts', href: '/methodology#publications_5y' }]}>66            No PubMed query has been run for this entity. Counts require a MeSH-anchored query built from the entity's terminology mappings; the query string is stored with each count so it can be reproduced.67          </EmptyState>68        )}69      </Section>70      <Section id="recent-publications" kicker="Literature" title={`Linked publications (${fmtInt(pubTotal)})`} description={`Publications linked to this entity or its descendants, newest first, ${PUBLICATION_PAGE_SIZE} per page. Link method and validation status are shown; 'candidate' links have not been reviewed.`}>71        {pubs.length ? (72          <>73            <PublicationList74              rows={pubs}75              summary={76                <>77                  Showing {fmtInt(info.from)}–{fmtInt(info.to)} of {fmtInt(pubTotal)} publications78                </>79              }80            />81            <Pager total={pubTotal} pageSize={PUBLICATION_PAGE_SIZE} page={info.page} hrefFor={hrefFor} label="Publication pages" noun="publications" />82          </>83        ) : (84          <EmptyState compact>No publication is linked to this entity yet.</EmptyState>85        )}86      </Section>87    </div>88  );89}90