rarediseasehedge ← AlmanacCandidatesCard listFunnelAudit

Accuracy audit — 2026-08-29

Scope: full adversarial audit of the Funnel-A pipeline (code, data, statutory encoding, dashboard, docs) plus an exhaustive sweep for data sources not yet wired in. 10 independent auditors raised 78 findings; each was put to 3 independent verifiers on different lenses (correctness / materiality / primary-source), giving 234 verdicts, of which 33 refuted the finding. Every defect acted on below was then re-measured by hand against the real files.

Bottom line: the previous headline number was wrong by roughly half, and the reason was a single unused column. load_ema read EMA's "Medicine status" field and never filtered on it, so applications that were withdrawn, refused, or still pending an EC decision were counted as foreign approvals. 33 of the 67 "approved abroad, never approved in the US" candidates had never been authorised anywhere the pipeline could see — including 11 of the 13 orphan-flagged rows the dashboard presented as the highest-value leads. Two independent measures agree exactly: 33 of the 57 EMA-sourced candidates carry no marketing-authorisation date, and 33 trace only to non-authorising status values.

A second, independent error is narrower than it first looks but worth stating precisely: eligibility.py encodes superseded law, and contradicts the README. It asserts the application may be filed under "505(b)(1) or 505(b)(2)"; Pub. L. 117-9 (2021) rewrote § 360ff(a)(4)(B) to require the application be "the subject of an application submitted under section 355(b)(1)" — a full 505(b)(1) NDA. To be clear about where the repo stands: README.md has this right ("505(b)(2) is not 505(b)(1)", "Drug approved abroad… via full 505(b)(1) NDA | YES"), as does rdh_diff.py. The thesis documentation is sound; the module that scores eligibility per candidate was the one carrying the pre-2021 text, which is the worst place for it to be.


Verdict on the headline number

beforeafterwhy
Named candidates, "approved abroad, never in US"6732EMA non-authorisations removed; 2 stereo false positives moved to REVIEW; 1 prodrug excluded
— orphan-flagged13211 of 13 were refused/withdrawn EU applications
Dossier-only (real dossier, no authorisation in any loaded register)0 (mixed into the 67)32new, separately reported bucket
Needs a human call (REVIEW)03zopiclone, enclomifene, levomenthol
Accuracy harness19/19 recall, 19/19 specificity23/23 recall, 20/20 specificity, 2/2 stereo4 new labelled cases, 1 mislabelled case corrected

The honest characterisation of the output is now: 32 moieties with a genuine marketing authorisation from EMA-centralised, Health Canada or Swissmedic and no US approval of the active moiety, plus 32 leads that have a dossier but no authorisation the pipeline can see, plus 3 open questions.

Of the 32, a further caution the pipeline can now state but not resolve: at least 2 (govorestat, tolebrutinib) have already had a US NDA filed and rejected — FDA Complete Response Letters NDA 219195 (2024-11-27) and NDA 219624 (2025-12-23). A CRL is not an approval, so they remain statutorily eligible, but they are not untouched opportunities.


Critical findings

C1 — load_ema never filtered on authorisation status. pipeline/sources.py:84-119. The EMA medicines report is an EPAR index, not an approval list: of 2,290 human rows, 312 carry a status that means the product never held an authorisation (266 "Application withdrawn", 63 "Refused", 29 "Opinion", 2 "Withdrawn from rolling review", 1 "Opinion under re-examination"), and a further 412 were authorised and later lost the licence. All were ingested identically. Corroborated two ways: exactly 33 of 57 EMA-sourced named candidates have an empty marketing-authorisation date (col 31). Fixed: every row is labelled authorised / previously_authorised / never_authorised; the text status is authoritative and the EC decision date is used only to classify an unrecognised status, so a newly-added status value cannot silently become an approval. Never-authorised rows are kept in a separate funnelA_dossier_only.csv rather than discarded — the dossier and human data are real, and that is the feasibility signal the thesis actually rests on.

