109 lines
4.4 KiB
Python
109 lines
4.4 KiB
Python
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_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()
|