Spaces:
Running
Running
| """Span-level validators for GLiNER2 post-processing. | |
| Mirrors the GLiNER2 RegexValidator(pattern, exclude=True) concept but operates | |
| on already-extracted span dicts (list of {label, start, end, text, score}). | |
| Usage: | |
| from evaluation.validators import MASKING_VALIDATOR, NUMERIC_MASK_VALIDATOR | |
| spans = MASKING_VALIDATOR.filter(spans) | |
| spans = NUMERIC_MASK_VALIDATOR.filter(spans) | |
| # Or combined convenience call: | |
| spans = apply_span_validators(spans) | |
| """ | |
| import re | |
| from typing import Dict, List, Optional, Set | |
| class SpanRegexValidator: | |
| """Exclude (or keep) span dicts whose ``text`` field matches a regex pattern. | |
| Mirrors ``RegexValidator(pattern, mode, exclude, flags)`` from the GLiNER2 | |
| schema API, but applied to a flat list of span dicts rather than a schema | |
| field pipeline. | |
| Args: | |
| pattern: Regex pattern string or compiled ``re.Pattern``. | |
| mode: ``"full"`` (re.fullmatch) or ``"partial"`` (re.search). | |
| exclude: When ``True`` (default), spans that *match* are removed. | |
| When ``False``, spans that do *not* match are removed. | |
| flags: ``re.RegexFlag`` value used when *pattern* is a string. | |
| labels: If given, validator only applies to spans whose ``label`` | |
| is in this set. Spans with other labels pass through unchanged. | |
| """ | |
| def __init__( | |
| self, | |
| pattern: str, | |
| mode: str = "partial", | |
| exclude: bool = True, | |
| flags: int = 0, | |
| labels: Optional[Set[str]] = None, | |
| ): | |
| if isinstance(pattern, str): | |
| self._re = re.compile(pattern, flags) | |
| else: | |
| self._re = pattern | |
| self._mode = mode | |
| self._exclude = exclude | |
| self._labels: Optional[Set[str]] = ( | |
| {lbl.upper() for lbl in labels} if labels is not None else None | |
| ) | |
| def _matches(self, text: str) -> bool: | |
| if self._mode == "full": | |
| return bool(self._re.fullmatch(text)) | |
| return bool(self._re.search(text)) | |
| def validate(self, span: Dict) -> bool: | |
| """Return ``True`` if the span should be *kept*.""" | |
| label = str(span.get("label", "")).upper() | |
| if self._labels is not None and label not in self._labels: | |
| return True # not in scope → keep unconditionally | |
| text = str(span.get("text") or span.get("value") or "") | |
| matched = self._matches(text) | |
| return not matched if self._exclude else matched | |
| def filter(self, spans: List[Dict]) -> List[Dict]: | |
| """Return only spans that pass this validator.""" | |
| return [s for s in spans if self.validate(s)] | |
| # --------------------------------------------------------------------------- | |
| # Numeric / identifier labels that may be masked with "XX…" placeholders | |
| # --------------------------------------------------------------------------- | |
| _NUMERIC_LABELS: Set[str] = { | |
| "CARD_NUMBER", | |
| "PHONE", | |
| "PIN", | |
| "CVV", | |
| "TIN", | |
| "BANK_ACCOUNT", | |
| "IBAN", | |
| "SWIFT", | |
| "WALLET", | |
| "IP", | |
| "ZIP_CODE", | |
| } | |
| # --------------------------------------------------------------------------- | |
| # Pre-built validators | |
| # --------------------------------------------------------------------------- | |
| # Rule 1: Any entity whose value contains two or more consecutive asterisks (**). | |
| # Pattern excludes "**", "***", "****", etc. | |
| MASKING_VALIDATOR = SpanRegexValidator( | |
| pattern=r"\*{2,}", | |
| mode="partial", | |
| exclude=True, | |
| labels=None, # applies to ALL labels | |
| ) | |
| # Rule 2: Numeric/identifier labels whose value contains two or more consecutive | |
| # uppercase X's (XX, XXX, XXXX …) — masked placeholders like "XXXX-XXXX". | |
| NUMERIC_MASK_VALIDATOR = SpanRegexValidator( | |
| pattern=r"X{2,}", | |
| mode="partial", | |
| exclude=True, | |
| flags=0, # case-sensitive: only uppercase X triggers the rule | |
| labels=_NUMERIC_LABELS, | |
| ) | |
| # Ordered list applied by apply_span_validators() | |
| _DEFAULT_VALIDATORS: List[SpanRegexValidator] = [ | |
| MASKING_VALIDATOR, | |
| NUMERIC_MASK_VALIDATOR, | |
| ] | |
| def apply_span_validators( | |
| spans: List[Dict], | |
| validators: Optional[List[SpanRegexValidator]] = None, | |
| ) -> List[Dict]: | |
| """Run all validators over *spans* in order, returning only passing spans. | |
| Args: | |
| spans: Flat list of span dicts ({label, start, end, text, score}). | |
| validators: Override the default validator list. ``None`` → use the | |
| module-level ``_DEFAULT_VALIDATORS``. | |
| """ | |
| active = _DEFAULT_VALIDATORS if validators is None else validators | |
| result = spans | |
| for v in active: | |
| result = v.filter(result) | |
| return result | |