C2 — eligibility.py encodes pre-2021 law and contradicts the README. pipeline/eligibility.py. The docstring and the filed_as_505b1_or_505b2 criterion asserted the application may be filed under 505(b)(1) or 505(b)(2). § 360ff(a)(4)(B)(i)(II) requires 355(b)(1) only, per Pub. L. 117-9 (2021); the pre-2021 text is what most law-firm explainers still describe, and the vouchers awarded on 505(b)(2) applications all predate it. README.md and rdh_diff.py both state the rule correctly, so this was an internal contradiction in which the wrong version sat in the module that scores per-candidate eligibility. Fixed: criterion renamed filed_as_505b1, cited to (a)(4)(B)(i)(II), with the public-law history in the note. Consequence for the thesis is unchanged from what the README already says — a 505(b)(1) may rest on published literature the sponsor did not generate, but it is a full NDA with the cost and data burden that implies.

C3 — the statutory test is "active moiety", not "active ingredient". Same file. The encoded criterion quoted the pre-2021 "active ingredient, including any ester or salt" formulation and cited 505(b)(1)/(b)(2)/(j) and 351(a)/351(k) together. The current drug prong at (a)(4)(B)(i)(I) says "no active moiety (as defined by the Secretary in section 314.3 of title 21, Code of Federal Regulations)" and lists only 505(b)(1)/(b)(2)/(j); 351(a)/(k) belong to the separate biologic prong at (a)(4)(B)(ii). This one is good news for the implementation: the pipeline screens on active moiety, which is exactly the correct standard. The code was right and its own documentation understated it.

C4 — UNII column indices were positional, and FDA moved a column. pipeline/unii.py. load_records read SUBSTANCE_TYPE at fixed index 23. The 4 Aug 2026 UNII release inserted a new UTF8_DISPLAY_NAME column at that position, shifting SUBSTANCE_TYPE to 24. row[23] therefore returned a display name, is_biologic() stopped matching, and the biologic/mixture filter collapsed from 203 rows to 4 — leaking ~197 proteins and mixtures straight into the candidate list, which jumped from 32 to 114 while every intermediate count still looked plausible.

This was found only because fixing H4 caused a real refresh; on the stale Feb-2026 file the bug was invisible. It is the most dangerous shape of defect in this codebase — a routine upstream data refresh silently inverting a filter — and it would have shipped as a 3.5× "improvement" in candidate count. Fixed three ways: (1) columns in both load_records and load_names are now resolved by header name, case- and separator-insensitively, so the same shift cannot recur (it also absorbs the Display Name → DISPLAY_NAME rename in the same release); (2) positional indices remain only as a fallback and warn loudly when used; (3) _check_subtypes validates the shape of the parsed column — a small controlled vocabulary dominated by chemical — and warns if that stops being true, which catches the class even if header names change too.

Verified across both releases: 7 distinct substance types and ~42,000 biologics in each. With the fix, the fresh 4 Aug 2026 index reproduces the same 32 candidates as the Feb index — a useful stability result in its own right.

Two further instances of the same root cause, both fixed: _snapshot_label() reported the mtime of the hardcoded Feb file rather than the index actually loaded, and test_accuracy.py --real loaded the hardcoded Feb files — so the harness that exists to "replay labeled cases through the SAME code path" was testing a different identity index from the pipeline.

C5 — all five (a)(4) citations were wrong. eligibility.py cited a structure (a)(4)(A)(i)–(a)(4)(A)(v) that does not exist in the statute. Correct: exclusion test (a)(4)(B)(i)(I); 505(b)(1) filing (a)(4)(B)(i)(II); priority review (C); pediatric data (D); no adult indication (E). Fixed, and the missing (a)(4)(F) "approved after 30 September 2016" criterion was added so the encoded set matches the statute rather than a remembered subset of it.


High / medium findings

H1 — two stereoisomer false positives the GSRS layer structurally cannot see. ZOPICLONE is the racemate of eszopiclone (Lunesta, NDA 021476) and ENCLOMIFENE is a component of US-approved clomifene citrate (Clomid). Both were in the headline list with no flag, and the REVIEW bucket was empty across all 94 survivors. Verified live: FDA's own GSRS graph carries no enantiomer/racemate edge for either pair (eszopiclone's record links to zopiclone only via IMPURITY->PARENT), and has_stereo_marker misses them because the descriptor is fused into the INN stem rather than written as "(R)-"/"(±)-". Fixed: moiety.stereo_name_variants adds/strips WHO INN stereo prefixes (es-, lev-, dex-, ar-, en-, zu-, cis-, trans-, rac-…) in both directions, and first strips a trailing descriptor so UNII's "ZOPICLONE, (R)-" spelling is treated as ZOPICLONE. That last part is load-bearing: which UNII record claims a moiety key is not stable across UNII releases — the 4 Aug 2026 file made "ZOPICLONE" resolve to the (R)- enantiomer record instead of the racemate, which moved the row into the unnamed appendix and silently dropped its flag until the suffix handling was added. Over the full survivor set the layer now finds three real pairs (zopiclone/eszopiclone, enclomifene/clomifene, levomenthol/menthol) and raises no false alarms — checked against near-miss shapes like "VITAMIN E" and "INTERFERON BETA". Verdict is REVIEW, not exclusion: whether a single enantiomer of an approved racemate shares the approved active moiety under 21 CFR 314.3 is genuinely unsettled.

