aria-code 4.1.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (284) hide show
  1. agents/__init__.py +32 -0
  2. agents/base.py +190 -0
  3. agents/deep/__init__.py +37 -0
  4. agents/deep/calibration_loop.py +144 -0
  5. agents/deep/critic.py +125 -0
  6. agents/deep/deepen.py +193 -0
  7. agents/deep/models.py +149 -0
  8. agents/deep/pipeline.py +164 -0
  9. agents/deep/quant_fusion.py +192 -0
  10. agents/deep/themes.py +95 -0
  11. agents/deep/tiers.py +106 -0
  12. agents/financial/__init__.py +10 -0
  13. agents/financial/catalyst.py +279 -0
  14. agents/financial/debate.py +145 -0
  15. agents/financial/earnings.py +303 -0
  16. agents/financial/fundamental.py +159 -0
  17. agents/financial/macro.py +99 -0
  18. agents/financial/news.py +207 -0
  19. agents/financial/risk.py +132 -0
  20. agents/financial/sector.py +279 -0
  21. agents/financial/synthesis.py +274 -0
  22. agents/financial/technical.py +258 -0
  23. agents/portfolio_agent.py +333 -0
  24. agents/realty/__init__.py +62 -0
  25. agents/realty/asset_diagnosis.py +150 -0
  26. agents/realty/business_match.py +165 -0
  27. agents/realty/cashflow_verify.py +208 -0
  28. agents/realty/contract_rules.py +209 -0
  29. agents/realty/energy_anomaly.py +188 -0
  30. agents/realty/exit_settlement.py +207 -0
  31. agents/realty/fulfillment_risk.py +205 -0
  32. agents/realty/ops_optimize.py +159 -0
  33. agents/realty/revenue_share.py +214 -0
  34. agents/registry.py +144 -0
  35. agents/sports/__init__.py +0 -0
  36. agents/sports/football_agent.py +169 -0
  37. agents/team.py +289 -0
  38. aliyun_data_client.py +660 -0
  39. apps/README.md +12 -0
  40. apps/__init__.py +2 -0
  41. apps/channels/README.md +15 -0
  42. apps/cli/README.md +13 -0
  43. apps/cli/__init__.py +2 -0
  44. apps/cli/bootstrap.py +99 -0
  45. apps/cli/codegen_paths.py +29 -0
  46. apps/cli/commands/__init__.py +16 -0
  47. apps/cli/commands/analysis_cmds.py +288 -0
  48. apps/cli/commands/backtest_cmds.py +1887 -0
  49. apps/cli/commands/broker_cmds.py +1154 -0
  50. apps/cli/commands/business_workflow_cmds.py +289 -0
  51. apps/cli/commands/catalog.py +84 -0
  52. apps/cli/commands/data_cmds.py +405 -0
  53. apps/cli/commands/diagnostic_cmds.py +179 -0
  54. apps/cli/commands/diagnostic_ops_cmds.py +696 -0
  55. apps/cli/commands/finance_render.py +12 -0
  56. apps/cli/commands/market.py +399 -0
  57. apps/cli/commands/market_cmds.py +1276 -0
  58. apps/cli/commands/market_context.py +425 -0
  59. apps/cli/commands/market_render.py +7 -0
  60. apps/cli/commands/model_cmds.py +1579 -0
  61. apps/cli/commands/ops_cmds.py +668 -0
  62. apps/cli/commands/portfolio_cmds.py +962 -0
  63. apps/cli/commands/report.py +377 -0
  64. apps/cli/commands/scaffold_templates.py +617 -0
  65. apps/cli/commands/session_cmds.py +179 -0
  66. apps/cli/commands/session_ux_cmds.py +280 -0
  67. apps/cli/commands/team.py +588 -0
  68. apps/cli/commands/team_render.py +8 -0
  69. apps/cli/commands/ui_cmds.py +358 -0
  70. apps/cli/commands/workflow_cmds.py +279 -0
  71. apps/cli/commands/workspace_cmds.py +1414 -0
  72. apps/cli/config_paths.py +70 -0
  73. apps/cli/config_store.py +61 -0
  74. apps/cli/deterministic.py +122 -0
  75. apps/cli/direct.py +48 -0
  76. apps/cli/github_app_auth.py +135 -0
  77. apps/cli/handlers/__init__.py +11 -0
  78. apps/cli/handlers/broker_handlers.py +122 -0
  79. apps/cli/handlers/chart_handlers.py +1309 -0
  80. apps/cli/handlers/market_handlers.py +2509 -0
  81. apps/cli/handlers/realty_handlers.py +114 -0
  82. apps/cli/handlers/strategy_advice.py +82 -0
  83. apps/cli/hooks.py +180 -0
  84. apps/cli/i18n.py +284 -0
  85. apps/cli/intent.py +136 -0
  86. apps/cli/intent_router.py +217 -0
  87. apps/cli/lifecycle_hooks.py +48 -0
  88. apps/cli/main.py +29 -0
  89. apps/cli/market_metadata.py +135 -0
  90. apps/cli/market_universe.py +265 -0
  91. apps/cli/message_processing.py +257 -0
  92. apps/cli/plan_mode.py +139 -0
  93. apps/cli/plotly_html.py +15 -0
  94. apps/cli/prediction_feedback.py +202 -0
  95. apps/cli/preflight.py +497 -0
  96. apps/cli/project_aria.py +60 -0
  97. apps/cli/prompts/__init__.py +0 -0
  98. apps/cli/prompts/coding.py +658 -0
  99. apps/cli/prompts/system_prompts.py +531 -0
  100. apps/cli/prompts/ui.py +434 -0
  101. apps/cli/providers/__init__.py +1 -0
  102. apps/cli/providers/base.py +271 -0
  103. apps/cli/providers/chat_routing.py +80 -0
  104. apps/cli/providers/llm/__init__.py +1 -0
  105. apps/cli/providers/llm/ollama_stream.py +1170 -0
  106. apps/cli/providers/llm/sse_stream.py +216 -0
  107. apps/cli/providers/runtime_bridge.py +185 -0
  108. apps/cli/runtime_consumer.py +489 -0
  109. apps/cli/session_export.py +87 -0
  110. apps/cli/session_jsonl.py +207 -0
  111. apps/cli/session_store.py +112 -0
  112. apps/cli/todo_tracker.py +190 -0
  113. apps/cli/tools/__init__.py +40 -0
  114. apps/cli/tools/context.py +46 -0
  115. apps/cli/tools/file_tools.py +112 -0
  116. apps/cli/tools/market_tools.py +549 -0
  117. apps/cli/tools/notebook_tools.py +111 -0
  118. apps/cli/tools/system_tools.py +669 -0
  119. apps/cli/tools/write_tools.py +715 -0
  120. apps/cli/tradingview_bridge.py +434 -0
  121. apps/cli/update_check.py +152 -0
  122. apps/cli/utils/__init__.py +0 -0
  123. apps/cli/utils/market_detect.py +1578 -0
  124. apps/daemon/README.md +14 -0
  125. apps/vscode/README.md +115 -0
  126. apps/vscode/package.json +70 -0
  127. aria_cli.py +11636 -0
  128. aria_code-4.1.3.dist-info/METADATA +952 -0
  129. aria_code-4.1.3.dist-info/RECORD +284 -0
  130. aria_code-4.1.3.dist-info/WHEEL +5 -0
  131. aria_code-4.1.3.dist-info/entry_points.txt +2 -0
  132. aria_code-4.1.3.dist-info/licenses/LICENSE +121 -0
  133. aria_code-4.1.3.dist-info/top_level.txt +50 -0
  134. aria_daemon.py +1295 -0
  135. aria_feishu_bot.py +1359 -0
  136. aria_relay_client.py +182 -0
  137. aria_relay_server.py +405 -0
  138. aria_telegram_bot.py +202 -0
  139. ariarc.py +328 -0
  140. artifacts.py +491 -0
  141. backtest_report.py +472 -0
  142. brokers/__init__.py +72 -0
  143. brokers/base.py +207 -0
  144. brokers/capabilities.py +264 -0
  145. brokers/cn/__init__.py +10 -0
  146. brokers/cn/easytrader_broker.py +193 -0
  147. brokers/cn/futu_broker.py +194 -0
  148. brokers/cn/longbridge_broker.py +190 -0
  149. brokers/cn/tiger_broker.py +196 -0
  150. brokers/cn/xtquant_broker.py +175 -0
  151. brokers/config.py +364 -0
  152. brokers/intl/__init__.py +5 -0
  153. brokers/intl/alpaca_broker.py +183 -0
  154. brokers/intl/ibkr_broker.py +215 -0
  155. brokers/intl/webull_broker.py +156 -0
  156. brokers/paper_broker.py +259 -0
  157. brokers/planning.py +296 -0
  158. brokers/registry.py +181 -0
  159. brokers/trading.py +237 -0
  160. change_store.py +127 -0
  161. command_safety.py +19 -0
  162. computer_use_tools.py +504 -0
  163. dashboard_generator.py +578 -0
  164. data_analysis_tools.py +808 -0
  165. data_cleaner.py +483 -0
  166. data_service.py +481 -0
  167. datasources/__init__.py +23 -0
  168. datasources/base.py +166 -0
  169. datasources/router.py +221 -0
  170. datasources/sources/__init__.py +15 -0
  171. datasources/sources/akshare_source.py +269 -0
  172. datasources/sources/alpha_vantage_source.py +202 -0
  173. datasources/sources/edgar_source.py +218 -0
  174. datasources/sources/finnhub_source.py +197 -0
  175. datasources/sources/fred_source.py +219 -0
  176. datasources/sources/tushare_source.py +141 -0
  177. datasources/sources/web_scraper_source.py +278 -0
  178. datasources/sources/world_bank_source.py +205 -0
  179. datasources/sources/yfinance_source.py +152 -0
  180. demo_player.py +204 -0
  181. doctor.py +508 -0
  182. file_analysis_tools.py +734 -0
  183. finance_formulas.py +389 -0
  184. football_data_client.py +1670 -0
  185. intent_classifier.py +358 -0
  186. local_finance_tools.py +3221 -0
  187. local_llm_provider.py +552 -0
  188. macro_tools.py +368 -0
  189. market_data_client.py +1899 -0
  190. mcp_client.py +506 -0
  191. memory_manager.py +245 -0
  192. model_capability.py +416 -0
  193. notification_tools.py +248 -0
  194. packages/__init__.py +23 -0
  195. packages/aria_agents/__init__.py +5 -0
  196. packages/aria_agents/manifest.py +69 -0
  197. packages/aria_core/__init__.py +34 -0
  198. packages/aria_core/architecture.py +192 -0
  199. packages/aria_core/export.py +124 -0
  200. packages/aria_core/manifest.py +65 -0
  201. packages/aria_infra/__init__.py +15 -0
  202. packages/aria_infra/arthera.py +52 -0
  203. packages/aria_infra/doctor.py +246 -0
  204. packages/aria_infra/product.py +37 -0
  205. packages/aria_mcp/__init__.py +25 -0
  206. packages/aria_mcp/bridge.py +38 -0
  207. packages/aria_mcp/config.py +97 -0
  208. packages/aria_mcp/tools.py +61 -0
  209. packages/aria_sdk/__init__.py +19 -0
  210. packages/aria_sdk/client.py +396 -0
  211. packages/aria_sdk/providers.py +70 -0
  212. packages/aria_sdk/streaming.py +73 -0
  213. packages/aria_sdk/types.py +86 -0
  214. packages/aria_services/__init__.py +55 -0
  215. packages/aria_services/context.py +258 -0
  216. packages/aria_services/data.py +11 -0
  217. packages/aria_services/provider_health.py +189 -0
  218. packages/aria_services/registry.py +213 -0
  219. packages/aria_services/usage.py +138 -0
  220. packages/aria_skills/__init__.py +5 -0
  221. packages/aria_skills/registry.py +59 -0
  222. packages/aria_tools/__init__.py +5 -0
  223. packages/aria_tools/registry.py +128 -0
  224. packages/quant_engine/__init__.py +6 -0
  225. packages/quant_engine/sports/__init__.py +72 -0
  226. packages/quant_engine/sports/calibrator.py +353 -0
  227. packages/quant_engine/sports/dixon_coles.py +234 -0
  228. packages/quant_engine/sports/elo.py +299 -0
  229. packages/quant_engine/sports/form.py +188 -0
  230. packages/quant_engine/sports/h2h.py +195 -0
  231. packages/quant_engine/sports/ml_model.py +354 -0
  232. packages/quant_engine/sports/predictor.py +311 -0
  233. packages/quant_engine/sports/tracker.py +664 -0
  234. packages/quant_engine/stochastic/__init__.py +27 -0
  235. packages/quant_engine/stochastic/gbm_enhanced.py +195 -0
  236. packages/quant_engine/stochastic/ito_calculus.py +477 -0
  237. packages/quant_engine/stochastic/kelly_criterion.py +181 -0
  238. packages/quant_engine/stochastic/monte_carlo_advanced.py +95 -0
  239. packages/quant_engine/stochastic/options_pricing.py +573 -0
  240. packages/quant_engine/stochastic/stochastic_processes.py +90 -0
  241. plan_utils.py +194 -0
  242. plugin_loader.py +328 -0
  243. portfolio_ledger.py +262 -0
  244. privacy/__init__.py +5 -0
  245. privacy/feedback.py +123 -0
  246. project_tools.py +525 -0
  247. providers/__init__.py +30 -0
  248. providers/llm/__init__.py +19 -0
  249. providers/llm/anthropic.py +184 -0
  250. providers/llm/base.py +139 -0
  251. providers/llm/ollama.py +128 -0
  252. providers/llm/openai_compat.py +282 -0
  253. providers/llm/registry.py +358 -0
  254. realty_data_tools.py +659 -0
  255. report_generator.py +1314 -0
  256. runtime/__init__.py +103 -0
  257. runtime/agent_loop.py +1183 -0
  258. runtime/approval.py +51 -0
  259. runtime/events.py +102 -0
  260. runtime/gateway.py +128 -0
  261. runtime/lsp.py +346 -0
  262. runtime/subagent.py +258 -0
  263. runtime/tool_executor.py +104 -0
  264. runtime/tool_policy.py +106 -0
  265. safety/__init__.py +21 -0
  266. safety/permissions.py +275 -0
  267. setup_wizard.py +653 -0
  268. strategy_vault.py +420 -0
  269. ui/__init__.py +100 -0
  270. ui/banner.py +310 -0
  271. ui/completer.py +391 -0
  272. ui/console.py +271 -0
  273. ui/image_render.py +243 -0
  274. ui/input_box.py +376 -0
  275. ui/picker.py +195 -0
  276. ui/render/__init__.py +11 -0
  277. ui/render/finance.py +1480 -0
  278. ui/render/market.py +225 -0
  279. ui/render/output.py +681 -0
  280. ui/render/team.py +346 -0
  281. ui/robot.py +235 -0
  282. workspace/__init__.py +6 -0
  283. workspace/files.py +170 -0
  284. workspace/verify.py +113 -0
@@ -0,0 +1,588 @@
1
+ """Team command parsing and execution helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import contextlib
7
+ from dataclasses import dataclass
8
+ from datetime import datetime
9
+ import io
10
+ import logging
11
+ import sys
12
+ from pathlib import Path
13
+ from typing import Any, Callable
14
+
15
+
16
+ def _detect_lang(text: str) -> str:
17
+ if not text:
18
+ return "zh"
19
+ zh_chars = sum(1 for c in text if '一' <= c <= '鿿')
20
+ return "zh" if zh_chars / max(len(text), 1) > 0.15 else "en"
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+ DEFAULT_TEAM_AGENTS = ["macro", "fundamental", "technical", "risk"]
25
+ FULL_TEAM_AGENTS = ["macro", "fundamental", "technical", "risk", "news", "catalyst", "sector"]
26
+
27
+
28
+ @dataclass(frozen=True)
29
+ class TeamArgs:
30
+ symbols_raw: list[str]
31
+ agent_names: list[str] | None = None
32
+ use_full_team: bool = False
33
+
34
+
35
+ @dataclass(frozen=True)
36
+ class TeamAnalysisResult:
37
+ symbol: str
38
+ team_result: Any
39
+ data_bundle: Any = None
40
+ quality_notes: list[str] | None = None
41
+ captured_noise: str = ""
42
+
43
+
44
+ @dataclass(frozen=True)
45
+ class SavedTeamReport:
46
+ path: Path
47
+ metadata_path: Path | None = None
48
+
49
+
50
+ def parse_team_args(args: str) -> TeamArgs:
51
+ parts = args.strip().split()
52
+ agent_names = None
53
+ symbols_raw: list[str] = []
54
+ use_full_team = False
55
+ idx = 0
56
+ while idx < len(parts):
57
+ part = parts[idx]
58
+ if part == "--agents" and idx + 1 < len(parts):
59
+ agent_names = [agent.strip() for agent in parts[idx + 1].split(",") if agent.strip()]
60
+ idx += 2
61
+ elif part.startswith("--agents="):
62
+ agent_names = [agent.strip() for agent in part.split("=", 1)[1].split(",") if agent.strip()]
63
+ idx += 1
64
+ elif part == "--full":
65
+ use_full_team = True
66
+ idx += 1
67
+ else:
68
+ symbols_raw.append(part)
69
+ idx += 1
70
+ if use_full_team and not agent_names:
71
+ agent_names = list(FULL_TEAM_AGENTS)
72
+ return TeamArgs(symbols_raw=symbols_raw, agent_names=agent_names, use_full_team=use_full_team)
73
+
74
+
75
+ def resolve_team_symbols(args: TeamArgs, config: dict[str, Any], limit: int = 3) -> list[str]:
76
+ if not args.symbols_raw or args.symbols_raw[0].lower() == "watchlist":
77
+ return [str(symbol).upper() for symbol in config.get("watchlist", ["AAPL", "MSFT", "NVDA"])[:limit]]
78
+ return [symbol.upper() for symbol in args.symbols_raw[:limit]]
79
+
80
+
81
+ def team_agent_names(args: TeamArgs) -> list[str]:
82
+ return args.agent_names or list(DEFAULT_TEAM_AGENTS)
83
+
84
+
85
+ def compact_market_cap(value: Any, currency: str = "USD") -> str:
86
+ try:
87
+ cap = float(value)
88
+ if cap <= 0:
89
+ return "-"
90
+ if cap >= 1e12:
91
+ return f"{currency} {cap / 1e12:.2f}T"
92
+ if cap >= 1e9:
93
+ return f"{currency} {cap / 1e9:.1f}B"
94
+ if cap >= 1e6:
95
+ return f"{currency} {cap / 1e6:.0f}M"
96
+ return f"{currency} {cap:,.0f}"
97
+ except Exception:
98
+ return "-"
99
+
100
+
101
+ def _first_present(*values: Any) -> Any:
102
+ for value in values:
103
+ if value not in (None, "", [], {}):
104
+ return value
105
+ return None
106
+
107
+
108
+ def _as_float(value: Any) -> float | None:
109
+ try:
110
+ if value in (None, ""):
111
+ return None
112
+ return float(value)
113
+ except Exception:
114
+ return None
115
+
116
+
117
+ def _fmt_num(value: Any, digits: int = 2) -> str:
118
+ number = _as_float(value)
119
+ if number is None:
120
+ return "-"
121
+ return f"{number:.{digits}f}"
122
+
123
+
124
+ def _fmt_pct(value: Any, digits: int = 2) -> str:
125
+ number = _as_float(value)
126
+ if number is None:
127
+ return "-"
128
+ return f"{number:+.{digits}f}%"
129
+
130
+
131
+ def _fmt_compact_number(value: Any) -> str:
132
+ number = _as_float(value)
133
+ if number is None:
134
+ return "-"
135
+ sign = "-" if number < 0 else ""
136
+ number = abs(number)
137
+ if number >= 1e12:
138
+ return f"{sign}{number / 1e12:.2f}T"
139
+ if number >= 1e9:
140
+ return f"{sign}{number / 1e9:.2f}B"
141
+ if number >= 1e6:
142
+ return f"{sign}{number / 1e6:.2f}M"
143
+ if number >= 1e3:
144
+ return f"{sign}{number / 1e3:.1f}K"
145
+ return f"{sign}{number:.0f}"
146
+
147
+
148
+ def _dedupe_missing(values: list[Any]) -> list[str]:
149
+ return list(dict.fromkeys(str(value) for value in values if value not in (None, "", [], {})))
150
+
151
+
152
+ def team_quote_snapshot(data_bundle: Any) -> dict[str, Any]:
153
+ quote = getattr(data_bundle, "quote", {}) or {}
154
+ fundamentals = getattr(data_bundle, "fundamentals", {}) or {}
155
+ technical = getattr(data_bundle, "technical", {}) or {}
156
+ quality = dict(getattr(data_bundle, "quality", {}) or {})
157
+ snapshot = {
158
+ "symbol": getattr(data_bundle, "symbol", "") or quote.get("symbol"),
159
+ "price": quote.get("price") or quote.get("current_price") or quote.get("regular_market_price"),
160
+ "change_pct": quote.get("change_pct") or quote.get("change_percent") or quote.get("pct_change"),
161
+ "currency": quote.get("currency") or quote.get("currency_symbol") or "USD",
162
+ "change": quote.get("change"),
163
+ "volume": quote.get("volume") or technical.get("volume"),
164
+ "high": quote.get("high"),
165
+ "low": quote.get("low"),
166
+ "open": quote.get("open"),
167
+ "prev_close": quote.get("prev_close"),
168
+ "name": quote.get("name") or fundamentals.get("name"),
169
+ "sector": fundamentals.get("sector"),
170
+ "industry": fundamentals.get("industry"),
171
+ "market_cap": quote.get("market_cap") or quote.get("marketCap") or fundamentals.get("market_cap"),
172
+ "pe_ratio": _first_present(fundamentals.get("pe_ratio"), fundamentals.get("pe_ttm"), quote.get("pe_ttm")),
173
+ "fwd_pe": fundamentals.get("fwd_pe"),
174
+ "eps": _first_present(fundamentals.get("eps"), fundamentals.get("fwd_eps")),
175
+ "roe": fundamentals.get("roe"),
176
+ "revenue_growth": fundamentals.get("revenue_growth"),
177
+ "analyst_target": fundamentals.get("analyst_target"),
178
+ "recommendation": fundamentals.get("recommendation"),
179
+ "beta": fundamentals.get("beta"),
180
+ "rsi": technical.get("rsi"),
181
+ "macd_hist": technical.get("macd_hist"),
182
+ "ma20": technical.get("ma20"),
183
+ "ma60": technical.get("ma60"),
184
+ "support": technical.get("support") or technical.get("supports"),
185
+ "resistance": technical.get("resistance") or technical.get("resistances"),
186
+ "history_bars": technical.get("history_bars"),
187
+ "provider_chain": getattr(data_bundle, "provider_chain", []) or [],
188
+ "missing_fields": getattr(data_bundle, "missing_fields", []) or [],
189
+ "warnings": getattr(data_bundle, "warnings", []) or [],
190
+ "errors": getattr(data_bundle, "errors", []) or [],
191
+ "quality": quality,
192
+ "stale": bool(quality.get("stale", False)),
193
+ "status": getattr(data_bundle, "status", "data_unavailable"),
194
+ "timestamp": getattr(data_bundle, "timestamp", ""),
195
+ }
196
+ display_missing = list(snapshot["missing_fields"])
197
+ if snapshot.get("volume") in (None, "", [], {}):
198
+ display_missing.append("volume")
199
+ if snapshot.get("analyst_target") in (None, "", [], {}):
200
+ display_missing.append("analyst_target")
201
+ if snapshot.get("beta") in (None, "", [], {}):
202
+ display_missing.append("risk_metrics")
203
+ snapshot["missing_fields"] = _dedupe_missing(display_missing)
204
+ if snapshot["missing_fields"] and snapshot["status"] in {"complete", "ok"}:
205
+ snapshot["status"] = "partial"
206
+ quality_missing = _dedupe_missing(list(quality.get("missing_fields") or []) + snapshot["missing_fields"])
207
+ if quality_missing:
208
+ quality["missing_fields"] = quality_missing
209
+ if quality.get("status") in {"complete", "ok"}:
210
+ quality["status"] = "partial"
211
+ snapshot["quality"] = quality
212
+ return snapshot
213
+
214
+
215
+ def build_team_market_context(data_bundle: Any) -> dict[str, Any]:
216
+ """Build the deterministic real-data block passed into synthesis."""
217
+ if not data_bundle:
218
+ return {}
219
+ snapshot = team_quote_snapshot(data_bundle)
220
+ quote = getattr(data_bundle, "quote", {}) or {}
221
+ fundamentals = getattr(data_bundle, "fundamentals", {}) or {}
222
+ technical = getattr(data_bundle, "technical", {}) or {}
223
+ quality = snapshot.get("quality") or {}
224
+ lines = [
225
+ f"data_status={snapshot.get('status') or 'unknown'}",
226
+ f"providers={', '.join(snapshot.get('provider_chain') or []) or 'unknown'}",
227
+ f"missing={', '.join(snapshot.get('missing_fields') or []) or 'none'}",
228
+ f"price={snapshot.get('currency') or 'USD'} {snapshot.get('price')}",
229
+ f"change_pct={snapshot.get('change_pct')}",
230
+ f"volume={snapshot.get('volume')}",
231
+ f"market_cap={compact_market_cap(snapshot.get('market_cap'), snapshot.get('currency') or 'USD')}",
232
+ f"pe={snapshot.get('pe_ratio')}",
233
+ f"forward_pe={snapshot.get('fwd_pe')}",
234
+ f"eps={snapshot.get('eps')}",
235
+ f"roe={snapshot.get('roe')}",
236
+ f"revenue_growth={snapshot.get('revenue_growth')}",
237
+ f"analyst_target={snapshot.get('analyst_target')}",
238
+ f"rsi={snapshot.get('rsi')}",
239
+ f"macd_hist={snapshot.get('macd_hist')}",
240
+ f"ma20={snapshot.get('ma20')}",
241
+ f"ma60={snapshot.get('ma60')}",
242
+ f"stale={bool(snapshot.get('stale'))}",
243
+ ]
244
+ return {
245
+ "quote": quote,
246
+ "fundamentals": fundamentals,
247
+ "technical": technical,
248
+ "data_quality": quality,
249
+ "market_snapshot": snapshot,
250
+ "market_data_block": "\n".join(lines),
251
+ }
252
+
253
+
254
+ def build_team_terminal_summary(data_bundle: Any) -> str:
255
+ """Return compact Rich markup summary for the /team panel."""
256
+ if not data_bundle:
257
+ return "[#57606a]数据:[/#57606a] unavailable"
258
+ snapshot = team_quote_snapshot(data_bundle)
259
+ currency = snapshot.get("currency") or "USD"
260
+ price = snapshot.get("price")
261
+ price_text = f"{currency} {_fmt_num(price)}" if price is not None else "unavailable"
262
+ cap_text = compact_market_cap(snapshot.get("market_cap"), currency)
263
+ change_text = _fmt_pct(snapshot.get("change_pct"))
264
+ volume_text = _fmt_compact_number(snapshot.get("volume"))
265
+ rsi_text = _fmt_num(snapshot.get("rsi"), 1)
266
+ macd_text = _fmt_num(snapshot.get("macd_hist"), 4)
267
+ ma20_text = _fmt_num(snapshot.get("ma20"))
268
+ ma60_text = _fmt_num(snapshot.get("ma60"))
269
+ providers = ", ".join(snapshot.get("provider_chain") or []) or "unknown"
270
+ missing = ", ".join(snapshot.get("missing_fields") or []) or "none"
271
+ status = snapshot.get("status") or "unknown"
272
+ stale = "yes" if snapshot.get("stale") else "no"
273
+ return "\n".join([
274
+ f"[bold]{snapshot.get('name') or snapshot.get('symbol') or ''}[/bold] "
275
+ f"价格 {price_text} ({change_text}) · 市值 {cap_text} · 成交量 {volume_text}",
276
+ f"技术面 RSI {rsi_text} · MACD hist {macd_text} · MA20 {ma20_text} · MA60 {ma60_text}",
277
+ f"[#57606a]数据:[/#57606a] {providers} · status {status} · stale {stale} · missing {missing}",
278
+ ])
279
+
280
+
281
+ def clean_team_synthesis_text(text: str) -> str:
282
+ """Strip raw Markdown markers that render poorly inside terminal panels."""
283
+ import re
284
+ cleaned = str(text or "").strip()
285
+ cleaned = re.sub(r"\*\*([^*]+)\*\*", r"\1", cleaned)
286
+ cleaned = re.sub(r"(?m)^\s*-\s*$", "", cleaned)
287
+ cleaned = re.sub(r"\n{3,}", "\n\n", cleaned)
288
+ return cleaned.strip()
289
+
290
+
291
+ def build_team_llm_provider(config: dict[str, Any]) -> Any:
292
+ try:
293
+ from providers.llm.base import ProviderConfig
294
+ from providers.llm.ollama import OllamaProvider
295
+
296
+ model = config.get("model", "qwen2.5:7b")
297
+ url = config.get("ollama_url", "http://localhost:11434")
298
+ return OllamaProvider(ProviderConfig(name="ollama", model=model, base_url=url))
299
+ except Exception as exc:
300
+ logger.debug("team LLM provider init failed: %s", exc)
301
+ return None
302
+
303
+
304
+ async def fetch_team_data_bundle(symbol: str) -> Any:
305
+ from packages.aria_services.data import DataService
306
+ try:
307
+ from datasources.router import get_router
308
+ router = get_router()
309
+ except Exception as exc:
310
+ logger.debug("team data router unavailable, using market client only: %s", exc)
311
+ router = False
312
+
313
+ return await asyncio.get_event_loop().run_in_executor(
314
+ None,
315
+ lambda: DataService(router=router).bundle(symbol),
316
+ )
317
+
318
+
319
+ async def run_team_analysis(
320
+ *,
321
+ symbol: str,
322
+ args: TeamArgs,
323
+ config: dict[str, Any],
324
+ sanitize_result: Callable[[Any, Any], list[str]] | None = None,
325
+ lang: str = "zh",
326
+ on_agent_done: Callable[[str, Any], None] | None = None,
327
+ ) -> TeamAnalysisResult:
328
+ from agents.team import run_team
329
+ try:
330
+ from datasources.router import get_router
331
+ data_router = get_router()
332
+ except Exception as exc:
333
+ logger.debug("team data router unavailable for agents: %s", exc)
334
+ data_router = None
335
+
336
+ llm_provider = build_team_llm_provider(config)
337
+ data_bundle = None
338
+ try:
339
+ data_bundle = await fetch_team_data_bundle(symbol)
340
+ except Exception as exc:
341
+ logger.debug("team data bundle fetch failed: %s", exc)
342
+
343
+ noisy_loggers = ["agents.base", "agents.team", "datasources.router", "data_cleaner"]
344
+ saved_levels = {name: logging.getLogger(name).level for name in noisy_loggers}
345
+ for name in noisy_loggers:
346
+ logging.getLogger(name).setLevel(logging.ERROR)
347
+
348
+ captured_stdout = io.StringIO()
349
+ captured_stderr = io.StringIO()
350
+ # The team runs inside a stdout redirect (to swallow noisy agent logs).
351
+ # Restore the real stdout just for the on_agent_done callback so its
352
+ # streaming tree output is actually visible to the user.
353
+ _real_stdout = sys.stdout
354
+
355
+ def _agent_done_proxy(name: str, result: Any) -> None:
356
+ if on_agent_done is None:
357
+ return
358
+ try:
359
+ with contextlib.redirect_stdout(_real_stdout):
360
+ on_agent_done(name, result)
361
+ except Exception as exc:
362
+ logger.debug("on_agent_done render failed: %s", exc)
363
+
364
+ try:
365
+ with contextlib.redirect_stdout(captured_stdout), contextlib.redirect_stderr(captured_stderr):
366
+ team_result = await run_team(
367
+ symbol=symbol,
368
+ agents=args.agent_names,
369
+ llm_provider=llm_provider,
370
+ data_router=data_router,
371
+ on_token=None,
372
+ on_agent_done=_agent_done_proxy if on_agent_done else None,
373
+ on_synthesis_start=None,
374
+ lang=lang,
375
+ market_context=build_team_market_context(data_bundle),
376
+ )
377
+ finally:
378
+ for name, level in saved_levels.items():
379
+ logging.getLogger(name).setLevel(level or logging.NOTSET)
380
+
381
+ quality_notes = sanitize_result(team_result, data_bundle) if sanitize_result else []
382
+ captured_noise = (captured_stdout.getvalue() + captured_stderr.getvalue()).strip()
383
+ if captured_noise:
384
+ logger.debug("captured /team noisy output for %s: %s", symbol, captured_noise[:3000])
385
+
386
+ return TeamAnalysisResult(
387
+ symbol=symbol,
388
+ team_result=team_result,
389
+ data_bundle=data_bundle,
390
+ quality_notes=quality_notes,
391
+ captured_noise=captured_noise,
392
+ )
393
+
394
+
395
+ async def run_deep_cli(
396
+ *,
397
+ symbol: str,
398
+ args: TeamArgs,
399
+ config: dict[str, Any],
400
+ lang: str = "zh",
401
+ on_agent_done: Callable[[str, Any], None] | None = None,
402
+ ):
403
+ """Run the deep analysis pipeline (P0–P3) reusing the team provider plumbing."""
404
+ from agents.deep.pipeline import DeepAnalysisPipeline
405
+ from datasources.router import get_router
406
+
407
+ llm_provider = build_team_llm_provider(config)
408
+ noisy = ["agents.base", "agents.team", "agents.deep", "datasources.router", "data_cleaner"]
409
+ saved = {name: logging.getLogger(name).level for name in noisy}
410
+ for name in noisy:
411
+ logging.getLogger(name).setLevel(logging.ERROR)
412
+
413
+ _real_stdout = sys.stdout
414
+
415
+ def _agent_done_proxy(name: str, result: Any) -> None:
416
+ if on_agent_done is None:
417
+ return
418
+ try:
419
+ with contextlib.redirect_stdout(_real_stdout):
420
+ on_agent_done(name, result)
421
+ except Exception as exc:
422
+ logger.debug("deep on_agent_done render failed: %s", exc)
423
+
424
+ cap_out, cap_err = io.StringIO(), io.StringIO()
425
+ try:
426
+ pipe = DeepAnalysisPipeline(
427
+ llm_provider=llm_provider, data_router=get_router(), lang=lang,
428
+ )
429
+ with contextlib.redirect_stdout(cap_out), contextlib.redirect_stderr(cap_err):
430
+ result = await pipe.run(
431
+ symbol,
432
+ agents=args.agent_names,
433
+ on_agent_done=_agent_done_proxy if on_agent_done else None,
434
+ )
435
+ finally:
436
+ for name, level in saved.items():
437
+ logging.getLogger(name).setLevel(level or logging.NOTSET)
438
+ return result
439
+
440
+
441
+ def _agent_result_summary(team_result: Any) -> list[dict[str, Any]]:
442
+ return [
443
+ {
444
+ "agent": getattr(result, "agent", ""),
445
+ "success": bool(getattr(result, "success", False)),
446
+ "signal": getattr(result, "signal", None),
447
+ "confidence": getattr(result, "confidence", None),
448
+ "error": getattr(result, "error", None),
449
+ "key_points": getattr(result, "key_points", None),
450
+ }
451
+ for result in getattr(team_result, "results", []) or []
452
+ ]
453
+
454
+
455
+ def build_team_report_markdown(
456
+ *,
457
+ symbol: str,
458
+ team_result: Any,
459
+ data_bundle: Any = None,
460
+ quality_notes: list[str] | None = None,
461
+ created_at: datetime | None = None,
462
+ ) -> str:
463
+ created = created_at or datetime.now()
464
+ quality_notes = quality_notes or []
465
+ quote_snapshot = team_quote_snapshot(data_bundle) if data_bundle else {}
466
+ currency = quote_snapshot.get("currency") or "USD"
467
+ price = quote_snapshot.get("price")
468
+ cap = quote_snapshot.get("market_cap")
469
+ providers = quote_snapshot.get("provider_chain") or []
470
+ missing = quote_snapshot.get("missing_fields") or []
471
+ warnings = quote_snapshot.get("warnings") or []
472
+ errors = quote_snapshot.get("errors") or []
473
+ quality = quote_snapshot.get("quality") or {}
474
+ stale = bool(quote_snapshot.get("stale"))
475
+
476
+ lines = [
477
+ f"# {symbol} 多 Agent 研究报告",
478
+ f"> 生成时间: {created:%Y-%m-%d %H:%M} | 最终信号: **{team_result.final_signal}**"
479
+ f" | 置信度: {team_result.confidence:.0%} | 耗时: {team_result.elapsed_sec:.1f}s",
480
+ "",
481
+ "## 数据质量",
482
+ "",
483
+ f"- 数据状态: `{quote_snapshot.get('status', 'unknown')}`",
484
+ f"- 是否过期: `{'yes' if stale else 'no'}`",
485
+ f"- 当前参考价: `{currency} {price}`" if price is not None else "- 当前参考价: `unavailable`",
486
+ f"- 涨跌幅: `{_fmt_pct(quote_snapshot.get('change_pct'))}`",
487
+ f"- 成交量: `{_fmt_compact_number(quote_snapshot.get('volume'))}`",
488
+ f"- 市值: `{compact_market_cap(cap, currency)}`" if cap else "- 市值: `unavailable`",
489
+ f"- PE / Forward PE / EPS: `{_fmt_num(quote_snapshot.get('pe_ratio'))}` / `{_fmt_num(quote_snapshot.get('fwd_pe'))}` / `{_fmt_num(quote_snapshot.get('eps'))}`",
490
+ f"- ROE / 营收增长: `{_fmt_num(quote_snapshot.get('roe'))}%` / `{_fmt_num(quote_snapshot.get('revenue_growth'))}%`",
491
+ f"- 技术指标: `RSI {_fmt_num(quote_snapshot.get('rsi'), 1)} · MACD hist {_fmt_num(quote_snapshot.get('macd_hist'), 4)} · MA20 {_fmt_num(quote_snapshot.get('ma20'))} · MA60 {_fmt_num(quote_snapshot.get('ma60'))}`",
492
+ f"- 分析师目标价: `{currency} {_fmt_num(quote_snapshot.get('analyst_target'))}`" if quote_snapshot.get("analyst_target") is not None else "- 分析师目标价: `unavailable`",
493
+ f"- 数据源链: `{', '.join(providers) if providers else 'unknown'}`",
494
+ f"- 缺失字段: `{', '.join(missing) if missing else 'none'}`",
495
+ ]
496
+ if quality_notes:
497
+ lines.append(f"- 输出校验: `{'; '.join(quality_notes)}`")
498
+ if warnings:
499
+ lines.append(f"- 数据警告: `{'; '.join(str(w) for w in warnings[:5])}`")
500
+ if errors:
501
+ lines.append(f"- 数据错误: `{'; '.join(str(e) for e in errors[:5])}`")
502
+ if quality:
503
+ lines.append(f"- 质量摘要: `{quality.get('status', quote_snapshot.get('status', 'unknown'))}`")
504
+ lines += ["", "---", ""]
505
+
506
+ for result in getattr(team_result, "results", []) or []:
507
+ if getattr(result, "success", False):
508
+ lines += [
509
+ f"## {result.agent.upper()} ({result.signal}, {result.confidence:.0%})",
510
+ "",
511
+ result.analysis or "*(无分析文本)*",
512
+ "",
513
+ ]
514
+ else:
515
+ lines += [
516
+ f"## {result.agent.upper()} (UNUSABLE)",
517
+ "",
518
+ f"> {result.error or 'agent_failed'}",
519
+ "",
520
+ result.analysis or "*(该 Agent 未产生可用分析文本)*",
521
+ "",
522
+ ]
523
+ lines += ["---", "", "## 综合结论", "", clean_team_synthesis_text(team_result.synthesis or "*(无综合结论)*"), ""]
524
+ return "\n".join(lines)
525
+
526
+
527
+ def save_team_report(
528
+ *,
529
+ symbol: str,
530
+ team_result: Any,
531
+ data_bundle: Any = None,
532
+ quality_notes: list[str] | None = None,
533
+ created_at: datetime | None = None,
534
+ ) -> SavedTeamReport:
535
+ from artifacts import create_user_artifact, write_artifact_metadata, write_artifact_raw_data
536
+
537
+ created = created_at or datetime.now()
538
+ quality_notes = quality_notes or []
539
+ artifact = create_user_artifact("reports/team", symbol, f"{symbol}_team_report", ".md")
540
+ markdown = build_team_report_markdown(
541
+ symbol=symbol,
542
+ team_result=team_result,
543
+ data_bundle=data_bundle,
544
+ quality_notes=quality_notes,
545
+ created_at=created,
546
+ )
547
+ artifact.path.write_text(markdown, encoding="utf-8")
548
+
549
+ quote_snapshot = team_quote_snapshot(data_bundle) if data_bundle else {}
550
+ quality = quote_snapshot.get("quality") or {}
551
+ agent_results = _agent_result_summary(team_result)
552
+ write_artifact_metadata(artifact, {
553
+ "kind": "team_report",
554
+ "status": "complete" if any(result.get("success") for result in agent_results) else "data_unavailable",
555
+ "symbol": symbol,
556
+ "created_at": created.isoformat(timespec="seconds"),
557
+ "data": {
558
+ "agent_count": len(agent_results),
559
+ "successful_agents": sum(1 for result in agent_results if result.get("success")),
560
+ "failed_agents": [result.get("agent") for result in agent_results if not result.get("success")],
561
+ "quote": quote_snapshot,
562
+ "quality": quality,
563
+ "quality_notes": quality_notes,
564
+ },
565
+ "verdict": {
566
+ "final_signal": getattr(team_result, "final_signal", None),
567
+ "confidence": getattr(team_result, "confidence", None),
568
+ "elapsed_sec": getattr(team_result, "elapsed_sec", None),
569
+ },
570
+ })
571
+ write_artifact_raw_data(artifact, {
572
+ "symbol": symbol,
573
+ "agents": agent_results,
574
+ "synthesis": getattr(team_result, "synthesis", None),
575
+ "data_bundle": {
576
+ "quote": getattr(data_bundle, "quote", {}) if data_bundle else {},
577
+ "history": getattr(data_bundle, "history", {}) if data_bundle else {},
578
+ "fundamentals": getattr(data_bundle, "fundamentals", {}) if data_bundle else {},
579
+ "technical": getattr(data_bundle, "technical", {}) if data_bundle else {},
580
+ "provider_chain": getattr(data_bundle, "provider_chain", []) if data_bundle else [],
581
+ "missing_fields": getattr(data_bundle, "missing_fields", []) if data_bundle else [],
582
+ "warnings": getattr(data_bundle, "warnings", []) if data_bundle else [],
583
+ "errors": getattr(data_bundle, "errors", []) if data_bundle else [],
584
+ "quality": getattr(data_bundle, "quality", {}) if data_bundle else {},
585
+ "status": getattr(data_bundle, "status", None) if data_bundle else None,
586
+ },
587
+ })
588
+ return SavedTeamReport(path=artifact.path, metadata_path=artifact.metadata_path)
@@ -0,0 +1,8 @@
1
+ """Backwards-compatibility shim — canonical code lives in ui/render/team.py."""
2
+ from ui.render.team import * # noqa: F401, F403
3
+ from ui.render.team import (
4
+ build_verdict_body, render_verdict_banner,
5
+ TeamTableRow, calc_column_widths, truncate_cell,
6
+ team_mode_label, build_team_table_rows,
7
+ render_team_rows_plain, render_team_table,
8
+ )