Merge feat/hf-spaces-demo into main; deploy config adapted to src/ layout
Browse filesmain now combines the Gold-RAP restructure (dev/refactor-cleancode, the spine) with the
Hugging Face Spaces deploy config (feat/hf-spaces-demo):
- Dockerfile rewritten for the new layout: installs the package via `pip install ".[app]"`
(deps from pyproject.toml) + `spacy download en_core_web_lg`, runs `streamlit run
streamlit_app.py` (was app/streamlit_app.py).
- README keeps the HF front-matter (sdk: docker, app_port 8501) on top of the restructured body.
- DEPLOY_HF_SPACES.md updated: push `main` to the Space; entry point is streamlit_app.py.
Dependencies consolidated to a single source of truth:
- Remove requirements.txt; pyproject.toml [project.dependencies] + [app]/[dev] extras is canonical.
CI (`pip install -e .[dev]`) and the Dockerfile (`pip install .[app]`) both build from pyproject.
Verified: ruff clean; 23 tests pass; src imports OK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- CLAUDE.md +1 -1
- DEPLOY_HF_SPACES.md +46 -0
- Dockerfile +27 -0
- README.md +13 -1
- requirements.txt +0 -9
|
@@ -7,7 +7,7 @@ data leaves a Trust. Encode Club "Trusted Data & AI Infrastructure" hackathon; f
|
|
| 7 |
```bash
|
| 8 |
# Setup (Windows PowerShell)
|
| 9 |
python -m venv .venv; .\.venv\Scripts\Activate.ps1
|
| 10 |
-
pip install -
|
| 11 |
|
| 12 |
python tests/run_eval.py --compare --limit 300 # VERIFIABLE SIGNAL: rules vs presidio+rules -> output/results.json
|
| 13 |
python -m src.trust_demo # two NHS Trusts share only de-identified data -> output/
|
|
|
|
| 7 |
```bash
|
| 8 |
# Setup (Windows PowerShell)
|
| 9 |
python -m venv .venv; .\.venv\Scripts\Activate.ps1
|
| 10 |
+
pip install -e ".[app,dev]"; python -m spacy download en_core_web_lg
|
| 11 |
|
| 12 |
python tests/run_eval.py --compare --limit 300 # VERIFIABLE SIGNAL: rules vs presidio+rules -> output/results.json
|
| 13 |
python -m src.trust_demo # two NHS Trusts share only de-identified data -> output/
|
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Deploy the live demo on Hugging Face Spaces
|
| 2 |
+
|
| 3 |
+
The repo ships everything an HF **Docker Space** needs (HF's create API only accepts
|
| 4 |
+
`gradio | docker | static`, so the Streamlit app runs via Docker):
|
| 5 |
+
|
| 6 |
+
1. **`Dockerfile`** — installs the package (`pip install ".[app]"`) + `en_core_web_lg`, then runs
|
| 7 |
+
`streamlit run streamlit_app.py` on port 8501.
|
| 8 |
+
2. **`README.md` front-matter** — `sdk: docker`, `app_port: 8501` (the Space card config).
|
| 9 |
+
|
| 10 |
+
The Space downloads the NHS dataset from Hugging Face at first run; if that fails it falls back to
|
| 11 |
+
paste-only mode.
|
| 12 |
+
|
| 13 |
+
## One-time deploy
|
| 14 |
+
|
| 15 |
+
```bash
|
| 16 |
+
# 1) log in (HF token with WRITE scope: https://huggingface.co/settings/tokens)
|
| 17 |
+
hf auth login
|
| 18 |
+
|
| 19 |
+
# 2) create the Space (Docker SDK, free cpu-basic) — or use https://huggingface.co/new-space
|
| 20 |
+
hf repos create <HF_USERNAME>/noteguard --repo-type space --space-sdk docker
|
| 21 |
+
|
| 22 |
+
# 3) push main to the Space's main branch
|
| 23 |
+
git remote add space https://huggingface.co/spaces/<HF_USERNAME>/noteguard
|
| 24 |
+
git push space main
|
| 25 |
+
```
|
| 26 |
+
|
| 27 |
+
> Uses the `hf` CLI from `huggingface_hub` >= 1.0 (`huggingface-cli` still works as a deprecated alias).
|
| 28 |
+
|
| 29 |
+
The Space builds the image (Presidio + spaCy + `en_core_web_lg`, ~3–5 min the first time), then serves
|
| 30 |
+
the app at `https://huggingface.co/spaces/<HF_USERNAME>/noteguard`.
|
| 31 |
+
|
| 32 |
+
## Updating an existing Space
|
| 33 |
+
|
| 34 |
+
After any change on `main`:
|
| 35 |
+
|
| 36 |
+
```bash
|
| 37 |
+
git push space main # the Space rebuilds automatically
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
The `Dockerfile` is the single source of truth for the entry point (`streamlit_app.py`) and the model,
|
| 41 |
+
so renames in the repo never require changing Space settings — just push.
|
| 42 |
+
|
| 43 |
+
## Notes
|
| 44 |
+
- **Resource-constrained?** If the build is slow or the Space OOMs, change `en_core_web_lg` to
|
| 45 |
+
`en_core_web_sm` in the `Dockerfile`.
|
| 46 |
+
- Front-matter lives at the top of `README.md`; GitHub renders it as a small table — harmless.
|
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Hugging Face Spaces (Docker SDK) — serves the NoteGuard Streamlit app.
|
| 2 |
+
# HF's create API only accepts gradio|docker|static, so Streamlit runs via Docker.
|
| 3 |
+
FROM python:3.11-slim
|
| 4 |
+
|
| 5 |
+
# Non-root user (HF Spaces convention) -> gives a writable HOME for model/dataset caches.
|
| 6 |
+
RUN useradd -m -u 1000 user
|
| 7 |
+
USER user
|
| 8 |
+
ENV HOME=/home/user \
|
| 9 |
+
PATH=/home/user/.local/bin:$PATH \
|
| 10 |
+
PYTHONUNBUFFERED=1
|
| 11 |
+
|
| 12 |
+
WORKDIR $HOME/app
|
| 13 |
+
|
| 14 |
+
# Install the package (deps come from pyproject.toml) + the spaCy model. Copy only what pip
|
| 15 |
+
# needs first, for Docker layer caching.
|
| 16 |
+
COPY --chown=user pyproject.toml README.md ./
|
| 17 |
+
COPY --chown=user src ./src
|
| 18 |
+
RUN pip install --no-cache-dir --upgrade pip && \
|
| 19 |
+
pip install --no-cache-dir ".[app]" && \
|
| 20 |
+
python -m spacy download en_core_web_lg
|
| 21 |
+
|
| 22 |
+
# App code (data/ and .venv are gitignored, so the app downloads the dataset at runtime).
|
| 23 |
+
COPY --chown=user . .
|
| 24 |
+
|
| 25 |
+
EXPOSE 8501
|
| 26 |
+
CMD ["streamlit", "run", "streamlit_app.py", \
|
| 27 |
+
"--server.port=8501", "--server.address=0.0.0.0", "--server.headless=true"]
|
|
@@ -1,3 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
# 🛡️ NoteGuard
|
| 2 |
|
| 3 |
**Automatic PII sanitisation for NHS clinical notes — clean data in, no identifiers out.**
|
|
@@ -36,7 +48,7 @@ layer** Presidio leaves to you:
|
|
| 36 |
## Results — residual leakage drops as we layer detection
|
| 37 |
|
| 38 |
*Known identifiers (joined from the structured tables) still present after sanitisation. Measured on all
|
| 39 |
-
**1,602 notes** (1,027 known-PII occurrences). Reproduce with `python run_eval.py --compare`.*
|
| 40 |
|
| 41 |
| Detector | NHS number F1 | PERSON recall | **Residual leakage** |
|
| 42 |
|---|---|---|---|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: NoteGuard — NHS De-Identification Gate
|
| 3 |
+
emoji: 🛡️
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: indigo
|
| 6 |
+
sdk: docker
|
| 7 |
+
app_port: 8501
|
| 8 |
+
pinned: false
|
| 9 |
+
license: mit
|
| 10 |
+
short_description: Sanitise-at-source PII removal for NHS notes
|
| 11 |
+
---
|
| 12 |
+
|
| 13 |
# 🛡️ NoteGuard
|
| 14 |
|
| 15 |
**Automatic PII sanitisation for NHS clinical notes — clean data in, no identifiers out.**
|
|
|
|
| 48 |
## Results — residual leakage drops as we layer detection
|
| 49 |
|
| 50 |
*Known identifiers (joined from the structured tables) still present after sanitisation. Measured on all
|
| 51 |
+
**1,602 notes** (1,027 known-PII occurrences). Reproduce with `python tests/run_eval.py --compare`.*
|
| 52 |
|
| 53 |
| Detector | NHS number F1 | PERSON recall | **Residual leakage** |
|
| 54 |
|---|---|---|---|
|
|
@@ -1,9 +0,0 @@
|
|
| 1 |
-
presidio-analyzer
|
| 2 |
-
presidio-anonymizer
|
| 3 |
-
spacy>=3.7,<3.9
|
| 4 |
-
faker
|
| 5 |
-
ftfy
|
| 6 |
-
huggingface_hub
|
| 7 |
-
pandas
|
| 8 |
-
streamlit
|
| 9 |
-
pytest
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|