H2 — TEGOMIL FUMARATE is a prodrug of a US-approved moiety. Tegomil fumarate (Riulvy, EMA-authorised 2026) is two monomethyl-fumarate moieties joined by tetraethylene glycol, cleaved in vivo to monomethyl fumarate — US-approved as Bafiertam. GSRS holds no relationship at all for UNII MXD6KMG2ZP, so the graph layer could not catch it. Fixed: a small, cited GSRS_GAP_PRODRUGS table that re-verifies the named parent against the live US record at run time, so a stale entry can never assert an approval that no longer exists.

H3 — two matcher gaps, found by using EMA Article 57 as an unseen test set.

ETEXILATE was missing from the ester-prodrug suffix list that already held AXETIL, PROXETIL, PIVOXIL, MEDOXOMIL and friends.

keys). Measured across 26 known INN/USAN divergences, UNII synonyms unify all of them (paracetamol/acetaminophen, salbutamol/albuterol, ciclosporin/cyclosporine, rifampicin/rifampin…) except the cephalosporin CEF-/CEPH- rule, which cost 2 misses (cefalexin, cefradine). Fixed: ETEXILATE added; a narrow leading-CEF/CEPH orthographic bridge added. Both now resolve; cefamandole, cefovecin and all 32 candidates are unaffected.

H4 — fetch.py downloaded the UNII index but never extracted it, and the diff hardcoded a six-month-old filename. FDA date-stamps these files. fetch.py wrote UNIIs.zip while rdh_diff.py read UNII_Names_26Feb2026.txt, a name fetch.py never produced — so a refreshed identity index could never reach the pipeline, which went on reading a Feb-2026 map while reporting a current snapshot. The newest release is 4 Aug 2026, 159 days newer. Fixed: the zips are extracted after download, and the diff globs for the newest date-stamped file by parsing the embedded date (string sort would put 26Feb2026 after 4Aug2026). The index is now 4 Aug 2026 — and doing this immediately exposed C4, a positional-column bug that the stale file had been hiding.

Audit self-correction worth recording. I first concluded these URLs were
blocked, having tested with curl -I. They are served through an AWS API
Gateway that accepts GET only and answers HEAD with
403 MissingAuthenticationToken. A verifier caught the error; a plain GET
retrieves 20.3 MB and 14.5 MB fine. The 403 was entirely an artifact of the
test method.

H5 — fetch.py disabled TLS verification for every host. ssl.CERT_NONE plus check_hostname = False, justified in a comment as "some regulator hosts have incomplete chains". Re-tested all seven hosts (ema.europa.eu, fda.gov, canada.ca, swissmedic.ch, precision.fda.gov, gsrs.ncats.nih.gov, download.open.fda.gov): all verify cleanly. The bypass bought nothing and allowed silent substitution of every input to the thesis. Fixed: full verification; all sources still download.

H6 — the Swissmedic URL had been dead for some time. 404, so every "refresh" silently kept the stale local copy — the atomic-write design meant this never surfaced as an error. Swissmedic renamed the list. Fixed: current path (zugelassen_wirkstoffe…/Stoffliste.xlsx, "Stand vom 31.07.2026"), scraped from the published index.

H7 — every foreign loader returned [] silently when its file was missing. A missing download turned into a smaller candidate list that looked exactly as confident; losing EMA alone would drop most of the headline set with no warning. Fixed: each loader's expected filename is named and a zero-row result is a loud warning.

