From b3555d260319b5f7a3bb906b5f53ef2f7cbb060f Mon Sep 17 00:00:00 2001 From: leefer Date: Fri, 31 Jul 2026 01:41:58 +0800 Subject: [PATCH] migration: preserve sentiment and pools slice --- app/backend/application.py | 176 +------ app/backend/data/providers/tushare_client.py | 2 +- app/backend/features/market/service.py | 2 +- app/backend/features/pools/__init__.py | 6 + app/backend/features/pools/repository.py | 27 + app/backend/features/pools/service.py | 150 ++++++ app/backend/features/sentiment/__init__.py | 19 + app/backend/features/sentiment/engine.py | 496 +++++++++++++++++ app/backend/features/sentiment/service.py | 39 ++ app/database.py | 23 +- app/demo_data.py | 2 +- app/screener.py | 2 +- app/sentiment_engine.py | 497 +----------------- app/tests/test_preservation_slice_market.py | 5 +- ...test_preservation_slice_sentiment_pools.py | 114 ++++ docs/migration/evidence/slice-03/README.md | 66 +++ .../evidence/slice-03/app-light-1920x1080.png | Bin 0 -> 154636 bytes docs/migration/保真迁移状态.json | 10 +- docs/migration/保真迁移账本.md | 16 +- 19 files changed, 956 insertions(+), 696 deletions(-) create mode 100644 app/backend/features/pools/__init__.py create mode 100644 app/backend/features/pools/repository.py create mode 100644 app/backend/features/pools/service.py create mode 100644 app/backend/features/sentiment/__init__.py create mode 100644 app/backend/features/sentiment/engine.py create mode 100644 app/backend/features/sentiment/service.py create mode 100644 app/tests/test_preservation_slice_sentiment_pools.py create mode 100644 docs/migration/evidence/slice-03/README.md create mode 100644 docs/migration/evidence/slice-03/app-light-1920x1080.png diff --git a/app/backend/application.py b/app/backend/application.py index b38b31a..c7bb74d 100644 --- a/app/backend/application.py +++ b/app/backend/application.py @@ -53,14 +53,13 @@ from screener import ( from backend.features.accounts.http import AccountHttpMixin from backend.features.accounts.security import SecretVault from backend.features.accounts.service import AccountService -from backend.features.system import SystemHttpMixin -from sentiment_engine import ( - COMPONENT_WEIGHTS, - SENTIMENT_ENGINE_VERSION, - apply_sentiment_to_dashboard, +from backend.features.pools import PoolServiceMixin +from backend.features.sentiment import SentimentServiceMixin +from backend.features.sentiment.engine import ( build_sentiment_history, latest_contiguous_history, ) +from backend.features.system import SystemHttpMixin from backend.data.providers.tushare_client import TushareClient, TushareError, _sector_coverage_issue @@ -141,7 +140,7 @@ MENTOR_ETF_UNIVERSE = ( ) -class DashboardService(MarketServiceMixin): +class DashboardService(MarketServiceMixin, SentimentServiceMixin, PoolServiceMixin): def __init__(self) -> None: runtime = load_runtime_settings() self.vault = SecretVault(runtime.encryption_key) @@ -734,31 +733,6 @@ class DashboardService(MarketServiceMixin): return AccountService.public_personal_profile(personal) - def _enrich_dashboard_sentiment( - self, - dashboard: dict[str, Any], - end_date: str, - ) -> dict[str, Any]: - history = self.database.list_snapshot_payloads(end_date, 260) - return apply_sentiment_to_dashboard(dashboard, history) - - def sentiment_history(self, trade_date: str, limit: int = 20) -> dict[str, Any]: - normalized_date = normalize_date(trade_date) - limit = max(10, min(120, int(limit))) - full_series = build_sentiment_history( - self.database.list_snapshot_payloads(normalized_date, 240) - ) - series = latest_contiguous_history(full_series) - rows = series[-limit:] - return { - "trade_date": rows[-1]["trade_date"] if rows else normalized_date, - "available_days": len(series), - "stored_days": len(full_series), - "requested_days": limit, - "rows": rows, - "weights": COMPONENT_WEIGHTS, - "normalization": rows[-1]["normalization"] if rows else "固定锚点", - } def rotation_history(self, trade_date: str, limit: int = 9) -> dict[str, Any]: normalized_date = normalize_date(trade_date) @@ -3380,146 +3354,6 @@ class DashboardService(MarketServiceMixin): } - def save_reason(self, trade_date: str, code: str, reason: str) -> None: - normalized_date = normalize_date(trade_date) - code = validate_stock_code(code) - reason = reason.strip() - if not reason or len(reason) > 200: - raise ValueError("涨停原因应为 1 至 200 个字符。") - self.database.save_reason_override(normalized_date, code, reason) - - - def _apply_reason_overrides(self, dashboard: dict[str, Any]) -> dict[str, Any]: - trade_date = str(dashboard.get("meta", {}).get("trade_date", "")).replace("-", "") - enrichment = self.database.get_data_snapshot("ifind_event_enrichment_v1", trade_date) - if enrichment: - self._merge_ifind_event_enrichment(dashboard, enrichment) - else: - self._schedule_ifind_event_enrichment(trade_date) - overrides = self.database.reason_overrides(trade_date) - if not overrides: - return dashboard - for key in ("limits", "broken", "down_limits"): - for row in dashboard.get(key) or []: - if row.get("code") in overrides: - row["reason"] = overrides[row["code"]] - row["reason_source"] = "manual" - return dashboard - - def _schedule_ifind_event_enrichment(self, trade_date: str) -> None: - ifind = getattr(self, "ifind", None) - if not ifind or not ifind.configured or not re.fullmatch(r"\d{8}", trade_date): - return - now = datetime.now().astimezone() - if trade_date == now.strftime("%Y%m%d") and now.time().replace(tzinfo=None) < dt_time(15, 0): - return - self.jobs.submit( - "market.ifind-event-enrichment", - f"{trade_date}:v1", - lambda: self._refresh_ifind_event_enrichment(trade_date), - {"trade_date": trade_date, "trigger": "dashboard-enrichment"}, - ) - - def _refresh_ifind_event_enrichment(self, trade_date: str) -> None: - if not self._ifind_event_lock.acquire(blocking=False): - return - try: - if self.database.get_data_snapshot("ifind_event_enrichment_v1", trade_date): - return - ifind = getattr(self, "ifind", None) - if not ifind or not ifind.configured: - return - current = datetime.strptime(trade_date, "%Y%m%d") - display_date = f"{current.year}年{current.month}月{current.day}日" - requests = { - "limits": ( - f"{display_date}涨停股票,股票代码、股票简称、涨停原因、" - "首次涨停时间、最终涨停时间、开板次数" - ), - "broken": ( - f"{display_date}曾涨停但收盘未涨停的股票,股票代码、股票简称、" - "涨停原因、首次涨停时间、开板次数" - ), - "down_limits": ( - f"{display_date}跌停股票,股票代码、股票简称、跌停原因" - ), - } - result: dict[str, Any] = { - "trade_date": trade_date, - "generated_at": datetime.now().astimezone().isoformat(timespec="seconds"), - "limits": {}, "broken": {}, "down_limits": {}, "partial": False, - } - for kind, query in requests.items(): - try: - rows = ifind.wencai(query, "stock", cache_ttl=900) - except IfindError: - result["partial"] = True - continue - for raw in rows: - code = self._ifind_row_code(raw) - if not code: - continue - reason_tokens = ( - ("跌停原因", "风险线索", "原因") - if kind == "down_limits" - else ("涨停原因类别", "涨停原因", "触板逻辑", "原因") - ) - reason = str(self._ifind_field(raw, reason_tokens) or "").strip() - first_time = self._normalize_ifind_event_time( - self._ifind_field(raw, ("首次涨停时间", "首次触板时间", "首次封板时间")) - ) - last_time = self._normalize_ifind_event_time( - self._ifind_field(raw, ("最终涨停时间", "最后涨停时间", "最后封板时间")) - ) - open_times = self._ifind_field(raw, ("开板次数", "打开涨停次数")) - try: - open_count = max(0, int(float(open_times))) if open_times not in (None, "") else None - except (TypeError, ValueError): - open_count = None - result[kind][code] = { - "reason": reason, - "first_time": first_time, - "last_time": last_time, - "open_times": open_count, - } - if any(result[kind] for kind in ("limits", "broken", "down_limits")): - self.database.save_data_snapshot( - "ifind_event_enrichment_v1", trade_date, "ifind", result - ) - finally: - self._ifind_event_lock.release() - - @staticmethod - def _normalize_ifind_event_time(value: Any) -> str: - text = str(value or "").strip() - match = re.search(r"(?:^|\s)(\d{1,2}:\d{2}(?::\d{2})?)(?:$|\s)", text) - if not match: - match = re.search(r"(? None: - for kind in ("limits", "broken", "down_limits"): - records = enrichment.get(kind) or {} - for row in dashboard.get(kind) or []: - event = records.get(str(row.get("code") or "")) or {} - reason = str(event.get("reason") or "").strip() - if reason: - row["reason"] = reason - row["reason_source"] = "market_event" - if event.get("first_time"): - row["first_time"] = event["first_time"] - if event.get("last_time"): - row["last_time"] = event["last_time"] - if event.get("open_times") is not None: - row["open_times"] = event["open_times"] def _apply_seat_aliases(self, payload: dict[str, Any]) -> dict[str, Any]: aliases = self.database.list_seat_aliases() diff --git a/app/backend/data/providers/tushare_client.py b/app/backend/data/providers/tushare_client.py index a2f65b0..8e3c71e 100644 --- a/app/backend/data/providers/tushare_client.py +++ b/app/backend/data/providers/tushare_client.py @@ -11,7 +11,7 @@ from datetime import datetime, time as dt_time, timedelta from threading import Lock from typing import Any, ClassVar -from sentiment_engine import apply_sentiment_to_dashboard +from backend.features.sentiment.engine import apply_sentiment_to_dashboard TUSHARE_URL = "http://api.tushare.pro" diff --git a/app/backend/features/market/service.py b/app/backend/features/market/service.py index 3d5dccd..f4e29db 100644 --- a/app/backend/features/market/service.py +++ b/app/backend/features/market/service.py @@ -14,7 +14,7 @@ from backend.bootstrap.config import ( from backend.data.providers.ifind_client import IfindError from backend.data.providers.tushare_client import TushareClient, TushareError from backend.features.market.charts import ChartDataError -from sentiment_engine import SENTIMENT_ENGINE_VERSION +from backend.features.sentiment.engine import SENTIMENT_ENGINE_VERSION SEARCH_INDEXES = ( diff --git a/app/backend/features/pools/__init__.py b/app/backend/features/pools/__init__.py new file mode 100644 index 0000000..40bacfb --- /dev/null +++ b/app/backend/features/pools/__init__.py @@ -0,0 +1,6 @@ +"""Limit-up, broken-board, limit-down and prior-limit pool feature.""" + +from .repository import PoolRepositoryMixin +from .service import PoolServiceMixin + +__all__ = ["PoolRepositoryMixin", "PoolServiceMixin"] diff --git a/app/backend/features/pools/repository.py b/app/backend/features/pools/repository.py new file mode 100644 index 0000000..85b5dc0 --- /dev/null +++ b/app/backend/features/pools/repository.py @@ -0,0 +1,27 @@ +from __future__ import annotations + +from datetime import datetime + + +class PoolRepositoryMixin: + def save_reason_override(self, trade_date: str, code: str, reason: str) -> None: + now = datetime.now().astimezone().isoformat(timespec="seconds") + with self.connect() as connection: + connection.execute( + """ + INSERT INTO reason_overrides (trade_date, code, reason, updated_at) + VALUES (?, ?, ?, ?) + ON CONFLICT(trade_date, code) DO UPDATE SET + reason = excluded.reason, + updated_at = excluded.updated_at + """, + (trade_date, code, reason, now), + ) + + def reason_overrides(self, trade_date: str) -> dict[str, str]: + with self.connect() as connection: + rows = connection.execute( + "SELECT code, reason FROM reason_overrides WHERE trade_date = ?", + (trade_date,), + ).fetchall() + return {row["code"]: row["reason"] for row in rows} diff --git a/app/backend/features/pools/service.py b/app/backend/features/pools/service.py new file mode 100644 index 0000000..a4424f3 --- /dev/null +++ b/app/backend/features/pools/service.py @@ -0,0 +1,150 @@ +from __future__ import annotations + +import re +from datetime import datetime, time as dt_time +from typing import Any + +from backend.bootstrap.config import normalize_date, validate_stock_code +from backend.data.providers.ifind_client import IfindError + + +class PoolServiceMixin: + def save_reason(self, trade_date: str, code: str, reason: str) -> None: + normalized_date = normalize_date(trade_date) + code = validate_stock_code(code) + reason = reason.strip() + if not reason or len(reason) > 200: + raise ValueError("涨停原因应为 1 至 200 个字符。") + self.database.save_reason_override(normalized_date, code, reason) + + def _apply_reason_overrides(self, dashboard: dict[str, Any]) -> dict[str, Any]: + trade_date = str(dashboard.get("meta", {}).get("trade_date", "")).replace("-", "") + enrichment = self.database.get_data_snapshot("ifind_event_enrichment_v1", trade_date) + if enrichment: + self._merge_ifind_event_enrichment(dashboard, enrichment) + else: + self._schedule_ifind_event_enrichment(trade_date) + overrides = self.database.reason_overrides(trade_date) + if not overrides: + return dashboard + for key in ("limits", "broken", "down_limits"): + for row in dashboard.get(key) or []: + if row.get("code") in overrides: + row["reason"] = overrides[row["code"]] + row["reason_source"] = "manual" + return dashboard + + def _schedule_ifind_event_enrichment(self, trade_date: str) -> None: + ifind = getattr(self, "ifind", None) + if not ifind or not ifind.configured or not re.fullmatch(r"\d{8}", trade_date): + return + now = datetime.now().astimezone() + if trade_date == now.strftime("%Y%m%d") and now.time().replace(tzinfo=None) < dt_time(15, 0): + return + self.jobs.submit( + "market.ifind-event-enrichment", + f"{trade_date}:v1", + lambda: self._refresh_ifind_event_enrichment(trade_date), + {"trade_date": trade_date, "trigger": "dashboard-enrichment"}, + ) + + def _refresh_ifind_event_enrichment(self, trade_date: str) -> None: + if not self._ifind_event_lock.acquire(blocking=False): + return + try: + if self.database.get_data_snapshot("ifind_event_enrichment_v1", trade_date): + return + ifind = getattr(self, "ifind", None) + if not ifind or not ifind.configured: + return + current = datetime.strptime(trade_date, "%Y%m%d") + display_date = f"{current.year}年{current.month}月{current.day}日" + requests = { + "limits": ( + f"{display_date}涨停股票,股票代码、股票简称、涨停原因、" + "首次涨停时间、最终涨停时间、开板次数" + ), + "broken": ( + f"{display_date}曾涨停但收盘未涨停的股票,股票代码、股票简称、" + "涨停原因、首次涨停时间、开板次数" + ), + "down_limits": ( + f"{display_date}跌停股票,股票代码、股票简称、跌停原因" + ), + } + result: dict[str, Any] = { + "trade_date": trade_date, + "generated_at": datetime.now().astimezone().isoformat(timespec="seconds"), + "limits": {}, "broken": {}, "down_limits": {}, "partial": False, + } + for kind, query in requests.items(): + try: + rows = ifind.wencai(query, "stock", cache_ttl=900) + except IfindError: + result["partial"] = True + continue + for raw in rows: + code = self._ifind_row_code(raw) + if not code: + continue + reason_tokens = ( + ("跌停原因", "风险线索", "原因") + if kind == "down_limits" + else ("涨停原因类别", "涨停原因", "触板逻辑", "原因") + ) + reason = str(self._ifind_field(raw, reason_tokens) or "").strip() + first_time = self._normalize_ifind_event_time( + self._ifind_field(raw, ("首次涨停时间", "首次触板时间", "首次封板时间")) + ) + last_time = self._normalize_ifind_event_time( + self._ifind_field(raw, ("最终涨停时间", "最后涨停时间", "最后封板时间")) + ) + open_times = self._ifind_field(raw, ("开板次数", "打开涨停次数")) + try: + open_count = max(0, int(float(open_times))) if open_times not in (None, "") else None + except (TypeError, ValueError): + open_count = None + result[kind][code] = { + "reason": reason, + "first_time": first_time, + "last_time": last_time, + "open_times": open_count, + } + if any(result[kind] for kind in ("limits", "broken", "down_limits")): + self.database.save_data_snapshot( + "ifind_event_enrichment_v1", trade_date, "ifind", result + ) + finally: + self._ifind_event_lock.release() + + @staticmethod + def _normalize_ifind_event_time(value: Any) -> str: + text = str(value or "").strip() + match = re.search(r"(?:^|\s)(\d{1,2}:\d{2}(?::\d{2})?)(?:$|\s)", text) + if not match: + match = re.search(r"(? None: + for kind in ("limits", "broken", "down_limits"): + records = enrichment.get(kind) or {} + for row in dashboard.get(kind) or []: + event = records.get(str(row.get("code") or "")) or {} + reason = str(event.get("reason") or "").strip() + if reason: + row["reason"] = reason + row["reason_source"] = "market_event" + if event.get("first_time"): + row["first_time"] = event["first_time"] + if event.get("last_time"): + row["last_time"] = event["last_time"] + if event.get("open_times") is not None: + row["open_times"] = event["open_times"] diff --git a/app/backend/features/sentiment/__init__.py b/app/backend/features/sentiment/__init__.py new file mode 100644 index 0000000..5a5f280 --- /dev/null +++ b/app/backend/features/sentiment/__init__.py @@ -0,0 +1,19 @@ +"""Market sentiment cycle and history feature.""" + +from .engine import ( + COMPONENT_WEIGHTS, + SENTIMENT_ENGINE_VERSION, + apply_sentiment_to_dashboard, + build_sentiment_history, + latest_contiguous_history, +) +from .service import SentimentServiceMixin + +__all__ = [ + "COMPONENT_WEIGHTS", + "SENTIMENT_ENGINE_VERSION", + "SentimentServiceMixin", + "apply_sentiment_to_dashboard", + "build_sentiment_history", + "latest_contiguous_history", +] diff --git a/app/backend/features/sentiment/engine.py b/app/backend/features/sentiment/engine.py new file mode 100644 index 0000000..9345cd5 --- /dev/null +++ b/app/backend/features/sentiment/engine.py @@ -0,0 +1,496 @@ +from __future__ import annotations + +from copy import deepcopy +from statistics import mean, median +from typing import Any + + +COMPONENT_WEIGHTS = { + "breadth": 20, + "limit_ecology": 25, + "profit_effect": 30, + "ladder_structure": 15, + "liquidity": 10, +} + +SENTIMENT_ENGINE_VERSION = 2 + + +def _number(value: Any, default: float = 0.0) -> float: + try: + number = float(value) + return number if number == number else default + except (TypeError, ValueError): + return default + + +def _clamp(value: float, lower: float = 0.0, upper: float = 100.0) -> float: + return min(upper, max(lower, value)) + + +def _linear(value: float, low: float, high: float) -> float: + if high <= low: + return 50.0 + return _clamp((value - low) / (high - low) * 100) + + +def _percentile(value: float, history: list[float]) -> float: + if not history: + return 50.0 + below = sum(item < value for item in history) + equal = sum(item == value for item in history) + return _clamp((below + equal * 0.5) / len(history) * 100) + + +def _adaptive_score(value: float, fixed: float, history: list[float]) -> float: + if len(history) < 20: + return fixed + return fixed * 0.25 + _percentile(value, history[-250:]) * 0.75 + + +def _trade_date(payload: dict[str, Any]) -> str: + meta = payload.get("meta") or {} + return str(meta.get("trade_date") or payload.get("_snapshot_date") or "").replace("-", "") + + +def _deduplicate_snapshots(snapshots: list[dict[str, Any]]) -> list[dict[str, Any]]: + by_trade_date: dict[str, dict[str, Any]] = {} + for payload in snapshots: + trade_date = _trade_date(payload) + if trade_date: + by_trade_date[trade_date] = payload + return [by_trade_date[key] for key in sorted(by_trade_date)] + + +def _snapshot_stats(payload: dict[str, Any]) -> dict[str, Any]: + overview = payload.get("overview") or {} + meta = payload.get("meta") or {} + limits = list(payload.get("limits") or []) + broken = list(payload.get("broken") or []) + down_limits = list(payload.get("down_limits") or []) + yesterday = list(payload.get("yesterday_limits") or []) + + limit_up = len(limits) if limits else int(_number(overview.get("limit_up_count"))) + broken_count = len(broken) if broken else int(_number(overview.get("broken_count"))) + limit_down = len(down_limits) if down_limits else int(_number(overview.get("limit_down_count"))) + streaks = [max(1, int(_number(row.get("streak"), 1))) for row in limits] + first_board = sum(streak == 1 for streak in streaks) + second_board = sum(streak == 2 for streak in streaks) + three_plus = sum(streak >= 3 for streak in streaks) + max_height = max(streaks, default=0) + present_levels = set(streaks) + ladder_completeness = ( + sum(level in present_levels for level in range(1, max_height + 1)) / max_height * 100 + if max_height else 0.0 + ) + + up_count = int(_number(overview.get("up_count"))) + down_count = int(_number(overview.get("down_count"))) + flat_count = int(_number(overview.get("flat_count"))) + active_count = up_count + down_count + breadth_ratio = up_count / max(active_count, 1) * 100 + seal_rate = _number(overview.get("seal_rate")) + if not seal_rate and limit_up + broken_count: + seal_rate = limit_up / (limit_up + broken_count) * 100 + + previous_limit_count = len(yesterday) + previous_positive_count = sum(_number(row.get("current_change")) > 0 for row in yesterday) + previous_positive_rate = previous_positive_count / max(previous_limit_count, 1) * 100 + advanced_count = sum(row.get("outcome") == "晋级" for row in yesterday) + advance_rate = advanced_count / max(previous_limit_count, 1) * 100 + average_previous_change = ( + mean(_number(row.get("current_change")) for row in yesterday) if yesterday else 0.0 + ) + median_previous_change = ( + median(_number(row.get("current_change")) for row in yesterday) if yesterday else 0.0 + ) + severe_loss_count = sum(_number(row.get("current_change")) <= -5 for row in yesterday) + severe_loss_rate = severe_loss_count / max(previous_limit_count, 1) * 100 + previous_down_count = sum(row.get("outcome") == "跌停" for row in yesterday) + high_previous = [row for row in yesterday if int(_number(row.get("prior_streak"), 1)) >= 2] + high_positive_rate = ( + sum(_number(row.get("current_change")) > 0 for row in high_previous) + / max(len(high_previous), 1) + * 100 + ) + + amount_billion = _number(overview.get("amount_billion")) + limit_amount_billion = sum(_number(row.get("amount_billion")) for row in limits) + return { + "trade_date": _trade_date(payload), + "previous_trade_date": str(meta.get("previous_trade_date") or "").replace("-", ""), + "up_count": up_count, + "down_count": down_count, + "flat_count": flat_count, + "breadth_ratio": round(breadth_ratio, 1), + "limit_up_count": limit_up, + "first_board_count": first_board, + "second_board_count": second_board, + "three_plus_count": three_plus, + "max_height": max_height, + "ladder_completeness": round(ladder_completeness, 1), + "broken_count": broken_count, + "limit_down_count": limit_down, + "seal_rate": round(seal_rate, 1), + "previous_limit_count": previous_limit_count, + "previous_positive_count": previous_positive_count, + "previous_positive_rate": round(previous_positive_rate, 1), + "advance_rate": round(advance_rate, 1), + "average_previous_change": round(average_previous_change, 2), + "median_previous_change": round(median_previous_change, 2), + "severe_loss_count": severe_loss_count, + "severe_loss_rate": round(severe_loss_rate, 1), + "previous_down_count": previous_down_count, + "high_positive_rate": round(high_positive_rate, 1), + "amount_billion": round(amount_billion, 1), + "limit_amount_billion": round(limit_amount_billion, 2), + } + + +def _sentiment_label(score: float) -> str: + if score >= 80: + return "情绪高涨" + if score >= 60: + return "情绪偏强" + if score >= 40: + return "情绪中性" + if score >= 20: + return "情绪偏弱" + return "情绪冰点" + + +def _phase_signal(score: float, momentum: float, profit_score: float) -> str: + if score < 25: + return "修复" if momentum > 3 else "冰点" + if score < 45: + return "修复" if momentum > 3 else "退潮" + if score >= 80: + return "高潮" if momentum >= -2 and profit_score >= 60 else "分化" + if score >= 65: + return "分化" if momentum < -3 or profit_score < 50 else "发酵" + if momentum < -5: + return "退潮" + return "发酵" if momentum >= 0 and profit_score >= 45 else "分化" + + +def _confirmed_phase( + previous: dict[str, Any] | None, + score: float, + day_change: float, + systemic_health: float, + profit_score: float, + ecology_score: float, + phase_signal: str, + extreme_ice: bool, + fermentation_signal_count: int, +) -> tuple[str, str]: + if previous is None: + return phase_signal, "首个连续交易日,采用原始阶段信号" + previous_phase = str(previous.get("phase") or phase_signal) + if extreme_ice: + return "冰点", "市场宽度与跌停数量触发极端冰点" + + recovery = day_change >= 6 and score >= 25 and systemic_health >= 24 + fermentation_confirmed = fermentation_signal_count >= 2 + climax_ready = ( + score >= 80 + and profit_score >= 60 + and systemic_health >= 60 + and ecology_score >= 70 + ) + + if previous_phase == "冰点": + return ("修复", "冰点后首次有效回升") if recovery else ("冰点", "冰点尚未形成有效修复") + + if previous_phase == "退潮": + if score < 25: + return "冰点", "退潮继续下探至冰点区间" + return ("修复", "退潮后出现有效回升") if recovery else ("退潮", "退潮尚未形成有效修复") + + if previous_phase == "修复": + if score < 25: + return "冰点", "修复失败并重新跌入冰点区间" + if day_change <= -6 and score < 45: + return "退潮", "修复失败且温度显著回落" + if fermentation_confirmed: + return "发酵", "发酵条件连续两个交易日成立" + return "修复", "修复延续,等待发酵确认" + + if previous_phase == "发酵": + if score < 25: + return "冰点", "发酵阶段出现极端情绪坍塌" + if score < 45 and (day_change < 0 or systemic_health < 35): + return "退潮", "发酵阶段温度与系统健康度同步转弱" + if climax_ready: + return "高潮", "温度、赚钱效应与涨停生态共同达到高潮条件" + if phase_signal in {"分化", "退潮"} or day_change <= -6: + return "分化", "发酵阶段出现降温或赚钱效应弱化" + return "发酵", "发酵状态延续" + + if previous_phase == "高潮": + if score < 25: + return "冰点", "高潮后出现极端情绪坍塌" + if climax_ready: + return "高潮", "高潮条件继续成立" + if score < 45 or systemic_health < 30: + return "退潮", "高潮后风险快速释放" + return "分化", "高潮条件消退,进入分化" + + if previous_phase == "分化": + if score < 25: + return "冰点", "分化继续恶化至冰点区间" + if score < 45 or systemic_health < 30: + return "退潮", "分化后温度或系统健康度继续下降" + if fermentation_confirmed: + return "发酵", "分化转强条件连续两个交易日成立" + return "分化", "分化延续,等待方向确认" + + return phase_signal, "采用原始阶段信号" + + +def build_sentiment_history(snapshots: list[dict[str, Any]]) -> list[dict[str, Any]]: + payloads = _deduplicate_snapshots(snapshots) + raw_rows = [_snapshot_stats(payload) for payload in payloads] + results: list[dict[str, Any]] = [] + + for index, stats in enumerate(raw_rows): + previous = raw_rows[:index] + limit_history = [float(row["limit_up_count"]) for row in previous] + down_limit_history = [float(row["limit_down_count"]) for row in previous] + height_history = [float(row["max_height"]) for row in previous] + three_plus_history = [float(row["three_plus_count"]) for row in previous] + amount_history = [float(row["amount_billion"]) for row in previous[-20:] if row["amount_billion"]] + + breadth_score = _clamp(float(stats["breadth_ratio"])) + limit_strength = _adaptive_score( + float(stats["limit_up_count"]), + _linear(float(stats["limit_up_count"]), 10, 100), + limit_history, + ) + down_relief = 100 - _adaptive_score( + float(stats["limit_down_count"]), + _linear(float(stats["limit_down_count"]), 0, 50), + down_limit_history, + ) + seal_quality = _linear(float(stats["seal_rate"]), 35, 90) + systemic_health = breadth_score * 0.60 + down_relief * 0.40 + systemic_gate = 1.0 if systemic_health >= 35 else 0.35 + systemic_health / 35 * 0.65 + ecology_base_score = limit_strength * 0.35 + seal_quality * 0.35 + down_relief * 0.30 + # Systemic risk is applied once to the final temperature. Reapplying it here + # would count market breadth and limit-down pressure twice. + limit_ecology_score = ecology_base_score + + if stats["previous_limit_count"]: + positive_score = float(stats["previous_positive_rate"]) + average_change_score = _clamp(50 + float(stats["average_previous_change"]) * 6) + median_change_score = _clamp(50 + float(stats["median_previous_change"]) * 7) + advance_score = _clamp(float(stats["advance_rate"]) * 2.5) + severe_loss_safety = _clamp(100 - float(stats["severe_loss_rate"]) * 3) + down_safety = _clamp(100 - float(stats["previous_down_count"]) / stats["previous_limit_count"] * 700) + tail_safety_score = severe_loss_safety * 0.70 + down_safety * 0.30 + profit_effect_score = ( + positive_score * 0.30 + + median_change_score * 0.25 + + average_change_score * 0.10 + + advance_score * 0.20 + + tail_safety_score * 0.15 + ) + else: + profit_effect_score = 50.0 + + max_height_score = _adaptive_score( + float(stats["max_height"]), + _linear(float(stats["max_height"]), 1, 7), + height_history, + ) + continuation_rate = ( + (float(stats["second_board_count"]) + float(stats["three_plus_count"])) + / max(float(stats["limit_up_count"]), 1) + * 100 + ) + three_plus_density = float(stats["three_plus_count"]) / max(float(stats["limit_up_count"]), 1) * 100 + three_plus_score = _adaptive_score( + float(stats["three_plus_count"]), + _clamp(three_plus_density * 5), + three_plus_history, + ) + ladder_structure_score = ( + max_height_score * 0.30 + + _clamp(continuation_rate * 3) * 0.25 + + three_plus_score * 0.25 + + float(stats["ladder_completeness"]) * 0.20 + ) + + amount_baseline = mean(amount_history) if amount_history else float(stats["amount_billion"] or 1) + amount_ratio = float(stats["amount_billion"]) / max(amount_baseline, 1) + amount_score = _clamp(50 + (amount_ratio - 1) * 100) + limit_amount_share = float(stats["limit_amount_billion"]) / max(float(stats["amount_billion"]), 1) * 100 + liquidity_score = amount_score * 0.70 + _clamp(limit_amount_share * 20) * 0.30 + + component_scores = { + "breadth": breadth_score, + "limit_ecology": limit_ecology_score, + "profit_effect": profit_effect_score, + "ladder_structure": ladder_structure_score, + "liquidity": liquidity_score, + } + raw_score = sum(component_scores[key] * weight / 100 for key, weight in COMPONENT_WEIGHTS.items()) + score = round( + raw_score * systemic_gate + ) + extreme_ice = float(stats["breadth_ratio"]) <= 15 and float(stats["limit_down_count"]) >= 100 + if extreme_ice: + score = min(score, 15) + elif float(stats["breadth_ratio"]) <= 25 and float(stats["limit_down_count"]) >= 50: + score = min(score, 24) + previous_scores: list[float] = [] + expected_date = str(stats.get("previous_trade_date") or "") + for prior_result in reversed(results): + if not expected_date or str(prior_result.get("trade_date") or "") != expected_date: + break + previous_scores.append(float(prior_result["score"])) + expected_date = str(prior_result.get("previous_trade_date") or "") + if len(previous_scores) == 3: + break + momentum = score - mean(previous_scores) if previous_scores else 0.0 + direction = "升温" if momentum > 3 else "降温" if momentum < -3 else "持平" + normalization = "历史百分位" if len(previous) >= 20 else "固定锚点" + previous_result = ( + results[-1] + if results and str(stats.get("previous_trade_date") or "") == str(results[-1].get("trade_date") or "") + else None + ) + day_change = score - float(previous_result["score"]) if previous_result else 0.0 + ema_score = round( + score if not previous_result + else score * 0.5 + float(previous_result.get("ema_score", previous_result["score"])) * 0.5, + 1, + ) + phase_signal = _phase_signal(score, momentum, profit_effect_score) + fermentation_ready = ( + phase_signal == "发酵" + and score >= 45 + and profit_effect_score >= 45 + and systemic_health >= 35 + and not extreme_ice + ) + previous_fermentation_count = int(previous_result.get("fermentation_signal_count") or 0) if previous_result else 0 + fermentation_signal_count = previous_fermentation_count + 1 if fermentation_ready else 0 + phase, transition_reason = _confirmed_phase( + previous_result, + score, + day_change, + systemic_health, + profit_effect_score, + limit_ecology_score, + phase_signal, + extreme_ice, + fermentation_signal_count, + ) + previous_phase = str(previous_result.get("phase") or "") if previous_result else "" + if phase not in {"修复", "分化"}: + fermentation_signal_count = 0 + elif phase == "分化" and previous_phase != "分化": + fermentation_signal_count = 0 + + components = { + "breadth": { + "label": "市场宽度", + "score": round(breadth_score, 1), + "weight": COMPONENT_WEIGHTS["breadth"], + "summary": f"上涨占比 {stats['breadth_ratio']:.1f}%", + }, + "limit_ecology": { + "label": "涨停生态", + "score": round(limit_ecology_score, 1), + "weight": COMPONENT_WEIGHTS["limit_ecology"], + "summary": ( + f"涨停 {stats['limit_up_count']} · 跌停 {stats['limit_down_count']} · " + f"封板 {stats['seal_rate']:.1f}%" + ), + }, + "profit_effect": { + "label": "赚钱效应", + "score": round(profit_effect_score, 1), + "weight": COMPONENT_WEIGHTS["profit_effect"], + "summary": ( + f"昨涨停红盘 {stats['previous_positive_rate']:.1f}% · " + f"中位 {stats['median_previous_change']:+.2f}% · " + f"重亏 {stats['severe_loss_rate']:.1f}%" + if stats["previous_limit_count"] else "缺少前一交易日样本" + ), + }, + "ladder_structure": { + "label": "连板结构", + "score": round(ladder_structure_score, 1), + "weight": COMPONENT_WEIGHTS["ladder_structure"], + "summary": f"最高 {stats['max_height']} 板 · 三板以上 {stats['three_plus_count']} 家", + }, + "liquidity": { + "label": "成交活跃度", + "score": round(liquidity_score, 1), + "weight": COMPONENT_WEIGHTS["liquidity"], + "summary": f"成交 {stats['amount_billion']:.1f} 亿 · 均值比 {amount_ratio:.2f}", + }, + } + results.append( + { + **stats, + "score": score, + "ema_score": ema_score, + "label": _sentiment_label(score), + "phase": phase, + "phase_signal": phase_signal, + "transition_reason": transition_reason, + "fermentation_signal_count": fermentation_signal_count, + "day_change": round(day_change, 1), + "direction": direction, + "momentum": round(momentum, 1), + "normalization": "250日历史百分位" if len(previous) >= 20 else normalization, + "history_days": len(previous) + 1, + "systemic_health": round(systemic_health, 1), + "risk_multiplier": round(systemic_gate, 3), + "components": components, + } + ) + return results + + +def latest_contiguous_history(series: list[dict[str, Any]]) -> list[dict[str, Any]]: + if not series: + return [] + contiguous = [series[-1]] + for row in reversed(series[:-1]): + expected_previous = str(contiguous[0].get("previous_trade_date") or "") + if not expected_previous or expected_previous != str(row.get("trade_date") or ""): + break + contiguous.insert(0, row) + return contiguous + + +def apply_sentiment_to_dashboard( + dashboard: dict[str, Any], + historical_snapshots: list[dict[str, Any]] | None = None, +) -> dict[str, Any]: + result = deepcopy(dashboard) + history = list(historical_snapshots or []) + history.append(result) + series = build_sentiment_history(history) + target_date = _trade_date(result) + sentiment = next((row for row in reversed(series) if row["trade_date"] == target_date), None) + if not sentiment: + return result + overview = dict(result.get("overview") or {}) + overview.update( + { + "sentiment_score": sentiment["score"], + "sentiment_trend_score": sentiment["ema_score"], + "sentiment_label": sentiment["label"], + "sentiment_phase": sentiment["phase"], + "sentiment_direction": sentiment["direction"], + "sentiment_components": sentiment["components"], + "sentiment_engine_version": SENTIMENT_ENGINE_VERSION, + } + ) + result["overview"] = overview + return result diff --git a/app/backend/features/sentiment/service.py b/app/backend/features/sentiment/service.py new file mode 100644 index 0000000..0947d4c --- /dev/null +++ b/app/backend/features/sentiment/service.py @@ -0,0 +1,39 @@ +from __future__ import annotations + +from typing import Any + +from backend.bootstrap.config import normalize_date +from backend.features.sentiment.engine import ( + COMPONENT_WEIGHTS, + apply_sentiment_to_dashboard, + build_sentiment_history, + latest_contiguous_history, +) + + +class SentimentServiceMixin: + def _enrich_dashboard_sentiment( + self, + dashboard: dict[str, Any], + end_date: str, + ) -> dict[str, Any]: + history = self.database.list_snapshot_payloads(end_date, 260) + return apply_sentiment_to_dashboard(dashboard, history) + + def sentiment_history(self, trade_date: str, limit: int = 20) -> dict[str, Any]: + normalized_date = normalize_date(trade_date) + limit = max(10, min(120, int(limit))) + full_series = build_sentiment_history( + self.database.list_snapshot_payloads(normalized_date, 240) + ) + series = latest_contiguous_history(full_series) + rows = series[-limit:] + return { + "trade_date": rows[-1]["trade_date"] if rows else normalized_date, + "available_days": len(series), + "stored_days": len(full_series), + "requested_days": limit, + "rows": rows, + "weights": COMPONENT_WEIGHTS, + "normalization": rows[-1]["normalization"] if rows else "固定锚点", + } diff --git a/app/database.py b/app/database.py index 9a3b60b..8fe4266 100644 --- a/app/database.py +++ b/app/database.py @@ -9,6 +9,7 @@ from typing import Any from backend.database import MIGRATIONS, MigrationRunner, SQLiteConnectionFactory from backend.features.accounts.repository import AccountRepositoryMixin from backend.features.market.repository import MarketRepositoryMixin +from backend.features.pools.repository import PoolRepositoryMixin from backend.features.system.repository import SystemSettingsRepositoryMixin @@ -24,6 +25,7 @@ def _optional_float(value: Any) -> float | None: class ReviewDatabase( AccountRepositoryMixin, MarketRepositoryMixin, + PoolRepositoryMixin, SystemSettingsRepositoryMixin, ): def __init__(self, path: Path) -> None: @@ -836,27 +838,6 @@ class ReviewDatabase( ) return cursor.rowcount > 0 - def save_reason_override(self, trade_date: str, code: str, reason: str) -> None: - now = datetime.now().astimezone().isoformat(timespec="seconds") - with self.connect() as connection: - connection.execute( - """ - INSERT INTO reason_overrides (trade_date, code, reason, updated_at) - VALUES (?, ?, ?, ?) - ON CONFLICT(trade_date, code) DO UPDATE SET - reason = excluded.reason, - updated_at = excluded.updated_at - """, - (trade_date, code, reason, now), - ) - - def reason_overrides(self, trade_date: str) -> dict[str, str]: - with self.connect() as connection: - rows = connection.execute( - "SELECT code, reason FROM reason_overrides WHERE trade_date = ?", - (trade_date,), - ).fetchall() - return {row["code"]: row["reason"] for row in rows} def list_seat_aliases(self) -> dict[str, str]: with self.connect() as connection: diff --git a/app/demo_data.py b/app/demo_data.py index 9bf51a6..e1165b8 100644 --- a/app/demo_data.py +++ b/app/demo_data.py @@ -5,7 +5,7 @@ import math from datetime import datetime, timedelta from typing import Any -from sentiment_engine import apply_sentiment_to_dashboard +from backend.features.sentiment.engine import apply_sentiment_to_dashboard DEMO_LIMITS = [ diff --git a/app/screener.py b/app/screener.py index 1ee940e..d0796db 100644 --- a/app/screener.py +++ b/app/screener.py @@ -10,7 +10,7 @@ from typing import Any from advanced_strategies import ADVANCED_CURATED_STRATEGIES from database import ReviewDatabase -from sentiment_engine import build_sentiment_history, latest_contiguous_history +from backend.features.sentiment.engine import build_sentiment_history, latest_contiguous_history from tushare_client import TushareClient, TushareError diff --git a/app/sentiment_engine.py b/app/sentiment_engine.py index 9345cd5..0d462ac 100644 --- a/app/sentiment_engine.py +++ b/app/sentiment_engine.py @@ -1,496 +1,7 @@ -from __future__ import annotations +"""Compatibility alias for the canonical sentiment engine implementation.""" -from copy import deepcopy -from statistics import mean, median -from typing import Any +import sys +from backend.features.sentiment import engine as _implementation -COMPONENT_WEIGHTS = { - "breadth": 20, - "limit_ecology": 25, - "profit_effect": 30, - "ladder_structure": 15, - "liquidity": 10, -} - -SENTIMENT_ENGINE_VERSION = 2 - - -def _number(value: Any, default: float = 0.0) -> float: - try: - number = float(value) - return number if number == number else default - except (TypeError, ValueError): - return default - - -def _clamp(value: float, lower: float = 0.0, upper: float = 100.0) -> float: - return min(upper, max(lower, value)) - - -def _linear(value: float, low: float, high: float) -> float: - if high <= low: - return 50.0 - return _clamp((value - low) / (high - low) * 100) - - -def _percentile(value: float, history: list[float]) -> float: - if not history: - return 50.0 - below = sum(item < value for item in history) - equal = sum(item == value for item in history) - return _clamp((below + equal * 0.5) / len(history) * 100) - - -def _adaptive_score(value: float, fixed: float, history: list[float]) -> float: - if len(history) < 20: - return fixed - return fixed * 0.25 + _percentile(value, history[-250:]) * 0.75 - - -def _trade_date(payload: dict[str, Any]) -> str: - meta = payload.get("meta") or {} - return str(meta.get("trade_date") or payload.get("_snapshot_date") or "").replace("-", "") - - -def _deduplicate_snapshots(snapshots: list[dict[str, Any]]) -> list[dict[str, Any]]: - by_trade_date: dict[str, dict[str, Any]] = {} - for payload in snapshots: - trade_date = _trade_date(payload) - if trade_date: - by_trade_date[trade_date] = payload - return [by_trade_date[key] for key in sorted(by_trade_date)] - - -def _snapshot_stats(payload: dict[str, Any]) -> dict[str, Any]: - overview = payload.get("overview") or {} - meta = payload.get("meta") or {} - limits = list(payload.get("limits") or []) - broken = list(payload.get("broken") or []) - down_limits = list(payload.get("down_limits") or []) - yesterday = list(payload.get("yesterday_limits") or []) - - limit_up = len(limits) if limits else int(_number(overview.get("limit_up_count"))) - broken_count = len(broken) if broken else int(_number(overview.get("broken_count"))) - limit_down = len(down_limits) if down_limits else int(_number(overview.get("limit_down_count"))) - streaks = [max(1, int(_number(row.get("streak"), 1))) for row in limits] - first_board = sum(streak == 1 for streak in streaks) - second_board = sum(streak == 2 for streak in streaks) - three_plus = sum(streak >= 3 for streak in streaks) - max_height = max(streaks, default=0) - present_levels = set(streaks) - ladder_completeness = ( - sum(level in present_levels for level in range(1, max_height + 1)) / max_height * 100 - if max_height else 0.0 - ) - - up_count = int(_number(overview.get("up_count"))) - down_count = int(_number(overview.get("down_count"))) - flat_count = int(_number(overview.get("flat_count"))) - active_count = up_count + down_count - breadth_ratio = up_count / max(active_count, 1) * 100 - seal_rate = _number(overview.get("seal_rate")) - if not seal_rate and limit_up + broken_count: - seal_rate = limit_up / (limit_up + broken_count) * 100 - - previous_limit_count = len(yesterday) - previous_positive_count = sum(_number(row.get("current_change")) > 0 for row in yesterday) - previous_positive_rate = previous_positive_count / max(previous_limit_count, 1) * 100 - advanced_count = sum(row.get("outcome") == "晋级" for row in yesterday) - advance_rate = advanced_count / max(previous_limit_count, 1) * 100 - average_previous_change = ( - mean(_number(row.get("current_change")) for row in yesterday) if yesterday else 0.0 - ) - median_previous_change = ( - median(_number(row.get("current_change")) for row in yesterday) if yesterday else 0.0 - ) - severe_loss_count = sum(_number(row.get("current_change")) <= -5 for row in yesterday) - severe_loss_rate = severe_loss_count / max(previous_limit_count, 1) * 100 - previous_down_count = sum(row.get("outcome") == "跌停" for row in yesterday) - high_previous = [row for row in yesterday if int(_number(row.get("prior_streak"), 1)) >= 2] - high_positive_rate = ( - sum(_number(row.get("current_change")) > 0 for row in high_previous) - / max(len(high_previous), 1) - * 100 - ) - - amount_billion = _number(overview.get("amount_billion")) - limit_amount_billion = sum(_number(row.get("amount_billion")) for row in limits) - return { - "trade_date": _trade_date(payload), - "previous_trade_date": str(meta.get("previous_trade_date") or "").replace("-", ""), - "up_count": up_count, - "down_count": down_count, - "flat_count": flat_count, - "breadth_ratio": round(breadth_ratio, 1), - "limit_up_count": limit_up, - "first_board_count": first_board, - "second_board_count": second_board, - "three_plus_count": three_plus, - "max_height": max_height, - "ladder_completeness": round(ladder_completeness, 1), - "broken_count": broken_count, - "limit_down_count": limit_down, - "seal_rate": round(seal_rate, 1), - "previous_limit_count": previous_limit_count, - "previous_positive_count": previous_positive_count, - "previous_positive_rate": round(previous_positive_rate, 1), - "advance_rate": round(advance_rate, 1), - "average_previous_change": round(average_previous_change, 2), - "median_previous_change": round(median_previous_change, 2), - "severe_loss_count": severe_loss_count, - "severe_loss_rate": round(severe_loss_rate, 1), - "previous_down_count": previous_down_count, - "high_positive_rate": round(high_positive_rate, 1), - "amount_billion": round(amount_billion, 1), - "limit_amount_billion": round(limit_amount_billion, 2), - } - - -def _sentiment_label(score: float) -> str: - if score >= 80: - return "情绪高涨" - if score >= 60: - return "情绪偏强" - if score >= 40: - return "情绪中性" - if score >= 20: - return "情绪偏弱" - return "情绪冰点" - - -def _phase_signal(score: float, momentum: float, profit_score: float) -> str: - if score < 25: - return "修复" if momentum > 3 else "冰点" - if score < 45: - return "修复" if momentum > 3 else "退潮" - if score >= 80: - return "高潮" if momentum >= -2 and profit_score >= 60 else "分化" - if score >= 65: - return "分化" if momentum < -3 or profit_score < 50 else "发酵" - if momentum < -5: - return "退潮" - return "发酵" if momentum >= 0 and profit_score >= 45 else "分化" - - -def _confirmed_phase( - previous: dict[str, Any] | None, - score: float, - day_change: float, - systemic_health: float, - profit_score: float, - ecology_score: float, - phase_signal: str, - extreme_ice: bool, - fermentation_signal_count: int, -) -> tuple[str, str]: - if previous is None: - return phase_signal, "首个连续交易日,采用原始阶段信号" - previous_phase = str(previous.get("phase") or phase_signal) - if extreme_ice: - return "冰点", "市场宽度与跌停数量触发极端冰点" - - recovery = day_change >= 6 and score >= 25 and systemic_health >= 24 - fermentation_confirmed = fermentation_signal_count >= 2 - climax_ready = ( - score >= 80 - and profit_score >= 60 - and systemic_health >= 60 - and ecology_score >= 70 - ) - - if previous_phase == "冰点": - return ("修复", "冰点后首次有效回升") if recovery else ("冰点", "冰点尚未形成有效修复") - - if previous_phase == "退潮": - if score < 25: - return "冰点", "退潮继续下探至冰点区间" - return ("修复", "退潮后出现有效回升") if recovery else ("退潮", "退潮尚未形成有效修复") - - if previous_phase == "修复": - if score < 25: - return "冰点", "修复失败并重新跌入冰点区间" - if day_change <= -6 and score < 45: - return "退潮", "修复失败且温度显著回落" - if fermentation_confirmed: - return "发酵", "发酵条件连续两个交易日成立" - return "修复", "修复延续,等待发酵确认" - - if previous_phase == "发酵": - if score < 25: - return "冰点", "发酵阶段出现极端情绪坍塌" - if score < 45 and (day_change < 0 or systemic_health < 35): - return "退潮", "发酵阶段温度与系统健康度同步转弱" - if climax_ready: - return "高潮", "温度、赚钱效应与涨停生态共同达到高潮条件" - if phase_signal in {"分化", "退潮"} or day_change <= -6: - return "分化", "发酵阶段出现降温或赚钱效应弱化" - return "发酵", "发酵状态延续" - - if previous_phase == "高潮": - if score < 25: - return "冰点", "高潮后出现极端情绪坍塌" - if climax_ready: - return "高潮", "高潮条件继续成立" - if score < 45 or systemic_health < 30: - return "退潮", "高潮后风险快速释放" - return "分化", "高潮条件消退,进入分化" - - if previous_phase == "分化": - if score < 25: - return "冰点", "分化继续恶化至冰点区间" - if score < 45 or systemic_health < 30: - return "退潮", "分化后温度或系统健康度继续下降" - if fermentation_confirmed: - return "发酵", "分化转强条件连续两个交易日成立" - return "分化", "分化延续,等待方向确认" - - return phase_signal, "采用原始阶段信号" - - -def build_sentiment_history(snapshots: list[dict[str, Any]]) -> list[dict[str, Any]]: - payloads = _deduplicate_snapshots(snapshots) - raw_rows = [_snapshot_stats(payload) for payload in payloads] - results: list[dict[str, Any]] = [] - - for index, stats in enumerate(raw_rows): - previous = raw_rows[:index] - limit_history = [float(row["limit_up_count"]) for row in previous] - down_limit_history = [float(row["limit_down_count"]) for row in previous] - height_history = [float(row["max_height"]) for row in previous] - three_plus_history = [float(row["three_plus_count"]) for row in previous] - amount_history = [float(row["amount_billion"]) for row in previous[-20:] if row["amount_billion"]] - - breadth_score = _clamp(float(stats["breadth_ratio"])) - limit_strength = _adaptive_score( - float(stats["limit_up_count"]), - _linear(float(stats["limit_up_count"]), 10, 100), - limit_history, - ) - down_relief = 100 - _adaptive_score( - float(stats["limit_down_count"]), - _linear(float(stats["limit_down_count"]), 0, 50), - down_limit_history, - ) - seal_quality = _linear(float(stats["seal_rate"]), 35, 90) - systemic_health = breadth_score * 0.60 + down_relief * 0.40 - systemic_gate = 1.0 if systemic_health >= 35 else 0.35 + systemic_health / 35 * 0.65 - ecology_base_score = limit_strength * 0.35 + seal_quality * 0.35 + down_relief * 0.30 - # Systemic risk is applied once to the final temperature. Reapplying it here - # would count market breadth and limit-down pressure twice. - limit_ecology_score = ecology_base_score - - if stats["previous_limit_count"]: - positive_score = float(stats["previous_positive_rate"]) - average_change_score = _clamp(50 + float(stats["average_previous_change"]) * 6) - median_change_score = _clamp(50 + float(stats["median_previous_change"]) * 7) - advance_score = _clamp(float(stats["advance_rate"]) * 2.5) - severe_loss_safety = _clamp(100 - float(stats["severe_loss_rate"]) * 3) - down_safety = _clamp(100 - float(stats["previous_down_count"]) / stats["previous_limit_count"] * 700) - tail_safety_score = severe_loss_safety * 0.70 + down_safety * 0.30 - profit_effect_score = ( - positive_score * 0.30 - + median_change_score * 0.25 - + average_change_score * 0.10 - + advance_score * 0.20 - + tail_safety_score * 0.15 - ) - else: - profit_effect_score = 50.0 - - max_height_score = _adaptive_score( - float(stats["max_height"]), - _linear(float(stats["max_height"]), 1, 7), - height_history, - ) - continuation_rate = ( - (float(stats["second_board_count"]) + float(stats["three_plus_count"])) - / max(float(stats["limit_up_count"]), 1) - * 100 - ) - three_plus_density = float(stats["three_plus_count"]) / max(float(stats["limit_up_count"]), 1) * 100 - three_plus_score = _adaptive_score( - float(stats["three_plus_count"]), - _clamp(three_plus_density * 5), - three_plus_history, - ) - ladder_structure_score = ( - max_height_score * 0.30 - + _clamp(continuation_rate * 3) * 0.25 - + three_plus_score * 0.25 - + float(stats["ladder_completeness"]) * 0.20 - ) - - amount_baseline = mean(amount_history) if amount_history else float(stats["amount_billion"] or 1) - amount_ratio = float(stats["amount_billion"]) / max(amount_baseline, 1) - amount_score = _clamp(50 + (amount_ratio - 1) * 100) - limit_amount_share = float(stats["limit_amount_billion"]) / max(float(stats["amount_billion"]), 1) * 100 - liquidity_score = amount_score * 0.70 + _clamp(limit_amount_share * 20) * 0.30 - - component_scores = { - "breadth": breadth_score, - "limit_ecology": limit_ecology_score, - "profit_effect": profit_effect_score, - "ladder_structure": ladder_structure_score, - "liquidity": liquidity_score, - } - raw_score = sum(component_scores[key] * weight / 100 for key, weight in COMPONENT_WEIGHTS.items()) - score = round( - raw_score * systemic_gate - ) - extreme_ice = float(stats["breadth_ratio"]) <= 15 and float(stats["limit_down_count"]) >= 100 - if extreme_ice: - score = min(score, 15) - elif float(stats["breadth_ratio"]) <= 25 and float(stats["limit_down_count"]) >= 50: - score = min(score, 24) - previous_scores: list[float] = [] - expected_date = str(stats.get("previous_trade_date") or "") - for prior_result in reversed(results): - if not expected_date or str(prior_result.get("trade_date") or "") != expected_date: - break - previous_scores.append(float(prior_result["score"])) - expected_date = str(prior_result.get("previous_trade_date") or "") - if len(previous_scores) == 3: - break - momentum = score - mean(previous_scores) if previous_scores else 0.0 - direction = "升温" if momentum > 3 else "降温" if momentum < -3 else "持平" - normalization = "历史百分位" if len(previous) >= 20 else "固定锚点" - previous_result = ( - results[-1] - if results and str(stats.get("previous_trade_date") or "") == str(results[-1].get("trade_date") or "") - else None - ) - day_change = score - float(previous_result["score"]) if previous_result else 0.0 - ema_score = round( - score if not previous_result - else score * 0.5 + float(previous_result.get("ema_score", previous_result["score"])) * 0.5, - 1, - ) - phase_signal = _phase_signal(score, momentum, profit_effect_score) - fermentation_ready = ( - phase_signal == "发酵" - and score >= 45 - and profit_effect_score >= 45 - and systemic_health >= 35 - and not extreme_ice - ) - previous_fermentation_count = int(previous_result.get("fermentation_signal_count") or 0) if previous_result else 0 - fermentation_signal_count = previous_fermentation_count + 1 if fermentation_ready else 0 - phase, transition_reason = _confirmed_phase( - previous_result, - score, - day_change, - systemic_health, - profit_effect_score, - limit_ecology_score, - phase_signal, - extreme_ice, - fermentation_signal_count, - ) - previous_phase = str(previous_result.get("phase") or "") if previous_result else "" - if phase not in {"修复", "分化"}: - fermentation_signal_count = 0 - elif phase == "分化" and previous_phase != "分化": - fermentation_signal_count = 0 - - components = { - "breadth": { - "label": "市场宽度", - "score": round(breadth_score, 1), - "weight": COMPONENT_WEIGHTS["breadth"], - "summary": f"上涨占比 {stats['breadth_ratio']:.1f}%", - }, - "limit_ecology": { - "label": "涨停生态", - "score": round(limit_ecology_score, 1), - "weight": COMPONENT_WEIGHTS["limit_ecology"], - "summary": ( - f"涨停 {stats['limit_up_count']} · 跌停 {stats['limit_down_count']} · " - f"封板 {stats['seal_rate']:.1f}%" - ), - }, - "profit_effect": { - "label": "赚钱效应", - "score": round(profit_effect_score, 1), - "weight": COMPONENT_WEIGHTS["profit_effect"], - "summary": ( - f"昨涨停红盘 {stats['previous_positive_rate']:.1f}% · " - f"中位 {stats['median_previous_change']:+.2f}% · " - f"重亏 {stats['severe_loss_rate']:.1f}%" - if stats["previous_limit_count"] else "缺少前一交易日样本" - ), - }, - "ladder_structure": { - "label": "连板结构", - "score": round(ladder_structure_score, 1), - "weight": COMPONENT_WEIGHTS["ladder_structure"], - "summary": f"最高 {stats['max_height']} 板 · 三板以上 {stats['three_plus_count']} 家", - }, - "liquidity": { - "label": "成交活跃度", - "score": round(liquidity_score, 1), - "weight": COMPONENT_WEIGHTS["liquidity"], - "summary": f"成交 {stats['amount_billion']:.1f} 亿 · 均值比 {amount_ratio:.2f}", - }, - } - results.append( - { - **stats, - "score": score, - "ema_score": ema_score, - "label": _sentiment_label(score), - "phase": phase, - "phase_signal": phase_signal, - "transition_reason": transition_reason, - "fermentation_signal_count": fermentation_signal_count, - "day_change": round(day_change, 1), - "direction": direction, - "momentum": round(momentum, 1), - "normalization": "250日历史百分位" if len(previous) >= 20 else normalization, - "history_days": len(previous) + 1, - "systemic_health": round(systemic_health, 1), - "risk_multiplier": round(systemic_gate, 3), - "components": components, - } - ) - return results - - -def latest_contiguous_history(series: list[dict[str, Any]]) -> list[dict[str, Any]]: - if not series: - return [] - contiguous = [series[-1]] - for row in reversed(series[:-1]): - expected_previous = str(contiguous[0].get("previous_trade_date") or "") - if not expected_previous or expected_previous != str(row.get("trade_date") or ""): - break - contiguous.insert(0, row) - return contiguous - - -def apply_sentiment_to_dashboard( - dashboard: dict[str, Any], - historical_snapshots: list[dict[str, Any]] | None = None, -) -> dict[str, Any]: - result = deepcopy(dashboard) - history = list(historical_snapshots or []) - history.append(result) - series = build_sentiment_history(history) - target_date = _trade_date(result) - sentiment = next((row for row in reversed(series) if row["trade_date"] == target_date), None) - if not sentiment: - return result - overview = dict(result.get("overview") or {}) - overview.update( - { - "sentiment_score": sentiment["score"], - "sentiment_trend_score": sentiment["ema_score"], - "sentiment_label": sentiment["label"], - "sentiment_phase": sentiment["phase"], - "sentiment_direction": sentiment["direction"], - "sentiment_components": sentiment["components"], - "sentiment_engine_version": SENTIMENT_ENGINE_VERSION, - } - ) - result["overview"] = overview - return result +sys.modules[__name__] = _implementation diff --git a/app/tests/test_preservation_slice_market.py b/app/tests/test_preservation_slice_market.py index e0bbb29..d15d042 100644 --- a/app/tests/test_preservation_slice_market.py +++ b/app/tests/test_preservation_slice_market.py @@ -127,12 +127,15 @@ class MarketSliceSourceEquivalenceTests(unittest.TestCase): def test_provider_logic_is_the_original_implementation(self) -> None: exact_moves = ( - ("tushare_client.py", "backend/data/providers/tushare_client.py"), ("ifind_client.py", "backend/data/providers/ifind_client.py"), ("realtime_aggregator.py", "backend/data/realtime.py"), ) for original, migrated in exact_moves: self.assertEqual(sha256(ORIGINAL_ROOT / original), sha256(APP_ROOT / migrated)) + self.assertEqual( + top_level_definitions(ORIGINAL_ROOT / "tushare_client.py"), + top_level_definitions(APP_ROOT / "backend/data/providers/tushare_client.py"), + ) self.assertEqual( top_level_definitions(ORIGINAL_ROOT / "chart_data_provider.py"), top_level_definitions(APP_ROOT / "backend/features/market/charts.py"), diff --git a/app/tests/test_preservation_slice_sentiment_pools.py b/app/tests/test_preservation_slice_sentiment_pools.py new file mode 100644 index 0000000..69aec93 --- /dev/null +++ b/app/tests/test_preservation_slice_sentiment_pools.py @@ -0,0 +1,114 @@ +from __future__ import annotations + +import ast +import hashlib +import unittest +from pathlib import Path + +import sentiment_engine +from backend.features.sentiment import engine as canonical_engine + + +APP_ROOT = Path(__file__).resolve().parents[1] +ORIGINAL_ROOT = APP_ROOT.parent + +SENTIMENT_METHODS = { + "_enrich_dashboard_sentiment", + "sentiment_history", +} +POOL_METHODS = { + "save_reason", + "_apply_reason_overrides", + "_schedule_ifind_event_enrichment", + "_refresh_ifind_event_enrichment", + "_normalize_ifind_event_time", + "_merge_ifind_event_enrichment", +} +POOL_REPOSITORY_METHODS = { + "save_reason_override", + "reason_overrides", +} + + +def class_methods(path: Path, class_name: str) -> dict[str, str]: + tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path)) + owner = next( + node + for node in tree.body + if isinstance(node, ast.ClassDef) and node.name == class_name + ) + return { + node.name: ast.dump(node, include_attributes=False) + for node in owner.body + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) + } + + +def sha256(path: Path) -> str: + return hashlib.sha256(path.read_bytes()).hexdigest() + + +class SentimentPoolSliceSourceEquivalenceTests(unittest.TestCase): + def test_sentiment_service_methods_are_exact_original_ast(self) -> None: + original = class_methods(ORIGINAL_ROOT / "server.py", "DashboardService") + migrated = class_methods( + APP_ROOT / "backend" / "features" / "sentiment" / "service.py", + "SentimentServiceMixin", + ) + self.assertEqual(set(migrated), SENTIMENT_METHODS) + for name in sorted(SENTIMENT_METHODS): + self.assertEqual(migrated[name], original[name], name) + + def test_pool_service_methods_are_exact_original_ast(self) -> None: + original = class_methods(ORIGINAL_ROOT / "server.py", "DashboardService") + migrated = class_methods( + APP_ROOT / "backend" / "features" / "pools" / "service.py", + "PoolServiceMixin", + ) + self.assertEqual(set(migrated), POOL_METHODS) + for name in sorted(POOL_METHODS): + self.assertEqual(migrated[name], original[name], name) + + def test_pool_repository_methods_are_exact_original_ast(self) -> None: + original = class_methods(ORIGINAL_ROOT / "database.py", "ReviewDatabase") + migrated = class_methods( + APP_ROOT / "backend" / "features" / "pools" / "repository.py", + "PoolRepositoryMixin", + ) + self.assertEqual(set(migrated), POOL_REPOSITORY_METHODS) + for name in sorted(POOL_REPOSITORY_METHODS): + self.assertEqual(migrated[name], original[name], name) + + def test_original_classes_no_longer_duplicate_moved_methods(self) -> None: + remaining_service = class_methods( + APP_ROOT / "backend" / "application.py", "DashboardService" + ) + remaining_database = class_methods(APP_ROOT / "database.py", "ReviewDatabase") + self.assertTrue((SENTIMENT_METHODS | POOL_METHODS).isdisjoint(remaining_service)) + self.assertTrue(POOL_REPOSITORY_METHODS.isdisjoint(remaining_database)) + + def test_sentiment_engine_is_exact_original_with_legacy_alias(self) -> None: + self.assertEqual( + sha256(ORIGINAL_ROOT / "sentiment_engine.py"), + sha256(APP_ROOT / "backend" / "features" / "sentiment" / "engine.py"), + ) + self.assertIs(sentiment_engine, canonical_engine) + + def test_api_and_frontend_assets_are_unchanged(self) -> None: + for relative in ( + "config/api.config.json", + "static/index.html", + "static/app.js", + "static/styles.css", + "static/pages/sentiment/page.js", + "static/pages/pools/page.js", + ): + self.assertEqual( + sha256(APP_ROOT / relative), + sha256(ORIGINAL_ROOT / relative), + relative, + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/docs/migration/evidence/slice-03/README.md b/docs/migration/evidence/slice-03/README.md new file mode 100644 index 0000000..91b469c --- /dev/null +++ b/docs/migration/evidence/slice-03/README.md @@ -0,0 +1,66 @@ +# 切片 03:情绪周期、五类股池与涨停表现 + +> 基线:`a426432`(切片 02) +> 回档标签:`xiaobai-preservation-slice-03-20260731` +> 结论:源码、API、数据库、真实页面和浏览器回归通过;最终视觉仍等待全站人工验收 + +## 1. 原实现归位 + +本切片只移动原版副本中的真实实现,没有从 `next/` 取用代码,也没有改写情绪公式、股池数据、 +原因补全、表格、样式或交互。 + +| 原位置 | 新的唯一实现位置 | 原位置兼容 | +|---|---|---| +| `app/backend/application.py` 的 2 个情绪服务方法 | `app/backend/features/sentiment/service.py` | `DashboardService` 继承 `SentimentServiceMixin` | +| `app/backend/application.py` 的 6 个股池原因及事件补全方法 | `app/backend/features/pools/service.py` | `DashboardService` 继承 `PoolServiceMixin` | +| `app/database.py` 的 2 个原因覆盖方法 | `app/backend/features/pools/repository.py` | `ReviewDatabase` 继承 `PoolRepositoryMixin` | +| `app/sentiment_engine.py` | `app/backend/features/sentiment/engine.py` | 根模块为同一模块对象的兼容别名 | + +五类股池、涨停梯队和涨停表现仍由切片 02 已归位的原 Tushare 总览实现生成,本切片没有建立第二套 +计算或数据来源。 + +## 2. 等价证据 + +- `test_preservation_slice_sentiment_pools.py` 对 8 个业务方法和 2 个 Repository 方法逐项执行无位置 + 信息 AST 比较,全部与根目录原版 `server.py`、`database.py` 完全相同。 +- 新的情绪引擎文件与原版 `sentiment_engine.py` SHA-256 完全相同;根级兼容模块与新模块是同一模块对象。 +- 已归位的应用、行情服务、Tushare Provider、演示数据和选股模块直接导入新的唯一实现;Tushare + Provider 仅调整该导入,其全部类和函数 AST 继续与原版一致。 +- 原版 `8784` 与迁移版 `8785` 在相同账号、日期和数据库副本上请求 `/api/dashboard` 与 + `/api/sentiment/history`,JSON 状态、字段、值和顺序完全相同。 +- 2026-07-30 的同请求结果均为:涨停 56、炸板 23、跌停 83、昨日涨停 81、情绪历史 20 日。 +- 原版和迁移版数据库均为 62 个 schema 对象,schema 哈希均为 + `17918327f8b919496e6630458293f9f777c7c24662625bb3fc0b64ff0a8fbeef`。 +- `config/api.config.json`、API 路径、鉴权和 `app/static/` 未修改。 +- `app-light-1920x1080.png` 是真实迁移服务载入完成后的情绪周期页面,SHA-256 为 + `e387417abbe0667e00875a8d4061b5546748ecf2452a692d06d078d516330dab`。 + +## 3. 真实运行检查 + +- 迁移副本:`http://127.0.0.1:8785/`,管理员会话与缓存行情载入正常。 +- 情绪周期:20 个连续交易日、当前阶段、评分构成和交易日明细均完整显示。 +- 股池:涨停池 56 行、炸板池 23 行、跌停池 83 行、昨日涨停 81 行。 +- 涨停表现:四档晋级率、市场宽度和今日结论均显示原版结果。 +- 1920×1080 下六个页面横向溢出均为 0;日间、夜间背景与面板状态正常;浏览器控制台无迁移错误。 + +## 4. 自动验证 + +| 验证 | 结果 | +|---|---:| +| `python -m unittest discover -s tests -q` | 248 项通过 | +| `python -m unittest tests.test_preservation_slice_sentiment_pools -q` | 6 项通过 | +| 情绪、总览、缓存、iFinD 与前端契约专项集合 | 48 项通过 | +| `npx.cmd playwright test --reporter=dot` | 45 项通过 | +| `python -m compileall -q ...` | 通过 | +| `git diff --check` | 通过 | + +Windows 下由 Playwright 自行创建临时静态服务器时,45 项完成后子进程无法回收;改为预先启动同一个 +`8876` 静态服务器并让 Playwright 复用后,测试以零退出码正常结束,结果为 `45 passed (2.1m)`。 + +## 5. 保留边界 + +- 板块轮动仍调用情绪历史公共函数,待切片 04 与市场天梯一并归位。 +- 竞价、题材、人气和龙虎榜对股池数据的消费保持原调用路径,待切片 05 迁移。 +- 根级情绪引擎兼容模块、`DashboardService` 与 `ReviewDatabase` 兼容面继续保留;数据库内尚未迁移的 + 选股统计方法仍走兼容别名,待切片 06 随完整方法一并归位。 +- 没有删除待定代码、没有改动根目录正式数据库、没有切换 Docker/NAS。 diff --git a/docs/migration/evidence/slice-03/app-light-1920x1080.png b/docs/migration/evidence/slice-03/app-light-1920x1080.png new file mode 100644 index 0000000000000000000000000000000000000000..987c2cf5c7ece351ca40248376e14ffb9cf4f5a6 GIT binary patch literal 154636 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%0A&UC&63clQZ3%&u7#L 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 当前状态:正式迁移,切片02“公共行情、搜索、详情、图表与数据网关”已完成 +> 当前状态:正式迁移,切片03“情绪周期、五类股池与涨停表现”已完成 本账本是上下文恢复和人工审计的连续记录。任何迁移提交必须在同一提交中更新本文件及 `保真迁移状态.json`。 @@ -22,6 +22,7 @@ | 2026-07-30 | `41329943c4878fc09ed82ec376eb93ab151e4092` | 完成只读资产清查并由用户批准`app/`结构 | 开始切片00 | | 2026-07-31 | `xiaobai-preservation-slice-01-20260731` | 启动、HTTP、账号、会员与系统管理原实现归位 | 自动差分通过,进入切片02 | | 2026-07-31 | `xiaobai-preservation-slice-02-20260731` | 公共行情、搜索、详情、图表与数据适配原实现归位 | 自动与浏览器差分通过,进入切片03 | +| 2026-07-31 | `xiaobai-preservation-slice-03-20260731` | 情绪周期、五类股池与涨停表现原实现归位 | 自动、API与浏览器差分通过,进入切片04 | ## 资产处置登记 @@ -36,6 +37,9 @@ | `commonReviewColumns`等5个前端函数 | 疑似无引用符号 | 未发现静态调用 | 待定 | 待删隔离账本 | 仍需动态注册与浏览器覆盖 | 保留 | | `wencai_saved_queries`及其方法 | 历史兼容数据 | 当前无前端入口 | 待定 | 数据库兼容区 | 不允许在迁移期破坏旧库 | 保留 | | 现有7层CSS | 视觉运行资产 | 全部页面和主题 | 原样保留后逐页归档 | `app/frontend/` | 必须通过截图与计算样式差分 | 保留 | +| `sentiment_engine.py` | 情绪周期计算 | 总览、轮动、选股 | 移动并保留兼容别名 | `app/backend/features/sentiment/engine.py` | 文件哈希与原版一致;248项Python与45项Playwright通过 | 已移动 | +| `DashboardService`情绪及股池原因方法 | 业务服务 | 情绪页、五类股池、涨停表现 | 按职责机械移动 | `app/backend/features/sentiment/`、`app/backend/features/pools/` | 8个方法AST与原版一致;真实API完全一致 | 已移动 | +| `ReviewDatabase`原因覆盖方法 | 持久化 | 股池原因人工覆盖 | 按职责机械移动 | `app/backend/features/pools/repository.py` | 2个方法AST与原版一致;数据库schema哈希一致 | 已移动 | 处置只允许:`原样保留`、`移动`、`合并重复`、`待定`、`确认废弃`。 @@ -73,6 +77,16 @@ - 回档:标签`xiaobai-preservation-slice-02-20260731`。 - 完整证据:`docs/migration/evidence/slice-02/README.md`。 +已完成切片:`slice-03-sentiment-pools-performance`。 + +- 原版基线:提交`a426432`,即切片02回档点。 +- 迁移范围:情绪计算引擎、2个情绪服务方法、6个股池原因与iFinD事件补全方法、2个原因覆盖持久化方法。 +- 兼容边界:根级`sentiment_engine.py`保留同一模块对象别名;股池生成仍使用切片02的原Tushare总览实现。 +- API与数据库:原版`8784`和迁移版`8785`的总览、情绪历史JSON完全一致;两库schema均为62项且哈希一致。 +- 验收:248项Python测试、6项切片源码等价测试、45项Playwright测试及六个真实页面流程通过。 +- 回档:标签`xiaobai-preservation-slice-03-20260731`。 +- 完整证据:`docs/migration/evidence/slice-03/README.md`。 + ## 决策记录 | 日期 | 决策 | 原因 |