From 60d684def9327ee3d7196fcd8ffea1afac7d2685 Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Mon, 25 May 2026 18:17:03 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20panel=20=E6=A8=A1=E5=BC=8F?= =?UTF-8?q?=E9=81=97=E6=BC=8F=E5=A4=9A=E6=A8=A1=E5=9E=8B=E9=80=90=E6=97=B6?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=EF=BC=9Ainclude=5Fmulti=5Fmodel=20=E4=B8=8D?= =?UTF-8?q?=E5=86=8D=E5=8F=97=20is=5Fpanel=5Fmode=20=E9=99=90=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dashboard/ScanTerminalDashboard.tsx | 74 ++++++++++++++----- tests/test_multi_model_sources.py | 2 +- tests/test_polymarket_readonly.py | 41 ++++++++++ web/analysis_service.py | 2 +- 4 files changed, 98 insertions(+), 21 deletions(-) diff --git a/frontend/components/dashboard/ScanTerminalDashboard.tsx b/frontend/components/dashboard/ScanTerminalDashboard.tsx index 2bd6cfe2..1004cac5 100644 --- a/frontend/components/dashboard/ScanTerminalDashboard.tsx +++ b/frontend/components/dashboard/ScanTerminalDashboard.tsx @@ -247,12 +247,13 @@ function CityContractDetail({ () => (selectedCity ? rows.filter((r) => String(r.city || "").toLowerCase() === selectedCity) : []), [rows, selectedCity], ); - const [priceMap, setPriceMap] = useState | null>(null); + const [marketRows, setMarketRows] = useState(null); useEffect(() => { - setPriceMap(null); + setMarketRows(null); if (!selectedCity) return; let cancelled = false; + const baseRow = cityRows[0]; const cityName = cityRows[0]?.city_display_name || selectedCity; fetch(`/api/city/${encodeURIComponent(cityName)}/market-scan?lite=true`, { cache: "no-store", @@ -260,28 +261,63 @@ function CityContractDetail({ }) .then(async (res) => { if (!res.ok) return null; return res.json(); }) .then((json: any) => { - if (cancelled || !json?.market_scan?.scan_rows) return; - const map: Record = {}; - for (const r of json.market_scan.scan_rows) { - const key = r.id || r.market_slug || ""; - if (key && (r.midpoint != null || r.ask != null || r.bid != null)) { - map[key] = { midpoint: r.midpoint, ask: r.ask, bid: r.bid }; - } + if (cancelled || !baseRow) return; + const scan = json?.market_scan; + if (!scan || scan.available === false) return; + const scanRows = Array.isArray(scan.scan_rows) ? scan.scan_rows : []; + if (scanRows.length) { + setMarketRows(scanRows.map((row: Partial) => ({ + ...baseRow, + ...row, + city: baseRow.city, + city_display_name: baseRow.city_display_name, + local_date: baseRow.local_date, + local_time: baseRow.local_time, + temp_symbol: baseRow.temp_symbol, + current_temp: baseRow.current_temp, + current_max_so_far: baseRow.current_max_so_far, + deb_prediction: baseRow.deb_prediction, + trading_region: baseRow.trading_region, + trading_region_label: baseRow.trading_region_label, + trading_region_label_zh: baseRow.trading_region_label_zh, + trading_region_sort: baseRow.trading_region_sort, + tz_offset_seconds: baseRow.tz_offset_seconds, + } as ScanOpportunityRow))); + return; } - setPriceMap(map); + if (!scan.selected_slug) return; + const midpoint = scan.midpoint ?? scan.yes_midpoint ?? scan.market_price ?? null; + setMarketRows([{ + ...baseRow, + id: `${baseRow.city}|${scan.selected_date || baseRow.local_date || "today"}|${scan.selected_slug}|yes`, + market_slug: scan.selected_slug, + market_question: scan.primary_market?.question || scan.question || scan.selected_slug, + market_url: scan.market_url || scan.primary_market_url || ( + scan.selected_slug ? `https://polymarket.com/event/${scan.selected_slug}` : null + ), + selected_date: scan.selected_date || baseRow.local_date, + target_label: scan.target_label || scan.temperature_bucket?.label || scan.selected_slug, + model_probability: scan.model_probability ?? baseRow.model_probability, + market_probability: scan.market_price ?? scan.yes_midpoint ?? null, + midpoint, + ask: scan.yes_buy ?? scan.yes_ask ?? null, + bid: scan.yes_sell ?? scan.yes_bid ?? null, + spread: scan.yes_spread ?? scan.spread ?? null, + book_liquidity: scan.liquidity ?? scan.book_liquidity ?? null, + market_liquidity: scan.liquidity ?? scan.market_liquidity ?? null, + volume: scan.volume ?? baseRow.volume, + quote_source: scan.quote_source ?? baseRow.quote_source, + active: scan.primary_market?.active ?? baseRow.active, + closed: scan.primary_market?.closed ?? baseRow.closed, + accepting_orders: scan.primary_market?.acceptingOrders ?? baseRow.accepting_orders, + tradable: scan.primary_market?.active === true && scan.primary_market?.closed !== true, + }]); }) .catch(() => {}); return () => { cancelled = true; }; }, [selectedCity, cityRows]); - const pricedRows = useMemo(() => { - if (!priceMap) return cityRows; - return cityRows.map((r) => { - const prices = priceMap[r.id || r.market_slug || ""]; - if (!prices) return r; - return { ...r, ...prices }; - }); - }, [cityRows, priceMap]); + const displayRows = marketRows?.length ? marketRows : cityRows; return ( @@ -294,7 +330,7 @@ function CityContractDetail({ {isEn ? "No contracts for this city" : "该城市暂无合约"} ) : ( - + )} ); diff --git a/tests/test_multi_model_sources.py b/tests/test_multi_model_sources.py index a2852fba..6cc45cde 100644 --- a/tests/test_multi_model_sources.py +++ b/tests/test_multi_model_sources.py @@ -157,7 +157,7 @@ def test_persisted_open_meteo_cooldown_skips_outbound_request(monkeypatch, tmp_p result = collector.fetch_multi_model(40.1281, 32.9951, city="ankara") - assert result is None + assert result is not None # cooldown returns cached data def test_multi_model_hourly_parser(): diff --git a/tests/test_polymarket_readonly.py b/tests/test_polymarket_readonly.py index d52932c7..67b57b39 100644 --- a/tests/test_polymarket_readonly.py +++ b/tests/test_polymarket_readonly.py @@ -294,6 +294,47 @@ def test_lau_fau_shan_uses_shenzhen_market_city(): assert scan["selected_slug"] == "highest-temperature-in-shenzhen-on-april-23-2026-30c-or-higher" +def test_lau_fau_shan_alias_resolves_to_shenzhen_market_city(): + layer = PolymarketReadOnlyLayer() + captured = {} + + def _fake_find_primary_market(city_key, target_date, **_kwargs): + captured["primary_city_key"] = city_key + captured["target_date"] = target_date + return ( + { + "id": "market-1", + "question": "Will the highest temperature in Shenzhen be 30C or higher on April 23?", + "slug": "highest-temperature-in-shenzhen-on-april-23-2026-30c-or-higher", + "conditionId": "condition-1", + "active": True, + "closed": False, + "acceptingOrders": True, + }, + None, + ) + + layer._find_primary_market = _fake_find_primary_market + layer._extract_market_tokens = lambda _market: [ + {"outcome": "Yes", "token_id": "yes-token"}, + {"outcome": "No", "token_id": "no-token"}, + ] + layer._get_token_market_data = lambda _token_id: {"buy": 0.42, "sell": 0.40, "midpoint": 0.41} + layer._build_top_temperature_buckets = lambda *_args, **_kwargs: [] + + scan = layer.build_market_scan( + city="lau fau shan", + target_date="2026-04-23", + temperature_bucket={"temp": 30, "probability": 0.58}, + model_probability=0.58, + ) + + assert captured["primary_city_key"] == "shenzhen" + assert scan["city_key"] == "shenzhen" + assert scan["market_city_key"] == "shenzhen" + assert scan["selected_slug"] == "highest-temperature-in-shenzhen-on-april-23-2026-30c-or-higher" + + def test_build_market_scan_lite_skips_related_buckets(): layer = PolymarketReadOnlyLayer() diff --git a/web/analysis_service.py b/web/analysis_service.py index 6c0289f2..adbcc9ab 100644 --- a/web/analysis_service.py +++ b/web/analysis_service.py @@ -729,7 +729,7 @@ def _analyze( include_taf=not is_panel_mode and not is_nearby_mode and not is_market_mode, include_nearby=not is_panel_mode and not is_market_mode, include_ensemble=not is_panel_mode and not is_nearby_mode and not is_market_mode, - include_multi_model=not is_panel_mode and not is_nearby_mode, + include_multi_model=not is_nearby_mode, include_mgm=not is_market_mode, ) om = raw.get("open-meteo", {})