# Consumer Health Intelligence: Application Layer Guide

> **Vertical:** Consumer-facing health information
> **Use Case:** [consumer-health-intelligence.md](../../use-cases/consumer-health-intelligence.md)
> **Status:** Design spec — implementation not started

This guide describes the **application layer components** needed to build a Consumer Health Intelligence platform on Episteme. It covers what you build, not what Episteme provides.

---

## Architecture Overview

```
┌─────────────────────────────────────────────────────────────────────────────┐
│                           CONSUMER HEALTH APP                                │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐     │
│  │   PubMed     │  │   Reddit     │  │    FAERS     │  │    FDA       │     │
│  │   Crawler    │  │   Crawler    │  │   Crawler    │  │   Crawler    │     │
│  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘     │
│         │                 │                 │                 │              │
│         └────────────┬────┴────────┬────────┴─────────┬───────┘              │
│                      │             │                  │                      │
│                      ▼             ▼                  ▼                      │
│              ┌───────────────────────────────────────────────┐               │
│              │           NLP Extraction Pipeline              │               │
│              │  (claim identification, confidence scoring)    │               │
│              └───────────────────────┬───────────────────────┘               │
│                                      │                                       │
│                                      ▼                                       │
│              ┌───────────────────────────────────────────────┐               │
│              │          Source-Class Classifier               │               │
│              │  (tier assignment: 0=regulatory → 6=media)     │               │
│              └───────────────────────┬───────────────────────┘               │
│                                      │                                       │
│                                      ▼                                       │
│              ┌───────────────────────────────────────────────┐               │
│              │          Metadata Enrichment Service           │               │
│              │  (DOI lookup, journal info, engagement stats)  │               │
│              └───────────────────────┬───────────────────────┘               │
│                                      │                                       │
│                                      ▼                                       │
│              ┌───────────────────────────────────────────────┐               │
│              │              Agent Wallet                      │               │
│              │  (key management, Ed25519 signing)             │               │
│              └───────────────────────┬───────────────────────┘               │
│                                      │                                       │
└──────────────────────────────────────┼───────────────────────────────────────┘
                                       │ POST /assert
                                       ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                              EPISTEME DATABASE                               │
│  ┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐           │
│  │   WAL   │  │   KV    │  │ Indexes │  │ Lenses  │  │   MV    │           │
│  └─────────┘  └─────────┘  └─────────┘  └─────────┘  └─────────┘           │
└─────────────────────────────────────────────────────────────────────────────┘
                                       │ GET /query
                                       ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                           CONSUMER HEALTH APP                                │
│                                                                              │
│              ┌───────────────────────────────────────────────┐               │
│              │           Background Gardener                  │               │
│              │  (cluster detection, escalation assertions)    │               │
│              └───────────────────────────────────────────────┘               │
│                                                                              │
│              ┌───────────────────────────────────────────────┐               │
│              │           LLM Summary Generator                │               │
│              │  (plain-language synthesis of query results)   │               │
│              └───────────────────────────────────────────────┘               │
│                                                                              │
│              ┌───────────────────────────────────────────────┐               │
│              │           Disagreement Dashboard               │               │
│              │  (web UI for consumers)                        │               │
│              └───────────────────────────────────────────────┘               │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘
```

---

## Component 1: Ingestion Pipeline

### 1.1 Data Sources

| Source | API/Method | Volume Estimate | Source Class |
|--------|------------|-----------------|--------------|
| **PubMed/biorxiv** | NCBI E-utilities API | ~5,000 papers for GLP-1 | Tier 1-2 |
| **ClinicalTrials.gov** | ClinicalTrials.gov API | ~800 registered trials | Tier 1-2 |
| **FDA Labels** | DailyMed API / EDGAR scraping | ~50 documents | Tier 0 |
| **FAERS** | openFDA API | ~50,000 reports for semaglutide | Tier 3 |
| **Reddit** | Reddit API (r/Ozempic, r/loseit) | ~500,000 posts/comments | Tier 5 |
| **Patient Forums** | Web scraping | ~100,000 posts | Tier 5 |
| **News** | News API | ~10,000 articles | Tier 6 |
| **Social Media** | Platform APIs / Firehose | ~1,000,000+ mentions | Tier 6 |

