Skip to content
Plate VIIIMethod

Integrity report

Last run

26 Sept 2026

20 days ago

Before any compound is shown, it is re-checked against the PubChem record it claims to be. This page is the result of that check, in full — including the records it removed from view. It is generated from the audit file, not written, so it cannot drift away from what the site is actually doing.

I

How the data is validated

Three bands. Two of them are written to disagree with the archive rather than to confirm it.

A check that can only pass is not a check. Each stage below is phrased as an attempt to falsify something already believed: the harvest asks what would disqualify a record before it asks what the record says, and the adversary asks whether a stored number can be shown to be wrong — against its own source, and against every other row this archive holds.

How data is validated: the harvest gives every upstream record a verdict, an adversary independently tries to falsify every stored row, and an identity audit decides which compounds may be displayed at all.A · WHAT MAY ENTEROne ChEMBL recordvalue, units, relation,pchembl, commentsdecide()pure — no I/O, so theadversary can re-derive itacceptquarantinerejectactivities rowstored WITH its verdict and therule that produced it — quarantinedrows are kept, explained, andrefused by the query layerrejected: no value upstream to record at allwhere two sources hold one pair: PDSP outranks ChEMBLB · WHAT IS RE-CHECKEDtools/adversary.py — opens the connection read-only, so it cannot write even by mistakeoffline · every rowpx must restate the stored valueno concentration off the end of the scalereplicates of one pair must reconciletwo sources of one pair must agreeupstream · every row against its sourcere-fetches from ChEMBL and comparesstored-but-not-upstream is a fabricationupstream-but-not-stored is a silent lossa verdict it cannot reproduce is a bugreport — never a writedata/adversary.jsonC · WHAT MAY BE SHOWNverify_compounds.pyre-checks identity upstreamverified | unconfirmed| quarantineddisplayableIds()inside every query

All three bands are now connected. Every ChEMBL measurement has been re-fetched and rewritten through decide(): 14,788 upstream records judged, 1,372 accepted, and the rest kept with the rule that refused them. The adversary then re-derived every verdict independently and reports no critical finding — where before it found three impossible concentrations and eighty-eight negative ones.

Why two programs

decide() is pure — the same record always earns the same verdict, and it touches neither network nor database. That is what lets adversary.py re-derive every verdict independently from a fresh copy of the upstream record. A verdict the adversary cannot reproduce is a bug in one of the two, and the disagreement says which.

Which source wins

196 compound–target pairs are held by both ChEMBL and the NIMH PDSP Kᵢ database, and 38 of them disagree by more than tenfold. PDSP wins. It measures these compounds at these receptors as its subject, under one protocol; ChEMBL aggregates whatever a depositor sent, at whatever confidence. The largest correction is psilocybin at 5-HT2A, which ChEMBL puts at 10,000 nM and PDSP at 13.

What it caught

Atropine was recorded binding three muscarinic receptors at about 2.5×10¹⁷ nM — a pX of −8.4. ChEMBL’s own record carries data_validity_comment of “Outside typical range” and no pChEMBL; this archive read the number and not the flag. Both mechanisms found it independently, which is the only kind of agreement worth having.

II

The weekly survey

What moved upstream since the last run — new activities in ChEMBL, new papers naming a compound, and which generated files are due. The survey itself only reports; what is done with each finding is set out below.

The survey runs from a scheduled job — ./run --weekly installs it, ./run --status says whether it is still installed and when it last wrote. Five steps: survey upstream, ingest the ChEMBL measurements the survey found had moved, re-run the identity audit over them, re-resolve the bibliography against PubMed, and append what changed to UPDATES.log. If the job stops, the date below stops advancing and turns amber after a week rather than the page going quiet about it.

The ingest step is deliberately narrow. It re-harvests measurements for compounds this archive already holds, through the same rules that gave every other row its verdict and reason, and it cannot add a compound: a molecule the archive has never held is an identity decision, so the survey writes it down as a proposal and the log keeps it under “for a person to decide” however many weeks it sits there. Everything the audit finds is still a proposal too — nothing on this page is applied to the archive by a machine.

Last surveyed

2026-08-31 · 45 days ago

window 2026/08/31 → 2026/08/31

