Glean Health — what the data would look like as a relational schema

One-time orientation page. The actual storage is JSON-on-disk (see data/extracted/, data/pipeline/, data/authors/). This page is the same shape projected into Postgres-style tables — useful for forming a mental model of joins and granularity. Sample rows are real, pulled from the on-disk corpus as of 2026-05-28.

Threaded through every sample is one specific UC post (1rmcvbj) where the author talked about "inflammation in sigmoid" while trying Remicade, Tremfya, and prednisone — so you can trace one Reddit post all the way through to its cluster row.

Companion artifact: data_dictionary.csv — flat machine-readable schema (one row per column, 103 rows total). Open in Excel to sort/filter by table, key type, or FK reference. This page and the CSV are coupled: any schema change should update both.

Tables (17)
  1. conditions
  2. sources
  3. raw_items
  4. extractions
  5. post_symptoms
  6. treatments_raw
  7. treatment_symptoms_targeted
  8. authors
  9. author_sub_counts
  10. clusters
  11. cluster_aliases
  12. cluster_outcomes
  13. cluster_subs
  14. cluster_treatment_events
  15. pubmed_articles
  16. rxnorm_cache
  17. manifest

Relationships at a glance

                            +-------------+
                            | conditions  |    26 rows
                            +------+------+
                                   |
                                   | 1:N
                                   v
                            +-------------+
                            |   sources   |    29 rows (some conditions have multiple subs)
                            +------+------+
                                   |
                                   | 1:N
                                   v
+-----------+   1:N   +-----------------+   1:1   +-------------+
|  authors  |<--------+    raw_items    +-------->| extractions |  102,458 extracted JSONs
+-----------+         +-----------------+         +------+------+
      ^                       ^                          |
      | 1:N                   | (only some raw items     | 1:N
      |                       |  pass filter+extract)    v
+-----+-----+                 |                   +---------------+
| author_sub|                 |                   | post_symptoms |
|  _counts  |                 |                   +---------------+
+-----------+                 |                          |
                              |                          | (each extraction also has)
                              |                          v
                              |                   +-----------------+
                              |                   | treatments_raw  |   133,190 treatment events
                              |                   +--------+--------+
                              |                            |
                              |                  M:N       | 1:N
                              |              (clustered    v
                              |               at stage 7)  +-----------------------------+
                              |                            | treatment_symptoms_targeted |
                              |                            +-----------------------------+
                              |                            |
                              v                            v
                       +--------------+         +------------------------+
                       |   clusters   |<--------+ cluster_treatment_events|  M:N join
                       +------+-------+         +------------------------+
                              |
              +---------------+-----+--------------+----------+
              | 1:N           1:N    1:N            1:N        1:N
              v               v      v              v          v
       cluster_aliases  outcomes  cluster_subs   pubmed_articles
                       (per-     (which subs    (PubMed enrichment
                        cluster   the cluster    + classification pro/
                        outcome   was seen in)   con/neutral)
                        tally)

independent lookup/state:
  rxnorm_cache   58,894 input_name -> generic_name mappings (used at stage 7+9)
  manifest       per-condition x per-stage freshness tracking (the integrity perimeter)

Core ingestion (config → reddit data → LLM extraction)

1. conditions

The 26 chronic conditions the system covers. Each is a top-level “tile” on the patient surface. Adding a new condition = adding a row here + onboarding its subreddit sources.

Schema

columntypenotes
condition_nameTEXT PKkebab/snake, matches conditions/<name>.json
display_nameTEXTpatient-facing title
descriptionTEXTone-line clinical scope
pubmed_termsTEXT[]used by stage 9 PubMed search (synonyms/abbreviations)
proximity_map_csvTEXTpath to per-condition proximity map (semantic anti-poisoning)

Sample data

