feat: implement comprehensive Polymarket weather analysis service with frontend dashboard and market scanning capabilities

This commit is contained in:
2569718930@qq.com
2026-04-23 20:45:32 +08:00
parent 4f57d4ed1a
commit 54b326ff42
12 changed files with 323 additions and 154 deletions
@@ -746,6 +746,7 @@ export function FutureForecastModal() {
dashboardClient
.getCityMarketScan(cityName, {
force: false,
lite: false,
marketSlug: detail.market_scan?.primary_market?.slug || null,
targetDate: dateStr,
})
@@ -766,7 +767,7 @@ export function FutureForecastModal() {
return;
}
refreshMarketScan();
}, 3000);
}, 30_000);
return () => {
cancelled = true;
@@ -346,6 +346,7 @@ type AssistantMessage = {
};
type OpportunityScanState = "pending" | "complete" | "empty" | "error";
const HOME_OPPORTUNITY_REFRESH_MS = 30_000;
type AssistantDockPosition = {
right: number;
@@ -2395,6 +2396,7 @@ function DashboardScreen() {
const { t } = useI18n();
const didAutoFocusRef = useRef(false);
const marketScanInflightRef = useRef<Set<string>>(new Set());
const marketScanPollingRef = useRef(false);
const [marketScanStatusByCity, setMarketScanStatusByCity] = useState<
Record<string, OpportunityScanState>
>({});
@@ -2517,7 +2519,7 @@ function DashboardScreen() {
const existingDetail = store.cityDetailsByName[cityName];
const marketScan =
existingDetail?.market_scan ||
(await store.ensureCityMarketScan(cityName, false));
(await store.ensureCityMarketScan(cityName, false, { lite: true }));
if (cancelled) return;
setMarketScanStatusByCity((current) => ({
...current,
@@ -2552,6 +2554,57 @@ function DashboardScreen() {
store.proAccess.loading,
]);
useEffect(() => {
if (!showHomepageChrome) return;
if (store.proAccess.loading || !store.proAccess.authenticated) return;
if (!marketScanTargetNames.length) return;
let cancelled = false;
let intervalId: number | null = null;
const refreshAllMarketScans = async () => {
if (cancelled || marketScanPollingRef.current) return;
marketScanPollingRef.current = true;
const queue = [...marketScanTargetNames];
try {
const runWorker = async () => {
while (!cancelled) {
const cityName = queue.shift();
if (!cityName) return;
await store.ensureCityMarketScan(cityName, false, { lite: true });
}
};
await Promise.allSettled(
Array.from({ length: Math.min(3, queue.length) }, () => runWorker()),
);
} finally {
marketScanPollingRef.current = false;
}
};
void refreshAllMarketScans();
intervalId = window.setInterval(() => {
if (typeof document !== "undefined" && document.visibilityState === "hidden") {
return;
}
void refreshAllMarketScans();
}, HOME_OPPORTUNITY_REFRESH_MS);
return () => {
cancelled = true;
marketScanPollingRef.current = false;
if (intervalId != null) {
window.clearInterval(intervalId);
}
};
}, [
marketScanTargetNames,
showHomepageChrome,
store.ensureCityMarketScan,
store.proAccess.authenticated,
store.proAccess.loading,
]);
return (
<div
className={clsx(