ChEMBL ChEMBL_37 · released 2026-05-01

New upstream activities · 0

No compound gained an upstream activity since the last survey.

“Held here” is lower than “now” for almost every compound and that is not a backlog: the archive filters on ingest, dropping rows with no normalisable value and assays that are not molecular targets. The column worth reading is new — activities that appeared upstream since the last survey.

Named in new literature · 3 compounds, 3 papers

These are candidates, not findings. A PubMed search for a compound name is a search for a string, and this archive has been caught by that before — DOI is also a digital object identifier, AMT is azidothymidine, DOC is docetaxel. 42 of the names searched are short enough to be someone else’s abbreviation and are marked. Hover a name for the exact query.

Not held here · 94 proposals

Compounds that carry a psychoactive MeSH pharmacological action at PubChem and are not in this archive. Salts and enantiomers of records already held are filtered out by structure — InChIKey skeleton first, then PubChem’s own parent relation — because a survey that proposed phencyclidine hydrochloride every week would teach its reader to stop looking.

Hallucinogens

41 upstream · 20 held here · 19 already settled

Anesthetics, Dissociative

5 upstream · 3 held here · 2 already settled

Nothing in this class that the archive does not already hold as a structure.

Psychotropic Drugs

34 upstream · 1 held here · 0 already settled

Serotonin Receptor Agonists

78 upstream · 8 held here · 11 already settled

Hallucinogens is the class this archive is about. The other three are adjacent and are searched separately, never merged: a serotonin receptor agonist is not a psychedelic, and pooling them would propose triptans and antiemetics every week. A proposal here is a CID and a title, which is enough to look at and not enough to hold — a name still has to resolve to a single compound that answers to it, and a structure still has to be new. The weekly job queues the proposals in Hallucinogens and Dissociative Anesthetics as names in tools/candidate_compounds.json, and expand_archive.py applies both tests to each; one that fails stays out and the report says why. The other two classes are never queued.

New substances in the literature · 0 papers

A reading list, not a result. A substance that is genuinely new has no name in this archive to search for, so the survey finds the papers where one would be announced and leaves the reading to a person. It deliberately does not pull compound names out of titles: a string lifted from a title has been resolved to nothing and may not be a compound at all.

new psychoactive substance

0 in window

Nothing published in the window.

novel psychedelic

0 in window

Nothing published in the window.

designer drug

0 in window

Nothing published in the window.

novel tryptamine or phenethylamine

0 in window

Nothing published in the window.

serotonin 5-HT2A agonist, newly reported

0 in window

Nothing published in the window.

Generated files · 0 due

  • anatomogram.json9.2 days old · refresh every 365current
  • drift.json10.3 days old · refresh every 180current
  • expression.json7.9 days old · refresh every 180current
  • literature.json0 days old · refresh every 30current
  • monograph.json9.2 days old · refresh every 90current
  • survey.json0 days old · refresh every 7current
  • target_function.json8.1 days old · refresh every 90current
  • toxicology.json10.8 days old · refresh every 90current
  • trials.json9.1 days old · refresh every 30current
  • verification.json0 days old · refresh every 7current

The survey reads and reports; it never writes to the archive. Acting on anything here is a deliberate step — ./run -v to re-audit identity, or the relevant tool in tools/ to re-ingest — for the same reason a quarantined record is a proposal rather than a correction.

III

The count

545
Records examined

Every row in the compounds table

545
Verified

Formula and name both agree with the cited PubChem record

0
Unconfirmed

Structure agrees; the archive's abbreviation could not be matched

0
Quarantined

Withheld from the archive entirely

0
Measurements withheld

Not shown, and not counted in any figure on this site

IV

What each check does

Three checks decide the verdict; a fourth is advisory and never withholds a record on its own.

Molecular formula

withholds the record

What it does
The formula stored locally must equal the formula PubChem returns for the CID the record cites.
What it catches
A record whose fields have drifted from its own reference.
What it misses
A record that cites the wrong CID consistently — every derived field will agree with the wrong molecule perfectly.

Name

marks it unconfirmed

