rebuild(stage-12): deliver private review workflows
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from backend.database import MIGRATIONS, Database, MigrationRunner
|
||||
from backend.features.review.prompt import messages
|
||||
from backend.features.review.repository import ReviewRepository
|
||||
from backend.features.review.views import trade_summary
|
||||
|
||||
|
||||
def _database(tmp_path) -> Database:
|
||||
database = Database(tmp_path / "review.db")
|
||||
MigrationRunner(database).upgrade(MIGRATIONS)
|
||||
with database.transaction() as connection:
|
||||
connection.executemany(
|
||||
"""INSERT INTO users (
|
||||
id, username, username_key, password_hash, is_admin,
|
||||
status, created_at, updated_at
|
||||
) VALUES (?, ?, ?, 'hash', 0, 'active', 'now', 'now')""",
|
||||
((1, "first", "first"), (2, "second", "second")),
|
||||
)
|
||||
return database
|
||||
|
||||
|
||||
def test_private_review_records_are_strictly_scoped_and_daily_notes_upsert(tmp_path) -> None:
|
||||
database = _database(tmp_path)
|
||||
repository = ReviewRepository()
|
||||
note = {
|
||||
"code": "",
|
||||
"stock_name": "",
|
||||
"trade_date": "2026-07-30",
|
||||
"summary": "缩量分化",
|
||||
"content": "追高一次",
|
||||
"plan": "等待承接",
|
||||
}
|
||||
trade = {
|
||||
"trade_date": "2026-07-30",
|
||||
"code": "000001",
|
||||
"name": "平安银行",
|
||||
"action": "buy",
|
||||
"price": 10.0,
|
||||
"quantity": 100,
|
||||
"position_pct": 20.0,
|
||||
"pnl_amount": None,
|
||||
"pnl_pct": None,
|
||||
"emotion": "calm",
|
||||
"tags": ["计划内"],
|
||||
"thesis": "承接",
|
||||
"execution": "符合",
|
||||
"id": None,
|
||||
}
|
||||
with database.transaction() as connection:
|
||||
repository.save_watch(
|
||||
connection,
|
||||
1,
|
||||
{
|
||||
"identifier": "000001.SZ",
|
||||
"name": "平安银行",
|
||||
"sector": "银行",
|
||||
},
|
||||
"now",
|
||||
)
|
||||
repository.save_watch(
|
||||
connection,
|
||||
2,
|
||||
{
|
||||
"identifier": "600000.SH",
|
||||
"name": "浦发银行",
|
||||
"sector": "银行",
|
||||
},
|
||||
"now",
|
||||
)
|
||||
assert repository.save_watch_remark(connection, 1, "000001.SZ", "观察承接")
|
||||
repository.save_watch(
|
||||
connection,
|
||||
1,
|
||||
{"identifier": "000001.SZ", "name": "平安银行", "sector": "银行"},
|
||||
"later",
|
||||
)
|
||||
note_id = repository.save_note(connection, 1, note, "now")
|
||||
note["summary"] = "更新后的盘面"
|
||||
assert repository.save_note(connection, 1, note, "later") == note_id
|
||||
trade_id = repository.save_trade(connection, 1, trade, "now")
|
||||
repository.save_alert(
|
||||
connection,
|
||||
1,
|
||||
{
|
||||
"kind": "manual",
|
||||
"title": "复核",
|
||||
"content": "看承接",
|
||||
"available_date": "2026-07-30",
|
||||
"code": "000001",
|
||||
"dedupe_key": "one",
|
||||
},
|
||||
"now",
|
||||
)
|
||||
repository.add_message(
|
||||
connection,
|
||||
1,
|
||||
{
|
||||
"role": "user",
|
||||
"content": "今天如何",
|
||||
"context_date": "2026-07-30",
|
||||
"request_id": None,
|
||||
"status": "complete",
|
||||
},
|
||||
"now",
|
||||
)
|
||||
with database.read() as connection:
|
||||
assert [row["identifier"] for row in repository.watchlist(connection, 1)] == ["000001.SZ"]
|
||||
assert repository.watchlist(connection, 1)[0]["remark"] == "观察承接"
|
||||
assert [row["identifier"] for row in repository.watchlist(connection, 2)] == ["600000.SH"]
|
||||
assert repository.note(connection, 1, "", "2026-07-30")["summary"] == "更新后的盘面"
|
||||
assert repository.note(connection, 2, "", "2026-07-30") is None
|
||||
assert [row["id"] for row in repository.trades(connection, 1)] == [trade_id]
|
||||
assert repository.trades(connection, 2) == ()
|
||||
assert len(repository.alerts(connection, 1, False)) == 1
|
||||
assert repository.alerts(connection, 2, False) == ()
|
||||
assert len(repository.messages(connection, 1)) == 1
|
||||
assert repository.messages(connection, 2) == ()
|
||||
|
||||
|
||||
def test_trade_summary_uses_only_realized_fields_and_keeps_zero_in_denominator() -> None:
|
||||
rows = [
|
||||
{"pnl_amount": None, "pnl_pct": None, "position_pct": None},
|
||||
{"pnl_amount": 100.0, "pnl_pct": None, "position_pct": 20.0},
|
||||
{"pnl_amount": None, "pnl_pct": 0.0, "position_pct": 40.0},
|
||||
{"pnl_amount": -20.0, "pnl_pct": -2.0, "position_pct": 60.0},
|
||||
]
|
||||
summary = trade_summary(rows)
|
||||
assert summary == {
|
||||
"total": 4,
|
||||
"realized": 3,
|
||||
"win_rate": 33.3,
|
||||
"pnl_amount": 80.0,
|
||||
"average_position": 40.0,
|
||||
}
|
||||
|
||||
|
||||
def test_review_prompt_separates_facts_records_inference_and_plan() -> None:
|
||||
prompt = messages({"market_facts": {"temperature": 42}}, [], "明天怎么看")
|
||||
system = prompt[0]["content"]
|
||||
assert all(label in system for label in ("市场事实", "用户记录", "推断", "条件化计划"))
|
||||
assert "不执行交易" in system
|
||||
Reference in New Issue
Block a user