# ============================================================================= # Projet : AIR — Accounting Intermediate Representation # Auteur : Simon-Pierre Boucher # Contact : contact@spboucher.ai # Fichier : test_llm_live.py # Description : LIVE integration tests for the Claude extractor — skipped without ANTHROPIC_API_KEY. # ============================================================================= """Live Claude extractor tests. These hit the real Claude API and are SKIPPED unless ANTHROPIC_API_KEY is set in the environment — the offline suite (mock extractor) never requires it. The key is read from the environment only; it is never stored in the repo. Run: ANTHROPIC_API_KEY=sk-ant-... .venv/bin/python -m pytest tests/test_llm_live.py -v """ from __future__ import annotations import os from decimal import Decimal from pathlib import Path import pytest pytestmark = pytest.mark.skipif( not os.environ.get("ANTHROPIC_API_KEY"), reason="ANTHROPIC_API_KEY not set — live LLM tests are opt-in", ) ROOT = Path(__file__).resolve().parents[1] POLICY = ROOT / "alsl" / "policies" / "ca-qc-2026.yaml" INVOICE = ROOT / "tests" / "fixtures" / "invoice_realistic.txt" def test_claude_extractor_reads_a_real_invoice() -> None: from ingestion.extractor import ClaudeExtractor from ingestion.pipeline import ingest outcome = ingest(INVOICE.read_text(encoding="utf-8"), ClaudeExtractor()) # extraction must be schema-valid AIR (the LLM never emits journal entries) assert outcome.document is not None, outcome.validation_errors (event,) = outcome.document.events # what a correct reading of this invoice must contain assert event.type.value in ("Sale", "Purchase") # perspective may vary assert event.date.isoformat() == "2026-07-28" subtotal = event.subtotal() assert subtotal is not None # pre-tax amount, never the tax-included total, never a float artifact assert subtotal.amount == Decimal("999.99") assert subtotal.currency == "CAD" assert event.tax is not None and event.tax.jurisdiction.upper().endswith("QC") # confidence must be an honest decimal in (0, 1] assert Decimal("0") < outcome.extraction.confidence <= Decimal("1") assert event.meta is not None and event.meta.llm is not None def test_live_extraction_compiles_to_a_balanced_journal() -> None: from aic.compiler import compile_document from alsl.loader import load_policy_set from core.invariant import entry_imbalances from ingestion.extractor import ClaudeExtractor from ingestion.pipeline import ingest outcome = ingest(INVOICE.read_text(encoding="utf-8"), ClaudeExtractor()) assert outcome.document is not None journal, _ = compile_document(outcome.document, load_policy_set(POLICY)) (entry,) = journal.entries assert entry_imbalances(entry) == {} # deterministic tax computation on the extracted subtotal: GST 50.00, QST 99.75 amounts = sorted(str(l.amount.amount) for l in entry.lines) assert "50.00" in amounts and "99.75" in amounts