What it does
The archive's name for the compound must appear in PubChem's synonym list for that CID.
What it catches
A name pointing at a structure nobody else calls by that name.
What it misses
Common drug abbreviations that PubChem simply does not list. Neither “PCP” nor “DPT” appears under the correct CID, which is why failing this marks a record unconfirmed rather than removing it.

Curated review

withholds the record

What it does
A short, hand-maintained list of identity failures the machine checks provably cannot see, each with the collision that caused it and a proposed correction that is re-resolved against PubChem on every run.
What it catches
Ambiguous abbreviations resolved to the wrong molecule during ingestion.
What it misses
Anything nobody has looked at yet. This check is only as complete as its review.

ChEMBL cross-reference

advisory

What it does
The InChIKey skeleton from ChEMBL is compared with PubChem's. It reports three outcomes, not two: agreement, disagreement, or — when ChEMBL could not be reached on that run — no comparison at all, listed separately below.
What it catches
A bioactivity cross-reference pointing at a different molecule.
What it misses
Nothing is withheld on this alone — ChEMBL legitimately holds salts and single stereoisomers where PubChem holds the free base.

V

Nothing is being withheld

Every record in the archive passes every check. That is a recent state, not the original one: earlier runs of this audit quarantined two records that cited the wrong molecule and flagged one molecule held twice. Those faults were repaired at the source by tools/repair_archive.py — the misidentified records were repointed to the correct PubChem entries and their measurements re-ingested from the correct ChEMBL molecules, and the duplicate record was merged away. The archive is smaller for it: 196 measurements that turned out to describe saccharin and a corticosteroid were deleted, and 12 real ones took their place.

This section will repopulate the moment a check fails again. It is generated from the audit file, so it cannot report a clean archive that is not one.

IX

Advisory disagreements

ChEMBL and PubChem describe slightly different molecules for these records. Not grounds for withholding — but worth knowing when reading their binding data.

  • AMT

    PubChem QSQQQURBVYWZKJ-UHFFFAOYSA-N · ChEMBL ADVCGXWUUOVPPB-XLPZGREQSA-N

  • PCP

    PubChem JTJMJGYZQZDUJJ-UHFFFAOYSA-N · ChEMBL BUAJNGPDPGKBGV-UHFFFAOYSA-N

XI

One receptor, two names

Not a compound fault but a target one, and the largest single inaccuracy this audit has found. It is repaired for display by a rule shown here; the archive itself still holds both spellings.

assays.target_name is free text, and two ingestions filled it from different vocabularies. ChEMBL writes the full protein name — Cannabinoid receptor 1 — and the NIMH PDSP ingest writes the receptor symbol — CB1. Nothing joined them, so one receptor became two columns in every grid, two nodes in the graph and two rows in every count. 253 measurements sat under a second name.

The fold below is applied before anything is counted, and it is a table of literal names rather than a pattern: 240 distinct target names become 207. A pattern written for this merged D(1A) dopamine receptor with D(1B) dopamine receptor on the first attempt — DRD1 and DRD5, different genes on different chromosomes — which is why every entry is now a checked string and why tools/check_synonyms.py fails the build rule if a key stops appearing in the archive.

Unsubtyped parents are deliberately not folded. Muscarinic acetylcholine receptor is not M1 and Cannabinoid receptor is not CB1: a measurement recorded against a family is not a measurement against one member of it, and promoting it would manufacture precision the assay never had. This is a display repair, not a correction — the proper fix is one name per receptor in the archive, applied at the source by a human.

5-HT2A

403 measurements

5-HT2A (365) + 5-hydroxytryptamine receptor 2A (38)

CB1

319 measurements

CB1 (318) + Cannabinoid receptor 1 (1)

5-HT2C

270 measurements

5-HT2C (236) + 5-hydroxytryptamine receptor 2C (34)

5-HT1A

200 measurements

5-HT1A (171) + 5-hydroxytryptamine receptor 1A (29)

CB2

195 measurements

CB2 (194) + Cannabinoid receptor 2 (1)

5-HT2B

170 measurements

5-HT2B (152) + 5-hydroxytryptamine receptor 2B (18)

5-HT7

126 measurements

5-HT7 (122) + 5-hydroxytryptamine receptor 7 (4)

5-HT1D