H8 — Japan and Switzerland are effectively absent, and the report blamed the wrong one. The funnel described 3,605 unresolved rows as "mostly PMDA katakana". Measured per source: Swissmedic 2,868 of 2,970 (97%), PMDA 139 of 140 (99%), Health Canada 223 of 715 (31%), EMA 375 of 2,292 (16%). Japan contributes 1 resolved moiety; the unresolved mass is Swiss Latin pharmacopoeial genitives ("abacaviri sulfas"), outnumbering Japan 20:1. Fixed: the funnel table now reports unresolved counts per source. This also explains multi_regulator: 0 — a real result, not a bug: EMA here is centralised-only while Canada and Switzerland list mostly nationally-approved drugs, so the sets barely overlap. The dashboard now says so instead of silently scoring every candidate down for it.

H9 — pre-2000 US withdrawals are missing from the bulk files. Of 30 hand-checked drugs with genuine FDA approvals later withdrawn, 13 are absent from Drugs@FDA + Orange Book — every one withdrawn before ~2000 (terfenadine, astemizole, mibefradil, temafloxacin, dexfenfluramine, encainide, flosequinan, zomepirac, benoxaprofen, ticrynafen, nomifensine, methaqualone, phenformin). Post-2000 withdrawals are retained (rofecoxib, valdecoxib, cisapride, sibutramine, cerivastatin, troglitazone all resolve). A withdrawn approval still disqualifies, so this is a real false-positive channel — but none of the 13 currently appears in any loaded foreign register, so the hole is latent, not active. US_WITHDRAWN_SUPPLEMENT patches exactly one case and is not a substitute for a 21 CFR 216.24 feed. Documented with the measurement; not "fixed", because the honest fix is a feed.

H10 — the dashboard's PRV price was ~$50-100M low. It claimed "recently sold for ~$100–160 million". This repo's own SEC-verified table (sources/18) records 2026 sales at $180M (Rocket), $195M (Denali), $205M (Cyprium) and a 2026-27 planning band of $175–210M. Fixed: the three 2026 SEC-verified figures are now quoted directly.

M1 — METHYLPARABEN and AMYLMETACRESOL are not drug candidates. Methylparaben is a preservative (its Swissmedic source name is the food-additive code "E 218"); it passes the "named drug" filter because UNII_Records genuinely carries a USAN_ID for it (Y-26), and openFDA NDC confirms US OTC MONOGRAPH FINAL status. Amylmetacresol is an OTC throat-lozenge antiseptic with a real INN. Both are correct "never US-approved under an application" results and useless as repurposing leads — the INN-or-USAN filter cannot separate them. Not fixed: an ATC/therapeutic-class screen is the right instrument, and inventing a hand-exclusion list here would hide the structural point.

M2 — the funnel report's OTC-monograph warning stated the law backwards. It said a US OTC-monograph moiety "can still surface as a false candidate", using aspirin as the example. § 360ff bars a prior approval in an application under 505(b)(1)/(b)(2)/(j); OTC-monograph marketing involves no such application and therefore does not disqualify. Adding OTC moieties to the exclusion set would introduce false negatives. (Aspirin also holds NDAs, so the example was doubly wrong.) Fixed.

M3 — the snapshot label was a hand-edited literal. It claimed "EMA 5 Aug 2026", named no Health Canada file at all, and survived a refresh of drug_ap.zip. Fixed: derived from the mtimes of the files actually on disk, and it reports MISSING rather than omitting an absent source.

M4 — "~7 estimated actionable" was a hardcoded, unsourced number presented as the honest size of the opportunity. Removed, replaced with the derived dossier-only and REVIEW counts.


Confirmed accurate (checked, found sound)

Worth as much as the defect list, because these are the things a reader would otherwise have to take on trust:

against the openFDA NDC directory (137,590 records): zero are marketed in the US under NDA / ANDA / BLA / NDA-authorized-generic. 18 appear only as BULK INGREDIENT, EXPORT ONLY or OTC-monograph — none disqualifying.

suspicion: EMA 0/3/6/7/8/11/15/23/31 all verified against the live header, and Health Canada drug_ap.txt col 2 = class, col 1 = product categorization — so the vet/OTC screen is genuinely wired to the right fields. (UNII's indices were correct for the Feb 2026 file and broke on the Aug release — see C4. Only UNII is now header-resolved; EMA and Health Canada remain positional and are the obvious next candidates for the same treatment.)

calcitriol, but activation is a 25-hydroxylation — not an ester or salt — so under 21 CFR 314.3 it is a distinct active moiety.

it is a false positive for the separate reason in H2).

the pipeline's treatment is right.

Book hole on the exclusion side.

zero overlap against any candidate.

