Coverage for apps/disclosures/providers.py: 89%

36 statements  

« prev     ^ index     » next       coverage.py v7.17.0, created at 2026-11-04 17:24 +0000

1"""Pluggable DocMagic adapter. 

2 

3A ``DevDocMagicProvider`` returns deterministic audit findings + a stub document 

4reference for local/dev; the real DocMagic adapter is a drop-in once org credentials 

5exist, the same seam as the credit/AUS providers in ``apps/underwriting/providers.py``. 

6""" 

7 

8from __future__ import annotations 

9 

10import uuid 

11from dataclasses import dataclass 

12from typing import Protocol 

13 

14 

15@dataclass 

16class AuditFinding: 

17 code: str 

18 severity: str # "fatal" | "warning" 

19 message: str 

20 

21 

22class DocMagicProvider(Protocol): 

23 def audit(self, loan, package_type: str) -> list[AuditFinding]: ... 

24 def generate(self, loan, package_type: str) -> str: ... 

25 

26 

27class DevDocMagicProvider: 

28 """Deterministic dev stub — NOT a real DocMagic audit/generation.""" 

29 

30 def audit(self, loan, package_type: str) -> list[AuditFinding]: 

31 findings: list[AuditFinding] = [] 

32 txn = getattr(loan, "transaction", None) 

33 if package_type == "LoanEstimate" and txn is None: 

34 findings.append( 

35 AuditFinding( 

36 "DEV-NO-TXN", 

37 "fatal", 

38 "No loan transaction data — cannot estimate figures.", 

39 ) 

40 ) 

41 return findings 

42 if package_type in ("ClosingDisclosure", "ClosingPackage") and loan.fees.count() == 0: 42 ↛ 43line 42 didn't jump to line 43 because the condition on line 42 was never true

43 findings.append( 

44 AuditFinding( 

45 "DEV-NO-FEES", 

46 "fatal", 

47 "No fee data recorded — final figures are required for this package.", 

48 ) 

49 ) 

50 return findings 

51 if not loan.borrowers.exists(): 

52 findings.append( 

53 AuditFinding("DEV-NO-BORROWER", "warning", "No borrower recorded on this loan.") 

54 ) 

55 return findings 

56 

57 def generate(self, loan, package_type: str) -> str: 

58 return f"dev-docmagic://{package_type.lower()}/{loan.id}/{uuid.uuid4().hex[:8]}" 

59 

60 

61class RealDocMagicProvider: 

62 """Real DocMagic adapter — wired at integration (org credentials via admin config).""" 

63 

64 def audit(self, loan, package_type: str) -> list[AuditFinding]: 

65 raise NotImplementedError("DocMagic not configured; set DOCMAGIC_PROVIDER=dev for local.") 

66 

67 def generate(self, loan, package_type: str) -> str: 

68 raise NotImplementedError("DocMagic not configured; set DOCMAGIC_PROVIDER=dev for local.") 

69 

70 

71def get_docmagic_provider(org=None) -> DocMagicProvider: 

72 """Resolve the DocMagic adapter for this org (org creds -> env default); fall back 

73 to the dev stub when nothing real is configured.""" 

74 from apps.integrations.resolver import resolve_credentials 

75 

76 cred = resolve_credentials("docmagic", org=org) 

77 if cred is not None and cred.provider != "dev": 

78 return RealDocMagicProvider() 

79 return DevDocMagicProvider()