File size: 10,568 Bytes
3eae4cc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
"""
test_phase2.py β€” Gov Workflow OpenEnv v2.0
End-to-end validation test suite for Phase 2.

Run with:
    python test_phase2.py
"""
from __future__ import annotations
import sys
import traceback
import random
from typing import Callable

# ─────────────────────────────────────────────────────────────────────────────
# Test runner β€” always prints every PASS/FAIL
# ─────────────────────────────────────────────────────────────────────────────

PASS = "PASS"
FAIL = "FAIL"
_results: list = []


def test(section: str, name: str, fn: Callable) -> bool:
    try:
        fn()
        _results.append((section, name, PASS))
        print(f"  [PASS]  {name}")
        return True
    except AssertionError as exc:
        _results.append((section, name, f"{FAIL}: {exc}"))
        print(f"  [FAIL]  {name}")
        print(f"          AssertionError: {exc}")
        return False
    except Exception as exc:
        _results.append((section, name, f"{FAIL}: {type(exc).__name__}: {exc}"))
        print(f"  [FAIL]  {name}")
        print(f"          {type(exc).__name__}: {exc}")
        traceback.print_exc()
        return False


def section(title: str):
    print(f"\n{'─' * 60}")
    print(f"  {title}")
    print(f"{'─' * 60}")


def summary():
    total  = len(_results)
    passed = sum(1 for _, _, s in _results if s == PASS)
    failed = total - passed
    print(f"\n{'=' * 60}")
    if failed == 0:
        print(f"  RESULTS: {passed}/{total} passed  <<< ALL CLEAR >>>")
    else:
        print(f"  RESULTS: {passed}/{total} passed  <<< {failed} FAILED >>>")
        print(f"\n  Failed tests:")
        for sec, name, status in _results:
            if status != PASS:
                print(f"    [{sec}] {name}")
                print(f"           {status}")
    print(f"{'=' * 60}")
    sys.exit(0 if failed == 0 else 1)


# =============================================================================
# T1 β€” MODELS & ENUMS
# =============================================================================
section("T1 β€” Models & Enums")


def t1_service_type_values():
    from app.models import ServiceType
    expected = {
        "passport", "driving_license", "gst_registration",
        "income_certificate", "caste_certificate",
        "birth_certificate", "land_registration",
    }
    actual = {s.value for s in ServiceType}
    missing = expected - actual
    assert not missing, f"Missing ServiceType values: {missing}"


def t1_stage_type_values():
    from app.models import StageType
    expected = {
        "submission", "document_verification",
        "field_verification", "approval", "issuance",
    }
    actual = {s.value for s in StageType}
    assert actual == expected, f"StageType mismatch: {actual ^ expected}"


def t1_internal_substate_has_blocked_enrichment():
    from app.models import InternalSubstate
    values = {s.value for s in InternalSubstate}
    assert "blocked_enrichment" in values, (
        "InternalSubstate must have blocked_enrichment (Phase 2)"
    )


def t1_doc_enrichment_type_exists():
    from app.models import DocEnrichmentType
    assert DocEnrichmentType.PAST_LAND_RECORDS is not None
    assert DocEnrichmentType.POLICE_VERIFICATION is not None
    assert DocEnrichmentType.NONE is not None
    assert DocEnrichmentType.FAMILY_CASTE_HISTORY is not None


def t1_internal_to_public_stage_mapping():
    from app.models import INTERNAL_TO_PUBLIC_STAGE
    assert "blocked_enrichment" in INTERNAL_TO_PUBLIC_STAGE, (
        "INTERNAL_TO_PUBLIC_STAGE must map blocked_enrichment"
    )
    assert INTERNAL_TO_PUBLIC_STAGE["blocked_enrichment"] == "document_verification"
    assert INTERNAL_TO_PUBLIC_STAGE["completed"] == "issuance"
    assert INTERNAL_TO_PUBLIC_STAGE["pre_scrutiny"] == "submission"


def t1_application_case_enrichment_fields():
    from app.models import ApplicationCase, ServiceType, DocEnrichmentType
    case = ApplicationCase(
        service_type=ServiceType.LAND_REGISTRATION,
        arrival_day=0, current_day=0, sla_deadline_day=30,
        doc_enrichment_type=DocEnrichmentType.PAST_LAND_RECORDS,
        doc_enrichment_triggered=True,
        enrichment_resolution_day=5,
    )
    assert case.doc_enrichment_type == DocEnrichmentType.PAST_LAND_RECORDS
    assert case.doc_enrichment_triggered is True
    assert case.enrichment_resolution_day == 5


def t1_observation_model_enrichment_fields():
    from app.models import ObservationModel, OfficerPool, ScenarioMode
    obs = ObservationModel(
        task_id="test", episode_id="ep-001",
        day=0, max_days=30,
        scenario_mode=ScenarioMode.NORMAL,
        officer_pool=OfficerPool(total_officers=5, available_officers=5),
        blocked_cases_enrichment=3,
        pending_enrichment_lookups=2,
    )
    assert obs.blocked_cases_enrichment == 3
    assert obs.pending_enrichment_lookups == 2


def t1_queue_snapshot_blocked_enrichment_field():
    from app.models import QueueSnapshot, ServiceType
    snap = QueueSnapshot(
        service_type=ServiceType.LAND_REGISTRATION,
        blocked_enrichment=4,
    )
    assert snap.blocked_enrichment == 4


