r/POTS · 2026-02-27 → 2026-03-27 · 25,614 raw items · 8,501 mentions

Treatment Signal Mining for Chronic Conditions

Glean Health collates Reddit treatment mentions with PubMed literature coverage to help patients with chronic conditions arrive at their appointments better-prepared. We mine condition-specific subreddits, score each poster's credibility from their full cross-Reddit history, cluster the treatments they mention, and surface how much published research exists for each. Not a recommender — a doctor-visit prep tool. The patient picks what to ask about; we save them the hours of Reddit and PubMed searching.

The Hypothesis

Patients with chronic conditions share what's worked for them on Reddit — both standard-of-care and long-tail options. Finding it requires hours of clicking through threads and cross-checking PubMed. The hypothesis: collating those mentions across credibility-scored patients, alongside PubMed presence per treatment, gives a 60-second version of that research so a patient arrives at their appointment with real questions instead of vague impressions.

The Approach

Extract blind. The LLM mines Reddit for treatment mentions without seeing PubMed first — patient signal isn't contaminated by literature priming. PubMed is then added as a parallel column (paper count + search link), so the patient sees community endorsement and literature coverage side-by-side. We collate, we don't combine.

Authority Matters

Not all posters carry the same weight. We pull each author's full cross-Reddit history — every subreddit they post in, not just the ones in our corpus — and score credibility from their behavior: posting longevity, subreddit diversity, commercial-link ratio, engagement quality. A U-curve catches single-purpose accounts; a commerce signal catches affiliate-driven promoters. Real patients with broad Reddit footprints get higher weight than focused shills.

Data Source

Arctic Shift — full Reddit archive, 2005-2026, every public subreddit, 2.5B items. We pull per-subreddit dumps from condition-specific communities across 25 chronic conditions, plus per-author cross-Reddit histories for credibility scoring. Reddit API only fills gaps in the live tail.

25,614

Raw Items Pulled

r/POTS · 2026-02-27 → 2026-03-27. 2098 submissions + 23,516 comments.

7142

After Filtering

Hard filter: posts need len 200–8000 and score ≥ 1; comments need len ≥ 75, score ≥ 1, and a treatment-verb keyword. No deleted or bot content.

3840

Contain Treatment Info

3840/6924 items (55%) had extractable treatment information, including those framed as rants or questions.

1038

Treatment Clusters

8,501 total mentions clustered into 1038 distinct treatments.

Pipeline Architecture

Every box in the diagram is clickable. Numbered boxes are pipeline stages; "Arctic Shift" and "Reddit API" describe the data sources; "Condition Config" describes the per-condition manifest that parameterizes stages 8 through 9.5.

