cadrumo.domain.calculations.registry.binding_selector_utils module

Shared selector normalization and field-validator helpers for registry bindings.

class BindingFixedExportSelector(**data)[source]

Bases: BaseModel

Typed fixed-width export projection carried by a binding selector.

Parameters:

data (Any)

record: str
offset: OneBasedExportOffset
length: int
data_type: BindingExportDataType
decimals: int | None
field: str | None
signed: bool

Whether the wire slot carries AEAT’s sign marker in its first position.

AEAT types a numeric design row N (numérico CON signo) or Num (sin signo), and an N row reserves position 1 for the marker: N for a negative value, a space otherwise. The two renderings differ for EVERY value, not only negatives – a 17-byte signed slot emits a space plus 16 magnitude digits where an unsigned one emits 17 digits – so a binding projecting into an N row must declare this or the slot is malformed on the wire and refuses outright on a negative.

class BindingRowExportSelector(**data)[source]

Bases: BaseModel

Typed row-field export projection carried by a binding selector.

data_type carries the same fact here as on BindingFixedExportSelector: the scalar type of the value this binding contributes to the export. The two projections differ only in how the value is POSITIONED – a fixed projection names an absolute offset/length, a row projection takes its position from the repeated record’s row layout – so the type belongs on both and means one thing.

Optional because the declaration is being adopted per family; absent leaves the consumer on whatever it used before.

Parameters:

data (Any)

record: str
row_field: str
data_type: BindingExportDataType | None
class BindingRowSetSelector(**data)[source]

Bases: BaseModel

Typed row-set projection carried by a row-producing binding selector.

Parameters:

data (Any)

fact: Literal['row_field']
row_field: str
grouping: str
record: str | None
selector_as_dict(binding)[source]

Return the provider’s authored field mapping without its own discriminator.

The provider’s top-level kind is excluded because it is the union tag, not a selector field: the per-family models this mapping is re-validated against accept it as a defaulted literal, and a family helper that reasoned over the tag as data would be reading the discriminator twice.

A nested union member’s tag is restored instead of dropped, because that mapping has no other way to say which member it is; see _restore_nested_discriminators().

Return type:

dict[str, object]

Parameters:

binding (BindingDefinition)

class BooleanBindingEncodedValue(**data)[source]

Bases: BaseModel

One accepted decimal encoding of a boolean-casilla manual_input binding.

A manual_input binding whose selector declares data_type = "boolean" (the Modelo 100 estimación-directa modality flag is the canonical case) is consumed by the registry formulas as a numeric 1 / 0 operand. The operator therefore supplies a decimal on the --binding channel, yet the accepted values and their meaning are opaque from the raw BindingDefinition. This record makes one accepted value explicit: encoded_value is the decimal the operator types, boolean_meaning is the affirmative/negative sense it carries, and registry_value is the underlying casilla token the boolean maps to (the selector’s declared true_value / false_value).

Parameters:

data (Any)

encoded_value: str
boolean_meaning: bool
registry_value: str
boolean_binding_encoded_values(binding)[source]

Return the decimal encoding of a boolean-casilla manual_input binding.

The result is empty for every binding that is not a boolean-casilla manual_input selector, so a caller can read a non-empty result as “this binding is a decimal-encoded boolean flag”. The encoding follows the registry convention that a boolean operand is consumed as 1 (true) / 0 (false); each sense is paired with the selector’s declared true_value / false_value casilla token, so the mapping is derived from the binding definition, never hardcoded per modelo.

Return type:

tuple[BooleanBindingEncodedValue, ...]

Returns:

Zero or two BooleanBindingEncodedValue rows.

Raises:

RegistryValidationError – When binding is a manual_input binding whose selector does not validate against ManualInputProvider – a malformed selector must be a named failure, not a silently empty “not a boolean binding” result.

Parameters:

binding (BindingDefinition)

class ManualInputRecordFieldSelector(**data)[source]

Bases: BaseModel

The record-field shape of a validated manual_input binding selector.

ManualInputProvider models both the casilla shape and the record-field shape on one class because the two are mutually exclusive and share data_type; this narrower model is what a caller that only cares about the record-field shape (a fichero-BOE fixed-record projection) actually wants, with record/field/offset/length as the non-optional fields the source model’s own validator already guarantees once record is not None.

Parameters:

data (Any)

record: str
field: str
offset: int
length: int
decimals: int | None
manual_input_record_field_selector(binding)[source]

Return the record-field selector of a manual_input binding, or None.

None covers two DIFFERENT, both legitimate, cases a caller does not need to distinguish: binding is not a manual_input binding at all, or it is a manual_input binding using the CASILLA shape (the operator types a value straight into a registry casilla, with no record/field position at all) rather than the record-field shape this accessor projects. Neither is a defect; a caller collecting record-field projections across a revision’s bindings is meant to skip both.

Return type:

ManualInputRecordFieldSelector | None

Returns:

The typed ManualInputRecordFieldSelector, or None when binding does not declare a record-field manual_input selector.

Raises:

RegistryValidationError – When binding is a manual_input binding whose selector does not validate against ManualInputProvider – a malformed selector must be a named failure, not silently read as “not a record-field binding”.

Parameters:

binding (BindingDefinition)

binding_export_selector(binding, *, revision)[source]

Return the typed export projection embedded in binding.provider.

Binding source-family selectors remain authoritative for business facts. Export record resolution only needs the official record-coordinate projection; this helper parses that projection once into a typed fixed-field or row-field selector instead of letting callers probe the raw selector map.

A binding is only export-eligible when its OWN revision declares at least one export layout; several unrelated source families (invoice, previous_filing) reuse the field name record for their own, different concepts, so calling this on a binding belonging to a layout-less revision would misread a foreign field as an incomplete export claim. Every current caller already checks revision.export_layouts before calling; this is the same precondition enforced here too, so a future caller that forgets it fails with a named cause instead of a misattributed completeness error.

