File size: 3,521 Bytes
7ff7119
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
"""03: Contract completeness — A/B level, universal best practice.

Universal contract-completeness checks (not jurisdiction-specific):
  * termination terms (high) — required for predictability
  * governing law (medium) — required for dispute resolution
  * penalty for high-value contracts (>1M) — uses a parity threshold
  * confidentiality clause (low) — only flagged when explicitly False
"""

from __future__ import annotations

from domain_checks.base import is_empty, make_risk
from graph.states.pipeline_state import Risk
from utils.numbers import coerce_number


_REGULATION = "Universal contract completeness"

_CONTRACT_CRITICAL_FIELDS = [
    ("termination_terms", "Termination terms", "high",
     "Without termination terms, the contract carries unpredictable risk."),
    ("governing_law", "Governing law", "medium",
     "Missing governing law creates uncertainty in any dispute."),
]


class ContractCompletenessCheck:
    check_id = "check_03_contract_completeness"
    regulation = _REGULATION
    is_hu_specific = False
    applies_to = {"contract"}

    def apply(self, extracted: dict) -> list[Risk]:
        risks: list[Risk] = []

        # Critical fields (termination, governing law)
        for field, label, sev, explanation in _CONTRACT_CRITICAL_FIELDS:
            if is_empty(extracted.get(field)):
                risks.append(make_risk(
                    description=f"Missing contract element: {label}",
                    severity=sev,
                    rationale=explanation,
                    regulation=_REGULATION,
                    source_check_id=self.check_id,
                ))

        # Penalty: should be present in writing for high-value contracts.
        # Two shapes supported: ``total_value`` (top-level) or legacy
        # ``value`` dict ({"amount": X, "currency": "USD"}).
        value_dict = extracted.get("value") or {}
        if isinstance(value_dict, dict) and value_dict:
            total = coerce_number(value_dict.get("amount"))
            currency = value_dict.get("currency", "")
        else:
            total = coerce_number(extracted.get("total_value"))
            currency = extracted.get("currency", "")

        if is_empty(extracted.get("penalty")) and total is not None and total > 1_000_000:
            risks.append(make_risk(
                description="No penalty clause defined in a high-value contract",
                severity="medium",
                rationale=(
                    f"Contract value is {total:,.0f} {currency} but no penalty "
                    f"clause is present. For high-value contracts, a penalty "
                    f"clause is best practice for predictable enforcement."
                ),
                regulation="Universal contract proportionality",
                source_check_id=self.check_id,
            ))

        # Confidentiality: critical for B2B. Flag ONLY when explicitly False
        # (not when missing/null) — mirrors the parity behavior.
        if extracted.get("confidentiality_clause") is False:
            risks.append(make_risk(
                description="Confidentiality clause missing",
                severity="low",
                rationale=(
                    "The contract has no confidentiality clause. In B2B "
                    "relationships, protecting business information is recommended."
                ),
                regulation=_REGULATION,
                source_check_id=self.check_id,
            ))

        return risks