### 1.2 Crawler Implementation

Each source needs a dedicated crawler. Here's the pattern:

```go
// crawler/pubmed/crawler.go

type PubMedCrawler struct {
    apiKey    string
    baseURL   string
    rateLimit time.Duration
}

func (c *PubMedCrawler) FetchArticles(query string, since time.Time) ([]RawArticle, error) {
    // Call NCBI E-utilities API
    // Respect rate limits (3 req/sec with API key)
    // Return raw article data
}

type RawArticle struct {
    PMID        string
    Title       string
    Abstract    string
    Authors     []string
    Journal     string
    DOI         string
    PubDate     time.Time
    MeSHTerms   []string
    StudyDesign string // RCT, observational, meta-analysis, etc.
}
```

### 1.3 NLP Extraction Pipeline

Transforms raw documents into structured claims.

```go
// extraction/pipeline.go

type ClaimExtractor struct {
    llm         LLMClient   // Claude, GPT-4, or local model
    promptStore PromptStore // Domain-specific extraction prompts
}

type ExtractedClaim struct {
    Subject    string  // e.g., "semaglutide/adverse-effects/gastroparesis"
    Predicate  string  // e.g., "risk_level"
    Object     string  // e.g., "No statistically significant increase"
    Confidence float32 // Extraction confidence, not source authority
    Evidence   string  // Quote from source supporting the claim
}

func (e *ClaimExtractor) Extract(doc RawDocument) ([]ExtractedClaim, error) {
    // Use LLM to identify claims in the document
    // Map claims to subject/predicate ontology
    // Assign extraction confidence based on clarity
    // Return structured claims
}
```

**Prompt Engineering:** The extraction prompt is critical. It must:
- Define the claim ontology (what subjects/predicates are valid)
- Distinguish claims from context
- Extract supporting evidence for provenance
- Rate extraction confidence (not source authority)

**Example prompt structure:**
```
You are extracting health claims from a medical document.

Ontology:
- Subjects: {drug}/adverse-effects/{condition}, {drug}/efficacy/{outcome}
- Predicates: risk_level, incidence_rate, relative_risk, clinical_significance

For each claim found, return:
- subject: The specific topic (e.g., "semaglutide/adverse-effects/gastroparesis")
- predicate: What aspect is being claimed
- object: The claim value (use exact quotes where possible)
- confidence: Your confidence in the extraction accuracy (0.0-1.0)
- evidence: The exact text supporting this claim

Document:
{document_text}
```

### 1.4 Source-Class Classifier

Assigns tier based on source metadata.

```go
// classification/source_class.go

type SourceClassifier struct {
    rules []ClassificationRule
}

type ClassificationRule struct {
    Name      string
    Predicate func(SourceMetadata) bool
    Tier      uint8
}

func (c *SourceClassifier) Classify(meta SourceMetadata) uint8 {
    for _, rule := range c.rules {
        if rule.Predicate(meta) {
            return rule.Tier
        }
    }
    return 6 // Default to lowest tier
}

// Default pharma rules
var PharmaRules = []ClassificationRule{
    {
        Name: "FDA Label",
        Predicate: func(m SourceMetadata) bool {
            return m.Source == "dailymed" || m.Source == "fda.gov"
        },
        Tier: 0,
    },
    {
        Name: "Peer-Reviewed RCT",
        Predicate: func(m SourceMetadata) bool {
            return m.DOI != "" && m.StudyDesign == "RCT" && m.PeerReviewed
        },
        Tier: 1,
    },
    {
        Name: "Meta-Analysis",
        Predicate: func(m SourceMetadata) bool {
            return m.StudyDesign == "meta-analysis" && m.PeerReviewed
        },
        Tier: 1,
    },
    {
        Name: "Observational Study",
        Predicate: func(m SourceMetadata) bool {
            return m.DOI != "" && (m.StudyDesign == "cohort" || m.StudyDesign == "case-control")
        },
        Tier: 2,
    },
    {
        Name: "FAERS Report",
        Predicate: func(m SourceMetadata) bool {
            return m.Source == "openfda" || m.Source == "faers"
        },
        Tier: 3,
    },
    {
        Name: "Case Report",
        Predicate: func(m SourceMetadata) bool {
            return m.StudyDesign == "case-report"
        },
        Tier: 4,
    },
    {
        Name: "Reddit",
        Predicate: func(m SourceMetadata) bool {
            return strings.Contains(m.URL, "reddit.com")
        },
        Tier: 5,
    },
    {
        Name: "Patient Forum",
        Predicate: func(m SourceMetadata) bool {
            return m.Source == "patient-forum"
        },
        Tier: 5,
    },
    {
        Name: "News",
        Predicate: func(m SourceMetadata) bool {
            return m.Source == "news-api"
        },
        Tier: 6,
    },
    {
        Name: "Social Media",
        Predicate: func(m SourceMetadata) bool {
            return m.Source == "tiktok" || m.Source == "instagram" || m.Source == "twitter"
        },
        Tier: 6,
    },
}
```

