File size: 6,939 Bytes
5e952bb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
155
156
157
158
159
160
161
162
163
164
165
166
from rich.console import Console
from rich.table import Table
from rich.panel import Panel
from rich.text import Text
from rich.box import ROUNDED, DOUBLE
from src.models.report import SynthesisReport

console = Console()

def format_claim_details(claim) -> Text:
    """Format claim details with colors for CLI panels."""
    text = Text()
    text.append("Claim: ", style="bold cyan")
    text.append(f"{claim.text}\n", style="white")
    
    authors_str = ", ".join(claim.authors) if claim.authors else "Unknown"
    text.append("Source: ", style="bold green")
    text.append(f"{authors_str} ({claim.year}) [PMID: {claim.paper_id}]\n", style="dim")
    
    text.append("Population: ", style="bold yellow")
    text.append(f"{claim.population}\n", style="yellow")
    
    text.append("Context: ", style="bold magenta")
    text.append(f"{claim.context}\n", style="magenta")
    
    text.append("Quote Anchor: ", style="bold blue")
    text.append(f'"{claim.quote_anchor}"', style="italic dim blue")
    return text

def print_cli_report(
    report: SynthesisReport,
    query: str,
    time_elapsed: float = 0.0,
    cost_estimate: float = 0.0,
):
    """Render a beautiful terminal report of the synthesis results using rich."""
    
    # 1. Header Title Panel
    title_text = Text()
    title_text.append("RESEARCH SYNTHESIS & CONTRADICTION ENGINE\n", style="bold magenta")
    title_text.append("Query: ", style="bold cyan")
    title_text.append(query, style="italic cyan")
    
    console.print(Panel(
        title_text,
        border_style="magenta",
        box=DOUBLE,
        padding=(1, 2)
    ))
    console.print()

    # 2. Stats Panel
    stats_table = Table(box=ROUNDED, border_style="cyan")
    stats_table.add_column("Ingested Papers", justify="center", style="bold green")
    stats_table.add_column("Extracted Claims", justify="center", style="bold green")
    stats_table.add_column("Detected Contradictions", justify="center", style="bold yellow")
    stats_table.add_column("Genuine Contradictions", justify="center", style="bold green")
    stats_table.add_column("Time Elapsed", justify="center", style="bold blue")
    stats_table.add_column("Estimated Cost", justify="center", style="bold red")

    genuine_count = sum(1 for c in report.contradictions if c.is_genuine)
    cost = cost_estimate if cost_estimate > 0 else report.metadata.get("cost_estimate", 0.0)
    duration = time_elapsed if time_elapsed > 0 else report.metadata.get("time_elapsed", 0.0)

    stats_table.add_row(
        str(report.total_papers),
        str(report.total_claims),
        str(len(report.contradictions)),
        str(genuine_count),
        f"{duration:.2f}s",
        f"${cost:.4f}"
    )
    console.print(Panel(stats_table, title="[bold cyan]Pipeline Execution Statistics[/bold cyan]", border_style="cyan"))
    console.print()

    # 3. Grounded Summary
    summary_text = report.summary if report.summary else "No grounded summary generated yet. (Phase 3 Synthesis)"
    console.print(Panel(
        summary_text,
        title="[bold green]Grounded Synthesis Narrative[/bold green]",
        border_style="green",
        box=ROUNDED,
        padding=(1, 1)
    ))
    console.print()

    # 4. Contradiction Summary Table
    if not report.contradictions:
        console.print("[yellow]No contradictions were found in the analyzed papers.[/yellow]")
        return

    table = Table(title="[bold yellow]Ranked Contradictions Overview[/bold yellow]", box=ROUNDED, border_style="yellow")
    table.add_column("#", justify="center", style="dim")
    table.add_column("Claim A", ratio=3, overflow="ellipsis")
    table.add_column("Claim B", ratio=3, overflow="ellipsis")
    table.add_column("Score", justify="center")
    table.add_column("Type", justify="center")
    table.add_column("Genuineness", justify="center")

    for i, c in enumerate(report.contradictions, 1):
        gen_text = "[green]Genuine[/green]" if c.is_genuine else "[yellow]Scope Mismatch[/yellow]"
        table.add_row(
            str(i),
            c.claim_a.text,
            c.claim_b.text,
            f"{c.contradiction_score:.2f}",
            c.contradiction_type.name,
            gen_text
        )
    console.print(table)
    console.print()

    # 5. Detailed Contradiction Displays (Side-by-Side)
    console.print("[bold cyan]=========================== Contradiction Drill-Down ===========================[/bold cyan]")
    console.print()
    
    for i, c in enumerate(report.contradictions[:5], 1):
        # Header for this contradiction
        gen_badge = "[bold green]GENUINE CONTRADICTION[/bold green]" if c.is_genuine else "[bold yellow]SCOPE MISMATCH / PSEUDO-CONTRADICTION[/bold yellow]"
        console.print(f"[bold cyan]Contradiction #{i}:[/bold cyan] {gen_badge} | Type: [magenta]{c.contradiction_type.name}[/magenta] | Score: [bold green]{c.contradiction_score:.2f}[/bold green]")
        console.print("-" * 100)

        # Format both claims
        claim_a_text = format_claim_details(c.claim_a)
        claim_b_text = format_claim_details(c.claim_b)

        # Put them in panels
        panel_a = Panel(claim_a_text, title=f"Claim A [dim]({c.claim_a.authors[0] if c.claim_a.authors else 'Unknown'}, {c.claim_a.year})[/dim]", border_style="cyan")
        panel_b = Panel(claim_b_text, title=f"Claim B [dim]({c.claim_b.authors[0] if c.claim_b.authors else 'Unknown'}, {c.claim_b.year})[/dim]", border_style="cyan")

        # Display side-by-side using an borderless table (works better than Columns on narrow screens)
        layout_table = Table.grid(expand=True)
        layout_table.add_column(ratio=1)
        layout_table.add_column(width=2)  # spacer
        layout_table.add_column(ratio=1)
        layout_table.add_row(panel_a, "", panel_b)

        console.print(layout_table)

        # Judge analysis explanation panel
        judge_explanation = Text()
        judge_explanation.append("Analysis: ", style="bold green")
        judge_explanation.append(f"{c.explanation}\n", style="white")
        if c.scope_note:
            judge_explanation.append("Scope Difference: ", style="bold yellow")
            judge_explanation.append(f"{c.scope_note}", style="white")

        console.print(Panel(
            judge_explanation,
            title="[bold green]LLM Judge Analysis[/bold green]",
            border_style="green",
            box=ROUNDED
        ))
        console.print()

    if len(report.contradictions) > 5:
        console.print(f"[dim]... and {len(report.contradictions) - 5} more contradictions. See exported JSON report for full details.[/dim]\n")

    # 6. Footer
    console.print(Panel(
        Text("Data sources: PubMed Central & NCBI E-utilities | Model: Gemini API & Sentence Transformers\n"
             "Local cross-encoder: cross-encoder/nli-deberta-v3-large", justify="center", style="dim"),
        border_style="magenta",
        box=ROUNDED
    ))