Source code for aeat.application.modelo._art109_activity_income
"""Period-scoped Art. 109 activity-income coverage from ledger evidence.The coverage helper derives the RIRPF Art. 109 70 percent withholding fact fromcurrent-period ledger rows in a:class:`~domain.transactions.TransactionCatalogue`. Work-unit consumers usea bucket-scoped :class:`~adapters.persistence.profile.transactions.TransactionCatalogueRepository`to load that catalogue before applying the same pure calculation.The result is intentionally evidence-scoped: the denominator and withholdingstatus must be proven from invoice-substrate ledger facts, so gross-only bankmovements fail closed instead of fabricating an activity-income ratio.See Also: :func:`derive_art109_activity_income_coverage` Pure catalogue-level derivation for already-loaded ledger rows. :func:`derive_art109_activity_income_coverage_for_work_unit` Work-unit adapter that resolves the matching bucket repository. :mod:`application.modelo._verification_actions` Verification path that folds a proven coverage fact into the workflow profile used for M130 verification."""from__future__importannotationsfromdataclassesimportdataclassfromdecimalimportDecimalfromenumimportStrEnumfrom...adapters.persistence.profile.transactionsimportTransactionCatalogueRepositoryfrom...coreimportModelo,Periodfrom...domain.modelosimportWorkUnitfrom...domain.transactionsimport(IRPF_CATEGORY_ACTIVIDAD_ECONOMICA,IRPF_CATEGORY_TRABAJO,BusinessClassification,Transaction,TransactionCatalogue,TransactionCatalogueRepositoryProtocol,TransactionDirection,TransactionLifecycleState,)_THRESHOLD=Decimal("0.70")_ZERO=Decimal("0")_PROVEN_ACTIVITY_STATES=frozenset({BusinessClassification.BUSINESS,BusinessClassification.MIXED})_UNRESOLVED_ACTIVITY_STATES=frozenset({BusinessClassification.NOT_YET_PROCESSED,BusinessClassification.PROCESSED_UNCLASSIFIED,BusinessClassification.SKIPPED_BY_RULE,BusinessClassification.FAILED_VALIDATION,},)
[docs]classArt109ActivityIncomeCoverageStatus(StrEnum):"""Whether current-period ledger evidence proves the Art. 109 threshold."""PROVEN="proven"INSUFFICIENT="insufficient"
[docs]@dataclass(frozen=True,slots=True)classArt109ActivityIncomeCoverage:"""Period-scoped Art. 109 70 percent activity-income coverage result."""status:Art109ActivityIncomeCoverageStatusmeets_threshold:bool|Nonenumerator:Decimaldenominator:Decimalreason:str@propertydefis_proven(self)->bool:"""Return whether ``meets_threshold`` is backed by sufficient evidence."""returnself.statusisArt109ActivityIncomeCoverageStatus.PROVEN
[docs]defderive_art109_activity_income_coverage_for_work_unit(work_unit:WorkUnit,*,transaction_repository:TransactionCatalogueRepositoryProtocol|None,)->Art109ActivityIncomeCoverage:"""Derive the Art. 109 current-payment-period coverage fact for an M130 work unit. The returned :class:`Art109ActivityIncomeCoverage` is proven only from current-period ledger rows, not from M130 output casillas. A row proves the denominator and withholding status only when it carries invoice substrate (``taxable_base`` and ``iva_amount``); gross-only bank movements fail closed because they cannot prove whether the receipt was subject to withholding. """ifstr(work_unit.modelo)!=Modelo.M130.value:return_insufficient("not_modelo_130")period=work_unit.periodifnotperiod.has_date_span():return_insufficient("period_without_date_span")repository=transaction_repositoryorTransactionCatalogueRepository(bucket_id=work_unit.bucket_id)ifrepository.bucket_id!=work_unit.bucket_id:return_insufficient("repository_bucket_mismatch")returnderive_art109_activity_income_coverage(repository.load(),period=period)
[docs]defderive_art109_activity_income_coverage(catalogue:TransactionCatalogue,*,period:Period,)->Art109ActivityIncomeCoverage:"""Derive Art. 109 current-period coverage from a transaction catalogue. Args: catalogue: :class:`~domain.transactions.TransactionCatalogue` containing the ledger rows to classify for the target period. period: Filing period whose date span selects the current-payment rows. Returns: The proven or insufficient :class:`Art109ActivityIncomeCoverage`. """ifnotperiod.has_date_span():return_insufficient("period_without_date_span")numerator=_ZEROdenominator=_ZEROfortransactionincatalogue.values():row=_classify_current_period_row(transaction,period=period)ifrowis_RowKind.IGNORE:continueifrowis_RowKind.INSUFFICIENT:return_insufficient("current_period_activity_income_unresolved")computable_income=_proved_computable_income(transaction)ifcomputable_incomeisNoneorcomputable_income<=_ZERO:return_insufficient("current_period_activity_income_substrate_incomplete")denominator+=computable_incomeif_proved_withheld_income(transaction):numerator+=computable_incomeifdenominator<=_ZERO:return_insufficient("current_period_activity_income_absent")returnArt109ActivityIncomeCoverage(status=Art109ActivityIncomeCoverageStatus.PROVEN,meets_threshold=(numerator/denominator)>=_THRESHOLD,numerator=numerator,denominator=denominator,reason="current_period_activity_income_ratio_proven",)