fix(dispatch): presence is three-state — absent status key is unknown, not off-duty

The previous commit treated a missing miler_status:<id> key as "not available".
In production only 2 of 34 milers have that key at all, so the agent would have
reported "no available rider" for nearly every failure — a confident wrong
answer in the opposite direction from the bug it fixed.

- _miler_presence returns available / unavailable / unknown. No key, an
  unparseable value, or an unrecognised status reads as unknown.
- _find_zone prefers a confirmed-available miler, otherwise reports the nearest
  unknown-presence one (it may well be assignable), and returns None only when
  every nearby candidate is confirmed off duty.
- Facts carry nearest_miler_within_km + nearest_miler_presence; the prompt
  states plainly that unknown presence is not evidence of a coverage gap and
  should lean to monitor/escalate rather than ops_alert.
- New eval case for riders-nearby-but-no-presence-data; tests for all three
  presence states.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AJLYcbTHCe45fyFnMfEin
This commit is contained in:
2026-09-22 16:39:33 +05:30
parent 4283c602f6
commit 5159c8d1a7
5 changed files with 137 additions and 73 deletions

View File

@@ -18,13 +18,16 @@ from agents.dispatch_agent import DispatchAgent
class FakeRedis:
"""decode_responses=True style. `geo` is the GEORADIUS result for every call.
`available` is the set of miler ids whose miler_status:<id> says Available;
every other geo member is treated as present-but-off-duty (the stale-index case)."""
def __init__(self, geo=None, counter_start=0, raise_on=(), available=None, alerted=False):
`available` / `on_break` are miler ids with a miler_status:<id> key saying so.
Any geo member in neither set has NO status key at all — the common case in
production, which must read as unknown presence, not off-duty."""
def __init__(self, geo=None, counter_start=0, raise_on=(), available=None,
on_break=(), alerted=False):
self._geo = list(geo) if geo is not None else []
self._counter = counter_start
self._raise_on = set(raise_on)
self._available = set(available) if available is not None else set(self._geo)
self._on_break = set(on_break)
self._kv = {}
if alerted:
self._kv["__alerted__"] = "1"
@@ -37,9 +40,9 @@ class FakeRedis:
mid = key.split(":", 1)[1]
if mid in self._available:
return '{"userid": %s, "status": "Available"}' % (mid if mid.isdigit() else 0)
if mid in self._geo:
if mid in self._on_break:
return '{"userid": 0, "status": "Break"}'
return None
return None # no status key — unknown, not off-duty
return self._kv.get(key)
async def exists(self, key):
@@ -117,7 +120,8 @@ class TestGatherAssignmentFacts(unittest.IsolatedAsyncioTestCase):
facts, count = await make_agent(redis)._gather_assignment_facts("hyderabad", 17.4, 78.4)
self.assertEqual(facts["zone_id"], "hyderabad")
self.assertTrue(facts["has_coordinates"])
self.assertEqual(facts["nearest_available_miler_within_km"], 10)
self.assertEqual(facts["nearest_miler_within_km"], 10)
self.assertEqual(facts["nearest_miler_presence"], "available")
self.assertEqual(facts["milers_in_geo_index_within_30km"], 1)
self.assertFalse(facts["alert_already_sent_today"])
self.assertEqual(facts["failures_today"], 1)
@@ -127,7 +131,7 @@ class TestGatherAssignmentFacts(unittest.IsolatedAsyncioTestCase):
async def test_no_rider_within_30km(self):
redis = FakeRedis(geo=[], counter_start=2) # every sweep empty
facts, count = await make_agent(redis)._gather_assignment_facts("pune", 18.5, 73.8)
self.assertEqual(facts["nearest_available_miler_within_km"], "none within 30km")
self.assertEqual(facts["nearest_miler_within_km"], "none within 30km")
self.assertEqual(facts["milers_in_geo_index_within_30km"], 0)
self.assertEqual(count, 3)
@@ -135,7 +139,7 @@ class TestGatherAssignmentFacts(unittest.IsolatedAsyncioTestCase):
redis = FakeRedis(geo=["m1"])
facts, count = await make_agent(redis)._gather_assignment_facts("unknown", None, None)
self.assertFalse(facts["has_coordinates"])
self.assertEqual(facts["nearest_available_miler_within_km"], "unknown (no coordinates)")
self.assertEqual(facts["nearest_miler_within_km"], "unknown (no coordinates)")
self.assertNotIn("milers_in_geo_index_within_30km", facts)
self.assertEqual(count, 1) # counter still incremented
self.assertEqual(redis.incr_calls and 1, 1)
@@ -143,37 +147,53 @@ class TestGatherAssignmentFacts(unittest.IsolatedAsyncioTestCase):
async def test_georadius_error_is_swallowed(self):
redis = FakeRedis(raise_on={"georadius"})
facts, count = await make_agent(redis)._gather_assignment_facts("z", 1.0, 2.0) # must not raise
self.assertEqual(facts["nearest_available_miler_within_km"], "none within 30km")
self.assertEqual(facts["nearest_miler_within_km"], "none within 30km")
self.assertEqual(facts["failures_today"], 1)
async def test_counter_error_is_swallowed(self):
redis = FakeRedis(geo=["m9"], raise_on={"incr"})
facts, count = await make_agent(redis)._gather_assignment_facts("z", 1.0, 2.0) # must not raise
self.assertEqual(facts["nearest_available_miler_within_km"], 10) # coverage still gathered
self.assertEqual(facts["nearest_miler_within_km"], 10) # coverage still gathered
self.assertEqual(facts["failures_today"], 0) # counter degraded to 0
self.assertEqual(count, 0)
class TestLiveness(unittest.IsolatedAsyncioTestCase):
"""The prod incident: 26 riders in the geo index, none on duty. The old
check said 'rider within 20 km' and called it a coverage gap."""
class TestPresence(unittest.IsolatedAsyncioTestCase):
"""Presence is three-state. The prod incident had 26 riders in the geo index;
only 2 of 34 milers had a status key at all, so "no key" must read as unknown
— reporting it as off-duty would be a confident wrong answer."""
async def test_stale_index_entries_are_not_available_riders(self):
redis = FakeRedis(geo=["4", "7", "12"], available=[]) # all present, all off-duty
facts, _ = await make_agent(redis)._gather_assignment_facts("coimbatore", 11.0, 76.9)
self.assertEqual(facts["nearest_available_miler_within_km"], "none within 30km")
self.assertEqual(facts["milers_in_geo_index_within_30km"], 3) # but the index knows them
async def test_first_available_candidate_wins(self):
redis = FakeRedis(geo=["4", "7", "12"], available=["12"])
async def test_confirmed_available_rider_is_preferred(self):
redis = FakeRedis(geo=["4", "7", "12"], available=["12"], on_break=["4", "7"])
found = await make_agent(redis)._find_zone(11.0, 76.9, radius_km=10)
self.assertEqual(found["miler_id"], "12")
self.assertEqual(found["stale_candidates_skipped"], 2)
self.assertEqual(found["presence"], "available")
async def test_missing_status_key_means_unavailable(self):
redis = FakeRedis(geo=["99"], available=[])
redis._geo = [] # nothing in geo → get() returns None for status
self.assertFalse(await make_agent(redis)._miler_is_available("99"))
async def test_no_status_key_reads_as_unknown_not_unavailable(self):
redis = FakeRedis(geo=["4", "7"], available=[], on_break=[]) # no status keys at all
agent = make_agent(redis)
self.assertEqual(await agent._miler_presence("4"), "unknown")
found = await agent._find_zone(11.0, 76.9, radius_km=10)
self.assertIsNotNone(found) # still reported — may be assignable
self.assertEqual(found["presence"], "unknown")
async def test_all_confirmed_off_duty_returns_none(self):
redis = FakeRedis(geo=["4", "7"], available=[], on_break=["4", "7"])
self.assertIsNone(await make_agent(redis)._find_zone(11.0, 76.9, radius_km=10))
async def test_facts_report_presence_unknown_with_rider_nearby(self):
redis = FakeRedis(geo=["4", "7", "12"], available=[], on_break=[])
facts, _ = await make_agent(redis)._gather_assignment_facts("coimbatore", 11.0, 76.9)
self.assertEqual(facts["nearest_miler_within_km"], 10)
self.assertEqual(facts["nearest_miler_presence"], "unknown")
self.assertEqual(facts["milers_in_geo_index_within_30km"], 3)
async def test_facts_report_none_found_when_all_off_duty(self):
redis = FakeRedis(geo=["4"], available=[], on_break=["4"])
facts, _ = await make_agent(redis)._gather_assignment_facts("coimbatore", 11.0, 76.9)
self.assertEqual(facts["nearest_miler_within_km"], "none within 30km")
self.assertEqual(facts["nearest_miler_presence"], "none found")
self.assertEqual(facts["milers_in_geo_index_within_30km"], 1) # index still knows them
class FakeMessages: