from __future__ import annotations import json import tempfile import unittest from pathlib import Path from us_cpa.questions import QuestionEngine, render_analysis, render_memo from us_cpa.sources import TaxYearCorpus, bootstrap_irs_catalog class QuestionEngineTests(unittest.TestCase): def build_engine(self, temp_dir: str) -> QuestionEngine: corpus = TaxYearCorpus(cache_root=Path(temp_dir)) def fake_fetch(url: str) -> bytes: return f"source for {url}".encode() corpus.download_catalog(2025, bootstrap_irs_catalog(2025), fetcher=fake_fetch) return QuestionEngine(corpus=corpus) def test_standard_deduction_question_returns_structured_analysis(self) -> None: with tempfile.TemporaryDirectory() as temp_dir: engine = self.build_engine(temp_dir) analysis = engine.answer( question="What is the standard deduction for single filers?", tax_year=2025, case_facts={"filingStatus": "single"}, ) self.assertEqual(analysis["issue"], "standard_deduction") self.assertEqual(analysis["taxYear"], 2025) self.assertEqual(analysis["conclusion"]["answer"], "$15,750") self.assertEqual(analysis["confidence"], "high") self.assertEqual(analysis["riskLevel"], "low") self.assertTrue(analysis["authorities"]) self.assertEqual(analysis["authorities"][0]["sourceClass"], "irs_instructions") def test_standard_deduction_infers_married_filing_jointly_from_question(self) -> None: with tempfile.TemporaryDirectory() as temp_dir: engine = self.build_engine(temp_dir) analysis = engine.answer( question="What is the standard deduction for married filing jointly?", tax_year=2025, case_facts={}, ) self.assertEqual(analysis["issue"], "standard_deduction") self.assertEqual(analysis["conclusion"]["answer"], "$31,500") self.assertIn("Married Filing Jointly", analysis["conclusion"]["summary"]) def test_standard_deduction_infers_head_of_household_from_question(self) -> None: with tempfile.TemporaryDirectory() as temp_dir: engine = self.build_engine(temp_dir) analysis = engine.answer( question="What is the standard deduction for a head of household filer?", tax_year=2025, case_facts={}, ) self.assertEqual(analysis["issue"], "standard_deduction") self.assertEqual(analysis["conclusion"]["answer"], "$23,625") self.assertIn("Head Of Household", analysis["conclusion"]["summary"]) def test_complex_question_flags_primary_law_escalation(self) -> None: with tempfile.TemporaryDirectory() as temp_dir: engine = self.build_engine(temp_dir) analysis = engine.answer( question="Does section 469 passive activity loss limitation apply here?", tax_year=2025, case_facts={}, ) self.assertEqual(analysis["confidence"], "low") self.assertEqual(analysis["riskLevel"], "high") self.assertTrue(analysis["primaryLawRequired"]) self.assertIn("Internal Revenue Code", analysis["missingFacts"][0]) self.assertTrue(any(item["sourceClass"] == "internal_revenue_code" for item in analysis["authorities"])) def test_capital_gains_question_returns_schedule_d_guidance(self) -> None: with tempfile.TemporaryDirectory() as temp_dir: engine = self.build_engine(temp_dir) analysis = engine.answer( question="Do I need Schedule D for capital gains?", tax_year=2025, case_facts={"capitalGainLoss": 400}, ) self.assertEqual(analysis["issue"], "schedule_d_required") self.assertEqual(analysis["confidence"], "medium") self.assertFalse(analysis["primaryLawRequired"]) self.assertTrue(any(item["slug"] == "f1040sd" for item in analysis["authorities"])) def test_schedule_e_question_returns_rental_guidance(self) -> None: with tempfile.TemporaryDirectory() as temp_dir: engine = self.build_engine(temp_dir) analysis = engine.answer( question="Do I need Schedule E for rental income?", tax_year=2025, case_facts={"rentalIncome": 1200}, ) self.assertEqual(analysis["issue"], "schedule_e_required") self.assertFalse(analysis["primaryLawRequired"]) self.assertTrue(any(item["slug"] == "f1040se" for item in analysis["authorities"])) def test_renderers_produce_conversation_and_memo(self) -> None: analysis = { "issue": "standard_deduction", "taxYear": 2025, "factsUsed": [{"field": "filingStatus", "value": "single"}], "missingFacts": [], "authorities": [{"title": "Instructions for Form 1040 and Schedules 1-3"}], "conclusion": {"answer": "$15,750", "summary": "Single filers use a $15,750 standard deduction for tax year 2025."}, "confidence": "high", "riskLevel": "low", "followUpQuestions": [], "primaryLawRequired": False, } conversation = render_analysis(analysis) memo = render_memo(analysis) self.assertIn("$15,750", conversation) self.assertIn("Issue", memo) self.assertIn("Authorities", memo) if __name__ == "__main__": unittest.main()