Skip to content

Commit d6c47e8

Browse files
committed
Add Docker image, Hugging Face Spaces sync, pre-commit and docs
1 parent 23b6859 commit d6c47e8

8 files changed

Lines changed: 223 additions & 3 deletions

File tree

.dockerignore

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
.git
2+
.venv
3+
__pycache__
4+
*.pyc
5+
.pytest_cache
6+
.ruff_cache
7+
node_modules
8+
frontend/node_modules
9+
frontend/dist
10+
app/static
11+
.env
12+
.idea
13+
.DS_Store
14+
tests

.env.example

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# LLM provider used by `convert` and `humanize`.
2+
# ollama (local), openai, anthropic, mistral, deepseek, gemini, huggingface
3+
# Override the provider's default model.
4+
# ollama: OPENSYNDROME_MODEL=qwen2.5:7b-instruct (model must be `ollama pull`ed)
5+
# huggingface: OPENSYNDROME_MODEL=<inference-provider>/<model>, e.g.
6+
# hf-inference/meta-llama/Llama-3.1-8B-Instruct
7+
OPENSYNDROME_PROVIDER=huggingface
8+
OPENSYNDROME_MODEL=moonshotai/Kimi-K2-Instruct-0905
9+
10+
# Auto-injected on Hugging Face Spaces; set locally to use the huggingface provider.
11+
# HF_TOKEN=
12+
13+
# Running ollama inside Docker? ollama runs on the host, not the container, so point
14+
#OLLAMA_HOST=http://host.docker.internal:11434
15+
16+
# Cloud provider keys (only needed for the matching provider)
17+
# OPENAI_API_KEY=
18+
# ANTHROPIC_API_KEY=
19+
# MISTRAL_API_KEY=
20+
# DEEPSEEK_API_KEY=
21+
# GEMINI_API_KEY=
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
name: Check file size
2+
on:
3+
pull_request:
4+
branches: [main]
5+
workflow_dispatch:
6+
7+
jobs:
8+
check-large-files:
9+
runs-on: ubuntu-latest
10+
steps:
11+
- name: Check large files
12+
uses: ActionsDesk/lfs-warning@v2.0
13+
with:
14+
filesizelimit: 10485760 # 10MB so we can sync to HF Spaces

.github/workflows/huggingface.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
name: Sync to Hugging Face hub
2+
on:
3+
push:
4+
branches: [main]
5+
workflow_dispatch:
6+
7+
jobs:
8+
sync-to-hub:
9+
runs-on: ubuntu-latest
10+
steps:
11+
- uses: actions/checkout@v6
12+
- uses: huggingface/hub-sync@v0.1.0
13+
with:
14+
github_repo_id: ${{ github.repository }}
15+
huggingface_repo_id: opensyndrome/editor
16+
hf_token: ${{ secrets.HF_TOKEN }}
17+
space_sdk: docker
18+
private: true

.gitignore

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,9 +195,9 @@ cython_debug/
195195
.abstra/
196196

197197
# Visual Studio Code
198-
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
198+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
199199
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
200-
# and can be added to the global gitignore or merged into this file. However, if you prefer,
200+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
201201
# you could uncomment the following to ignore the entire vscode folder
202202
# .vscode/
203203
# Temporary file for partial code execution
@@ -216,3 +216,10 @@ __marimo__/
216216

217217
# Streamlit
218218
.streamlit/secrets.toml
219+
220+
# Open Syndrome Editor
221+
node_modules/
222+
frontend/node_modules/
223+
frontend/dist/
224+
app/static/
225+
.env

.pre-commit-config.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
repos:
2+
- repo: https://github.com/pre-commit/pre-commit-hooks
3+
rev: v6.0.0
4+
hooks:
5+
- id: trailing-whitespace
6+
- id: end-of-file-fixer
7+
- id: check-yaml
8+
- id: check-toml
9+
- id: check-added-large-files
10+
args: ["--maxkb=10240"]
11+
- repo: https://github.com/astral-sh/ruff-pre-commit
12+
rev: v0.15.16
13+
hooks:
14+
- id: ruff-check
15+
args: ["--fix"]
16+
- id: ruff-format

