feat(infospace): add metrics history and viability tracking (S2.5)
History module with snapshot creation from check results, metrics file I/O, auto-append to history after checks, date-based snapshot lookup, and metric trend extraction. CLI commands: history, history-diff. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -331,3 +331,91 @@ def check(config_path: Optional[str], concerns: tuple, as_json: bool):
|
||||
click.echo("Metrics summary:")
|
||||
for k, v in sorted(m.items()):
|
||||
click.echo(f" {k}: {v:.4f}")
|
||||
|
||||
# Record to history
|
||||
if m:
|
||||
from markitect.infospace.history import record_check_results
|
||||
snap = record_check_results(report, cfg, root, entity_count=len(entity_list))
|
||||
if not as_json:
|
||||
click.echo(f"\nRecorded snapshot {snap.snapshot_id}")
|
||||
|
||||
|
||||
# ── history ─────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@infospace_commands.command()
|
||||
@click.option("--config", "config_path", default=None, help="Path to infospace.yaml.")
|
||||
@click.option("--metric", default=None, help="Show trend for a specific metric.")
|
||||
@click.option("--json", "as_json", is_flag=True, help="Output as JSON.")
|
||||
def history(config_path: Optional[str], metric: Optional[str], as_json: bool):
|
||||
"""Show metrics history — snapshots over time."""
|
||||
cfg, cfg_path = _load_config_or_exit(config_path)
|
||||
root = cfg_path.parent
|
||||
|
||||
from markitect.infospace.history import get_history, metric_trend
|
||||
|
||||
snapshots = get_history(cfg, root)
|
||||
if not snapshots:
|
||||
click.echo("No history found. Run 'markitect infospace check' first.")
|
||||
return
|
||||
|
||||
if metric:
|
||||
trend = metric_trend(snapshots, metric)
|
||||
if not trend:
|
||||
click.echo(f"No data for metric '{metric}'.")
|
||||
return
|
||||
if as_json:
|
||||
import json
|
||||
click.echo(json.dumps(trend, indent=2))
|
||||
else:
|
||||
click.echo(f"Trend: {metric}\n")
|
||||
for entry in trend:
|
||||
click.echo(f" {entry['date'][:19]} {entry['value']:.4f}")
|
||||
return
|
||||
|
||||
if as_json:
|
||||
import json
|
||||
click.echo(json.dumps([s.to_dict() for s in snapshots], indent=2, default=str))
|
||||
return
|
||||
|
||||
click.echo(f"History: {len(snapshots)} snapshot(s)\n")
|
||||
click.echo(f"{'#':<4} {'Date':<20} {'Entities':>8} {'Metrics':>8}")
|
||||
click.echo("-" * 42)
|
||||
for i, snap in enumerate(snapshots, 1):
|
||||
date_str = snap.created_at.isoformat()[:19]
|
||||
n_metrics = len(snap.collection_metrics)
|
||||
click.echo(f"{i:<4} {date_str:<20} {snap.entity_count:>8} {n_metrics:>8}")
|
||||
|
||||
|
||||
@infospace_commands.command(name="history-diff")
|
||||
@click.argument("date_a")
|
||||
@click.argument("date_b")
|
||||
@click.option("--config", "config_path", default=None, help="Path to infospace.yaml.")
|
||||
def history_diff(date_a: str, date_b: str, config_path: Optional[str]):
|
||||
"""Compare two history snapshots by date (YYYY-MM-DD)."""
|
||||
cfg, cfg_path = _load_config_or_exit(config_path)
|
||||
root = cfg_path.parent
|
||||
|
||||
from markitect.infospace.history import find_snapshot_by_date, get_history
|
||||
from markitect.infospace.evaluation_io import diff_snapshots
|
||||
|
||||
snapshots = get_history(cfg, root)
|
||||
if len(snapshots) < 2:
|
||||
click.echo("Need at least two snapshots to diff.")
|
||||
return
|
||||
|
||||
snap_a = find_snapshot_by_date(snapshots, date_a)
|
||||
snap_b = find_snapshot_by_date(snapshots, date_b)
|
||||
|
||||
if snap_a is None:
|
||||
click.echo(f"No snapshot found near '{date_a}'.")
|
||||
return
|
||||
if snap_b is None:
|
||||
click.echo(f"No snapshot found near '{date_b}'.")
|
||||
return
|
||||
if snap_a.snapshot_id == snap_b.snapshot_id:
|
||||
click.echo("Both dates resolve to the same snapshot.")
|
||||
return
|
||||
|
||||
diff = diff_snapshots(snap_a, snap_b)
|
||||
click.echo(diff.summary())
|
||||
|
||||
Reference in New Issue
Block a user