Files
semantica/benchmarks/infrastructure/compare.py
T
KaifAhmad1 311a7b43b1 feat(cli): modern Rich terminal styling across all modules
## Summary

Overhaul the CLI and all library modules to produce polished, modern
terminal output comparable to tools like uv, gh, and cargo. Rich was
already a declared dependency but barely used — this commit wires it
throughout every layer.

## Changes by layer

### semantica/cli.py — visual overhaul
- Add imports: `box`, `Panel`, `Rule`, `Syntax`, `Text` from Rich
- Add 7 style constants (`_BRAND`, `_KEY`, `_VAL`, `_DIM`, `_SUCCESS`,
  `_WARN_STY`, `_TABLE_BOX`) for a consistent colour palette
- `_ok()` now prefixes output with a green ✓ checkmark
- New `_info()` helper (neutral · bullet, respects --quiet)
- New `_warn()` helper (yellow ⚠ prefix, never suppressed)
- New `_pprint()` helper: renders dicts/lists as syntax-highlighted JSON
  (Rich Syntax, monokai theme) instead of raw Python repr; strings
  pass through unchanged; respects --quiet
- `info` command: banner replaced with a rounded Rich Panel showing
  version + tagline; component table uses SIMPLE_HEAD box
- All 7 table sites updated: `box=SIMPLE_HEAD`, `show_edge=False`,
  consistent `_KEY`/`_VAL` column styles (KG Stats, Reasoning Engines,
  Recent Decisions, Configured Backends, Backup Info, MCP Tools)
- `_run_build()`: `console.status(spinner="dots")` wraps the blocking
  build call; skipped under --quiet / --json
- `parse`, `extract`, `embed generate`, `reason run`, `reason explain`,
  `deduplicate`: each wraps its long-running operation in a status
  spinner, guarded by --quiet / --json
- All 30+ `console.print(result)` calls replaced with `_pprint()`
- All raw `[yellow]Warning:[/yellow]` and "not running" patterns
  replaced with the new `_warn()` / `_WARN_STY` style

### semantica/explorer/__init__.py
- Error messages use `Console(stderr=True)` with `[bold red]Error:[/bold red]`
- Graph loading wrapped in `console.status()` spinner
- Startup info replaced with a cyan-bordered Rich Panel showing URL,
  API docs, and health endpoint

### Library internals — replace print() with structured logger calls
All modules below had active `print()` calls that bypassed the logging
framework, corrupted spinners, and polluted stdout in piped/programmatic
use. All replaced with appropriate `self.logger.*` calls:

- `semantica/kg/graph_builder.py` — 23 calls: entity resolution
  progress, graph structure steps, GraphStore persistence timing, and
  the two `='*60` completion banners → `self.logger.info/debug()`
- `semantica/semantic_extract/methods.py` — 4 verbose-mode debug
  prints → `logger.debug()`
- `semantica/semantic_extract/relation_extractor.py` — progress +
  error prints → `self.logger.debug/warning()` with `exc_info`
- `semantica/semantic_extract/triplet_extractor.py` — same pattern
- `semantica/semantic_extract/semantic_network_extractor.py` — batch
  error prints → `self.logger.warning/error()`
- `semantica/semantic_extract/coreference_resolver.py` — error print
  → `self.logger.error()`
- `semantica/semantic_extract/providers.py` — debug print →
  `self.logger.debug()`

### Tooling
- `benchmarks/benchmarks_runner.py`: Rule banner, ✓/✗/⚠ status lines,
  Rule separators around regression alert
- `benchmarks/infrastructure/compare.py`: removed manual ANSI escape
  codes; comparison output is now a Rich Table with SIMPLE_HEAD;
  summary uses coloured Rule + styled SUCCESS/FAILURE messages
- `cookbook/advanced/snowflake_ingestion_examples.py`: `_section()`
  helper using Rule; tabular data rendered as Rich Table; result lines
  use ✓/✗/⚠ prefixes; logger.error already present, retained
- `docs_check.py`: `pass`/`FAIL` lines use `[bold green]` /
  `[bold red]`; summary uses styled output

