from __future__ import annotations import argparse import json from pathlib import Path import sys PROJECT_ROOT = Path(__file__).resolve().parents[1] if str(PROJECT_ROOT) not in sys.path: sys.path.insert(0, str(PROJECT_ROOT)) from canonical_layer.features import FeatureService from config.settings import LOGS_DIR def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser( description="Run feature/anomaly engine over canonical store", ) parser.add_argument( "--baseline-window-hours", type=int, default=None, help="Baseline window in hours used for drift context", ) parser.add_argument( "--stale-refresh-threshold-hours", type=int, default=None, help="Trigger stale_refresh anomaly when latest refresh is older than this threshold", ) parser.add_argument( "--top-account-tokens", type=int, default=20, help="How many account-like tokens to keep in feature metrics", ) parser.add_argument( "--entity-limit", type=int, default=None, help="How many canonical entities to scan", ) parser.add_argument( "--output", default=str(LOGS_DIR / "features_last_run.json"), help="Where to write run summary json", ) parser.add_argument( "--strict", action="store_true", help="Exit with code 1 if feature run status is not success", ) return parser.parse_args() def main() -> int: args = parse_args() service = FeatureService.build() result = service.run_feature_engine( baseline_window_hours=args.baseline_window_hours, stale_refresh_threshold_hours=args.stale_refresh_threshold_hours, top_account_tokens=args.top_account_tokens, entity_limit=args.entity_limit, ) payload = result.to_dict() payload["feature_store_stats"] = service.stats() payload["feature_runs"] = service.list_recent_runs(limit=5) payload["active_anomalies"] = service.list_anomalies(limit=100, active_only=True) output_path = Path(args.output) output_path.parent.mkdir(parents=True, exist_ok=True) output_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8") print(json.dumps(payload, ensure_ascii=False, indent=2)) if args.strict and result.status != "success": return 1 return 0 if __name__ == "__main__": sys.exit(main())