Chaeyoon Claude Opus 4.8 commited on
Commit ·
9ee0f17
1
Parent(s): 28fea0d
Rename output/ -> outputs/; add docs/CHANGELOG.md
Browse files- Generated artifacts now write to outputs/ (data/raw = inputs stay under data/;
the old data/out is legacy and gitignored).
- Update all path refs: src/trust_demo.py, streamlit_app.py, tests/run_eval.py,
.gitignore, README.md, CLAUDE.md.
- Add docs/CHANGELOG.md documenting big changes per PR since the fork's initial commit.
Verified: ruff clean; 23 tests pass; run_eval writes outputs/results.json.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- .gitignore +2 -2
- CLAUDE.md +3 -3
- README.md +3 -3
- docs/CHANGELOG.md +52 -0
- {output → outputs}/.gitkeep +0 -0
- src/trust_demo.py +1 -1
- streamlit_app.py +2 -2
- tests/run_eval.py +3 -3
.gitignore
CHANGED
|
@@ -1,8 +1,8 @@
|
|
| 1 |
# NHS PHI safety: raw data, generated output, and the re-identification vault NEVER leave the repo
|
| 2 |
data/*
|
| 3 |
!data/.gitkeep
|
| 4 |
-
|
| 5 |
-
!
|
| 6 |
*.vault.json
|
| 7 |
|
| 8 |
# Python / packaging
|
|
|
|
| 1 |
# NHS PHI safety: raw data, generated output, and the re-identification vault NEVER leave the repo
|
| 2 |
data/*
|
| 3 |
!data/.gitkeep
|
| 4 |
+
outputs/*
|
| 5 |
+
!outputs/.gitkeep
|
| 6 |
*.vault.json
|
| 7 |
|
| 8 |
# Python / packaging
|
CLAUDE.md
CHANGED
|
@@ -9,8 +9,8 @@ data leaves a Trust. Encode Club "Trusted Data & AI Infrastructure" hackathon; f
|
|
| 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 ->
|
| 13 |
-
python -m src.trust_demo # two NHS Trusts share only de-identified data ->
|
| 14 |
streamlit run streamlit_app.py # demo (Try-it / Metrics / Governance / Two-Trust)
|
| 15 |
python -m pytest tests/ -v
|
| 16 |
|
|
@@ -29,7 +29,7 @@ python -m pytest tests/ -v
|
|
| 29 |
WITHOUT spaCy/Presidio (the fallback path). snake_case / PascalCase.
|
| 30 |
|
| 31 |
## Data rules (treat the synthetic notes as if real NHS PHI)
|
| 32 |
-
- `data/raw/`, `
|
| 33 |
into prompts; point at file paths.
|
| 34 |
- The note→patient join (`src/data.py` ground truth) is the EVAL-ONLY oracle. It must NEVER feed
|
| 35 |
detection/transform — that is data leakage and invalidates the metric.
|
|
|
|
| 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 -> outputs/results.json
|
| 13 |
+
python -m src.trust_demo # two NHS Trusts share only de-identified data -> outputs/
|
| 14 |
streamlit run streamlit_app.py # demo (Try-it / Metrics / Governance / Two-Trust)
|
| 15 |
python -m pytest tests/ -v
|
| 16 |
|
|
|
|
| 29 |
WITHOUT spaCy/Presidio (the fallback path). snake_case / PascalCase.
|
| 30 |
|
| 31 |
## Data rules (treat the synthetic notes as if real NHS PHI)
|
| 32 |
+
- `data/raw/`, `outputs/`, and any vault export are gitignored — never commit. Never paste note text
|
| 33 |
into prompts; point at file paths.
|
| 34 |
- The note→patient join (`src/data.py` ground truth) is the EVAL-ONLY oracle. It must NEVER feed
|
| 35 |
detection/transform — that is data leakage and invalidates the metric.
|
README.md
CHANGED
|
@@ -88,7 +88,7 @@ src/ the package
|
|
| 88 |
tests/ unit tests + run_eval.py (the evaluation CLI)
|
| 89 |
docs/ tool card + project docs
|
| 90 |
data/ input CSVs (gitignored)
|
| 91 |
-
|
| 92 |
streamlit_app.py the demo UI pyproject.toml packaging + lint/test config
|
| 93 |
```
|
| 94 |
|
|
@@ -113,8 +113,8 @@ pip install -e ".[app,dev]"
|
|
| 113 |
python -m spacy download en_core_web_lg # or en_core_web_sm for a faster, lighter run
|
| 114 |
|
| 115 |
# 3) run
|
| 116 |
-
python tests/run_eval.py --compare --limit 300 # reproduce the table ->
|
| 117 |
-
python -m src.trust_demo # two NHS Trusts share only de-identified data ->
|
| 118 |
streamlit run streamlit_app.py # demo: Try-it · Metrics · Governance · Two-Trust
|
| 119 |
pytest -q # unit tests
|
| 120 |
```
|
|
|
|
| 88 |
tests/ unit tests + run_eval.py (the evaluation CLI)
|
| 89 |
docs/ tool card + project docs
|
| 90 |
data/ input CSVs (gitignored)
|
| 91 |
+
outputs/ generated artifacts: results.json, manifests (gitignored)
|
| 92 |
streamlit_app.py the demo UI pyproject.toml packaging + lint/test config
|
| 93 |
```
|
| 94 |
|
|
|
|
| 113 |
python -m spacy download en_core_web_lg # or en_core_web_sm for a faster, lighter run
|
| 114 |
|
| 115 |
# 3) run
|
| 116 |
+
python tests/run_eval.py --compare --limit 300 # reproduce the table -> outputs/results.json
|
| 117 |
+
python -m src.trust_demo # two NHS Trusts share only de-identified data -> outputs/
|
| 118 |
streamlit run streamlit_app.py # demo: Try-it · Metrics · Governance · Two-Trust
|
| 119 |
pytest -q # unit tests
|
| 120 |
```
|
docs/CHANGELOG.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Changelog
|
| 2 |
+
|
| 3 |
+
Big changes since the fork of [`NoteGuard/Automatic-PII-preprocessing-tool`](https://github.com/NoteGuard/Automatic-PII-preprocessing-tool),
|
| 4 |
+
grouped by pull request / milestone (newest first). Encode Club "Trusted Data & AI Infrastructure" hackathon.
|
| 5 |
+
|
| 6 |
+
## Gold-RAP restructure & Hugging Face deploy — 2026-06-20
|
| 7 |
+
Branches `dev/refactor-cleancode` + `feat/hf-spaces-demo`, merged to `main`.
|
| 8 |
+
- Reorganised to a Gold-RAP "analysis as a product" layout: `src/` package, `tests/`, `docs/`,
|
| 9 |
+
`data/` (inputs), `outputs/` (generated artifacts).
|
| 10 |
+
- Renamed package `noteguard/` → `src/`; British spelling throughout (`recognisers`).
|
| 11 |
+
- Packaged via `pyproject.toml` as the single dependency source (removed `requirements.txt`).
|
| 12 |
+
- CI (`.github/workflows/ci.yml`) runs `ruff` + `pytest` on every push/PR; added logging.
|
| 13 |
+
- Streamlit demo moved to repo root (`streamlit_app.py`); `run_eval.py` moved under `tests/`.
|
| 14 |
+
- Hugging Face Spaces (Docker SDK) deploy: `Dockerfile`, README front-matter, `DEPLOY_HF_SPACES.md`.
|
| 15 |
+
- Decluttered Claude tooling (eval skill, post-edit hook, local settings) and the root changelog.
|
| 16 |
+
|
| 17 |
+
## Post-PR #3 hardening — 2026-06-20
|
| 18 |
+
Direct fixes on `main` after the team-briefing work.
|
| 19 |
+
- Only **date-of-birth** is masked; visit / admission dates are kept (clinically useful). Human-readable
|
| 20 |
+
redaction labels (e.g. `[NHS number]`).
|
| 21 |
+
- Fixed pseudonym mis-mapping caused by spaCy ORGANIZATION over-tagging.
|
| 22 |
+
- Removed the roster / gazetteer feature (kept the metric honest — no circularity).
|
| 23 |
+
- Consolidated the frontend on Streamlit; removed the Gradio demo.
|
| 24 |
+
|
| 25 |
+
## PR #3 — Action items 1–4 from the team briefing — 2026-06-20
|
| 26 |
+
- Default spaCy model `en_core_web_lg` (best UK-name recall; clinical transformer tested and rejected).
|
| 27 |
+
- **Human-in-the-loop review queue**: low-confidence spans are still redacted *and* flagged for IG
|
| 28 |
+
analyst confirmation before SDE-pool admission.
|
| 29 |
+
- ORGANIZATION excluded from Presidio's kept entities (it over-tagged labels like "NHS"/"GMC"); NHS
|
| 30 |
+
site names caught by a LOCATION rule instead.
|
| 31 |
+
- Overlap-safe, priority-ranked span merge (precise rule entities beat broad NER spans).
|
| 32 |
+
|
| 33 |
+
## PR #2 — UK entity backport — 2026-06-20
|
| 34 |
+
- Added `UK_NINO`, `UK_PASSPORT`, `UK_VEHICLE_REGISTRATION` recognisers and comma-separated
|
| 35 |
+
NHS-number detection, with format-correct fake surrogates for each.
|
| 36 |
+
|
| 37 |
+
## PR #1 — NoteGuard de-identification gate — 2026-06-20
|
| 38 |
+
Consolidated two parallel implementations into one package.
|
| 39 |
+
- One pluggable `Detector` interface: `RuleDetector` / `PresidioDetector`, degrading gracefully to
|
| 40 |
+
pure-Python rules when spaCy/Presidio are unavailable.
|
| 41 |
+
- Patient-consistent pseudonymisation via a Faker(en_GB) vault + per-patient date-shift; redaction mode.
|
| 42 |
+
- Evaluation harness: per-entity precision / recall / F1 **and** a measured residual-leakage rate,
|
| 43 |
+
using the dataset's structured patient/admission tables as free ground truth.
|
| 44 |
+
- Two-Trust "sanitise-at-source" demo; Streamlit demo UI.
|
| 45 |
+
|
| 46 |
+
## Initial NoteGuard implementation
|
| 47 |
+
- Pure-Python `noteguard` package: rule recognisers (NHS Modulus-11 + context anchor, postcode, date,
|
| 48 |
+
phone, email), detection layer, redaction/pseudonymisation transforms, evaluation, pipeline.
|
| 49 |
+
- Loads the NHSE synthetic clinical notes (3 CSVs joined on `person_id`/`admission_id`).
|
| 50 |
+
|
| 51 |
+
## Initial commit
|
| 52 |
+
- Fork of `NoteGuard/Automatic-PII-preprocessing-tool`.
|
{output → outputs}/.gitkeep
RENAMED
|
File without changes
|
src/trust_demo.py
CHANGED
|
@@ -22,7 +22,7 @@ from .evaluate import _find_all, ground_truth_spans, value_variants
|
|
| 22 |
from .pipeline import Pipeline
|
| 23 |
from .transform import PSEUDONYM, PseudonymVault
|
| 24 |
|
| 25 |
-
OUT_DIR = Path(__file__).resolve().parent.parent / "
|
| 26 |
TRUST_NAMES = {0: "Trust A (Northgate NHS Foundation Trust)", 1: "Trust B (Riverside NHS Trust)"}
|
| 27 |
|
| 28 |
|
|
|
|
| 22 |
from .pipeline import Pipeline
|
| 23 |
from .transform import PSEUDONYM, PseudonymVault
|
| 24 |
|
| 25 |
+
OUT_DIR = Path(__file__).resolve().parent.parent / "outputs"
|
| 26 |
TRUST_NAMES = {0: "Trust A (Northgate NHS Foundation Trust)", 1: "Trust B (Riverside NHS Trust)"}
|
| 27 |
|
| 28 |
|
streamlit_app.py
CHANGED
|
@@ -24,8 +24,8 @@ from src.evaluate import evaluate # noqa: E402
|
|
| 24 |
from src.pipeline import Pipeline # noqa: E402
|
| 25 |
from src.transform import PSEUDONYM, REDACTION, PseudonymVault # noqa: E402
|
| 26 |
|
| 27 |
-
OUT_DIR = REPO / "
|
| 28 |
-
RESULTS = REPO / "
|
| 29 |
|
| 30 |
ENTITY_COLORS = {
|
| 31 |
"PERSON": "#ffd6e0", "UK_NHS": "#ffe9b3", "DATE_TIME": "#d4f4dd", "UK_POSTCODE": "#cfe8ff",
|
|
|
|
| 24 |
from src.pipeline import Pipeline # noqa: E402
|
| 25 |
from src.transform import PSEUDONYM, REDACTION, PseudonymVault # noqa: E402
|
| 26 |
|
| 27 |
+
OUT_DIR = REPO / "outputs"
|
| 28 |
+
RESULTS = REPO / "outputs" / "results.json"
|
| 29 |
|
| 30 |
ENTITY_COLORS = {
|
| 31 |
"PERSON": "#ffd6e0", "UK_NHS": "#ffe9b3", "DATE_TIME": "#d4f4dd", "UK_POSTCODE": "#cfe8ff",
|
tests/run_eval.py
CHANGED
|
@@ -4,7 +4,7 @@
|
|
| 4 |
python tests/run_eval.py --method pseudonym # leakage under pseudonymisation
|
| 5 |
python tests/run_eval.py --compare # rules-only vs presidio+rules
|
| 6 |
|
| 7 |
-
Writes
|
| 8 |
This is the pipeline's evaluation entry point; it lives under tests/ alongside the unit tests.
|
| 9 |
"""
|
| 10 |
from __future__ import annotations
|
|
@@ -23,7 +23,7 @@ from src.detect import RuleDetector, build_detector # noqa: E402
|
|
| 23 |
from src.evaluate import EvalResult, evaluate # noqa: E402
|
| 24 |
from src.transform import REDACTION # noqa: E402
|
| 25 |
|
| 26 |
-
OUTPUT_DIR = REPO / "
|
| 27 |
logger = logging.getLogger("noteguard.eval")
|
| 28 |
|
| 29 |
|
|
@@ -49,7 +49,7 @@ def main() -> None:
|
|
| 49 |
ap.add_argument("--method", default=REDACTION, choices=["redaction", "pseudonym"])
|
| 50 |
ap.add_argument("--no-presidio", action="store_true", help="rules only")
|
| 51 |
ap.add_argument("--compare", action="store_true", help="rules vs presidio+rules")
|
| 52 |
-
ap.add_argument("--out", default=None, help="output JSON path (default:
|
| 53 |
args = ap.parse_args()
|
| 54 |
|
| 55 |
logger.info("loading notes (limit=%s) ...", args.limit)
|
|
|
|
| 4 |
python tests/run_eval.py --method pseudonym # leakage under pseudonymisation
|
| 5 |
python tests/run_eval.py --compare # rules-only vs presidio+rules
|
| 6 |
|
| 7 |
+
Writes outputs/results.json (consumed by the Streamlit metrics panel) and prints a summary.
|
| 8 |
This is the pipeline's evaluation entry point; it lives under tests/ alongside the unit tests.
|
| 9 |
"""
|
| 10 |
from __future__ import annotations
|
|
|
|
| 23 |
from src.evaluate import EvalResult, evaluate # noqa: E402
|
| 24 |
from src.transform import REDACTION # noqa: E402
|
| 25 |
|
| 26 |
+
OUTPUT_DIR = REPO / "outputs"
|
| 27 |
logger = logging.getLogger("noteguard.eval")
|
| 28 |
|
| 29 |
|
|
|
|
| 49 |
ap.add_argument("--method", default=REDACTION, choices=["redaction", "pseudonym"])
|
| 50 |
ap.add_argument("--no-presidio", action="store_true", help="rules only")
|
| 51 |
ap.add_argument("--compare", action="store_true", help="rules vs presidio+rules")
|
| 52 |
+
ap.add_argument("--out", default=None, help="output JSON path (default: outputs/results.json)")
|
| 53 |
args = ap.parse_args()
|
| 54 |
|
| 55 |
logger.info("loading notes (limit=%s) ...", args.limit)
|