Retired Stage 3 — condition-context filter → why it's gone
flowchart TD subgraph D["DATA SOURCES"] D1["Arctic Shift (Phase 1)
Per-sub bulk dumps + per-author API
2005 through 2026-02"] D2["Reddit API (Phase 2)
Gap backfill + ongoing ingestion"] D1 -.-> D2 end subgraph CFG["CONDITION CONFIG — conditions/<name>.json"] C1["sources[] — subreddits, dirs, date ranges
pubmed_terms[] — for stage 9
proximity_map_csv — for stage 6 U-curve
--condition flag drives stages 8-10"] end subgraph FUNNEL["THE FUNNEL — cheap filters before LLM"] S1["1. Ingest
Per-sub JSONL (--sub)
deterministic I/O"] S2["2. Hard Filter
len 200-8000, score ≥1
rules, keeps ~77%"] S45["4+5. LLM Classify + Extract
Haiku 4.5 via Anthropic API
async, concurrency-capped, prompt-cached
contains_treatment_info, stance,
commercial_intent, symptoms[],
treatments[] w/ symptoms_targeted[]
~$3-5 per condition / 1.6k items"] S7["7. Treatment Clustering
(1) RxNorm canonicalization via _drug_key
collapses brand variants pre-embedding
(2) fastembed BAAI/bge-small-en-v1.5
agglomerative complete-link cosine ≥ 0.75
Atomic + combined_with links"] S1 --> S2 --> S45 --> S7 end subgraph TRACK_C["PARALLEL TRACK: USER AUTHORITY (deterministic)"] U1["Distinct Authors
from filtered corpus"] U2["Per-Author Pull
Arctic Shift API
cross-sub history"] U3["Six Feature Families
longevity / karma / commerce
U-curve fit / dispersion / sufficiency"] U4["user_credibility
composite (0.3 to 1.3)"] U1 --> U2 --> U3 --> U4 end subgraph AGG["AGGREGATION & EVIDENCE"] S8["8. Cluster Aggregation
Σ(post_q × user_c)"] S9["9. PubMed Enrichment
NCBI eUtils, [Title/Abstract] tag
Evidence coverage tag per cluster"] S95["9.5 Pro/Con/Neutral Classify
Haiku 4.5 on top-N abstracts
EFETCH bulk fetch + LLM label
pro / con / neutral counts per cluster"] S10["10. Surface Ranking
Novelty = weight × (1-evidence)"] S8 --> S9 --> S95 --> S10 end D1 --> S1 C1 -.drives.-> S1 C1 -.drives.-> S8 C1 -.drives.-> S9 S2 -.author list.-> U1 S7 --> S8 U4 --> S8 classDef dataSource fill:#eff6ff,stroke:#4f46e5,stroke-width:2px,color:#1e293b classDef config fill:#fffbeb,stroke:#d97706,stroke-width:2px,color:#1e293b classDef funnel fill:#fef2f2,stroke:#dc2626,stroke-width:2px,color:#1e293b classDef parallelTrack fill:#f5f3ff,stroke:#7c3aed,stroke-width:2px,color:#1e293b classDef aggregation fill:#ecfdf5,stroke:#059669,stroke-width:2px,color:#1e293b classDef highlight fill:#f5f3ff,stroke:#7c3aed,stroke-width:3px,color:#1e293b classDef finalNode fill:#ecfdf5,stroke:#059669,stroke-width:3px,color:#1e293b classDef configNode fill:#fffbeb,stroke:#d97706,stroke-width:2px,color:#1e293b class D dataSource class CFG config class FUNNEL funnel class TRACK_C parallelTrack class AGG aggregation class S45 highlight class S10 finalNode class C1 configNode
Click a stage in the diagram to see its details

Stage 1: Ingest

Pull raw posts and comments from Arctic Shift API for target subreddits.

Input

Arctic Shift API — per-subreddit paginated search, date range, posts + comments

Output

JSONL files per (subreddit, kind, date-range) in data/raw/
2098
Posts
23,516
Comments
25,614
Total

Stage 2: Hard Filter

Drop noise: deleted content, bots, too short/long, low score. Comments also require treatment-verb keyword match.

Input

25,614 raw items

Output

7142 filtered items (27% kept)
Posts: len in [200, 8000] AND score ≥ 1 AND not deleted/bot
Comments: len ≥ 75 AND score ≥ 1 AND contains treatment verb AND not deleted/bot
1420
Posts kept (67%)
5722
Comments kept (24%)
18,472
Dropped

Stage 4+5: LLM Classification + Extraction

Haiku 4.5 reads each item and produces structured JSON: treatment info presence, stance, commercial intent, and extracted treatment details.

Input

7142 items × (subreddit context + title + body)

Output

3840 items with treatment info (55%), 8,501 treatment mentions
Per item: contains_treatment_info, stance (7-class), about_condition, commercial_intent (3-level), skip_reason, symptoms[] (name + duration)
Per treatment (12 fields): intervention, category, form, dose, frequency, timing, duration_tried, outcome, outcome_detail, symptoms_targeted, combined_with, caveats
6924
Items processed
3840
Contain treatments
8,501
Treatment mentions

Stage 6: User Authority (parallel track)

Score each author's credibility from their posting behavior. Runs in parallel with the LLM extraction track; only joins back at stage 8.

Input

Distinct author list from filtered corpus + Arctic Shift per-author cross-Reddit history

Output

user_credibility composite in [0.3, 1.3] per author
Phase A (within-corpus, always runs): karma + commerce + sufficiency
Phase B (when cross-Reddit history is cached): adds longevity + condition-fit U-curve + dispersion. Commerce becomes a blend of within-corpus LLM signal and cross-Reddit regex.

Current rollout (this condition): 2,993 of 3,009 authors scored on Phase B (99%). The remaining 16 are on Phase A — in most cases because their cached cross-Reddit history is too thin to compute Phase B features (very new accounts, very few items). For the other 26 conditions, the Arctic Shift author-history puller has cached ~86% of authors and is paused (see data/authors/_TRUNCATION_NOTE.md); each condition's Phase B coverage will lift once we re-run build_pipeline.py for it.

Stage 7: Treatment Clustering

Group treatment mentions by semantic similarity, so "L-glutamine", "l glutamine powder", and "Lglutamine" all collapse into one cluster — and so do harder cases like "low-FODMAP" and "FODMAP elimination" that pure string matching would miss.

Input

8,501 treatment mentions with free-text intervention names

Output

1038 treatment clusters with canonical names
Embed each intervention name with fastembed BAAI/bge-small-en-v1.5, L2-normalized.
Pairwise cosine distance → scipy agglomerative complete-linkage (NN-chain).
Cut the dendrogram at cosine similarity ≥ 0.75 → merge into same cluster.
Canonical name = most frequent spelling within cluster.

Stage 8: Per-Cluster Aggregation

Combine post-level quality and user-level credibility into a single cluster weight.

Input

Treatment clusters + author credibility scores

Output

cluster_weight, cluster_breadth, positive_ratio per cluster
cluster_weight = Σ(author_credibility) per mention
cluster_breadth = distinct_authors × distinct_subs
positive_ratio = (helped_a_lot + helped_some) / (helped_a_lot + helped_some + worsened + no_effect + mixed)
unclear outcomes are excluded from the denominator; if no rated outcomes, defaults to 0.5

Stage 9: PubMed Enrichment

Query PubMed for each treatment cluster to tag evidence coverage level.

Input

1038 cluster canonical names

Output

pubmed_count + evidence_level (none/weak/moderate/strong) + top article links
Query structure: "<treatment>" AND (<condition.pubmed_terms> joined with OR)
For this condition: "<treatment>" AND ("postural orthostatic tachycardia syndrome" OR "POTS" OR "dysautonomia" OR "autonomic dysfunction")
none: 0 results | weak: 1-5 | moderate: 6-20 | strong: 21+
699
None
170
Weak
77
Moderate
92
Strong

Stage 9.5: PubMed Pro/Con/Neutral Classification

For each cluster's top-N PubMed articles (from stage 9), fetch the abstract via NCBI EFETCH and ask Haiku 4.5 to classify the paper as pro (supports the treatment for this condition), con (refutes or warns), or neutral (mention/background/review without stance, or empty abstract). Conservative prompt — defaults to neutral when unsure. Honest sample-of-top-N framing: this classifies the top 5 most-relevance-sorted papers per cluster, not all pubmed_count papers.

Input

339 clusters × top-N pubmed_articles[].pmid from stage 9

Output

Per cluster: pubmed_pro_count / pubmed_con_count / pubmed_neutral_count / pubmed_classified_count. Per article: classification + fetched abstract
EFETCH bulk fetch (200 PMIDs/chunk, XML parsed) → AsyncAnthropic classify (concurrency=10, prompt-cached, max-cost guard)
Patient surface chip: {pro} supportive · {con} against · {neutral} background with tooltip "of the top N most-relevant PubMed papers"
128
Pro
36
Con
1052
Neutral
339
Clusters classified

Stage 10: Surface Ranking (dev-only QC)

Compute novelty_score as an internal QC metric — credibility-weighted Reddit signal scaled down by literature coverage. The patient-facing Browse tab does not use this; it sorts by net-positive credibility-weighted endorsement instead. Novelty is kept for the Results Table sort and the novelty histogram, useful when scanning for high-signal treatments the literature hasn't touched.

Input

Cluster weights + evidence levels

Output

Ranked treatment list → Results Table (dev tab only)
novelty_score = cluster_weight × (1 - evidence_score)

evidence_score: none=0.00 | weak=0.35 | moderate=0.70 | strong=0.95

Data Source: Arctic Shift (Phase 1)

Bulk Reddit archive that powers every ingest. Per-subreddit dumps + per-author cross-Reddit history search. Public mirror of Reddit going back to its first commit.

Coverage

2005-06 through 2026-02 monthly releases (~1-2 month lag). 2.5B items (1.3B comments + 1.3B submissions). 261.8 GB compressed across every public subreddit.

How we use it

Per-subreddit search (stage 1 ingest) + per-author history API (stage 6 user authority). Polite at ~2 req/sec; circuit-breaker at 5 failures → 30 min cool-off (~1.2k authors/IP threshold).
Endpoints: /api/posts/search?subreddit=X & /api/comments/search?subreddit=X (ingest)
/api/posts/search?author=Y & /api/comments/search?author=Y (user authority)
limit:"auto" returns 100-1000 per page. Schema-tagged on ingest: source=arctic_shift, retrieved_at=<timestamp>.

Data Source: Reddit API (Phase 2, future)

Not yet integrated. Planned for the live tail: Arctic Shift releases monthly and lags ~1-2 months behind real-time, so a production deployment needs Reddit API to close that gap.

Why Phase 2

Phase 1 (Arctic Shift only) proves the concept on stable historical data. The 1-2 month lag is acceptable for a research MVP but unacceptable for ongoing patient use.

What it adds

Backfill: ingest items from (latest Arctic Shift dump) → now. Ongoing: scheduled pulls for new posts in the 26 condition-specific subreddits.
Schema rule (already enforced in stage 1): every record carries source=arctic_shift|reddit_api and retrieved_at=<timestamp>, so the two sources can be mixed in the pipeline without ambiguity.

Condition Config — conditions/<name>.json

Per-condition manifest that parameterizes the pipeline. The --condition flag on stages 8 through 9.5 reads this file to drive what gets ingested, how PubMed is queried, and how the U-curve is fit. Onboarding a new condition is a config file + sample ingest — no code change.

Schema

sources[] — {subreddit, extracted_dir, filtered_dir, date_range} per source. pubmed_terms[] — condition terms ORed and wrapped in [Title/Abstract]. proximity_map_csv — pointer to the subreddit → cluster table for U-curve scoring.

For pots

Sources: 1 · PubMed terms: 4 · Proximity map: conditions/pots_proximity_map.csv
Operator handoff: --sub drives ingest/filter/extract; --condition drives build/enrich/classify/UI. The subreddit field inside sources[] is what to pass to --sub. Ingest is NOT condition-aware yet (manual handoff works; parameterization is a possible cleanup, not a blocker).

Stage 3: Retired (no longer in pipeline)

Stage 3 was specified in early-prototype design as a condition-context keyword filter sitting between hard-filter (2) and LLM extract (4+5). It was never implemented and was formally removed from pipeline.mmd on 2026-05-25.

Why it was unnecessary

Each condition's sources[] list pre-vets subreddit relevance (e.g., r/migraine items are already on-topic by construction). A separate keyword-matching pass would just re-filter what subreddit selection already filtered for free.

Why the number stays "3"

Stages 4+5, 6, 7, 8, 9, 9.5, 10 are referenced by number across code, comments, docs, and the doc-coupling guard's stage-alignment check. Re-numbering would touch dozens of references; leaving the gap is the cheaper signal.
See PLAN.md Decisions Log #29 and the architecture overview header for the same explanation in the project plan.

Data Flow Waterfall

POTS — r/POTS — 2026-02-27 → 2026-03-27 — every number below is from this run

1. Arctic Shift Pull
Source: Arctic Shift — full Reddit archive (2005–2026, ~2.5B items). We query their public API per-subreddit.
Method: Paginated search via /api/posts/search and /api/comments/search, sorted ascending by timestamp. Sliding after parameter for pagination. Rate limit: 0.5s between requests, honors X-RateLimit-Remaining header. Retry with exponential backoff on 429/5xx.
Tagging: Every item gets _source: "arctic_shift" and _retrieved_at: <unix_ts> appended — so we can distinguish data sources when Reddit API is added later.
Raw fields per item: id, author, subreddit, created_utc, score, title (posts), selftext (posts), body (comments), link_id (comments), parent_id (comments).
25,614
raw items
↓ 2098 submissions + 23,516 comments
2. Hard Filter
Purpose: Drop items that can't possibly contain extractable treatment information before spending LLM tokens on them.

Post filter (submissions):
Length [200–8000 chars] (title + body combined). Below 200: too short for treatment context (e.g., "IBS sucks" at 9 chars). Above 8000: mega-posts, usually life stories. Threshold calibrated from spike: original 2000-char cap was dropping real treatment-protocol posts.
Score ≥ 1. "Score" = Reddit karma = upvotes minus downvotes. Score 0 or negative means the community buried it (spam, off-topic, or auto-removed). Score 1 = at minimum the author's own upvote (default). Original threshold of ≥ 5 dropped 54% of corpus including real treatment claims. Relaxed to ≥ 1 after spike audit.
Not deleted/removed. Body is literally "[deleted]" or "[removed]" — no text to extract.
Not a bot. Blocklist: AutoModerator, RemindMeBot, sneakpeekbot, WikiSummarizerBot, LocationBot, RepostSleuthBot, FatFingerHelperBot.
→ 1420/2098 kept (67%)

Comment filter:
• Same author/deletion/bot rules as posts.
Length ≥ 75 chars. Shorter = "same," "thanks," "lol" — no treatment info possible.
Score ≥ 1. Same logic as posts.
Must contain at least one treatment-verb keyword. This is the key lever for comments — drops 78% of noise. A comment saying "same here :(" has no verb → dropped. A comment saying "tried L-glutamine 5g daily" → kept.
Treatment verbs: tried, took, taking, started, helped, helps, worked, works, cured, fixed, relief, reduced, improved, failed, stopped, recommend, suggest, noticed, eliminated, switched, prescribed, diagnosed
→ 5722/23,516 kept (24%)
7142
filtered
↓ dropped 18,472 items (72%)
6. User AuthorityPARALLEL TRACK
Branches off from Step 2 (filtered corpus); rejoins at Step 8 (Aggregation). Runs independently of LLM extraction — no dependency on what Stage 4+5 finds. The patient signal and the “who's saying it” signal stay separate until Stage 8 multiplies them.

Step A — Collect authors: distinct authors from the filtered corpus (subreddit-bounded). For this condition: 3,009 distinct authors scored.
Step B — Pull cross-Reddit history: for each author, arctic_client.author_submissions() + author_comments()no subreddit filter. We want the author's entire Reddit footprint to gauge whether they're a real human (broad activity across many subs) or a single-purpose account (concentrated in one or two health subs — the shill / health-anxiety signature).
Step C — Score: composite per author in [0.3, 1.3]:
Phase A (always, within-corpus): karma + commerce + sufficiency — 16 authors on Phase A here.
Phase B (when cross-Reddit history is cached): adds longevity + condition-fit U-curve + dispersion — 2,993 authors on Phase B here.

Why cross-Reddit footprint matters: a poster looks like a normal patient on r/migraine. The credibility check looks at the other 50 subs they post in — their cooking, their gaming, their work life. Real people have broad footprints; shills don't.

Rollout state (this condition): 2,993 of 3,009 authors on Phase B (99%); the remaining 16 fall back to Phase A because their cached history is too thin for Phase B features. The other 26 conditions are at lower Phase B coverage pending the U-curve re-aggregation pass — the Arctic Shift author-history puller cached ~86% of remaining authors before being paused (see data/authors/_TRUNCATION_NOTE.md).
3,009
scored authors
cluster_weight = Σ(user_credibility) per mention — this is where the parallel track rejoins (Step 8 input)
4+5. LLM Extraction
Model: Claude Haiku 4.5 (claude-haiku-4-5-20251001) via the Anthropic Python SDK (AsyncAnthropic) — async with a concurrency semaphore and ephemeral prompt caching on the system prompt. Hard per-run cost cap via --max-cost. Realized cost ~$0.0023/item.
Unit of analysis: One post or comment + subreddit context. Author identity is NOT passed to the LLM (user-authority scoring is a separate, independent track).

What the LLM classifies per item:
contains_treatment_info (bool): Does this post contain extractable treatment information? Measures content presence, not post-intent tone. A rant with an embedded "I tried X" still gets true.
stance (7-class): embodied (poster tried it) | secondhand (someone they know) | hearsay | question | rant | meta | none
about_condition (bool): Is the post on-topic for the target condition? Off-topic posts in a condition sub get false.
commercial_intent (3-level): Is the poster promoting something they created/profit from?
none = no self-promotion | weak = free tool/blog/channel | strong = paid product, referral codes, affiliate links
Mentioning someone else's ad/brand = none. Casual brand names = none. (Validated 7/7 on real + synthetic tests.)
skip_reason (text): When contains_treatment_info is false, brief explanation why. Used for calibration.
symptoms[]: array of {name, duration} extracted regardless of whether treatments are present.

What the LLM extracts per treatment (12 fields):
• intervention (free text, canonicalized downstream), category (9-class), form, dose, frequency, timing
• duration_tried, outcome (6-class: helped_a_lot | helped_some | no_effect | worsened | mixed | unclear)
• outcome_detail (free text), symptoms_targeted (subset of post-level symptoms this treatment is aimed at), combined_with (links to co-treatments), caveats

Compound protocols: "Low-FODMAP + L-glutamine + walking" becomes three atomic treatment records linked via combined_with. Clustering needs atomic units; protocol links preserved.

Key finding from spike: 64% of items contain treatment info. 68% of posts labeled "question" and 56% of "rants" had embedded treatment claims. Filtering by post-intent tone would destroy the pipeline.
3840
with treatments
if contains_treatment_info: extract treatments[] with schema fields | else: skip_reason = "why not"
↓ 3084 items (44%) had no extractable treatment content
Treatment Mentions
3840 items yielded 8,501 individual treatment mentions (avg 2.2 treatments per item).
Each mention is one atomic intervention. Compound protocols are decomposed: "low-FODMAP + probiotics" = 2 mentions, each with combined_with pointing to the other.
Intervention names are free text at this point — "L-glutamine," "l glutamine powder," "glutamine supplement" are all separate strings. Canonicalization happens in the next step.
8,501
mentions
7. Clustering
Purpose: Group variant spellings and semantically-equivalent phrasings of the same treatment into one cluster.
Method: Embed each intervention name with fastembed BAAI/bge-small-en-v1.5 (a sentence-embedding model), L2-normalize, then compute pairwise cosine distance and run scipy agglomerative complete-linkage clustering (NN-chain implementation, O(N²)). Cut the dendrogram at cosine similarity ≥ 0.75.
Why embeddings, not string matching: "low-fodmap diet" and "FODMAP elimination" are the same intervention but share few characters — string matching would miss it. Embeddings catch the semantic equivalence. Brand/generic pairs ("imodium" vs "loperamide") are handled separately via the RxNorm dictionary in extract/rxnorm.py, applied at stage 9 (PubMed query expansion).
Canonical name: Most frequent spelling within each cluster becomes the display name.
1038
clusters
8. Aggregation
Purpose: Score each treatment cluster by community consensus strength.

Formulas:
cluster_weight = Σ(author_credibility) for each mention in the cluster. Higher = more credible voices reporting this treatment.
Output range [0.3, 1.3]. Phase A (always runs): karma signal (1 - pct_at_floor) + commerce signal (LLM-flagged commercial_intent) + data sufficiency (min(n_posts/3, 1)). Phase B (when cross-Reddit history is cached): adds longevity (log-bounded by account age), condition-fit U-curve over proximity-map clusters, and dispersion (cross-sub entropy penalty); commerce becomes a blend of within-corpus LLM signal and cross-Reddit regex. Rollout state (this condition): 2,993 of 3,009 authors on Phase B (99%); the remaining 16 are on Phase A. The other 26 conditions are at lower Phase B coverage pending the U-curve re-aggregation pass — the Arctic Shift author-history puller cached ~86% of remaining authors before being paused (see data/authors/_TRUNCATION_NOTE.md). Per-author breakdown lives in pipeline_output.author_credibility_index.
cluster_breadth = distinct_authors × distinct_subreddits. Higher = signal isn't one person repeating themselves.
positive_ratio = (helped_a_lot + helped_some) / (helped_a_lot + helped_some + worsened + no_effect + mixed). The "did it work?" rate. unclear outcomes are excluded; defaults to 0.5 if no rated outcomes.
Σ
weighted scores
9. PubMed Enrichment
Purpose: Tag each treatment with how much published medical research exists. PubMed annotates; it never subtracts. Even well-studied treatments stay in the output.
Query structure: "<treatment_name>" AND (<condition.pubmed_terms> joined with OR) — terms come from each condition's config file.
For this condition (pots): "<treatment_name>" AND ("postural orthostatic tachycardia syndrome" OR "POTS" OR "dysautonomia" OR "autonomic dysfunction")
API: NCBI E-utilities (free, no key required). ~3 requests/sec. Returns result count + top 5 PMIDs. Article titles fetched via eSummary endpoint.

Evidence level mapping:
0 results → none (evidence_score = 0.00)
1–5 results → weak (evidence_score = 0.35)
6–20 results → moderate (evidence_score = 0.70)
21+ results → strong (evidence_score = 0.95)

This run: 699 none | 170 weak | 77 moderate | 92 strong
699
novel (no PubMed)
novelty_score = cluster_weight × (1 - evidence_score)
10. Surface Ranking (dev-only QC)
Purpose: Compute novelty_score as an internal QC metric. The patient-facing Browse tab does not use this — it sorts by net-positive credibility-weighted endorsement so well-studied and under-studied treatments are surfaced together. Novelty is retained for the dev-facing Results Table sort and the novelty histogram, where it's useful for scanning the long tail.
Formula: novelty_score = cluster_weight × (1 - evidence_score)

How it works:
• cluster_weight=10, evidence=none (0.00): novelty = 10 × 1.0 = 10.0 ← high signal, zero research
• cluster_weight=10, evidence=strong (0.95): novelty = 10 × 0.05 = 0.5 ← well-studied, suppressed in this internal metric
• cluster_weight=2, evidence=none: novelty = 2 × 1.0 = 2.0 ← novel but weak signal

Why we keep it: for dev QC. The Results Table novelty sort is a fast way to scan for high-credibility patient signal that the literature hasn't touched — candidates worth investigating further, even though we never present them to patients as such.
1038
ranked treatments
0
Treatment Clusters
0
Total Mentions
0
Posts Analyzed
0
Novel (no PubMed)
Helped
Mixed
Worsened/No effect
Unclear
# Treatment Category Mentions Authors Outcomes PubMed Evidence Novelty

Results Charts

Evidence Distribution

Treatment Categories

Outcome Distribution (all mentions)

Mentions vs PubMed Coverage

Top 25 Treatments by Mention Count

Novelty Score Distribution

Project Structure

How the codebase is organized. Data files (raw, filtered, extracted) are generated by the pipeline and gitignored.

glean-health/
CLAUDE.md — Project rules, coupled-artifact protocol, stack description
PLAN.md — Living plan: architecture, 34 decisions, spike findings, open questions, next actions
pipeline.html — Coupled Mermaid diagram (standalone, open in browser to view pipeline)
schema.html — Data schema & lineage: every table/file, schema, and join, stage-by-stage
web/ (Vercel app) — Patient surface: Next.js app on Vercel reading Supabase Postgres (condition + treatment search)
dev.html — Dev hub card grid linking out to schema, pipeline, corpus, and dev_status
condition_map.md — 95 subreddits mapped to 9 clusters for user-authority U-curve scoring
condition_map.csv — Machine-readable version of condition map (subreddit, cluster, notes)
.gitignore — Excludes data/, deploy/, .wrangler/ from git
ingest/ STAGE 1-2
Arctic Shift API client and data ingestion pipeline
arctic_client.py — ArcticShiftClient class: paginated search, rate limiting, retry, per-author queries
ingest_sub.py — CLI: pull one subreddit's posts + comments for a date range
ingest_all.py — CLI: batch pull subreddits across all 26 condition configs, then auto-filter
filter_posts.py — Hard filter for submissions: len [200-8000], score ≥ 1, not deleted/bot
filter_comments.py — Hard filter for comments: len ≥ 75, score ≥ 1, treatment-verb keyword gate
README.md — Usage docs and scale notes
extract/ STAGES 4-10
LLM extraction, clustering, PubMed enrichment, and dashboard generation
run_extraction.py — Stage 4+5: Haiku 4.5 extraction via Anthropic SDK (async, prompt-cached, hard cost cap via --max-cost). Production prompt in prompt.md
build_pipeline.py — Stages 6-8: fuzzy clustering, author credibility, per-cluster aggregation
pubmed_enrich.py — Stage 9: query PubMed per cluster, tag evidence level, compute novelty score
build_ui.py — Stage 10: generate this dashboard (self-contained HTML with embedded data)
summarize.py — Quick CLI summary of extraction results (treatment counts, categories)
archive/spike-2026-04-06/ CALIBRATION
Throwaway scripts from the calibration spike that informed pipeline design decisions
pull_ibs_sample.py — One-off Arctic Shift pull for r/ibs Jan-Feb 2026
filter.py — Spike-era hard filter (led to threshold calibration)
sample_for_labeling.py — Random sample 100 items for hand-labeling
build_labeler.py — Browser-based labeling UI generator
run_haiku_batch.ps1 — First Haiku extraction test (50 items)
run_haiku_on_labeled.ps1 — Haiku vs hand-label agreement test (30 items)
tabulate.py — Compute spike metrics: density, keyword P/R, Haiku quality
test_commercial_prompt.py — Commercial intent prompt fix validation (7/7 pass)
data/ GENERATED — GITIGNORED
All data is regenerated by the pipeline. Not checked into git.
raw/<sub>/ — JSONL from Arctic Shift API (posts + comments per subreddit per date range)
filtered/<sub>/ — Post-filter JSONL (hard rules applied, noise removed)
extracted/<sub>/ — One JSON per item: Haiku's structured extraction output
pipeline/ — pipeline_output.json (clustered + scored) and results.html (this dashboard)
_spike_archive/ — Archived spike-era data files (label sheets, old extractions)
archive/ RETIRED
Previous version of the project (pre-rewrite)
v1-retired-2026-04-02/ — Original PDF pipeline design, research notes

Treatments people with POTS have tried

Below are the things real patients with POTS have talked about, surfaced from 6,924 Reddit posts. For each one you can see how many people endorsed it, how it worked for them, and how much published research exists. Bring this list to your next appointment as ideas to discuss with your doctor — both well-studied options and ones the literature hasn't caught up to yet are mixed in by how strongly the community endorses them.

Crowd-sourced from Reddit. Not medical advice. Talk to your doctor before trying anything. ← Back to all conditions

Type to filter the list below

What patients are talking about

Ranked by how strongly the community endorses each — well-studied and under-studied treatments mixed together. Click any card for posts, doses, and PubMed links.

Browse all 0 treatments (advanced) Full sortable list with filters — for power users.