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