feat: add us-cpa review workflow

This commit is contained in:
Stefano Fiorini
2026-03-15 01:31:43 -05:00
parent 82cf3d9010
commit 10a9d40f1d
6 changed files with 321 additions and 5 deletions

View File

@@ -261,6 +261,94 @@ class UsCpaCliSmokeTests(unittest.TestCase):
self.assertEqual(payload["summary"]["requiredForms"], ["f1040"])
self.assertTrue((case_dir / "output" / "artifacts.json").exists())
def test_review_command_returns_findings(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
env = os.environ.copy()
env["PYTHONPATH"] = str(SRC_DIR)
env["US_CPA_CACHE_DIR"] = str(Path(temp_dir) / "cache")
subprocess.run(
[sys.executable, "-m", "us_cpa.cli", "fetch-year", "--tax-year", "2025"],
text=True,
capture_output=True,
env=env,
check=True,
)
case_dir = Path(temp_dir) / "2025-jane-doe"
facts_path = Path(temp_dir) / "facts.json"
facts_path.write_text(
json.dumps(
{
"taxpayer.fullName": "Jane Doe",
"filingStatus": "single",
"wages": 50000,
"taxableInterest": 100,
"federalWithholding": 6000,
}
)
)
subprocess.run(
[
sys.executable,
"-m",
"us_cpa.cli",
"extract-docs",
"--tax-year",
"2025",
"--case-dir",
str(case_dir),
"--create-case",
"--case-label",
"Jane Doe",
"--facts-json",
str(facts_path),
],
text=True,
capture_output=True,
env=env,
check=True,
)
subprocess.run(
[
sys.executable,
"-m",
"us_cpa.cli",
"prepare",
"--tax-year",
"2025",
"--case-dir",
str(case_dir),
],
text=True,
capture_output=True,
env=env,
check=True,
)
normalized_path = case_dir / "return" / "normalized-return.json"
normalized = json.loads(normalized_path.read_text())
normalized["totals"]["adjustedGrossIncome"] = 99999.0
normalized_path.write_text(json.dumps(normalized, indent=2))
result = subprocess.run(
[
sys.executable,
"-m",
"us_cpa.cli",
"review",
"--tax-year",
"2025",
"--case-dir",
str(case_dir),
],
text=True,
capture_output=True,
env=env,
)
self.assertEqual(result.returncode, 0, result.stderr)
payload = json.loads(result.stdout)
self.assertEqual(payload["status"], "reviewed")
self.assertEqual(payload["findingCount"], 2)
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,90 @@
from __future__ import annotations
import json
import tempfile
import unittest
from io import BytesIO
from pathlib import Path
from reportlab.pdfgen import canvas
from us_cpa.cases import CaseManager
from us_cpa.prepare import PrepareEngine
from us_cpa.review import ReviewEngine, render_review_memo, render_review_summary
from us_cpa.sources import TaxYearCorpus, bootstrap_irs_catalog
class ReviewEngineTests(unittest.TestCase):
def build_prepared_case(self, temp_dir: str) -> tuple[Path, TaxYearCorpus]:
case_dir = Path(temp_dir) / "2025-jane-doe"
manager = CaseManager(case_dir)
manager.create_case(case_label="Jane Doe", tax_year=2025)
manager.intake(
tax_year=2025,
user_facts={
"taxpayer.fullName": "Jane Doe",
"filingStatus": "single",
"wages": 50000,
"taxableInterest": 100,
"federalWithholding": 6000,
},
document_paths=[],
)
corpus = TaxYearCorpus(cache_root=Path(temp_dir) / "cache")
def fake_fetch(url: str) -> bytes:
buffer = BytesIO()
pdf = canvas.Canvas(buffer)
pdf.drawString(72, 720, f"Template for {url}")
pdf.save()
return buffer.getvalue()
corpus.download_catalog(2025, bootstrap_irs_catalog(2025), fetcher=fake_fetch)
PrepareEngine(corpus=corpus).prepare_case(case_dir)
return case_dir, corpus
def test_review_detects_mismatched_return_and_missing_artifacts(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
case_dir, corpus = self.build_prepared_case(temp_dir)
normalized_path = case_dir / "return" / "normalized-return.json"
normalized = json.loads(normalized_path.read_text())
normalized["totals"]["adjustedGrossIncome"] = 99999.0
normalized_path.write_text(json.dumps(normalized, indent=2))
artifacts_path = case_dir / "output" / "artifacts.json"
artifacts = json.loads(artifacts_path.read_text())
artifacts["artifacts"] = []
artifacts["artifactCount"] = 0
artifacts_path.write_text(json.dumps(artifacts, indent=2))
review = ReviewEngine(corpus=corpus).review_case(case_dir)
self.assertEqual(review["status"], "reviewed")
self.assertEqual(review["findings"][0]["severity"], "high")
self.assertIn("adjusted gross income", review["findings"][0]["title"].lower())
self.assertTrue(any("missing rendered artifact" in item["title"].lower() for item in review["findings"]))
def test_review_renderers_produce_summary_and_memo(self) -> None:
review = {
"status": "reviewed",
"findings": [
{
"severity": "high",
"title": "Adjusted gross income mismatch",
"explanation": "Stored AGI does not match recomputed AGI.",
"suggestedAction": "Update Form 1040 line 11.",
"authorities": [{"title": "Instructions for Form 1040 and Schedules 1-3"}],
}
],
}
summary = render_review_summary(review)
memo = render_review_memo(review)
self.assertIn("Adjusted gross income mismatch", summary)
self.assertIn("# Review Memo", memo)
self.assertIn("Suggested correction", memo)
if __name__ == "__main__":
unittest.main()