Files
xiaobaifupan/next/backend/features/review/repository.py
T

307 lines
12 KiB
Python

from __future__ import annotations
import json
import sqlite3
class ReviewRepository:
def watchlist(self, connection: sqlite3.Connection, user_id: int) -> tuple[sqlite3.Row, ...]:
return tuple(
connection.execute(
"""SELECT identifier, name, sector, remark, created_at FROM watchlist_entries
WHERE user_id = ? ORDER BY created_at, identifier""",
(user_id,),
).fetchall()
)
def save_watch(
self, connection: sqlite3.Connection, user_id: int, item: dict, now: str
) -> None:
connection.execute(
"""INSERT INTO watchlist_entries (user_id, identifier, name, sector, remark, created_at)
VALUES (?, ?, ?, ?, ?, ?)
ON CONFLICT(user_id, identifier) DO UPDATE SET
name=excluded.name, sector=excluded.sector,
remark=CASE
WHEN excluded.remark = '' THEN watchlist_entries.remark
ELSE excluded.remark
END""",
(
user_id,
item["identifier"],
item["name"],
item.get("sector"),
item.get("remark") or "",
now,
),
)
def delete_watch(self, connection: sqlite3.Connection, user_id: int, identifier: str) -> bool:
cursor = connection.execute(
"DELETE FROM watchlist_entries WHERE user_id = ? AND identifier = ?",
(user_id, identifier),
)
return cursor.rowcount > 0
def save_watch_remark(
self, connection: sqlite3.Connection, user_id: int, identifier: str, remark: str
) -> bool:
cursor = connection.execute(
"UPDATE watchlist_entries SET remark=? WHERE user_id=? AND identifier=?",
(remark, user_id, identifier),
)
return cursor.rowcount > 0
def latest_factors(self, connection: sqlite3.Connection, through: str) -> dict[str, dict]:
snapshot = connection.execute(
"""SELECT id, trade_date FROM screener_factor_snapshots
WHERE trade_date <= ? ORDER BY trade_date DESC, id DESC LIMIT 1""",
(through,),
).fetchone()
if snapshot is None:
return {}
return {
str(row["identifier"]): json.loads(str(row["payload_json"]))
for row in connection.execute(
"SELECT identifier, payload_json FROM screener_factor_values WHERE snapshot_id = ?",
(int(snapshot["id"]),),
).fetchall()
}
def latest_auction_scores(
self, connection: sqlite3.Connection, through: str
) -> dict[str, float]:
row = connection.execute(
"""SELECT payload_json FROM market_insight_snapshots
WHERE kind = 'auction' AND trade_date <= ? AND entity_key = ''
ORDER BY trade_date DESC LIMIT 1""",
(through,),
).fetchone()
if row is None:
return {}
payload = json.loads(str(row["payload_json"]))
scores = {}
for key in ("_market_rows", "rows", "one_price_rows"):
for item in payload.get(key) or []:
identifier = str(item.get("identifier") or item.get("ts_code") or "")
score = item.get("attention_score")
if identifier and isinstance(score, (int, float)):
scores[identifier] = float(score)
return scores
def note(
self, connection: sqlite3.Connection, user_id: int, code: str, trade_date: str
) -> sqlite3.Row | None:
return connection.execute(
"SELECT * FROM review_notes WHERE user_id = ? AND code = ? AND trade_date = ?",
(user_id, code, trade_date),
).fetchone()
def notes(
self, connection: sqlite3.Connection, user_id: int, code: str = "", limit: int = 60
) -> tuple[sqlite3.Row, ...]:
return tuple(
connection.execute(
"""SELECT * FROM review_notes WHERE user_id = ? AND code = ?
ORDER BY trade_date DESC, id DESC LIMIT ?""",
(user_id, code, limit),
).fetchall()
)
def save_note(self, connection: sqlite3.Connection, user_id: int, data: dict, now: str) -> int:
connection.execute(
"""INSERT INTO review_notes (
user_id, code, stock_name, trade_date, summary, content, plan,
created_at, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(user_id, code, trade_date) DO UPDATE SET
stock_name=excluded.stock_name, summary=excluded.summary,
content=excluded.content, plan=excluded.plan, updated_at=excluded.updated_at""",
(
user_id,
data["code"],
data["stock_name"],
data["trade_date"],
data["summary"],
data["content"],
data["plan"],
now,
now,
),
)
row = self.note(connection, user_id, data["code"], data["trade_date"])
if row is None:
raise RuntimeError("复盘记录写入失败")
return int(row["id"])
def delete_note(self, connection: sqlite3.Connection, user_id: int, note_id: int) -> bool:
cursor = connection.execute(
"DELETE FROM review_notes WHERE user_id = ? AND id = ?", (user_id, note_id)
)
return cursor.rowcount > 0
def trades(
self, connection: sqlite3.Connection, user_id: int, limit: int = 300
) -> tuple[sqlite3.Row, ...]:
return tuple(
connection.execute(
"""SELECT * FROM trade_entries WHERE user_id = ?
ORDER BY trade_date DESC, id DESC LIMIT ?""",
(user_id, limit),
).fetchall()
)
def trade(
self, connection: sqlite3.Connection, user_id: int, trade_id: int
) -> sqlite3.Row | None:
return connection.execute(
"SELECT * FROM trade_entries WHERE user_id = ? AND id = ?", (user_id, trade_id)
).fetchone()
def save_trade(self, connection: sqlite3.Connection, user_id: int, data: dict, now: str) -> int:
values = (
data["trade_date"],
data["code"],
data["name"],
data["action"],
data["price"],
data["quantity"],
data["position_pct"],
data["pnl_amount"],
data["pnl_pct"],
data["emotion"],
json.dumps(data["tags"], ensure_ascii=False),
data["thesis"],
data["execution"],
now,
)
if data.get("id"):
cursor = connection.execute(
"""UPDATE trade_entries SET trade_date=?, code=?, name=?, action=?, price=?,
quantity=?, position_pct=?, pnl_amount=?, pnl_pct=?, emotion=?, tags_json=?,
thesis=?, execution=?, updated_at=? WHERE user_id=? AND id=?""",
(*values, user_id, int(data["id"])),
)
if cursor.rowcount == 0:
raise ValueError("交易记录不存在或无权修改。")
return int(data["id"])
cursor = connection.execute(
"""INSERT INTO trade_entries (
trade_date, code, name, action, price, quantity, position_pct, pnl_amount,
pnl_pct, emotion, tags_json, thesis, execution, created_at, updated_at, user_id
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
(*values, now, user_id),
)
return int(cursor.lastrowid)
def delete_trade(self, connection: sqlite3.Connection, user_id: int, trade_id: int) -> bool:
cursor = connection.execute(
"DELETE FROM trade_entries WHERE user_id = ? AND id = ?", (user_id, trade_id)
)
return cursor.rowcount > 0
def save_alert(self, connection: sqlite3.Connection, user_id: int, data: dict, now: str) -> int:
connection.execute(
"""INSERT INTO alerts (
user_id, kind, title, content, available_date, code, dedupe_key,
created_at, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(user_id, dedupe_key) DO NOTHING""",
(
user_id,
data["kind"],
data["title"],
data["content"],
data["available_date"],
data["code"],
data["dedupe_key"],
now,
now,
),
)
row = connection.execute(
"SELECT id FROM alerts WHERE user_id = ? AND dedupe_key = ?",
(user_id, data["dedupe_key"]),
).fetchone()
return int(row["id"])
def alerts(
self, connection: sqlite3.Connection, user_id: int, unread: bool
) -> tuple[sqlite3.Row, ...]:
clause = "AND is_read = 0" if unread else ""
return tuple(
connection.execute(
f"""SELECT * FROM alerts WHERE user_id = ? {clause}
ORDER BY available_date > date('now'), is_read,
available_date DESC, id DESC LIMIT 100""",
(user_id,),
).fetchall()
)
def unread_count(self, connection: sqlite3.Connection, user_id: int, today: str) -> int:
row = connection.execute(
"""SELECT COUNT(*) total FROM alerts
WHERE user_id = ? AND available_date <= ? AND is_read = 0""",
(user_id, today),
).fetchone()
return int(row["total"])
def mark_alert(
self, connection: sqlite3.Connection, user_id: int, alert_id: int, now: str
) -> bool:
cursor = connection.execute(
"UPDATE alerts SET is_read=1, read_at=?, updated_at=? WHERE user_id=? AND id=?",
(now, now, user_id, alert_id),
)
return cursor.rowcount > 0
def mark_all_alerts(
self, connection: sqlite3.Connection, user_id: int, today: str, now: str
) -> int:
cursor = connection.execute(
"""UPDATE alerts SET is_read=1, read_at=?, updated_at=?
WHERE user_id=? AND available_date <= ? AND is_read=0""",
(now, now, user_id, today),
)
return int(cursor.rowcount)
def delete_alert(self, connection: sqlite3.Connection, user_id: int, alert_id: int) -> bool:
cursor = connection.execute(
"DELETE FROM alerts WHERE user_id=? AND id=?", (user_id, alert_id)
)
return cursor.rowcount > 0
def messages(
self, connection: sqlite3.Connection, user_id: int, limit: int = 100
) -> tuple[sqlite3.Row, ...]:
rows = connection.execute(
"""SELECT * FROM review_assistant_messages WHERE user_id=?
ORDER BY id DESC LIMIT ?""",
(user_id, limit),
).fetchall()
return tuple(reversed(rows))
def add_message(
self, connection: sqlite3.Connection, user_id: int, data: dict, now: str
) -> None:
connection.execute(
"""INSERT INTO review_assistant_messages
(user_id, role, content, context_date, request_id, status, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?)""",
(
user_id,
data["role"],
data["content"],
data["context_date"],
data.get("request_id"),
data["status"],
now,
),
)
def clear_messages(self, connection: sqlite3.Connection, user_id: int) -> int:
cursor = connection.execute(
"DELETE FROM review_assistant_messages WHERE user_id=?", (user_id,)
)
return int(cursor.rowcount)