File size: 4,248 Bytes
3f820d9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""Unit tests for src.analysis.validation_engine."""

from __future__ import annotations

import copy

from src.analysis.validation_engine import ValidationEngine, summarize
from src.config.schema import DifficultyWeights, ValidationConfig

VALID_METADATA = {
    "document_id": "sample_000001",
    "template": "bank_doc_01",
    "page": 1,
    "page_size": [1000, 800],
    "signer": {"name": "Jane Doe", "role": "Account Holder"},
    "signature_source": {"signature_id": "sig_0001", "path": "dataset/genuine/x.png"},
    "placement": {"rotation_deg": 0.0},
    "rendering": {"blend_mode": "multiply"},
    "difficulty": {
        "misplacement": 0.6, "whitespace": 0.5, "faintness": 0.5, "smallness": 0.5, "low_texture": 0.5,
        "score": round(0.35 * 0.6 + 0.20 * 0.5 + 0.20 * 0.5 + 0.15 * 0.5 + 0.10 * 0.5, 4),
        "tier": "medium",
    },
    "quality": {"degraded": False},
    "verification": {
        "case_type": None, "verdict": None, "reject_code": None,
        "target_box_id": None, "expected_signer_group": None, "expected_signer_script": None,
        "actual_signer_group": None, "actual_source_type": None, "actual_signer_script": None,
        "companion": None,
    },
    "augmentation": {},
    "geometry": {
        "detected": True,
        "ink_pixels": 120,
        "target_box": [100, 100, 200, 150],
        "actual_box": [110, 105, 190, 145],
        "signature_bbox_px": [110, 105, 190, 145],
        "box_px": [100, 100, 200, 150],
        "signature_polygon_px": [[110, 105], [190, 105], [190, 145], [110, 145]],
    },
    "metrics": {"box_coverage_pct": 80.0},
}


def _engine(strict: bool = False) -> ValidationEngine:
    return ValidationEngine(ValidationConfig(strict=strict), DifficultyWeights())


def test_valid_metadata_passes():
    report = _engine().validate(copy.deepcopy(VALID_METADATA))
    assert report.passed, [i.message for i in report.issues]
    assert not report.errors


def test_missing_top_level_key_fails():
    bad = copy.deepcopy(VALID_METADATA)
    del bad["geometry"]
    report = _engine().validate(bad)
    assert not report.passed
    assert any("geometry" in i.field for i in report.errors)


def test_signature_not_detected_fails():
    bad = copy.deepcopy(VALID_METADATA)
    bad["geometry"]["detected"] = False
    report = _engine().validate(bad)
    assert not report.passed


def test_degenerate_polygon_fails():
    bad = copy.deepcopy(VALID_METADATA)
    bad["geometry"]["signature_polygon_px"] = [[0, 0], [1, 1]]
    report = _engine().validate(bad)
    assert not report.passed


def test_invalid_box_ordering_fails():
    bad = copy.deepcopy(VALID_METADATA)
    bad["geometry"]["target_box"] = [200, 100, 100, 150]  # x2 < x1
    report = _engine().validate(bad)
    assert not report.passed


def test_box_outside_page_is_warning_not_error_by_default():
    bad = copy.deepcopy(VALID_METADATA)
    bad["geometry"]["target_box"] = [900, 700, 1200, 900]  # exceeds page_size [1000, 800]
    report = _engine(strict=False).validate(bad)
    assert report.passed  # warning only, not strict
    assert report.warnings

    report_strict = _engine(strict=True).validate(bad)
    assert not report_strict.passed


def test_nan_value_fails():
    bad = copy.deepcopy(VALID_METADATA)
    bad["metrics"]["box_coverage_pct"] = float("nan")
    report = _engine().validate(bad)
    assert not report.passed


def test_difficulty_score_mismatch_fails():
    bad = copy.deepcopy(VALID_METADATA)
    bad["difficulty"]["score"] = 0.999  # inconsistent with components
    report = _engine().validate(bad)
    assert not report.passed


def test_difficulty_tier_mismatch_fails():
    bad = copy.deepcopy(VALID_METADATA)
    bad["difficulty"]["tier"] = "hard"  # inconsistent with score ~0.49 -> medium
    report = _engine().validate(bad)
    assert not report.passed


def test_summarize_batch():
    good = _engine().validate(copy.deepcopy(VALID_METADATA))
    bad_meta = copy.deepcopy(VALID_METADATA)
    bad_meta["geometry"]["detected"] = False
    bad = _engine().validate(bad_meta)

    summary = summarize([good, bad])
    assert summary.total == 2
    assert summary.passed == 1
    assert summary.failed == 1
    assert "sample_000001" in summary.failed_sample_ids