Ctrl K
ComunidadGuías…

app/masking/detector.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
from __future__ import annotations

from dataclasses import asdict, dataclass

from .regex_patterns import ENTITY_PRIORITY, PATTERNS, PatternSpec


@dataclass(frozen=True, slots=True)
class Entity:
    type: str
    text: str
    start: int
    end: int
    confidence: float = 1.0
    method: str = "regex"

    def to_dict(self) -> dict[str, object]:
        return asdict(self)


def _candidate_from_match(text: str, spec: PatternSpec, match: object) -> Entity:
    # ``match`` is a re.Match at runtime; keeping this helper small makes the
    # detector's overlap logic easy to test.
    regex_match = match  # type: ignore[assignment]
    if spec.capture_group:
        start, end = regex_match.span(spec.capture_group)
    else:
        start, end = regex_match.span()
    value = text[start:end].rstrip(".,;:!?)]}>")
    end = start + len(value)
    return Entity(
        type=spec.entity_type.value,
        text=value,
        start=start,
        end=end,
    )


def _overlaps(left: Entity, right: Entity) -> bool:
    return left.start < right.end and right.start < left.end


def resolve_overlaps(candidates: list[Entity]) -> list[Entity]:
    # Sort by priority first, then prefer the longer match at equal priority.
    ordered = sorted(
        candidates,
        key=lambda item: (
            -ENTITY_PRIORITY.get(item.type, 0),
            -(item.end - item.start),
            item.start,
        ),
    )
    selected: list[Entity] = []
    for candidate in ordered:
        if any(_overlaps(candidate, existing) for existing in selected):
            continue
        selected.append(candidate)
    return sorted(selected, key=lambda item: (item.start, item.end))


class RegexDetector:
    def __init__(self, patterns: tuple[PatternSpec, ...] = PATTERNS) -> None:
        self.patterns = patterns

    def detect(self, text: str) -> list[Entity]:
        candidates: list[Entity] = []
        for spec in self.patterns:
            for match in spec.pattern.finditer(text):
                entity = _candidate_from_match(text, spec, match)
                if entity.end > entity.start:
                    candidates.append(entity)
        return resolve_overlaps(candidates)