diff --git a/frontend/components/dashboard/FutureForecastModal.tsx b/frontend/components/dashboard/FutureForecastModal.tsx index 2e5cd246..d9b06173 100644 --- a/frontend/components/dashboard/FutureForecastModal.tsx +++ b/frontend/components/dashboard/FutureForecastModal.tsx @@ -69,13 +69,6 @@ function formatMarketPercent(value?: number | null) { return `${(normalized * 100).toFixed(1)}%`; } -function formatSignedPercent(value?: number | null) { - const numeric = Number(value); - if (!Number.isFinite(numeric)) return "--"; - const sign = numeric > 0 ? "+" : ""; - return `${sign}${numeric.toFixed(1)}%`; -} - function formatBucketLabel( bucket?: { label?: string | null; @@ -622,7 +615,6 @@ export function FutureForecastModal() { const store = useDashboardStore(); const { locale, t } = useI18n(); const detail = store.selectedDetail; - const marketScan = store.selectedMarketScan; const dateStr = store.futureModalDate; const isPro = store.proAccess.subscriptionActive; const isProLoading = store.proAccess.loading; @@ -667,8 +659,7 @@ export function FutureForecastModal() { const detailDepth = detail.detail_depth || "full"; const isFullDetailReady = detailDepth === "full"; const isStructureSyncing = store.loadingState.futureDeep || !isFullDetailReady; - const isMarketSyncing = store.loadingState.marketScan; - const isAnyLayerSyncing = isStructureSyncing || isMarketSyncing; + const isAnyLayerSyncing = isStructureSyncing; const view = getFutureModalView(detail, dateStr, locale); const scorePosition = `${50 + view.front.score / 2}%`; const barStyle = { @@ -713,7 +704,7 @@ export function FutureForecastModal() { const boundaryRiskView = useMemo(() => { if (!showDeferredTodaySections) return null; if (!isToday || !paceView) return null; - const selectedBucket = marketScan?.temperature_bucket || null; + const selectedBucket = topProbabilityBucket || null; const bounds = parseBucketBoundaries(selectedBucket); if (!bounds) return null; const projected = @@ -756,7 +747,7 @@ export function FutureForecastModal() { tone, value: `${nearest.gap.toFixed(1)}${detail.temp_symbol}`, }; - }, [detail.deb?.prediction, detail.temp_symbol, isToday, locale, marketScan?.temperature_bucket, paceView, showDeferredTodaySections]); + }, [detail.deb?.prediction, detail.temp_symbol, isToday, locale, paceView, showDeferredTodaySections, topProbabilityBucket]); const peakWindowStateView = useMemo(() => { if (!showDeferredTodaySections) return null; if (!isToday || !paceView) return null; @@ -850,27 +841,7 @@ export function FutureForecastModal() { String(detail.current?.station_name || "").trim() || String(detail.risk?.airport || "").trim() || noaaStationCode; - const topBucket = Array.isArray(marketScan?.top_buckets) - ? [...marketScan.top_buckets] - .map((item) => ({ - ...item, - probability: normalizeMarketValue(item?.probability), - })) - .filter( - ( - item, - ): item is { - label?: string | null; - bucket?: string | null; - range?: string | null; - value?: number | null; - temp?: number | null; - probability: number; - } => item.probability != null, - ) - .sort((a, b) => b.probability - a.probability)[0] - : null; - const hottestBucketLabel = formatBucketLabel(topBucket); + const hottestBucketLabel = formatBucketLabel(topProbabilityBucket); const probabilitySummary = (() => { if (!topProbabilityBucket) { return locale === "en-US" @@ -904,17 +875,10 @@ export function FutureForecastModal() { })(); const upperAirSignal = detail.vertical_profile_signal || {}; const tafSignal = detail.taf?.signal || {}; - const topBucketProbability = normalizeMarketValue(topBucket?.probability); - const numericEdge = Number(marketScan?.edge_percent); - const hottestMatchesSettlement = - hottestBucketLabel !== "--" && - formatBucketLabel(marketScan?.temperature_bucket) !== "--" && - hottestBucketLabel === formatBucketLabel(marketScan?.temperature_bucket); - const marketAwareUpperAirCue = useMemo(() => { + const upperAirCue = useMemo(() => { if (!showDeferredTodaySections) return null; if (!isToday || (!upperAirSignal.source && !tafSignal.available)) return null; - const crowded = hottestMatchesSettlement && (topBucketProbability || 0) >= 0.3; const setup = String(upperAirSignal.heating_setup || "neutral").toLowerCase(); const tafSuppression = String( tafSignal.suppression_level || "low", @@ -922,9 +886,6 @@ export function FutureForecastModal() { const tafDisruption = String( tafSignal.disruption_level || "low", ).toLowerCase(); - const signalLabel = String(marketScan?.signal_label || "").toUpperCase(); - const edgeAbs = Number.isFinite(numericEdge) ? Math.abs(numericEdge) : 0; - const strongEdge = edgeAbs >= 8; const reasons: string[] = []; let score = 0; @@ -976,37 +937,12 @@ export function FutureForecastModal() { ); } - if (strongEdge && signalLabel === "BUY YES") { - score += 1; - reasons.push( - locale === "en-US" - ? `market edge still leans hotter (${formatSignedPercent(numericEdge)})` - : `市场 edge 仍偏向更热一侧(${formatSignedPercent(numericEdge)})`, - ); - } else if (strongEdge && signalLabel === "BUY NO") { - score -= 1; - reasons.push( - locale === "en-US" - ? `market edge still leans cooler (${formatSignedPercent(numericEdge)})` - : `市场 edge 仍偏向更冷一侧(${formatSignedPercent(numericEdge)})`, - ); - } - - if (crowded && score > 0) { - score -= 0.5; - reasons.push( - locale === "en-US" - ? "the target bucket is already getting crowded" - : "目标区间已经开始变拥挤", - ); - } - if (score >= 1.5) { return { summary: locale === "en-US" - ? "The combined upper-air, TAF, and market read still leans warmer. Do not fade lower buckets too early." - : "高空、TAF 和市场三层信号合并后仍偏暖侧,不宜过早做更低温区间。", + ? "The combined upper-air and TAF read still leans warmer. Do not fade lower buckets too early." + : "高空和 TAF 两层信号合并后仍偏暖侧,不宜过早做更低温区间。", note: locale === "en-US" ? `${reasons.slice(0, 2).join("; ")}.` @@ -1020,8 +956,8 @@ export function FutureForecastModal() { return { summary: locale === "en-US" - ? "The combined upper-air, TAF, and market read leans more defensive. Be more careful chasing higher buckets." - : "高空、TAF 和市场三层信号合并后更偏防守,追更高温区间要更谨慎。", + ? "The combined upper-air and TAF read leans more defensive. Be more careful chasing higher buckets." + : "高空和 TAF 两层信号合并后更偏防守,追更高温区间要更谨慎。", note: locale === "en-US" ? `${reasons.slice(0, 2).join("; ")}.` @@ -1034,8 +970,8 @@ export function FutureForecastModal() { return { summary: locale === "en-US" - ? "The combined upper-air, TAF, and market read is mixed. Let surface structure and price action decide before taking a side." - : "高空、TAF 和市场三层信号目前偏混合,先看近地面结构和盘口变化,不急着站边。", + ? "The combined upper-air and TAF read is mixed. Let surface structure decide before taking a side." + : "高空和 TAF 两层信号目前偏混合,先看近地面结构变化,不急着站边。", note: locale === "en-US" ? `${reasons.slice(0, 2).join("; ") || "No clean edge from the upper-air layer alone"}.` @@ -1047,12 +983,8 @@ export function FutureForecastModal() { tafSignal.available, tafSignal.disruption_level, tafSignal.suppression_level, - marketScan?.signal_label, - hottestMatchesSettlement, isToday, locale, - numericEdge, - topBucketProbability, upperAirSignal.heating_setup, upperAirSignal.source, showDeferredTodaySections, @@ -1081,18 +1013,18 @@ export function FutureForecastModal() { }; })(); const displayedUpperAirSummary = showDeferredTodaySections - ? marketAwareUpperAirCue?.summary || view.front.upperAirSummary + ? upperAirCue?.summary || view.front.upperAirSummary : ""; const displayedUpperAirMetrics = showDeferredTodaySections ? (view.front.upperAirMetrics || []).map((metric, index) => index === 0 && (metric.label === "Trade cue" || metric.label === "交易动作") && - marketAwareUpperAirCue + upperAirCue ? { ...metric, - note: marketAwareUpperAirCue.note, - tone: marketAwareUpperAirCue.tone, - value: marketAwareUpperAirCue.value, + note: upperAirCue.note, + tone: upperAirCue.tone, + value: upperAirCue.value, } : metric, ) @@ -1162,23 +1094,15 @@ export function FutureForecastModal() { }, { key: "market", - state: isMarketSyncing ? "syncing" : "ready", + state: "ready", label: locale === "en-US" - ? isMarketSyncing - ? "Syncing market ladder" - : "Market ladder ready" - : isMarketSyncing - ? "市场挂单同步中" - : "市场挂单已加载", + ? "Probability layer ready" + : "概率层已加载", note: locale === "en-US" - ? isMarketSyncing - ? "Polymarket buckets and edge are updating in the background." - : "Probability buckets and edge are in sync." - : isMarketSyncing - ? "Polymarket 概率桶和 edge 正在后台更新。" - : "概率桶和 edge 已同步完成。", + ? "Probability buckets are derived from the local model stack." + : "概率桶当前由本地模型栈推导。", }, { key: "structure", @@ -1584,28 +1508,10 @@ export function FutureForecastModal() { {probabilitySummary}
- {/* Loading Overlay */} - {store.loadingState.marketScan && ( -
-
- {locale === "en-US" - ? "Crunching Polymarket Edges..." - : "正在同步市场挂单..."} -
- )}
@@ -1841,7 +1747,6 @@ export function FutureForecastModal() { detail={detail} targetDate={dateStr} hideTitle - marketScan={marketScan} />
@@ -1861,3 +1766,4 @@ export function FutureForecastModal() {
); } + diff --git a/frontend/hooks/useDashboardStore.tsx b/frontend/hooks/useDashboardStore.tsx index ee889bea..55242a95 100644 --- a/frontend/hooks/useDashboardStore.tsx +++ b/frontend/hooks/useDashboardStore.tsx @@ -24,7 +24,6 @@ import { HistoryPayloadMeta, HistoryState, LoadingState, - MarketScan, ProAccessState, } from "@/lib/dashboard-types"; @@ -46,12 +45,10 @@ interface DashboardStoreValue extends DashboardState { refreshAll: () => Promise; refreshProAccess: () => Promise; refreshSelectedCity: () => Promise; - selectedMarketScan: MarketScan | null; selectedDetail: CityDetail | null; selectCity: (cityName: string) => Promise; setMapInteractionActive: (active: boolean) => void; setForecastDate: (dateStr: string | null) => void; - marketScanByCityName: Record; } const DashboardStoreContext = createContext(null); @@ -94,11 +91,6 @@ function getInitialProAccessState(): ProAccessState { }; } -function getMarketScanCacheKey(cityName: string, targetDate?: string | null) { - const normalizedDate = String(targetDate || "").trim() || "local"; - return `${cityName}::${normalizedDate}`; -} - const SELECTED_CITY_STORAGE_KEY = "polyWeather_selected_city_v1"; const BACKGROUND_SUMMARY_REFRESH_MS = 30_000; type CityDetailDepth = "panel" | "market" | "nearby" | "full"; @@ -291,9 +283,6 @@ export function DashboardStoreProvider({ const [cityDetailMetaByName, setCityDetailMetaByName] = useState< Record >({}); - const [marketScanByCityName, setMarketScanByCityName] = useState< - Record - >({}); const [selectedCity, setSelectedCity] = useState(null); const [isPanelOpen, setIsPanelOpen] = useState(false); const [selectedForecastDate, setSelectedForecastDate] = useState< @@ -325,19 +314,6 @@ export function DashboardStoreProvider({ selectedCity && proAccess.subscriptionActive ? cityDetailsByName[selectedCity] || null : null; - const selectedMarketDate = - futureModalDate || - selectedForecastDate || - selectedDetail?.local_date || - null; - const selectedMarketScanKey = selectedCity - ? getMarketScanCacheKey(selectedCity, selectedMarketDate) - : null; - const selectedMarketScan = - selectedCity && proAccess.subscriptionActive - ? marketScanByCityName[selectedMarketScanKey || ""] || null - : null; - useEffect(() => { if (proAccess.loading) return; if (!proAccess.authenticated || !proAccess.subscriptionActive) { @@ -402,7 +378,6 @@ export function DashboardStoreProvider({ dashboardClient.clearCityDetailCache(); setCityDetailsByName({}); setCityDetailMetaByName({}); - setMarketScanByCityName({}); }, [proAccess]); const scheduleBackgroundDetailRefresh = ( @@ -578,32 +553,6 @@ export function DashboardStoreProvider({ selectedCity, ]); - const ensureCityMarketScan = async ( - cityName: string, - force = false, - marketSlug?: string | null, - targetDate?: string | null, - ) => { - const cacheKey = getMarketScanCacheKey(cityName, targetDate); - const cached = marketScanByCityName[cacheKey]; - if (!force && cached && !marketSlug) { - return cached; - } - - const latestScan = await dashboardClient.getCityMarketScan(cityName, { - force, - marketSlug, - targetDate, - }); - if (latestScan) { - setMarketScanByCityName((current) => ({ - ...current, - [cacheKey]: latestScan, - })); - } - return latestScan; - }; - const loadCities = async () => { setLoadingState((current) => ({ ...current, cities: true })); try { @@ -1055,8 +1004,6 @@ export function DashboardStoreProvider({ ); setFutureModalDate(dateStr); - const cacheKey = getMarketScanCacheKey(selectedCity, dateStr); - setLoadingState((current) => ({ ...current, marketScan: true })); if (!hasMarketCachedDetail || forceRefresh) { void ensureCityDetail(cityName, forceRefresh, "market").catch(() => {}); } @@ -1075,17 +1022,6 @@ export function DashboardStoreProvider({ })); }); } - void ensureCityMarketScan( - cityName, - forceRefresh || !marketScanByCityName[cacheKey], - null, - dateStr, - ) - .catch(() => {}) - .finally(() => { - if (selectedCityRef.current !== cityName) return; - setLoadingState((current) => ({ ...current, marketScan: false })); - }); }, openHistory, openTodayModal: async (forceRefresh?: boolean) => { @@ -1129,7 +1065,6 @@ export function DashboardStoreProvider({ setLoadingState((current) => ({ ...current, futureDeep: needsDetailRefresh, - marketScan: true, })); if (!hasMarketCachedDetail || forceRefresh) { void ensureCityDetail( @@ -1138,26 +1073,6 @@ export function DashboardStoreProvider({ "market", ).catch(() => {}); } - const initialTargetDate = - cachedDetail?.local_date || selectedForecastDate || null; - const initialMarketKey = getMarketScanCacheKey( - cityName, - initialTargetDate, - ); - void ensureCityMarketScan( - cityName, - forceRefresh || !marketScanByCityName[initialMarketKey], - null, - initialTargetDate, - ) - .catch(() => {}) - .finally(() => { - if (selectedCityRef.current !== cityName) return; - setLoadingState((current) => ({ - ...current, - marketScan: false, - })); - }); void ensureCityDetail( cityName, needsDetailRefresh, @@ -1189,7 +1104,6 @@ export function DashboardStoreProvider({ refreshAll, refreshProAccess, refreshSelectedCity, - selectedMarketScan, selectedCity, selectedDetail, selectedForecastDate, @@ -1197,7 +1111,6 @@ export function DashboardStoreProvider({ setMapInteractionActive: setIsMapInteracting, setForecastDate: (dateStr: string | null) => setSelectedForecastDate(dateStr), - marketScanByCityName, }), [ cities, @@ -1208,8 +1121,6 @@ export function DashboardStoreProvider({ isPanelOpen, loadingState, proAccess, - marketScanByCityName, - selectedMarketScan, selectedCity, selectedDetail, selectedForecastDate, @@ -1259,3 +1170,4 @@ export function useHistoryData(name?: string | null) { meta: key ? store.historyState.metaByCity[key] || null : null, }; } + diff --git a/frontend/lib/dashboard-client.ts b/frontend/lib/dashboard-client.ts index 1f4e5845..614d9ff6 100644 --- a/frontend/lib/dashboard-client.ts +++ b/frontend/lib/dashboard-client.ts @@ -3,7 +3,6 @@ import { CityDetail, CityListItem, - MarketScan, CitySummary, HistoryPayload, } from "@/lib/dashboard-types"; @@ -13,7 +12,6 @@ const CACHE_TTL_MS = 5 * 60 * 1000; const pendingCityDetailRequests = new Map>(); const pendingHistoryRequests = new Map>(); const pendingCitySummaryRequests = new Map>(); -const pendingMarketScanRequests = new Map>(); type CityCacheMeta = { cachedAt: number; @@ -195,64 +193,6 @@ export const dashboardClient = { ); }, - async getCityMarketScan( - cityName: string, - options?: { - force?: boolean; - marketSlug?: string | null; - targetDate?: string | null; - }, - ) { - const force = options?.force ?? false; - const marketSlug = options?.marketSlug || null; - const targetDate = options?.targetDate || null; - if (!force) { - const requestKey = `${cityName}::cached::${marketSlug || "-"}::${ - targetDate || "-" - }`; - const existing = pendingMarketScanRequests.get(requestKey); - if (existing) { - return existing; - } - - const params = new URLSearchParams({ - force_refresh: "false", - }); - if (marketSlug) { - params.set("market_slug", marketSlug); - } - if (targetDate) { - params.set("target_date", targetDate); - } - - const request = fetchJson<{ market_scan?: MarketScan }>( - `/api/city/${normalizeCityName(cityName)}/market-scan?${params.toString()}`, - ) - .then((data) => data.market_scan || null) - .finally(() => { - pendingMarketScanRequests.delete(requestKey); - }); - - pendingMarketScanRequests.set(requestKey, request); - return request; - } - - const params = new URLSearchParams({ - force_refresh: "true", - _ts: String(Date.now()), - }); - if (marketSlug) { - params.set("market_slug", marketSlug); - } - if (targetDate) { - params.set("target_date", targetDate); - } - - return fetchJson<{ market_scan?: MarketScan }>( - `/api/city/${normalizeCityName(cityName)}/market-scan?${params.toString()}`, - ).then((data) => data.market_scan || null); - }, - async getHistory(cityName: string, options?: { includeRecords?: boolean }) { const includeRecords = options?.includeRecords === true; const requestKey = `${normalizeCityName(cityName)}::${ diff --git a/frontend/lib/dashboard-types.ts b/frontend/lib/dashboard-types.ts index cb9cca89..13ac8776 100644 --- a/frontend/lib/dashboard-types.ts +++ b/frontend/lib/dashboard-types.ts @@ -556,3 +556,4 @@ export interface DashboardState { historyState: HistoryState; proAccess: ProAccessState; } + diff --git a/web/routes.py b/web/routes.py index 62660c7b..3ecc211e 100644 --- a/web/routes.py +++ b/web/routes.py @@ -600,21 +600,7 @@ async def auth_me(request: Request): user_id, respect_requirement=False, ) - if ( - not latest_subscription - and getattr(PAYMENT_CHECKOUT, "enabled", False) - ): - try: - PAYMENT_CHECKOUT.reconcile_latest_intent(user_id) - latest_subscription = SUPABASE_ENTITLEMENT.get_latest_active_subscription( - user_id, - respect_requirement=False, - ) - except Exception: - latest_subscription = SUPABASE_ENTITLEMENT.get_latest_active_subscription( - user_id, - respect_requirement=False, - ) + latest_known_subscription = latest_subscription if not latest_known_subscription: latest_known_subscription = ( @@ -1119,3 +1105,4 @@ async def city_market_scan( market_slug, target_date, ) +