Case study
RareVerse
An evidence-first map of rare diseases that shows a family which diseases share their child's biology, and which researchers to contact next.
1,891 cited links across 93 rare diseases, shipped in 21 hours
Who had the problem#
For most rare diseases, the person pushing research forward is a parent. They know the name of their child's disease, and maybe the gene. What they can't see is which other diseases break the same biology, even though a mouse model, a registry or a gene therapy built for one of those could help theirs.
That knowledge exists, but it is scattered across a dozen databases built for specialists. A disease with 200 known patients could borrow momentum from one with 2,000, if a family could find it. And the need is huge: 263 to 446 million people live with a rare disease, and about 95% of rare diseases have no FDA-approved treatment.
What I built#
RareVerse joins 7 public databases into one map. Search a disease, a gene or a symptom and it lands in the center, with the diseases that share its biology closest to it. Every sentence in the side panel ends with a numbered citation, and every link says whether a curated database stated it or we inferred it.
I led our team of 3. I built the grading engine that decides which diseases are related, how strongly and why, the Next.js frontend with its map, 3D view and list, and the "Who can I contact?" button that turns the papers and grants behind a disease into researchers a family can write to. Families get plain words, researchers get score tables over the same evidence, and the whole view lives in the URL, so any map can be shared as a link.
On the map, distance from the center is relevance and direction is the kind of thing: related diseases above, research and patient groups to the right, symptoms below, genes and pathways to the left. Nothing is placed by hand. Tests check that distance stays honest at all 101 steps of the relevance slider, and a 14-name label budget keeps the map readable.
How it works#
I designed the graph schema: typed nodes and typed edges, ids instead of names, and a source, URL, date and kind (observed, inferred or contradicted) on every edge, enforced by JSON Schema 2020-12 in CI. A Python pipeline and a set of TypeScript build scripts pull Monarch, the Human Phenotype Ontology, Reactome, ClinVar, Orphanet, PubMed and NIH RePORTER into that shape: 1,122 nodes and 1,891 edges. The diagram traces one record from a public database to a citation on screen.
- A Python pipeline and a set of TypeScript build scripts pull 7 public databases: Monarch, HPO, Reactome, ClinVar, Orphanet, PubMed and NIH RePORTER.
- They write one graph of typed nodes and typed edges, 1,122 nodes and 1,891 edges, with a source, URL, date and kind on every edge.
- JSON Schema 2020-12 enforces that shape in CI.
- An inverted index with a set-similarity prefix filter picks the candidate pairs of diseases.
- The grading engine scores each pair on 11 evidence dimensions in 3 families, with a noisy-OR and caps, and every run is byte-identical.
- The result shows on screen with its citation.
- For contacts, the server builds the candidate list from PubMed and NIH records without AI, and OpenAI's Responses API must pick people by id.
The grading engine scores each pair of diseases on 11 evidence dimensions in 3 families: biology, clinical resemblance and collaboration. Inside a family, lines of evidence combine as a noisy-OR with caps, so agreement is what makes a link strong and no score ever reaches 1. Rare evidence counts for more: a pathway is weighted by how few diseases reach it and how small it is, and symptoms are compared with SimGIC over HPO, calibrated between 20,000 random disease pairs and 497 pairs of records that describe the same disease. An inverted index with a set-similarity prefix filter picks the candidate pairs instead of comparing everything with everything, and sorted keys, 4-decimal rounding and a timestamp taken from the input make every run byte-identical.
The contact finder is built so the AI can't invent a person, a page or an email. The server builds the candidate list without AI, from the senior, first and corresponding authors of linked PubMed papers and the leads of linked NIH grants, and the model (OpenAI's Responses API with web search and a strict JSON schema) must pick people by id. Every link must be a page the search actually consulted or one of our own PubMed and NIH records. Pages are fetched behind an SSRF guard (6 seconds, 1 MB, public hosts only), and the page, or at least its address, must name the person. An email appears only if it is printed on an official page or in the PubMed record. A 24-hour cache, per-visitor rate limits, at most 2 OpenAI calls at once and a 30-per-hour server cap keep the bill in check.
The hardest part#
A single shared gene could pass for 3 lines of evidence. When 2 diseases share a gene, they also share that gene's ClinVar variants and its Reactome pathways, so a naive engine counts 3 agreeing lines and calls every same-gene pair Strong. That is wrong in a way that matters: Hajdu-Cheney syndrome and Alagille syndrome 2 both come from NOTCH2, one from changes that increase its signaling and one from changes that decrease it.
So the engine counts a shared gene as one line. Variants count only when they are tied to the disease itself or to a gene with no other disease in the atlas, and pathways that only arrive with the shared gene are set aside and listed under "What we don't know." A Strong grade needs 2 independent lines, one of them stated by a curated database. Every same-gene pair now stops at Moderate (0.70), and the reason on screen says exactly what the gene can't tell.
Results in numbers#
We shipped in 21 hours. The atlas covers 93 rare diseases with 1,891 sourced links (1,514 stated by curated databases, 377 inferred), and it screens all 4,278 disease pairs and grades the 1,040 that share evidence in under half a second, with byte-identical output on every run.
All 321 tests across 34 files pass in CI, next to a schema check, a grade check, the production build, a typecheck and the check I wrote that fails unless the graph rebuilds byte for byte from its committed inputs. A literature answer key of 14 disease pairs gates the engine: 9 of 14 hold, and the other 5 are documented with their causes instead of hidden.
Search knows about 1,900 synonyms from Monarch and HPO, so "globoid" finds Krabbe disease and a misspelled "gaushur" gets "Did you mean Gaucher disease?" A family doesn't need the textbook name to find their disease.
What's next#
The data is where I'd start, because our docs already say where it's thin. Gene changes are recorded per gene rather than per disease, so every same-gene pair stops at 0.70, and 31 of the 93 diseases have no symptoms on record because the pipeline filters them upstream. Fetching ClinVar variants per condition and keeping every Monarch phenotype would fix both.
Then I'd ask a clinician to review the answer key, which is one JSON file and one npm test away from recalibrating the engine, and grow the atlas past 93 diseases. Translations come after that, because many rare-disease families don't read English.