import Link from 'next/link'; import { SourcesTable } from '@/components/entity/blocks'; import { ViewBeacon } from '@/components/layout/view-beacon'; import { BreadcrumbLd, Breadcrumbs } from '@/components/meta/breadcrumb-ld'; import { EntityBadge } from '@/components/ui/badges'; import { EntityLink, QualityMark } from '@/components/ui/entity'; import { Container, Note, Section } from '@/components/ui/section'; import { api, safe } from '@/lib/api'; import { fmtAgo, fmtDate, fmtInt } from '@/lib/format'; import { routes, SITE_NAME, SITE_URL } from '@/lib/site'; import type { EntityDetail, EntitySummary } from '@/lib/types'; /* Researcher page (server, async): affiliations (stated relations / organization), papers (`authored` out), coauthors (researchers named on the same papers — from the papers' `authored` relations). Public professional data only; the page says plainly when the record is a name-only row created from an author list. */ function relItems(d: EntityDetail, types: string[], predicates?: string[]): EntitySummary[] { const out: EntitySummary[] = []; const seen = new Set(); for (const g of d.relations ?? []) { if (predicates && !predicates.includes(g.predicate)) continue; for (const it of g.items) if (types.includes(it.entity_type) && !seen.has(it.id)) { seen.add(it.id); out.push(it); } } return out; } export async function ResearcherPage({ d, canonical }: { d: EntityDetail; canonical: string }) { const papers = relItems(d, ['paper'], ['authored']); const affiliations = relItems(d, ['company', 'organization', 'lab', 'university']); const details = await Promise.all(papers.slice(0, 8).map((p) => safe(api.entity(p.slug)))); const co = new Map(); for (const pd of details) { if (!pd) continue; for (const r of relItems(pd, ['researcher'], ['authored'])) { if (r.id === d.id) continue; const cur = co.get(r.id); if (cur) cur.shared += 1; else co.set(r.id, { ...r, shared: 1 }); } } const coauthors = [...co.values()].sort((a, b) => b.shared - a.shared || a.name.localeCompare(b.name)); const nameOnly = !d.identifiers?.length && !d.organization && affiliations.length === 0; const crumbs = [{ name: SITE_NAME, href: '/' }, { name: 'Researchers', href: '/explore/researcher' }, { name: d.name, href: canonical }]; const ld = { '@context': 'https://schema.org', '@type': 'Person', name: d.name, url: `${SITE_URL}${canonical}`, affiliation: (d.organization ? [d.organization] : affiliations).map((o) => ({ '@type': 'Organization', name: o.name })), identifier: d.identifiers?.map((i) => ({ '@type': 'PropertyValue', propertyID: i.scheme, value: i.value })) }; return (