117 measurements

5-HT1D (116) + 5-hydroxytryptamine receptor 1D (1)

KOR

115 measurements

KOR (114) + Kappa-type opioid receptor (1)

5-HT1B

112 measurements

5-HT1B (99) + 5-hydroxytryptamine receptor 1B (13)

5-HT6

97 measurements

5-HT6 (92) + 5-hydroxytryptamine receptor 6 (5)

5-HT2

96 measurements

5-HT2 (81) + Serotonin 2 (5-HT2) receptor (15)

D2

85 measurements

D2 (71) + D(2) dopamine receptor (14)

M2

65 measurements

M2 (63) + Muscarinic acetylcholine receptor M2 (2)

MOR

59 measurements

MOR (58) + Mu-type opioid receptor (1)

M1

55 measurements

M1 (53) + Muscarinic acetylcholine receptor M1 (2)

SERT

50 measurements

SERT (39) + Sodium-dependent serotonin transporter (11)

5-HT5A

49 measurements

5-HT5A (44) + 5-hydroxytryptamine receptor 5A (5)

DOR

47 measurements

DOR (46) + Delta-type opioid receptor (1)

5-HT1E

42 measurements

5-HT1E (40) + 5-hydroxytryptamine receptor 1E (2)

D1

39 measurements

D1 (35) + D(1A) dopamine receptor (4)

D3

38 measurements

D3 (34) + D(3) dopamine receptor (4)

5-HT4

38 measurements

5-HT4 (34) + 5-hydroxytryptamine receptor 4 (4)

M3

36 measurements

M3 (33) + Muscarinic acetylcholine receptor M3 (3)

Sigma 1

35 measurements

Sigma 1 (29) + Sigma non-opioid intracellular receptor 1 (6)

5-HT1

26 measurements

5-HT1 (22) + Serotonin 1 (5-HT1) receptor (4)

D4

26 measurements

D4 (23) + D(4) dopamine receptor (3)

DAT

24 measurements

DAT (23) + Sodium-dependent dopamine transporter (1)

NET

23 measurements

NET (21) + Sodium-dependent noradrenaline transporter (2)

5-HT3

20 measurements

5-HT3 (4) + Serotonin 3 (5-HT3) receptor (16)

Beta1

19 measurements

Beta1 (16) + Beta-1 adrenergic receptor (3)

5-HT3A

7 measurements

5-HT3A (3) + 5-hydroxytryptamine receptor 3A (4)

Glutamate NMDA receptor; GRIN1/GRIN2A

2 measurements

Glutamate NMDA receptor; GRIN1/GRIN2A (1) + Glutamate NMDA receptor; Grin1/Grin2a (1)

XII

Why quarantine rather than repair

Identity failures that the automated audit provably cannot catch, recorded by hand with the evidence that establishes them.

The machine checks in verify_compounds.py compare a record against the PubChem CID it cites. That catches a record whose fields have drifted from its own reference, but it cannot catch a record that cites the wrong reference consistently: if the ETL resolved an ambiguous abbreviation to the wrong CID, then the formula, weight, structure and descriptors were all derived from that CID and agree with it perfectly. The record is internally consistent and entirely wrong.

Short drug abbreviations are exactly where this happens, because they collide with unrelated chemistry. Each entry below names the collision, and each carries a 'proposed_cid' that verify_compounds.py re-resolves against PubChem on every run. Nothing here is asserted from memory; if a proposal stops resolving to the molecule described, the run says so.

Entries quarantine a record. They never rewrite one. Repointing a CID would silently replace one unverified structure with another, and the binding measurements attached to the old CID would follow it across -- which is how the wrong compound acquires the right pharmacology. The upstream database is left untouched and the record is withheld from display until a human repairs the chain of custody at the source.

The 'Iris' entry is the third instance of the same collision and the reason the curated layer exists: an automated name check cannot reject a name that is genuinely registered to the wrong molecule. It was caught by a human reading the expansion report, not by the audit.

Audit generated by tools/verify_compounds.py using RDKit 2025.09.2 on Python 3.9.6. Authorities: PubChem, MeSH Pharmacological Classification, ChEMBL. Re-run it with ./run -v.