Skip to content

BAMorgan/EctoCompass

Repository files navigation

EctoCompass

EctoCompass is an educational medical question-answering app for ectodermal dysplasia, especially hypohidrotic ectodermal dysplasia. It answers from a curated corpus, preserves source metadata, shows citation support, and avoids becoming a medical-record or diagnosis system.

The app is built around a safety-first retrieval pipeline:

  • classify the request and detect urgent-risk or PHI-like input
  • retrieve evidence from approved local, Postgres, and freshness sources
  • assemble evidence spans with source tier, source id, document id, dates, and URLs
  • draft short answers with sentence-level citations
  • verify citation support before returning the answer
  • fall back instead of returning unsupported medical claims

EctoCompass is for education only. It does not diagnose, provide emergency care, or give individualized treatment instructions. Do not enter names, contact details, medical record numbers, uploads, or personal medical histories.

Source Tiers

Answers preserve source-quality labels:

  • Tier A - Registry or official medical source
  • Tier B - Literature or permitted medical reference
  • Tier C - Practical support guidance

Biomedical claims should be grounded in Tier A or Tier B sources. Tier C material is for practical support context and is labeled as practical support guidance.

Repository Layout

app/              FastAPI app, retrieval, answering, safety, freshness, worker code
manifests/        Curated source manifest and source metadata schema inputs
migrations/       Postgres schema migrations
schemas/          JSON schemas
scripts/          Local ingestion, database, eval, embedding, and health commands
sources/          Local PDF source files, subject to license/reuse review
storage/          Local generated artifacts, ignored by git
tests/            Unit and integration-style regression tests

Quick Start: Local Python

This path uses the deterministic seed/local retrieval fallback and does not require Postgres.

python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
python -m pip install -r requirements.txt

$env:SOURCE_MANIFEST_PATH="manifests/source_manifest.json"
$env:RETRIEVAL_BACKEND="seed"
python -m uvicorn app.api:app --reload --host 127.0.0.1 --port 8000

Open http://127.0.0.1:8000.

Check the API:

Invoke-RestMethod `
  -Method Post `
  -Uri "http://127.0.0.1:8000/chat/preview" `
  -ContentType "application/json" `
  -Body '{"question":"What is hypohidrotic ectodermal dysplasia?"}'

Docker Compose

Docker Compose starts the API, worker, Postgres with pgvector, and Redis.

Copy-Item .env.example .env
docker compose up --build

The default .env.example sets RETRIEVAL_BACKEND=postgres. For a clean local database, initialize it before relying on Postgres retrieval:

docker compose up -d postgres redis

$env:DATABASE_URL="postgresql://ectocompass:ectocompass@localhost:5432/ectocompass"
python -m scripts.apply_migrations
python -m scripts.ingest_source_documents --include-reviewed-full-text
python -m scripts.load_postgres_corpus

Only use --include-reviewed-full-text after verifying license and reuse terms for every PDF under sources/.

After loading the corpus:

docker compose up --build

Open http://127.0.0.1:8000.

Local LLM Answer Synthesis

EctoCompass defaults to the deterministic extractive composer:

$env:ANSWER_COMPOSER_MODE="extractive"

In this mode, no answer-generation LLM is called. The app selects supported sentences from retrieved evidence, applies source-tier rules, and verifies citations locally.

You can enable a local LLM through Ollama for more natural synthesis while keeping the same evidence and verifier boundaries. The local LLM is optional and never replaces retrieval or citation verification.

How It Works

When ANSWER_COMPOSER_MODE=llm, app.api builds an LLMAnswerComposer and passes it into QuestionService.

For each question:

  1. QuestionService runs the PHI guard and request classifier.
  2. Retrieval returns an EvidencePack containing only approved spans.
  3. assemble_evidence clusters and ranks candidate spans.
  4. bounded_composer_input creates the only payload the model may see:
    • the user question
    • the request label
    • allowed span IDs
    • retrieved span text
    • source tier labels
    • content domains
    • publication, update, and retrieval dates
  5. For Ollama, EctoCompass sends a local chat request to OLLAMA_API_URL with:
    • stream: false
    • think: false
    • temperature: 0
    • a JSON schema in format
  6. The model must return structured JSON with:
    • claims
    • supporting_span_ids
    • source_tier_label
    • uncertainty_statement
  7. BoundedComposerOutput.from_dict rejects claims that cite span IDs outside the evidence pack.
  8. verify_answer checks that each returned claim is supported by the cited span text.
  9. If the provider call, JSON parse, span validation, or citation verification fails, EctoCompass falls back to the deterministic extractive composer.

