From c3ca20c84999a24d20ccaa7c393f26212d71b1d9 Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Thu, 23 Apr 2026 00:02:25 +0800 Subject: [PATCH] Hydrate homepage market data --- .../app/api/city/[name]/market-scan/route.ts | 4 +- frontend/app/api/city/[name]/route.ts | 4 +- .../dashboard/PolyWeatherDashboard.tsx | 51 +++++++++++++++++-- frontend/hooks/useDashboardStore.tsx | 40 +++++++++++++++ 4 files changed, 88 insertions(+), 11 deletions(-) diff --git a/frontend/app/api/city/[name]/market-scan/route.ts b/frontend/app/api/city/[name]/market-scan/route.ts index 3a7c4310..a9b25929 100644 --- a/frontend/app/api/city/[name]/market-scan/route.ts +++ b/frontend/app/api/city/[name]/market-scan/route.ts @@ -36,9 +36,7 @@ export async function GET( const url = `${API_BASE}/api/city/${encodeURIComponent(name)}/market-scan?${params.toString()}`; try { - const auth = await buildBackendRequestHeaders(req, { - includeSupabaseIdentity: false, - }); + const auth = await buildBackendRequestHeaders(req); const res = await fetch(url, { headers: auth.headers, cache: "no-store", diff --git a/frontend/app/api/city/[name]/route.ts b/frontend/app/api/city/[name]/route.ts index 1c2e5201..9678079a 100644 --- a/frontend/app/api/city/[name]/route.ts +++ b/frontend/app/api/city/[name]/route.ts @@ -127,9 +127,7 @@ export async function GET( const url = `${API_BASE}/api/city/${encodeURIComponent(name)}?force_refresh=${forceRefresh}&depth=${encodeURIComponent(depth)}`; try { - const auth = await buildBackendRequestHeaders(req, { - includeSupabaseIdentity: false, - }); + const auth = await buildBackendRequestHeaders(req); const res = await fetch(url, { headers: auth.headers, cache: "no-store", diff --git a/frontend/components/dashboard/PolyWeatherDashboard.tsx b/frontend/components/dashboard/PolyWeatherDashboard.tsx index 0a269985..33f2f1d6 100644 --- a/frontend/components/dashboard/PolyWeatherDashboard.tsx +++ b/frontend/components/dashboard/PolyWeatherDashboard.tsx @@ -197,6 +197,12 @@ function buildSparklinePoints(values: number[] | undefined) { .join(" "); } +function readNumericField(source: unknown, key: string) { + if (!source || typeof source !== "object") return undefined; + const value = (source as Record)[key]; + return typeof value === "number" && Number.isFinite(value) ? value : undefined; +} + function HomeIntelligencePanel({ snapshots }: { snapshots: CitySnapshot[] }) { const store = useDashboardStore(); const { locale } = useI18n(); @@ -237,13 +243,35 @@ function HomeIntelligencePanel({ snapshots }: { snapshots: CitySnapshot[] }) { const modelLabels = getModelLabels(detail); const marketScan = detail?.market_scan; const marketBucket = marketScan?.temperature_bucket || marketScan?.top_buckets?.[0] || null; + const yesPrice = + marketScan?.yes_buy ?? + readNumericField(marketBucket, "yes_buy") ?? + marketScan?.yes_token?.buy_price ?? + marketScan?.yes_token?.midpoint ?? + marketScan?.price_analysis?.yes?.ask; + const noPrice = + marketScan?.no_buy ?? + readNumericField(marketBucket, "no_buy") ?? + marketScan?.no_token?.buy_price ?? + marketScan?.no_token?.midpoint ?? + marketScan?.price_analysis?.no?.ask; + const marketEdge = + marketScan?.edge_percent ?? + marketScan?.price_analysis?.yes?.edge_percent ?? + marketScan?.price_analysis?.yes?.edge ?? + marketScan?.price_analysis?.no?.edge_percent ?? + marketScan?.price_analysis?.no?.edge; const marketLabel = marketBucket ? getProbabilityLabel(marketBucket, symbol) : debPrediction != null ? `> ${formatTemperature(debPrediction, symbol)}` : "--"; const marketProbability = - marketScan?.market_price ?? marketScan?.yes_buy ?? marketScan?.model_probability; + marketScan?.market_price ?? + readNumericField(marketBucket, "market_price") ?? + readNumericField(marketBucket, "yes_buy") ?? + yesPrice ?? + marketScan?.model_probability; const marketModelProbability = marketScan?.model_probability ?? marketBucket?.probability; const sparklinePoints = buildSparklinePoints(marketScan?.sparkline); const isLoading = store.loadingState.cityDetail && store.selectedCity === city.name; @@ -258,7 +286,7 @@ function HomeIntelligencePanel({ snapshots }: { snapshots: CitySnapshot[] }) { : getRiskCopy(riskLevel, locale); const keySignals = [ { - active: Number(marketScan?.edge_percent) > 0, + active: Number(marketEdge) > 0, label: locale === "en-US" ? "DEB > Market implied" @@ -411,13 +439,13 @@ function HomeIntelligencePanel({ snapshots }: { snapshots: CitySnapshot[] }) { {marketScan?.selected_date || detail?.local_date || ""}
- YES {formatCents(marketScan?.yes_buy)} - NO {formatCents(marketScan?.no_buy)} + YES {formatCents(yesPrice)} + NO {formatCents(noPrice)}
- Edge {formatEdge(marketScan?.edge_percent)} + Edge {formatEdge(marketEdge)} Implied {formatProbability(marketProbability)} @@ -531,6 +559,7 @@ function DashboardScreen() { const store = useDashboardStore(); const { t } = useI18n(); const didAutoFocusRef = useRef(false); + const preloadedOpportunityRef = useRef>(new Set()); const activeSummary = store.selectedCity ? store.citySummariesByName[store.selectedCity] || null : null; @@ -603,6 +632,18 @@ function DashboardScreen() { void store.focusCity(topOpportunity); }, [homepageSnapshots, showHomepageChrome, store]); + useEffect(() => { + if (!showHomepageChrome) return; + const targets = homepageSnapshots.slice(0, 4).map((snapshot) => snapshot.city.name); + targets.forEach((cityName) => { + if (preloadedOpportunityRef.current.has(cityName)) return; + preloadedOpportunityRef.current.add(cityName); + void store.ensureCityDetail(cityName, false, "panel").catch(() => { + preloadedOpportunityRef.current.delete(cityName); + }); + }); + }, [homepageSnapshots, showHomepageChrome, store]); + return (
{ + const cached = cityDetailsByName[cityName]; + try { + const payload = await dashboardClient.getCityMarketScan(cityName, { + force, + targetDate: cached?.local_date || selectedForecastDate || null, + }); + if (!payload.market_scan) return null; + setCityDetailsByName((current) => { + const detail = current[cityName]; + if (!detail) return current; + return { + ...current, + [cityName]: { + ...detail, + market_scan: payload.market_scan || undefined, + }, + }; + }); + return payload.market_scan; + } catch { + return null; + } + }; + useEffect(() => { if (proAccess.loading) return; if (!selectedCity) return; @@ -826,6 +851,12 @@ export function DashboardStoreProvider({ if (selectedCityRef.current !== cityName) return; if (detail.status === "fulfilled") { setSelectedForecastDate(detail.value.local_date); + if ( + proAccessRef.current.subscriptionActive && + !detail.value.market_scan + ) { + void ensureCityMarketScan(cityName, false); + } } }) .finally(() => { @@ -834,6 +865,15 @@ export function DashboardStoreProvider({ }); }; + useEffect(() => { + if (!selectedCity) return; + if (!proAccess.subscriptionActive) return; + const detail = cityDetailsByName[selectedCity]; + if (!detail) return; + if (detail.market_scan) return; + void ensureCityMarketScan(selectedCity, false); + }, [cityDetailsByName, proAccess.subscriptionActive, selectedCity]); + const clearCityFocus = () => { selectedCityRef.current = null; setSelectedCity(null);