fa45d8aa5f
- health_checklist.json: 192.168.1.122→node122
- ocr_client.py: docstring IP→node122
- docs/market-data-requirements.md: IP→node122
- 所有API调用通过ProxyHandler({})绕过系统代理
Privoxy对node122:18003返回500,直连正常
179 lines
6.3 KiB
Python
179 lines
6.3 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Stable DSA integration adapter.
|
|
|
|
DSA should depend on this module instead of AlphaSift internal pipeline,
|
|
strategy, or model details. Keep this contract backward-compatible.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import asdict, is_dataclass
|
|
from typing import Any, Dict, List
|
|
|
|
from alphasift import __version__
|
|
from alphasift.pipeline import screen as run_screen
|
|
from alphasift.strategy import list_strategies as load_strategies
|
|
|
|
CONTRACT_VERSION = "1"
|
|
|
|
|
|
def get_contract_version() -> str:
|
|
"""Return the stable adapter contract version."""
|
|
return CONTRACT_VERSION
|
|
|
|
|
|
def get_status(context: Dict[str, Any] | None = None) -> Dict[str, Any]:
|
|
"""Return non-sensitive adapter status for DSA health checks."""
|
|
try:
|
|
strategies = list_strategies(context=context)
|
|
strategy_count = len(strategies)
|
|
available = True
|
|
error = ""
|
|
except Exception as exc: # pragma: no cover - defensive integration boundary
|
|
strategy_count = 0
|
|
available = False
|
|
error = str(exc)
|
|
|
|
payload: Dict[str, Any] = {
|
|
"available": available,
|
|
"version": __version__,
|
|
"contract_version": CONTRACT_VERSION,
|
|
"strategy_count": strategy_count,
|
|
}
|
|
if error:
|
|
payload["error"] = error
|
|
return payload
|
|
|
|
|
|
def list_strategies(context: Dict[str, Any] | None = None) -> List[Dict[str, Any]]:
|
|
"""List enabled AlphaSift strategies using a DSA-stable shape."""
|
|
return [
|
|
{
|
|
"id": item.name,
|
|
"name": item.display_name,
|
|
"description": item.description,
|
|
"version": item.version,
|
|
"category": item.category,
|
|
"tags": list(item.tags),
|
|
"market_scope": list(item.market_scope),
|
|
}
|
|
for item in load_strategies()
|
|
]
|
|
|
|
|
|
def screen(
|
|
strategy: str,
|
|
*,
|
|
market: str = "cn",
|
|
max_results: int = 20,
|
|
use_llm: bool = True,
|
|
context: Dict[str, Any] | None = None,
|
|
) -> Dict[str, Any]:
|
|
"""Run one screening request and return a stable DSA response."""
|
|
result = run_screen(
|
|
strategy,
|
|
market=market,
|
|
max_output=max_results,
|
|
use_llm=use_llm,
|
|
context=context,
|
|
)
|
|
picks = [_normalize_pick(item, index + 1) for index, item in enumerate(result.picks)]
|
|
return {
|
|
"source": "alphasift",
|
|
"contract_version": CONTRACT_VERSION,
|
|
"run_id": result.run_id,
|
|
"strategy": result.strategy,
|
|
"strategy_version": result.strategy_version,
|
|
"strategy_category": result.strategy_category,
|
|
"market": result.market,
|
|
"snapshot_count": result.snapshot_count,
|
|
"after_filter_count": result.after_filter_count,
|
|
"llm_ranked": result.llm_ranked,
|
|
"llm_market_view": result.llm_market_view,
|
|
"llm_selection_logic": result.llm_selection_logic,
|
|
"llm_portfolio_risk": result.llm_portfolio_risk,
|
|
"llm_coverage": result.llm_coverage,
|
|
"llm_parse_errors": list(result.llm_parse_errors),
|
|
"candidate_count": len(picks),
|
|
"candidates": picks,
|
|
"warnings": list(result.degradation),
|
|
"source_errors": list(result.source_errors),
|
|
}
|
|
|
|
|
|
def _normalize_pick(raw: Any, fallback_rank: int) -> Dict[str, Any]:
|
|
item = _to_plain(raw)
|
|
if not isinstance(item, dict):
|
|
item = {"code": str(item)}
|
|
score = item.get("final_score")
|
|
if score is None:
|
|
score = item.get("screen_score")
|
|
reason = item.get("llm_thesis") or item.get("ranking_reason") or item.get("risk_summary") or _build_reason(item)
|
|
return {
|
|
"rank": item.get("rank") or fallback_rank,
|
|
"code": item.get("code") or item.get("symbol") or item.get("stock_code") or "",
|
|
"name": item.get("name") or item.get("stock_name") or "",
|
|
"score": score,
|
|
"screen_score": item.get("screen_score"),
|
|
"reason": reason,
|
|
"risk_level": item.get("risk_level") or "",
|
|
"risk_flags": list(item.get("risk_flags") or []),
|
|
"llm_score": item.get("llm_score"),
|
|
"llm_confidence": item.get("llm_confidence"),
|
|
"llm_sector": item.get("llm_sector") or "",
|
|
"llm_theme": item.get("llm_theme") or "",
|
|
"llm_tags": list(item.get("llm_tags") or []),
|
|
"llm_thesis": item.get("llm_thesis") or "",
|
|
"llm_catalysts": list(item.get("llm_catalysts") or []),
|
|
"llm_risks": list(item.get("llm_risks") or []),
|
|
"llm_watch_items": list(item.get("llm_watch_items") or []),
|
|
"llm_invalidators": list(item.get("llm_invalidators") or []),
|
|
"llm_style_fit": item.get("llm_style_fit") or "",
|
|
"price": item.get("price"),
|
|
"change_pct": item.get("change_pct"),
|
|
"amount": item.get("amount"),
|
|
"industry": item.get("industry") or "",
|
|
"factor_scores": dict(item.get("factor_scores") or {}),
|
|
"dsa_context": dict(item.get("dsa_context") or {}),
|
|
"dsa_news": list(item.get("dsa_news") or []),
|
|
"dsa_analysis_summary": item.get("dsa_analysis_summary") or "",
|
|
"post_analysis_summaries": dict(item.get("post_analysis_summaries") or {}),
|
|
"post_analysis_tags": list(item.get("post_analysis_tags") or []),
|
|
"raw": item,
|
|
}
|
|
|
|
|
|
def _build_reason(item: Dict[str, Any]) -> str:
|
|
summaries = item.get("post_analysis_summaries") or {}
|
|
if isinstance(summaries, dict):
|
|
summary = next((str(value) for value in summaries.values() if value), "")
|
|
if summary:
|
|
return summary
|
|
|
|
parts: List[str] = []
|
|
factors = item.get("factor_scores") or {}
|
|
if isinstance(factors, dict):
|
|
top_factors = sorted(
|
|
((key, value) for key, value in factors.items() if isinstance(value, (int, float))),
|
|
key=lambda pair: pair[1],
|
|
reverse=True,
|
|
)[:3]
|
|
if top_factors:
|
|
factor_text = ", ".join(f"{key} {value:.1f}" for key, value in top_factors)
|
|
parts.append(f"Top factors: {factor_text}")
|
|
if item.get("industry"):
|
|
parts.append(f"Industry: {item['industry']}")
|
|
if item.get("risk_level"):
|
|
parts.append(f"Risk level: {item['risk_level']}")
|
|
return "; ".join(parts)
|
|
|
|
|
|
def _to_plain(value: Any) -> Any:
|
|
if is_dataclass(value):
|
|
return asdict(value)
|
|
if hasattr(value, "model_dump"):
|
|
return value.model_dump()
|
|
if hasattr(value, "dict") and callable(value.dict):
|
|
return value.dict()
|
|
return value
|