"""Domain-owned AI capabilities; caller owns authorization and transactions.""" from pydantic import Field from ..schemas import AlphaFilters, BulkInput, BulkUpdate, Contract, ResearchInput, ResearchUpdate from .capabilities import Capability, EmptyArgs class SearchArgs(Contract): filters: AlphaFilters = Field(default_factory=AlphaFilters) class AlphaArgs(Contract): alpha_id: str = Field(min_length=1, max_length=100, pattern=r"^[A-Za-z0-9_-]+$") class ResearchArgs(AlphaArgs): changes: ResearchInput async def search(ctx, args): data = await ctx.business.search_alphas(args.filters) return {**data, "filters": args.filters.model_dump(mode="json")} async def pnl(ctx, args): data = await ctx.business.get_alpha_pnl(args.alpha_id) points = data.pop("points") # Chart data is fetched by the UI; keep large series out of model context. series = data.pop("series", []) return { **data, "alpha_id": args.alpha_id, "count": len(points), "series": [{"id": item["id"], "label": item["label"]} for item in series], "first": points[0] if points else None, "last": points[-1] if points else None, "null_count": sum(p["value"] is None for p in points), } async def research_preview(ctx, ids, changes): """Fix before/after values and versions using the same validation as execution.""" targets, versions = [], {} for alpha_id in ids: before = (await ctx.business.get_alpha(alpha_id))["research"] after = {**before, **changes(before)} ResearchInput.model_validate({k: after[k] for k in ("note", "tags", "favorite", "state")}) targets.append({"alpha_id": alpha_id, "before": before, "after": after}) versions[alpha_id] = before["version"] return {"targets": targets, "versions": versions} async def preview_research(ctx, args): return await research_preview( ctx, [args.alpha_id], lambda before: args.changes.model_dump(exclude_unset=True) ) async def preview_bulk(ctx, args): return await research_preview( ctx, args.alpha_ids, lambda before: { "tags": sorted((set(before["tags"]) | set(args.add_tags)) - set(args.remove_tags)), **({"state": args.state} if args.state else {}), }, ) async def update_research(ctx, args, preview): return await ctx.business.update_research( args.alpha_id, ResearchUpdate( **args.changes.model_dump(exclude_unset=True), version=preview["versions"][args.alpha_id] ), ) async def update_bulk(ctx, args, preview): return await ctx.business.bulk_update_research( BulkUpdate(**args.model_dump(), versions=preview["versions"]) ) INSTRUCTIONS = "缺失指标保持未知;Turnover 0.15 表示15%。陈述依据、Alpha ID 和数据时间,区分当前页与全部结果。\n只传需要修改的研究字段。批量操作固定ID,最多100个。不要自行扩大选中范围。" CAPABILITIES = ( Capability( name="search_alphas", schema=SearchArgs, description="按明确筛选条件查询本地 Alpha。Turnover 0.15 表示 15%;支持分页,禁止把当前页当作全部结果。", label="查询 Alpha", renderer="alpha", effect="query", handler=search, ), Capability( name="get_alpha_facets", schema=EmptyArgs, description="获取可用地区、类型、状态、标签与本地 Alpha 总数。", label="查询筛选选项", renderer="alpha", effect="query", handler=lambda ctx, args: ctx.business.get_alpha_facets(), ), Capability( name="get_alpha", schema=AlphaArgs, description="读取指定 Alpha 的表达式、指标、已有检查和本地研究记录。", label="读取 Alpha", renderer="alpha", effect="query", handler=lambda ctx, args: ctx.business.get_alpha(args.alpha_id), ), Capability( name="get_alpha_pnl", schema=AlphaArgs, description="读取指定 Alpha 的 PnL 缓存摘要。缺失缓存时说明情况,不自动刷新。", label="读取 PnL 缓存", renderer="alpha", effect="query", handler=pnl, ), Capability( name="update_research", schema=ResearchArgs, description="提出指定 Alpha 的本地研究记录修改。仅传要修改的字段,等待用户在界面确认。", label="修改研究记录", renderer="alpha", effect="confirm", preview=preview_research, execute=update_research, refresh=("alphas",), ), Capability( name="bulk_update_research", schema=BulkInput, description="提出固定 1–100 个 Alpha 的批量标签或研究状态修改,等待用户确认。", label="批量修改研究记录", renderer="alpha", effect="confirm", preview=preview_bulk, execute=update_bulk, refresh=("alphas",), ), )