"""Historical evidence reads, independent of transport and current Alpha refreshes.""" from collections import Counter from sqlalchemy import func, or_, select from ..alphas import number, sanitize from ..backtests.contracts import fingerprint from ..models import BacktestItem, BacktestResult, BacktestRun, Pnl from ..platform_checks import is_submission_limit from ..research.serialization import encode_snapshot def page(items, total, limit, offset): return {"items": items, "total": total, "limit": limit, "offset": offset, "has_more": offset + len(items) < total} def checks_summary(snapshot): """Preserve unknown check values; missing checks can never mean passed.""" checks = [] for section in ("is", "os"): metrics = snapshot.get(section) if isinstance(metrics, dict) and "checks" in metrics: raw = metrics["checks"] checks.extend({"section": section, "raw": c} for c in (raw if isinstance(raw, list) else [raw])) if "checks" in snapshot: raw = snapshot["checks"] checks.extend({"section": "root", "raw": c} for c in (raw if isinstance(raw, list) else [raw])) submission_checks = [c for c in checks if is_submission_limit(c["raw"])] checks = [c for c in checks if not is_submission_limit(c["raw"])] counts = Counter({key: 0 for key in ("PASS", "FAIL", "PENDING", "WARNING", "UNKNOWN")}) non_pass = [] for check in checks: raw = check["raw"] value = raw.get("result", raw.get("status")) if isinstance(raw, dict) else None state = value if isinstance(value, str) and value in counts else "UNKNOWN" counts[state] += 1 if state != "PASS": non_pass.append({**check, "status": state}) return {"status": "unknown" if not checks else "reported", "counts": dict(counts), "total": len(checks), "non_pass": non_pass, "submission_limits": submission_checks, "meaning": "Alpha 检查统计不含提交限制;限制为快照观测,不代表实时提交资格"} def item_summary(item, result): snapshot = sanitize(result.snapshot) if result else {} metrics = {} for section in ("is", "os"): raw = snapshot.get(section) raw = raw if isinstance(raw, dict) else {} metrics[section] = {key: number(raw.get(key)) for key in ("sharpe", "fitness", "returns", "turnover", "margin", "drawdown")} return encode_snapshot({ **{k: getattr(item, k) for k in ( "id", "run_id", "client_item_id", "expression", "selection", "combo", "alpha_type", "settings", "attempt_id", "platform_status", "collection_status", "persistence_status", "simulation_id", "alpha_id", )}, "error": sanitize(item.error), "metrics": metrics, "missing_metrics_reason": "来源未提供或非有限数字;null 不等于零", "checks": checks_summary(snapshot), "result": {"observed_at": result.observed_at, "complete": result.complete} if result else None, "artifact_reference": {"item_id": item.id}, }) class EvidenceQueries: def __init__(self, db): self.db = db async def history(self, args): query = select(BacktestItem, BacktestResult, BacktestRun).join( BacktestRun, BacktestRun.id == BacktestItem.run_id ).outerjoin(BacktestResult, BacktestResult.item_id == BacktestItem.id) for key in ("source", "reference", "research_id"): value = getattr(args, key) if value is not None: query = query.where(BacktestRun.source["kind" if key == "source" else key].as_string() == value) if args.alpha_type: query = query.where(BacktestItem.alpha_type == args.alpha_type) if args.status: query = query.where(BacktestRun.status == args.status) if args.created_from: query = query.where(BacktestRun.created_at >= args.created_from) if args.created_to: query = query.where(BacktestRun.created_at <= args.created_to) if args.scope: for source, target in (("instrument_type", "instrumentType"), ("region", "region"), ("universe", "universe")): query = query.where(BacktestItem.settings[target].as_string() == getattr(args.scope, source)) query = query.where(BacktestItem.settings["delay"].as_integer() == args.scope.delay) if args.q: escaped = args.q.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_") query = query.where(or_(*(getattr(BacktestItem, key).ilike(f"%{escaped}%", escape="\\") for key in ("expression", "selection", "combo")))) matches = {} if args.candidates: for c in args.candidates: matches.setdefault(fingerprint(c.platform_input()), []).append(c.client_item_id) query = query.where(BacktestItem.fingerprint.in_(matches)) total = await self.db.scalar(select(func.count()).select_from(query.subquery())) rows = (await self.db.execute(query.order_by(BacktestRun.created_at.desc(), BacktestRun.id, BacktestItem.ordinal) .limit(args.limit).offset(args.offset))).all() items = [{**item_summary(i, r), "source": run.source, "run_status": run.status, "created_at": run.created_at, "matched_candidates": matches.get(i.fingerprint, []), "match_type": "exact_input" if args.candidates else "filter"} for i, r, run in rows] return encode_snapshot(page(items, total, args.limit, args.offset)) async def results(self, args): query = select(BacktestItem, BacktestResult).outerjoin( BacktestResult, BacktestResult.item_id == BacktestItem.id ).where(BacktestItem.run_id == args.run_id) if args.item_ids: query = query.where(BacktestItem.id.in_(args.item_ids)) total = await self.db.scalar(select(func.count()).select_from(query.subquery())) rows = (await self.db.execute(query.order_by(BacktestItem.ordinal).limit(args.limit).offset(args.offset))).all() return {"backtest_run_id": args.run_id, **page([item_summary(i, r) for i, r in rows], total, args.limit, args.offset)} async def artifact(self, args): from .service import ResearchError item = await self.db.get(BacktestItem, args.item_id) if not item: raise ResearchError("NOT_FOUND", "候选不存在") result = await self.db.get(BacktestResult, item.id) if args.kind == "components": from ..superalpha.evidence import actual_components return await actual_components(self.db, item.id, args.limit, args.offset) if args.kind == "snapshot": # Top-level entries retain complete nested values; no hidden string/list truncation. entries = [{"key": k, "value": v} for k, v in sanitize(result.snapshot).items()] if result else [] return encode_snapshot({"item_id": item.id, "kind": args.kind, "status": "available" if result else "not_available", "observed_at": result.observed_at if result else None, "complete": result.complete if result else False, **page(entries[args.offset:args.offset + args.limit], len(entries), args.limit, args.offset)}) pnl = await self.db.get(Pnl, item.alpha_id) if item.alpha_id else None points = pnl.points if pnl else [] points = [p for p in points if (not args.date_from or p["date"][:10] >= args.date_from.isoformat()) and (not args.date_to or p["date"][:10] <= args.date_to.isoformat())] return encode_snapshot({"item_id": item.id, "alpha_id": item.alpha_id, "kind": args.kind, "status": "available" if pnl else "not_cached", "fetched_at": pnl.fetched_at if pnl else None, "units": "供应商原始累计值;未提供货币或规模单位", **page(points[args.offset:args.offset + args.limit], len(points), args.limit, args.offset)})