SPB Git

spb/air Public MIT

AIR — The Language of Accounting.

Python 100%
4.6 KB · 106 lines python
Raw Blame History
1# =============================================================================2# Projet : AIR — Accounting Intermediate Representation3# Auteur : Simon-Pierre Boucher4# Contact : contact@spboucher.ai5# Fichier : tax.py6# Description : Tax pass — applies ALSL tax policies deterministically (no rates in code).7# =============================================================================8"""Tax pass.910For each taxable event, evaluates the ALSL tax policies matching the event's11jurisdiction and computes each tax component on its base with the policy12set's rounding mode. Every computed tax amount becomes a provenance node13derived from the event subtotal.1415NO tax rate exists in this file — rates live in versioned ALSL policy sets16with mandatory source citations (see docs/research/canada-gst-qst.md).17"""18from __future__ import annotations1920from aic.diagnostics import Diagnostic, Severity21from aic.passes.base import Pass22from aic.unit import CompilationUnit, TaxLineState23from alsl.evaluator import applicable_tax_policies24from core.events import EventType2526TAXABLE_TYPES = (EventType.SALE, EventType.PURCHASE, EventType.REFUND)272829class TaxPass(Pass):30    name = "tax"3132    def run(self, unit: CompilationUnit) -> list[Diagnostic]:33        diags: list[Diagnostic] = []34        rounding = unit.policies.rounding.mode3536        for event in unit.document.events:37            if event.type not in TAXABLE_TYPES:38                continue39            if event.tax is None or event.tax.exempt:40                continue41            state = unit.event_state(event.id)42            if state.subtotal is None or state.subtotal_node is None:43                continue  # validation already failed this event4445            policies = applicable_tax_policies(unit.policies, event, state.subtotal)46            if not policies:47                diags.append(Diagnostic(48                    code="AIR-E400", severity=Severity.ERROR,49                    message=(50                        f"no tax policy in set '{unit.policies.name}' matches "51                        f"jurisdiction '{event.tax.jurisdiction}'"52                    ),53                    location=f"event {event.id}, field tax.jurisdiction",54                    suggestion="add an ALSL tax policy for this jurisdiction "55                               "or mark the event tax.exempt: true",56                    origin_pass=self.name,57                ))58                continue5960            wanted = set(event.tax.codes)  # optional explicit filter61            produced: set[str] = set()62            for policy in policies:63                for comp in policy.components:64                    if wanted and comp.code not in wanted:65                        continue66                    if comp.base != "subtotal":67                        diags.append(Diagnostic(68                            code="AIR-E401", severity=Severity.ERROR,69                            message=f"unsupported tax base '{comp.base}' in policy "70                                    f"'{policy.name}' (ALSL v0.1 supports 'subtotal')",71                            location=f"policy {policy.name}, component {comp.code}",72                            suggestion="use base: subtotal",73                            origin_pass=self.name,74                        ))75                        continue76                    raw = state.subtotal.multiply(comp.rate)77                    amount = raw.quantized(rounding)78                    node = unit.provenance.define(79                        kind="tax",80                        operation=f"tax:{comp.code}@{comp.rate}~{rounding.value}",81                        amount=amount,82                        inputs=(state.subtotal_node,),83                        source_ref=policy.source,84                    )85                    state.taxes.append(TaxLineState(86                        code=comp.code,87                        amount=amount,88                        node_id=node.id,89                        payable_role=comp.payable_role,90                        receivable_role=comp.receivable_role,91                        recoverable=comp.recoverable_on_purchase,92                        policy=policy.name,93                    ))94                    produced.add(comp.code)9596            for missing in wanted - produced:97                diags.append(Diagnostic(98                    code="AIR-W400", severity=Severity.WARNING,99                    message=f"event requested tax code '{missing}' but no policy "100                            "produced it",101                    location=f"event {event.id}, field tax.codes",102                    suggestion="check the policy set covers this code for the jurisdiction",103                    origin_pass=self.name,104                ))105        return diags106