Spaces:
Running
Running
| """Rich-based terminal rendering for the ``dataforge profile`` command. | |
| This module contains **rendering only** — no business logic, no data loading, | |
| no detector invocation. It receives a list of :class:`Issue` objects and | |
| renders them as a Rich table with color-coded severity. | |
| """ | |
| from __future__ import annotations | |
| from rich.console import Console | |
| from rich.panel import Panel | |
| from rich.table import Table | |
| from rich.text import Text | |
| from dataforge.detectors.base import Issue, Severity | |
| # Severity-to-color mapping for the Rich table. | |
| _SEVERITY_STYLE = { | |
| Severity.SAFE: "green", | |
| Severity.REVIEW: "yellow", | |
| Severity.UNSAFE: "bold red", | |
| } | |
| # Severity sort key: UNSAFE first, then REVIEW, then SAFE. | |
| _SEVERITY_ORDER = {Severity.UNSAFE: 0, Severity.REVIEW: 1, Severity.SAFE: 2} | |
| def render_profile_table( | |
| issues: list[Issue], | |
| console: Console | None = None, | |
| *, | |
| file_path: str = "", | |
| ) -> None: | |
| """Render detected issues as a rich-formatted terminal table. | |
| The table is sorted by severity (UNSAFE first) then confidence | |
| (highest first). Each severity level is color-coded. | |
| Args: | |
| issues: List of Issue objects to display. | |
| console: Optional Rich Console instance (for testing / capture). | |
| If None, a new Console is created. | |
| file_path: Optional file path to display in the header panel. | |
| Example: | |
| >>> from dataforge.detectors.base import Issue, Severity | |
| >>> issues = [ | |
| ... Issue(row=3, column="price", issue_type="decimal_shift", | |
| ... severity=Severity.REVIEW, confidence=0.92, | |
| ... actual="1020.0", expected="102.0", | |
| ... reason="Value appears 10x too large"), | |
| ... ] | |
| >>> render_profile_table(issues) # doctest: +SKIP | |
| """ | |
| if console is None: | |
| console = Console() | |
| # Header panel. | |
| header_text = "DataForge Profile Report" | |
| if file_path: | |
| header_text += f" | {file_path}" | |
| console.print( | |
| Panel( | |
| header_text, | |
| style="bold cyan", | |
| expand=True, | |
| ) | |
| ) | |
| if not issues: | |
| console.print( | |
| Panel( | |
| "[green]No issues detected.[/green]", | |
| title="Result", | |
| style="green", | |
| ) | |
| ) | |
| return | |
| # Sort: UNSAFE first, then REVIEW, then SAFE; highest confidence first. | |
| sorted_issues = sorted( | |
| issues, | |
| key=lambda i: (_SEVERITY_ORDER[i.severity], -i.confidence), | |
| ) | |
| # Build the table. | |
| table = Table( | |
| title="Detected Issues", | |
| show_lines=True, | |
| header_style="bold magenta", | |
| title_style="bold white", | |
| ) | |
| table.add_column("Row", style="dim", justify="right", width=5) | |
| table.add_column("Column", style="cyan", min_width=12) | |
| table.add_column("Issue Type", style="white", min_width=14) | |
| table.add_column("Severity", justify="center", min_width=8) | |
| table.add_column("Confidence", justify="right", min_width=10) | |
| table.add_column("Reason", min_width=30) | |
| for issue in sorted_issues: | |
| severity_style = _SEVERITY_STYLE[issue.severity] | |
| severity_text = Text(issue.severity.value.upper(), style=severity_style) | |
| confidence_pct = f"{issue.confidence:.0%}" | |
| table.add_row( | |
| str(issue.row), | |
| issue.column, | |
| issue.issue_type, | |
| severity_text, | |
| confidence_pct, | |
| issue.reason, | |
| ) | |
| console.print(table) | |
| # Summary panel. | |
| total = len(sorted_issues) | |
| by_severity: dict[Severity, int] = {} | |
| for issue in sorted_issues: | |
| by_severity[issue.severity] = by_severity.get(issue.severity, 0) + 1 | |
| summary_parts: list[str] = [f"[bold]{total}[/bold] issues found"] | |
| for sev in (Severity.UNSAFE, Severity.REVIEW, Severity.SAFE): | |
| count = by_severity.get(sev, 0) | |
| if count > 0: | |
| style = _SEVERITY_STYLE[sev] | |
| summary_parts.append(f"[{style}]{sev.value.upper()}: {count}[/{style}]") | |
| console.print( | |
| Panel( | |
| " | ".join(summary_parts), | |
| title="Summary", | |
| style="dim", | |
| ) | |
| ) | |