Skip to content

Repository files navigation

Medical Tourism Atlas

Source-grounded RAG over a medical-tourism corpus — a retrieval API that answers only from indexed documents and returns the passages it used, so every answer can be traced back to a source.

Live landing page: medtourismatlas.com

Status: retired prototype. Built solo in ~2.5 weeks as pre-contract proof of competence in an unfamiliar domain, before joining a Korean medical-tourism company — a working system in their industry instead of slides. It was built to do that, it did it, and it was retired afterwards.

What it ran on: 264 practitioner interview videos, two industry books and seven academic papers, ingested end to end. 24 Postgres-backed integration tests cover the retrieval API. Hybrid keyword-plus-vector search and parent-document retrieval were deferred as known next steps — written up in ingestion/docs/architecture-review.md rather than quietly dropped — and the auth shortcuts that made the deadline are documented in SECURITY_RISKS.md rather than hidden.

What it does

The system splits the two halves of RAG that usually get tangled together:

  • This repo is the retrieval half. It ingests documents, chunks them semantically, embeds them into Postgres + pgvector, and exposes a search endpoint that returns ranked passages with their source metadata.
  • Generation is delegated to an external chat front-end (OpenWebUI), wired through openwebui_tool.py. The model is forced to call the search tool, gets back passages below a configurable relevance threshold or nothing at all, and runs under a cite-or-refuse system prompt (docs/SYSTEM_PROMPT.md) that tells it to say "I don't know" rather than answer unsupported.

The consequence is that the interesting engineering — chunking, embedding, ranking, provenance — lives in code that can be tested, rather than in a prompt.

Architecture

                 ingestion/                              app/
  PDF ─┐                                        ┌── FastAPI + async SQLAlchemy
       ├─ parse ─ clean ─ semantic chunk ─ embed ┤
YouTube┘   (spaCy, ~300 tok)  (BGE-M3 / OpenAI) └── Postgres 16 + pgvector
                                                        │
                                                        ├── HNSW cosine index
                                                        └── MMR re-ranking (λ = 0.5)
                                                              │
                          OpenWebUI ── openwebui_tool.py ─────┘
                          (forced tool-use, cite-or-refuse)

Data model: Source → Document → Chunk, with per-source reliability tiers and SHA-256 content hashing so re-ingesting the same material is idempotent.

Retrieval: the query is embedded, pgvector returns candidates by cosine distance over an HNSW index, then Maximal Marginal Relevance re-ranks them to trade off relevance against redundancy so several near-identical passages from one document don't crowd out the rest.

API surface: JWT auth for the dashboard, and separate mt_-prefixed API keys for machine clients, both rate-limited. Chunk search accepts a score threshold and returns chunks enriched with document title, source name and reliability tier — that metadata is what the chat client turns into citation pills.

Dashboard: React 19 + Chakra UI for managing sources, documents, chunks and API keys.

Layout

Path What's in it
app/ FastAPI backend — endpoints, services, repositories, SQLAlchemy models
ingestion/ LangChain ingestion pipeline, PDF/YouTube → chunks → pgvector. See ingestion/README.md
frontend/ React 19 admin dashboard (Vite + Chakra UI)
landing_page/ Static marketing site — Jinja2 templates + Tailwind, rendered to index.html
alembic/ Database migrations, including the pgvector HNSW indexes
tests/ Integration tests against a live Postgres
openwebui_tool.py The OpenWebUI tool definition that calls the search API

The corpus is not in this repo and will not be. It was assembled from published books, whitepapers, research papers and recorded talks; redistributing those verbatim is not mine to do. ingestion/input/ and ingestion/output/ are gitignored.

Running it

cp .env.example .env          # fill in POSTGRES_*, SECRET_KEY, FIRST_SUPERUSER*, OPENAI_API_KEY
docker compose up -d          # backend :8000, dashboard :3001, pgvector :5432

scripts/prestart.sh waits for the database, runs alembic upgrade head and seeds the first superuser. API docs are then at http://localhost:8000/docs.

Tests need a running Postgres with the pgvector extension:

pytest

For the ingestion pipeline, see ingestion/README.md — it has its own dependencies (spaCy models, and optionally a local llama.cpp build for figure-heavy PDFs).

License

MIT — see LICENSE.

About

Source-grounded RAG over a medical-tourism corpus — FastAPI + pgvector (HNSW + MMR), cite-or-refuse grounding delegated to an external chat surface, React admin dashboard, LangChain ingestion.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages