cadrumo.application.aggregation._modelo_bindings_invoice_iva module

Invoice-to-ledger IVA screening for modelo binding resolution.

The calculation-facing ledger resolver remains in ._modelo_bindings. This sibling owns the supporting invoice evidence comparison, category and recargo diagnostics, and candidate screening required by that resolver.

line_contributes_to_the_iva_screen(base_amount, iva_amount)[source]

Return whether one invoice line has anything the IVA screen can declare.

A line contributes when it carries a base OR a cuota. Screening on the cuota alone reads as a sensible “nothing to declare” filter and is not one: an exempt operation (LIVA art. 20), an intra-community supply (art. 25) and an issued-side reverse charge all carry a real base with a cuota that is zero BY LAW. The component table says so outright – both categories are base=required, cuota=zero_by_law – and Modelo 303 declares those bases in its own base-only casillas.

So a cuota-only filter dropped exactly the lines whose base was the only thing they were ever going to contribute, and the declaration understated the exempt base by the whole amount with nothing reporting it.

A line carrying neither is the only shape that genuinely contributes nothing, and is the only one this predicate declines.

This predicate alone does not make an exempt base reach Modelo 303, and was mistakenly reported as doing so. The loop classifies from the RATE SLOT, so an exempt line becomes domestic_exempt/exempt while casilla 59 selects intra_community_supply/zero and casilla 60 the two export categories – a miss on both axes. No M303 binding selects domestic_exempt or domestic_zero, so such an observation routes nowhere rather than into a wrong casilla; the effect is inert, not harmful.

Keeping the line is a PRECONDITION of the real fix, not the fix. The real fix is to construct the observation from the invoice’s own iva_category the way the bank-transaction path already does (_iva_ledger.py: explicit category first, rate-derived domestic category only as a fallback, with the intracom/export counterparty gate). Two feeds populating one binding source with divergent logic is what one-aggregation-path-pull-equals-calculate exists to prevent.

Return type:

bool

Parameters:
category_counterparty_mismatch_diagnostics(invoices, *, resolver_id)[source]

Return one advisory per invoice withheld for a category the counterparty contradicts.

Withholding the volume is correct: an intra-community supply to a third country is not an intra-community supply whatever it claims, and routing it on the category alone would declare volume the taxpayer never supplied that way. What was missing is the operator being told, so a real operation left a declaration with nothing on any surface saying so.

The remedy names both fields, because either could be the wrong one – the category may be mis-tagged, or the counterparty country may be. The record does not know which, and guessing would point the operator at the wrong fix.

Return type:

tuple[CalculationSourceDiagnostic, ...]

Parameters:
reverse_charge_underivable_diagnostics(invoices, *, resolver_id)[source]

Return one advisory per received reverse charge whose cuota cannot be derived.

The operation IS declarable and the operator is liable for the cuota; what is missing is the rate to compute it against. Naming the invoice and the missing fact is what turns a silently short return into one the operator can correct, and it makes the population countable – which is what a decision about whether an invoice line may carry a rated slot with a zero cuota needs in order to be made on evidence.

Return type:

tuple[CalculationSourceDiagnostic, ...]

Parameters:
missing_invoice_deduction_authority_diagnostics(invoices, *, resolver_id)[source]

Report received invoice evidence that reached no IVA input row.

An invoice has an amount and a direction, but not the statutory deduction fact family nor the immutable evidence provenance that distinguishes a domestic current expense from an investment, import, acquisition, or rectification. Treating every received invoice as domestic-current would invent that authority, so the invoice is withheld instead.

Non-blocking by the time it reaches here: the enclosing guard has already established that the transaction ledger carries this cuota, so the filing’s totals are correct and only the invoice-to-transaction link is absent. Where the ledger does NOT carry it the guard refuses outright and this diagnostic is never reached.

Return type:

tuple[CalculationSourceDiagnostic, ...]

Parameters:
recargo_rate_mismatch_diagnostics(divergences, *, resolver_id)[source]

Return one advisory per invoice whose recargo departs from the published rate.

Advisory, never a refusal, and the filed figure is unchanged whether or not this fires. A legitimate invoice can carry a figure the table does not predict, so refusing here would block a correct filing on a correct invoice.

