File size: 4,866 Bytes
9a88af0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
"""Tests for InvoiceGuard data models."""

import sys
import os

sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))

from models import (
    ActionType,
    DecisionType,
    ExceptionType,
    TaskID,
    Difficulty,
    InvoiceGuardAction,
    InvoiceGuardObservation,
    InvoiceGuardState,
    GraderResult,
    Invoice,
    InvoiceLineItem,
    PurchaseOrder,
    POLineItem,
    GoodsReceiptNote,
    GRNLineItem,
    VendorProfile,
    CompanyPolicy,
    CaseData,
    CaseHistory,
    GroundTruth,
)


class TestEnums:
    def test_action_types_count(self):
        assert len(ActionType) == 12

    def test_decision_types(self):
        expected = {
            "approve_for_payment",
            "place_on_hold",
            "reject_invoice",
            "escalate_for_supervisor_review",
        }
        assert {d.value for d in DecisionType} == expected

    def test_exception_types_include_key_variants(self):
        values = {e.value for e in ExceptionType}
        assert "clean_match" in values
        assert "duplicate_invoice" in values
        assert "price_mismatch" in values
        assert "partial_receipt" in values
        assert "mixed_discrepancy" in values

    def test_task_ids_minimum_12_canonical(self):
        canonical = [t for t in TaskID if not any(c in t.value for c in ["b_", "c_"])]
        assert len(canonical) >= 12

    def test_difficulty_levels(self):
        assert set(d.value for d in Difficulty) == {"easy", "moderate", "hard"}


class TestAction:
    def test_minimal_action(self):
        a = InvoiceGuardAction(action_type=ActionType.inspect_purchase_order)
        assert a.action_type == ActionType.inspect_purchase_order
        assert a.final_decision is None
        assert a.evidence_references == []

    def test_resolution_action(self):
        a = InvoiceGuardAction(
            action_type=ActionType.submit_final_resolution,
            final_decision=DecisionType.approve_for_payment,
            exception_type=ExceptionType.clean_match,
            evidence_references=["inspect_purchase_order", "compare_quantity"],
            explanation="All documents match.",
        )
        assert a.final_decision == DecisionType.approve_for_payment
        assert len(a.evidence_references) == 2


class TestObservation:
    def test_defaults(self):
        obs = InvoiceGuardObservation()
        assert obs.case_id == ""
        assert obs.available_actions == []
        assert obs.suggested_next_actions == []
        assert obs.remaining_steps == 0
        assert obs.done is False
        assert obs.grader_result == {}

    def test_populated(self):
        obs = InvoiceGuardObservation(
            case_id="CASE-001",
            task_id="task_1_clean_match",
            difficulty="easy",
            remaining_steps=10,
            suggested_next_actions=["inspect_purchase_order"],
        )
        assert obs.case_id == "CASE-001"
        assert obs.suggested_next_actions == ["inspect_purchase_order"]


class TestState:
    def test_defaults(self):
        s = InvoiceGuardState()
        assert s.step_count == 0
        assert s.actions_taken == []
        assert s.is_finalized is False
        assert s.cumulative_reward == 0.0

    def test_mutation(self):
        s = InvoiceGuardState()
        s.actions_taken.append("inspect_purchase_order")
        s.step_count = 1
        assert len(s.actions_taken) == 1


class TestGraderResult:
    def test_valid_score(self):
        r = GraderResult(score=0.85)
        assert r.score == 0.85
        assert r.explanation_score == 0.0

    def test_score_bounds(self):
        r = GraderResult(score=0.0)
        assert r.score == 0.0
        r = GraderResult(score=1.0)
        assert r.score == 1.0


class TestBusinessEntities:
    def test_invoice_creation(self):
        inv = Invoice(
            invoice_number="INV-001",
            supplier_name="Test Corp",
            supplier_id="SUP-001",
            invoice_date="2024-01-01",
            po_reference="PO-001",
            line_items=[
                InvoiceLineItem(
                    item_code="ITM-A",
                    description="Widget",
                    quantity_billed=10,
                    unit_price_billed=5.0,
                    line_total_billed=50.0,
                )
            ],
            subtotal=50.0,
            tax=5.0,
            total_amount=55.0,
        )
        assert inv.currency == "USD"
        assert len(inv.line_items) == 1

    def test_company_policy_defaults(self):
        p = CompanyPolicy()
        assert p.quantity_tolerance_pct == 5.0
        assert p.price_tolerance_pct == 5.0
        assert p.duplicate_check_enabled is True