20 December 2024 and was restored by the Consolidated Appropriations Act, 2026 (Pub. L. 119-75, 3 February 2026) § 6604. There is no separate designation deadline, so encoding a single date is correct.

small-molecule survivors, with 2 REVIEW counted separately.

Of the 78 findings raised, 33 verdicts refuted the finding they examined — including my own UNII conclusion (H4) and several overstated severity claims. The refutations are the reason this document does not contain them.


Data sources not yet wired in

32 catalogued, 27 fetched successfully today. Ranked by measured value.

Wire these three next

#SourceMeasured effectEffort
1EMA Article 57 / xEVMPD — ema.europa.eu/en/documents/other/article-57-product-data_en.xlsx (7.8 MB, 163,791 product rows, 11,932 substances)+377 named never-US moieties vs the current 32 — a 12× larger universe. Nationally-authorised EEA products, the pipeline's single biggest blind spot. Also fixes the mislabelling that forced KNOWN_AUTHORISED_ELSEWHERE. Real rare-pediatric hits in the sample: sulthiame (childhood epilepsy), racecadotril (pediatric diarrhoea), levosimendan, carbetocin.low — already added to fetch.py as ema_art57
2Swissmedic Extended HAM list — …/erweiterte_ham_ind…/Erweiterte_Arzneimittelliste HAM.xlsx (2.6 MB, 11,073 rows, "Stand 31.07.2026")Attacks the 97%-unresolved Swiss problem. Unlike the current abbreviated file it carries ATC code (an alternate join path around the Latin genitives), Zulassungsart (drops export-only authorisations) and Abgabekategorie (drops category-E OTC).low
3KEGG DRUG — rest.kegg.jp/find/drug/JAN (2,288 Japan-approved drugs)Japan currently contributes 1 moiety. KEGG carries Japanese drugs under Latin-script names tagged JAN, bypassing katakana transliteration entirely — the "top next build" the repo has been deferring, for a 600 KB GET. Note KEGG is free for research; production use needs a licence.low

Also live-verified and valuable

SourceSideWhat it fixes / adds
openFDA drug/ndc (27 MB)RIGHTmarketing_category separates a real 505/351 approval from OTC-monograph, unapproved-marketed, bulk-ingredient and export-only. Already added to fetch.py; used for this audit's zero-false-positive check.
openFDA transparency/crl (1 MB, 458 letters)verificationPrior-US-filing and competitor detection. Found govorestat and tolebrutinib. Includes full letter text. Already added to fetch.py.
openFDA other/substance (317 MB)RIGHTThe entire GSRS substance graph offline — would replace ~750 live API calls, remove the GSRS-unavailable REVIEW risk, and likely close prodrug gaps like H2 wholesale. Highest-value robustness change.
FDA CDER NDA Type 7 via api.fda.gov/drug/drugsfda.jsonRIGHTDESI / pre-1938 marketed-without-approval drugs — the principled fix for H9's 13 measured misses.
Orphadata en_product9_prev.xml (6,728 disorders) and en_product9_ages.xml (7,790 with age-of-onset)enrichmentMoves rare_under_200k from UNKNOWN and primarily_pediatric from HUMAN — the two criteria that currently prevent any candidate from clearing the gate. Biggest decision-readiness win.
HPO phenotype.hpoa (12,958 diseases with onset)enrichmentSecond, independent onset source.
Orange Book exclusivity.txt (695 active ODE records) — already inside eob.zipverificationOrphan-exclusivity conflict check, at zero download cost.
EU Community Register of Orphan Medicinal Products (2,171 active designations)verificationCompetitor detection; also the correct source for orphan status, which is currently EMA-medicines-report-only.
MHRA UK products.mhra.gov.uk/sitemap.xml (3,775 substances)LEFTPost-Brexit GB approvals are not in Article 57 (which covers UK-NI only).
France BDPM CIS_COMPO_bdpm.txt, Spain CIMA REST, Brazil ANVISA CSV (5,277), Turkey TITCK (1,253), Japan MHLW NHI Yakka (2,694)LEFTMostly overlap Article 57 for the EU members; Brazil and Turkey are genuinely independent.
EDGAR full-text search, NIH RePORTER, ClinicalTrials.gov v2, EU CTIS, Europe PMC, RxNormverification / enrichmentPer-candidate evidence and competitor signals.

Confirmed not machine-accessible today

