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("")
|