### 1.5 Metadata Enrichment

Adds structured metadata before submission.

```go
// enrichment/service.go

type EnrichmentService struct {
    crossref CrossRefClient
    pubmed   PubMedClient
    reddit   RedditClient
}

type EnrichedMetadata struct {
    // Academic sources
    Journal     string `json:"journal,omitempty"`
    DOI         string `json:"doi,omitempty"`
    SampleSize  int    `json:"sample_size,omitempty"`
    StudyDesign string `json:"study_design,omitempty"`

    // Social sources
    Platform    string `json:"platform,omitempty"`
    Subreddit   string `json:"subreddit,omitempty"`
    Upvotes     int    `json:"upvotes,omitempty"`
    Replies     int    `json:"replies,omitempty"`

    // Sentiment (computed during extraction)
    Sentiment   string  `json:"sentiment,omitempty"`
    SentimentPolarity float32 `json:"sentiment_polarity,omitempty"`
}

func (e *EnrichmentService) Enrich(source SourceMetadata) (*EnrichedMetadata, error) {
    meta := &EnrichedMetadata{}

    if source.DOI != "" {
        // CrossRef lookup for journal, citations
        crossrefData, _ := e.crossref.GetByDOI(source.DOI)
        meta.Journal = crossrefData.ContainerTitle
    }

    if strings.Contains(source.URL, "reddit.com") {
        // Reddit API for engagement metrics
        redditData, _ := e.reddit.GetPost(source.URL)
        meta.Upvotes = redditData.Score
        meta.Replies = redditData.NumComments
        meta.Subreddit = redditData.Subreddit
    }

    return meta, nil
}
```

### 1.6 Assembly & Submission

Combines all components into a signed assertion.

```go
// ingestion/submit.go

type AssertionSubmitter struct {
    episteme    EpistemeClient
    wallet      AgentWallet
    classifier  SourceClassifier
    enricher    EnrichmentService
}

func (s *AssertionSubmitter) Submit(claim ExtractedClaim, source SourceMetadata) error {
    // 1. Classify source tier
    tier := s.classifier.Classify(source)

    // 2. Enrich metadata
    meta, _ := s.enricher.Enrich(source)
    metaJSON, _ := json.Marshal(meta)

    // 3. Compute source hash
    sourceHash := blake3.Sum256([]byte(source.URL + source.Content))

    // 4. Build assertion
    assertion := &CreateAssertionRequest{
        Subject:        claim.Subject,
        Predicate:      claim.Predicate,
        Object:         ObjectText(claim.Object),
        Confidence:     claim.Confidence,
        SourceClass:    &tier,
        SourceHash:     hex.EncodeToString(sourceHash[:]),
        SourceMetadata: string(metaJSON),
        Lifecycle:      "Proposed",
    }

    // 5. Sign with agent key
    signature, _ := s.wallet.Sign(assertion)
    assertion.Signatures = []SignatureDTO{signature}

    // 6. Submit to Episteme
    return s.episteme.Assert(assertion)
}
```

---

## Component 2: Background Gardener

Monitors the knowledge graph for emerging signals.

### 2.1 Cluster Detection

```go
// gardener/cluster_detector.go

type ClusterDetector struct {
    episteme   EpistemeClient
    thresholds ClusterThresholds
}

type ClusterThresholds struct {
    MinAssertions   int           // Minimum assertions to be considered a cluster
    TimeWindow      time.Duration // Window for growth rate calculation
    GrowthRateHigh  float64       // Assertions per month considered "high"
    TierGapTrigger  bool          // Trigger if Tier 5+ exists but Tier 1-2 doesn't
}

type DetectedCluster struct {
    Subject        string
    Predicate      string
    Tier5Count     int
    Tier1Count     int
    GrowthRate     float64 // Assertions per month
    HasClinicalGap bool    // Tier 5+ exists, no Tier 1-2
    EarliestReport time.Time
}

func (d *ClusterDetector) Scan() ([]DetectedCluster, error) {
    // Query Episteme for all subject+predicate pairs
    // For each pair, count assertions by tier
    // Calculate growth rate over time window
    // Flag clusters that meet thresholds
}
```

### 2.2 Escalation Assertion Generation

```go
// gardener/escalation.go

func (g *Gardener) GenerateEscalation(cluster DetectedCluster) *CreateAssertionRequest {
    meta := map[string]interface{}{
        "trigger":          "cluster_threshold",
        "tier_5_count":     cluster.Tier5Count,
        "tier_1_count":     cluster.Tier1Count,
        "growth_rate":      fmt.Sprintf("%.0f/month", cluster.GrowthRate),
        "clinical_gap":     cluster.HasClinicalGap,
        "earliest_report":  cluster.EarliestReport.Format(time.RFC3339),
    }
    metaJSON, _ := json.Marshal(meta)

    return &CreateAssertionRequest{
        Subject:        cluster.Subject,
        Predicate:      "escalation_signal",
        Object:         ObjectText("Anecdotal cluster detected"),
        Confidence:     0.6, // Moderate confidence in the signal
        SourceClass:    ptr(uint8(255)), // Meta-tier for system-generated
        SourceMetadata: string(metaJSON),
        Lifecycle:      "UnderReview",
    }
}
```

### 2.3 Scheduled Tasks

```go
// gardener/scheduler.go

func (g *Gardener) RunSchedule(ctx context.Context) {
    // Every hour: Scan for new clusters
    go g.runEvery(ctx, 1*time.Hour, g.ScanClusters)

    // Every day: Decay trust ranks
    go g.runEvery(ctx, 24*time.Hour, g.DecayTrustRanks)

    // Every week: Generate summary reports
    go g.runEvery(ctx, 7*24*time.Hour, g.GenerateReports)
}

func (g *Gardener) DecayTrustRanks(ctx context.Context) error {
    return g.episteme.Post("/v1/admin/decay-trust-ranks", nil)
}
```

---

## Component 3: Disagreement Dashboard

### 3.1 Query Patterns

The dashboard needs these Episteme queries:

```go
// dashboard/queries.go

// Get layered consensus for a topic
func (d *Dashboard) GetLayeredConsensus(subject string) (*LayeredResponse, error) {
    return d.episteme.Query(&QueryParams{
        Subject: subject,
        Lens:    "LayeredConsensus",
    })
}

// Get conflict map using Skeptic lens
func (d *Dashboard) GetConflictMap(subject string) (*SkepticResponse, error) {
    return d.episteme.Query(&QueryParams{
        Subject: subject,
        Lens:    "Skeptic",
    })
}

// Get historical state at a point in time
func (d *Dashboard) GetHistoricalState(subject string, asOf time.Time) (*QueryResponse, error) {
    return d.episteme.Query(&QueryParams{
        Subject: subject,
        Lens:    "LayeredConsensus",
        AsOf:    asOf.Unix(),
    })
}

// Get changes since last visit
func (d *Dashboard) GetChangesSince(subject string, since time.Time) (*QueryResponse, error) {
    return d.episteme.Query(&QueryParams{
        Subject: subject,
        Lens:    "LayeredConsensus",
        Since:   since.Unix(),
    })
}
```