Australia TGA ARTG (timeout from US egress), China NMPA (HTTP 412), Korea MFDS, India CDSCO, Taiwan TFDA (times out from non-TW IP), Russia GRLS (ASP.NET ViewState, no API). The precision.fda.gov UNII archive is accessible — see the H4 self-correction.

The A2 population is documented but never computed

README.md already defines Funnel A as having two sub-populations — A1 (never approved in the US) and A2 (US drugs whose only prior approval was a pre-1962 deemed-approved NDA, newly available per Sun Pharma v. FDA, D.D.C., over phenobarbital/SEZABY) — with the appropriate caveats about it being an unreviewed district-court holding. No code implements A2. rdh_diff.py excludes any moiety appearing anywhere in Drugs@FDA or the Orange Book, regardless of approval vintage, so A2 is defined in prose and discarded in practice.

First quantification, from data already on disk: 740 applications in the current Drugs@FDA snapshot have an original approval before 10 Oct 1962, and 70 distinct active moieties are approved only through them — trimethadione, paramethadione, phensuximide, diethylcarbamazine, guanidine, furazolidone and dimercaprol among them, several squarely rare-and-pediatric. Caveats on the measurement: it uses the earliest ORIG SubmissionStatusDate as the vintage proxy, many pre-1962 records have blank or placeholder dates, and truly "deemed approved" drugs are often absent from Drugs@FDA altogether — so 70 is a noisy floor. Some entries (sulfur, inulin, histamine) are diagnostics or reagents rather than repurposing candidates.

It costs nothing to compute and is the cheapest new population available, but it must land in a clearly-labelled speculative bucket, never the headline.


Recommended fix order

Applied in this commit: C1–C5, H1–H10, M2–M4.

Still open, in order:

1. Wire EMA Article 57 (ema_art57 already fetches). Changes the candidate count from 32 to roughly 400 named rows; expect to re-run the GSRS sweep over ~380 new survivors and to find more H3-class matcher gaps — Article 57 was the unseen test set that produced dabigatran and cefalexin. Do this behind the harness, not in front of it. 2. Swap in openFDA other/substance for the live GSRS calls. No count change; removes the network dependency and probably subsumes GSRS_GAP_PRODRUGS. 3. Add Orphadata prevalence + age-of-onset. No count change; converts eligibility.py's two blocking criteria from UNKNOWN/HUMAN to real values — the difference between a screening list and a decision list. 4. Swissmedic Extended HAM + KEGG JAN. Expect low hundreds of additional candidates, mostly Swiss and Japanese. 5. NDA Type 7 / DESI feed to retire US_WITHDRAWN_SUPPLEMENT (H9). 6. Pre-1962-only bucket as a separate, labelled output (70 moieties). 7. Orphan-designation conflict check (FDA OOPD + EU register) — the audit found ~15 of the pre-fix 67 carried active EU orphan designations, i.e. somebody else is already working on them. 8. Header-resolve the EMA and Health Canada loaders too. C4 was a UNII schema shift; nothing makes EMA's 39-column report or the DPD extract immune to the same thing, and both are still read positionally.


Residual limitations after all fixes

active-moiety exclusion test). Rarity and pediatric-onset need external data; seriousness, priority review and all three filing-design criteria need a human and ultimately FDA. eligibility.py can never return "eligible" — only "nothing has ruled it out yet", which is the correct ceiling.

withdrawals (H9). A hand check remains necessary for any candidate whose molecule is old enough to have had a 1980s US life.

substances are the likeliest next one, because curation lags approval.

"not rare", for every Canadian, Swiss and Japanese row.

own optimistic timeline (sources/26) already sits on its outer edge with no buffer, and its per-program probability model treats correlated risks as independent. Treat the timeline, not the candidate count, as the binding constraint.


Addendum — 2026-08-31: Article 57 wired in (+ NDC exclusion, validated)

Follow-up implementing "recommended fix order" #1. Numbers below supersede the body above where they conflict.

What changed

ema_art57.xlsx, 11,900 nationally-authorised EEA substances). Candidate universe 32 → 342 named. Each row carries its set of authorising EEA countries; the count is a quality signal (a drug authorised in 25 states is an established medicine) and is now a sort key, a CSV column (n_eea_countries), and a dashboard field. Multi-regulator candidates went 0 → 23.

it is MAH-submitted data, so presence is taken as authorisation and it cannot be status-filtered the way the EU-central report now is. It is labelled as its own source, EU national (Art57), so it can be discounted separately.