The message names both figures and the provision. Without the provision the advisory reads as the application second-guessing the supplier, and an operator who cannot see what authority is being cited will learn to dismiss it – which costs more than never having emitted it.

Return type:

tuple[CalculationSourceDiagnostic, ...]

Parameters:
  • divergences (Sequence[_RecargoRateDivergence])

  • resolver_id (str)

class ScreenedInvoiceIva(observations=(), invoice_ids=(), compared=(), category_counterparty_mismatches=(), reverse_charge_underivable=(), deduction_authority_missing=(), recargo_rate_divergences=(), storage_degraded=False)[source]

Bases: object

What the invoice IVA screen found, named rather than positional.

Several fields are tuple[Invoice, ...] and one more is a tuple of ids, so as a positional return they would be mutually substitutable and a type checker could not tell a mis-ordering from correct code. That is not hypothetical: the tuple was widened repeatedly as findings landed, the annotation fell out of step with the returns on the way, and every widening broke unpack sites in unrelated test modules.

Variables:
  • observations – the IVA observations the screen built.

  • invoice_ids – source invoice ids behind those observations.

  • compared – invoices whose IVA was compared against the ledger, so the caller can disclose how their period placement was arrived at.

  • category_counterparty_mismatches – invoices withheld because the declared category and the counterparty country contradict each other.

  • reverse_charge_underivable – invoices declaring a reverse charge whose line carries no rate slot, so no cuota can be derived without inventing one.

  • deduction_authority_missing – received invoices whose lines contribute IVA evidence but which are linked to no frozen transaction-ledger observation carrying the exact deduction family and immutable evidence provenance an input row requires, or whose linked observations disagree about it.

  • recargo_rate_divergences – invoices whose recorded recargo departs from the rate art. 161 publishes for that slot. Unlike the two above, these are NOT withheld – the figure is declared exactly as recorded and the advisory is a cross-check beside it.

Parameters:
observations: tuple[IvaLedgerObservation, ...]
invoice_ids: tuple[str, ...]
compared: tuple[Invoice, ...]
category_counterparty_mismatches: tuple[Invoice, ...]
reverse_charge_underivable: tuple[Invoice, ...]
deduction_authority_missing: tuple[Invoice, ...]
recargo_rate_divergences: tuple[_RecargoRateDivergence, ...]
storage_degraded: bool

The catalogue could not be READ, as distinct from holding no invoices. Without this the two are the same value downstream, and the silence guard returns as though it had compared a catalogue it never saw.

class InvoiceIvaSilenceReport(compared=(), category_counterparty_mismatches=(), reverse_charge_underivable=(), deduction_authority_missing=(), recargo_rate_divergences=(), storage_degraded=False)[source]

Bases: object

The advisory surfaces the silence screen hands back to the resolver.

Same reasoning as ScreenedInvoiceIva: all three fields are tuple[Invoice, ...], so positionally they were interchangeable. The annotation on the previous tuple form had already drifted to two slots while the code returned three, which is what an unchecked positional widening looks like just before it goes wrong.

Parameters:
compared: tuple[Invoice, ...]
category_counterparty_mismatches: tuple[Invoice, ...]
reverse_charge_underivable: tuple[Invoice, ...]
deduction_authority_missing: tuple[Invoice, ...]

Received invoices withheld for want of a linked ledger deduction authority, whose cuota the transaction ledger nonetheless already carries. Not a refusal – the filing’s totals are right – but the operator still needs telling, or the unlinked invoice looks reconciled when it is not.

recargo_rate_divergences: tuple[_RecargoRateDivergence, ...]

Carried on every return path, including the two early ones. A divergence is a fact about the recorded figure, not about whether the screen went on to build an observation, so dropping it when the screen returns early would silence the advisory exactly when the invoice is least examined.

storage_degraded: bool

The screen could not read the invoice catalogue, so it reached NO verdict about whether invoice IVA is absent from the ledger totals. Distinct from a clean pass, which is what a silent empty return looked like.

screened_invoice_iva_observations(*, context, period, ledger_observations=(), ports, operation=None)[source]
Return type:

ScreenedInvoiceIva

Parameters:
out_of_window_summary_diagnostics(summary, *, source_kind, resolver_id)[source]
Return type:

tuple[CalculationSourceDiagnostic, ...]

Parameters: