aeat.application.calculations._maritime_exemption_service module

Application-layer service for maritime worker IRPF exemption resolution.

Bridges the domain maritime exemption engine with the application observation pipeline. The service accepts resolved MaritimeWorkerFacts and income inputs, evaluates the Art. 7.p) and REBECA selectors, delegates calculation to calculate_art_7p_exemption() and calculate_rebeca_exemption(), and returns typed CasillaObservation rows alongside a derived flat casilla_values mapping.

The flat mapping is for human readability; the typed observation tuple is the canonical contract per aeat-calculation-grounding because it carries legal_refs and source_refs from the registry binding entries.

Calling conventions:

result = resolve_maritime_exemption(
    facts=MaritimeWorkerFacts(
        worker_class="trabajador_del_mar",
        vessel_flag="foreign",
        waters_type="international",
    ),
    annual_salary=Decimal("36500"),
    qualifying_days=180,
)
# result.observations: tuple of CasillaObservation with legal_refs/source_refs
# result.casilla_values: {casilla_id: Decimal} derived view

Error handling:

MaritimeExemptionInactiveError  - DA 41 selector resolved True (inactive)
ProfileCompletenessError        - RETMAR mandatory filing gate triggered
RentaValidationError            - input validation failed

Callers are responsible for catching ProfileCompletenessError, surfacing its message to the operator, and continuing processing. It is not a blocking calculation error.

class MaritimeExemptionResult(**data)[source]

Bases: BaseModel

Typed result for a maritime worker exemption resolution.

Carries the ordered tuple of CasillaObservation rows with full legal/source provenance and a derived flat mapping for human readability. The observations field is the canonical contract; callers must not persist or transmit only the flat casilla_values view.

Parameters:
observations: tuple[CasillaObservation, ...]
retmar_mandatory_filing: bool
property casilla_values: Mapping[CasillaId, Decimal]

Derived flat view from canonical observations.

Returns a Mapping[CasillaId, Decimal] for display and operator preview paths. The canonical storage is observations; this view must not be used for persistence or wire payloads because it omits CasillaObservation provenance.

resolve_maritime_exemption(*, facts, annual_salary=None, qualifying_days=None, gross_navigation_income=None)[source]

Resolve the applicable maritime exemption pathway and produce typed observations.

Evaluates art_7p_eligible() and rebeca_eligible() in order. For each eligible pathway the corresponding domain calculation function is called and its CasillaObservation is appended to the result.

The DA 41 inactive guard runs before any calculation: if the DA 41 selector resolves true (currently when tuna_fleet and pending_eu_clearance are both set on a trabajador del mar profile), MaritimeExemptionInactiveError is raised and no observations are produced.

The RETMAR mandatory-filing gate is checked independently through check_retmar_mandatory_filing(). When retmar_registered is true, ProfileCompletenessError is raised. Callers should catch it, surface the message to the operator, and continue processing. The gate does not suppress exemption calculations.

Parameters:
  • facts (MaritimeWorkerFacts) – Resolved MaritimeWorkerFacts from the user profile.

  • annual_salary (Decimal | None) – Gross annual salary in EUR. Required when Art. 7.p) is potentially eligible.

  • qualifying_days (int | None) – Days of work outside Spain in the tax year. Required when Art. 7.p) is potentially eligible.

  • gross_navigation_income (Decimal | None) – Total gross navigation income in EUR. Required when REBECA is potentially eligible.

Return type:

MaritimeExemptionResult

Returns:

MaritimeExemptionResult with typed observations and flat view.

Raises:

RentaValidationError – Eligibility predicate mismatch or invalid input. The DA 41 inactive guard and RETMAR completeness gate raise their own typed exceptions from the called guards.