From 9d76e90a28b72495854367e45234249f46f487f2 Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Sat, 4 Jul 2026 01:33:56 +0800 Subject: [PATCH] Handle empty ops market scan snapshots --- tests/test_ops_market_opportunities.py | 70 ++++++++++++++++++++++++ web/services/ops/market_opportunities.py | 39 +++++++++---- 2 files changed, 99 insertions(+), 10 deletions(-) diff --git a/tests/test_ops_market_opportunities.py b/tests/test_ops_market_opportunities.py index 151038dc..05911f27 100644 --- a/tests/test_ops_market_opportunities.py +++ b/tests/test_ops_market_opportunities.py @@ -114,6 +114,37 @@ def test_build_market_opportunities_scans_yes_and_no_low_price_edges(): assert rows[0]["edge"] >= rows[-1]["edge"] +def test_build_market_opportunities_includes_ask_equal_to_max_price(): + event = { + "slug": "highest-temperature-in-shanghai-on-july-3-2026", + "markets": [ + { + "question": "Will the highest temperature in Shanghai be 33°C on July 3?", + "slug": "highest-temperature-in-shanghai-on-july-3-2026-33c", + "active": True, + "closed": False, + "enableOrderBook": True, + "outcomes": '["Yes", "No"]', + "clobTokenIds": '["yes-33", "no-33"]', + } + ], + } + + rows = build_market_opportunity_rows( + [_row()], + {"shanghai": event}, + {"yes-33": 0.18, "no-33": 0.82}, + max_price=0.18, + side="yes", + positive_edge_only=True, + min_edge=0.0, + limit=20, + ) + + assert len(rows) == 1 + assert rows[0]["ask_price"] == 0.18 + + def test_build_market_opportunities_aggregates_fahrenheit_distribution(): event = { "slug": "highest-temperature-in-houston-on-july-3-2026", @@ -296,6 +327,45 @@ def test_ops_market_opportunities_reuses_prewarmed_terminal_snapshot(monkeypatch assert captured["filters"]["min_liquidity"] == 500 +def test_ops_market_opportunities_force_refreshes_empty_terminal_snapshot(monkeypatch): + calls = [] + monkeypatch.setattr(market_opportunities, "_require_ops", lambda _request: {"email": "ops@example.com"}) + + def fake_scan(_filters, *, force_refresh=False): + calls.append(force_refresh) + return {"rows": [_row()]} if force_refresh else {"rows": [], "status": "ready"} + + monkeypatch.setattr(market_opportunities, "build_scan_terminal_payload", fake_scan) + monkeypatch.setattr( + market_opportunities, + "_collect_events_and_prices", + lambda rows, _scanner: ({row["city"]: None for row in rows}, {}, "ready"), + ) + + payload = get_ops_market_opportunities(object(), max_price=0.90, min_edge=0.0) + + assert calls == [False, True] + assert payload["summary"]["scanned_city_count"] == 1 + assert payload["summary"]["quote_status"] == "ready" + assert payload["summary"]["scan_source"] == "force_refresh" + + +def test_ops_market_opportunities_reports_scan_empty_when_refresh_is_empty(monkeypatch): + monkeypatch.setattr(market_opportunities, "_require_ops", lambda _request: {"email": "ops@example.com"}) + monkeypatch.setattr( + market_opportunities, + "build_scan_terminal_payload", + lambda *_args, **_kwargs: {"rows": [], "status": "ready"}, + ) + + payload = get_ops_market_opportunities(object(), max_price=0.90, min_edge=0.0) + + assert payload["summary"]["scanned_city_count"] == 0 + assert payload["summary"]["quote_status"] == "scan_empty" + assert payload["summary"]["matched_event_count"] == 0 + assert payload["rows"] == [] + + def test_collect_events_and_prices_uses_gamma_hint_prices_without_clob_fanout(): class FakeScanner: def fetch_event(self, _slug): diff --git a/web/services/ops/market_opportunities.py b/web/services/ops/market_opportunities.py index 52462b45..f65c58b2 100644 --- a/web/services/ops/market_opportunities.py +++ b/web/services/ops/market_opportunities.py @@ -367,7 +367,7 @@ def build_market_opportunity_rows( continue ask = ask_prices_by_token.get(token_id) ask_number = _finite_number(ask) - if ask_number is None or ask_number <= 0 or ask_number >= float(max_price): + if ask_number is None or ask_number <= 0 or ask_number > float(max_price): continue if option_side == "no" and _is_late_priced_no_noise( scan_row, @@ -549,20 +549,38 @@ def get_ops_market_opportunities( scan_rows = scan_payload.get("rows") if isinstance(scan_payload, dict) else [] if not isinstance(scan_rows, list): scan_rows = [] + scan_source = "cache" + if not scan_rows: + refreshed_payload = build_scan_terminal_payload(filters, force_refresh=True) + refreshed_rows = refreshed_payload.get("rows") if isinstance(refreshed_payload, dict) else [] + if isinstance(refreshed_rows, list) and refreshed_rows: + scan_payload = refreshed_payload + scan_rows = refreshed_rows + scan_source = "force_refresh" quote_status = "ready" - try: - events_by_city, ask_prices, quote_status = _collect_events_and_prices( - scan_rows, - PolymarketQuoteScanner(), - ) - except Exception as exc: + if not scan_rows: events_by_city = {} ask_prices = {} - quote_status = "unavailable" - error = str(exc) + quote_status = "scan_empty" + error = ( + str(scan_payload.get("stale_reason") or scan_payload.get("error") or "") + if isinstance(scan_payload, dict) + else "" + ) or "scan terminal returned no rows" else: - error = None + try: + events_by_city, ask_prices, quote_status = _collect_events_and_prices( + scan_rows, + PolymarketQuoteScanner(), + ) + except Exception as exc: + events_by_city = {} + ask_prices = {} + quote_status = "unavailable" + error = str(exc) + else: + error = None rows = build_market_opportunity_rows( scan_rows, @@ -595,6 +613,7 @@ def get_ops_market_opportunities( "min_price": min(prices) if prices else None, "max_edge": max(edges) if edges else None, "quote_status": quote_status, + "scan_source": scan_source, "scanned_city_count": len(scan_rows), "matched_event_count": sum(1 for event in events_by_city.values() if isinstance(event, Mapping)), "error": error,