# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## What this project is A Gradio web app that anonymizes Italian public procurement documents (*gare d'appalto*). Deployed as a Hugging Face Space (`app_file: app.py`). The UI is in Italian. ## Running the app ```bash pip install -r requirements.txt python -m spacy download it_core_news_lg python app.py ``` There is no test suite and no linter configuration. ## Architecture: 4-layer detection pipeline The core logic is in `pipeline.py`. Detection runs 4 layers in parallel, then `span_resolver.py` merges them without overlaps. Priority order (highest first): | Priority | Layer | Source | |---|---|---| | 0 | Regex recognizers | `recognizers.py` → `_RECOGNIZERS` list | | 1 | Transformer NER | `DeepMount00/Italian_NER_XXL_v2` via Presidio | | 2 | GLiNER zero-shot | `DeepMount00/GLiNER_PII_ITA` via `gliner_recognizer.py` | | 3 | Presidio IT built-in | `analyzer_step0` in `recognizers.py` | Higher priority layers always win over lower ones when spans overlap. Within a layer, higher score + longer span wins. Two score-boosting passes happen after merging: - **Cross-layer agreement**: +0.15 if ≥2 layers agree on the same span (Jaccard ≥0.8) - **Post-boost regex**: +0.30 if the span fully matches a `POST_BOOST_PATTERNS` entry in `recognizers.py` Long texts are split into overlapping chunks (`_CHUNK_SIZE=1500`, `_CHUNK_OVERLAP=200`) in `pipeline.py` before being fed to each layer. ## Key files - `pipeline.py` — active anonymization logic (`detect()`, `anonymize()`, `apply_custom_lines()`). **This supersedes `anonymizer.py`**, which is a legacy file no longer imported anywhere. - `recognizers.py` — three `AnalyzerEngine` instances (`analyzer_full`, `analyzer_ner_only`, `analyzer_step0`) plus all regex `PatternRecognizer`s and `POST_BOOST_PATTERNS`. Model loading happens at import time. - `gliner_recognizer.py` — lazy-loading Presidio-compatible GLiNER wrapper; model loads on first `analyze()` call. - `span_resolver.py` — `resolve_overlapping_spans(results_by_priority)` merges all layers; sets `recognition_metadata["source_priority"]` on each accepted result. - `config.py` — all constants: `MODES`, `LABEL_IT` (entity type → Italian label), `ENTITY_SEVERITY`, severity colors/labels. - `renderers.py` — three HTML renderers for the Gradio output tabs (Highlighted / Anonymized / Report). The Highlighted tab embeds JavaScript for per-line checkbox selection. - `app.py` — Gradio UI; shared `state` dict carries `orig_text`, `mode`, `anon_text` between the main process and the custom-lines update handler. - `utils.py` — SpaCy model auto-download helper + PDF text extraction via pdfplumber. ## Anonymization modes Defined in `config.py → MODES` and applied in `pipeline.py → _apply_mode()`: - `placeholder` → `[TIPO]` (or numbered `[PREFIX_001]` for GLiNER procurement entities) - `last4` → `****mith` - `stars` → `****` - `first` → `J***` `IMPORTO_BASE_ASTA` in placeholder mode is replaced with a deterministically scaled amount (not a tag). Currency symbols (€, EUR, $, £) are preserved as a prefix when anonymizing `IMPORTO_GARA` / `VALUTA`. ## Adding a new entity type 1. Add a `PatternRecognizer` entry to `_RECOGNIZERS` in `recognizers.py` (and its name to `_REGEX_RECOGNIZER_NAMES` in `pipeline.py` if it is regex-based). 2. Map it in `config.py → LABEL_IT` and assign a severity in `ENTITY_SEVERITY`. 3. Optionally add a fullmatch pattern to `POST_BOOST_PATTERNS` in `recognizers.py` for score boosting. ## Deployment The project is a Hugging Face Space. `README.md` contains the Space metadata header (title, SDK version, `app_file`, etc.) rather than documentation.