Dockerfile

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# --- Stage 1: build the React (Vite) frontend ---
2+
FROM node:20-slim AS frontend
3+
WORKDIR /frontend
4+
COPY frontend/package.json frontend/package-lock.json ./
5+
RUN npm install
6+
COPY frontend/ ./
7+
# vite outDir is ../app/static -> resolves to /app/static in this stage
8+
RUN npm run build
9+
10+
# --- Stage 2: Python API serving the built SPA ---
11+
FROM python:3.12-slim-trixie
12+
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
13+
14+
RUN apt-get update && apt-get install -y --no-install-recommends git \
15+
&& rm -rf /var/lib/apt/lists/*
16+
17+
RUN useradd -m -u 1000 appuser
18+
WORKDIR /app
19+
20+
COPY --chown=appuser:appuser pyproject.toml uv.lock ./
21+
COPY --chown=appuser:appuser app ./app
22+
COPY --from=frontend --chown=appuser:appuser /app/static ./app/static
23+
24+
ENV UV_NO_DEV=1
25+
# Provider/model are configured per-deployment (HF Space variables or --env-file),
26+
# never baked in, so a mounted .env is always respected.
27+
28+
RUN chown -R appuser:appuser /app
29+
USER appuser
30+
RUN uv sync --locked --no-dev
31+
32+
EXPOSE 7860
33+
# `fastapi run` reads the entrypoint from [tool.fastapi] in pyproject.toml.
34+
CMD ["uv", "run", "fastapi", "run", "--port", "7860"]

README.md

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,97 @@
1-
# editor
1+
---
2+
title: Open Syndrome Editor
3+
emoji: 🩺
4+
colorFrom: green
5+
colorTo: blue
6+
sdk: docker
7+
app_port: 7860
8+
pinned: false
9+
---
10+
11+
# Open Syndrome Definition — Editor
12+
13+
A web editor for [Open Syndrome Definitions (OSD)](https://github.com/OpenSyndrome/schema):
14+
write a free-text clinical case definition and **convert** it to machine-readable JSON,
15+
**validate** it against the schema, **enrich** ontology IDs via EBI OLS4, and
16+
**humanize** JSON back into narrative text — all powered by the
17+
[`opensyndrome`](https://github.com/OpenSyndrome/open-syndrome-python) Python package.
18+
19+
It pairs a two-pane "clinical IDE" UI (React + Vite) with a FastAPI backend, served
20+
together as a single container on [Hugging Face Spaces](https://huggingface.co/spaces).
21+
22+
## Features
23+
24+
- **Convert** — parse free clinical text into the OSD schema (LLM via the configured provider).
25+
- **Validate** — check a definition against the downloaded OSD JSON Schema.
26+
- **Enrich** — populate `ontology_id`s by matching criterion names through EBI OLS4.
27+
- **Humanize** — regenerate narrative text from the structured definition.
28+
- **Import** — clinical text, a local JSON file, or browse the community
29+
[definitions repo](https://github.com/OpenSyndrome/definitions).
30+
- Recursive **inclusion / exclusion** criteria trees (AND / OR / AT_LEAST), full metadata,
31+
live validation, View / Copy / Download JSON.
32+
33+
## Models (convert & humanize)
34+
35+
The LLM provider/model is configurable via environment variables (see `.env.example`):
36+
37+
```bash
38+
OPENSYNDROME_PROVIDER=huggingface # default in the container
39+
OPENSYNDROME_MODEL=... # a model served by HF Inference Providers
40+
HF_TOKEN=... # auto-injected on Hugging Face Spaces
41+
```
42+
43+
`validate` and `enrich` need no LLM (enrich only calls EBI OLS4).
44+
45+
## Run locally
46+
47+
**Dev mode (recommended — reads your `.env`, simplest for ollama):**
48+
49+
```bash
50+
cp .env.example .env # set OPENSYNDROME_PROVIDER / OPENSYNDROME_MODEL
51+
uv sync
52+
uv run uvicorn app.main:app --reload --port 8000 # API on :8000
53+
54+
cd frontend && npm install && npm run dev # UI on :5173 (proxies /api -> :8000)
55+
# open http://localhost:5173
56+
```
57+
58+
With `OPENSYNDROME_PROVIDER=ollama`, this talks to ollama on `localhost:11434` directly.
59+
Make sure the model is pulled: `ollama pull qwen2.5:7b-instruct`.
60+
61+
**Single production image (what ships to HF Spaces):**
62+
63+
```bash
64+
docker build -t opensyndrome-editor .
65+
66+
docker run --rm -p 7860:7860 opensyndrome-editor
67+
68+
# convert/humanize need a provider. Pass your .env:
69+
docker run --rm -p 7860:7860 --env-file .env opensyndrome-editor
70+
```
71+
72+
**ollama from Docker**: ollama runs on the host, so point the container at it with `OLLAMA_HOST`:
73+
74+
```bash
75+
docker run --rm -p 7860:7860 \
76+
--add-host=host.docker.internal:host-gateway \
77+
-e OPENSYNDROME_PROVIDER=ollama \
78+
-e OPENSYNDROME_MODEL=qwen2.5:7b-instruct \
79+
-e OLLAMA_HOST=http://host.docker.internal:11434 \
80+
opensyndrome-editor
81+
# open http://localhost:7860
82+
```
83+
84+
On **Hugging Face Spaces**, set `OPENSYNDROME_PROVIDER`, `OPENSYNDROME_MODEL` (and any key)
85+
as Space *variables/secrets*`HF_TOKEN` is injected automatically.
86+
87+
## Tests
88+
89+
```bash
90+
uv run pytest # backend API
91+
cd frontend && npm test # frontend logic (osd.js)
92+
```
93+
94+
## Deploy
95+
96+
Hosted on Hugging Face Spaces (Docker SDK). Pushes to `main` are mirrored to the Space
97+
by the `Sync to Hugging Face hub` GitHub Action.

0 commit comments

Comments
 (0)