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.
- agents/__init__.py +32 -0
- agents/base.py +190 -0
- agents/deep/__init__.py +37 -0
- agents/deep/calibration_loop.py +144 -0
- agents/deep/critic.py +125 -0
- agents/deep/deepen.py +193 -0
- agents/deep/models.py +149 -0
- agents/deep/pipeline.py +164 -0
- agents/deep/quant_fusion.py +192 -0
- agents/deep/themes.py +95 -0
- agents/deep/tiers.py +106 -0
- agents/financial/__init__.py +10 -0
- agents/financial/catalyst.py +279 -0
- agents/financial/debate.py +145 -0
- agents/financial/earnings.py +303 -0
- agents/financial/fundamental.py +159 -0
- agents/financial/macro.py +99 -0
- agents/financial/news.py +207 -0
- agents/financial/risk.py +132 -0
- agents/financial/sector.py +279 -0
- agents/financial/synthesis.py +274 -0
- agents/financial/technical.py +258 -0
- agents/portfolio_agent.py +333 -0
- agents/realty/__init__.py +62 -0
- agents/realty/asset_diagnosis.py +150 -0
- agents/realty/business_match.py +165 -0
- agents/realty/cashflow_verify.py +208 -0
- agents/realty/contract_rules.py +209 -0
- agents/realty/energy_anomaly.py +188 -0
- agents/realty/exit_settlement.py +207 -0
- agents/realty/fulfillment_risk.py +205 -0
- agents/realty/ops_optimize.py +159 -0
- agents/realty/revenue_share.py +214 -0
- agents/registry.py +144 -0
- agents/sports/__init__.py +0 -0
- agents/sports/football_agent.py +169 -0
- agents/team.py +289 -0
- aliyun_data_client.py +660 -0
- apps/README.md +12 -0
- apps/__init__.py +2 -0
- apps/channels/README.md +15 -0
- apps/cli/README.md +13 -0
- apps/cli/__init__.py +2 -0
- apps/cli/bootstrap.py +99 -0
- apps/cli/codegen_paths.py +29 -0
- apps/cli/commands/__init__.py +16 -0
- apps/cli/commands/analysis_cmds.py +288 -0
- apps/cli/commands/backtest_cmds.py +1887 -0
- apps/cli/commands/broker_cmds.py +1154 -0
- apps/cli/commands/business_workflow_cmds.py +289 -0
- apps/cli/commands/catalog.py +84 -0
- apps/cli/commands/data_cmds.py +405 -0
- apps/cli/commands/diagnostic_cmds.py +179 -0
- apps/cli/commands/diagnostic_ops_cmds.py +696 -0
- apps/cli/commands/finance_render.py +12 -0
- apps/cli/commands/market.py +399 -0
- apps/cli/commands/market_cmds.py +1276 -0
- apps/cli/commands/market_context.py +425 -0
- apps/cli/commands/market_render.py +7 -0
- apps/cli/commands/model_cmds.py +1579 -0
- apps/cli/commands/ops_cmds.py +668 -0
- apps/cli/commands/portfolio_cmds.py +962 -0
- apps/cli/commands/report.py +377 -0
- apps/cli/commands/scaffold_templates.py +617 -0
- apps/cli/commands/session_cmds.py +179 -0
- apps/cli/commands/session_ux_cmds.py +280 -0
- apps/cli/commands/team.py +588 -0
- apps/cli/commands/team_render.py +8 -0
- apps/cli/commands/ui_cmds.py +358 -0
- apps/cli/commands/workflow_cmds.py +279 -0
- apps/cli/commands/workspace_cmds.py +1414 -0
- apps/cli/config_paths.py +70 -0
- apps/cli/config_store.py +61 -0
- apps/cli/deterministic.py +122 -0
- apps/cli/direct.py +48 -0
- apps/cli/github_app_auth.py +135 -0
- apps/cli/handlers/__init__.py +11 -0
- apps/cli/handlers/broker_handlers.py +122 -0
- apps/cli/handlers/chart_handlers.py +1309 -0
- apps/cli/handlers/market_handlers.py +2509 -0
- apps/cli/handlers/realty_handlers.py +114 -0
- apps/cli/handlers/strategy_advice.py +82 -0
- apps/cli/hooks.py +180 -0
- apps/cli/i18n.py +284 -0
- apps/cli/intent.py +136 -0
- apps/cli/intent_router.py +217 -0
- apps/cli/lifecycle_hooks.py +48 -0
- apps/cli/main.py +29 -0
- apps/cli/market_metadata.py +135 -0
- apps/cli/market_universe.py +265 -0
- apps/cli/message_processing.py +257 -0
- apps/cli/plan_mode.py +139 -0
- apps/cli/plotly_html.py +15 -0
- apps/cli/prediction_feedback.py +202 -0
- apps/cli/preflight.py +497 -0
- apps/cli/project_aria.py +60 -0
- apps/cli/prompts/__init__.py +0 -0
- apps/cli/prompts/coding.py +658 -0
- apps/cli/prompts/system_prompts.py +531 -0
- apps/cli/prompts/ui.py +434 -0
- apps/cli/providers/__init__.py +1 -0
- apps/cli/providers/base.py +271 -0
- apps/cli/providers/chat_routing.py +80 -0
- apps/cli/providers/llm/__init__.py +1 -0
- apps/cli/providers/llm/ollama_stream.py +1170 -0
- apps/cli/providers/llm/sse_stream.py +216 -0
- apps/cli/providers/runtime_bridge.py +185 -0
- apps/cli/runtime_consumer.py +489 -0
- apps/cli/session_export.py +87 -0
- apps/cli/session_jsonl.py +207 -0
- apps/cli/session_store.py +112 -0
- apps/cli/todo_tracker.py +190 -0
- apps/cli/tools/__init__.py +40 -0
- apps/cli/tools/context.py +46 -0
- apps/cli/tools/file_tools.py +112 -0
- apps/cli/tools/market_tools.py +549 -0
- apps/cli/tools/notebook_tools.py +111 -0
- apps/cli/tools/system_tools.py +669 -0
- apps/cli/tools/write_tools.py +715 -0
- apps/cli/tradingview_bridge.py +434 -0
- apps/cli/update_check.py +152 -0
- apps/cli/utils/__init__.py +0 -0
- apps/cli/utils/market_detect.py +1578 -0
- apps/daemon/README.md +14 -0
- apps/vscode/README.md +115 -0
- apps/vscode/package.json +70 -0
- aria_cli.py +11636 -0
- aria_code-4.1.3.dist-info/METADATA +952 -0
- aria_code-4.1.3.dist-info/RECORD +284 -0
- aria_code-4.1.3.dist-info/WHEEL +5 -0
- aria_code-4.1.3.dist-info/entry_points.txt +2 -0
- aria_code-4.1.3.dist-info/licenses/LICENSE +121 -0
- aria_code-4.1.3.dist-info/top_level.txt +50 -0
- aria_daemon.py +1295 -0
- aria_feishu_bot.py +1359 -0
- aria_relay_client.py +182 -0
- aria_relay_server.py +405 -0
- aria_telegram_bot.py +202 -0
- ariarc.py +328 -0
- artifacts.py +491 -0
- backtest_report.py +472 -0
- brokers/__init__.py +72 -0
- brokers/base.py +207 -0
- brokers/capabilities.py +264 -0
- brokers/cn/__init__.py +10 -0
- brokers/cn/easytrader_broker.py +193 -0
- brokers/cn/futu_broker.py +194 -0
- brokers/cn/longbridge_broker.py +190 -0
- brokers/cn/tiger_broker.py +196 -0
- brokers/cn/xtquant_broker.py +175 -0
- brokers/config.py +364 -0
- brokers/intl/__init__.py +5 -0
- brokers/intl/alpaca_broker.py +183 -0
- brokers/intl/ibkr_broker.py +215 -0
- brokers/intl/webull_broker.py +156 -0
- brokers/paper_broker.py +259 -0
- brokers/planning.py +296 -0
- brokers/registry.py +181 -0
- brokers/trading.py +237 -0
- change_store.py +127 -0
- command_safety.py +19 -0
- computer_use_tools.py +504 -0
- dashboard_generator.py +578 -0
- data_analysis_tools.py +808 -0
- data_cleaner.py +483 -0
- data_service.py +481 -0
- datasources/__init__.py +23 -0
- datasources/base.py +166 -0
- datasources/router.py +221 -0
- datasources/sources/__init__.py +15 -0
- datasources/sources/akshare_source.py +269 -0
- datasources/sources/alpha_vantage_source.py +202 -0
- datasources/sources/edgar_source.py +218 -0
- datasources/sources/finnhub_source.py +197 -0
- datasources/sources/fred_source.py +219 -0
- datasources/sources/tushare_source.py +141 -0
- datasources/sources/web_scraper_source.py +278 -0
- datasources/sources/world_bank_source.py +205 -0
- datasources/sources/yfinance_source.py +152 -0
- demo_player.py +204 -0
- doctor.py +508 -0
- file_analysis_tools.py +734 -0
- finance_formulas.py +389 -0
- football_data_client.py +1670 -0
- intent_classifier.py +358 -0
- local_finance_tools.py +3221 -0
- local_llm_provider.py +552 -0
- macro_tools.py +368 -0
- market_data_client.py +1899 -0
- mcp_client.py +506 -0
- memory_manager.py +245 -0
- model_capability.py +416 -0
- notification_tools.py +248 -0
- packages/__init__.py +23 -0
- packages/aria_agents/__init__.py +5 -0
- packages/aria_agents/manifest.py +69 -0
- packages/aria_core/__init__.py +34 -0
- packages/aria_core/architecture.py +192 -0
- packages/aria_core/export.py +124 -0
- packages/aria_core/manifest.py +65 -0
- packages/aria_infra/__init__.py +15 -0
- packages/aria_infra/arthera.py +52 -0
- packages/aria_infra/doctor.py +246 -0
- packages/aria_infra/product.py +37 -0
- packages/aria_mcp/__init__.py +25 -0
- packages/aria_mcp/bridge.py +38 -0
- packages/aria_mcp/config.py +97 -0
- packages/aria_mcp/tools.py +61 -0
- packages/aria_sdk/__init__.py +19 -0
- packages/aria_sdk/client.py +396 -0
- packages/aria_sdk/providers.py +70 -0
- packages/aria_sdk/streaming.py +73 -0
- packages/aria_sdk/types.py +86 -0
- packages/aria_services/__init__.py +55 -0
- packages/aria_services/context.py +258 -0
- packages/aria_services/data.py +11 -0
- packages/aria_services/provider_health.py +189 -0
- packages/aria_services/registry.py +213 -0
- packages/aria_services/usage.py +138 -0
- packages/aria_skills/__init__.py +5 -0
- packages/aria_skills/registry.py +59 -0
- packages/aria_tools/__init__.py +5 -0
- packages/aria_tools/registry.py +128 -0
- packages/quant_engine/__init__.py +6 -0
- packages/quant_engine/sports/__init__.py +72 -0
- packages/quant_engine/sports/calibrator.py +353 -0
- packages/quant_engine/sports/dixon_coles.py +234 -0
- packages/quant_engine/sports/elo.py +299 -0
- packages/quant_engine/sports/form.py +188 -0
- packages/quant_engine/sports/h2h.py +195 -0
- packages/quant_engine/sports/ml_model.py +354 -0
- packages/quant_engine/sports/predictor.py +311 -0
- packages/quant_engine/sports/tracker.py +664 -0
- packages/quant_engine/stochastic/__init__.py +27 -0
- packages/quant_engine/stochastic/gbm_enhanced.py +195 -0
- packages/quant_engine/stochastic/ito_calculus.py +477 -0
- packages/quant_engine/stochastic/kelly_criterion.py +181 -0
- packages/quant_engine/stochastic/monte_carlo_advanced.py +95 -0
- packages/quant_engine/stochastic/options_pricing.py +573 -0
- packages/quant_engine/stochastic/stochastic_processes.py +90 -0
- plan_utils.py +194 -0
- plugin_loader.py +328 -0
- portfolio_ledger.py +262 -0
- privacy/__init__.py +5 -0
- privacy/feedback.py +123 -0
- project_tools.py +525 -0
- providers/__init__.py +30 -0
- providers/llm/__init__.py +19 -0
- providers/llm/anthropic.py +184 -0
- providers/llm/base.py +139 -0
- providers/llm/ollama.py +128 -0
- providers/llm/openai_compat.py +282 -0
- providers/llm/registry.py +358 -0
- realty_data_tools.py +659 -0
- report_generator.py +1314 -0
- runtime/__init__.py +103 -0
- runtime/agent_loop.py +1183 -0
- runtime/approval.py +51 -0
- runtime/events.py +102 -0
- runtime/gateway.py +128 -0
- runtime/lsp.py +346 -0
- runtime/subagent.py +258 -0
- runtime/tool_executor.py +104 -0
- runtime/tool_policy.py +106 -0
- safety/__init__.py +21 -0
- safety/permissions.py +275 -0
- setup_wizard.py +653 -0
- strategy_vault.py +420 -0
- ui/__init__.py +100 -0
- ui/banner.py +310 -0
- ui/completer.py +391 -0
- ui/console.py +271 -0
- ui/image_render.py +243 -0
- ui/input_box.py +376 -0
- ui/picker.py +195 -0
- ui/render/__init__.py +11 -0
- ui/render/finance.py +1480 -0
- ui/render/market.py +225 -0
- ui/render/output.py +681 -0
- ui/render/team.py +346 -0
- ui/robot.py +235 -0
- workspace/__init__.py +6 -0
- workspace/files.py +170 -0
- 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
|
+
)
|