"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; expires_at?: string | 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 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}
City Source Station Truth Feature EMOS LGBM
{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)} {formatDateTime(item.expires_at)}
暂无有效会员
用户查询 按 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}
); }