spb/air Public MIT
AIR — The Language of Accounting.
Python 100%
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