Alessandro Tomassini
deploy(hf): overlay README/Dockerfile da huggingface/, senza docs/binari/model
c42a5a1
Raw
History Blame Contribute Delete
5.2 kB
"""Astrazioni del sottosistema di rilevamento (SOLID).
Speculare al sottosistema di rendering PDF: ogni *metodologia di rilevamento*
e' una strategia isolata che dichiara quali riconoscitori attivare. Le
strategie sono registrate in un `DetectionRegistry` ed escludibili da
configurazione: aggiungerne/toglierne una non tocca il codice esistente
(Open/Closed) ne' il livello web (Dependency Inversion).
- Single Responsibility: ogni strategia vive in un proprio file e sa solo
comporre il proprio insieme di riconoscitori.
- Liskov: tutte rispettano `recognizers(provider) -> list[Recognizer]`.
- Interface Segregation: la strategia espone solo `info` e `recognizers`;
il `RecognizerProvider` espone una factory lazy per ogni famiglia di tecniche.
- Dependency Inversion: le strategie dipendono dal Protocol `RecognizerProvider`,
non dai modelli pesanti concreti.
"""
from __future__ import annotations
from abc import ABC, abstractmethod
from collections.abc import Sequence
from dataclasses import dataclass
from enum import Enum
from typing import Protocol, runtime_checkable
from core.contracts import Recognizer
class DetectionTechnique(str, Enum):
"""Famiglia tecnologica di una metodologia di rilevamento."""
DETERMINISTIC = "deterministic" # regex + checksum, nessun modello
STATISTICAL_NER = "statistical_ner" # token classification (transformer)
SEMANTIC_ZEROSHOT = "zero_shot" # natural language inference
GENERATIVE_LLM = "generative_llm" # LLM istruito che estrae JSON
EMBEDDING = "embedding" # similarita' del coseno su embedding
HYBRID = "hybrid" # fusione multi-tecnica
@dataclass(frozen=True, slots=True)
class StrategyInfo:
"""Metadati di una strategia, usati anche dalla UI per i controlli."""
key: str
label: str
technique: DetectionTechnique
description: str
requires_ml: bool # True se senza modelli degrada al solo backbone
models: tuple[str, ...] = () # id dei modelli usati (informativo per la UI)
default: bool = False
@runtime_checkable
class RecognizerProvider(Protocol):
"""Sorgente lazy di riconoscitori per famiglia di tecnica.
Ogni metodo costruisce (una sola volta, con caching a carico
dell'implementazione) i riconoscitori della propria famiglia. In assenza
dei modelli o con ML disabilitato ritorna una sequenza vuota: le strategie
degradano in modo controllato sul backbone a regole.
"""
def rules(self) -> Sequence[Recognizer]: ...
def ner(self) -> Sequence[Recognizer]: ...
def ner_comuni(self) -> Sequence[Recognizer]: ...
def zero_shot(self) -> Sequence[Recognizer]: ...
def baseline(self) -> Sequence[Recognizer]: ...
def llm(self) -> Sequence[Recognizer]: ...
def embedding(self) -> Sequence[Recognizer]: ...
# Costruzione generica per chiave di layer (supporta layer aggiunti a runtime).
def family(self, key: str) -> Sequence[Recognizer]: ...
class DetectionStrategy(ABC):
"""Interfaccia comune a tutte le metodologie di rilevamento."""
#: Metadati statici della strategia.
info: StrategyInfo
@abstractmethod
def recognizers(self, provider: RecognizerProvider) -> list[Recognizer]:
"""Compone l'insieme di riconoscitori della metodologia."""
def make_strategy(info: StrategyInfo, *provider_methods: str) -> DetectionStrategy:
"""Crea una strategia che compone i riconoscitori dei metodi indicati.
`provider_methods` sono i nomi dei metodi del `RecognizerProvider` da
concatenare in ordine (es. "rules", "ner"). Elimina la duplicazione delle
classi-strategia quasi identiche: aggiungere una metodologia diventa una
riga dichiarativa (Open/Closed) invece di un nuovo file.
"""
class _ComposedStrategy(DetectionStrategy):
def recognizers(self, provider: RecognizerProvider) -> list[Recognizer]:
recs: list[Recognizer] = []
for method in provider_methods:
recs.extend(getattr(provider, method)())
return recs
strategy = _ComposedStrategy()
strategy.info = info
return strategy
class DetectionRegistry:
"""Collezione ordinata di metodologie abilitate (Open/Closed)."""
def __init__(self) -> None:
self._strategies: dict[str, DetectionStrategy] = {}
def register(self, strategy: DetectionStrategy) -> DetectionRegistry:
if strategy.info.key in self._strategies:
raise ValueError(f"Strategia gia' registrata: {strategy.info.key}")
self._strategies[strategy.info.key] = strategy
return self
def has(self, key: str) -> bool:
return key in self._strategies
def get(self, key: str) -> DetectionStrategy | None:
return self._strategies.get(key)
def infos(self) -> list[StrategyInfo]:
return [s.info for s in self._strategies.values()]
def default_key(self) -> str:
for s in self._strategies.values():
if s.info.default:
return s.info.key
# fallback: la prima registrata
return next(iter(self._strategies))
def __len__(self) -> int:
return len(self._strategies)