Raises:

RegistryValidationError – When revision declares no export layouts at all, or when the export projection binding declares is malformed or incomplete.

Parameters:
Return type:

BindingFixedExportSelector | BindingRowExportSelector | None

Core types: ModeloRevision.

Return type:

BindingFixedExportSelector | BindingRowExportSelector | None

Parameters:
binding_row_set_selector(binding)[source]

Return the typed row-set projection embedded in binding.provider.

Source-family selectors remain the authority for fact-specific filters. Row-set consumers only need the common fact = "row_field" projection that names the detail grouping and the emitted row field, so callers parse that projection once instead of probing the raw selector map.

Only a BindingAggregationOp.ROWS binding is row-set-eligible; several unrelated source families (invoice, previous_filing) reuse the field names record / grouping / fact for their own, different concepts, so calling this on a non-ROWS binding would misread a foreign field as an incomplete row-set claim. Every current caller already checks the aggregation op before calling; this is the same precondition enforced here too, so a future caller that forgets it fails with a named cause instead of a misattributed completeness error.

Return type:

BindingRowSetSelector | None

Returns:

The parsed BindingRowSetSelector, or None when the binding selector does not declare a row-set projection.

Raises:

RegistryValidationError – When binding is not row-set-eligible (its resolved aggregation op is not rows), or when the row-set projection it declares is malformed or incomplete.

Parameters:

binding (BindingDefinition)

provider_member(binding, provider_model)[source]

Return binding.provider narrowed to the member its kind is registered for.

The discriminated union already constructed the member from the authored row, so this is a narrowing rather than a re-validation: there is no authored input that reaches here holding a different class. A mismatch can therefore only mean the registration table and the union have drifted apart, which is refused loudly instead of being silently revalidated or skipped.

Raises:

RegistryValidationError – The constructed member is not an instance of the model its kind is registered for.

Return type:

TypeVar(ProviderT, bound= BaseModel)

Parameters:
selector_against_model(binding, selector_model)[source]

Validate binding.provider against selector_model, accumulating diagnostics.

Projects the selector through selector_as_dict() (the same normalised mapping the resolve-time helpers see, so the build gate is never stricter than runtime), validates against the strict pydantic model, and returns the underlying field message verbatim in a diagnostic naming the binding id, its source, and the violated model. The underlying pydantic error is preserved rather than flattened to a generic “malformed selector”, matching the shape the counterpart/withholding build-time lift already emits.

Returns an empty list when the selector validates.

Return type:

list[str]

Parameters:
canonical_selector_key_hint(selector, selector_model)[source]

Return a canonical-key correction for a known legacy selector spelling.

Return type:

str

Parameters:
invariant_diagnostics(binding, label, check)[source]

Run a raise-style op/fact invariant check and collect its diagnostic.

The detail-record, previous-filing, counterpart, withholding, invoice, and ledger families enforce their op/fact cross-invariants by raising RegistryValidationError. This adapter runs the raising check and converts the raised message into one accumulating diagnostic string naming the binding id, its source, and the label family, preserving the underlying field message. Returns an empty list when the invariant holds.

Return type:

list[str]

Parameters:
uppercase_alpha_code(field_label)[source]

Build a field validator that rejects a non-uppercase-alphabetic code.

Shared by the binding observation models whose ISO country / member-state / currency codes must be uppercase alphabetic; field_label names the field in the raised RegistryValidationError.

Return type:

Callable[[type, str], str]

Parameters:

field_label (str)

optional_uppercase_alpha_code(field_label)[source]

Build the nullable counterpart of uppercase_alpha_code().

Delegates to that validator for anything present, so what “uppercase alphabetic” MEANS keeps one home and this adds only the nullability axis. A separate builder rather than a flag on the shared one, because most of the fields it guards are genuinely required and loosening it for them would make an absent code representable where nothing should represent one.

Return type:

Callable[[type, str | None], str | None]

Parameters:

field_label (str)

M347_OPERATION_CLAVES: frozenset[str]

Modelo 347’s OWN clave de operacion vocabulary (RD 1065/2007 arts. 31/33, RD 1619/2012 disposicion adicional cuarta), disjoint from M349’s intracommunity clave set above – the letter A means a different thing in each. Confirmed against every bundled M347 diseno de registro (2008-2009, 2010, 2011, 2025-y-siguientes): none declares an “H” or “I” clave.

intracommunity_clave_validator()[source]

Build the shared intracommunity_clave field validator.

An invoice observation’s clave is optional, must be uppercase, and must be one of the closed AEAT clave de operación set.

Return type:

Callable[[type, str | None], str | None]

operation_clave_validator(*, field_label, claves)[source]

Build an optional, uppercase, closed-set clave-de-operacion field validator.

Generalises intracommunity_clave_validator() to any closed clave vocabulary – a modelo’s clave letters mean nothing outside their own modelo’s set, so the closed set is a parameter, never a hand-listed literal at the call site.

Return type:

Callable[[type, str | None], str | None]

Parameters:
validate_rectification_fields(observation)[source]

Enforce the rectification-field coupling shared by the invoice families.

A rectification observation must declare rectified_year, rectified_period and rectified_base_previous; a non-rectification observation must declare none of them. The shared check raises RegistryValidationError on a violation.

Return type:

None

Parameters:

observation (_RectifiableObservation)

unique_tuple(label)[source]

Build a field validator that rejects duplicate entries in a tuple field.

Shared by the binding requirement models; label names the offending tuple in the raised RegistryValidationError ("<label> entries must be unique").

Return type:

Callable[[type, tuple[str, ...]], tuple[str, ...]]

Parameters:

label (str)