From 767c80d38761e7ded1958781ce0faf2a0bbdaefe Mon Sep 17 00:00:00 2001 From: aaron <> Date: Mon, 8 Jun 2026 11:11:05 +0800 Subject: [PATCH] 1 --- backend/app/api/recommendations.py | 1 + backend/app/engine/recommender.py | 176 +++++++++++++++--- backend/astock.db | Bin 2797568 -> 2797568 bytes frontend/.next/app-build-manifest.json | 82 +++++--- frontend/.next/build-manifest.json | 8 +- frontend/.next/server/app-paths-manifest.json | 15 +- .../.next/server/middleware-build-manifest.js | 2 +- .../server/server-reference-manifest.json | 2 +- frontend/.next/trace | 69 +++---- frontend/src/app/(auth)/dashboard/page.tsx | 61 +++++- .../src/app/(auth)/recommendations/page.tsx | 53 +++++- frontend/src/lib/api.ts | 26 +++ 12 files changed, 382 insertions(+), 113 deletions(-) diff --git a/backend/app/api/recommendations.py b/backend/app/api/recommendations.py index e3b90fb8..d78453fa 100644 --- a/backend/app/api/recommendations.py +++ b/backend/app/api/recommendations.py @@ -86,6 +86,7 @@ async def get_latest(): "market_anomalies": anomalies, "scan_mode": result.get("scan_mode", "unknown"), "strategy_profile": result.get("strategy_profile"), + "latest_scan": result.get("latest_scan"), } diff --git a/backend/app/engine/recommender.py b/backend/app/engine/recommender.py index 1cf9d73d..6bb81420 100644 --- a/backend/app/engine/recommender.py +++ b/backend/app/engine/recommender.py @@ -85,6 +85,61 @@ def _build_legacy_decision_trace(row) -> dict: } +def _scan_meta_from_row(row) -> dict | None: + if not row: + return None + r = row._mapping if hasattr(row, "_mapping") else row + detail = _safe_json_dict(r.get("detail_json")) + return { + "scan_session": r.get("scan_session") or "", + "scan_mode": r.get("scan_mode") or "", + "status": r.get("status") or "ok", + "stage": r.get("stage") or "", + "input_count": int(r.get("input_count") or 0), + "output_count": int(r.get("output_count") or 0), + "filtered_count": int(r.get("filtered_count") or 0), + "summary": r.get("summary") or "", + "created_at": str(r.get("created_at") or ""), + "date": str(r.get("created_at") or "")[:10] if r.get("created_at") else "", + "detail": detail, + "action_counts": detail.get("action_counts") or {}, + "elimination_reasons": detail.get("elimination_reasons") or {}, + } + + +async def _load_latest_completed_scan(db) -> dict | None: + from sqlalchemy import text + + result = await db.execute( + text( + "SELECT * FROM scan_process_logs " + "WHERE stage = 'final_filter' " + "ORDER BY created_at DESC, id DESC LIMIT 1" + ) + ) + return _scan_meta_from_row(result.fetchone()) + + +async def _load_latest_strategy_profile_for_session(db, scan_session: str) -> dict | None: + if not scan_session: + return None + from sqlalchemy import text + + result = await db.execute( + text( + "SELECT detail_json FROM scan_process_logs " + "WHERE scan_session = :session AND stage = 'strategy_profile' " + "ORDER BY created_at DESC, id DESC LIMIT 1" + ), + {"session": scan_session}, + ) + row = result.fetchone() + if not row: + return None + profile = _safe_json_dict(row._mapping.get("detail_json")) + return profile or None + + async def refresh_recommendations(trade_date: str = None, scan_session: str = "manual") -> dict: """刷新推荐列表(带扫描锁防止并发)""" global _scan_running @@ -577,6 +632,25 @@ async def get_recommendation_history(days: int = 7) -> list[dict]: async with get_db() as db: from sqlalchemy import text + scan_result = await db.execute( + text( + "SELECT s.* FROM scan_process_logs s " + "INNER JOIN (" + " SELECT date(created_at) AS scan_date, MAX(id) AS max_id " + " FROM scan_process_logs " + " WHERE stage = 'final_filter' AND created_at >= :start " + " GROUP BY date(created_at)" + ") latest ON s.id = latest.max_id " + "ORDER BY s.created_at DESC, s.id DESC" + ), + {"start": start}, + ) + scan_groups = { + meta["date"]: meta + for meta in (_scan_meta_from_row(row) for row in scan_result.fetchall()) + if meta and meta.get("date") + } + # 查询所有历史推荐,按 ts_code 去重(每天取最新一条) stmt = text( "SELECT r.*, " @@ -614,8 +688,11 @@ async def get_recommendation_history(days: int = 7) -> list[dict]: result = await db.execute(stmt, {"start": start}) rows = result.fetchall() - # 按日期分组 - grouped: dict[str, list[dict]] = {} + # 按日期分组。先用扫描日志建组,保证“当天扫描但无推荐”也会被展示。 + grouped: dict[str, dict] = { + date_str: {"scan": scan_meta, "recommendations": []} + for date_str, scan_meta in scan_groups.items() + } for row in rows: r = row._mapping # SQLite created_at 是字符串 "YYYY-MM-DD HH:MM:SS" @@ -679,13 +756,15 @@ async def get_recommendation_history(days: int = 7) -> list[dict]: } if date_str not in grouped: - grouped[date_str] = [] - grouped[date_str].append(rec_dict) + grouped[date_str] = {"scan": None, "recommendations": []} + grouped[date_str]["recommendations"].append(rec_dict) # 转为列表,按日期降序 result_list = [] for date_str in sorted(grouped.keys(), reverse=True): - recs = grouped[date_str] + group = grouped[date_str] + recs = group["recommendations"] + scan_meta = group["scan"] buy_count = sum(1 for r in recs if r["signal"] == "BUY") avg_score = round(sum(r["score"] for r in recs) / len(recs), 1) if recs else 0 result_list.append({ @@ -694,6 +773,15 @@ async def get_recommendation_history(days: int = 7) -> list[dict]: "buy_count": buy_count, "avg_score": avg_score, "recommendations": recs, + "scan_session": scan_meta.get("scan_session") if scan_meta else "", + "scan_mode": scan_meta.get("scan_mode") if scan_meta else "", + "scan_status": scan_meta.get("status") if scan_meta else "", + "scanned_at": scan_meta.get("created_at") if scan_meta else "", + "scan_summary": scan_meta.get("summary") if scan_meta else "", + "scan_input_count": scan_meta.get("input_count") if scan_meta else 0, + "scan_output_count": scan_meta.get("output_count") if scan_meta else len(recs), + "scan_filtered_count": scan_meta.get("filtered_count") if scan_meta else 0, + "elimination_reasons": scan_meta.get("elimination_reasons") if scan_meta else {}, }) return result_list @@ -908,6 +996,10 @@ async def _load_today_from_db() -> dict: from sqlalchemy import text import json + latest_scan = await _load_latest_completed_scan(db) + latest_scan_date = (latest_scan or {}).get("date") or "" + latest_scan_session = (latest_scan or {}).get("scan_session") or "" + # 加载市场温度(按 trade_date 取最新交易日) result = await db.execute( text( @@ -931,22 +1023,47 @@ async def _load_today_from_db() -> dict: temperature=m["temperature"], ) - # 加载推荐(取最近一个有数据的日期,按 ts_code 去重,优先保留行动级别更高的结果) - result = await db.execute( - text("SELECT * FROM recommendations " - "WHERE date(created_at) = (SELECT date(created_at) FROM recommendations ORDER BY created_at DESC LIMIT 1) " - "AND (action_plan IN ('可操作', '重点关注') OR score >= 56) " - "AND (" - " COALESCE(recall_tags, '[]') LIKE '%hot_theme_core%' " - " OR COALESCE(recall_tags, '[]') LIKE '%theme_leader%' " - " OR COALESCE(recall_tags, '[]') LIKE '%top_theme_member%' " - " OR COALESCE(recall_tags, '[]') LIKE '%sector_recall%'" - ") " - "AND id IN (SELECT MAX(id) FROM recommendations " - " WHERE date(created_at) = (SELECT date(created_at) FROM recommendations ORDER BY created_at DESC LIMIT 1) " - " GROUP BY ts_code) " - "ORDER BY score DESC") - ) + if latest_scan: + recommendation_sql = ( + "SELECT * FROM recommendations " + "WHERE date(created_at) = :target_date " + "AND (:scan_session = '' OR scan_session = :scan_session) " + "AND (action_plan IN ('可操作', '重点关注') OR score >= 56) " + "AND (" + " COALESCE(recall_tags, '[]') LIKE '%hot_theme_core%' " + " OR COALESCE(recall_tags, '[]') LIKE '%theme_leader%' " + " OR COALESCE(recall_tags, '[]') LIKE '%top_theme_member%' " + " OR COALESCE(recall_tags, '[]') LIKE '%sector_recall%'" + ") " + "AND id IN (" + " SELECT MAX(id) FROM recommendations " + " WHERE date(created_at) = :target_date " + " AND (:scan_session = '' OR scan_session = :scan_session) " + " GROUP BY ts_code" + ") " + "ORDER BY score DESC" + ) + result = await db.execute( + text(recommendation_sql), + {"target_date": latest_scan_date, "scan_session": latest_scan_session}, + ) + else: + # 兼容旧库:没有扫描日志时,才回退到最近一个有推荐的日期。 + result = await db.execute( + text("SELECT * FROM recommendations " + "WHERE date(created_at) = (SELECT date(created_at) FROM recommendations ORDER BY created_at DESC LIMIT 1) " + "AND (action_plan IN ('可操作', '重点关注') OR score >= 56) " + "AND (" + " COALESCE(recall_tags, '[]') LIKE '%hot_theme_core%' " + " OR COALESCE(recall_tags, '[]') LIKE '%theme_leader%' " + " OR COALESCE(recall_tags, '[]') LIKE '%top_theme_member%' " + " OR COALESCE(recall_tags, '[]') LIKE '%sector_recall%'" + ") " + "AND id IN (SELECT MAX(id) FROM recommendations " + " WHERE date(created_at) = (SELECT date(created_at) FROM recommendations ORDER BY created_at DESC LIMIT 1) " + " GROUP BY ts_code) " + "ORDER BY score DESC") + ) rows = result.fetchall() recommendations = [] for row in rows: @@ -988,24 +1105,29 @@ async def _load_today_from_db() -> dict: focus_points=json.loads(r.get("focus_points") or "[]"), decision_trace=_safe_json_dict(r.get("decision_trace")) or _build_legacy_decision_trace(r), scan_session=r["scan_session"] or "", + created_at=r["created_at"], )) + strategy_profile = None + if latest_scan: + strategy_profile = await _load_latest_strategy_profile_for_session(db, latest_scan_session) + if not strategy_profile and recommendations: + strategy_profile = get_strategy_profile_by_id(recommendations[0].strategy).model_dump() + return { "market_temp": market_temp, "hot_sectors": [], "capital_filtered": [], "recommendations": recommendations, - "strategy_profile": ( - get_strategy_profile_by_id(recommendations[0].strategy).model_dump() - if recommendations - else None - ), + "strategy_profile": strategy_profile, + "latest_scan": latest_scan, + "scan_mode": (latest_scan or {}).get("scan_mode", "unknown"), } except Exception as e: logger.error(f"从数据库加载推荐失败: {e}") from app.db.error_logger import log_error await log_error("recommender", f"从数据库加载推荐失败: {e}", detail=traceback.format_exc()) - return {"market_temp": None, "hot_sectors": [], "capital_filtered": [], "recommendations": []} + return {"market_temp": None, "hot_sectors": [], "capital_filtered": [], "recommendations": [], "latest_scan": None} async def _load_sectors_from_db() -> list[SectorInfo]: diff --git a/backend/astock.db b/backend/astock.db index 8eaad2ca216936191a0e027853790ad97248ba8e..7ffd3270bbf2c9c52de93f8b16263dc251ad8547 100644 GIT binary patch delta 65555 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 delta 46079 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 rec.action_plan === "可操作"); const watch = recommendations.filter((rec) => rec.action_plan === "重点关注"); const observe = recommendations.filter((rec) => !["可操作", "重点关注"].includes(rec.action_plan ?? "")); @@ -185,6 +186,7 @@ export default function DashboardPage() { ); const focusQueue = actionable.length ? actionable : watch.length ? watch : recommendations; + const scanTimeLabel = latestScan?.created_at ? formatScanTime(latestScan.created_at) : "暂无完成扫描"; if (loading) { return ( @@ -212,6 +214,9 @@ export default function DashboardPage() { 已收盘 )} + + 最近扫描 {scanTimeLabel} + @@ -246,6 +251,7 @@ export default function DashboardPage() { actionableCount={actionable.length} watchCount={watch.length} observeCount={observe.length} + latestScan={latestScan} /> @@ -255,6 +261,7 @@ export default function DashboardPage() { focusQueue={focusQueue.slice(0, 6)} actionableCount={actionable.length} watchCount={watch.length} + latestScan={latestScan} />
; @@ -297,6 +305,7 @@ function DecisionHero({ actionableCount: number; watchCount: number; observeCount: number; + latestScan: LatestResult["latest_scan"]; }) { const leadingSectors = sectors.slice(0, 3); @@ -349,14 +358,15 @@ function DecisionHero({
-
焦点标的
+
+
焦点标的
+
{latestScan?.created_at ? formatScanTime(latestScan.created_at) : "暂无扫描"}
+
{focusQueue.length ? ( focusQueue.map((rec) => ) ) : ( -
- 暂无焦点标的。 -
+ )}
@@ -371,11 +381,13 @@ function OpportunityBoard({ focusQueue, actionableCount, watchCount, + latestScan, }: { sectors: SectorData[]; focusQueue: RecommendationData[]; actionableCount: number; watchCount: number; + latestScan: LatestResult["latest_scan"]; }) { const leadingSectors = sectors.slice(0, 5); @@ -416,7 +428,9 @@ function OpportunityBoard({ {focusQueue.length ? focusQueue.map((rec) => ( )) : ( -
暂无重点标的
+
+ +
)} @@ -727,6 +741,32 @@ function LargeFocusStockCard({ rec }: { rec: RecommendationData }) { ); } +function EmptyScanResult({ latestScan, compact = false }: { latestScan: LatestResult["latest_scan"]; compact?: boolean }) { + const reasons = latestScan?.elimination_reasons ? Object.entries(latestScan.elimination_reasons).slice(0, 3) : []; + return ( +
+
+
本次扫描无入选标的
+ + {latestScan?.created_at ? formatScanTime(latestScan.created_at) : "未扫描"} + +
+
+ {latestScan?.summary || "最近一次扫描没有保留到作战池,不展示历史标的,避免误导。"} +
+ {reasons.length ? ( +
+ {reasons.map(([reason, count]) => ( + + {reason} {count} + + ))} +
+ ) : null} +
+ ); +} + function getExecutionMeta(rec: RecommendationData) { const hint = rec.decision_trace?.position_adjustment?.hint ?? rec.decision_trace?.context?.position_hint ?? "neutral"; const note = rec.decision_trace?.position_adjustment?.notes?.[0] ?? ""; @@ -757,6 +797,17 @@ function getExecutionMeta(rec: RecommendationData) { return { label: "", note: "", className: "", textClass: "" }; } +function formatScanTime(value: string) { + const normalized = value.includes("T") ? value : value.replace(" ", "T"); + const date = new Date(normalized); + if (Number.isNaN(date.getTime())) return value; + const today = new Date(); + const isToday = date.toDateString() === today.toDateString(); + const time = date.toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit" }); + if (isToday) return `今日 ${time}`; + return date.toLocaleString("zh-CN", { month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit" }); +} + function TinyInfo({ label, value }: { label: string; value: string | number }) { return (
diff --git a/frontend/src/app/(auth)/recommendations/page.tsx b/frontend/src/app/(auth)/recommendations/page.tsx index 70dd5919..faddc5d2 100644 --- a/frontend/src/app/(auth)/recommendations/page.tsx +++ b/frontend/src/app/(auth)/recommendations/page.tsx @@ -26,6 +26,18 @@ function formatDate(dateStr: string): string { return `${d.getMonth() + 1}月${d.getDate()}日 ${weekDays[d.getDay()]}`; } +function formatScanTime(value?: string): string { + if (!value) return "暂无扫描"; + const normalized = value.includes("T") ? value : value.replace(" ", "T"); + const d = new Date(normalized); + if (Number.isNaN(d.getTime())) return value; + const today = new Date(); + const isToday = d.toDateString() === today.toDateString(); + const time = d.toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit" }); + if (isToday) return `今日 ${time}`; + return d.toLocaleString("zh-CN", { month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit" }); +} + type RecommendationWithDate = RecommendationData & { groupDate: string }; type FocusTab = "actionable" | "watch" | "observe" | "tracking" | "closed"; @@ -149,6 +161,7 @@ export default function RecommendationsPage() { const todayCount = applyHistoryFilter(dayGroups[0]?.recommendations ?? []).length; const totalCount = dayGroups.reduce((sum, group) => sum + applyHistoryFilter(group.recommendations).length, 0); + const latestScanLabel = latest?.latest_scan?.created_at || dayGroups[0]?.scanned_at; const focusSummary = buildFocusSummary({ strategyProfile: latest?.strategy_profile ?? null, actionable, @@ -209,6 +222,8 @@ export default function RecommendationsPage() {
+ +
@@ -312,10 +327,10 @@ export default function RecommendationsPage() {
{dayGroups.map((group, index) => { const filtered = applyHistoryFilter(group.recommendations); - if (historyFilter !== "all" && filtered.length === 0) return null; const isExpanded = expandedDays.has(group.date); const isToday = index === 0; + const reasons = group.elimination_reasons ? Object.entries(group.elimination_reasons).slice(0, 3) : []; return (
@@ -331,17 +346,43 @@ export default function RecommendationsPage() { {group.date}
- {filtered.length} 只推荐 + 扫描 {formatScanTime(group.scanned_at)} · {filtered.length} 只入选 + {group.scan_input_count ? ` / 候选 ${group.scan_input_count}` : ""}
+ {group.scan_summary ? ( +
{group.scan_summary}
+ ) : null}
{isExpanded ? "收起" : "展开"} {isExpanded ? ( -
- {filtered.map((rec) => ( - - ))} +
+ {filtered.length ? ( +
+ {filtered.map((rec) => ( + + ))} +
+ ) : ( +
+
+ {historyFilter === "all" ? "本日扫描无入选标的" : "本日扫描存在,但当前筛选条件下无标的"} +
+
+ {group.scan_summary || "系统已完成扫描,但没有标的进入历史推荐池。"} +
+ {reasons.length ? ( +
+ {reasons.map(([reason, count]) => ( + + {reason} {count} + + ))} +
+ ) : null} +
+ )}
) : null}
diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index a7533a98..4cd6f845 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -365,6 +365,23 @@ export interface LatestResult { feedback_notes?: string[]; generated_by?: string; } | null; + latest_scan?: ScanMeta | null; + scan_mode?: string; +} + +export interface ScanMeta { + scan_session: string; + scan_mode: string; + status: string; + stage: string; + input_count: number; + output_count: number; + filtered_count: number; + summary: string; + created_at: string; + date: string; + action_counts?: Record; + elimination_reasons?: Record; } export interface DayGroup { @@ -373,6 +390,15 @@ export interface DayGroup { buy_count: number; avg_score: number; recommendations: RecommendationData[]; + scan_session?: string; + scan_mode?: string; + scan_status?: string; + scanned_at?: string; + scan_summary?: string; + scan_input_count?: number; + scan_output_count?: number; + scan_filtered_count?: number; + elimination_reasons?: Record; } // ---------- Performance Stats ----------