Spaces:
Running
Running
| """ | |
| 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 = |