Skip to content

Commit f671646

Browse files
xielevidministrator
authored andcommitted
fix: address second-round review issues from PR ZhuLinsen#1053
- performance_tracker: guard record_prediction/get_analyst_weights/backfill_result with _db_available flag; use ANALYST_PERF_DB env var instead of hardcoded path - backfill_performance: unpack (df, source) tuple from get_daily_data() call - ReportOverview: gate MasterVotingDisk with Object.keys check; fix ExpertLeaderboard guard from .length to Object.keys().length - analysis_service: use getattr for dashboard access to avoid AttributeError on SimpleNamespace - tests: add analysis_mode=None to submit_tasks_batch assertions; accept **kwargs in thread-safety test mock
1 parent df86db3 commit f671646

7 files changed

Lines changed: 37 additions & 10 deletions

File tree

.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,3 +553,6 @@ ADMIN_AUTH_ENABLED=false
553553
# 东财接口补丁:东财/Efinance/Akshare 东方财富接口频繁失败(RemoteDisconnected、连接被关闭)时建议开启
554554
# 开启后会注入 NID 令牌与随机 User-Agent,降低被东财限流概率
555555
# ENABLE_EASTMONEY_PATCH=false
556+
557+
# 专家战绩数据库路径(默认:workspace/analyst_performance.db,只读容器可指向 /tmp 等可写路径)
558+
# ANALYST_PERF_DB=workspace/analyst_performance.db

apps/dsa-web/src/components/report/ReportOverview.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -345,11 +345,13 @@ export const ReportOverview: React.FC<ReportOverviewProps> = ({
345345
</Card>
346346
)}
347347

348-
{/* 专家共识投票 */}
349-
<MasterVotingDisk reports={details?.ensembleReports} />
350-
348+
{/* 专家共识投票:仅在有集成报告时挂载 */}
349+
{details?.ensembleReports && Object.keys(details.ensembleReports).length > 0 && (
350+
<MasterVotingDisk reports={details.ensembleReports} />
351+
)}
352+
351353
{/* 专家胜率榜单:仅在有集成报告时展示,避免只读容器触发 DB 请求 */}
352-
{details?.ensembleReports && details.ensembleReports.length > 0 && (
354+
{details?.ensembleReports && Object.keys(details.ensembleReports).length > 0 && (
353355
<ExpertLeaderboard />
354356
)}
355357
</div>

scripts/backfill_performance.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def get_5d_return(fetcher: DataFetcherManager, ticker: str, pred_date_str: str)
3636
pred_date = datetime.strptime(pred_date_str, "%Y-%m-%d")
3737
window_end = pred_date + timedelta(days=15) # 多取一些,保证覆盖节假日
3838

39-
df = fetcher.get_daily_data(
39+
df, _source = fetcher.get_daily_data(
4040
ticker,
4141
start_date=pred_date_str,
4242
end_date=window_end.strftime("%Y-%m-%d"),

src/services/analysis_service.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ def _build_analysis_response(
173173
"technical_analysis": result.technical_analysis,
174174
"fundamental_analysis": result.fundamental_analysis,
175175
"risk_warning": result.risk_warning,
176-
"ensemble_reports": result.dashboard.get("ensemble_reports") if result.dashboard else None,
176+
"ensemble_reports": getattr(result, "dashboard", None) and result.dashboard.get("ensemble_reports"),
177177
"radar_data": result.get_radar_data() if hasattr(result, "get_radar_data") else None,
178178
}
179179
}

src/services/performance_tracker.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@ class PerformanceTracker:
1010
"""
1111
专家战绩追踪器:负责持久化大师们的预测结果,并计算动态权重。
1212
"""
13-
def __init__(self, db_path: str = "workspace/analyst_performance.db"):
14-
self.db_path = db_path
13+
def __init__(self, db_path: str = ""):
14+
self.db_path = db_path or os.environ.get(
15+
"ANALYST_PERF_DB",
16+
os.path.join("workspace", "analyst_performance.db"),
17+
)
1518
self._db_available = False
1619
try:
1720
os.makedirs(os.path.dirname(self.db_path), exist_ok=True)
@@ -42,7 +45,11 @@ def _init_db(self):
4245
def record_prediction(self, analyst_id: str, ticker: str, signal: str, confidence: int) -> int:
4346
"""
4447
记录一次专家的预测,返回新插入记录的 row ID。
48+
DB 不可用时返回 -1 并记录警告,不抛出异常。
4549
"""
50+
if not self._db_available:
51+
logger.warning("PerformanceTracker: DB unavailable, skipping record_prediction for %s/%s", analyst_id, ticker)
52+
return -1
4653
date_str = datetime.now().strftime("%Y-%m-%d")
4754
with sqlite3.connect(self.db_path) as conn:
4855
cursor = conn.cursor()
@@ -58,10 +65,14 @@ def get_analyst_weights(self, lookback_days: int = 30) -> Dict[str, float]:
5865
计算各大师的动态权重,并进行归一化处理。
5966
算法:raw_weight = (accuracy + 0.1) ^ 1.5,归一化后所有权重之和为 1.0。
6067
如果没有历史战绩,使用等权基准值(1.0)参与归一化,确保新专家不被惩罚。
68+
DB 不可用时直接返回等权权重。
6169
"""
62-
raw_weights = {}
6370
analysts = ["warren_buffett", "li_lu", "paul_tudor_jones", "jensen_huang", "nassim_taleb"]
71+
if not self._db_available:
72+
equal = round(1.0 / len(analysts), 4)
73+
return {aid: equal for aid in analysts}
6474

75+
raw_weights = {}
6576
with sqlite3.connect(self.db_path) as conn:
6677
conn.row_factory = sqlite3.Row
6778
cursor = conn.cursor()
@@ -151,7 +162,11 @@ def backfill_result(self, record_id: int, actual_return: float):
151162
- signal=bearish 且 return < -1% -> 1 (Correct)
152163
- signal=neutral 且 |return| < 2% -> 1 (Correct)
153164
- 其他 -> 0 (Wrong)
165+
DB 不可用时跳过并记录警告。
154166
"""
167+
if not self._db_available:
168+
logger.warning("PerformanceTracker: DB unavailable, skipping backfill_result for record %s", record_id)
169+
return
155170
with sqlite3.connect(self.db_path, isolation_level='IMMEDIATE') as conn:
156171
cursor = conn.cursor()
157172
cursor.execute("SELECT signal FROM track_record WHERE id = ? AND is_correct IS NULL", (record_id,))

tests/test_analysis_api_contract.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,7 @@ def test_trigger_analysis_accepts_us_suffix_code(self) -> None:
652652
report_type="detailed",
653653
force_refresh=False,
654654
notify=True,
655+
analysis_mode=None,
655656
)
656657

657658
def test_trigger_analysis_accepts_hk_suffix_code_from_autocomplete(self) -> None:
@@ -687,6 +688,7 @@ def test_trigger_analysis_accepts_hk_suffix_code_from_autocomplete(self) -> None
687688
report_type="detailed",
688689
force_refresh=False,
689690
notify=True,
691+
analysis_mode=None,
690692
)
691693

692694
def test_trigger_analysis_accepts_hk_prefixed_code(self) -> None:
@@ -722,6 +724,7 @@ def test_trigger_analysis_accepts_hk_prefixed_code(self) -> None:
722724
report_type="detailed",
723725
force_refresh=False,
724726
notify=True,
727+
analysis_mode=None,
725728
)
726729