def t1_sector_profile_enrichment_fields():
    from app.models import SectorProfile, ServiceType, UrgencyProfile, DocEnrichmentType
    profile = SectorProfile(
        service_type=ServiceType.LAND_REGISTRATION,
        sector_name="Test Land",
        missing_docs_probability=0.3,
        doc_defect_rate_digital=0.2,
        doc_defect_rate_paper=0.5,
        field_verification_probability=0.6,
        manual_scrutiny_intensity=0.7,
        decision_backlog_sensitivity=0.8,
        system_dependency_risk=0.5,
        sla_days=30,
        urgency_profile=UrgencyProfile.LOW,
        base_processing_rate=4.0,
        field_verification_days=5,
        doc_enrichment_type=DocEnrichmentType.PAST_LAND_RECORDS,
        doc_enrichment_probability=0.70,
        doc_enrichment_delay_days_min=2,
        doc_enrichment_delay_days_max=5,
    )
    assert profile.doc_enrichment_type == DocEnrichmentType.PAST_LAND_RECORDS
    assert profile.doc_enrichment_probability == 0.70


def t1_sla_risk_property():
    from app.models import ApplicationCase, ServiceType
    case = ApplicationCase(
        service_type=ServiceType.INCOME_CERTIFICATE,
        arrival_day=0, current_day=10, sla_deadline_day=21,
    )
    risk = case.sla_risk
    assert 0.0 <= risk <= 1.0, f"sla_risk out of range: {risk}"
    expected = 10 / 21
    assert abs(risk - expected) < 0.01, f"Expected ~{expected:.3f}, got {risk:.3f}"


for fn in [
    t1_service_type_values, t1_stage_type_values,
    t1_internal_substate_has_blocked_enrichment,
    t1_doc_enrichment_type_exists,
    t1_internal_to_public_stage_mapping,
    t1_application_case_enrichment_fields,
    t1_observation_model_enrichment_fields,
    t1_queue_snapshot_blocked_enrichment_field,
    t1_sector_profile_enrichment_fields,
    t1_sla_risk_property,
]:
    test("T1", fn.__name__, fn)


# =============================================================================
# T2 β€” SECTOR PROFILES
# =============================================================================
section("T2 β€” Sector Profiles")


def t2_all_services_have_profiles():
    from app.models import ServiceType
    from app.sector_profiles import get_sector_profile
    for svc in ServiceType:
        profile = get_sector_profile(svc)
        assert profile is not None, f"No profile for {svc.value}"


def t2_income_cert_no_enrichment():
    from app.models import ServiceType, DocEnrichmentType
    from app.sector_profiles import get_sector_profile
    p = get_sector_profile(ServiceType.INCOME_CERTIFICATE)
    assert p.doc_enrichment_type == DocEnrichmentType.NONE
    assert p.doc_enrichment_probability == 0.0
    assert p.sla_days == 21


def t2_land_reg_has_enrichment():
    from app.models import ServiceType, DocEnrichmentType
    from app.sector_profiles import get_sector_profile
    p = get_sector_profile(ServiceType.LAND_REGISTRATION)
    assert p.doc_enrichment_type == DocEnrichmentType.PAST_LAND_RECORDS
    assert p.doc_enrichment_probability > 0.5
    assert p.doc_enrichment_delay_days_min >= 2
    assert p.doc_enrichment_delay_days_max >= p.doc_enrichment_delay_days_min


def t2_passport_police_verification():
    from app.models import ServiceType, DocEnrichmentType
    from app.sector_profiles import get_sector_profile
    p = get_sector_profile(ServiceType.PASSPORT)
    assert p.doc_enrichment_type == DocEnrichmentType.POLICE_VERIFICATION
    assert p.field_verification_days >= 14, (
        f"Passport police check should take >=14 days, got {p.field_verification_days}"
    )


def t2_birth_cert_fast_sla():
    from app.models import ServiceType
    from app.sector_profiles import get_sector_profile
    p = get_sector_profile(ServiceType.BIRTH_CERTIFICATE)
    assert p.sla_days <= 7, f"Birth cert SLA should be <=7 days, got {p.sla_days}"


def t2_probabilities_in_range():
    from app.models import ServiceType
    from app.sector_profiles import get_sector_profile
    for svc in ServiceType:
        p = get_sector_profile(svc)
        assert 0.0 <= p.missing_docs_probability <= 1.0, f"{svc.value}: missing_docs out of range"
        assert 0.0 <= p.field_verification_probability <= 1.0, f"{svc.value}: fv_prob out of range"
        assert 0.0 <= p.doc_enrichment_probability <= 1.0, f"{svc.value}: enrichment_prob out of range"


for fn in [
    t2_all_services_have_profiles,
    t2_income_cert_no_enrichment,
    t2_land_reg_has_enrichment,
    t2_passport_police_verification,
    t2_birth_cert_fast_sla,
    t2_probabilities_in_range,
]:
    test("T2", fn.__name__, fn)


# =============================================================================
# T3 β€” TASKS REGISTRY
# =============================================================================
section("T3 β€” Tasks Registry")


def t3_all_benchmark_tasks_loadable():
    from app.tasks import list_benchmark_tasks, get_task
    for tid in list_benchmark_tasks():
        t = get_task(tid)
        assert t.task_id == tid, f"task_id mismatch: {t.task_id} != {tid}"


def t3_task_seeds_deterministic():
    from app.tasks import get_task
    easy1 = get_task("district_backlog_easy")
    easy2 =