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,202 @@
|
|
|
1
|
+
"""Local LLM-prediction outcome tracker — the finance ground-truth loop.
|
|
2
|
+
|
|
3
|
+
The market is an objective judge that Claude Code can't have: when Aria says
|
|
4
|
+
"NVDA 看多", the market settles it. We record directional/price calls at T=0,
|
|
5
|
+
verify them against real moves at T+N, and turn the result into a DPO signal:
|
|
6
|
+
correct call → "chosen" training example
|
|
7
|
+
wrong call → "rejected" training example
|
|
8
|
+
|
|
9
|
+
Local-first: predictions live in ~/.arthera/predictions.jsonl; settled DPO
|
|
10
|
+
signals are appended to the FeedbackStore (shared=False until /privacy opt-in).
|
|
11
|
+
Honors the ARIA_NO_TELEMETRY kill switch. Never raises into the chat flow.
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import os
|
|
17
|
+
import re
|
|
18
|
+
import time
|
|
19
|
+
from datetime import datetime, timezone
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
22
|
+
|
|
23
|
+
# Directional keywords (zh + en).
|
|
24
|
+
_BULL = ("看多", "看涨", "买入", "增持", "做多", "上涨", "bullish", "buy", "long", "outperform")
|
|
25
|
+
_BEAR = ("看空", "看跌", "卖出", "减持", "做空", "下跌", "bearish", "sell", "short", "underperform")
|
|
26
|
+
_NEUTRAL = ("中性", "震荡", "观望", "持有", "neutral", "hold", "sideways")
|
|
27
|
+
|
|
28
|
+
_TARGET_RE = re.compile(
|
|
29
|
+
r"(?:目标价|价格目标|target\s*price)[::\s]*(?:约|~)?\s*[\$¥]?\s*([\d,]+\.?\d*)",
|
|
30
|
+
re.IGNORECASE,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# Settlement threshold: a directional call is "correct" only if the move
|
|
34
|
+
# exceeds this magnitude (avoids crediting noise as a hit).
|
|
35
|
+
_MOVE_THRESHOLD = 0.01 # 1%
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def detect_direction(text: str) -> str:
|
|
39
|
+
"""Return 'bullish' | 'bearish' | 'neutral' | '' from a response."""
|
|
40
|
+
if not text:
|
|
41
|
+
return ""
|
|
42
|
+
t = text.lower()
|
|
43
|
+
bull = sum(1 for k in _BULL if k.lower() in t)
|
|
44
|
+
bear = sum(1 for k in _BEAR if k.lower() in t)
|
|
45
|
+
neut = sum(1 for k in _NEUTRAL if k.lower() in t)
|
|
46
|
+
if bull == 0 and bear == 0 and neut == 0:
|
|
47
|
+
return ""
|
|
48
|
+
if bull > bear and bull >= neut:
|
|
49
|
+
return "bullish"
|
|
50
|
+
if bear > bull and bear >= neut:
|
|
51
|
+
return "bearish"
|
|
52
|
+
if neut >= bull and neut >= bear:
|
|
53
|
+
return "neutral"
|
|
54
|
+
return ""
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def extract_target_price(text: str) -> Optional[float]:
|
|
58
|
+
m = _TARGET_RE.search(text or "")
|
|
59
|
+
if not m:
|
|
60
|
+
return None
|
|
61
|
+
try:
|
|
62
|
+
return float(m.group(1).replace(",", ""))
|
|
63
|
+
except ValueError:
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class PredictionTracker:
|
|
68
|
+
"""Records and settles LLM market calls against real prices."""
|
|
69
|
+
|
|
70
|
+
def __init__(self, config_dir: Path):
|
|
71
|
+
self.path = Path(config_dir) / "predictions.jsonl"
|
|
72
|
+
|
|
73
|
+
# ── record (T=0) ──────────────────────────────────────────────────────────
|
|
74
|
+
def record(self, *, symbol: str, response_text: str, entry_price: float,
|
|
75
|
+
session_id: str = "", model: str = "") -> Optional[Dict[str, Any]]:
|
|
76
|
+
"""Record a directional call if one is detectable. Returns the entry or None."""
|
|
77
|
+
if os.environ.get("ARIA_NO_TELEMETRY"):
|
|
78
|
+
return None
|
|
79
|
+
if not symbol or not entry_price or entry_price <= 0:
|
|
80
|
+
return None
|
|
81
|
+
direction = detect_direction(response_text)
|
|
82
|
+
if not direction:
|
|
83
|
+
return None
|
|
84
|
+
entry = {
|
|
85
|
+
"id": f"{symbol}_{int(time.time())}",
|
|
86
|
+
"symbol": symbol.upper(),
|
|
87
|
+
"direction": direction,
|
|
88
|
+
"target": extract_target_price(response_text),
|
|
89
|
+
"entry_price": round(float(entry_price), 4),
|
|
90
|
+
"excerpt": (response_text or "")[:400],
|
|
91
|
+
"session_id": session_id,
|
|
92
|
+
"model": model,
|
|
93
|
+
"created_at": datetime.now(timezone.utc).isoformat(),
|
|
94
|
+
"status": "pending",
|
|
95
|
+
}
|
|
96
|
+
try:
|
|
97
|
+
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
98
|
+
with self.path.open("a", encoding="utf-8") as fh:
|
|
99
|
+
fh.write(json.dumps(entry, ensure_ascii=False) + "\n")
|
|
100
|
+
except Exception:
|
|
101
|
+
return None
|
|
102
|
+
return entry
|
|
103
|
+
|
|
104
|
+
# ── settle (T=N) ──────────────────────────────────────────────────────────
|
|
105
|
+
def _load(self) -> List[Dict[str, Any]]:
|
|
106
|
+
if not self.path.exists():
|
|
107
|
+
return []
|
|
108
|
+
out = []
|
|
109
|
+
try:
|
|
110
|
+
for line in self.path.read_text(encoding="utf-8").splitlines():
|
|
111
|
+
if line.strip():
|
|
112
|
+
out.append(json.loads(line))
|
|
113
|
+
except Exception:
|
|
114
|
+
return []
|
|
115
|
+
return out
|
|
116
|
+
|
|
117
|
+
def _save(self, rows: List[Dict[str, Any]]) -> None:
|
|
118
|
+
try:
|
|
119
|
+
with self.path.open("w", encoding="utf-8") as fh:
|
|
120
|
+
for r in rows:
|
|
121
|
+
fh.write(json.dumps(r, ensure_ascii=False) + "\n")
|
|
122
|
+
except Exception:
|
|
123
|
+
pass
|
|
124
|
+
|
|
125
|
+
@staticmethod
|
|
126
|
+
def _is_correct(direction: str, entry: float, now: float) -> Optional[bool]:
|
|
127
|
+
move = (now - entry) / entry if entry else 0.0
|
|
128
|
+
if direction == "bullish":
|
|
129
|
+
return move > _MOVE_THRESHOLD
|
|
130
|
+
if direction == "bearish":
|
|
131
|
+
return move < -_MOVE_THRESHOLD
|
|
132
|
+
if direction == "neutral":
|
|
133
|
+
return abs(move) <= _MOVE_THRESHOLD
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
def verify_pending(self, quote_fn: Callable[[str], Optional[float]],
|
|
137
|
+
min_age_hours: float = 24.0,
|
|
138
|
+
emit_feedback: Optional[Callable[[str, str, str], None]] = None
|
|
139
|
+
) -> Dict[str, int]:
|
|
140
|
+
"""Settle pending predictions older than min_age_hours against live prices.
|
|
141
|
+
|
|
142
|
+
quote_fn(symbol) -> current price (or None).
|
|
143
|
+
emit_feedback(rating, message, comment) -> append a DPO signal (optional).
|
|
144
|
+
Returns {"settled": n, "correct": c, "wrong": w}.
|
|
145
|
+
"""
|
|
146
|
+
if os.environ.get("ARIA_NO_TELEMETRY"):
|
|
147
|
+
return {"settled": 0, "correct": 0, "wrong": 0}
|
|
148
|
+
rows = self._load()
|
|
149
|
+
if not rows:
|
|
150
|
+
return {"settled": 0, "correct": 0, "wrong": 0}
|
|
151
|
+
now_ts = datetime.now(timezone.utc)
|
|
152
|
+
settled = correct = wrong = 0
|
|
153
|
+
for r in rows:
|
|
154
|
+
if r.get("status") != "pending":
|
|
155
|
+
continue
|
|
156
|
+
try:
|
|
157
|
+
created = datetime.fromisoformat(r["created_at"])
|
|
158
|
+
age_h = (now_ts - created).total_seconds() / 3600.0
|
|
159
|
+
except Exception:
|
|
160
|
+
continue
|
|
161
|
+
if age_h < min_age_hours:
|
|
162
|
+
continue
|
|
163
|
+
price_now = None
|
|
164
|
+
try:
|
|
165
|
+
price_now = quote_fn(r["symbol"])
|
|
166
|
+
except Exception:
|
|
167
|
+
price_now = None
|
|
168
|
+
if not price_now or price_now <= 0:
|
|
169
|
+
continue
|
|
170
|
+
ok = self._is_correct(r["direction"], r["entry_price"], float(price_now))
|
|
171
|
+
if ok is None:
|
|
172
|
+
continue
|
|
173
|
+
r["status"] = "correct" if ok else "wrong"
|
|
174
|
+
r["exit_price"] = round(float(price_now), 4)
|
|
175
|
+
r["settled_at"] = now_ts.isoformat()
|
|
176
|
+
settled += 1
|
|
177
|
+
correct += int(ok)
|
|
178
|
+
wrong += int(not ok)
|
|
179
|
+
if emit_feedback:
|
|
180
|
+
_move = (float(price_now) - r["entry_price"]) / r["entry_price"] * 100
|
|
181
|
+
_msg = r.get("excerpt", "")
|
|
182
|
+
_cmt = (f"{r['symbol']} {r['direction']} "
|
|
183
|
+
f"{r['entry_price']}→{price_now} ({_move:+.1f}%) "
|
|
184
|
+
f"= {'命中' if ok else '落空'}")
|
|
185
|
+
emit_feedback("prediction_correct" if ok else "prediction_wrong", _msg, _cmt)
|
|
186
|
+
if settled:
|
|
187
|
+
self._save(rows)
|
|
188
|
+
return {"settled": settled, "correct": correct, "wrong": wrong}
|
|
189
|
+
|
|
190
|
+
def accuracy(self) -> Dict[str, Any]:
|
|
191
|
+
rows = self._load()
|
|
192
|
+
done = [r for r in rows if r.get("status") in ("correct", "wrong")]
|
|
193
|
+
pend = [r for r in rows if r.get("status") == "pending"]
|
|
194
|
+
n = len(done)
|
|
195
|
+
c = sum(1 for r in done if r["status"] == "correct")
|
|
196
|
+
return {
|
|
197
|
+
"total": len(rows),
|
|
198
|
+
"settled": n,
|
|
199
|
+
"pending": len(pend),
|
|
200
|
+
"correct": c,
|
|
201
|
+
"accuracy": round(c / n, 3) if n else None,
|
|
202
|
+
}
|