Spaces:
Sleeping
Sleeping
Alessandro Tomassini
deploy(hf): overlay README/Dockerfile da huggingface/, senza docs/binari/model
c42a5a1 | """Facade del motore di dominio. | |
| Unisce pipeline (rilevamento) + anonymizer (offuscamento) + builder dei | |
| risultati, dietro un'interfaccia semplice usata dal livello web. I componenti | |
| sono iniettati (Dependency Inversion): l'engine non istanzia modelli pesanti. | |
| Distingue due fasi: | |
| - `detect`: costosa (esegue i riconoscitori). Da fare una sola volta. | |
| - `render`: economica (applica modalita'/gravita' su span gia' rilevati). | |
| Cambiare modalita' o toggle gravita' chiama solo `render`. | |
| """ | |
| from __future__ import annotations | |
| from dataclasses import dataclass, replace | |
| from config.catalog.entities import EntityCatalog | |
| from core.contracts import LayerPriority, ScoringConfig, Severity, Span | |
| from core.output.anonymizer import Anonymizer | |
| from core.output.results import ( | |
| Report, | |
| Segment, | |
| build_json_export, | |
| build_report, | |
| build_segments, | |
| ) | |
| from core.output.trace import ClusterTrace | |
| from core.pipeline import DetectionPipeline | |
| class RenderResult: | |
| segments: list[Segment] | |
| anonymized_text: str | |
| report: Report | |
| json_export: dict | |
| class AnonymizationEngine: | |
| def __init__( | |
| self, | |
| pipeline: DetectionPipeline, | |
| anonymizer: Anonymizer, | |
| catalog: EntityCatalog, | |
| config: ScoringConfig, | |
| ) -> None: | |
| self._pipeline = pipeline | |
| self._anonymizer = anonymizer | |
| self._catalog = catalog | |
| self._config = config | |
| def config(self) -> ScoringConfig: | |
| return self._config | |
| def has_verifier(self) -> bool: | |
| """True se è agganciato il verificatore LLM (giudice a valle).""" | |
| return self._pipeline.verifier is not None | |
| def verifier_error(self) -> str | None: | |
| """Messaggio d'errore del giudice dopo l'ULTIMA verifica, se `generate()` | |
| ha fallito (es. modello non caricato). None se assente o non fallito.""" | |
| return getattr(self._pipeline.verifier, "error", None) | |
| def detect(self, text: str, min_confidence: float | None = None, | |
| progress=None, timings=None) -> list[Span]: | |
| """Fase costosa: ritorna gli span rilevati (da mettere in cache). | |
| `progress(frazione, messaggio)` opzionale per l'avanzamento in UI. | |
| `timings` (dict) opzionale: riempito coi secondi per processo/giudice. | |
| """ | |
| return self._pipeline.detect( | |
| text, min_confidence, progress=progress, timings=timings | |
| ) | |
| def detect_raw(self, text: str) -> list[Span]: | |
| """Span grezzi per-riconoscitore (no boost, no fusione, no soglia). | |
| Espone i rilevamenti di ogni processo cosi' come sono, per confronto | |
| tra metodologie. Vedi `DetectionPipeline.detect_raw`. | |
| """ | |
| return self._pipeline.detect_raw(text) | |
| def detect_with_trace( | |
| self, text: str, min_confidence: float | None = None, | |
| progress=None, timings=None, | |
| ) -> tuple[list[Span], list[ClusterTrace]]: | |
| """Come `detect`, ma restituisce anche la traccia di debug per cluster. | |
| Arricchisce ogni traccia con etichetta e gravita' del catalogo (che la | |
| pipeline neutra non conosce). `progress(frazione, messaggio)` opzionale | |
| per l'avanzamento in UI. `timings` (dict) opzionale: riempito coi | |
| secondi per processo/giudice. | |
| """ | |
| spans, traces = self._pipeline.detect_with_trace( | |
| text, min_confidence, progress=progress, timings=timings | |
| ) | |
| enriched = [ | |
| replace( | |
| t, | |
| label=self._catalog.get(t.entity_type).label, | |
| severity=self._catalog.severity(t.entity_type).value, | |
| ) | |
| for t in traces | |
| ] | |
| return spans, enriched | |
| def build_manual_spans( | |
| self, text: str, ranges: list[tuple[int, int]] | |
| ) -> list[Span]: | |
| """Crea span manuali (correzione: l'utente forza l'offuscamento). | |
| Score 1.0 e layer RULES cosi' vincono sempre nelle sovrapposizioni. | |
| """ | |
| manual: list[Span] = [] | |
| for start, end in ranges: | |
| start = max(0, min(start, len(text))) | |
| end = max(start, min(end, len(text))) | |
| if end <= start: | |
| continue | |
| manual.append( | |
| Span( | |
| start=start, | |
| end=end, | |
| text=text[start:end], | |
| entity_type="DATO_SENSIBILE", | |
| score=1.0, | |
| layer=LayerPriority.RULES, | |
| validated=True, | |
| source="manuale", | |
| ) | |
| ) | |
| return manual | |
| def merge_manual(self, auto: list[Span], manual: list[Span]) -> list[Span]: | |
| """Unisce span automatici e manuali, rimuovendo gli automatici coperti | |
| da una correzione manuale (il manuale ha priorita'). | |
| """ | |
| result = list(manual) | |
| for sp in auto: | |
| covered = any( | |
| m.start < sp.end and sp.start < m.end for m in manual | |
| ) | |
| if not covered: | |
| result.append(sp) | |
| result.sort(key=lambda s: s.start) | |
| return result | |
| def exclude_spans( | |
| self, spans: list[Span], excluded_ranges: list[tuple[int, int]] | |
| ) -> list[Span]: | |
| """Rimuove gli span coperti da un range escluso (ripristino manuale: | |
| l'utente chiede di NON offuscare un'entita' gia' rilevata).""" | |
| if not excluded_ranges: | |
| return spans | |
| return [ | |
| sp for sp in spans | |
| if not any(r0 < sp.end and sp.start < r1 for r0, r1 in excluded_ranges) | |
| ] | |
| def render( | |
| self, | |
| text: str, | |
| spans: list[Span], | |
| mode: str = "placeholder", | |
| enabled_severities: set[Severity] | None = None, | |
| ) -> RenderResult: | |
| """Fase economica: applica modalita'/gravita' su span gia' rilevati.""" | |
| active = ( | |
| spans | |
| if enabled_severities is None | |
| else [ | |
| s for s in spans | |
| if self._catalog.severity(s.entity_type) in enabled_severities | |
| ] | |
| ) | |
| return RenderResult( | |
| segments=build_segments(text, active, self._catalog), | |
| anonymized_text=self._anonymizer.apply( | |
| text, spans, mode, enabled_severities | |
| ), | |
| report=build_report(active, self._catalog), | |
| json_export=build_json_export(active, self._catalog), | |
| ) | |