diff --git a/backend/app/api/data.py b/backend/app/api/data.py index b03933b..ce3ec92 100644 --- a/backend/app/api/data.py +++ b/backend/app/api/data.py @@ -411,7 +411,7 @@ def _safe_aggregate_financials(repo) -> dict | None: tables_info: dict[str, dict] = {} total_rows = 0 - for table in ("metrics", "income", "balance_sheet", "cash_flow"): + for table in ("metrics", "income", "balance_sheet", "cash_flow", "shares"): path = data_dir / "financials" / table / "part.parquet" if path.exists(): try: diff --git a/backend/tests/test_financial_shares.py b/backend/tests/test_financial_shares.py index a324f5b..42eaa85 100644 --- a/backend/tests/test_financial_shares.py +++ b/backend/tests/test_financial_shares.py @@ -1,10 +1,12 @@ from __future__ import annotations from datetime import date +from types import SimpleNamespace import polars as pl import pytest +from app.api import data as data_api from app.indicators import pipeline from app.services import financial_sync from app.tickflow.capabilities import CapabilitySet @@ -159,3 +161,19 @@ def test_turnover_without_share_history_keeps_existing_behavior(monkeypatch): ) assert result["turnover_rate"][0] == pytest.approx(0.5) + + +def test_data_status_includes_share_history(tmp_path): + path = tmp_path / "financials" / "shares" / "part.parquet" + path.parent.mkdir(parents=True, exist_ok=True) + pl.DataFrame({ + "symbol": ["600000.SH", "600000.SH", "000001.SZ"], + "period_end": ["2023-12-31", "2024-06-30", "2024-06-30"], + }).write_parquet(path) + + repo = SimpleNamespace(store=SimpleNamespace(data_dir=tmp_path)) + result = data_api._safe_aggregate_financials(repo) + + assert result is not None + assert result["rows"] == 3 + assert result["tables"]["shares"] == {"rows": 3, "symbols": 2} diff --git a/frontend/src/components/data/EnrichedRebuildPanel.tsx b/frontend/src/components/data/EnrichedRebuildPanel.tsx index b50cfdc..f188db0 100644 --- a/frontend/src/components/data/EnrichedRebuildPanel.tsx +++ b/frontend/src/components/data/EnrichedRebuildPanel.tsx @@ -5,10 +5,24 @@ import { api } from '@/lib/api' import { QK } from '@/lib/queryKeys' import { usePreferences } from '@/lib/useSharedQueries' -export function EnrichedRebuildPanel({ isRunning, onStart }: { isRunning: boolean; onStart: () => void }) { +type Props = { + isRunning: boolean + onStart: (jobId: string) => void + purpose?: 'enriched' | 'turnover' + historicalShareRows?: number +} + +export function EnrichedRebuildPanel({ + isRunning, + onStart, + purpose = 'enriched', + historicalShareRows = 0, +}: Props) { const qc = useQueryClient() const prefs = usePreferences() const batchSize = prefs.data?.enriched_batch_size ?? 1000 + const isTurnoverRebuild = purpose === 'turnover' + const canRebuild = !isTurnoverRebuild || historicalShareRows > 0 const [editing, setEditing] = useState(false) const [draftSize, setDraftSize] = useState(String(batchSize)) const [hint, setHint] = useState(null) @@ -28,8 +42,8 @@ export function EnrichedRebuildPanel({ isRunning, onStart }: { isRunning: boolea }) const rebuild = useMutation({ mutationFn: api.rebuildEnriched, - onSuccess: () => { - onStart() + onSuccess: ({ job_id }) => { + onStart(job_id) qc.invalidateQueries({ queryKey: QK.pipelineJobs }) }, }) @@ -93,18 +107,38 @@ export function EnrichedRebuildPanel({ isRunning, onStart }: { isRunning: boolea
-
基于已有 kline_daily + adj_factor 全量计算前复权 + 技术指标 + 信号
+ {isTurnoverRebuild && ( +
+ {canRebuild + ? `已检测到 ${historicalShareRows.toLocaleString()} 条历史股本记录。重算会按公告可用日匹配历史流通股本,并覆盖全部 Enriched 分区。` + : '未检测到历史股本数据,请先在财务分析页面同步“股本表”,再执行重算。'} +
+ )} +
+ {isTurnoverRebuild + ? '为保证数据一致性,将基于现有日 K、除权因子和历史股本重新生成 Enriched;其他指标也会按当前逻辑同步更新。' + : '基于已有 kline_daily + adj_factor 全量计算前复权 + 技术指标 + 信号'} +
+ {rebuild.isError && ( +
+ 启动失败:{String((rebuild.error as Error)?.message ?? rebuild.error)} +
+ )}
) diff --git a/frontend/src/components/data/StatCard.tsx b/frontend/src/components/data/StatCard.tsx index 0d6df2d..dafb10e 100644 --- a/frontend/src/components/data/StatCard.tsx +++ b/frontend/src/components/data/StatCard.tsx @@ -230,6 +230,7 @@ export function StatCard({ className={`p-0.5 rounded hover:bg-elevated transition-colors ${ settingsOpen ? 'text-accent' : 'text-secondary' }`} + title="设置" > diff --git a/frontend/src/pages/Data.tsx b/frontend/src/pages/Data.tsx index 3e4c980..0165320 100644 --- a/frontend/src/pages/Data.tsx +++ b/frontend/src/pages/Data.tsx @@ -512,19 +512,24 @@ export function Data() { settingsOpen={openSettings === 'minute'} /> ) - case 'financials': + case 'financials': { + const historicalShareRows = s?.financials?.tables?.shares?.rows ?? 0 return ( setOpenSettings(v => v === 'financials' ? null : 'financials') : undefined} + settingsOpen={openSettings === 'financials'} /> ) + } default: return null } @@ -958,7 +963,23 @@ export function Data() { {openSettings === 'enriched' && ( setOpenSettings(null)}> - setOpenSettings(null)} /> + { setActiveJobId(jobId); setOpenSettings(null) }} + /> + + )} + + + + {openSettings === 'financials' && ( + setOpenSettings(null)}> + { setActiveJobId(jobId); setOpenSettings(null) }} + /> )} diff --git a/frontend/src/pages/Financials.tsx b/frontend/src/pages/Financials.tsx index 0bb3035..e298bb5 100644 --- a/frontend/src/pages/Financials.tsx +++ b/frontend/src/pages/Financials.tsx @@ -1,5 +1,5 @@ import { useState, useEffect } from 'react' -import { RefreshCw, Lock, Loader2, X, Search, FileText, Database, Clock, CheckCircle2, Hourglass, Lightbulb, ExternalLink, ChartPie } from 'lucide-react' +import { RefreshCw, Download, Lock, Loader2, X, Search, FileText, Database, Clock, CheckCircle2, Hourglass, Lightbulb, ExternalLink, ChartPie } from 'lucide-react' import { PageHeader } from '@/components/PageHeader' import { EmptyState } from '@/components/EmptyState' import { useCapabilities } from '@/lib/useSharedQueries' @@ -239,11 +239,11 @@ export function Financials() { className="text-muted hover:text-accent transition-colors disabled:opacity-30 disabled:cursor-not-allowed" onClick={() => handleSync(key)} disabled={syncing} - title={syncing ? '正在同步…' : `同步${label}`} + title={syncing ? '正在同步…' : `更新${label}`} > {syncing ? - : } + : }