~10 moieties that ARE in Drugs@FDA but under a spelling whose UNII didn't link to the foreign name (penicillamine, tetrofosmin, mebrofenin, selenium, L-tryptophan, methionine…). The NDC directory has broader per-moiety name coverage, so its NDA/ANDA/BLA rows are now ingested into the exclusion set through the same collect() path (71k rows). After this, zero candidates match any NDC application category. The non-application NDC categories (OTC monograph, bulk, unapproved) remain a flag-only layer, with a defensive tripwire that routes any missed NDA/ANDA/BLA to REVIEW.

Validation

A 77-candidate stratified sample (all 23 multi-regulator + top 45 by EEA breadth + both orphan + 20 random tail) was checked against primary US-approval sources by an independent agent per batch, and every claimed false positive was adversarially re-verified against a primary source. Result: 75/77 correct, 2 confirmed false positives, both real US approvals the bulk files miss:

19 Apr 2000 (65 FR 54, doc 00-6722). The pre-2000 withdrawn-NDA class (H9), now with a concrete instance.

(STN 103738, US License 1888) marketed since 1994.

Both added to US_WITHDRAWN_SUPPLEMENT with citations and as harness known-answer cases. Candidate count 344 → 342.

Harness

25/25 recall, 20/20 specificity, 2/2 stereo in both offline and --real (was 23/23 + 20/20; +2 for the validated false positives). The stereo layer, unchanged, now also correctly quarantines dexibuprofen, levomethadone, leucovorin, carnitine and D-penicillamine to REVIEW as US-approved-stereo-relative cases (review bucket 3 → 12) — none of these reach the candidate list, and their base moieties are all excluded.

Final counts

2026-08-292026-08-31
Named candidates (approved abroad, never US)32342
— INN-designated30338
— orphan-flagged (EMA-only)22
— approved in >1 foreign regulator023
Dossier-only3231
REVIEW312
US exclusion setDrugs@FDA + OB+ openFDA NDC NDA/ANDA/BLA (71k rows)

Residual, updated

patched by hand, not a feed. A DESI / NDA-Type-7 feed remains the systematic fix.

product cannot be distinguished from a live one; the country count is the only available proxy for how established a drug is.

(antihistamines, mucolytics, NSAIDs). That is correct for Funnel-A enumeration but means the rare-pediatric filter downstream now does most of the narrowing — wiring Orphadata prevalence + age-of-onset (fix #3) is the next high-value step.


Addendum — 2026-08-31 (2): Orphadata wired in (#3); almanac report

Orphadata (rare-disease epidemiology)

orphadata.py loads Orphanet point-prevalence + age-of-onset (7,888 disorders, from data/raw/orphadata_{prev,ages}.xml; fetch.py entries orphadata_prev/orphadata_ages). enrich.py resolves each row's disease (EMA therapeutic area / indication text → Orphanet name, Jaccard ≥ 0.8) and attaches an orpha block (rarity + onset + evidence). eligibility.py consumes it via a new Candidate.from_summary(): rare_under_200k and primarily_pediatric now come off real data instead of UNKNOWN/HUMAN for any row whose disease resolves.

Two properties make this honest rather than oversold:

most Article-57 rows carry no disease name. A looser matcher produced real false matches (vildagliptin → "neonatal diabetes"), so the threshold is set for precision; all 9 matches were hand-verified correct (mifamurtide → osteosarcoma, ataluren → DMD, pridopidine → Huntington, …).

drug correctly fails to match and earns no rarity claim — which also helps flag the common-disease drugs that dominate the Article-57 expansion.

Worked example: assess(Candidate.from_summary(ataluren_row, …)) now returns rare_under_200k = PASS ("Orphanet 1-9/100 000 ≈ 16,750 in the US") and primarily_pediatric = PASS ("onset: Childhood"), where both were previously open. Harness unchanged at 25/25 + 20/20 + 2/2.

Almanac report (pipeline/report/)

The searchable candidate list is now the first section of the "State of Bio" almanac (#shortlist, before the visualisations), in the almanac's own style: search box, filter chips with live counts (orphan / multi-regulator / rare (Orphanet) / pediatric onset / licence-pulled), sort, and 342 cards carrying the new Orphadata rare/pediatric badges. The market/pipeline/map/category visualisations follow below. build_report_data.py now emits the full candidate list into data.js. Verified in-browser: shortlist + all six charts render, zero console errors.