From 546bc0c21e9cca83d55a0d49fb9915a0182b68e4 Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Mon, 18 May 2026 20:26:24 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=97=A7=E7=9A=84=20OpsDashb?= =?UTF-8?q?oard=201694=20=E8=A1=8C=E5=8D=95=E9=A1=B5=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/ops/OpsDashboard.tsx | 1693 ---------------------- 1 file changed, 1693 deletions(-) delete mode 100644 frontend/components/ops/OpsDashboard.tsx diff --git a/frontend/components/ops/OpsDashboard.tsx b/frontend/components/ops/OpsDashboard.tsx deleted file mode 100644 index 3910eabd..00000000 --- a/frontend/components/ops/OpsDashboard.tsx +++ /dev/null @@ -1,1693 +0,0 @@ -"use client"; - -import Link from "next/link"; -import { useCallback, useEffect, useMemo, useState } from "react"; -import { AlertTriangle, Database, RefreshCcw, ShieldCheck, Wallet } from "lucide-react"; -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; - -type HealthPayload = { - status?: string; - db?: { ok?: boolean }; -}; - -type ProbabilityRollout = { - decision?: string; - ready_for_primary?: boolean; - blocking_reasons?: string[]; -}; - -type SystemStatusPayload = { - state_storage_mode?: string; - db?: { ok?: boolean; db_path?: string }; - features?: Record; - metrics?: Record; - cache?: { - api_cache_entries?: number; - open_meteo_forecast_entries?: number; - open_meteo_ensemble_entries?: number; - open_meteo_multi_model_entries?: number; - metar_entries?: number; - taf_entries?: number; - nmc_entries?: number; - settlement_entries?: number; - analysis?: { - total_requests?: number; - cache_hits?: number; - cache_misses?: number; - force_refresh_requests?: number; - last_cache_hit_at?: string | null; - last_cache_miss_at?: string | null; - last_city?: string | null; - hit_rate?: number | null; - miss_rate?: number | null; - }; - }; - probability?: { - engine_mode?: string; - rollout?: { - decision?: ProbabilityRollout; - evaluation_report_exists?: boolean; - shadow_report_exists?: boolean; - }; - }; - integrations?: Record; - training_data?: { - db_ok?: boolean; - db_path?: string; - error?: string; - truth_records?: { - row_count?: number; - cities_count?: number; - min_date?: string | null; - max_date?: string | null; - source_counts?: Record; - }; - truth_revisions?: { - row_count?: number; - last_updated_at?: number | null; - }; - training_features?: { - row_count?: number; - cities_count?: number; - min_date?: string | null; - max_date?: string | null; - }; - city_coverage?: { - total_cities?: number; - with_truth_rows?: number; - with_feature_rows?: number; - highlighted?: Array<{ - city: string; - name?: string; - settlement_source?: string; - settlement_station_code?: string; - truth_rows?: number; - feature_rows?: number; - truth_min_date?: string | null; - truth_max_date?: string | null; - feature_min_date?: string | null; - feature_max_date?: string | null; - }>; - top_gaps?: Array<{ - city: string; - name?: string; - settlement_source?: string; - settlement_station_code?: string; - truth_rows?: number; - feature_rows?: number; - truth_min_date?: string | null; - truth_max_date?: string | null; - feature_min_date?: string | null; - feature_max_date?: string | null; - }>; - }; - model_city_coverage?: { - cities_with_emos_training?: number; - cities_with_lgbm_candidates?: number; - weakest?: Array<{ - city: string; - name?: string; - settlement_source?: string; - truth_rows?: number; - feature_rows?: number; - emos_training_samples?: number; - emos_snapshot_samples?: number; - emos_evaluation_samples?: number; - lgbm_candidate_rows?: number; - }>; - strongest?: Array<{ - city: string; - name?: string; - settlement_source?: string; - truth_rows?: number; - feature_rows?: number; - emos_training_samples?: number; - emos_snapshot_samples?: number; - emos_evaluation_samples?: number; - lgbm_candidate_rows?: number; - }>; - }; - artifacts?: { - emos_training_samples?: number; - emos_snapshot_samples?: number; - emos_daily_record_samples?: number; - emos_evaluation_samples?: number; - emos_shadow_samples?: number; - emos_delta_crps?: number | null; - lgbm_sample_count?: number; - lgbm_train_count?: number; - lgbm_validation_count?: number; - lgbm_validation_mae?: number | null; - lgbm_validation_deb_mae?: number | null; - }; - }; - station_networks?: { - airport_anchor_coverage?: number; - official_station_anchor_coverage?: number; - providers?: Record< - string, - { - cities?: string[]; - cities_count?: number; - } - >; - }; - prewarm?: { - enabled?: boolean; - base_url?: string; - configured_cities?: string[]; - configured_city_count?: number; - interval_sec?: number; - jitter_sec?: number; - include_detail?: boolean; - include_market?: boolean; - force_refresh?: boolean; - thread_alive?: boolean; - runtime?: { - cycle_count?: number; - success_count?: number; - failure_count?: number; - last_started_at?: string | null; - last_finished_at?: string | null; - last_duration_sec?: number | null; - last_success?: boolean | null; - last_http_status?: number | null; - last_error?: string | null; - last_requested_cities?: string[]; - last_requested_city_count?: number; - last_include_detail?: boolean; - last_include_market?: boolean; - last_force_refresh?: boolean; - last_warmed_count?: number; - last_summary_ok?: number; - last_detail_ok?: number; - last_market_ok?: number; - last_failed_count?: number; - }; - }; -}; - -type PaymentRuntimePayload = { - checkout?: { - enabled?: boolean; - configured?: boolean; - chain_id?: number; - receiver_contract?: string; - confirmations?: number; - }; - rpc?: { - configured_rpc_count?: number; - active_rpc_url?: string; - }; - event_loop_state?: { - last_scanned_block?: number; - updated_at?: string; - }; - recent_audit_events?: Array<{ - id: number; - event_type: string; - created_at: string; - payload?: { - reason?: string; - detail?: string; - tx_hash?: string; - receiver_actual?: string; - receiver_expected?: string; - }; - }>; -}; - -type PaymentIncident = { - id: number; - event_type: string; - created_at: string; - payload?: { - reason?: string; - detail?: string; - tx_hash?: string; - receiver_actual?: string; - receiver_expected?: string; - plan_code?: string; - resolved_at?: string; - resolved_by?: string; - }; -}; - -type AuthMePayload = { - authenticated?: boolean; - email?: string | null; - entitlement_mode?: string; - subscription_active?: boolean | null; - weekly_rank?: number | null; - points?: number; -}; - -type AnalyticsFunnelStep = { - total?: number; - unique_users?: number; - unique_actors?: number; -}; - -type AnalyticsFunnelPayload = { - window_days?: number; - since?: string; - events?: Record; - rates?: Record; -}; - -type OpsUser = { - telegram_id: number; - username?: string | null; - points?: number; - daily_points?: number; - daily_points_date?: string | null; - weekly_points?: number; - weekly_points_week?: string | null; - message_count?: number; - supabase_email?: string | null; - last_message_at?: string | null; -}; - -type WeeklyLeaderboardEntry = { - telegram_id: number; - username?: string | null; - points?: number; - message_count?: number; - weekly_points?: number; -}; - -type MembershipEntry = { - user_id: string; - email?: string | null; - telegram_id?: number | null; - username?: string | null; - registered_at?: string | null; - plan_code?: string | null; - starts_at?: string | null; - current_expires_at?: string | null; - total_expires_at?: string | null; - expires_at?: string | null; - queued_days?: number | null; - queued_count?: number | null; -}; - -function formatDateTime(value?: string | null) { - if (!value) return "-"; - const date = new Date(value); - if (Number.isNaN(date.getTime())) return value; - return date.toLocaleString("zh-CN", { hour12: false }); -} - -function formatUnixDateTime(value?: number | null) { - if (!value) return "-"; - return formatDateTime(new Date(value * 1000).toISOString()); -} - -function formatMembershipExpiry(item: MembershipEntry) { - const total = item.total_expires_at || item.expires_at; - const current = item.current_expires_at || item.expires_at; - const queuedDays = Math.max(0, Number(item.queued_days || 0)); - const totalLabel = formatDateTime(total); - if (!queuedDays || !current || current === total) return totalLabel; - return `${totalLabel}(已续 +${queuedDays} 天)`; -} - -function formatMetric(value?: number | null, digits = 3) { - if (value === null || value === undefined || Number.isNaN(value)) return "-"; - return Number(value).toFixed(digits); -} - -function maskUrl(value?: string | null) { - if (!value) return "-"; - if (value.length <= 40) return value; - return `${value.slice(0, 28)}...${value.slice(-8)}`; -} - -async function readJson(url: string): Promise { - const response = await fetch(url, { cache: "no-store" }); - if (!response.ok) { - const raw = await response.text(); - throw new Error(`${url} -> HTTP ${response.status} ${raw.slice(0, 180)}`); - } - return response.json() as Promise; -} - -function MobileField({ - label, - value, - mono = false, -}: { - label: string; - value: string; - mono?: boolean; -}) { - return ( -
-
{label}
-
{value}
-
- ); -} - -export function OpsDashboard() { - const [health, setHealth] = useState(null); - const [status, setStatus] = useState(null); - const [payments, setPayments] = useState(null); - const [auth, setAuth] = useState(null); - const [analyticsFunnel, setAnalyticsFunnel] = useState(null); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - const [refreshedAt, setRefreshedAt] = useState(null); - const [searchQuery, setSearchQuery] = useState(""); - const [users, setUsers] = useState([]); - const [memberships, setMemberships] = useState([]); - const [paymentIncidents, setPaymentIncidents] = useState([]); - const [incidentReasonFilter, setIncidentReasonFilter] = useState("all"); - const [usersLoading, setUsersLoading] = useState(false); - const [usersError, setUsersError] = useState(null); - const [leaderboard, setLeaderboard] = useState([]); - const [membershipsLoading, setMembershipsLoading] = useState(false); - const [incidentsLoading, setIncidentsLoading] = useState(false); - const [resolvingIncidentId, setResolvingIncidentId] = useState(null); - const [grantEmail, setGrantEmail] = useState(""); - const [grantPoints, setGrantPoints] = useState("300"); - const [grantStatus, setGrantStatus] = useState(null); - const [grantError, setGrantError] = useState(null); - const [grantLoading, setGrantLoading] = useState(false); - - const load = useCallback(async () => { - setLoading(true); - setError(null); - try { - const [healthData, statusData, paymentData, authData, analyticsData] = await Promise.all([ - readJson("/api/healthz"), - readJson("/api/system/status"), - readJson("/api/payments/runtime"), - readJson("/api/auth/me"), - readJson("/api/ops/analytics/funnel?days=30"), - ]); - - setHealth(healthData); - setStatus(statusData); - setPayments(paymentData); - setAuth(authData); - setAnalyticsFunnel(analyticsData); - setRefreshedAt(new Date().toISOString()); - } catch (loadError) { - setError(String(loadError)); - } finally { - setLoading(false); - } - }, []); - - useEffect(() => { - void load(); - }, [load]); - - const loadUsers = useCallback(async (query: string) => { - setUsersLoading(true); - setUsersError(null); - try { - const url = new URL("/api/ops/users", window.location.origin); - if (query.trim()) { - url.searchParams.set("q", query.trim()); - } - url.searchParams.set("limit", "20"); - const data = await readJson<{ users?: OpsUser[] }>(url.toString()); - setUsers(data.users || []); - } catch (loadError) { - setUsersError(String(loadError)); - } finally { - setUsersLoading(false); - } - }, []); - - const loadLeaderboard = useCallback(async () => { - try { - const data = await readJson<{ leaderboard?: WeeklyLeaderboardEntry[] }>( - "/api/ops/leaderboard/weekly?limit=10", - ); - setLeaderboard(data.leaderboard || []); - } catch { - setLeaderboard([]); - } - }, []); - - const loadMemberships = useCallback(async () => { - setMembershipsLoading(true); - try { - const data = await readJson<{ memberships?: MembershipEntry[] }>( - "/api/ops/memberships?limit=200", - ); - setMemberships(data.memberships || []); - } catch { - setMemberships([]); - } finally { - setMembershipsLoading(false); - } - }, []); - - const loadPaymentIncidents = useCallback(async (reasonFilter?: string) => { - setIncidentsLoading(true); - try { - const url = new URL("/api/ops/payments/incidents", window.location.origin); - url.searchParams.set("limit", "20"); - const selectedReason = String(reasonFilter || incidentReasonFilter || "all"); - if (selectedReason && selectedReason !== "all") { - url.searchParams.set("reason", selectedReason); - } - const data = await readJson<{ incidents?: PaymentIncident[] }>(url.toString()); - setPaymentIncidents(data.incidents || []); - } catch { - setPaymentIncidents([]); - } finally { - setIncidentsLoading(false); - } - }, [incidentReasonFilter]); - - useEffect(() => { - void loadUsers(""); - void loadLeaderboard(); - void loadMemberships(); - void loadPaymentIncidents(incidentReasonFilter); - }, [loadLeaderboard, loadMemberships, loadPaymentIncidents, loadUsers]); - - const resolveIncident = useCallback(async (eventId: number) => { - setResolvingIncidentId(eventId); - try { - const response = await fetch(`/api/ops/payments/incidents/${eventId}/resolve`, { - method: "POST", - }); - if (!response.ok) { - const raw = await response.text(); - throw new Error(raw || `HTTP ${response.status}`); - } - await loadPaymentIncidents(incidentReasonFilter); - await load(); - } finally { - setResolvingIncidentId(null); - } - }, [incidentReasonFilter, load, loadPaymentIncidents]); - - const rolloutDecision = status?.probability?.rollout?.decision; - const trainingData = status?.training_data; - const truthRecords = trainingData?.truth_records; - const truthRevisions = trainingData?.truth_revisions; - const trainingFeatures = trainingData?.training_features; - const cityCoverage = trainingData?.city_coverage; - const modelCityCoverage = trainingData?.model_city_coverage; - const trainingArtifacts = trainingData?.artifacts; - const stationNetworks = status?.station_networks; - const cacheSummary = status?.cache; - const analysisCache = cacheSummary?.analysis; - const prewarm = status?.prewarm; - const prewarmRuntime = prewarm?.runtime; - const truthSources = Object.entries(truthRecords?.source_counts || {}); - const providerRows = useMemo( - () => - Object.entries(stationNetworks?.providers || {}) - .map(([code, value]) => ({ - code, - cities: value?.cities || [], - citiesCount: value?.cities_count ?? 0, - })) - .sort((a, b) => b.citiesCount - a.citiesCount || a.code.localeCompare(b.code)), - [stationNetworks?.providers], - ); - const cityCoverageRows = useMemo(() => { - const modelIndex = new Map( - [...(modelCityCoverage?.strongest || []), ...(modelCityCoverage?.weakest || [])].map((entry) => [entry.city, entry]), - ); - const rows: Array> = []; - for (const entry of [...(cityCoverage?.top_gaps || []), ...(cityCoverage?.highlighted || [])]) { - if (!entry || rows.some((row) => row.city === entry.city)) continue; - const modelEntry = modelIndex.get(entry.city); - rows.push({ - ...entry, - emos_training_samples: modelEntry?.emos_training_samples ?? 0, - emos_evaluation_samples: modelEntry?.emos_evaluation_samples ?? 0, - lgbm_candidate_rows: modelEntry?.lgbm_candidate_rows ?? entry.feature_rows ?? 0, - }); - } - rows.sort((a, b) => { - const aScore = Number(a.truth_rows || 0) + Number(a.feature_rows || 0) + Number(a.emos_training_samples || 0); - const bScore = Number(b.truth_rows || 0) + Number(b.feature_rows || 0) + Number(b.emos_training_samples || 0); - return aScore - bScore || String(a.city || "").localeCompare(String(b.city || "")); - }); - return rows; - }, [cityCoverage?.highlighted, cityCoverage?.top_gaps, modelCityCoverage?.strongest, modelCityCoverage?.weakest]); - - const funnelSteps = useMemo(() => { - const events = analyticsFunnel?.events || {}; - const getStep = (key: string) => events[key] || {}; - const signups = getStep("signup_completed"); - const active = getStep("dashboard_active"); - const featureClicks = getStep("paywall_feature_clicked"); - const paywallViews = getStep("paywall_viewed"); - const checkoutStarted = getStep("checkout_started"); - const checkoutSucceeded = getStep("checkout_succeeded"); - const base = Math.max(active.unique_actors || 0, signups.unique_actors || 0, 1); - - return [ - { - key: "signup_completed", - label: "注册成功", - helper: "近 30 天首次试用用户", - count: signups.unique_actors || 0, - total: signups.total || 0, - width: `${Math.max(((signups.unique_actors || 0) / base) * 100, 10)}%`, - }, - { - key: "dashboard_active", - label: "登录活跃", - helper: "进入主面板并产生有效会话", - count: active.unique_actors || 0, - total: active.total || 0, - width: `${Math.max(((active.unique_actors || 0) / base) * 100, 10)}%`, - rateLabel: analyticsFunnel?.rates?.login_active_rate, - }, - { - key: "paywall_feature_clicked", - label: "点击受限功能", - helper: "今日日内分析 / 历史对账触发拦截", - count: featureClicks.unique_actors || 0, - total: featureClicks.total || 0, - width: `${Math.max(((featureClicks.unique_actors || 0) / base) * 100, 10)}%`, - rateLabel: analyticsFunnel?.rates?.paywall_click_rate, - }, - { - key: "paywall_viewed", - label: "看到付费入口", - helper: "功能弹窗或账户中心升级层", - count: paywallViews.unique_actors || 0, - total: paywallViews.total || 0, - width: `${Math.max(((paywallViews.unique_actors || 0) / base) * 100, 10)}%`, - rateLabel: analyticsFunnel?.rates?.paywall_view_rate, - }, - { - key: "checkout_started", - label: "发起支付", - helper: "创建支付 intent", - count: checkoutStarted.unique_actors || 0, - total: checkoutStarted.total || 0, - width: `${Math.max(((checkoutStarted.unique_actors || 0) / base) * 100, 10)}%`, - rateLabel: analyticsFunnel?.rates?.checkout_start_rate, - }, - { - key: "checkout_succeeded", - label: "支付成功", - helper: "链上确认并写入权益", - count: checkoutSucceeded.unique_actors || 0, - total: checkoutSucceeded.total || 0, - width: `${Math.max(((checkoutSucceeded.unique_actors || 0) / base) * 100, 10)}%`, - rateLabel: analyticsFunnel?.rates?.checkout_success_rate, - }, - ]; - }, [analyticsFunnel]); - - const rolloutVariant = useMemo(() => { - const decision = rolloutDecision?.decision; - if (decision === "promote") return "success" as const; - if (decision === "observe") return "warning" as const; - return "danger" as const; - }, [rolloutDecision?.decision]); - - const submitGrant = useCallback(async () => { - setGrantLoading(true); - setGrantError(null); - setGrantStatus(null); - try { - const response = await fetch("/api/ops/users/grant-points", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - email: grantEmail.trim(), - points: Number(grantPoints), - }), - }); - const payload = (await response.json()) as { - ok?: boolean; - detail?: { reason?: string }; - points_after?: number; - points_added?: number; - supabase_email?: string; - }; - if (!response.ok) { - throw new Error( - payload?.detail?.reason || `HTTP ${response.status}`, - ); - } - setGrantStatus( - `${payload.supabase_email || grantEmail.trim()} 已补 ${payload.points_added || grantPoints} 分,当前 ${payload.points_after ?? "-"}`, - ); - await loadUsers(searchQuery); - await loadLeaderboard(); - await loadMemberships(); - await loadPaymentIncidents(incidentReasonFilter); - } catch (submitError) { - setGrantError(String(submitError)); - } finally { - setGrantLoading(false); - } - }, [grantEmail, grantPoints, incidentReasonFilter, loadLeaderboard, loadMemberships, loadPaymentIncidents, loadUsers, searchQuery]); - - const opsNav = [ - { id: "overview", label: "总览" }, - { id: "station-networks", label: "站网覆盖" }, - { id: "prewarm", label: "预热缓存" }, - { id: "funnel", label: "转化漏斗" }, - { id: "probability", label: "EMOS 门禁" }, - { id: "training-data", label: "训练数据" }, - { id: "coverage", label: "覆盖状态" }, - { id: "payments", label: "支付异常" }, - { id: "members", label: "会员" }, - { id: "users", label: "用户与积分" }, - ]; - - return ( -
-
- -
-
-
-
- Ops - - Health {health?.status || "unknown"} - - - SQLite {status?.db?.ok ? "ok" : "error"} - - - EMOS {rolloutDecision?.decision || "hold"} - -
-
-

PolyWeather Ops

-

- 用后台管理系统的方式组织运营视图,把系统状态、训练数据、模型覆盖、支付与会员放到一页里。 -

-
-
-
- 刷新时间: {formatDateTime(refreshedAt)} - -
-
-
-
Health
-
{health?.status || "unknown"}
-
-
-
Truth Rows
-
{truthRecords?.row_count ?? 0}
-
-
-
EMOS Samples
-
{trainingArtifacts?.emos_training_samples ?? 0}
-
-
-
LGBM Samples
-
{trainingArtifacts?.lgbm_sample_count ?? 0}
-
-
-
- -
- - - 站网协议覆盖 - - 统一查看机场锚点覆盖、官方站锚点覆盖,以及新的国家 provider 分布。 - - - -
- 机场锚点覆盖 - {stationNetworks?.airport_anchor_coverage ?? 0} -
-
- 官方站锚点覆盖 - {stationNetworks?.official_station_anchor_coverage ?? 0} -
-
- 国家 provider 数 - {providerRows.length} -
-
- 规则固定为“机场主站 + 官方增强 + 模型层”。官方站网只做增强,除明确官方结算站外不会替代机场锚点。 -
-
-
- - - - 国家 Provider 分布 - - 这些 provider 已进入统一协议层,业务侧不再直接按国家写分支。 - - - - {providerRows.length === 0 ? ( -
- 暂无站网 provider 数据。 -
- ) : ( - providerRows.map((provider) => ( -
-
-
{provider.code}
- {provider.citiesCount} 城 -
-
- {provider.cities.length > 0 ? provider.cities.join(", ") : "无城市"} -
-
- )) - )} -
-
-
- -
- - - 预热 Worker - 确认后台定向预热是否真的在跑,以及最近一次有没有成功。 - - -
- - - - -
-
-
最近开始{formatDateTime(prewarmRuntime?.last_started_at)}
-
最近结束{formatDateTime(prewarmRuntime?.last_finished_at)}
-
最近耗时{prewarmRuntime?.last_duration_sec ?? "-"} s
-
最近结果{prewarmRuntime?.last_success === true ? "success" : prewarmRuntime?.last_success === false ? "failed" : "-"}
-
最近 HTTP{prewarmRuntime?.last_http_status ?? "-"}
-
最近预热城市{prewarmRuntime?.last_requested_city_count ?? 0}
-
最近命中结果{prewarmRuntime?.last_warmed_count ?? 0}
-
summary / detail / market{prewarmRuntime?.last_summary_ok ?? 0} / {prewarmRuntime?.last_detail_ok ?? 0} / {prewarmRuntime?.last_market_ok ?? 0}
-
最近失败数{prewarmRuntime?.last_failed_count ?? 0}
-
累计 cycle{prewarmRuntime?.cycle_count ?? 0}
-
success / failed{prewarmRuntime?.success_count ?? 0} / {prewarmRuntime?.failure_count ?? 0}
-
- {prewarmRuntime?.last_error ? ( -
- 最近错误:{prewarmRuntime.last_error} -
- ) : null} -
- 当前预热目标:{(prewarmRuntime?.last_requested_cities || prewarm?.configured_cities || []).slice(0, 12).join(", ") || "未配置"} -
-
-
- - - - 缓存桶状态 - 观察热点数据是不是已经被打热,避免每次请求都现场抓取。 - - -
- - - - - - - - -
-
-
summary 请求总数{analysisCache?.total_requests ?? 0}
-
cache hit / miss{analysisCache?.cache_hits ?? 0} / {analysisCache?.cache_misses ?? 0}
-
hit rate{typeof analysisCache?.hit_rate === "number" ? `${(analysisCache.hit_rate * 100).toFixed(1)}%` : "-"}
-
force refresh{analysisCache?.force_refresh_requests ?? 0}
-
最近 hit{formatDateTime(analysisCache?.last_cache_hit_at)}
-
最近 miss{formatDateTime(analysisCache?.last_cache_miss_at)}
-
最近城市{analysisCache?.last_city || "-"}
-
-
- 这里同时展示桶内条目数和 summary 层 cache hit/miss。detail / market 目前仍以最近一次预热结果为准。 -
-
-
-
- - {error ? ( - - - 加载失败 - {error} - - - ) : null} - -
- - - - 系统健康 - - 后端健康、鉴权策略、状态存储模式。 - - -
healthz{health?.status || "-"}
-
鉴权模式{auth?.entitlement_mode || "-"}
-
状态存储{status?.state_storage_mode || "-"}
-
DB{health?.db?.ok ? "ok" : "-"}
-
-
- - - - - 运行态存储 - - SQLite 是否正常,以及 rollout 当前状态。 - - -
SQLite{status?.db?.ok ? "ok" : "error"}
-
EMOS 模式{status?.probability?.engine_mode || "-"}
-
上线门禁{rolloutDecision?.decision || "-"}
-
ready_for_primary{rolloutDecision?.ready_for_primary ? "true" : "false"}
-
-
- - - - - 支付运行态 - - 当前 RPC、事件循环区块、合约配置。 - - -
支付启用{payments?.checkout?.enabled ? "true" : "false"}
-
RPC 数量{payments?.rpc?.configured_rpc_count ?? 0}
-
{payments?.checkout?.chain_id ?? "-"}
-
最后区块{payments?.event_loop_state?.last_scanned_block ?? "-"}
-
-
- - - - - 当前登录态 - - 先确认管理员自己当前有没有会话。 - - -
authenticated{auth?.authenticated ? "true" : "false"}
-
email{auth?.email || "-"}
-
points{auth?.points ?? 0}
-
weekly_rank{auth?.weekly_rank ?? "-"}
-
-
-
- -
- - - 转化漏斗 - - 最近 {analyticsFunnel?.window_days || 30} 天从注册、活跃到付费的最小闭环。 - - - -
- {funnelSteps.map((step) => ( -
-
-
-
{step.label}
-
{step.count}
-
- {typeof step.rateLabel === "number" ? ( - {(step.rateLabel * 100).toFixed(1)}% - ) : null} -
-
{step.helper}
-
-
-
-
- 事件 {step.total} 次 · 独立用户 {step.count} -
-
- ))} -
- - - - - - 漏斗速读 - 先看哪一层掉得最厉害,再决定改拦截、文案还是支付页。 - - -
-
窗口起点
-
{formatDateTime(analyticsFunnel?.since)}
-
-
-
- 注册 → 活跃 - {((analyticsFunnel?.rates?.login_active_rate || 0) * 100).toFixed(1)}% -
-
- 活跃 → 点受限功能 - {((analyticsFunnel?.rates?.paywall_click_rate || 0) * 100).toFixed(1)}% -
-
- 点击 → 看到付费入口 - {((analyticsFunnel?.rates?.paywall_view_rate || 0) * 100).toFixed(1)}% -
-
- 付费入口 → 发起支付 - {((analyticsFunnel?.rates?.checkout_start_rate || 0) * 100).toFixed(1)}% -
-
- 发起支付 → 成功 - {((analyticsFunnel?.rates?.checkout_success_rate || 0) * 100).toFixed(1)}% -
-
-
- 这些数字直接来自 /api/ops/analytics/funnel,后面如果要再拆“试用到期 / Pro 到期 / 游客”漏斗,可以在事件 payload 上继续加维度。 -
-
-
-
- -
- - - EMOS 上线门禁 - 当前 shadow 到 primary 的发布判断。 - - -
- {rolloutDecision?.decision || "hold"} - - ready={rolloutDecision?.ready_for_primary ? "true" : "false"} - -
-
-
阻塞原因
-
    - {(rolloutDecision?.blocking_reasons || []).length ? ( - (rolloutDecision?.blocking_reasons || []).map((reason) => ( -
  • - {reason} -
  • - )) - ) : ( -
  • 当前无阻塞项
  • - )} -
-
-
-
- - - - 支付审计摘要 - 先展示最新几条事件,日常巡检够用。 - - -
-
RPC
-
{maskUrl(payments?.rpc?.active_rpc_url)}
-
-
-
Receiver Contract
-
{payments?.checkout?.receiver_contract || "-"}
-
-
- {(payments?.recent_audit_events || []).slice(0, 6).map((item) => ( -
-
- {item.event_type} - #{item.id} -
-
{formatDateTime(item.created_at)}
- {item.payload?.reason ? ( -
-
原因: {item.payload.reason}
- {item.payload.tx_hash ?
Tx: {maskUrl(item.payload.tx_hash)}
: null} - {item.payload.receiver_actual ? ( -
实际收款: {maskUrl(item.payload.receiver_actual)}
- ) : null} - {item.payload.receiver_expected ? ( -
期望收款: {maskUrl(item.payload.receiver_expected)}
- ) : null} -
- ) : null} -
- ))} - {!payments?.recent_audit_events?.length ? ( -
暂无审计事件
- ) : null} -
-
-
-
- -
- - - 历史真值主表 - 永久监督真值,不再受 14 天运行态缓存裁剪影响。 - - -
-
-
Truth Viewer
-
按城市和日期直接查看历史真值、来源站点与更新记录。
-
- -
-
- - - - -
-
-
来源分布
-
- {truthSources.length ? ( - truthSources.map(([source, count]) => ( - - {source}: {count} - - )) - ) : ( - 暂无来源统计 - )} -
-
-
-
- revision rows - {truthRevisions?.row_count ?? 0} -
-
- last revision - {formatUnixDateTime(truthRevisions?.last_updated_at)} -
-
-
-
- - - - 长期训练特征 - 概率快照与训练特征长期归档,避免未来样本继续被裁掉。 - - -
- - - - -
-
- 当前这张表代表“从现在开始不再继续丢训练特征”。如果历史样本仍偏少,通常说明旧日期本身没有被长期归档,而不是现在的写入链坏了。 -
-
- DB: {trainingData?.db_ok ? "ok" : "error"} · {trainingData?.db_path || "-"} - {trainingData?.error ?
{trainingData.error}
: null} -
-
-
- - - - 模型样本状态 - 直接看当前 EMOS / LGBM 可用样本与最新验证结果。 - - -
-
EMOS
-
-
training samples{trainingArtifacts?.emos_training_samples ?? 0}
-
evaluation samples{trainingArtifacts?.emos_evaluation_samples ?? 0}
-
shadow samples{trainingArtifacts?.emos_shadow_samples ?? 0}
-
delta CRPS{formatMetric(trainingArtifacts?.emos_delta_crps)}
-
-
-
-
LGBM
-
-
sample count{trainingArtifacts?.lgbm_sample_count ?? 0}
-
train / val{trainingArtifacts?.lgbm_train_count ?? 0} / {trainingArtifacts?.lgbm_validation_count ?? 0}
-
val mae{formatMetric(trainingArtifacts?.lgbm_validation_mae)}
-
DEB val mae{formatMetric(trainingArtifacts?.lgbm_validation_deb_mae)}
-
-
-
-
-
- -
- - - Wunderground 回填观察 - 先盯已经切到 WU 结算的关键城市,确认真值和特征都在持续积累。 - - -
- truth cities {cityCoverage?.with_truth_rows ?? 0}/{cityCoverage?.total_cities ?? 0} - feature cities {cityCoverage?.with_feature_rows ?? 0}/{cityCoverage?.total_cities ?? 0} -
- {(cityCoverage?.highlighted || []).map((entry) => ( -
-
-
{entry.name || entry.city}
- {entry.settlement_source || "-"} -
-
- - - - -
-
- ))} - {!(cityCoverage?.highlighted || []).length ? ( -
暂无 Wunderground 重点城市状态。
- ) : null} -
-
- - - - 城市覆盖缺口 - 按 truth / feature 覆盖从弱到强排序,优先看哪些城市还没形成长期训练样本。 - - - {(cityCoverage?.top_gaps || []).length ? ( -
- {(cityCoverage?.top_gaps || []).map((entry) => ( -
-
-
-
{entry.name || entry.city}
-
- {entry.city} · {entry.settlement_source || "-"} · {entry.settlement_station_code || "-"} -
-
-
- truth {entry.truth_rows ?? 0} - feature {entry.feature_rows ?? 0} -
-
-
-
truth range: {entry.truth_min_date || "-"} → {entry.truth_max_date || "-"}
-
feature range: {entry.feature_min_date || "-"} → {entry.feature_max_date || "-"}
-
-
- ))} -
- ) : ( -
当前无城市覆盖缺口摘要。
- )} -
-
-
- -
- - - 模型城市覆盖 - 直接看当前哪些城市已经开始积累 EMOS 训练样本,哪些城市只有真值没有特征。 - - -
- EMOS cities {modelCityCoverage?.cities_with_emos_training ?? 0} - LGBM candidate cities {modelCityCoverage?.cities_with_lgbm_candidates ?? 0} -
-
- {(modelCityCoverage?.strongest || []).map((entry) => ( -
-
-
-
{entry.name || entry.city}
-
{entry.city} · {entry.settlement_source || "-"}
-
-
- EMOS {entry.emos_training_samples ?? 0} - LGBM {entry.lgbm_candidate_rows ?? 0} -
-
-
- ))} - {!(modelCityCoverage?.strongest || []).length ? ( -
暂无模型覆盖摘要。
- ) : null} -
-
-
- - - - 模型样本缺口 - 优先处理长期真值有了但 EMOS/LGBM 样本仍然很薄的城市。 - - - {(modelCityCoverage?.weakest || []).length ? ( -
- {(modelCityCoverage?.weakest || []).map((entry) => ( -
-
-
-
{entry.name || entry.city}
-
{entry.city} · {entry.settlement_source || "-"}
-
-
- truth {entry.truth_rows ?? 0} - EMOS {entry.emos_training_samples ?? 0} - LGBM {entry.lgbm_candidate_rows ?? 0} -
-
-
-
feature rows: {entry.feature_rows ?? 0}
-
snapshot rows: {entry.emos_snapshot_samples ?? 0}
-
eval rows: {entry.emos_evaluation_samples ?? 0}
-
-
- ))} -
- ) : ( -
当前无模型样本缺口摘要。
- )} -
-
-
- - - - 城市覆盖矩阵 - 把 truth / feature / EMOS / LGBM 放到一张表里,快速判断哪些城市还只能靠 DEB。 - - -
- {cityCoverageRows.map((entry) => ( -
-
-
-
{String(entry.name || entry.city || "-")}
-
- {String(entry.city || "-")} · {String(entry.settlement_source || "-")} · {String(entry.settlement_station_code || "-")} -
-
-
-
- - - - -
-
- ))} -
-
- - - - - - - - - - - - - - {cityCoverageRows.map((entry) => ( - - - - - - - - - - ))} - {!cityCoverageRows.length ? ( - - - - ) : null} - -
CitySourceStationTruthFeatureEMOSLGBM
-
{String(entry.name || entry.city || "-")}
-
{String(entry.city || "-")}
-
{String(entry.settlement_source || "-")}{String(entry.settlement_station_code || "-")} - 0 ? "success" : "warning"}>{String(entry.truth_rows || 0)} - - 0 ? "success" : "warning"}>{String(entry.feature_rows || 0)} - - 0 ? "success" : "warning"}>{String(entry.emos_training_samples || 0)} - - 0 ? "success" : "warning"}>{String(entry.lgbm_candidate_rows || 0)} -
- 暂无城市覆盖矩阵数据 -
-
-
-
- - - -
-
- 支付异常单 - 只显示已明确标记失败的支付确认事故。 -
-
- - -
-
-
- -
- {paymentIncidents.map((item) => ( -
-
-
-
{item.payload?.reason || "-"}
-
{formatDateTime(item.created_at)}
-
- -
-
- - - - -
-
- ))} - {!paymentIncidents.length ? ( -
- 暂无支付异常单 -
- ) : null} -
-
- - - - - - - - - - - - - - {paymentIncidents.map((item) => ( - - - - - - - - - - ))} - {!paymentIncidents.length ? ( - - - - ) : null} - -
时间原因套餐Tx实际收款期望收款操作
{formatDateTime(item.created_at)}{item.payload?.reason || "-"}{item.payload?.plan_code || "-"}{maskUrl(item.payload?.tx_hash)}{maskUrl(item.payload?.receiver_actual)}{maskUrl(item.payload?.receiver_expected)} - -
- 暂无支付异常单 -
-
-
-
- - - - 当前会员 - 当前有效订阅用户、注册时间和到期时间。 - - -
-
- 当前有效会员数:{memberships.length} -
- -
-
- {memberships.map((item) => ( -
-
{item.email || "-"}
-
{item.username || "-"}
-
- - - -
-
- ))} - {!memberships.length ? ( -
- 暂无有效会员 -
- ) : null} -
-
- - - - - - - - - - - - {memberships.map((item) => ( - - - - - - - - ))} - {!memberships.length ? ( - - - - ) : null} - -
邮箱用户名User ID注册时间到期时间
{item.email || "-"}{item.username || "-"}{item.user_id || "-"}{formatDateTime(item.registered_at)}{formatMembershipExpiry(item)}
- 暂无有效会员 -
-
-
-
- -
- - - 用户查询 - 按 Telegram ID、用户名或 Supabase 邮箱搜用户。 - - -
- setSearchQuery(event.target.value)} - placeholder="telegram id / username / email" - className="h-10 flex-1 rounded-xl border border-slate-800 bg-slate-950 px-3 text-sm text-slate-100 outline-none ring-0" - /> - -
- {usersError ?
{usersError}
: null} -
- {users.map((user) => ( -
-
-
-
{user.username || "(未命名用户)"}
-
- TG {user.telegram_id} · {user.supabase_email || "未绑定邮箱"} -
-
-
- 总分 {user.points ?? 0} - 周分 {user.weekly_points ?? 0} - 发言 {user.message_count ?? 0} -
-
-
- 今日积分 {user.daily_points ?? 0} · 最近发言 {formatDateTime(user.last_message_at)} -
-
- ))} - {!users.length && !usersLoading ? ( -
没有匹配用户
- ) : null} -
-
-
- - - - 积分运营 - 先做最小版:按 Supabase 邮箱手动补分。 - - -
- setGrantEmail(event.target.value)} - placeholder="user@example.com" - className="h-10 w-full rounded-xl border border-slate-800 bg-slate-950 px-3 text-sm text-slate-100 outline-none ring-0" - /> - setGrantPoints(event.target.value)} - placeholder="300" - className="h-10 w-full rounded-xl border border-slate-800 bg-slate-950 px-3 text-sm text-slate-100 outline-none ring-0" - /> - -
- {grantStatus ?
{grantStatus}
: null} - {grantError ?
{grantError}
: null} - -
-
本周榜前 10
- {(leaderboard || []).map((item, index) => ( -
-
-
#{index + 1} {item.username || "(未命名用户)"}
-
TG {item.telegram_id}
-
-
-
周分 {item.weekly_points ?? 0}
-
总分 {item.points ?? 0}
-
-
- ))} - {!leaderboard.length ? ( -
当前没有周榜数据
- ) : null} -
-
-
-
-
-
-
- ); -}