Source code for cadrumo.domain.calculations.registry.m303_orden_constants

"""Shared shape constants and coordinate validation for annual Orden authority."""

from __future__ import annotations

from decimal import Decimal
from typing import Protocol

from .errors import RegistryValidationError
from .ids import RevisionId, SourceRefId

EXPECTED_ACTIVITY_COUNT = 49
EXPECTED_MODULE_COUNT = 141
EXPECTED_MODULE_DISTRIBUTION = {1: 2, 2: 25, 3: 12, 4: 4, 5: 1, 6: 3, 7: 2}
EXPECTED_MODULE_DISTRIBUTION_VECTOR = (2, 25, 12, 4, 1, 3, 2)
EXPECTED_NON_AGRICULTURAL_INGRESO_A_CUENTA_COUNT = 47
EXPECTED_AGRICULTURAL_AXIS_COUNTS = {2022: 16, 2023: 16, 2024: 16, 2025: 17, 2026: 17}
#: Shape bounds for the temporada corrector bands, NOT their values. The
#: coefficients and day bands are fixed by each annual Orden and are already
#: grounded in the census by its own required_text; asserting them here would
#: make a lawful change to a coefficient indistinguishable from a corrupt
#: extraction, and the validator would refuse the new law. What is safe to
#: assert is that the bands remain a contiguous ascending partition carrying
#: positive coefficients, which a garbled extraction would violate.
MINIMUM_SEASONAL_INDEX_BANDS = 1
FIRST_SEASONAL_INDEX_DAY = 1
EXTRACTOR_VERSION = "m303-annual-orden-html-v7"


class _GeneratedSourceShape(Protocol):
    ejercicio: int
    activity_table_count: int
    module_row_count: int
    module_distribution: tuple[int, ...]
    agricultural_index_row_count: int
    agricultural_ingreso_a_cuenta_row_count: int
    non_agricultural_ingreso_a_cuenta_row_count: int
    seasonal_index_day_bands: tuple[tuple[int, int], ...]
    seasonal_index_coefficients: tuple[Decimal, ...]
    difficult_justification_pct: Decimal
    lorca_reduction_pct: Decimal | None


[docs] def validate_generated_source_counts(source: _GeneratedSourceShape) -> None: """Validate generated M303 source counts against structural expectations.""" expected_agricultural_count = EXPECTED_AGRICULTURAL_AXIS_COUNTS.get(source.ejercicio) if source.activity_table_count != EXPECTED_ACTIVITY_COUNT: raise RegistryValidationError("annual Orden manifest has the wrong activity table count") if source.module_row_count != EXPECTED_MODULE_COUNT: raise RegistryValidationError("annual Orden manifest has the wrong module row count") if source.module_distribution != EXPECTED_MODULE_DISTRIBUTION_VECTOR: raise RegistryValidationError("annual Orden manifest has the wrong module distribution") if ( expected_agricultural_count is None or source.agricultural_index_row_count != expected_agricultural_count or source.agricultural_ingreso_a_cuenta_row_count != expected_agricultural_count ): raise RegistryValidationError("annual Orden manifest has the wrong agricultural axis row count")
[docs] def validate_generated_source_axis_shape(source: _GeneratedSourceShape) -> None: """Validate that generated M303 source axes have the required shape.""" if source.non_agricultural_ingreso_a_cuenta_row_count != EXPECTED_NON_AGRICULTURAL_INGRESO_A_CUENTA_COUNT: raise RegistryValidationError("annual Orden manifest has the wrong IAE ingreso-a-cuenta row count") validate_seasonal_index_shape( source.seasonal_index_day_bands, source.seasonal_index_coefficients, scope="manifest", ) validate_percentage_shape( source.difficult_justification_pct, scope="manifest", subject="difficult-justification", ) if source.lorca_reduction_pct is not None: validate_percentage_shape(source.lorca_reduction_pct, scope="manifest", subject="Lorca reduction")
[docs] def validate_seasonal_index_shape( day_bands: tuple[tuple[int, int], ...], coefficients: tuple[Decimal, ...], *, scope: str, ) -> None: """Refuse a temporada corrector table that is structurally impossible. Asserts SHAPE, never the legal figures: the bands must be a contiguous ascending partition starting at day one, each band non-empty, with one positive coefficient apiece. A future Orden may change any coefficient or band boundary and still satisfy this; a garbled extraction cannot. Raises: RegistryValidationError: If the table is empty, misaligned, or carries a non-positive coefficient. """ if len(day_bands) < MINIMUM_SEASONAL_INDEX_BANDS: raise RegistryValidationError(f"annual Orden {scope} declares no seasonal index band") if len(day_bands) != len(coefficients): raise RegistryValidationError(f"annual Orden {scope} seasonal bands and coefficients disagree in length") expected_start = FIRST_SEASONAL_INDEX_DAY for start, end in day_bands: if start != expected_start: raise RegistryValidationError(f"annual Orden {scope} seasonal index bands are not contiguous") if end < start: raise RegistryValidationError(f"annual Orden {scope} seasonal index band ends before it starts") expected_start = end + 1 if any(coefficient <= 0 for coefficient in coefficients): raise RegistryValidationError(f"annual Orden {scope} seasonal index coefficient is not positive")
[docs] def validate_percentage_shape(percentage: Decimal, *, scope: str, subject: str) -> None: """Refuse a percentage outside the only range a percentage can occupy. Asserts SHAPE, never the legal figure, for the same reason as :func:`validate_seasonal_index_shape`. Raises: RegistryValidationError: If ``percentage`` is negative or above 100. """ if percentage < 0 or percentage > 100: raise RegistryValidationError(f"annual Orden {scope} {subject} percentage is outside 0-100")
_M303_2022_REVISION_ID = "2022" _M303_2022_ORDEN_SOURCE_REF = "boe-orden-hfp-1335-2021-iva-authority" _M303_2022_ORDEN_SOURCE_DIGEST = "3fda96dcf2dcb3b3f0863bc07b0eabd45e21c6850d4b611e635627befb450c46"
[docs] def validate_2022_annual_orden_coordinate( *, ejercicio: int, registry_revision_id: RevisionId, source_ref: SourceRefId, source_content_digest: str, scope: str, ) -> None: """Require the pinned BOE/revision coordinate for 2022 annual authority.""" if not ( ejercicio == 2022 and registry_revision_id == _M303_2022_REVISION_ID and source_ref == _M303_2022_ORDEN_SOURCE_REF and source_content_digest == _M303_2022_ORDEN_SOURCE_DIGEST ): raise RegistryValidationError(f"annual Orden 2022 {scope} must retain its exact BOE/revision coordinate")
__all__ = [ "EXPECTED_ACTIVITY_COUNT", "EXPECTED_AGRICULTURAL_AXIS_COUNTS", "EXPECTED_MODULE_COUNT", "EXPECTED_MODULE_DISTRIBUTION", "EXPECTED_MODULE_DISTRIBUTION_VECTOR", "EXPECTED_NON_AGRICULTURAL_INGRESO_A_CUENTA_COUNT", "EXTRACTOR_VERSION", "FIRST_SEASONAL_INDEX_DAY", "MINIMUM_SEASONAL_INDEX_BANDS", "validate_2022_annual_orden_coordinate", "validate_generated_source_axis_shape", "validate_generated_source_counts", "validate_percentage_shape", "validate_seasonal_index_shape", ]