1"""P0 audit: live FinData catalog, bars schema, OpenAPI, timestamp conventions on real rows (secrets never printed)."""2from __future__ import annotations34import json5import sys6import time7from datetime import datetime, timezone8from pathlib import Path910sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))11from chronos_x.data.client import FinDataClient # noqa: E40212from chronos_x.data.calendar import sessions # noqa: E4021314ROOT = Path(__file__).resolve().parents[1]15OUT = ROOT / "results" / "metrics" / "p0_api_audit.json"161718def main() -> None:19 c = FinDataClient(log_path=ROOT / "results" / "logs" / "provenance_audit.jsonl")20 t0 = time.time()21 health = c.health()22 datasets = c.datasets()23 schema = c.bars_schema()24 openapi = c.openapi()25 report = {"audited_at": datetime.now(timezone.utc).isoformat(), "base": c.base,26 "health": {k: health.get(k) for k in ("status", "checked_at", "alerts")},27 "lake": health.get("components", {}).get("lake"),28 "openapi": {"version": openapi.get("openapi"), "n_paths": len(openapi.get("paths", {})),29 "export_schema": openapi.get("components", {}).get("schemas", {}).get("ExportIn")},30 "bars_schema": schema,31 "datasets": {d["id"]: {k: d.get(k) for k in ("status", "row_count", "first_session", "last_session",32 "license_class")}33 for d in datasets if d["id"].startswith(("market_bars.stock", "market_bars.etf", "corporate"))}}34 # timestamp conventions on real rows35 checks = {}36 for sym, day in (("AAPL", "2024-03-08"), ("AAPL", "2024-03-11"), ("AAPL", "2024-11-29"), ("SPY", "2024-07-03")):37 t = c.bars(sym, day, day).to_pandas()38 s = sessions(day, day)[0]39 reg = t[(t["timestamp_utc"] >= s.open_utc) & (t["timestamp_utc"] <= s.close_utc)]40 checks[f"{sym}_{day}"] = {"rows_total": int(len(t)), "rows_regular": int(len(reg)), "expected_regular": s.n_slots,41 "first_ts": str(t["timestamp_utc"].min()), "last_ts": str(t["timestamp_utc"].max()),42 "open_utc": str(s.open_utc), "close_utc": str(s.close_utc),43 "closing_print_volume": float(reg[reg["timestamp_utc"] == s.close_utc]["volume"].sum()),44 "median_regular_volume": float(reg["volume"].median()),45 "quality_flags_nonempty": int((reg["quality_flags"].map(len) > 0).sum()),46 "revision_ids": sorted(int(x) for x in t["revision_id"].unique())}47 # split consistency: raw vs server-side split on AAPL around 2020-08-31 (4:1)48 raw = c.bars("AAPL", "2020-08-28", "2020-08-28").to_pandas()49 spl = c.bars("AAPL", "2020-08-28", "2020-08-28", adjustment="split").to_pandas()50 ratio = float((raw["close"] / spl["close"]).median())51 checks["AAPL_split_2020-08-31"] = {"raw_over_split_median": ratio, "expected": 4.0,52 "volume_ratio": float((spl["volume"] / raw["volume"]).median())}53 report["timestamp_checks"] = checks54 report["elapsed_s"] = round(time.time() - t0, 1)55 OUT.parent.mkdir(parents=True, exist_ok=True)56 OUT.write_text(json.dumps(report, indent=1, default=str))57 print(json.dumps({k: v for k, v in report.items() if k in ("health", "lake", "timestamp_checks", "elapsed_s")},58 indent=1, default=str))596061if __name__ == "__main__":62 main()63