File size: 7,647 Bytes
11a28db
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
from rich.console import Console
from rich.table import Table
from rich.panel import Panel
from rich.tree import Tree
import copy

class BibUI:
    """Handles all terminal UI interactions for BibGuard."""
    
    def __init__(self):
        self.console = Console()
    
    def show_analysis_report(self, ok_entries, to_fix, to_review, to_remove):
        """Display the initial analysis summary table."""
        table = Table(title="πŸ“Š Analysis Report", show_header=True, header_style="bold magenta")
        table.add_column("Category", style="cyan")
        table.add_column("Count", justify="right")
        table.add_column("Description")
        
        table.add_row("βœ… Correct", str(len(ok_entries)), "Entries match valid metadata")
        table.add_row("πŸ› οΈ  To Fix", str(len(to_fix)), "[green]High confidence auto-fixes[/green]")
        table.add_row("πŸ” Review", str(len(to_review)), "[yellow]Ambiguous or low confidence[/yellow]")
        table.add_row("πŸ—‘οΈ  Remove", str(len(to_remove)), "[red]No metadata found (Hallucinations)[/red]")
        
        self.console.print(table)
        
        if not (to_fix or to_review or to_remove):
            self.console.print(Panel("[green]βœ“ No issues found. All entries are valid.[/green]", title="Status"))

    def show_manual_review(self, entry, best_res, candidates, apply_fix_func):
        """Display manual review table for a single entry."""
        self.console.print(f"\n[bold]Entry: {entry.key}[/bold]")
        self.console.print(f"Title: {entry.title}")
        self.console.print(f"Year:  {entry.year}")
        self.console.print(f"Auth:  {entry.author}")
        
        cand_table = Table(show_header=True, header_style="bold blue")
        cand_table.add_column("#", style="dim", width=4)
        cand_table.add_column("Source", style="cyan", width=12)
        cand_table.add_column("Conf", justify="right")
        cand_table.add_column("Candidate Metadata (Fetched)", style="white")
        cand_table.add_column("Proposed Changes", style="green")
        
        for i, cand in enumerate(candidates, 1):
            # We need to simulate the fix to show changes
            # We pass the apply_fix function to avoid circular dependency or logic duplication
            temp_entry = copy.deepcopy(entry)
            changes = apply_fix_func(temp_entry, cand.fetched_data)
            change_desc = "\n".join(changes) if changes else "[dim]No changes[/dim]"
            
            conf_style = "green" if cand.confidence > 0.7 else "yellow" if cand.confidence > 0.4 else "red"
            
            # Format the candidate's actual metadata
            fd = cand.fetched_data
            meta_lines = []
            if getattr(fd, 'title', None): 
                meta_lines.append(f"[bold]Title:[/bold] {fd.title[:60] + '...' if len(fd.title) > 60 else fd.title}")
            if getattr(fd, 'authors', None):
                a_str = " and ".join(fd.authors)
                meta_lines.append(f"[bold]Authors:[/bold] {a_str[:60] + '...' if len(a_str) > 60 else a_str}")
            if getattr(fd, 'year', None): 
                meta_lines.append(f"[bold]Year:[/bold] {fd.year}")
            if getattr(fd, 'doi', None): 
                meta_lines.append(f"[bold]DOI:[/bold] {fd.doi}")
            meta_desc = "\n".join(meta_lines) if meta_lines else "[dim]No metadata details[/dim]"
            
            cand_table.add_row(
                str(i), 
                cand.source, 
                f"[{conf_style}]{cand.confidence:.2f}[/{conf_style}]", 
                meta_desc,
                change_desc
            )
        
        self.console.print(cand_table)

    def show_final_report(self, total, verified, issues, not_found, reports, fixed_count, fixed_details, removed_details):
        """Display the verification status and modification tree."""
        # Visual Final Status
        status_table = Table(box=None, padding=(0, 2))
        status_table.add_column("Metric", style="bold")
        status_table.add_column("Value", justify="right")
        status_table.add_row("Total Entries", str(total))
        status_table.add_row("Verified", f"[green]{verified}[/green]")
        status_table.add_row("Issues", f"[red]{issues}[/red]" if issues > 0 else "0")
        status_table.add_row("Not Found", f"[yellow]{not_found}[/yellow]" if not_found > 0 else "0")
        
        self.console.print(Panel(status_table, title="πŸ“Š Final Status", expand=False))
        
        if issues > 0:
            self.console.print("\n[bold red]⚠ Remaining Issues (Not Auto-Fixed):[/bold red]")
            for r in reports:
                if r.comparison and r.comparison.has_issues:
                    self.console.print(f"  - [bold]{r.entry.key}[/bold] (Conf: {r.comparison.confidence:.2f}): {', '.join(r.comparison.issues)}")
        
        # Report fixes and removals
        if fixed_count > 0 or removed_details:
            tree = Tree("✏️  Modifications Report")
            
            if removed_details: 
                rem_node = tree.add(f"[red]Removed {len(removed_details)} entries[/red]")
                for entry, reason in removed_details:
                     rem_node.add(f"[bold]{entry.key}[/bold]: \"{entry.title}\" ([italic]{reason}[/italic])")
                     
            if fixed_count > 0: 
                fix_node = tree.add(f"[green]Fixed {fixed_count} entries[/green]")
                for key, changes in fixed_details.items():
                    entry_node = fix_node.add(f"[bold]{key}[/bold]")
                    for change in changes:
                        entry_node.add(change)
            
            self.console.print(tree)
            self.console.print("\n[green]βœ“ Changes applied and saved to file.[/green]")
        else:
            self.console.print("\n[green]βœ“ No changes were needed.[/green]")

    def show_sanitize_report(self, sanitize_fixes: dict):
        """Display sanitization results as a rich tree."""
        if not sanitize_fixes:
            self.console.print("[green]βœ“ No formatting issues found.[/green]\n")
            return
        
        # Category display info
        category_info = {
            "dblp_id": ("πŸ”’", "DBLP Disambiguation ID Cleanup", "red"),
            "corporate_author": ("🏒", "Corporate Author Protection", "yellow"),
            "entry_type": ("πŸ“‹", "Entry Type Correction", "cyan"),
            "title_case": ("πŸ”€", "Title Capitalization Protection", "blue"),
            "doi_mismatch": ("πŸ”—", "DOI Mismatch", "red"),
            "future_year": ("πŸ“…", "Future Year Detection", "magenta"),
            "field_cleanup": ("🧹", "Junk Field Removal", "dim"),
        }
        
        total_fixes = sum(len(fixes) for fixes in sanitize_fixes.values())
        tree = Tree(f"🧹 Sanitization Report ({total_fixes} fixes in {len(sanitize_fixes)} entries)")
        
        # Group fixes by category across all entries
        by_category = {}
        for entry_key, fixes in sanitize_fixes.items():
            for fix in fixes:
                if fix.category not in by_category:
                    by_category[fix.category] = []
                by_category[fix.category].append(fix)
        
        for cat, fixes in by_category.items():
            icon, label, color = category_info.get(cat, ("❓", cat, "white"))
            cat_node = tree.add(f"{icon} [{color}]{label} ({len(fixes)})[/{color}]")
            for fix in fixes:
                cat_node.add(f"[bold]{fix.entry_key}[/bold]: {fix.description}")
        
        self.console.print(tree)
        self.console.print("")