Source code for aeat.domain.calculations.registry._validate_semantic_role_required
"""Required-role hard-flip gate and cross-reference accessor.Enforces that every casilla whose label matches a registered patternin :data:`_REQUIRED_ROLE_LABEL_PATTERNS` declares the expected``semantic_role``. A miss-declared or absent declaration is a hard:class:`~aeat.domain.calculations.registry._schema.ModeloDefinition`validation failure surfaced by:func:`_validate_required_role_declarations`.Also exposes :func:`collect_casillas_by_semantic_role`, the publiccross-reference accessor used by downstream consumers to walk everycasilla sharing a semantic role across the registry corpus."""from__future__importannotationsimportrefromcollectionsimportdefaultdictfromcollections.abcimportIterable,Mappingfrom._idsimportCasillaId,ModeloId,RevisionIdfrom._schemaimportModeloDefinitiontypeSemanticRoleCasillaOccurrence=tuple[ModeloId,RevisionId,CasillaId]# Enforced semantic_role requirements: each entry is (label_pattern,# expected_role). A casilla whose label matches the pattern must declare# the expected semantic_role; missing declarations raise# RegistryValidationError at snapshot build. The set starts conservative# — only patterns where corpus rollout is provably complete should land# here. Modellers extending this set must run a discovery audit first to# confirm all in-corpus casillas already carry the role.## Today's enforcement set:# - "Ejercicio al que se refiere la declaracion" -> filing_year# (16 casillas covered across 13 modelos, complete rollout# per the role-rollout-strategy audit)._REQUIRED_ROLE_LABEL_PATTERNS:tuple[tuple[re.Pattern[str],str],...]=((re.compile(r"^Ejercicio al que se refiere la declaracion$",re.IGNORECASE),"filing_year"),# Exact-match "Resultado a ingresar" (not "...o a devolver" or# "...de autoliquidaciones anteriores"; those carry distinct# semantics - signed cuota vs. prior-period balance - and would# need their own roles).(re.compile(r"^Resultado a ingresar$",re.IGNORECASE),"cuota_a_ingresar"),# M100 IRPF base surfaces: the general base and special-regime# imputed base are distinct roles despite similar labels.(re.compile(r"^Base imponible general\s*$",re.IGNORECASE),"irpf_base_imponible_general"),(re.compile(r"^Base imponible imputada\s*$",re.IGNORECASE),"irpf_re_agrup_interes_economico_base_imponible_imputada",),# "Base imponible o importe..." / "Base imponible o importes# rectificados" - M349 intracomunitario amount + rectifications.(re.compile(r"^Base imponible o importe",re.IGNORECASE),"base_intracomunitaria"),# "Base imponible negativa o cero" - M200 IS carry-forward.(re.compile(r"^Base imponible negativa o cero",re.IGNORECASE),"base_imponible_negativa_is"),)def_validate_required_role_declarations(modelos:Iterable[ModeloDefinition],)->tuple[str,...]:"""Hard-flip: every casilla matching a required-role label pattern must declare that role. Each entry in :data:`_REQUIRED_ROLE_LABEL_PATTERNS` names a label pattern plus the canonical role expected on every matching :class:`CasillaDefinition`. A miss-declared casilla (wrong role or missing role) is a snapshot-build failure. Start the set narrow and widen as role rollouts complete. """failures:list[str]=[]formodeloinmodelos:forrevisioninmodelo.revisions.values():forcasillainrevision.casillas:forpattern,expected_rolein_REQUIRED_ROLE_LABEL_PATTERNS:ifnotpattern.match(casilla.label):continueifcasilla.semantic_roleisNone:failures.append(f"required-role gate: casilla "f"{modelo.id}.{revision.id}.{casilla.id} label "f"{casilla.label!r} matches pattern {pattern.pattern!r} "f"but declares no semantic_role (expected "f"{expected_role!r})",)elifcasilla.semantic_role!=expected_role:failures.append(f"required-role gate: casilla "f"{modelo.id}.{revision.id}.{casilla.id} label "f"{casilla.label!r} matches pattern {pattern.pattern!r} "f"but declares semantic_role "f"{casilla.semantic_role!r} (expected "f"{expected_role!r})",)returntuple(failures)
[docs]defcollect_casillas_by_semantic_role(modelos:Iterable[ModeloDefinition],)->Mapping[str,tuple[SemanticRoleCasillaOccurrence,...]]:"""Cross-reference accessor: role -> tuple of (modelo_id, revision_id, casilla_id). Used by downstream consumers that need to walk every casilla sharing a semantic role across the corpus. The returned mapping is immutable and document-order stable per role; the validator consumes the same accessor through :func:`~aeat.domain.calculations.registry._validate_semantic_roles._collect_role_observations` internally. Args: modelos: Iterable of :class:`ModeloDefinition` instances to index by the semantic roles declared on their casillas. """grouped:dict[str,list[SemanticRoleCasillaOccurrence]]=defaultdict(list)formodeloinmodelos:forrevisioninmodelo.revisions.values():forcasillainrevision.casillas:ifcasilla.semantic_roleisNone:continuegrouped[casilla.semantic_role].append((modelo.id,revision.id,casilla.id))return{role:tuple(occs)forrole,occsingrouped.items()}