condition_namedisplay_namedescriptionpubmed_termsproximity_map_csv
ulcerative_colitisUlcerative ColitisUlcerative colitis (chronic inflammatory bowel disease of the colon)["ulcerative colitis","UC","inflammatory bowel disease"]conditions/ulcerative_colitis_proximity_map.csv
type1_diabetesType 1 DiabetesType 1 diabetes (juvenile diabetes / insulin-dependent diabetes mellitus)["type 1 diabetes","T1D","juvenile diabetes","insulin-dependent diabetes mellitus","IDDM"]conditions/type1_diabetes_proximity_map.csv
migrainesMigrainesChronic migraine and recurrent migraine headaches["migraine","migraines","chronic migraine"]conditions/migraines_proximity_map.csv
crohns_diseaseCrohn's DiseaseCrohn's disease (inflammatory bowel disease, can affect any part of GI tract)["crohn's disease","crohns","inflammatory bowel disease"]conditions/crohns_disease_proximity_map.csv

2. sources

The subreddits + date windows each condition pulls from. Most conditions have one source; T1D has three (diabetes_t1, Type1Diabetes, diabetes) because the condition is spread across general and specific subs.

Schema

columntypenotes
source_idSERIAL PK
condition_nameTEXT FK→ conditions
subredditTEXTcase preserved, no r/ prefix
date_rangeTEXTformat YYYY-MM-DD_YYYY-MM-DD
extracted_dirTEXToutput dir under data/extracted/
filtered_dirTEXTwhere pre-LLM filtered JSONL lives under data/filtered/

Sample data

source_idcondition_namesubredditdate_rangeextracted_dirfiltered_dir
1ulcerative_colitisUlcerativeColitis2026-02-27_2026-03-27ulcerative_colitisUlcerativeColitis
2type1_diabetesdiabetes_t12026-02-27_2026-03-27type1_diabetesdiabetes_t1
3type1_diabetesType1Diabetes2026-02-27_2026-03-27type1_diabetesType1Diabetes
4type1_diabetesdiabetes2026-02-27_2026-03-27type1_diabetesdiabetes
5migrainesmigraine2026-02-27_2026-03-27migrainesmigraine

3. raw_items

Reddit submissions and comments after the term-match filter. The raw Reddit JSON has 50+ fields; only the load-bearing ones are modeled here. Each item gets at most one extraction (stage 4 LLM call).

Schema

columntypenotes
item_idTEXT PKReddit's id (t3_* for posts, t1_* for comments), prefix stripped
subredditTEXT FK→ sources.subreddit
kindENUMpost or comment
authorTEXT FK→ authors.author_username
titleTEXTNULL for comments
bodyTEXTselftext for posts, body for comments
scoreINTupvotes - downvotes
created_utcBIGINTunix seconds
permalinkTEXTrelative path under reddit.com
parent_idTEXTNULL for posts; t3_/t1_ for comments

Sample data

