Runbooks for editing the dataset and refreshing external assets, plus the reference
for what each tool is (Tool reference, below) and the emitted data contract
(Data contract, below). Both moved here from ../CLAUDE.md to keep that
file terse; CLAUDE.md keeps the viewer/runtime map and the non-obvious rules. Author-side
corpus locations are in CLAUDE.local.md.
- Edit the relevant list in
generate_data.py(ortools/data/drugs_data.jsonlfor drugs).- Structures: edit
PAIRED/MIDLINE. Paired entries are auto-mirrored (define on the right, x > 0; the generator emits one right-side shape file and the_Lmember references it withmirror:true). A region is a noise-deformed ellipsoid by default; blob/curve/composite shape knobs are in the Geometry section (medial=Truederives the right medial clip;clip_planesis auto via_bisecting_clip_planes,JIGSAW_CLIP.enabled; cortex pattern is shader-drawn viainjectCortexSwirl/CORTEX_SWIRL). Layout: theposfield positions regions to assemble at explode 0; lobes overlap +medialso the hemispheres meet atMIDLINE_GAP(temporal is the lateral exception); deep nuclei sit small + central. Re-render to check (only=frontal_R,parietal_R,temporal_R,occipital_R&explode=0&view=right). - Structure links + grades: add a
base -> URLentry to theWIKIPEDIAregistry for a Wikipedia link (both hemispheres share it; a non-base key raises). Anatomy source grade isclassification_provenance, defaultllm, overridable inSTRUCTURE_PROVENANCE(theRECEPTOR_PROVENANCE/TARGET_PROVENANCE/STRUCTURE_PROVENANCEtrio via_lookup_provenance). - Projections: edit
PROJECTIONS.from/toare structure ids; the arrow pointsfrom -> to. Carrylabel/neurotransmitter/description. A pathway is graded by a verified quote inKANDEL_QUOTES(keyed by the right-side(from, to)pair); an unsourced pathway showsNOSOURCE(no fabricated citations).bidirectional: True(both cones; use withsymmetric: False+ explicit_L/_Rfor commissures).tentative: True(dotted, Hypothetical section). Projections are bilateral by default (define once on the right): such a pathway is emitted once withmirror: trueand the consumer reflects it to the other hemisphere (no duplicate row per pathway; seemirrorunder the data contract below).symmetric: Falsekeeps a one-sided pathway (emits no flag).kindmust be aPROJECTION_COLORSkey (excitatory / inhibitory / dopaminergic / cholinergic / neuroendocrine / serotonergic / noradrenergic); a new kind also needsKIND_TO_SIGN(->SIGN_COLORS/SIGN_LABELS) andBURSTincircuit-anim.js. - Circuits: append to
CIRCUITS:id,name,structuresas base ids (arrows derived). Optionaldescription+description_fr+wikipedia+sources(a list of quote-level{corpus,page,quote,provenance}dicts, validated by_expand_sources). - Projection groups: edit
PROJECTION_GROUPS: one entry per group in both modes,{mode, key, name, description, description_fr, wikipedia, sources}(modekind|sign,keyvalidated;sourcesquote-level dicts). Normally you only edit descriptions/wikipedia (all 7 kinds + 3 signs exist); a new entry is needed only when adding a new projectionkind. - Receptors: append to
RECEPTORS:id,name,family,neurotransmitter,receptor_class,sign,synaptic,locations(base ids or"ALL"). Optionaldescription+description_fr(inline) +wikipedia. A stub = emptylocations+ no description._receptor_recordvalidates keys + bases. A new family/class/synaptic value needs its label map entry (+ FR). The four classification attributes (family/receptor_class/sign/synaptic) are each independently graded: the base grade isRECEPTOR_PROVENANCE(defaultllm), lifted per-attribute only whereRECEPTOR_CLASSIFICATION_COVERAGEsays the receptor's Stahl-Essential quote (STAHL_ESSENTIAL_RECEPTOR_QUOTES) actually backs that attribute (conservative: never list an attribute the quote or the record disagrees on;RECEPTOR_ATTR_QUOTESgives an attribute a different quote than the main one, or several to back a compound value likesynaptic="both"). An individual expression region is sourced (abovellm) by adding a{receptor_id: {base: [quote-source]}}entry toRECEPTOR_LOCATION_SOURCES. - Drugs: edit
tools/data/drugs_data.jsonl. Each:id,name,categories, optionalnbn+description(inline{en,fr}),wikipedia,bindings. A binding is{target, action}(+ optionaleffect/note/tentative);targetis a merged map key (aDRUG_TARGETSkey or a receptor id),actionaDRUG_ACTIONSkey (agonist / partial_agonist / antagonist / inverse_agonist / reuptake_inhibitor / releaser / enzyme_inhibitor / pam / nam / blocker / modulator)."bindings": []->focusable: false. A new coarse target/category/action needs aDRUG_TARGETS/DRUG_CATEGORY_LABELS/DRUG_ACTIONSentry (with{en,fr}labels; aDRUG_TARGETSentry needs atype+ optionalwikipedia). Target classification grade overridable inTARGET_PROVENANCE; an individual "Found in" region is sourced (abovellm) by adding a{target_id: {base: [quote-source]}}entry toTARGET_LOCATION_SOURCES(mirror ofRECEPTOR_LOCATION_SOURCES). The drug's class classification grade is overridable inDRUG_CATEGORY_PROVENANCE(or upgraded by a quote-levelcategory_sourceson the authored drug). Keep extraction strictly dump-sourced. - Translations: every display string is wrapped with
_t(); add the French to theFRtable or the build raises listing every miss. For a feminine/plural paired name setfr_gender(f/mp/fp).
- Structures: edit
- Run
python tools/generate_data.pyto regeneratepublic/data/. - Optionally run
python tools/check_data.py. - For new drugs/structures with links, run the fetch tools (network, idempotent, touch
only the new ones):
fetch_molecules.py,fetch_structure_images.py. To refresh binding affinities, runfetch_ki.py --apply(reads the local PDSP CSV; idempotent), which rewritesdrugs_data.jsonl'skiannotations +affinity_onlybindings. - Commit the generator change + the regenerated artifacts together.
The legend is generated at runtime from the data, so it updates automatically.
To re-pull every third-party asset the dataset hot-links or vendors, run these (all
network, idempotent, polite; each touches only what changed). Always finish with
generate_data.py so the emitted public/data/ picks up the new urls/files:
python tools/fetch/fetch_molecules.py— new per-drug molecule SVGs intopublic/data/molecules/(only drugs missing one); writestools/generated_cache/molecules_sources.json.python tools/fetch/fetch_structure_images.py— re-resolve each structure's and wiki-linked circuit's Wikipedia hero + gallery image urls intotools/generated_cache/structure_images_sources.json+tools/generated_cache/circuit_images_sources.json(no bytes downloaded; the gif/svg is hot-linked at runtime).--target structures|circuitsscopes to one.- PDSP Ki: re-download the whole-DB CSV from
https://pdspdb.unc.edu/databases/kiDownload/download.phpoverdata_sources/books/pdsp_ki/KiDatabase.csv(author-side; see that dir'sREADME.md), thenpython tools/fetch/fetch_ki.py --applyto rewritedrugs_data.jsonl'ski+affinity_only. - GtoPdb receptor expression (the
receptor_locationssources):python tools/fetch/fetch_gtopdb.pyre-pulls each receptor's tissue comments (caches todata_sources/gtopdb/, author-side), then run the confirm-only judge overdata_sources/gtopdb/worklist.jsonandpython tools/sourcing/apply_location_sources.py --judged <f>to merge newverifiedsources intotools/generated_cache/location_sources.json. See CLAUDE.md Source provenance (corpora #7/#8). - Allen AHBA expression (the
target_locations+ residualreceptor_locationssources):python tools/fetch/fetch_allen.pyre-pulls each donor's microarray PACall (caches todata_sources/allen/, author-side; ~2GB across donors), thenpython tools/sourcing/apply_location_sources.py --corpus allenmerges the deterministicverifiedsources (no judge) intotools/generated_cache/location_sources.json. See CLAUDE.md Source provenance (corpora #7/#8). python tools/generate_data.py— regeneratepublic/data/from all of the above.python tools/update_readme_stats.py— refresh the README sourcing table (CI runs it--check).
Panel descriptions need no refresh script: each fetches the current Wikipedia lead
at runtime (js/wiki.js), so they stay current on their own.
What each authoring / fetch / check script in tools/ is (one line each). The dev/runtime
tooling (serve.py, shot.py, demos/) is also summarized in ../CLAUDE.md (Running,
Screenshots).
generate_data.py— single source of truth for the anatomy (stdlib-only, offline): defines every region/projection/receptor once, emits the artifacts below. Drugs are the exception (authored intools/data/drugs_data.jsonl, read by_load_drugs). Display strings are{en,fr}via_t()(see CLAUDE.md I18n).tools/data/drugs_data.jsonl— the authored drug dataset (from Stahl 8th ed.), one compact JSON object per line, read by_load_drugs, emitted todata/drugs.jsonl. Edit to add/change a drug.tools/data_generators/— pure-data modules imported bygenerate_data.py:drugs.py,provenance.py(also houses the sourcing-tally helpers_GRADE_RANK/_strongest_grade/_binding_grade+_provenance_stats, which reduce the emitted nodes tometa.provenance_stats),i18n.py,quote_table.py(the serialization-time source-quote externalize pass ->quotes.jsonl, mirroring the i18n externalize), and thereceptors/subpackage (one module per neurotransmitter family, e.g.serotonergic.py, each exposingENTRIES;__init__.pyconcatenates them intoRECEPTORSin the original order), theregions/subpackage (one module per anatomicalgroup, e.g.cortex.py/basal_ganglia.py, each exposingPAIRED+MIDLINE;__init__.pyconcatenates them intoPAIRED/MIDLINEin the original order), andgeometry.py(the shared cortical-dome SDF helpers, e.g._cortex_lobe_entry+MIDLINE_GAP, plus the pure geometry math helpers_scale_sdf/_scale_triple/_directional_extent/_bisecting_clip_planes, imported byregions/cortex.pyandgenerate_data.py), andconnectivity.py(the three connectivity node literalsPROJECTIONS/CIRCUITS/PROJECTION_GROUPSplus the_KQ_*/_SG_*pathway quote-source constants they cite; the shared_kandel/_nieuwenhuys/_stahl_essquote constructors live inprovenance.py), andpresentation.py(the presentation maps emitted intometa.json: the colour/flow mapsPROJECTION_COLORS/KIND_TO_SIGN/SIGN_COLORS/SYSTEM_FLOW_KINDS, the label mapsSIGN_LABELS/GROUP_LABELS/RECEPTOR_FAMILY_LABELS/RECEPTOR_CLASS_LABELS/SYNAPTIC_LABELS, plus the per-structureWIKIPEDIAlink table; a dependency-free leaf), and thequotes/subpackage (verified quote registries by corpus:kandel.py=PROJECTION_QUOTES+STRUCTURE_QUOTES(Kandel/Nieuwenhuys anatomy, cites the connectivity_KQ_*);stahl_essential.py=STAHL_ESSENTIAL_RECEPTOR_QUOTES/STAHL_ESSENTIAL_TARGET_QUOTES/RECEPTOR_ATTR_QUOTES/RECEPTOR_CLASSIFICATION_COVERAGE/CLASSIFICATION_ATTRS/TARGET_POLARITY_QUOTES; chain stays acyclic provenance <- connectivity <- quotes <- generate_data).tools/drugs_io.py— shared JSONL load/save fordrugs_data.jsonl(load_drugs/save_drugs); used bygenerate_data.py,fetch_ki.py, and the threeapply_*_sources.pywriters.tools/check_data.py— stdlib integrity checker over emittedpublic/data/(see CLAUDE.md Data checks).tools/serve.py— stdlib dev server,Cache-Control: no-store, roots atpublic/(see CLAUDE.md Running).tools/shot.py— Playwright screenshot helper (see CLAUDE.md Screenshots).tools/demos/— Playwright demo-video recorder:recorder.py(aDemoAPI) +neurarium.py(the showcase tour, writes the README herodocs/images/preview.gif). Needs ffmpeg+gifski+GPU; seetools/demos/README.md.tools/sourcing/build_source_worklist.py— lists not-yet-sourced drug bindings with Stahl page ranges (input to the source-extraction workflow; resumable).tools/sourcing/apply_source_quotes.py— applies the extraction workflow's accepted quotes onto bindings (re-finds the quote in the page range; idempotent).tools/sourcing/apply_nbn_sources.py— sources each drug's NbN line (greps Stahl's verbatim line, substring-confirms, no judge); falls back to the drug Class line (nbn_nonstandard) for a newer drug with no NbN line. Idempotent.tools/sourcing/apply_category_sources.py— sources each drug's class classification (drug_categories) from an extract/judge results file (a judge is needed: our coarsecategoriesre-map Stahl's free-text class line, unlike the fixed NbN field). Idempotent.tools/fetch/fetch_gtopdb.py— fetches receptor tissue-distribution comments from the Guide to Pharmacology API (corpus #7gtopdb), the source for receptor expression regions;RECEPTOR_GENESmaps receptor->gene->targetId. Cachesdata_sources/gtopdb/+worklist.json(each quote carries assay species). See CLAUDE.md Source provenance (corpora #7/#8).tools/fetch/fetch_allen.py— fetches the Allen Human Brain Atlas microarray (corpus #8allen_ahba), the source for target expression regions + the receptor regions GtoPdb misses; a PACall detection-boolean vote per (gene, region), no judge.TARGET_GENES+fetch_gtopdb.RECEPTOR_GENESmap owners to genes. Cachesdata_sources/allen/+confirmed.json. See CLAUDE.md Source provenance (corpora #7/#8).tools/sourcing/apply_location_sources.py— merges accepted expression quotes intotools/generated_cache/location_sources.json,--corpus {gtopdb,allen}(gtopdb needs a judged file; allen is deterministic). Idempotent. See CLAUDE.md Source provenance (corpora #7/#8).tools/generated_cache/location_sources.json— machine-written bulk location sources, loaded bygenerate_data.pyintoRECEPTOR_LOCATION_SOURCES/TARGET_LOCATION_SOURCES. Not served.tools/sourcing/recheck_quotes.py— re-verifies every emitted verified quote with a stronger model (Sonnet) and stamps the sourcing LLM.build --out <dir>writes per-page batches (page text loaded once per batch to minimize tokens; Allen AHBA excluded as deterministic); an LLM judges each batch (present + supports claim);apply --batches <dir> --verdicts <f> [--llm sonnet]writestools/generated_cache/quote_llm.json({quote_id: llm}, applied uniformly byquote_table) +quote_recheck_flagged.json(quotes the recheck could not fully confirm, for review). See CLAUDE.md Source provenance ("The sourcing model").tools/fetch/fetch_ki.py— parses the PDSP Ki CSV (data_sources/books/pdsp_ki/, author-side) into per-drug binding affinities;--applywrites eachki+ adds median-strongeraffinity_onlybindings. A curatedALIASmap recovers drugs PDSP lists under a related compound. See CLAUDE.md Drugs.tools/fetch/fetch_wikipedia_pharmacology.py—uv run(deps: beautifulsoup4). Fetches a drug's English Wikipedia article pinned to its revision id, stores the whole page author-side (data_sources/wikipedia/raw/<slug>.html+pages/<slug>.md, corpus #9wikipedia_pharm), and mines the pharmacodynamics binding table for a per-target Ki (adaptive multi-row-header grid + fuzzy target resolution reusingfetch_ki'snorm/parse_ki/NAME_PATTERNS). A Ki source'squoteis the verbatim table row, so the normal quote gate applies. Preview by default;--json,--no-fetch. Fills the Ki gap where PDSP has none (e.g. alpha1, non-psychiatric agents). See CLAUDE.md Drugs.tools/fetch/fetch_brand_names.py—uv run(deps: beautifulsoup4). Resolves each drug's French Wikipedia article via the EN article's langlinks (reusingfetch_wikipedia_pharmacology.py), stores it author-side (data_sources/wikipedia/pages_fr/<slug>.md, corpus #10wikipedia_fr), and writes a candidate trade-name-sentence worklist (tools/generated_cache/brand_worklist.json) for the LLM that extracts each drug's ordered European/French brands. EN is used only for the langlink (its pages are not refetched, so no corpus #9 Ki gate can drift). See CLAUDE.md Source provenance (corpus #10).tools/sourcing/apply_brand_names.py— merges the LLM-extracted eu/fr brands (brand_judged.json) intodrugs_data.jsonl, quote-gating each name verbatim on its FR page and tagging the firstfr, the resteu(na from Stahl is kept). Idempotent. See CLAUDE.md Source provenance (corpus #10).tools/fetch/pdf_to_pages.py— splits a PDF into one<page>.mdper page (the quote-gate text);uv run,--layoutfor OCR.tools/fetch/build_toc_index.py—INDEX.mdfrom a PDF's embedded TOC (generic).uv run.tools/fetch/build_index.py— Stahl-specific page index (byTHERAPEUTICSheading).uv run.tools/update_readme_stats.py— rewrites the READMESOURCING_STATS+SOURCES_TABLEblocks (and the headline %) frommeta;--checkexits 1 if stale (CI). Idempotent.tools/fetch/fetch_molecules.py— downloads each drug's molecule SVG intopublic/data/molecules/; writestools/generated_cache/molecules_sources.json. See CLAUDE.md Images.tools/fetch/fetch_structure_images.py— resolves the url of each structure's (and wiki-linked circuit's) Wikipedia hero + gallery images intotools/{structure,circuit}_images_sources.json(--target structures|circuits|all); downloads no bytes. See CLAUDE.md Images.tools/generated_cache/{molecules,structure_images,circuit_images}_sources.json— provenance/attribution for the fetch tools (the image ones are read bygenerate_data.pyoffline; not served).tools/git-hooks/— repo-tracked git hooks (see CLAUDE.md Git hooks).
The field list of each emitted file. The viewer reads exactly these shapes; the generator
emits them. Every claim carries its own quote-level source (see CLAUDE.md Source provenance);
there is no node-level catch-all sources block.
Source quotes are externalized. A quote-bearing source is emitted on the node as a bare
{quote_id, provenance}reference; its immutable excerpt{id, corpus, page, quote, species?}lives once in the deduplicatedquotes.jsonlside table (keyed by a content hash, so identical excerpts share one entry). The viewer (js/data.js) andcheck_data.pyboth rehydrate each reference in memory (merging the excerpt back onto the source) at load, so every shape below that showssources[{corpus,page,quote,...}]is the rehydrated view; on disk it is{quote_id, provenance}. Ki sources (noquote) and bare wikipedia provenance are not externalized. See the serialization pass indata_generators/quote_table.py(mirrors the i18n externalize pass).
Emitted data is English-only. Display strings marked
{en,fr}below are authored bilingual but serialized as the plain English string; the French is deduplicated into one side table,translations.fr.json({english: french}, sorted keys), which the viewer fetches only in French and looks each English string up in (see docs/I18N.md, the externalize pass). So on disk everyname{en,fr}etc. is just a string.
meta.json— presentation maps + tallies, so the dataset is self-describing (a port needs no hardcoded palette):projection_colors,kind_labels,group_labels,kind_signs,sign_colors,sign_labels,system_flow_kinds(drug target system -> projection kind), the receptor maps (receptor_family_labelskey order = legend family order,receptor_class_labels,synaptic_labels), the drug maps (drug_category_labelskey order = Drugs legend order,drug_actionsaction->{label,effect},drug_effect_colors,drug_effect_labels,drug_targets= every non-receptor target + every receptor id; a target with a direction-flippingvesicular/sign/synapticflag also carriespolarity_provenance(+ optionalpolarity_sources), its own graded node kindtarget_polarity; areceptor_grouptarget also carriessubtypes(its modeled subtype receptor ids, a sourceless taxonomy the viewer lists as per-subtype drug dropdowns)),target_type_labels/target_type_colors,source_corpora,provenance_stats(the sourcing tally; see CLAUDE.md Source provenance).translations.fr.json— the deduplicated French side table,{english: french}with sorted keys, covering every emitted display string whose French differs from its English. Fetched by the viewer only in French (English users skip it); a missing key falls back to the English string. Written last bywrite_artifactsfrom the externalize pass (see docs/I18N.md).quotes.jsonl— the deduplicated source-quote side table, one quote node per line sorted byid(aq_<12 hex>content hash):id,corpus,page,quote, optionalspecies, optionalllm(the model that extracted+judged the quote,haiku/sonnet/opus; absent = unknown). Every node's quote-bearing source references one byquote_id; the viewer +check_data.pyrehydrate it at load (see the externalize note above).structures.jsonl—id,name{en,fr},base_name{en,fr}(hemisphere-stripped, legend row),group,position,color,shape_file,classification_provenance, optionalwikipedia(+_provenance), optionalstructure_image(hot-linked Wikimedia url, shared by both hemispheres) +structure_image_gallery.projections.jsonl—from,to,kind,label{en,fr},neurotransmitter{en,fr},description{en,fr}, optionalsources[{corpus,page,quote,provenance}](fromKANDEL_QUOTES),bidirectional,tentative(dotted, off-by-default section).mirror: truemarks a symmetric pathway stored once (the right-hemisphere record): the consumer reflects it by flipping_R<->_Lon both endpoints (js/data.jsat load,check_data.pybefore its checks), so the file carries no per-side duplicate. Set from thesymmetricauthoring hint (see Projections above).circuits.jsonl—id,name{en,fr},structures[ids](arrows derived in the viewer), optionaldescription{en,fr}+sources+wikipedia(+prov) +structure_image(+ gallery); same shape + rendering as a structure's.projection_groups.jsonl— a legend pathway row promoted to a sourced structure so it opens a panel:id(<mode>_<key>),mode(kind|sign),key,name{en,fr},description{en,fr},classification_provenance, optionalwikipedia(+prov) +sources. One record per group in BOTH colour modes (7 per-transmitter + 3 per-sign); member pathways derived in the viewer.receptors.jsonl—id,name,family,neurotransmitter{en,fr},receptor_class(ionotropic/metabotropic/chaperone),sign(excit/inhib/modulatory),synaptic(pre/post/both),locations(structure base ids, both hemispheres), optionalubiquitous:true,classification({family,receptor_class,sign,synaptic}-> each a{grade, sources?}sub-claim, so a quote grades only the attributes it substantiates), optionallocation_sources({base:[quote-source]}, sparse per-region upgrade abovellm;"ALL"= the ubiquitous claim), optionaldescription{en,fr}+wikipedia(+prov). Empty locations + no description = a deliberate stub (listed, not focusable).drugs.jsonl—id,name,categories,category_provenance(+ optionalcategory_sources), optionalnbn{en,fr}(+nbn_sources, +nbn_nonstandard:truewhen the value is Stahl's class descriptor not a formal NbN),bindings[](each:target,action, optionaleffect/note{en,fr}/tentative/sources[{corpus,page,quote,provenance}]/ki(measured PDSP affinity)/affinity_only:true(Ki but no known direction, panel-only)), optionalwikipedia(+prov), optionalstructure_image(vendoreddata/molecules/<id>.svg, only when the file exists),focusable. No drug-level source: provenance is per-claim (see CLAUDE.md Source provenance).molecules/<id>.svg— vendored per-drug structure diagrams (fetch_molecules.py). Structure illustrations are NOT vendored (hot-linked, see CLAUDE.md Images).
Geometry (data/shapes/<name>.json): one file per distinct form. L/R pairs share a single
right-side file; the left member sets mirror:true on its structure record and the viewer
reflects it across x. Three types:
blob{radii, seed, detail, noise, + optional octaves/ridged/frequency/aniso/ clip/clip_planes}— a gradient-noise-deformed ellipsoid.curve{points, profile, seed, noise, radial/tubular_segments}— a round-capped tapered tube swept along a spline (caudate; brainstem levels midbrain/pons/medulla).composite{parts:[...]}— sub-shapes (each optional offset/scale/rotate) merged into one mesh (cerebellum = 2 hemispheres + vermis). Thesdftype (SDF atlas, undergeometry_refinements/) is documented there; see CLAUDE.md's geometry note.