727730
def test_trigger_analysis_allows_stock_names_with_star_and_hyphen(self) -> None:
@@ -757,6 +760,7 @@ def test_trigger_analysis_allows_stock_names_with_star_and_hyphen(self) -> None:
757760
report_type="detailed",
758761
force_refresh=False,
759762
notify=True,
763+
analysis_mode=None,
760764
)
761765

762766
def test_trigger_analysis_accepts_resolvable_free_text_input(self) -> None:
@@ -792,6 +796,7 @@ def test_trigger_analysis_accepts_resolvable_free_text_input(self) -> None:
792796
report_type="detailed",
793797
force_refresh=False,
794798
notify=True,
799+
analysis_mode=None,
795800
)
796801

797802
def test_trigger_analysis_preserves_batch_metadata(self) -> None:
@@ -826,6 +831,7 @@ def test_trigger_analysis_preserves_batch_metadata(self) -> None:
826831
report_type="detailed",
827832
force_refresh=False,
828833
notify=True,
834+
analysis_mode=None,
829835
)
830836

831837
def test_trigger_analysis_rejects_cross_request_duplicate_for_equivalent_code_shapes(self) -> None:
@@ -916,6 +922,7 @@ def test_trigger_analysis_batch_does_not_apply_single_stock_name_to_all_tasks(se
916922
report_type="detailed",
917923
force_refresh=False,
918924
notify=True,
925+
analysis_mode=None,
919926
)
920927

921928
def test_spa_fallback_returns_json_404_for_bare_api_path(self) -> None:

tests/test_pipeline_single_notify_thread_safety.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ def test_process_single_stock_serializes_direct_notification_path(self):
7272

7373
notify_barrier = threading.Barrier(2)
7474

75-
def _analyze(code, report_type, query_id):
75+
def _analyze(code, report_type, query_id, **kwargs):
7676
notify_barrier.wait(timeout=10)
7777
return _make_result(code)
7878

0 commit comments

Comments
 (0)