item_idsubredditkindauthortitlebodyscorecreated_utcpermalinkparent_id
1rmcvbjUlcerativeColitispostComfortable-Way-8029Tremfya at month 5 - bleeding back, dr says coast on 10mg predDiagnosed pancolitis 8 months ago. Failed Remicade after a few months (wiped pancolitis but sigmoid got worse). Started Tremfya 11/29/25...421773312000/r/UlcerativeColitis/comments/1rmcvbj/tremfya_at_month_5/
1rg8hhuUlcerativeColitispostHot_Storage2517Failing Mesalazine?Hi there, I'm wondering if anyone has failed Mesalazine and how it developed?171772700000/r/UlcerativeColitis/comments/1rg8hhu/failing_mesalazine/
oo5xqr2UlcerativeColitiscommentredditor_xyzI switched from oral mesalamine to rectal enemas after 3 months and it helped a lot.81772795000/r/UlcerativeColitis/comments/1rg8hhu/.../oo5xqr2/t3_1rg8hhu
Reality: data/raw/<sub>/*.jsonl is the unfiltered Arctic Shift bulk pull; data/filtered/<sub>/*.jsonl is the term-match filtered subset that enters stage 4. Only the filtered subset gets an extraction row.

4. extractions

One row per Reddit item that survived the LLM extraction pass. The extraction itself produces structured JSON; this table holds the post-level metadata. Symptoms and treatments are joined off this row.

Schema

columntypenotes
extraction_idTEXT PK= item_id (1:1 with raw_items)
condition_nameTEXT FK→ conditions (which pipeline owns this)
contains_treatment_infoBOOLfalse → row is a skip stub
stanceTEXTrant / embodied / recommendation / asking / informational
about_conditionBOOLtrue → on-topic for the condition tile
commercial_intentTEXTnone / ad / affiliate / brand_mention
skip_reasonTEXTNULL unless contains_treatment_info=false

Sample data

extraction_idcondition_namecontains_treatment_infostanceabout_conditioncommercial_intentskip_reason
1rmcvbjulcerative_colitistrueranttruenone
1rg8hhuulcerative_colitistrueaskingtruenone
o7q6c3mulcerative_colitisfalseinformationalfalsenonenot about UC treatment
1rkkzwqulcerative_colitistrueembodiedtruenone

5. post_symptoms

Symptoms the author mentions having (post-level — not specific to any one treatment). This is the field currently dropped during cluster aggregation — the data is here but the patient surface doesn't index it. Phase 1 of the symptom-first frontend = aggregating this into a per-cluster symptom-targeted counts map.

Schema

columntypenotes
extraction_idTEXT FK→ extractions
symptom_nameTEXTfree-text, in the author's words (no controlled vocabulary)
durationTEXTfree-text, often empty

Sample data

extraction_idsymptom_nameduration
1rmcvbjpancolitissince diagnosis, 8 months ago
1rmcvbjinflammation in sigmoidongoing
1rmcvbjlight, intermittent bleedingreturned right before going down to 5mg prednisone
1rmcvbjactive bleeding in the cecum
1rmcvbjexhausted 24/7
1rg8hhuproctosigmoiditisa year ago
1rg8hhubloody mucus

6. treatments_raw

One row per treatment mention. This is the granular pre-clustering layer — each Reddit post mentioning Remicade produces a new treatment_event_id. Stage 7 (embedding + clustering) merges these into clusters.

Schema

columntypenotes
treatment_event_idSERIAL PK
extraction_idTEXT FK→ extractions
interventionTEXTraw LLM-extracted name (e.g., “Humira” or “adalimumab” or “Hyrimoz”)
categoryTEXTmedication / supplement / dietary / lifestyle / procedure / device / other
doseTEXTfree-text
duration_triedTEXTfree-text
outcomeENUMhelped_a_lot / helped_some / no_effect / worsened / mixed / unclear
outcome_detailTEXTnarrative blurb from the LLM
combined_withTEXT[]other treatments mentioned in the same regimen
caveatsTEXTe.g., “failed after a few months”

Sample data

treatment_event_idextraction_idinterventioncategorydoseduration_triedoutcomeoutcome_detailcombined_withcaveats
842311rmcvbjRemicademedicationa few monthsworsenednearly wiped out pancolitis and rectum looked great, but inflammation in sigmoid got much worse; patient failed the drug[]failed after a few months
842321rmcvbjTremfyamedicationstarted 11/29/25, ongoing at time of postmixedcolon wall thickness returned to normal limits; active bleeding in cecum persists; light intermittent bleeding returned before dose reduction[""prednisone""]patient was very sick at baseline
842331rmcvbjprednisonemedication10mg current, previously higherunclearadvised to coast on 10mg after light bleeding returned[""Tremfya""]taper was attempted but bleeding returned
842341rg8hhuMesalazinemedication2gno_effectdidn't do much[]
842351rg8hhuMesalazinemedication4ga few daysworsenedsuddenly gave much more urgency, mucus and speckles of blood; poster stopped after a few days[]

7. treatment_symptoms_targeted

The link table that says “treatment X was tried specifically for symptom Y.” This is the high-precision symptom signal — not every symptom the author mentioned, but specifically what they were targeting with this treatment. Currently aggregated to nothing on the patient surface; it's the input to the deferred symptom-first frontend.

Schema

columntypenotes
treatment_event_idINT FK→ treatments_raw
symptom_nameTEXTfree-text, in author's words

Sample data

treatment_event_idsymptom_name
84231pancolitis
84232pancolitis
84232inflammation in sigmoid
84232active bleeding in the cecum
84233pancolitis
84233inflammation in sigmoid
84233light, intermittent bleeding

Authority signal (Phase B credibility)

8. authors

Per-author behavioral features used by Phase B credibility scoring (stage 6). Pulled from Arctic Shift author-history queries; n_items caps at 1000 due to Arctic Shift's per-author rate-limit floor. The truncated flag tells stage 6 to fall back to Phase A heuristics for those authors.

Schema

columntypenotes
author_usernameTEXT PKReddit handle
n_itemsINT= n_posts + n_comments (capped at 1000)
n_postsINT
n_commentsINT
truncatedBOOLtrue → history hit 1000-item cap; Phase B can't use this author
first_seen_utcBIGINT
last_seen_utcBIGINT
account_age_daysNUMERIC
distinct_subsINTdiversity of subs the author posts in
sub_entropyNUMERICShannon entropy over sub_counts (concentration vs spread)
mean_scoreNUMERICaverage upvote score per item
median_scoreNUMERIC
pct_at_floorNUMERICfraction of items at score ≤ 0 (low-engagement signal)
pct_engagedNUMERICfraction at score ≥ threshold (high-engagement signal)
commercial_link_ratioNUMERICfraction of comments containing shopping/affiliate links

Sample data

author_usernamen_itemsn_postsn_commentstruncatedfirst_seen_utclast_seen_utcaccount_age_daysdistinct_subssub_entropymean_scoremedian_scorepct_at_floorpct_engagedcommercial_link_ratio
0-60_now_what1137661071false165251499517789852821463.8383.6736.720.2850.1430.066
Comfortable-Way-8029841272false17480000001779000000361.072.14.210.310.110.0
Hot_Storage251743835false17500000001779000000344.0122.95.020.260.160.0
mega_poster_421000212788true150000000017790000003230.0895.212.430.180.320.04

9. author_sub_counts

Per-author per-subreddit activity counts. Used to compute sub_entropy, identify cross-condition authors (an author posting in >=2 of the 26 condition subs), and detect cluster-poisoning patterns.

Schema

columntypenotes
author_usernameTEXT FK→ authors
subredditTEXT
countINTitems in this sub by this author

Sample data

author_usernamesubredditcount
0-60_now_whatFTMOver30185
0-60_now_whatftm223
0-60_now_whatTopSurgery116
0-60_now_whatTestosteroneKickoff106
0-60_now_whatFTMOver50134
0-60_now_whatcovidlonghaulers32
0-60_now_whatLowDoseNaltrexone6
Comfortable-Way-8029UlcerativeColitis42
Comfortable-Way-8029IBD17

Clustering output (stage 7 + 8 + 9)

10. clusters

The aggregated treatments per condition. This is what powers the patient-facing Browse tab and the dev corpus view. Each row collapses many treatments_raw mentions of the same drug/treatment (under different LLM-written names) into one entity with a canonical_name and aggregate metrics.

Schema

columntypenotes
cluster_idTEXT PKcomposite: <condition>:<int> (e.g. ulcerative_colitis:18)
condition_nameTEXT FK→ conditions
canonical_nameTEXTmost-common alias used as the display label
categoryTEXTaggregated from member treatment_events
mention_countINTtotal treatments_raw rows in this cluster
distinct_authorsINTunique authors contributing
positive_ratioNUMERIC(helped_a_lot + helped_some) / total outcomes
cluster_weightNUMERICPhase B credibility-weighted score (replaces raw mention_count in ranking)
cluster_breadthINT= distinct_authors (anti-poisoning signal)
pubmed_countINTPubMed hits with [Title/Abstract] field tag
evidence_levelENUMstrong / moderate / weak / none (derived from pubmed_count + thresholds)
novelty_scoreNUMERICcluster_weight if evidence_level=none, scaled down otherwise (surfaces under-studied treatments)
pubmed_pro_countINTtop-N classified as “supports” (stage 9.5)
pubmed_con_countINT“against”
pubmed_neutral_countINT“background”

Sample data

cluster_idcondition_namecanonical_namecategorymention_countdistinct_authorspositive_ratiocluster_weightcluster_breadthpubmed_countevidence_levelnovelty_scorepubmed_pro_countpubmed_con_countpubmed_neutral_count
ulcerative_colitis:3ulcerative_colitismesalaminemedication189870.62178.4872959strong4.8401
ulcerative_colitis:18ulcerative_colitismesalamine retention enemasmedication29120.7129.62120none29.62000
ulcerative_colitis:7ulcerative_colitisRemicademedication42280.5540.1281247strong5.6410
ulcerative_colitis:12ulcerative_colitisprednisonemedication156980.41151.2984823strong3.1221
type1_diabetes:1type1_diabetesinsulin pumpdevice3172010.88302.4201892strong7.1500

11. cluster_aliases

The original LLM-extracted names that all map to one canonical_name. Stage 7 builds these by embedding similarity + RxNorm brand→generic mapping. This is where the “humira / adalimumab / Hyrimoz / Yusimry” problem gets solved.

Schema

columntypenotes
cluster_idTEXT FK→ clusters
alias_nameTEXTraw LLM name (preserved before canonicalization)

Sample data

cluster_idalias_name
ulcerative_colitis:18mesalamine 4g retention enemas
ulcerative_colitis:18mesalamine oral and enemas
ulcerative_colitis:18mesalamine rectal enemas
ulcerative_colitis:18mesalamine rectal retention enemas
ulcerative_colitis:18mesalamine retention enema
ulcerative_colitis:18mesalamine retention enemas
ulcerative_colitis:18mesalazine enema
ulcerative_colitis:18mesalazine enemas
ulcerative_colitis:18mesalazine enemas 2g
ulcerative_colitis:18mesalazine enemas 4g
12 aliases all collapsed into one cluster. Before the stage-7 RxNorm fix (2026-05-26), variants like “mesalamine” vs “mesalazine” were sometimes split across clusters — UK vs US spellings, plus typos like “mesalimine”.

12. cluster_outcomes

Tally of outcomes across all member treatment_events. Same enum as treatments_raw.outcome; this is the histogram per cluster.

Schema

columntypenotes
cluster_idTEXT FK→ clusters
outcomeENUMhelped_a_lot / helped_some / no_effect / worsened / mixed / unclear
countINT

Sample data

cluster_idoutcomecount
ulcerative_colitis:18helped_a_lot9
ulcerative_colitis:18helped_some3
ulcerative_colitis:18no_effect3
ulcerative_colitis:18worsened1
ulcerative_colitis:18mixed1
ulcerative_colitis:18unclear12

13. cluster_subs

Which subreddits the cluster's mentions came from. For single-source conditions this is one row; for multi-source like T1D it identifies how distributed the signal is.

Schema

columntypenotes
cluster_idTEXT FK→ clusters
subredditTEXT

Sample data

cluster_idsubreddit
ulcerative_colitis:18UlcerativeColitis
type1_diabetes:1diabetes_t1
type1_diabetes:1Type1Diabetes
type1_diabetes:1diabetes

14. cluster_treatment_events

The M:N join between clusters and treatments_raw. This is what makes the patient drilldown work — clicking a cluster on the dashboard shows you the original posts.

Schema

columntypenotes
cluster_idTEXT FK→ clusters
treatment_event_idINT FK→ treatments_raw

Sample data

cluster_idtreatment_event_id
ulcerative_colitis:784231
ulcerative_colitis:1284233
ulcerative_colitis:384234
ulcerative_colitis:384235
Treatment event 84232 (Tremfya from the same post) lives in ulcerative_colitis:??? — Tremfya gets its own cluster because RxNorm canonicalizes to guselkumab which is distinct from mesalamine and the biologics class above.

Evidence enrichment (stage 9 + 9.5)

15. pubmed_articles

PubMed enrichment per cluster. Stage 9 queries PubMed with "<canonical_name>[Title/Abstract] AND <condition_pubmed_term>[Title/Abstract]", fetches top-N (typically 5) abstracts, and stage 9.5 has Haiku classify each abstract as pro/con/neutral for that cluster.

Schema

columntypenotes
pmidTEXT PKPubMed ID (composite with cluster_id since same PMID can appear in multiple clusters)
cluster_idTEXT FK→ clusters
titleTEXT
sourceTEXTjournal abbreviation
pubdateTEXTfree-text, often year + month
abstractTEXTfull abstract text
classificationENUMpro / con / neutral (from stage 9.5 Haiku call)

Sample data

pmidcluster_idtitlesourcepubdateabstract_snippetclassification
30840605ulcerative_colitis:3ACG Clinical Guideline: Ulcerative Colitis in Adults.Am J Gastroenterol2019 MarUlcerative colitis (UC) is an idiopathic inflammatory disorder. These guidelines indicate the preferred approach to the management of adults with UC...pro
39225555ulcerative_colitis:3Practical management of mild-to-moderate ulcerative colitis: an international expert consensus.Expert Rev Gastroenterol Hepatol2024 AugAlthough there are well-defined guidelines for the management of mild-to-moderate ulcerative colitis (UC), there are still unmet needs...pro
35672432ulcerative_colitis:7Infliximab safety profile in inflammatory bowel disease: a 10-year retrospective.Inflamm Bowel Dis2022 JunLong-term infliximab use was associated with increased risk of opportunistic infection in 4.2% of patients...con

Lookup / state tables

16. rxnorm_cache

Brand→generic mapping cache for stage 7 (clustering canonicalization) and stage 9 (PubMed query construction). 58,894 entries; empty list = “not in RxNorm or in the manual fallback dict.” Hand-maintained fallback dict in extract/rxnorm.py covers non-US brands and recently-approved FDA drugs RxNorm hasn't ingested yet (e.g., slynd).

Schema

columntypenotes
input_nameTEXT PKlowercase LLM-extracted treatment name
matched_genericsTEXT[]generic name(s); empty array if no match

Sample data

input_namematched_generics
5-asa["mesalamine"]
1% hydrocortisone cream["hydrocortisone"]
5% lidocaine patches["lidocaine"]
slynd["drospirenone"]
mounjaro["tirzepatide"]
bright spots and landmines"" book[]
0 net carb tortillas[]
0 2wk
Most empty matches are not drug names at all — lifestyle / diet / book references the LLM happened to flag as treatments. The cache stores them anyway to avoid re-querying RxNorm on every pipeline run.

17. manifest

Per-condition x per-pipeline-step freshness tracking. This is the integrity perimeter: each pipeline step writes a row on success, and check_manifest.py compares stored shas to current code/feature shas to detect staleness.

Schema

columntypenotes
condition_nameTEXT FK→ conditions
step_nameENUMbuild_pipeline / pubmed_enrich / pubmed_classify / build_ui
atTIMESTAMPUTC ISO-8601
code_shaTEXTsha256 of the script that wrote this row
features_shaTEXTsha256 of data/authors/_features.json (build_pipeline only)
n_clustersINTbuild_pipeline only
n_authors_phase_bINTbuild_pipeline only
api_cost_usdNUMERICpubmed_classify only (Haiku spend)
preserve_pubmedBOOLbuild_pipeline only — whether stage 9 results were preserved across rebuild

Sample data

condition_namestep_nameatcode_shafeatures_shan_clustersn_authors_phase_bapi_cost_usdpreserve_pubmed
adenomyosisbuild_pipeline2026-05-26T05:03:31Z86af8d18...32f0f3ab32828d7e...e469f9b153422true
adenomyosispubmed_enrich2026-05-26T08:18:18Z0a94c774...c9a6a4c5
adenomyosispubmed_classify2026-05-26T14:29:13Z7a507652...99a50e1d0.2214
adenomyosisbuild_ui2026-05-26T15:52:32Zaf33509b...fec61631
ulcerative_colitisbuild_pipeline2026-05-28T03:11:42Z86af8d18...32f0f3ab32828d7e...e469f9b4551247true
type1_diabetesbuild_pipeline2026-05-28T04:55:18Z86af8d18...32f0f3ab32828d7e...e469f9b6123214true

What this view does NOT capture

What lives where on disk (file→table mapping)

on-disk filemaps to table(s)
conditions/<name>.jsonconditions + sources
data/raw/<sub>/*.jsonlraw_items (unfiltered superset)
data/filtered/<sub>/*.jsonlraw_items (term-match filtered subset)
data/extracted/<cond>/<id>.jsonextractions + post_symptoms + treatments_raw + treatment_symptoms_targeted
data/authors/_features.jsonauthors + author_sub_counts
data/authors/<user>.jsonl(raw input to authors; not modeled as a table)
data/pipeline/<cond>_pipeline_output.jsonclusters + cluster_aliases + cluster_outcomes + cluster_subs + cluster_treatment_events + pubmed_articles
data/rxnorm_cache.jsonrxnorm_cache
data/pipeline/_manifest.jsonmanifest