## Tests
- `tests/test_cli_commands.py`: fix 3 pre-existing mock mismatches
  - `test_kg_stats_json_with_mock`: mock now uses `compute_metrics()`
    (the method the code actually calls) instead of `get_statistics()`
  - `test_dry_run_not_needed_extract_is_read_only` and
    `test_stdin_input`: mock now provides `NERExtractor`,
    `RelationExtractor`, `TripletExtractor`, `EventDetector`
    (the classes the code imports) instead of `SemanticAnalyzer`
  Result: 230/230 tests pass (was 227/230)
- `tests/verify_rich_cli.py`: new verification script; exercises all
  14 command groups (92 --help checks, table rendering, dry-run
  formatting, --json mode, _pprint helper); 111 pass, 0 fail
2026-06-04 12:34:12 +05:30

112 lines
3.3 KiB
Python

import argparse
import json
import sys
from pathlib import Path
from typing import Any, Dict, List
from rich import box
from rich.console import Console
from rich.rule import Rule
from rich.table import Table
console = Console()
def load_results(filepath: str) -> Dict[str, Any]:
with open(filepath, "r") as f:
return json.load(f)
def calc_z_score(current_mean, base_mean, base_stddev):
"""
Z-Score indicates how many standard deviations
away current run is from baseline
"""
if base_stddev == 0:
return 0 if current_mean == base_mean else 100.0
return (current_mean - base_mean) / base_stddev
def compare_benchmarks(
baseline: Dict[str, Any], current: Dict[str, Any], threshold_pct: float = 10.0
) -> bool:
"""
Uses Mean for % change and Z-score for noise detection.
Returns True if regressions were detected.
"""
baseline_map = {b["name"]: b for b in baseline["benchmarks"]}
current_map = {b["name"]: b for b in current["benchmarks"]}
table = Table(
title="[bold]Benchmark Comparison[/bold]",
box=box.SIMPLE_HEAD,
show_edge=False,
padding=(0, 1),
)
table.add_column("Benchmark", style="cyan", no_wrap=False, max_width=60)
table.add_column("Change %", justify="right")
table.add_column("Sigma (Z)", justify="right")
table.add_column("Status")
regressions: List[str] = []
for name, curr in current_map.items():
base = baseline_map.get(name)
if not base:
table.add_row(name, "—", "—", "[dim]NEW[/dim]")
continue
m1 = base["stats"]["mean"]
s1 = base["stats"]["stddev"]
m2 = curr["stats"]["mean"]
delta_pct = 0.0 if m1 == 0 else ((m2 - m1) / m1) * 100
z_score = calc_z_score(m2, m1, s1)
if delta_pct > threshold_pct and abs(z_score) > 2.0:
status = "[bold red]REGRESSION[/bold red]"
regressions.append(name)
elif delta_pct > threshold_pct:
status = "[yellow]NOISE[/yellow]"
elif delta_pct < -threshold_pct and abs(z_score) > 2.0:
status = "[bold green]IMPROVED[/bold green]"
else:
status = "[green]OK[/green]"
change_str = f"{delta_pct:+.2f}%"
z_str = f"{z_score:.2f}"
table.add_row(name, change_str, z_str, status)
console.print(table)
if regressions:
console.print(Rule(style="red"))
console.print(
f"[bold red]FAILURE:[/bold red] Performance regression detected "
f"in [cyan]{len(regressions)}[/cyan] test(s)."
)
return True
console.print(Rule(style="green"))
console.print("[bold green]SUCCESS:[/bold green] No significant regressions.")
return False
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("baseline", help="Gold standard JSON")
parser.add_argument("current", help="New run JSON")
parser.add_argument(
"--threshold", type=float, default=10.0, help="FAIL if slower by %%"
)
args = parser.parse_args()
try:
failed = compare_benchmarks(
load_results(args.baseline), load_results(args.current), args.threshold
)
sys.exit(1 if failed else 0)
except FileNotFoundError as e:
console.print(f"[bold red]Error:[/bold red] loading files: {e}")
sys.exit(0)