Coverage for domain/conditions.py: 100%
13 statements
« prev ^ index » next coverage.py v7.17.0, created at 2026-11-04 14:06 +0000
« prev ^ index » next coverage.py v7.17.0, created at 2026-11-04 14:06 +0000
1"""Underwriting condition status state machine.
3Pure, mirroring ``domain/lifecycle.py``'s pattern for ``LoanStage`` — a separate small
4graph because conditions and loan stages have different states, different terminal
5states, and no shared behavior beyond "it's a graph walk."
6"""
8from __future__ import annotations
10from .enums import ConditionStatus
11from .result import AppError, Result, err, ok
13CONDITION_TRANSITIONS: dict[ConditionStatus, tuple[ConditionStatus, ...]] = {
14 ConditionStatus.ADDED: (ConditionStatus.READY_TO_SEND,),
15 ConditionStatus.READY_TO_SEND: (ConditionStatus.SENT_TO_LENDER,),
16 ConditionStatus.SENT_TO_LENDER: (
17 ConditionStatus.CLEARED,
18 ConditionStatus.WAIVED,
19 ConditionStatus.RE_REQUESTED,
20 ),
21 ConditionStatus.RE_REQUESTED: (ConditionStatus.READY_TO_SEND,),
22 ConditionStatus.CLEARED: (),
23 ConditionStatus.WAIVED: (),
24}
26# Terminal statuses, plus everything else: a condition is "open" (not yet resolved)
27# unless it's Cleared or Waived.
28OPEN_CONDITION_STATUSES: frozenset[ConditionStatus] = frozenset(
29 s for s in ConditionStatus if s not in (ConditionStatus.CLEARED, ConditionStatus.WAIVED)
30)
33def can_transition_condition(frm: ConditionStatus, to: ConditionStatus) -> bool:
34 return to in CONDITION_TRANSITIONS[frm]
37def assert_condition_transition(frm: ConditionStatus, to: ConditionStatus) -> Result:
38 if frm == to:
39 return ok(to)
40 if not can_transition_condition(frm, to):
41 return err(
42 AppError(
43 code="INVALID_TRANSITION",
44 message=f"Condition cannot move from {frm} to {to}.",
45 )
46 )
47 return ok(to)