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