The LLM does not receive the whole corpus, cannot browse, and cannot cite sources that retrieval did not return. It is used only to phrase claims from retrieved evidence spans.

Set Up Ollama

Install Ollama, then pull a local chat model. The project defaults to qwen3:8b for local synthesis.

ollama pull qwen3:8b
ollama serve

Leave Ollama running while EctoCompass is running.

Run Local Python With Ollama

In a new PowerShell session from the repository root:

.\.venv\Scripts\Activate.ps1

$env:SOURCE_MANIFEST_PATH="manifests/source_manifest.json"
$env:RETRIEVAL_BACKEND="seed"
$env:ANSWER_COMPOSER_MODE="llm"
$env:ANSWER_LLM_PROVIDER="ollama"
$env:ANSWER_MODEL="qwen3:8b"
$env:ANSWER_API_TIMEOUT_SECONDS="60"
$env:OLLAMA_API_URL="http://localhost:11434/api/chat"

python -m uvicorn app.api:app --reload --host 127.0.0.1 --port 8000

Confirm the app sees the local LLM configuration:

Invoke-RestMethod "http://127.0.0.1:8000/api/info"

Expected fields:

answer_composer_mode: llm
answer_llm_provider: ollama
answer_model: qwen3:8b

Ask a debug question and inspect answer.metadata:

Invoke-RestMethod `
  -Method Post `
  -Uri "http://127.0.0.1:8000/chat/preview?debug=true" `
  -ContentType "application/json" `
  -Body '{"question":"How does reduced sweating relate to overheating in HED?"}'

When the local model succeeds, metadata includes:

generation_mode: llm_structured_evidence_composer
provider: ollama
model: qwen3:8b
verification.passed: true

If Ollama is stopped or returns invalid output, the request should still complete with deterministic fallback metadata such as:

generation_mode: structured_evidence_composer
llm_fallback_to_extractive: true

Run Docker Compose With Host Ollama

When the API runs inside Docker and Ollama runs on the host machine, use Docker's host gateway URL.

Update .env:

ANSWER_COMPOSER_MODE=llm
ANSWER_LLM_PROVIDER=ollama
ANSWER_MODEL=qwen3:8b
ANSWER_API_TIMEOUT_SECONDS=60.0
OLLAMA_API_URL=http://host.docker.internal:11434/api/chat

Then run:

docker compose up --build

The API container will call the Ollama server running on the host.

Hosted LLM Option

The same bounded composer can call OpenAI instead of Ollama:

$env:ANSWER_COMPOSER_MODE="llm"
$env:ANSWER_LLM_PROVIDER="openai"
$env:ANSWER_MODEL="gpt-5.4-nano"
$env:OPENAI_API_KEY="..."

Hosted LLM output goes through the same span validation, citation verification, and fallback behavior.

Freshness Mode

Research and trial questions can use bounded live metadata checks when FRESHNESS_ENABLED=true.

Supported freshness sources are:

  • ClinicalTrials.gov
  • NCBI/PubMed metadata

Freshness calls are metadata-oriented. The app does not browse arbitrary websites and should not ingest full text unless reuse rights are verified.

Useful Commands

Run tests:

python -m pytest

Run focused answer and configuration tests:

python -m pytest tests/test_answering.py tests/test_embeddings.py

Run local evals with seed retrieval:

python -m scripts.run_local_evals --backend seed

Run local evals with Postgres retrieval:

$env:DATABASE_URL="postgresql://ectocompass:ectocompass@localhost:5432/ectocompass"
python -m scripts.run_local_evals --backend postgres

Check run health:

python -m scripts.run_health

Check database-backed health:

$env:DATABASE_URL="postgresql://ectocompass:ectocompass@localhost:5432/ectocompass"
python -m scripts.run_health --with-db

Privacy And Safety Defaults

The MVP defaults are intentionally conservative:

  • STORE_USER_HEALTH_TEXT=false
  • LOG_RAW_USER_TEXT=false
  • PER_USER_TELEMETRY_ENABLED=false
  • document uploads are not supported
  • symptom diaries and saved patient histories are not supported
  • urgent symptoms get safety footer language

For severe or urgent symptoms, users should contact a clinician or local emergency services. EctoCompass is an educational tool, not clinical care.

About

EctoCompass is an educational medical question-answering app for ectodermal dysplasia, especially hypohidrotic ectodermal dysplasia

Topics

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors