graph LR
subgraph BUILD["COMPILE KG WORKFLOW (offline, Databricks)"]
direction LR
P[paper] --> EX[Semantic Parser\nLLM] --> F[findingsIR] --> R1[Resolve\nper entity type] --> CY1[compile to\nCypher query]
end
subgraph QUERY["COMPILE QUERY WORKFLOW (online, website)"]
direction LR
Q[question] --> DE[Semantic Parser\nLLM] --> M[queryIR\nlogical form for eval] --> R2[Resolve\nper entity type] --> CY2[compile to\nCypher query]
end
CY2 -->|"execute cypher query"| NEO["KG Instance\non Neo4j"]
CY1 -->|"execute cypher merge"| NEO
NEO --> SG["subgraph\nfor eval"] --> SYN["LLM synopsis\nanswer for eval"]
style M fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style SG fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
style SYN fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px
graph LR
U["User\nquestion"] --> W["Website\nFastAPI"]
W --> LLM1["LLM Service\ndecompose\n(PICO extraction)"]
LLM1 --> CR["Concept Resolver\nregex → UMLS → LLM → embed\n(4-step, cached)"]
CR --> NEO["Neo4j\nCypher query\n(broad recall)"]
NEO --> LLM2["LLM Service\npertinence filter\n(concurrent fan-out)"]
LLM2 --> LLM3["LLM Service\nsynopsis\n(structured citations)"]
LLM3 --> U2["User\nevidence + synopsis"]
style CR fill:#e3f2fd,stroke:#1976d2
style NEO fill:#e8f5e9,stroke:#388e3c
style LLM1 fill:#fff3e0,stroke:#f57c00
style LLM2 fill:#fff3e0,stroke:#f57c00
style LLM3 fill:#fff3e0,stroke:#f57c00
| Step | Time | What |
|---|---|---|
| LLM decompose | ~1.0s | gpt-4.1 structured output — extract PICO mentions from question |
| Concept resolver | ~0.2s | UMLS lookup per mention (2 HTTP calls for typical query) |
| Graph expansion | ~0.2s | Neo4j query: what conditions are linked to these interventions? |
| Neo4j Cypher | ~0.1s | Broad recall: LIMIT 100 with CONTAINS matching |
| Pertinence filter | ~0.5s | gpt-4.1-mini per finding, all concurrent via asyncio.gather |
| Total | ~2s | End-to-end, warm cache. Test: /smoke/query?q=exercise+for+depression |
Every LLM call in the entire system goes through the LLM Service (cached gateway). Zero direct Azure OpenAI calls. One gateway, one cache, one place to observe.
| Component | LLM calls via | Status |
|---|---|---|
| Website (decompose, filter, synopsis) | ✓ LLM Service | Live |
| Extraction | ✓ LLM Service | Code ready |
| Canonicalization | ✓ LLM Service | Code ready |
| Pertinence filter | ✓ LLM Service | Code ready |
| Concept resolver (Step 3) | ✓ LLM Service | Code ready |
graph LR
subgraph CI["GITHUB ACTIONS"]
direction LR
PUSH["push to prod"] --> BUILD["Build\nDocker image\n→ ACR"]
BUILD --> STAGING["Deploy to\nSTAGING"]
STAGING --> TEST{"E2E Smoke Test\nALL blue pills\nvia /smoke/query"}
TEST -->|"all pass"| PROD["Deploy to\nPROD"]
TEST -->|"any fail"| BLOCK["BLOCKED\nprod not updated"]
end
subgraph APPS["CONTAINER APPS"]
S["nobsmed-staging\n0-1 replicas"]
P["nobsmed.com\n1-10 replicas"]
MCPAPP["nobsmcpserver\n1 replica (always on)\n/mcp + /health"]
end
STAGING --> S
PROD --> P
subgraph MCP_CI["MCP SERVER CI/CD"]
direction LR
MPUSH["push to prod\napps/mcp-server/**"] --> MBUILD["Build\nDocker image\n→ ACR"]
MBUILD --> MDEPLOY["Deploy to\nnobsmcpserver"]
MDEPLOY --> MHEALTH{"Health check\n+ MCP handshake"}
end
MDEPLOY --> MCPAPP
style BLOCK fill:#f8d7da,stroke:#dc3545
style TEST fill:#fff3cd,stroke:#ffc107
style PROD fill:#d4edda,stroke:#198754
style MCPAPP fill:#e3f2fd,stroke:#1976d2
style MHEALTH fill:#e3f2fd,stroke:#1976d2
Single MCP server serves all AI clients. 2 read-only clinical evidence tools. No Custom GPT Actions — MCP-first architecture.
| Endpoint | URL | Status |
|---|---|---|
| MCP (streamable-http) | nobsmcpserver.../mcp | Live |
| Health check | nobsmcpserver.../health | Live |
| Tool | Purpose | Input | Output |
|---|---|---|---|
| lookup | Resolve user language → canonical concepts | name, entity_type? |
canonical name, type, aliases, score |
| evidence | Retrieve clinical findings from KG | intervention?, condition?, outcome? |
claim, effect_direction, effect_size, CI, study_title, pmc_id |
| AI Client | Protocol | Status |
|---|---|---|
| Claude Desktop | MCP streamable-http | ✓ Ready |
| Claude Code | MCP streamable-http | ✓ Ready |
| ChatGPT App Directory | MCP via Apps SDK | Pending submission |
.github/workflows/deploy-mcp-server.yml
docs/mcp-app-reference.md
graph TD
KG[":KG name"] --> META[":KGMeta\nrole, schema_version"]
META -->|HAS_BUILD| BUILD[":BuildInfo\nstatus, resolver, git_commit"]
META -->|HAS_AUDIT| AUDIT[":AuditReport\nresolved_ratio, smoke_test"]
META -->|HAS_SHOWCASE| PILL[":ShowcaseQuestion\nquestion, score, blue"]
META --> EV[":Evidence (unified label)"]
EV --> FIND[":Finding:Evidence\nclinical study"]
FIND -->|tested| INT[":Intervention"]
FIND -->|for_condition| COND[":Condition"]
FIND -->|on_outcome| OUT[":Outcome"]
FIND -->|reported_in| STUDY[":Study pmc"]
EV --> EXP[":Experience:Evidence\nReddit / anecdotal"]
EXP -->|tested| INT2[":Intervention"]
EXP -->|for_condition| COND2[":Condition"]
The drivers below are payoffs of three decisions: (1) canonical identity (resolver + SNOMED) → dedup, IS-A recall, pooling; (2) ontology-as-code (one schema, projected + constrained) → zero drift + no-hallucination; (3) typed artifacts, not prose → debuggable reasoning, re-runnable lineage, gradeable output.
| Driver / capability | Min rung | Beneficiary |
|---|---|---|
| Deterministic, reproducible queries | SQL | Agent / Business |
| Citation on every finding (no fabrication) | SQL | User / Business |
| Surface facts, not paraphrase (IP posture) | SQL | Business |
| Flywheel CI — optimize the extractor vs gold (no GEPA / manual prompt-search) | SQL | Developer |
| Gradeable audit — score/benchmark the audit output (precision/recall vs gold) = the grader | SQL | Business |
| Zero drift (one grammar → models/prompts/schema) | SQL | Developer |
| Debuggable intermediate artifacts + stratified re-run | SQL | Developer |
| Exact-match dedup | SQL | — |
| Multi-hop / causal-chain reasoning | KG | Agent / User |
| Semantic dedup (synonyms) | SNOMED | — |
| Like-family / IS-A reasoning | SNOMED | Agent |
| Cross-study / cross-patient pooling | SNOMED | Agent / User |
| EHR interoperability | SNOMED | Business |