File size: 4,150 Bytes
0e39d80
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""Confidence scoring and decision engine β€” explainable decisions."""

from __future__ import annotations

from ml_utils.ocr import OcrResult, get_average_confidence

# Legacy constants used by fft_detect.py and edge_detect.py
FFT_MAX = 8.0
EDGE_MAX = 7.0

# ── Required fields per doc type ────────────────────────────────────────
REQUIRED_FIELDS: dict[str, set[str]] = {
    "aadhaar": {"aadhaar_number", "name", "dob"},
    "pan": {"pan_number", "name"},
    "caste": {"applicant_name", "caste_category"},
    "experience": {"company_name", "employee_name"},
    "education": {"candidate_name", "institute", "degree"},
    "resume": {"name", "email"},
    "general": set(),
}


# ── Score Components ────────────────────────────────────────────────────
def compute_ocr_score(ocr_results: list[OcrResult]) -> float:
    """OCR quality: avg confidence Γ— 35 (max 35)."""
    avg = get_average_confidence(ocr_results)
    return round(min(35.0, avg * 35.0), 1)


def compute_field_score(fields: dict, doc_type: str) -> float:
    """Field completeness: (found / required) Γ— 30 (max 30)."""
    required = REQUIRED_FIELDS.get(doc_type, set())
    if not required:
        # For general docs, count any detected fields
        detected_count = len(fields)
        return round(min(30.0, (detected_count / 5.0) * 30.0), 1)

    found = sum(1 for f in required if fields.get(f))
    ratio = found / len(required)
    return round(ratio * 30.0, 1)


def compute_validation_score(validation_score: float) -> float:
    """Already computed by validators as 0-20. Just pass through."""
    return round(min(20.0, max(0.0, validation_score)), 1)


def compute_image_score(fft_score: float | None, edge_score: float | None) -> float:
    """Image quality from forgery detection (max 15).

    fft_score is 0-15 from fft_detect (higher = more authentic).
    edge_score is 0-12 from edge_detect (higher = more consistent).
    We normalize to max 15.
    """
    fft = fft_score if fft_score is not None else 8.0
    edge = edge_score if edge_score is not None else 7.0
    # fft max 8, edge max 7 β†’ total 15
    return round(min(15.0, fft + edge), 1)


def compute_final_score(
    ocr_quality: float,
    field_completeness: float,
    validation: float,
    image_quality: float,
) -> float:
    """Sum all components, cap at 100."""
    return round(min(100.0, ocr_quality + field_completeness + validation + image_quality), 1)


# ── Decision ────────────────────────────────────────────────────────────
STATUS_THRESHOLDS = [
    (90, "Auto-Verified"),
    (75, "Verified"),
    (50, "Manual Review Required"),
    (25, "Low Confidence"),
    (0, "Rejected"),
]


def decide_status(score: float, flags: list[str]) -> str:
    """Map score + flags to a decision status."""
    # Critical flags force rejection regardless of score
    critical_flags = {"VERHOEFF_CHECKSUM_FAILED", "INVALID_PAN_FORMAT", "TEXT_EXTRACT_FAILED"}
    if any(f in critical_flags for f in flags):
        return "Rejected"

    for threshold, status in STATUS_THRESHOLDS:
        if score >= threshold:
            return status
    return "Rejected"


def build_score_breakdown(
    ocr_results: list[OcrResult],
    fields: dict,
    doc_type: str,
    validation_score: float,
    fft_score: float | None,
    edge_score: float | None,
) -> dict:
    """Compute all scores and return full breakdown dict."""
    ocr_q = compute_ocr_score(ocr_results)
    field_c = compute_field_score(fields, doc_type)
    valid_s = compute_validation_score(validation_score)
    img_q = compute_image_score(fft_score, edge_score)
    final = compute_final_score(ocr_q, field_c, valid_s, img_q)

    return {
        "ocr_quality": ocr_q,
        "field_completeness": field_c,
        "validation": valid_s,
        "image_quality": img_q,
        "overall": final,
    }