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.
+-------------+
| 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
column
type
notes
condition_name
TEXT PK
kebab/snake, matches conditions/<name>.json
display_name
TEXT
patient-facing title
description
TEXT
one-line clinical scope
pubmed_terms
TEXT[]
used by stage 9 PubMed search (synonyms/abbreviations)
proximity_map_csv
TEXT
path to per-condition proximity map (semantic anti-poisoning)
Sample data
condition_name
display_name
description
pubmed_terms
proximity_map_csv
ulcerative_colitis
Ulcerative Colitis
Ulcerative colitis (chronic inflammatory bowel disease of the colon)
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
column
type
notes
source_id
SERIAL PK
condition_name
TEXT FK
→ conditions
subreddit
TEXT
case preserved, no r/ prefix
date_range
TEXT
format YYYY-MM-DD_YYYY-MM-DD
extracted_dir
TEXT
output dir under data/extracted/
filtered_dir
TEXT
where pre-LLM filtered JSONL lives under data/filtered/
Sample data
source_id
condition_name
subreddit
date_range
extracted_dir
filtered_dir
1
ulcerative_colitis
UlcerativeColitis
2026-02-27_2026-03-27
ulcerative_colitis
UlcerativeColitis
2
type1_diabetes
diabetes_t1
2026-02-27_2026-03-27
type1_diabetes
diabetes_t1
3
type1_diabetes
Type1Diabetes
2026-02-27_2026-03-27
type1_diabetes
Type1Diabetes
4
type1_diabetes
diabetes
2026-02-27_2026-03-27
type1_diabetes
diabetes
5
migraines
migraine
2026-02-27_2026-03-27
migraines
migraine
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
column
type
notes
item_id
TEXT PK
Reddit's id (t3_* for posts, t1_* for comments), prefix stripped
subreddit
TEXT FK
→ sources.subreddit
kind
ENUM
post or comment
author
TEXT FK
→ authors.author_username
title
TEXT
NULL for comments
body
TEXT
selftext for posts, body for comments
score
INT
upvotes - downvotes
created_utc
BIGINT
unix seconds
permalink
TEXT
relative path under reddit.com
parent_id
TEXT
NULL for posts; t3_/t1_ for comments
Sample data
item_id
subreddit
kind
author
title
body
score
created_utc
permalink
parent_id
1rmcvbj
UlcerativeColitis
post
Comfortable-Way-8029
Tremfya at month 5 - bleeding back, dr says coast on 10mg pred
Diagnosed pancolitis 8 months ago. Failed Remicade after a few months (wiped pancolitis but sigmoid got worse). Started Tremfya 11/29/25...
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.
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
column
type
notes
extraction_id
TEXT FK
→ extractions
symptom_name
TEXT
free-text, in the author's words (no controlled vocabulary)
duration
TEXT
free-text, often empty
Sample data
extraction_id
symptom_name
duration
1rmcvbj
pancolitis
since diagnosis, 8 months ago
1rmcvbj
inflammation in sigmoid
ongoing
1rmcvbj
light, intermittent bleeding
returned right before going down to 5mg prednisone
1rmcvbj
active bleeding in the cecum
1rmcvbj
exhausted 24/7
1rg8hhu
proctosigmoiditis
a year ago
1rg8hhu
bloody 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
column
type
notes
treatment_event_id
SERIAL PK
extraction_id
TEXT FK
→ extractions
intervention
TEXT
raw LLM-extracted name (e.g., “Humira” or “adalimumab” or “Hyrimoz”)
nearly wiped out pancolitis and rectum looked great, but inflammation in sigmoid got much worse; patient failed the drug
[]
failed after a few months
84232
1rmcvbj
Tremfya
medication
started 11/29/25, ongoing at time of post
mixed
colon 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
84233
1rmcvbj
prednisone
medication
10mg current, previously higher
unclear
advised to coast on 10mg after light bleeding returned
[""Tremfya""]
taper was attempted but bleeding returned
84234
1rg8hhu
Mesalazine
medication
2g
no_effect
didn't do much
[]
84235
1rg8hhu
Mesalazine
medication
4g
a few days
worsened
suddenly 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
column
type
notes
treatment_event_id
INT FK
→ treatments_raw
symptom_name
TEXT
free-text, in author's words
Sample data
treatment_event_id
symptom_name
84231
pancolitis
84232
pancolitis
84232
inflammation in sigmoid
84232
active bleeding in the cecum
84233
pancolitis
84233
inflammation in sigmoid
84233
light, 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
column
type
notes
author_username
TEXT PK
Reddit handle
n_items
INT
= n_posts + n_comments (capped at 1000)
n_posts
INT
n_comments
INT
truncated
BOOL
true → history hit 1000-item cap; Phase B can't use this author
first_seen_utc
BIGINT
last_seen_utc
BIGINT
account_age_days
NUMERIC
distinct_subs
INT
diversity of subs the author posts in
sub_entropy
NUMERIC
Shannon entropy over sub_counts (concentration vs spread)
mean_score
NUMERIC
average upvote score per item
median_score
NUMERIC
pct_at_floor
NUMERIC
fraction of items at score ≤ 0 (low-engagement signal)
pct_engaged
NUMERIC
fraction at score ≥ threshold (high-engagement signal)
commercial_link_ratio
NUMERIC
fraction of comments containing shopping/affiliate links
Sample data
author_username
n_items
n_posts
n_comments
truncated
first_seen_utc
last_seen_utc
account_age_days
distinct_subs
sub_entropy
mean_score
median_score
pct_at_floor
pct_engaged
commercial_link_ratio
0-60_now_what
1137
66
1071
false
1652514995
1778985282
1463.8
38
3.673
6.7
2
0.285
0.143
0.066
Comfortable-Way-8029
84
12
72
false
1748000000
1779000000
361.0
7
2.1
4.2
1
0.31
0.11
0.0
Hot_Storage2517
43
8
35
false
1750000000
1779000000
344.0
12
2.9
5.0
2
0.26
0.16
0.0
mega_poster_42
1000
212
788
true
1500000000
1779000000
3230.0
89
5.2
12.4
3
0.18
0.32
0.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
column
type
notes
author_username
TEXT FK
→ authors
subreddit
TEXT
count
INT
items in this sub by this author
Sample data
author_username
subreddit
count
0-60_now_what
FTMOver30
185
0-60_now_what
ftm
223
0-60_now_what
TopSurgery
116
0-60_now_what
TestosteroneKickoff
106
0-60_now_what
FTMOver50
134
0-60_now_what
covidlonghaulers
32
0-60_now_what
LowDoseNaltrexone
6
Comfortable-Way-8029
UlcerativeColitis
42
Comfortable-Way-8029
IBD
17
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.
cluster_weight if evidence_level=none, scaled down otherwise (surfaces under-studied treatments)
pubmed_pro_count
INT
top-N classified as “supports” (stage 9.5)
pubmed_con_count
INT
“against”
pubmed_neutral_count
INT
“background”
Sample data
cluster_id
condition_name
canonical_name
category
mention_count
distinct_authors
positive_ratio
cluster_weight
cluster_breadth
pubmed_count
evidence_level
novelty_score
pubmed_pro_count
pubmed_con_count
pubmed_neutral_count
ulcerative_colitis:3
ulcerative_colitis
mesalamine
medication
189
87
0.62
178.4
87
2959
strong
4.8
4
0
1
ulcerative_colitis:18
ulcerative_colitis
mesalamine retention enemas
medication
29
12
0.71
29.62
12
0
none
29.62
0
0
0
ulcerative_colitis:7
ulcerative_colitis
Remicade
medication
42
28
0.55
40.1
28
1247
strong
5.6
4
1
0
ulcerative_colitis:12
ulcerative_colitis
prednisone
medication
156
98
0.41
151.2
98
4823
strong
3.1
2
2
1
type1_diabetes:1
type1_diabetes
insulin pump
device
317
201
0.88
302.4
201
892
strong
7.1
5
0
0
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
column
type
notes
cluster_id
TEXT FK
→ clusters
alias_name
TEXT
raw LLM name (preserved before canonicalization)
Sample data
cluster_id
alias_name
ulcerative_colitis:18
mesalamine 4g retention enemas
ulcerative_colitis:18
mesalamine oral and enemas
ulcerative_colitis:18
mesalamine rectal enemas
ulcerative_colitis:18
mesalamine rectal retention enemas
ulcerative_colitis:18
mesalamine retention enema
ulcerative_colitis:18
mesalamine retention enemas
ulcerative_colitis:18
mesalazine enema
ulcerative_colitis:18
mesalazine enemas
ulcerative_colitis:18
mesalazine enemas 2g
ulcerative_colitis:18
mesalazine 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.
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
column
type
notes
cluster_id
TEXT FK
→ clusters
subreddit
TEXT
Sample data
cluster_id
subreddit
ulcerative_colitis:18
UlcerativeColitis
type1_diabetes:1
diabetes_t1
type1_diabetes:1
Type1Diabetes
type1_diabetes:1
diabetes
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
column
type
notes
cluster_id
TEXT FK
→ clusters
treatment_event_id
INT FK
→ treatments_raw
Sample data
cluster_id
treatment_event_id
ulcerative_colitis:7
84231
ulcerative_colitis:12
84233
ulcerative_colitis:3
84234
ulcerative_colitis:3
84235
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
column
type
notes
pmid
TEXT PK
PubMed ID (composite with cluster_id since same PMID can appear in multiple clusters)
cluster_id
TEXT FK
→ clusters
title
TEXT
source
TEXT
journal abbreviation
pubdate
TEXT
free-text, often year + month
abstract
TEXT
full abstract text
classification
ENUM
pro / con / neutral (from stage 9.5 Haiku call)
Sample data
pmid
cluster_id
title
source
pubdate
abstract_snippet
classification
30840605
ulcerative_colitis:3
ACG Clinical Guideline: Ulcerative Colitis in Adults.
Am J Gastroenterol
2019 Mar
Ulcerative colitis (UC) is an idiopathic inflammatory disorder. These guidelines indicate the preferred approach to the management of adults with UC...
pro
39225555
ulcerative_colitis:3
Practical management of mild-to-moderate ulcerative colitis: an international expert consensus.
Expert Rev Gastroenterol Hepatol
2024 Aug
Although there are well-defined guidelines for the management of mild-to-moderate ulcerative colitis (UC), there are still unmet needs...
pro
35672432
ulcerative_colitis:7
Infliximab safety profile in inflammatory bowel disease: a 10-year retrospective.
Inflamm Bowel Dis
2022 Jun
Long-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
column
type
notes
input_name
TEXT PK
lowercase LLM-extracted treatment name
matched_generics
TEXT[]
generic name(s); empty array if no match
Sample data
input_name
matched_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.
sha256 of data/authors/_features.json (build_pipeline only)
n_clusters
INT
build_pipeline only
n_authors_phase_b
INT
build_pipeline only
api_cost_usd
NUMERIC
pubmed_classify only (Haiku spend)
preserve_pubmed
BOOL
build_pipeline only — whether stage 9 results were preserved across rebuild
Sample data
condition_name
step_name
at
code_sha
features_sha
n_clusters
n_authors_phase_b
api_cost_usd
preserve_pubmed
adenomyosis
build_pipeline
2026-05-26T05:03:31Z
86af8d18...32f0f3ab
32828d7e...e469f9b
153
422
true
adenomyosis
pubmed_enrich
2026-05-26T08:18:18Z
0a94c774...c9a6a4c5
adenomyosis
pubmed_classify
2026-05-26T14:29:13Z
7a507652...99a50e1d
0.2214
adenomyosis
build_ui
2026-05-26T15:52:32Z
af33509b...fec61631
ulcerative_colitis
build_pipeline
2026-05-28T03:11:42Z
86af8d18...32f0f3ab
32828d7e...e469f9b
455
1247
true
type1_diabetes
build_pipeline
2026-05-28T04:55:18Z
86af8d18...32f0f3ab
32828d7e...e469f9b
612
3214
true
What this view does NOT capture
Text bodies in full — raw_items.body is shown truncated; the real corpus has the full text on disk in JSONL form.
Embeddings — stage 7 uses sentence embeddings (sentence-transformers, in-memory only) for clustering. Not persisted.
Author full history — data/authors/<username>.jsonl holds the raw 1000-item Arctic Shift pull per author. Only the aggregated features in authors survive.
Proximity maps — per-condition CSV files (conditions/<name>_proximity_map.csv) that anti-poison clusters by tagging semantically-related terms. Currently one-cluster stubs for 23/26 conditions .
Per-condition prompts — the LLM extraction prompt is in extract/prompt.md, condition-agnostic.