cadrumo.domain.iva.invoice_classification module

IVA classification record for the invoice-import bridge to the ledger.

Bridges the substrate’s three IVA classification axes (IvaCategory, IvaRateKind, IvaFlowDirection) into one frozen pydantic record. The record captures:

  • The operation kind (domestic / intra-community / recargo / OSS / etc.).

  • The applicable rate tier (general / reduced / super-reduced / zero / exempt).

  • The flow direction (output / input / self-assessed reverse charge).

  • The derived settlement-side classification (devengada and / or deducible) — pre-computed at construction time so consumers don’t have to call settlement_sides_for_flow() repeatedly.

Scope: this record is the standard-case construction path for invoice lines specifically — classify_invoice_line_for_iva() derives it from an IvaRate plus the invoice direction (issued / received), and invoice_line_to_iva_observation() is the sole production caller, feeding the invoice-derived Modelo 303 observations in cadrumo.application.aggregation.modelo_bindings. For reverse-charge, intra-community, OSS / IOSS, and other non-domestic invoice cases, callers construct the record directly with the appropriate IvaCategory from the substrate’s classifier output (classify_iva()).

This is NOT the construction path for the general ledger-transaction pipeline. Bank-transaction-derived IVA observations (payment splits, expense-report lines, OSS / IOSS observations, recargo de equivalencia entries resolved from already-classified ledger rows) are built directly as IvaLedgerObservation in cadrumo.application.aggregation.iva_ledger, whose category, rate, and flow axes are resolved upstream (manual or LLM classification) rather than re-derived from an IvaRate + direction pair — that pipeline never constructs an IvaInvoiceClassification.

class IvaInvoiceClassification(**data)[source]

Bases: BaseModel

Frozen pydantic record bundling the IVA classification triple and derived settlement sides.

Variables:
  • category – Substrate IvaCategory classifying the operation kind.

  • rate_kind – Substrate IvaRateKind rate tier; None for operations outside the scope of IVA (out-of-scope, ERRONEOUS_INVOICE, UNKNOWN).

  • flow_direction – Substrate IvaFlowDirection — REPERCUTIDO (output), SOPORTADO (input), or INVERSION_SUJETO_PASIVO (self-assessed reverse charge).

  • settlement_sides – Pre-computed frozen set of the IvaSettlementSide cornerstones the line contributes to. Derived from flow_direction at construction time via settlement_sides_for_flow() so consumers don’t recompute it.

Parameters:

data (Any)

category: IvaCategory
rate_kind: IvaRateKind | None
flow_direction: IvaFlowDirection
settlement_sides: frozenset[IvaSettlementSide]
classify_invoice_line_for_iva(*, iva_rate, invoice_kind, on_date=None)[source]

Build a classification record for the standard domestic-IVA case.

Covers the most common autónomo operation: a domestic invoice issued to a Spanish customer or received from a Spanish supplier at one of the ordinary IVA rate slots (or a registry-declared exempt slot). The record’s flow direction is derived from invoice_kind:

  • InvoiceKind.ISSUED → IvaFlowDirection.REPERCUTIDO (the autónomo charged output IVA on a sale).

  • InvoiceKind.RECEIVED → IvaFlowDirection.SOPORTADO (the autónomo bore input IVA on a purchase).

For reverse-charge, intra-community, export, import, recargo de equivalencia, and OSS / IOSS cases, callers construct IvaInvoiceClassification directly with the appropriate IvaCategory from the substrate classifier (cadrumo.domain.iva.classification.classify_iva()).

recargo_amount defaults to zero because most lines carry none. It is a parameter here rather than something a caller sets on the returned observation, so this stays the ONE bridge from invoice metadata into the observation shape: a caller that constructed the record itself to add a recargo would be a second construction path for the same concept, free to drift from this one’s classification rules.

Parameters:
  • iva_rate (IvaRate) – One of the registry-governed IvaRate slots. The out-of-scope slot is rejected — see the module docstring for the rationale.

  • invoice_kind (InvoiceKind) – Whether the invoice was issued (sale) or received (purchase).

  • on_date (date | None) – Date selecting the registry rate-tier table; today when omitted, matching the rate-slot tier projection.

Return type:

IvaInvoiceClassification

Returns:

A frozen IvaInvoiceClassification with the derived substrate triple and pre-computed settlement-side set.

Raises:

InvoiceValidationError – If iva_rate resolves without a rate-tier classification and cannot be handled by the standard-case helper.

invoice_line_to_iva_observation(*, invoice_id, issued_at, invoice_kind, iva_rate, base_amount, iva_amount, deduction_fact_kind, deduction_provenance, recargo_amount=Decimal('0'), investment_asset_id=None, rectifies_ledger_id=None)[source]

Build an IvaLedgerObservation from invoice line metadata.

The runtime resolver for the substrate’s ledger_iva_aggregation binding source kind consumes cadrumo.domain.calculations.registry.ledger_iva_bindings.IvaLedgerObservation records. This helper turns invoice-line metadata into the observation shape the modelo registry expects, applying the standard-case classification (domestic IVA, REPERCUTIDO for issued invoices, SOPORTADO for received). Reverse-charge and intra-community lines must construct the observation directly.

The function is the canonical ledger → modelo bridge for the standard-case IVA flows. Callers iterate the invoice’s lines, call this helper for each, and pass the resulting tuple to resolve_ledger_iva_aggregation_binding_values() to populate the modelo’s binding values.

Parameters:
  • invoice_id (str) – Stable id of the source invoice (becomes ledger_id on the observation).

  • issued_at (date) – Invoice issue date (becomes transaction_date).

  • invoice_kind (InvoiceKind) – Whether the invoice was issued or received.

  • iva_rate (IvaRate) – Registry-governed IvaRate slot for the line. An out-of-scope slot raises (the substrate-NULL category needs explicit construction).

  • base_amount (Decimal) – Taxable base in EUR.

  • iva_amount (Decimal) – IVA amount in EUR.

  • deduction_fact_kind (IvaDeductionFactKind | None) – Exact statutory deduction family for a received IVA input, or None for an issued output.

  • deduction_provenance (IvaDeductionClassificationProvenance | None) – Immutable evidence provenance for a received IVA input, or None for an issued output.

  • recargo_amount (Decimal) – Recargo de equivalencia charged on this line in EUR (LIVA art. 161), or zero when none was. Routed to the Modelo 303 recargo cuota casilla for the line’s rate tier via the recargo_amount_sum fact.

  • deduction_fact_kind – Exact evidence-grounded deduction family for a received line. Output lines must leave it unset.

  • deduction_provenance – Immutable authority for the received line’s deduction family. Output lines must leave it unset.

  • investment_asset_id (str | None) – Reciprocal investment-register identity when the deduction family is an investment acquisition.

  • rectifies_ledger_id (str | None) – Corrected ledger identity for a rectification.

Return type:

IvaLedgerObservation

Returns:

An IvaLedgerObservation with the full classification triple ready for binding-resolver consumption.

Raises:
  • InvoiceValidationError – If the classification produces a None rate_kind (e.g. when iva_rate is outside the taxable scope).

  • IvaRateNotFoundError – If iva_rate names a rate that was not in force for its tier on issued_at – a transitional food slot used outside its statutory window. The line asserts a rate the statute did not offer that day, so it is refused rather than recorded at whatever the tier happened to mean.