Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,12 @@ tests/
docs/
*.md
!README.md

.medcheck/
artifacts/
output/
data/
scans/
*.dcm
*.dicom
*.zip
100 changes: 39 additions & 61 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,73 +1,51 @@
# =============================================================================
# MedCheck Configuration
# =============================================================================
# Copy this file to .env and fill in your values:
# cp .env.example .env

# =============================================================================
# LLM API Keys (at least one required for Vision analysis)
# =============================================================================

# Anthropic - Claude Opus 4.8 (recommended, best quality)
# Get your key at: https://console.anthropic.com/settings/keys
# Copy to .env for Docker Compose. For a local shell, export the values explicitly.
ANTHROPIC_API_KEY=

# OpenAI - GPT-5.5 (best image resolution at 10.2MP)
# Get your key at: https://platform.openai.com/api-keys
OPENAI_API_KEY=

# Google - Gemini 3.5 Flash (fastest and cheapest)
# Get your key at: https://aistudio.google.com/apikey
GOOGLE_API_KEY=

# =============================================================================
# Server Configuration
# =============================================================================

# Bind address. Defaults to 127.0.0.1 (localhost only). Set to 0.0.0.0 to expose
# on the network — only do this behind a firewall/reverse proxy AND with
# MEDCHECK_API_KEY set, since this server handles patient PHI.
# Single-user local workbench; set a key before exposing the server to a network.
MEDCHECK_HOST=127.0.0.1
MEDCHECK_PORT=8080

# Optional API key. When set, /api endpoints require a matching X-API-Key header.
# Strongly recommended whenever MEDCHECK_HOST is not 127.0.0.1.
MEDCHECK_API_KEY=

# Only when a trusted reverse proxy fronts the server: set to 1 so the rate
# limiter keys on the first X-Forwarded-For hop instead of the proxy's IP.
# Leave off otherwise — without a proxy the header is client-spoofable.
MEDCHECK_TRUST_PROXY_HEADERS=
MEDCHECK_RATE_LIMIT=10
MEDCHECK_LANGUAGE=en

# =============================================================================
# Default Settings
# =============================================================================
# Server paths are restricted to DATA_ROOT. Browser uploads use opaque IDs.
MEDCHECK_DATA_ROOT=./scans
MEDCHECK_STATE_DIR=./.medcheck
MEDCHECK_MAX_UPLOAD_BYTES=536870912
MEDCHECK_MAX_DICOM_BYTES=536870912
MEDCHECK_MAX_JOBS=20
MEDCHECK_JOB_WORKERS=1
MEDCHECK_MAX_DOWNLOAD_BYTES=2147483648

# Default LLM provider: claude | openai | gemini
# Falls back automatically if the selected provider has no API key configured.
# Note: "local" (offline LLaVA-Med) is not yet implemented — see
# https://github.com/Liohtml/MedCheck/issues/18
# CLI vision default; local statistical analysis needs no model or key.
MEDCHECK_LLM_PROVIDER=claude

# Default report language: en | de
MEDCHECK_LANGUAGE=en

# Consent to sending patient-derived data to external cloud LLM APIs
# (Claude/GPT/Gemini). Off by default — vision analysis refuses to transmit
# until you opt in here, via --allow-cloud-llm, or the interactive prompt.
MEDCHECK_ALLOW_EXTERNAL_LLM=

# =============================================================================
# Data Provider Configuration (optional)
# =============================================================================

# easyRadiology portal - no API key needed.
# Authentication uses the access code provided per exam. A date of birth may be
# requested by the portal but is NOT verified by MedCheck (--dob is optional).
# Example usage:
# medcheck analyze \
# --source "https://portal.easyradiology.net/View/your-exam-hash" \
# --code "A2C-AB3-4BC-1BC"
#
# The access code is provided by your radiology clinic
# (usually via SMS, email, or printed letter).
MEDCHECK_MAX_VISION_IMAGES=12
MEDCHECK_LLM_TIMEOUT=120
MEDCHECK_LLM_RETRIES=2
# Model IDs are configurable; access/availability depends on your provider account.
MEDCHECK_CLAUDE_MODEL=claude-opus-4-8
MEDCHECK_OPENAI_MODEL=gpt-5.5
MEDCHECK_GEMINI_MODEL=gemini-3.5-flash

# Optional preinstalled local vision server. Only loopback IP URLs accepted.
MEDCHECK_LOCAL_URL=
MEDCHECK_LOCAL_MODEL=
# Example: MEDCHECK_LOCAL_URL=http://127.0.0.1:11434/v1
# Set MEDCHECK_LOCAL_MODEL to the exact model ID returned by that server.

# Conservative per-analysis USD estimates supplied by the operator.
# Include selected image budget, tokens and retries. Empty means unknown.
# A user-entered budget blocks unknown/over-budget estimates, not provider billing.
MEDCHECK_CLAUDE_ESTIMATED_COST_USD=
MEDCHECK_OPENAI_ESTIMATED_COST_USD=
MEDCHECK_GEMINI_ESTIMATED_COST_USD=

# Optional ResNet feature extraction for CLI; browser local mode uses statistics.
MEDCHECK_ML_DEVICE=cpu
MEDCHECK_ML_BATCH_SIZE=16
# OCR: install medcheck[privacy] AND the local Tesseract executable.
# Explicit pixel review is still required before cloud transmission.
4 changes: 4 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,7 @@ updates:
directory: "/"
schedule:
interval: weekly
- package-ecosystem: docker
directory: "/"
schedule:
interval: weekly
57 changes: 54 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
steps:
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@v7
- run: uv sync --all-extras --locked
- run: uv sync --extra dev --locked
- run: uv run ruff check src/ tests/
- run: uv run ruff format --check src/ tests/

