Add explain analysis workflow and UI
This commit is contained in:
236
backend/tests/test_cli.py
Normal file
236
backend/tests/test_cli.py
Normal file
@@ -0,0 +1,236 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from pathlib import Path
|
||||
|
||||
from backend import cli
|
||||
|
||||
|
||||
def test_live_runs_incremental_market_store_update_before_start(monkeypatch, tmp_path):
|
||||
project_root = tmp_path
|
||||
(project_root / ".env").write_text("FINNHUB_API_KEY=test\n", encoding="utf-8")
|
||||
|
||||
calls = []
|
||||
|
||||
monkeypatch.setattr(cli, "get_project_root", lambda: project_root)
|
||||
monkeypatch.setattr(cli, "handle_history_cleanup", lambda config_name, auto_clean=False: None)
|
||||
monkeypatch.setattr(cli, "run_data_updater", lambda project_root: calls.append(("run_data_updater", project_root)))
|
||||
monkeypatch.setattr(
|
||||
cli,
|
||||
"auto_update_market_store",
|
||||
lambda config_name, end_date=None: calls.append(("auto_update_market_store", config_name, end_date)),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
cli,
|
||||
"auto_enrich_market_store",
|
||||
lambda config_name, end_date=None, lookback_days=120, force=False: calls.append(
|
||||
("auto_enrich_market_store", config_name, end_date, lookback_days, force)
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr(cli.os, "chdir", lambda path: calls.append(("chdir", Path(path))))
|
||||
|
||||
def fake_run(cmd, check=True, **kwargs):
|
||||
calls.append(("subprocess.run", cmd, check))
|
||||
return 0
|
||||
|
||||
monkeypatch.setattr(cli.subprocess, "run", fake_run)
|
||||
|
||||
cli.live(
|
||||
mock=False,
|
||||
config_name="smoke_fullstack",
|
||||
host="0.0.0.0",
|
||||
port=8765,
|
||||
trigger_time="now",
|
||||
poll_interval=10,
|
||||
clean=False,
|
||||
enable_memory=False,
|
||||
)
|
||||
|
||||
assert any(item[0] == "run_data_updater" for item in calls)
|
||||
assert any(
|
||||
item[0] == "auto_update_market_store" and item[1] == "smoke_fullstack"
|
||||
for item in calls
|
||||
)
|
||||
assert any(
|
||||
item[0] == "auto_enrich_market_store" and item[1] == "smoke_fullstack"
|
||||
for item in calls
|
||||
)
|
||||
run_call = next(item for item in calls if item[0] == "subprocess.run")
|
||||
assert run_call[1][:6] == [
|
||||
cli.sys.executable,
|
||||
"-u",
|
||||
"-m",
|
||||
"backend.main",
|
||||
"--mode",
|
||||
"live",
|
||||
]
|
||||
|
||||
|
||||
def test_backtest_runs_full_market_store_prepare_before_start(monkeypatch, tmp_path):
|
||||
project_root = tmp_path
|
||||
calls = []
|
||||
|
||||
monkeypatch.setattr(cli, "get_project_root", lambda: project_root)
|
||||
monkeypatch.setattr(cli, "handle_history_cleanup", lambda config_name, auto_clean=False: None)
|
||||
monkeypatch.setattr(cli, "run_data_updater", lambda project_root: calls.append(("run_data_updater", project_root)))
|
||||
monkeypatch.setattr(
|
||||
cli,
|
||||
"auto_prepare_backtest_market_store",
|
||||
lambda config_name, start_date, end_date: calls.append(
|
||||
("auto_prepare_backtest_market_store", config_name, start_date, end_date)
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
cli,
|
||||
"auto_enrich_market_store",
|
||||
lambda config_name, end_date=None, lookback_days=120, force=False: calls.append(
|
||||
("auto_enrich_market_store", config_name, end_date, lookback_days, force)
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr(cli.os, "chdir", lambda path: calls.append(("chdir", Path(path))))
|
||||
|
||||
def fake_run(cmd, check=True, **kwargs):
|
||||
calls.append(("subprocess.run", cmd, check))
|
||||
return 0
|
||||
|
||||
monkeypatch.setattr(cli.subprocess, "run", fake_run)
|
||||
|
||||
cli.backtest(
|
||||
start="2026-03-01",
|
||||
end="2026-03-10",
|
||||
config_name="smoke_fullstack",
|
||||
host="0.0.0.0",
|
||||
port=8765,
|
||||
poll_interval=10,
|
||||
clean=False,
|
||||
enable_memory=False,
|
||||
)
|
||||
|
||||
assert any(item[0] == "run_data_updater" for item in calls)
|
||||
assert any(
|
||||
item[0] == "auto_prepare_backtest_market_store"
|
||||
and item[1:] == ("smoke_fullstack", "2026-03-01", "2026-03-10")
|
||||
for item in calls
|
||||
)
|
||||
assert any(
|
||||
item[0] == "auto_enrich_market_store"
|
||||
and item[1] == "smoke_fullstack"
|
||||
and item[2] == "2026-03-10"
|
||||
for item in calls
|
||||
)
|
||||
run_call = next(item for item in calls if item[0] == "subprocess.run")
|
||||
assert run_call[1][:6] == [
|
||||
cli.sys.executable,
|
||||
"-u",
|
||||
"-m",
|
||||
"backend.main",
|
||||
"--mode",
|
||||
"backtest",
|
||||
]
|
||||
|
||||
|
||||
def test_ingest_enrich_runs_batch_enrichment(monkeypatch):
|
||||
calls = []
|
||||
|
||||
monkeypatch.setattr(cli, "_resolve_symbols", lambda raw_tickers, config_name=None: ["AAPL", "MSFT"])
|
||||
|
||||
class DummyStore:
|
||||
pass
|
||||
|
||||
monkeypatch.setattr(cli, "MarketStore", lambda: DummyStore())
|
||||
monkeypatch.setattr(
|
||||
cli,
|
||||
"enrich_symbols",
|
||||
lambda store, symbols, start_date=None, end_date=None, limit=200, analysis_source="local", skip_existing=True: calls.append(
|
||||
("enrich_symbols", symbols, start_date, end_date, limit, analysis_source, skip_existing)
|
||||
) or [
|
||||
{
|
||||
"symbol": symbol,
|
||||
"news_count": 3,
|
||||
"queued_count": 3,
|
||||
"analyzed": 3,
|
||||
"skipped_existing_count": 0,
|
||||
"deduped_count": 0,
|
||||
"llm_count": 0,
|
||||
"local_count": 3,
|
||||
}
|
||||
for symbol in symbols
|
||||
],
|
||||
)
|
||||
|
||||
cli.ingest_enrich(
|
||||
tickers=None,
|
||||
start="2026-03-01",
|
||||
end="2026-03-10",
|
||||
limit=150,
|
||||
force=False,
|
||||
config_name="smoke_fullstack",
|
||||
)
|
||||
|
||||
assert calls == [
|
||||
("enrich_symbols", ["AAPL", "MSFT"], "2026-03-01", "2026-03-10", 150, "local", True)
|
||||
]
|
||||
|
||||
|
||||
def test_ingest_report_reads_market_store_report(monkeypatch):
|
||||
calls = []
|
||||
printed = []
|
||||
|
||||
monkeypatch.setattr(cli, "_resolve_symbols", lambda raw_tickers, config_name=None: ["AAPL"])
|
||||
|
||||
class DummyStore:
|
||||
def get_enrich_report(self, symbols=None, start_date=None, end_date=None):
|
||||
calls.append(("get_enrich_report", symbols, start_date, end_date))
|
||||
return [
|
||||
{
|
||||
"symbol": "AAPL",
|
||||
"raw_news_count": 10,
|
||||
"analyzed_news_count": 8,
|
||||
"coverage_pct": 80.0,
|
||||
"llm_count": 5,
|
||||
"local_count": 3,
|
||||
"latest_trade_date": "2026-03-16",
|
||||
"latest_analysis_at": "2026-03-16T09:00:00",
|
||||
}
|
||||
]
|
||||
|
||||
monkeypatch.setattr(cli, "MarketStore", lambda: DummyStore())
|
||||
monkeypatch.setattr(cli, "get_explain_model_info", lambda: {"provider": "DASHSCOPE", "model_name": "qwen-max", "label": "DASHSCOPE:qwen-max"})
|
||||
monkeypatch.setattr(cli, "llm_enrichment_enabled", lambda: True)
|
||||
monkeypatch.setattr(cli.console, "print", lambda value: printed.append(value))
|
||||
|
||||
cli.ingest_report(
|
||||
tickers=None,
|
||||
start="2026-03-01",
|
||||
end="2026-03-16",
|
||||
config_name="smoke_fullstack",
|
||||
only_problematic=False,
|
||||
)
|
||||
|
||||
assert calls == [
|
||||
("get_enrich_report", ["AAPL"], "2026-03-01", "2026-03-16")
|
||||
]
|
||||
assert printed
|
||||
assert getattr(printed[0], "caption", "") == "Explain LLM: DASHSCOPE:qwen-max"
|
||||
|
||||
|
||||
def test_filter_problematic_report_rows_keeps_low_coverage_and_no_llm():
|
||||
rows = [
|
||||
{
|
||||
"symbol": "AAPL",
|
||||
"coverage_pct": 100.0,
|
||||
"llm_count": 2,
|
||||
},
|
||||
{
|
||||
"symbol": "MSFT",
|
||||
"coverage_pct": 80.0,
|
||||
"llm_count": 1,
|
||||
},
|
||||
{
|
||||
"symbol": "NVDA",
|
||||
"coverage_pct": 100.0,
|
||||
"llm_count": 0,
|
||||
},
|
||||
]
|
||||
|
||||
filtered = cli._filter_problematic_report_rows(rows)
|
||||
|
||||
assert [row["symbol"] for row in filtered] == ["MSFT", "NVDA"]
|
||||
Reference in New Issue
Block a user