### 3.2 Response Transformation

```go
// dashboard/transform.go

type ConsumerView struct {
    Topic           string              `json:"topic"`
    Summary         string              `json:"summary"` // LLM-generated
    ConflictScore   float32             `json:"conflict_score"`
    TierPositions   []TierPosition      `json:"tier_positions"`
    ResolvedTopics  []ResolvedTopic     `json:"resolved"`
    ActiveDisputes  []ActiveDispute     `json:"active_disputes"`
    EmergingSignals []EmergingSignal    `json:"emerging_signals"`
    LastUpdated     time.Time           `json:"last_updated"`
}

type TierPosition struct {
    TierName      string  `json:"tier_name"` // "Clinical Evidence", "Patient Community"
    TierNumber    uint8   `json:"tier_number"`
    Position      string  `json:"position"`
    Confidence    float32 `json:"confidence"`
    AssertionCount int    `json:"assertion_count"`
}

func TransformForConsumer(layered *LayeredResponse, skeptic *SkepticResponse) *ConsumerView {
    view := &ConsumerView{
        ConflictScore: layered.OverallConflictScore,
    }

    for _, tier := range layered.Tiers {
        view.TierPositions = append(view.TierPositions, TierPosition{
            TierName:       tierToName(tier.Tier),
            TierNumber:     tier.Tier,
            Position:       summarizePosition(tier.Winner),
            Confidence:     tier.Winner.Confidence,
            AssertionCount: tier.CandidatesCount,
        })
    }

    // Categorize by conflict level
    for _, topic := range skeptic.Topics {
        if topic.ConflictScore < 0.2 {
            view.ResolvedTopics = append(view.ResolvedTopics, ...)
        } else if topic.ConflictScore < 0.7 {
            view.ActiveDisputes = append(view.ActiveDisputes, ...)
        } else {
            view.EmergingSignals = append(view.EmergingSignals, ...)
        }
    }

    return view
}

func tierToName(tier uint8) string {
    names := map[uint8]string{
        0: "Regulatory",
        1: "Clinical Evidence",
        2: "Real-World Evidence",
        3: "Pharmacovigilance",
        4: "Clinical Anecdote",
        5: "Patient Community",
        6: "Media",
    }
    return names[tier]
}
```

### 3.3 LLM Summary Generation

```go
// dashboard/summary.go

type SummaryGenerator struct {
    llm LLMClient
}

func (g *SummaryGenerator) GenerateSummary(view *ConsumerView) (string, error) {
    prompt := fmt.Sprintf(`
Summarize the following health topic for a consumer in 2-3 sentences.
Be factual. Acknowledge uncertainty where it exists. Do not give medical advice.

Topic: %s
Conflict Score: %.2f (0 = agreement, 1 = max disagreement)

Tier Positions:
%s

Output a concise, balanced summary.
`, view.Topic, view.ConflictScore, formatTierPositions(view.TierPositions))

    return g.llm.Complete(prompt)
}
```

**Example output:**
> "Clinical trials show low incidence of gastroparesis with semaglutide; post-marketing reports and patient communities report higher rates. The FDA added gastroparesis to the label in January 2024. There is moderate disagreement between clinical evidence and real-world reports."

---

## Component 4: Agent Wallet

Manages signing keys for the ingestion pipeline.

### 4.1 Key Storage

```go
// wallet/wallet.go

type AgentWallet struct {
    keyPath    string
    privateKey ed25519.PrivateKey
    publicKey  ed25519.PublicKey
}

func NewAgentWallet(keyPath string) (*AgentWallet, error) {
    // Load or generate Ed25519 keypair
    // Store private key securely (file permissions, encryption at rest)
}

func (w *AgentWallet) Sign(assertion *CreateAssertionRequest) (*SignatureDTO, error) {
    // Serialize assertion for signing
    message := fmt.Sprintf("%s:%s", assertion.Subject, assertion.Predicate)
    sig := ed25519.Sign(w.privateKey, []byte(message))

    return &SignatureDTO{
        AgentID:   hex.EncodeToString(w.publicKey),
        Signature: hex.EncodeToString(sig),
        Timestamp: time.Now().Unix(),
    }, nil
}
```