Expand All @@ -25,15 +25,15 @@ jobs:
steps:
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@v7
- run: uv sync --all-extras --locked
- run: uv sync --extra dev --locked
- run: uv run mypy src/medcheck

security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@v7
- run: uv sync --all-extras --locked
- run: uv sync --extra dev --locked
- run: uv run bandit -r src/medcheck -ll -q

test:
Expand All @@ -55,3 +55,54 @@ jobs:
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: ./coverage.xml

provider-install:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- extra: claude
module: anthropic
- extra: openai
module: openai
- extra: gemini
module: google.genai
- extra: cloud
module: anthropic, openai, google.genai
steps:
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@v7
with:
python-version: "3.10"
- run: uv sync --no-dev --extra ${{ matrix.extra }} --locked
- run: uv run --no-sync python -c "import ${{ matrix.module }}; import medcheck.llm.router"

browser:
runs-on: ubuntu-latest
env:
MEDCHECK_API_KEY: browser-test-only
MEDCHECK_RATE_LIMIT: "0"
MEDCHECK_STATE_DIR: /tmp/medcheck-browser-state
steps:
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@v7
with:
python-version: "3.13"
- run: uv sync --extra dev --locked
- run: uv run --with playwright playwright install --with-deps chromium
- name: Test the real browser workflow with synthetic DICOM
shell: bash
run: |
.venv/bin/medcheck serve --port 8765 > /tmp/medcheck-browser.log 2>&1 &
medcheck_server_pid=$!
trap 'kill "$medcheck_server_pid"' EXIT
for attempt in {1..30}; do
if curl --fail --silent http://127.0.0.1:8765/health; then break; fi
sleep 1
done
uv run --with playwright python tests/browser/web_user_journey.py --api-key browser-test-only
- uses: actions/upload-artifact@v7
if: always()
with:
name: browser-screenshots
path: artifacts/ui/
4 changes: 2 additions & 2 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
security-events: write
steps:
- uses: actions/checkout@v7
- uses: github/codeql-action/init@d1ba80a13dd99fba24a470575428917156a28b43 # v4
- uses: github/codeql-action/init@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
with:
languages: python
- uses: github/codeql-action/analyze@d1ba80a13dd99fba24a470575428917156a28b43 # v4
- uses: github/codeql-action/analyze@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ Thumbs.db

# Report output (may contain patient PHI — never commit)
output/
.medcheck/
artifacts/

# Logs
*.log
Expand Down
2 changes: 2 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ repos:
- id: check-yaml
- id: check-toml
- id: end-of-file-fixer
# Preserve exact vendored bytes: index.html pins the script with SRI.
exclude: "^src/medcheck/web/static/htmx\\.min\\.js$"
- id: trailing-whitespace
- id: no-commit-to-branch
args: [--branch, main]
Expand Down
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
- Working browser upload/study/analysis workflow, bounded jobs, progress, cancellation,
durable reports and slice viewer, authenticated downloads and explicit cleanup.
- Finding image references, review edits/audit trail, local reference-report comparison,
preliminary FHIR R4 and DICOM Basic Text SR exports, regression evaluation CLI.
- Conservative metadata de-identification, explicit pixel-review gate, optional local
OCR/manual masks; known-identifier free-text cleanup without anonymity guarantees.
- DICOMDIR/single-image imports, UID study selection, geometry and quality checks.
- Configured loopback vision server and installable cloud/per-provider SDK extras.
- Browser user-journey tests across desktop/mobile, four UI locales and failure states.

### Fixed
- Explicitly close viewer memory maps before deletion/shutdown, copy viewer slices
under the store lock, and retain failed deletions for retry instead of reporting
success while files remain on Windows.
- Series collisions, slice ordering/decoding, ResNet evaluation mode/batching, signal
metric semantics, unfair image allocation and overwritten processing warnings.
- Missing cloud SDKs; Gemini now uses google-genai. Docker preserves installed extras.
- Report filename collisions and PDF text escaping/wrapping; confidence is explicitly
uncalibrated and reviewed findings retain their original audit context.

### Changed
- Docker dependency automation, leaner CI install checks, CodeQL 4.38.0 pins.
- Browser defaults to statistics without model downloads; cloud requires both consent
and pixel review. Unsupported multiframe/color data is reported instead of guessed.
- Research scope, model limitations, storage retention and costs documented explicitly.

## [0.3.0] - 2026-07-02

### Added
Expand Down
10 changes: 6 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,22 @@ COPY workflows/ workflows/

# === Lite (cloud APIs only, ~500MB) ===
FROM base AS lite
RUN uv sync --no-dev --locked --no-cache \
RUN uv sync --no-dev --extra cloud --locked --no-cache \
&& mkdir -p /app/.medcheck \
&& chown -R medcheck:medcheck /app
EXPOSE 8080
ENV MEDCHECK_HOST=0.0.0.0
ENV MEDCHECK_PORT=8080
USER medcheck
CMD ["uv", "run", "medcheck", "serve"]
CMD ["/app/.venv/bin/medcheck", "serve"]

# === Full (with local ML models, ~10GB) ===
FROM base AS full
RUN uv sync --no-dev --extra local-models --locked --no-cache \
RUN uv sync --no-dev --extra cloud --extra local-models --locked --no-cache \
&& mkdir -p /app/.medcheck \
&& chown -R medcheck:medcheck /app
EXPOSE 8080
ENV MEDCHECK_HOST=0.0.0.0
ENV MEDCHECK_PORT=8080
USER medcheck
CMD ["uv", "run", "medcheck", "serve"]
CMD ["/app/.venv/bin/medcheck", "serve"]
Loading
Loading