Elephant Hawk logo Elephant Hawk ← Chrysalis library Start Tools Log In Context Brief

Clinical Trials + PubMed — Usage Guide

How to get value out of the two new evidence streams now flowing into every briefing.
What's new (May 2026): ClinicalTrials.gov v2 and PubMed (NCBI E-utilities) are now first-class data sources alongside openFDA. Wired into the standard fda-radar refresh. Surfaced in 13 of 13 briefings (trials) and 11 of 13 briefings (pubmed). Generated: 2026-05-31.

Contents

  1. TL;DR — Where to look first
  2. The two new warehouse tables
  3. What each briefing now shows
  4. 5 strategic questions you can answer now
  5. Query helpers (analysis layer)
  6. Refreshing the data
  7. Coverage gaps + what NOT to conclude
  8. Future extensions worth scoping

TL;DR — Where to look first

Trials in DB
606
smoke-test (1 week); full 5y after next refresh ~50-100k
PubMed papers in DB
900
smoke-test (1 week); full 5y ~50-200k
Briefings with trials
13 / 13
all of them
Briefings with PubMed
11 / 13
skipping capital_allocation + acquisition_sourcing
Important caveat to internalize before drawing conclusions: these are signal sources, not complete ones. Sponsor name matching is fuzzy (subsidiaries, foreign legal entities, recent renames break joins). Keyword matching is curated, not exhaustive. Trial absence ≠ no trial; publication absence ≠ no research. Always cross-check anything that drives a decision against the live CT.gov or PubMed UI.

The two new warehouse tables

clinical_trials

Pulled from ClinicalTrials.gov v2 API. Filtered to studies whose intervention type is DEVICE, DIAGNOSTIC_TEST, or COMBINATION_PRODUCT. Watermarked on lastUpdatePostDate with a 14-day lookback, so updates to a trial's status (e.g. RECRUITING → COMPLETED) are picked up on the next refresh, not just new trials.

ColumnNotes
nct_idPrimary key. Always NCT + 8 digits.
brief_title / official_titleOfficial title preferred for searching; brief title for display.
overall_statusRECRUITING · ACTIVE_NOT_RECRUITING · ENROLLING_BY_INVITATION · NOT_YET_RECRUITING · COMPLETED · TERMINATED · WITHDRAWN · SUSPENDED
phasePipe-joined for multi-phase (e.g. PHASE2|PHASE3).
lead_sponsor_normalizedSame normalize_applicant() as device_510k.applicant_normalized — enables equi-joins.
lead_sponsor_classINDUSTRY · NIH · OTHER_GOV · ACADEMIC · INDIV — useful for filtering out academic-led trials when you only care about commercial activity.
conditions / interventionsPipe-joined strings. Use LIKE '%dental%' for case-insensitive substring match.
locations_countryPipe-joined unique countries from all trial sites.
enrollment_countActual enrolled (for completed) or estimated (for ongoing). Read with enrollment_type.
start_date / completion_date / last_update_postedAll DATE typed. Order by start_date DESC for "recently launched" views.
rawFull v2 protocolSection as JSON — pull additional fields without re-ingesting.

pubmed_papers

Pulled from NCBI E-utilities (esearch + efetch). Two query strategies running in series each refresh:

ColumnNotes
pmidPrimary key. NCBI's unique paper ID.
title / abstractFull text, useful for keyword searches even outside the curated set.
journal / journal_issnFor "what journals does X publish in" or "what's the top venue for Y" queries.
pub_date / pub_yearWatch for future dates — PubMed sometimes indexes papers with print issue dates that haven't occurred yet.
authorsPipe-joined "Lastname Initials" strings. Compact; full author objects in raw.
affiliationsPipe-joined full affiliation strings. Use LIKE '%india%' for country/city matching.
affiliation_sponsorsPipe-joined list of known sponsor names that matched. Empty for keyword-only matches.
mesh_termsMajor MeSH headings — most precise topical signal NCBI provides.
keyword_matchesWhich curated keyword brought the paper in. NULL if matched only via sponsor.
provenancesponsor · keyword · both — filter by this to keep narrow scope.
doiWhen present, build full-text link as https://doi.org/{doi}.

What each briefing now shows

Briefing Trials view Pubmed view How matched
landscapeTop-20 players (sponsor)Top-20 players (sponsor)From `top_players()` ranked by 510(k)+PMA*2 weight
positioningCast (sponsor) + dental topicalCast (sponsor) + dental keywordsCOMPETITORS list normalized via normalize_applicant()
indiaby_country("India")by_country([India + 8 cities])locations_country / affiliations substring match
chinaby_country("China")by_country([China + 8 cities])same — Beijing, Shanghai, Shenzhen, …
europeby_country([20 EU countries])by_country([20 EU countries])Germany, France, UK, Italy, Spain, Netherlands, …
singaporeby_country("Singapore")by_country(["Singapore"])single-country
three_pillarPillar conditions (wearable, biosensor, mental health, …)Pillar keywords (wearable biosensor, digital therapeutic, …)Curated condition + keyword lists at module top
product_shortlistBUY-list firms (sponsor)BUY-list firms + SaMD topical keywordsBUY_LIST firm names normalized
whitespaceWhitespace conditions (POC, wearable, neonatal, pediatric, …)Whitespace keywordsCurated lists in module
Empty section ≠ broken section. Every macro renders an italic empty-state callout if it finds no matches. That happens legitimately when (a) the trial/paper match is genuinely zero, or (b) the sponsor-name join misses (subsidiaries, etc.), or (c) the briefing was rendered before the next full ingest catches up. The empty-state text tells you which.

5 strategic questions you can answer now

Q1 · "Is sponsor X actually doing work, or are they coasting on old clearances?"
Q2 · "What's the maturity of the clinical evidence in [target product code]?"
Trials answer the future evidence question ("what's being studied"). PubMed answers the past evidence question ("what's been demonstrated"). Run the three_pillar or whitespace briefing for your area, then read the trials phase distribution (PHASE3 + PHASE4 = mature, EARLY_PHASE1 = exploratory) alongside the PubMed publication volume + top journals. Decision rule: if you're entering a category where US peers have PHASE3+ trials AND >30 papers in last 5y, your 510(k) reviewer will already have a clinical framework — your evidence package can be lighter. If both are sparse, you may be early enough that De Novo is on the table.
Use the whitespace briefing's new trials + pubmed sections. The most valuable signal is publication volume rising in a topical area where FDA clearance volume in the corresponding product code is flat. That's clinical evidence accumulating ahead of regulated commercialization — the gap is where a fast-moving manufacturer wins. Cross-check your own capability fit before committing.

Query helpers (analysis layer)

Both query modules are pure-read: they take a DuckDB connection + intent, return list-of-dicts ready for any briefing template or notebook. They tolerate missing tables (return [] if you query before the first ingest finishes) — so you can wire them into a briefing draft without breaking the render.

analysis/trials.py

from fda_radar.analysis import trials as trial_q

# All trials sponsored by a sponsor cohort (normalized names)
trials = trial_q.trials_for_sponsors(con, ["medtronic", "boston scientific"], limit=50)

# Only active (RECRUITING / NOT_YET_RECRUITING / ACTIVE_NOT_RECRUITING / ENROLLING_BY_INVITATION)
active = trial_q.trials_for_sponsors(con, ["medtronic"], active_only=True)

# Trials with at least one location in one of several countries
eu = trial_q.trials_by_country(con, ["Germany", "France", "Italy"], limit=40)

# Trials with conditions matching ANY of several substrings
dental = trial_q.trials_for_conditions(con, ["dental", "caries", "oral health"], limit=30)

# Aggregate summary for a peer cohort — counts by status, top conditions, top phases
summary = trial_q.cohort_trial_summary(con, ["medtronic", "abbott medical", "boston scientific"])
# returns: {total, active, by_status, top_conditions, top_phases}

analysis/pubmed.py

from fda_radar.analysis import pubmed as pub_q

# Papers whose affiliation_sponsors column matches a known sponsor cohort
papers = pub_q.papers_for_sponsors(con, ["medtronic", "boston scientific"], limit=30)

# Papers whose keyword_matches OR title OR mesh_terms contains a substring (any)
dental_ai = pub_q.papers_for_keywords(con, ["intraoral camera", "dental AI"], limit=30)

# Papers whose author affiliations contain a country/city substring
in_papers = pub_q.papers_by_country(con, ["India", "Mumbai", "Bengaluru"], limit=25)

# Monthly publication count per sponsor cohort (last N months) — "scientific velocity" signal
vel = pub_q.sponsor_publication_velocity(con, ["medtronic"], months_back=24)

# Top journals overall or within a topical slice
top = pub_q.top_journals(con, keyword_substrings=["wearable", "biosensor"], limit=15)
For a one-off question, open a Python REPL in the project:
cd "Chrysalis Intelligence" && export UV_PROJECT_ENVIRONMENT=$HOME/fda-radar-local/venv
uv run python
>>> import duckdb
>>> con = duckdb.connect('/Users/michaelsappington/fda-radar-local/data/fda.duckdb', read_only=True)
>>> from fda_radar.analysis import trials, pubmed
>>> trials.trials_for_sponsors(con, ["dexcom"], active_only=True)

Refreshing the data

Both ingesters are wired into the standard pipeline. The daily fda-radar refresh picks them up automatically — no separate command needed.

EndpointHow oftenCostNotes
clinical_trials Daily incremental (lastUpdatePostDate watermark) Free No API key. ~3-5 min per refresh once the historical 5y is loaded. Page size 1000.
pubmed Daily incremental (pub_date watermark) Free, but rate-limited 3 req/sec anonymous. Set NCBI_API_KEY in ~/fda-radar-local/.env for 10 req/sec (3× faster, no other change needed). Get a key at account.ncbi.nlm.nih.gov.

Ad-hoc single-endpoint refresh

# Trials only, narrow window
uv run fda-radar ingest --endpoint clinical_trials --start 2026-05-01 --end 2026-05-31

# PubMed only, narrow window
uv run fda-radar ingest --endpoint pubmed --start 2026-05-01 --end 2026-05-31

# Full incremental refresh (uses watermark - 14 day lookback)
uv run fda-radar ingest --endpoint clinical_trials
uv run fda-radar ingest --endpoint pubmed

First-time full 5y backfill

The next time you run fda-radar refresh after this change, both endpoints will do a 5y backfill (no watermark exists yet). Realistic times:

The launchd daily refresh at 6:15 AM may extend by these amounts the first day. Subsequent runs are 14-day incrementals — back to ~10 min for trials, ~30 min for PubMed.

Coverage gaps + what NOT to conclude

The sponsor-name join is fuzzy

Both ingesters normalize sponsor names with the same normalize_applicant() the FDA tables use (lowercase, strip Inc/LLC/Ltd/Corp/GmbH/etc., collapse whitespace). That works for ~70-80% of obvious cases. It will miss:

When a firm appears empty in the trials/pubmed section but you know it's active, the name-match probably missed. Try the live ClinicalTrials.gov or PubMed UI to confirm.

Keyword scope is curated, not exhaustive

The next refresh picks the new keywords up; existing data isn't re-tagged automatically.

Future-dated PubMed records are real

PubMed's pub_date reflects the print issue date, which can be months in the future for accepted-but-not-yet-printed papers. You'll see records with pub_date in 2026-12 or even 2027. They're not corrupt — they're "in press". Treat them as current.

What this data does NOT tell you

Future extensions worth scoping

ExtensionWhat it addsEffort
Trial outcomes ingestPull results when reported — primary outcome direction, statistical significance, safety events. Currently we only have protocol + status.Medium — CT.gov v2 has results modules but reporting is sparse; needs cleanup logic.
Pre-print monitorAdd bioRxiv / medRxiv ingest for earlier signal on dental-AI / wearable / SaMD research before peer review.Small — both have JSON APIs; ~3 hrs.
FDA AdComm coverageFDA Advisory Committee meetings often discuss the same devices that surface in CT.gov / PubMed; cross-linking would be powerful for PMA-track briefings.Medium — FDA AdComm calendars exist as RSS; record extraction needs parsing.
Citation-graph viewWhich papers cite which other papers in a topical area — surfaces seminal works + emerging thought leaders.Large — requires NCBI's PMC OAI-PMH or external citation databases (OpenCitations).
Per-sponsor scientific velocity dashboard pageThe macro already supports a view='velocity' bar-chart for monthly publication count; not yet wired into any briefing. Easy to surface in landscape or positioning.Small — <1 hr per briefing.