fix(regime): 修复重算无产出并补全 beta 标签

后端
- /recompute 全量分支原本误调 compute_regime_incremental(增量补差),
  无缺口/stale 时直接返回 0 行; 改为用 earliest_enriched_date ~ 今天
  走 run_regime_batch + upsert 强制覆盖, 真正实现"全量重算"
- _scan_enriched_fallback 原本漏调 compute_limit_signals 的 instruments
  参数致慢路径必抛异常 → enriched 缓存未预热时拿不到含信号列数据 → 聚合
  返回空; 改用主管道同款 compute_all(df, instruments, historical_shares)
  一站式补算, 与 indicators/pipeline.py 对齐
- 新增 enriched_date_set / earliest_enriched_date 辅助函数(抽出复用)

前端
- Regime 重算按钮: 加"重算中…"文字反馈 + 完成 toast(区分有/无新数据) +
  catch 捕获错误(原异常被静默吞掉)
- 市场环境菜单加 beta 胶囊标签(对齐 Settings 数据源标签样式)

验证
- 后端 582 passed; 真实数据全量重算产出 986 行(2022-07-08 ~ 2026-07-31)
- 前端 tsc + pnpm build 通过
This commit is contained in:
shy3130
2026-08-02 12:22:48 +08:00
parent 8aef435849
commit 46ebcd89c8
4 changed files with 63 additions and 25 deletions
+10 -4
View File
@@ -80,7 +80,7 @@ const nav = [
{ to: '/monitor', label: '监控中心', icon: RadioTower },
{ to: '/review', label: '复盘', icon: BookOpenCheck },
{ to: '/indices', label: '指数', icon: BarChart3 },
{ to: '/regime', label: '市场环境', icon: Gauge },
{ to: '/regime', label: '市场环境', icon: Gauge, badge: 'beta' },
{ to: '/data', label: '数据', icon: Database },
] as const
@@ -381,11 +381,12 @@ export function Layout() {
}, [alertsTotal])
// 合并内置页面 + 可见的扩展分析菜单
const analysisNav = (analysisMenus?.items ?? [])
type NavItem = { to: string; label: string; icon: typeof Gauge; badge?: string }
const analysisNav: NavItem[] = (analysisMenus?.items ?? [])
.filter(m => m.visible)
.map(m => ({ to: `/analysis/${m.id}`, label: m.label, icon: m.icon === 'tags' ? Tags : BarChart3 }))
const allNav = [...nav, ...analysisNav]
const allNav: NavItem[] = [...nav, ...analysisNav]
const savedOrder = prefs?.nav_order ?? []
const navItems = savedOrder.length > 0
@@ -463,7 +464,7 @@ export function Layout() {
</div>
<nav className="flex-1 min-h-0 overflow-y-auto px-2 py-3 space-y-0.5">
{visibleNavItems.map(({ to, label, icon: Icon }) => (
{visibleNavItems.map(({ to, label, icon: Icon, badge }) => (
<NavLink
key={to}
to={to}
@@ -480,6 +481,11 @@ export function Layout() {
<>
<Icon className="h-4 w-4 shrink-0" />
<span className="flex-1">{label}</span>
{badge && (
<span className="ml-auto inline-flex items-center rounded-full border border-amber-400/30 bg-amber-400/10 px-1.5 py-0.5 text-[9px] font-semibold uppercase tracking-wider text-amber-400 shrink-0">
{badge}
</span>
)}
{/* 数据同步状态: 同步中转圈, 刚完成显示绿色对勾闪烁 3 秒 */}
{to === '/data' && isDataSyncing && (
<Loader2 className="h-3.5 w-3.5 shrink-0 animate-spin text-accent" />
+7 -2
View File
@@ -15,6 +15,7 @@ import {
import { QK } from '@/lib/queryKeys'
import { useChartTheme } from '@/lib/theme'
import { fmtBigNum } from '@/lib/format'
import { toast } from '@/components/Toast'
const STATE_ORDER: RegimeState[] = ['strong', 'lean_strong', 'range', 'lean_weak', 'weak']
@@ -120,12 +121,16 @@ export function Regime() {
const handleRecompute = async () => {
setRecomputing(true)
try {
await api.regimeRecompute()
const r = await api.regimeRecompute()
// computed=0 表示无缺口/stale, 数据未变更; >0 表示新增/重算了 N 天
toast(r.computed > 0 ? `重算完成 · 新增 ${r.computed}` : '重算完成 · 数据已是最新', 'success')
await Promise.all([
qc.invalidateQueries({ queryKey: ['regime-history'] }),
qc.invalidateQueries({ queryKey: ['regime-states'] }),
qc.invalidateQueries({ queryKey: ['regime-latest'] }),
])
} catch (e) {
toast(`重算失败 · ${String((e as Error)?.message || e)}`, 'error')
} finally {
setRecomputing(false)
}
@@ -148,7 +153,7 @@ export function Regime() {
<button onClick={handleRecompute} disabled={recomputing}
className="inline-flex items-center gap-1.5 h-7 px-3 rounded-btn border border-border bg-base text-xs text-secondary hover:text-accent disabled:opacity-50">
{recomputing ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <RefreshCw className="h-3.5 w-3.5" />}
{recomputing ? '重算中…' : '重算'}
</button>
</div>
</div>