Skip to content

Architecture

System Design

graph TB
    subgraph Input["User Input"]
        SEEDS["Seed genes<br/>(or disease category)"]
    end

    subgraph Expand["Candidate Expansion"]
        PATHWAY_EXP["Reactome pathway<br/>co-membership"]
    end

    subgraph Signals["Demand Signal Collection (async, parallel)"]
        NIH["NIH RePORTER<br/>api.reporter.nih.gov"]
        OA["OpenAlex<br/>api.openalex.org"]
        CT["ClinicalTrials.gov<br/>clinicaltrials.gov/api/v2"]
        GWAS["GWAS Catalog<br/>ebi.ac.uk/gwas/rest/api"]
    end

    subgraph Feasibility["Feasibility Scoring"]
        BLOOD["HPA Blood Secretome<br/>(pass/fail filter)"]
        XR["InterPro families<br/>(pairwise risk)"]
        CONC["Concentration range<br/>(internal LOD data)"]
    end

    subgraph Evidence["Causal Evidence"]
        PQTL["Open Targets Genetics<br/>pQTL colocalisation (H4)"]
        GRAPHRAG["GraphRAG API<br/>Neptune KG queries"]
    end

    subgraph Selection["Optimization"]
        NORM["Min-max normalize<br/>all dimensions to [0,1]"]
        WEIGHT["Weighted composite<br/>Σ(wᵢ × sᵢ)"]
        GREEDY["Greedy selector<br/>+ diversity constraints"]
        PARETO["Pareto front<br/>(multi-objective)"]
    end

    subgraph Output["Output"]
        PANEL["PanelCandidate<br/>{proteins, scores}"]
        BACKTEST["Publication backtest"]
        COMPARE["Competitor comparison"]
    end

    SEEDS --> PATHWAY_EXP
    PATHWAY_EXP --> Signals
    Signals --> NORM
    Feasibility --> NORM
    Evidence --> NORM
    NORM --> WEIGHT
    WEIGHT --> GREEDY
    WEIGHT --> PARETO
    GREEDY --> PANEL
    PANEL --> BACKTEST
    PANEL --> COMPARE

Data Pipeline

sequenceDiagram
    participant U as User
    participant P as Pipeline
    participant R as Reactome
    participant API as External APIs
    participant C as Cache

    U->>P: score(genes)
    P->>C: Check cache
    alt Cache hit
        C-->>P: Cached signals
    else Cache miss
        P->>API: NIH + OpenAlex + CT.gov + GWAS (parallel)
        API-->>P: Raw counts
        P->>C: Store (TTL 24h)
    end
    P->>P: Normalize [0,1]
    P->>P: Compute composite
    P-->>U: Ranked DataFrame

Module Dependency Graph

graph LR
    CLI["cli.py"] --> PIPE["pipeline.py"]
    TUI["tui.py"] --> PIPE
    PIPE --> SIG["signals/"]
    PIPE --> FEAS["feasibility/"]
    PIPE --> SEL["selection/"]
    PIPE --> EXP["expand.py"]
    PIPE --> EV["evidence.py"]
    EV --> PQTL["evidence_pqtl.py"]
    SIG --> CACHE["cache.py"]
    PQTL --> CACHE
    SEL --> DATA["data/"]
    FEAS --> DATA
    EXP --> DATA
    DATA --> BIOINGEST[("bioingest/<br/>bulk data")]

Scoring Dimensions

Dimension Range Sources Default weight Interpretation
demand [0,1] NIH grants, pub velocity, clinical trials, GWAS 1.0 Who wants this measured?
evidence [0,1] pQTL H4 > 0.8 count, KG disease associations 1.0 Is this causally important?
feasibility [0,1] Blood secretome, LOD, cross-reactivity 1.0 Can we technically build it?
uniqueness [0,1] 1/(1 + n_competitors_measuring) 1.0 Does it differentiate us?

Normalization

All raw scores are min-max normalized within the candidate set before weighting:

normalized_i = (raw_i - min(all)) / (max(all) - min(all))

This ensures: - Weights actually control trade-offs (not dominated by high-magnitude signals) - Scores are interpretable: 1.0 = best in set, 0.0 = worst in set - Equal-value candidates get 0.5 (midpoint)

Composite Aggregation

composite = w_demand × norm_demand
          + w_evidence × norm_evidence  
          + w_feasibility × norm_feasibility
          + w_uniqueness × norm_uniqueness

Default weights are all 1.0. Override with --weights demand=3,uniqueness=2 to prioritize.

Caching Strategy

Source Cache key TTL Rationale
NIH RePORTER nih_reporter:{gene} 24h Grants update infrequently
OpenAlex pub_velocity:{gene} 24h New papers daily but trends stable
GWAS Catalog gwas:{gene} 7d Updated quarterly
pQTL evidence pqtl:{gene} 7d Genetic data rarely changes
Ensembl IDs ensembl_id:{gene} 30d Static mapping

Optimizer Algorithm

Greedy selection (default): 1. Sort candidates by composite_score descending 2. For each candidate in order: - Skip if feasibility_score < 0.5 (not blood-detectable) - Skip if in exclude_proteins set - Skip if max_per_pathway constraint violated - Add to panel 3. Stop at panel_size

Pareto optimization (--pareto flag): 1. Generate N candidate panels, each optimized for a single objective 2. Filter to Pareto front: panels where no other panel dominates on ALL objectives 3. Return all non-dominated panels for human decision-making

Expansion Algorithm

graph TD
    S[10 seed genes] --> P[Find all Reactome pathways<br/>containing any seed]
    P --> M[Collect all OTHER members<br/>of those pathways]
    M --> R[Rank by shared pathway count<br/>(more overlap = more relevant)]
    R --> T[Take top N to reach target size]
    T --> C[50 expanded candidates]

A protein appearing in 5 pathways shared with seeds is more biologically relevant than one appearing in only 1 shared pathway.