File size: 2,635 Bytes
aac350d
23d337e
 
aac350d
 
 
 
23d337e
 
 
 
 
aac350d
 
 
 
 
 
 
 
 
 
 
 
 
23d337e
 
 
 
 
 
 
 
 
 
 
 
 
aac350d
 
 
 
 
 
 
 
 
 
 
 
 
 
23d337e
 
 
aac350d
 
23d337e
 
 
 
aac350d
 
 
23d337e
 
 
aac350d
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
"""
Explainer — turns a ConfidenceScore or UnifiedFaceReport into a
multi-line human-readable string for display in the UI / logs.
"""

from __future__ import annotations

from models.reports import (
    ConfidenceScore,
    ConflictReport,
    UnifiedFaceReport,
)


class Explainer:
    """Renders confidence + report as human-readable text."""

    @staticmethod
    def explain_score(score: ConfidenceScore) -> str:
        lines = [f"Overall confidence: {score.overall:.2%} ({score.method})"]
        if score.explanation:
            lines.append(f"  {score.explanation}")
        if score.components:
            lines.append("  Components:")
            for k, v in score.components.items():
                lines.append(f"    - {k}: {v:.3f}" if isinstance(v, float) else f"    - {k}: {v}")
        return "\n".join(lines)

    @staticmethod
    def explain_conflicts(conflicts: list[ConflictReport]) -> str:
        if not conflicts:
            return "No conflicts detected."
        lines = [f"{len(conflicts)} conflict(s) detected:"]
        for c in conflicts:
            lines.append(
                f"  [{c.severity.upper()}] {c.kind}: {c.description} "
                f"(providers: {', '.join(c.providers)})"
            )
        return "\n".join(lines)

    @staticmethod
    def explain_report(report: UnifiedFaceReport) -> str:
        m = report.metadata
        lines = [
            f"Job {m.job_id}{m.total_elapsed_ms:.0f}ms total",
            f"  Providers invoked: {', '.join(m.providers_invoked) or 'none'}",
            f"  Succeeded: {', '.join(m.providers_succeeded) or 'none'}",
            f"  Failed: {', '.join(m.providers_failed) or 'none'}",
            f"  Detections: {len(report.detections)}",
            f"  Matches: {len(report.matches)}",
            f"  Scraped images: {len(report.scraped_images)}",
            f"  Reverse matches: {len(report.reverse_matches)}",
            f"  Image analyses: {len(report.image_analyses)}",
            f"  Metadata extractions: {len(report.metadata_extractions)}",
            f"  Forensics: {len(report.forensics)}",
            f"  Conflicts: {len(report.conflicts)}",
        ]
        if m.limitations:
            lines.append("  Limitations:")
            for lim in m.limitations:
                lines.append(f"    - {lim}")
        if report.overall_confidence:
            lines.append("")
            lines.append(Explainer.explain_score(report.overall_confidence))
        if report.conflicts:
            lines.append("")
            lines.append(Explainer.explain_conflicts(report.conflicts))
        return "\n".join(lines)