### 4.2 Multi-Agent Setup

For different source types, use different agent identities:

```go
// wallet/multi.go

type MultiAgentWallet struct {
    agents map[string]*AgentWallet
}

func (m *MultiAgentWallet) SignAs(agentName string, assertion *CreateAssertionRequest) (*SignatureDTO, error) {
    agent, ok := m.agents[agentName]
    if !ok {
        return nil, fmt.Errorf("unknown agent: %s", agentName)
    }
    return agent.Sign(assertion)
}

// Usage:
// - "pubmed-crawler" agent for academic sources
// - "reddit-crawler" agent for social sources
// - "gardener" agent for escalation assertions
```

---

## Deployment Architecture

```
┌─────────────────────────────────────────────────────────────────┐
│                        Kubernetes Cluster                        │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐              │
│  │   PubMed    │  │   Reddit    │  │   FAERS     │   Crawlers   │
│  │   Crawler   │  │   Crawler   │  │   Crawler   │   (CronJob)  │
│  │   Pod       │  │   Pod       │  │   Pod       │              │
│  └──────┬──────┘  └──────┬──────┘  └──────┬──────┘              │
│         │                │                │                      │
│         └────────────────┼────────────────┘                      │
│                          │                                       │
│                          ▼                                       │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │                    Extraction Service                      │  │
│  │                    (Deployment, 3 replicas)                │  │
│  └───────────────────────────────────────────────────────────┘  │
│                          │                                       │
│                          ▼                                       │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │                    Episteme API                            │  │
│  │                    (Deployment, 3 replicas)                │  │
│  └───────────────────────────────────────────────────────────┘  │
│                          │                                       │
│                          ▼                                       │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │                    Dashboard API                           │  │
│  │                    (Deployment, 2 replicas)                │  │
│  └───────────────────────────────────────────────────────────┘  │
│                          │                                       │
│                          ▼                                       │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │                    Dashboard UI                            │  │
│  │                    (Static, CDN)                           │  │
│  └───────────────────────────────────────────────────────────┘  │
│                                                                  │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │                    Background Gardener                     │  │
│  │                    (CronJob, hourly)                       │  │
│  └───────────────────────────────────────────────────────────┘  │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘
```

---

## Checklist: What You Need to Build

### Crawlers (one per source)
- [ ] PubMed/biorxiv crawler (NCBI E-utilities)
- [ ] ClinicalTrials.gov crawler
- [ ] FDA DailyMed scraper
- [ ] openFDA FAERS consumer
- [ ] Reddit API consumer
- [ ] Patient forum scrapers
- [ ] News API consumer
- [ ] Social media sampler

### Extraction Pipeline
- [ ] LLM-based claim extractor
- [ ] Subject/predicate ontology for pharma domain
- [ ] Extraction prompt library
- [ ] Confidence calibration

### Classification & Enrichment
- [ ] Source-class classifier with pharma rules
- [ ] CrossRef integration for DOI lookup
- [ ] PubMed integration for study metadata
- [ ] Reddit API for engagement metrics
- [ ] Sentiment analysis model

### Infrastructure
- [ ] Agent wallet with secure key storage
- [ ] Multi-agent identity management
- [ ] Rate limiting for external APIs
- [ ] Retry logic and error handling

### Gardener
- [ ] Cluster detection algorithm
- [ ] Escalation assertion generator
- [ ] TrustRank decay scheduler
- [ ] Alert/notification system

### Dashboard
- [ ] Query orchestration layer
- [ ] Response transformation
- [ ] LLM summary generator
- [ ] React/Vue frontend
- [ ] Time-travel UI
- [ ] Change notification system

---

## See Also

- [Use Case: Consumer Health Intelligence](../../use-cases/consumer-health-intelligence.md) — Full scenario walkthrough
- [App Concepts Index](./index.md) — General application layer patterns
- [Roadmap: Phase 3](../../roadmap.md) — Database features needed
- [ADK-Go Integration](../../.claude/guides/integrations/adk-go-episteme.md) — Agent framework integration
