Source code for aeat.domain.iva_compensation._balance
"""Pure IVA-compensation wallet balance projection for Modelo 303.Summarises a :class:`IvaCompensationCarryForwardReport` into a balance snapshot.All logic here is pure: it depends only on :mod:`decimal`, pydantic,:data:`STRICT_FROZEN_CONFIG`-style strict config, and the sibling carry-forwardrecords. The repository-backed orchestration that loads stored period states andbuilds the report lives in the application layer."""from__future__importannotationsfromdecimalimportDecimalfromtypingimportFinalfrompydanticimportBaseModel,Fieldfrom...coreimportSTRICT_FROZEN_CONFIGas_STRICT_FROZENfrom._carry_forwardimport(IvaCompensationCarryForwardReport,IvaCompensationExpiryReviewState,)_FOUR_YEAR_WINDOW:Final[int]=4
[docs]classIvaWalletBalanceReport(BaseModel):"""Aggregated IVA compensation carry-forward balance as of a reference year."""model_config=_STRICT_FROZENas_of_year:int=Field(ge=2000,le=2099)total_balance:Decimal=Field(ge=Decimal("0"))active_balance:Decimal=Field(ge=Decimal("0"))expired_balance:Decimal=Field(ge=Decimal("0"))lot_count:int=Field(ge=0)next_expiry_year:int|None=Field(default=None,ge=2000,le=2200)unallocated_applied_amount:Decimal=Field(ge=Decimal("0"))
[docs]defbuild_iva_wallet_balance_report(carry_forward:IvaCompensationCarryForwardReport,)->IvaWalletBalanceReport:"""Summarise a carry-forward report into a balance snapshot. ``total_balance`` is the gross remaining balance across all positive lots. ``active_balance`` is the portion still inside the four-year compensation window, including lots due for expiry review. ``expired_balance`` is the portion past that window and therefore not usable without separate review. ``next_expiry_year`` is ``source_filing_year + 4`` for the earliest non-expired lot that still carries a non-zero remaining balance (the lot closest to its four-year expiry boundary). ``None`` when no non-expired lots with remaining balance exist. Returns an :class:`IvaWalletBalanceReport`. """lots_with_balance=[lotforlotincarry_forward.lotsiflot.remaining_amount>Decimal("0")]expired_lots_with_balance=[lotforlotinlots_with_balanceiflot.expiry_review_stateisIvaCompensationExpiryReviewState.EXPIRED_REVIEW_REQUIRED]# Include ACTIVE and EXPIRY_REVIEW_DUE lots (age <= 4). EXPIRED_REVIEW_REQUIRED# lots (age > 4) have passed the four-year boundary and are not usable without# a separate policy review.active_lots_with_balance=[lotforlotinlots_with_balanceiflot.expiry_review_stateisnotIvaCompensationExpiryReviewState.EXPIRED_REVIEW_REQUIRED]next_expiry_year:int|None=Noneifactive_lots_with_balance:next_expiry_year=min(lot.source_filing_year+_FOUR_YEAR_WINDOWforlotinactive_lots_with_balance)active_balance=sum((lot.remaining_amountforlotinactive_lots_with_balance),Decimal("0"))expired_balance=sum((lot.remaining_amountforlotinexpired_lots_with_balance),Decimal("0"))total_balance=active_balance+expired_balancereturnIvaWalletBalanceReport(as_of_year=carry_forward.as_of_year,total_balance=total_balance,active_balance=active_balance,expired_balance=expired_balance,lot_count=len(carry_forward.lots),next_expiry_year=next_expiry_year,unallocated_applied_amount=carry_forward.unallocated_applied_amount,)