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

@@ -34,10 +34,18 @@ DISPATCH_STREAM = os.getenv("DISPATCH_STREAM", "")
# The LLM is only consulted before the first alert, so cost is bounded per zone. # The LLM is only consulted before the first alert, so cost is bounded per zone.
DISPATCH_REALERT_EVERY = int(os.getenv("DISPATCH_REALERT_EVERY", "100")) DISPATCH_REALERT_EVERY = int(os.getenv("DISPATCH_REALERT_EVERY", "100"))
# A miler counts as available only if the Go backend's presence key says so. # Presence, as reported by the Go backend's miler_status:<id> key. The
# The milers:locations geo index keeps last-known positions indefinitely, so # milers:locations geo index keeps last-known positions indefinitely, so
# membership alone says nothing about whether anyone is actually on duty. # membership alone says nothing about whether anyone is actually on duty.
#
# Three-state on purpose. Only 2 of 34 milers had a status key in production
# (2026-09-22), so treating "no key" as off-duty would report "nobody
# available" even where the backend can assign — a confident wrong answer.
# Absent presence data is reported as unknown and the decision is told so.
MILER_AVAILABLE_STATUSES = {"available"} MILER_AVAILABLE_STATUSES = {"available"}
MILER_UNAVAILABLE_STATUSES = {"break", "offline", "busy", "onbreak", "off_duty", "unavailable"}
AVAILABLE, UNAVAILABLE, UNKNOWN = "available", "unavailable", "unknown"
class DispatchAgent(SpecializedAgent): class DispatchAgent(SpecializedAgent):
@@ -138,25 +146,34 @@ class DispatchAgent(SpecializedAgent):
# Redis GEO lookup # # Redis GEO lookup #
# ------------------------------------------------------------------ # # ------------------------------------------------------------------ #
async def _miler_is_available(self, miler_id) -> bool: async def _miler_presence(self, miler_id) -> str:
"""Liveness check against the backend-owned miler_status:<id> key """AVAILABLE / UNAVAILABLE / UNKNOWN from the backend-owned
({"userid": .., "status": "Available" | "Break" | ...}). Missing or miler_status:<id> key ({"userid": .., "status": "Available"|"Break"|..}).
unparseable → not available: geo-index presence alone is not evidence."""
No key, an unparseable value, or an unrecognised status → UNKNOWN, never
UNAVAILABLE: most milers have no key at all, and absence of evidence is
not evidence of absence."""
try: try:
raw = await self._redis.get(f"miler_status:{miler_id}") raw = await self._redis.get(f"miler_status:{miler_id}")
if not raw: if not raw:
return False return UNKNOWN
status = json.loads(raw).get("status", "") status = str(json.loads(raw).get("status", "")).lower()
return str(status).lower() in MILER_AVAILABLE_STATUSES
except Exception: except Exception:
return False return UNKNOWN
if status in MILER_AVAILABLE_STATUSES:
return AVAILABLE
if status in MILER_UNAVAILABLE_STATUSES:
return UNAVAILABLE
return UNKNOWN
async def _find_zone( async def _find_zone(
self, lat: float, lon: float, radius_km: int = 10 self, lat: float, lon: float, radius_km: int = 10
) -> Optional[Dict[str, Any]]: ) -> Optional[Dict[str, Any]]:
"""GEORADIUS sweep on milers:locations filtered to milers whose status """GEORADIUS sweep on milers:locations, preferring a miler the backend
key says they are available. Returns the nearest available miler (with reports Available. If none is confirmed Available but some have no
how many index entries were skipped as stale/off-duty), or None.""" presence data, the nearest of those is returned with presence=UNKNOWN —
it may well be assignable. Returns None only when every candidate is
confirmed unavailable (or there are none)."""
try: try:
results = await self._redis.georadius( results = await self._redis.georadius(
"milers:locations", "milers:locations",
@@ -168,21 +185,32 @@ class DispatchAgent(SpecializedAgent):
if not results: if not results:
return None return None
first_unknown = None
for candidate in results: for candidate in results:
if await self._miler_is_available(candidate): presence = await self._miler_presence(candidate)
miler_info = await self._redis.hgetall(f"miler:{candidate}") if presence == AVAILABLE:
return { return await self._miler_record(candidate, AVAILABLE, results)
"miler_id": candidate, if presence == UNKNOWN and first_unknown is None:
"hub_id": miler_info.get("hub_id", "UNKNOWN"), first_unknown = candidate
"zone_id": miler_info.get("zone_id", "unknown"),
"avg_delivery_time": int(miler_info.get("avg_delivery_time", 60)), if first_unknown is not None:
"stale_candidates_skipped": len(results) - 1, return await self._miler_record(first_unknown, UNKNOWN, results)
}
return None return None
except Exception as e: except Exception as e:
logger.warning(f"Redis GEORADIUS error: {e}") logger.warning(f"Redis GEORADIUS error: {e}")
return None return None
async def _miler_record(self, miler_id, presence, candidates) -> Dict[str, Any]:
miler_info = await self._redis.hgetall(f"miler:{miler_id}")
return {
"miler_id": miler_id,
"presence": presence,
"hub_id": miler_info.get("hub_id", "UNKNOWN"),
"zone_id": miler_info.get("zone_id", "unknown"),
"avg_delivery_time": int(miler_info.get("avg_delivery_time", 60)),
"candidates_considered": len(candidates),
}
async def _count_in_geo_index(self, lat: float, lon: float, radius_km: int = 30) -> Optional[int]: async def _count_in_geo_index(self, lat: float, lon: float, radius_km: int = 30) -> Optional[int]:
"""Raw geo-index membership (any status) — reported separately so the """Raw geo-index membership (any status) — reported separately so the
decision can tell 'nobody registered here' from 'riders exist but none on duty'.""" decision can tell 'nobody registered here' from 'riders exist but none on duty'."""
@@ -327,22 +355,30 @@ class DispatchAgent(SpecializedAgent):
facts["has_coordinates"] = has_coords facts["has_coordinates"] = has_coords
nearest_km = None nearest_km = None
nearest_presence = None
if has_coords: if has_coords:
for radius in (10, 20, 30): for radius in (10, 20, 30):
try: try:
if await self._find_zone(lat, lon, radius_km=radius): found = await self._find_zone(lat, lon, radius_km=radius)
nearest_km = radius
break
except Exception as e: except Exception as e:
logger.warning(f"assignment facts: GEORADIUS {radius}km failed: {e}") logger.warning(f"assignment facts: GEORADIUS {radius}km failed: {e}")
break break
if found:
nearest_km = radius
nearest_presence = found.get("presence", UNKNOWN)
break
if not has_coords: if not has_coords:
facts["nearest_available_miler_within_km"] = "unknown (no coordinates)" facts["nearest_miler_within_km"] = "unknown (no coordinates)"
facts["nearest_miler_presence"] = "unknown (no coordinates)"
elif nearest_km is None: elif nearest_km is None:
facts["nearest_available_miler_within_km"] = "none within 30km" facts["nearest_miler_within_km"] = "none within 30km"
facts["nearest_miler_presence"] = "none found"
else: else:
facts["nearest_available_miler_within_km"] = nearest_km facts["nearest_miler_within_km"] = nearest_km
# "available" = backend confirms on duty; "unknown" = no presence
# data for any nearby miler, so this is not evidence of a gap.
facts["nearest_miler_presence"] = nearest_presence
# Distinguish "no riders registered here" from "riders exist, none on duty". # Distinguish "no riders registered here" from "riders exist, none on duty".
if has_coords: if has_coords:

View File

@@ -181,13 +181,20 @@ fallback failed, so no rider was assigned. Decide how to react. Choose exactly o
- "ops_alert": a genuine coverage gap in this zone (repeated failures, no nearby riders). Alert operations to onboard or redirect riders here. - "ops_alert": a genuine coverage gap in this zone (repeated failures, no nearby riders). Alert operations to onboard or redirect riders here.
- "escalate": ambiguous or contradictory — e.g. riders ARE nearby yet assignment keeps failing, which suggests a systemic issue rather than a coverage gap. A human dispatcher should look. - "escalate": ambiguous or contradictory — e.g. riders ARE nearby yet assignment keeps failing, which suggests a systemic issue rather than a coverage gap. A human dispatcher should look.
Weigh how many times assignment has failed in this zone today, how far the nearest *available* rider is, Weigh how many times assignment has failed in this zone today, how far the nearest rider is and whether
the time of day, and whether coordinates were even available. "nearest_available_miler_within_km" counts that rider is actually on duty, the time of day, and whether coordinates were even available.
only riders whose live status is Available; "milers_in_geo_index_within_30km" is the raw count of riders
ever seen nearby (it includes off-duty and stale entries), so a large index count with no available rider Read the rider facts carefully:
means riders exist here but nobody is on duty — a staffing gap, not a systemic fault. A single failure with - "nearest_miler_within_km": distance to the nearest rider in the location index.
an available rider nearby is usually transient; repeated failures with no available rider is a coverage - "nearest_miler_presence": "available" = the backend confirms that rider is on duty;
gap; repeated failures *despite* available riders nearby is not a coverage gap and warrants a human. "unavailable" = confirmed off duty / on break; "unknown" = there is NO presence data for nearby
riders. Unknown is not evidence of a coverage gap — do not treat it as "nobody is working".
- "milers_in_geo_index_within_30km": raw count of riders ever seen nearby, including stale entries.
A single failure with a rider nearby is usually transient. Repeated failures with no rider at all within
30 km is a genuine coverage gap. Repeated failures *despite* a confirmed-available rider nearby is not a
coverage gap but a systemic issue — escalate to a human. When presence is unknown, lean toward monitoring
or escalating for a human to check rather than declaring a coverage gap.
Report an honest confidence in [0, 1].""" Report an honest confidence in [0, 1]."""
_ASSIGNMENT_SCHEMA = { _ASSIGNMENT_SCHEMA = {

View File

@@ -1,11 +1,12 @@
{"id": "first-failure-close-rider", "hour_of_day_utc": 14, "facts": {"zone_id": "hyderabad", "failures_today": 1, "has_coordinates": true, "nearest_available_miler_within_km": 10, "milers_in_geo_index_within_30km": 3, "alert_already_sent_today": false}, "acceptable": ["monitor", "notify_customer"], "ideal": "monitor", "rationale": "Single failure with a rider only 10km away \u2014 almost certainly transient; let the backend retry."} {"id": "first-failure-close-rider", "hour_of_day_utc": 14, "facts": {"zone_id": "hyderabad", "failures_today": 1, "nearest_miler_within_km": 10, "nearest_miler_presence": "available", "milers_in_geo_index_within_30km": 3, "has_coordinates": true, "alert_already_sent_today": false}, "acceptable": ["monitor", "notify_customer"], "ideal": "monitor", "rationale": "Single failure with a rider only 10km away \u2014 almost certainly transient; let the backend retry."}
{"id": "repeated-zone-gap", "hour_of_day_utc": 12, "facts": {"zone_id": "pune", "failures_today": 4, "has_coordinates": true, "nearest_available_miler_within_km": "none within 30km", "milers_in_geo_index_within_30km": 0, "alert_already_sent_today": false}, "acceptable": ["ops_alert", "escalate"], "ideal": "ops_alert", "rationale": "Repeated failures and no rider within 30km \u2014 a genuine coverage gap; alert ops."} {"id": "repeated-zone-gap", "hour_of_day_utc": 12, "facts": {"zone_id": "pune", "failures_today": 4, "nearest_miler_within_km": "none within 30km", "nearest_miler_presence": "none found", "milers_in_geo_index_within_30km": 0, "has_coordinates": true, "alert_already_sent_today": false}, "acceptable": ["ops_alert", "escalate"], "ideal": "ops_alert", "rationale": "Repeated failures and no rider within 30km \u2014 a genuine coverage gap; alert ops."}
{"id": "second-failure-far-rider", "hour_of_day_utc": 18, "facts": {"zone_id": "bangalore", "failures_today": 2, "has_coordinates": true, "nearest_available_miler_within_km": 30, "milers_in_geo_index_within_30km": 3, "alert_already_sent_today": false}, "acceptable": ["notify_customer", "monitor"], "ideal": "notify_customer", "rationale": "A couple of failures with the nearest rider 30km out during rush \u2014 real delay; keep the customer informed."} {"id": "second-failure-far-rider", "hour_of_day_utc": 18, "facts": {"zone_id": "bangalore", "failures_today": 2, "nearest_miler_within_km": 30, "nearest_miler_presence": "available", "milers_in_geo_index_within_30km": 3, "has_coordinates": true, "alert_already_sent_today": false}, "acceptable": ["notify_customer", "monitor"], "ideal": "notify_customer", "rationale": "A couple of failures with the nearest rider 30km out during rush \u2014 real delay; keep the customer informed."}
{"id": "no-coordinates-first", "hour_of_day_utc": 10, "facts": {"zone_id": "unknown", "failures_today": 1, "has_coordinates": false, "nearest_available_miler_within_km": "unknown (no coordinates)", "alert_already_sent_today": false}, "acceptable": ["monitor", "escalate"], "ideal": "monitor", "rationale": "Coverage can't be assessed without coordinates, but a single failure is most likely transient."} {"id": "no-coordinates-first", "hour_of_day_utc": 10, "facts": {"zone_id": "unknown", "failures_today": 1, "nearest_miler_within_km": "unknown (no coordinates)", "nearest_miler_presence": "unknown (no coordinates)", "has_coordinates": false, "alert_already_sent_today": false}, "acceptable": ["monitor", "escalate"], "ideal": "monitor", "rationale": "Coverage can't be assessed without coordinates, but a single failure is most likely transient."}
{"id": "many-failures-riders-near", "hour_of_day_utc": 15, "facts": {"zone_id": "mumbai_west", "failures_today": 5, "has_coordinates": true, "nearest_available_miler_within_km": 10, "milers_in_geo_index_within_30km": 3, "alert_already_sent_today": false}, "acceptable": ["escalate", "ops_alert"], "ideal": "escalate", "rationale": "Riders ARE nearby yet assignment keeps failing \u2014 not a coverage gap; a systemic issue for a human."} {"id": "many-failures-riders-near", "hour_of_day_utc": 15, "facts": {"zone_id": "mumbai_west", "failures_today": 5, "nearest_miler_within_km": 10, "nearest_miler_presence": "available", "milers_in_geo_index_within_30km": 3, "has_coordinates": true, "alert_already_sent_today": false}, "acceptable": ["escalate", "ops_alert"], "ideal": "escalate", "rationale": "Riders ARE nearby yet assignment keeps failing \u2014 not a coverage gap; a systemic issue for a human."}
{"id": "night-coverage-gap", "hour_of_day_utc": 2, "facts": {"zone_id": "kolkata", "failures_today": 3, "has_coordinates": true, "nearest_available_miler_within_km": "none within 30km", "milers_in_geo_index_within_30km": 0, "alert_already_sent_today": false}, "acceptable": ["ops_alert", "escalate"], "ideal": "ops_alert", "rationale": "Repeated overnight failures with no nearby rider \u2014 a coverage gap worth flagging to ops."} {"id": "night-coverage-gap", "hour_of_day_utc": 2, "facts": {"zone_id": "kolkata", "failures_today": 3, "nearest_miler_within_km": "none within 30km", "nearest_miler_presence": "none found", "milers_in_geo_index_within_30km": 0, "has_coordinates": true, "alert_already_sent_today": false}, "acceptable": ["ops_alert", "escalate"], "ideal": "ops_alert", "rationale": "Repeated overnight failures with no nearby rider \u2014 a coverage gap worth flagging to ops."}
{"id": "single-far-rider", "hour_of_day_utc": 9, "facts": {"zone_id": "hyderabad", "failures_today": 1, "has_coordinates": true, "nearest_available_miler_within_km": 30, "milers_in_geo_index_within_30km": 3, "alert_already_sent_today": false}, "acceptable": ["monitor", "notify_customer"], "ideal": "monitor", "rationale": "One failure, distant rider \u2014 likely resolves on retry."} {"id": "single-far-rider", "hour_of_day_utc": 9, "facts": {"zone_id": "hyderabad", "failures_today": 1, "nearest_miler_within_km": 30, "nearest_miler_presence": "available", "milers_in_geo_index_within_30km": 3, "has_coordinates": true, "alert_already_sent_today": false}, "acceptable": ["monitor", "notify_customer"], "ideal": "monitor", "rationale": "One failure, distant rider \u2014 likely resolves on retry."}
{"id": "persistent-far-rush", "hour_of_day_utc": 18, "facts": {"zone_id": "mumbai_east", "failures_today": 3, "has_coordinates": true, "nearest_available_miler_within_km": 30, "milers_in_geo_index_within_30km": 3, "alert_already_sent_today": false}, "acceptable": ["notify_customer", "ops_alert"], "ideal": "notify_customer", "rationale": "Rush-hour, repeated, distant rider \u2014 the customer should hear about the delay."} {"id": "persistent-far-rush", "hour_of_day_utc": 18, "facts": {"zone_id": "mumbai_east", "failures_today": 3, "nearest_miler_within_km": 30, "nearest_miler_presence": "available", "milers_in_geo_index_within_30km": 3, "has_coordinates": true, "alert_already_sent_today": false}, "acceptable": ["notify_customer", "ops_alert"], "ideal": "notify_customer", "rationale": "Rush-hour, repeated, distant rider \u2014 the customer should hear about the delay."}
{"id": "borderline-count", "hour_of_day_utc": 13, "facts": {"zone_id": "north_delhi", "failures_today": 3, "has_coordinates": true, "nearest_available_miler_within_km": 20, "milers_in_geo_index_within_30km": 3, "alert_already_sent_today": false}, "acceptable": ["notify_customer", "ops_alert", "monitor"], "ideal": "notify_customer", "rationale": "Genuinely borderline \u2014 a few failures with a moderately-close rider; any of these is defensible."} {"id": "borderline-count", "hour_of_day_utc": 13, "facts": {"zone_id": "north_delhi", "failures_today": 3, "nearest_miler_within_km": 20, "nearest_miler_presence": "available", "milers_in_geo_index_within_30km": 3, "has_coordinates": true, "alert_already_sent_today": false}, "acceptable": ["notify_customer", "ops_alert", "monitor"], "ideal": "notify_customer", "rationale": "Genuinely borderline \u2014 a few failures with a moderately-close rider; any of these is defensible."}
{"id": "severe-gap", "hour_of_day_utc": 11, "facts": {"zone_id": "pune", "failures_today": 8, "has_coordinates": true, "nearest_available_miler_within_km": "none within 30km", "milers_in_geo_index_within_30km": 0, "alert_already_sent_today": false}, "acceptable": ["ops_alert", "escalate"], "ideal": "ops_alert", "rationale": "Many failures and no rider anywhere near \u2014 a clear, severe coverage gap."} {"id": "severe-gap", "hour_of_day_utc": 11, "facts": {"zone_id": "pune", "failures_today": 8, "nearest_miler_within_km": "none within 30km", "nearest_miler_presence": "none found", "milers_in_geo_index_within_30km": 0, "has_coordinates": true, "alert_already_sent_today": false}, "acceptable": ["ops_alert", "escalate"], "ideal": "ops_alert", "rationale": "Many failures and no rider anywhere near \u2014 a clear, severe coverage gap."}
{"id": "index-full-nobody-on-duty", "hour_of_day_utc": 7, "facts": {"zone_id": "coimbatore", "failures_today": 6, "nearest_available_miler_within_km": "none within 30km", "milers_in_geo_index_within_30km": 26, "has_coordinates": true, "alert_already_sent_today": false}, "acceptable": ["ops_alert", "escalate"], "ideal": "ops_alert", "rationale": "26 riders have been seen here but none is Available \u2014 a staffing/on-duty gap; ops needs to get riders online, not a systemic fault."} {"id": "index-full-nobody-on-duty", "hour_of_day_utc": 7, "facts": {"zone_id": "coimbatore", "failures_today": 6, "nearest_miler_within_km": "none within 30km", "nearest_miler_presence": "none found", "milers_in_geo_index_within_30km": 26, "has_coordinates": true, "alert_already_sent_today": false}, "acceptable": ["ops_alert", "escalate"], "ideal": "ops_alert", "rationale": "26 riders have been seen here but none is Available \u2014 a staffing/on-duty gap; ops needs to get riders online, not a systemic fault."}
{"id": "rider-near-presence-unknown", "hour_of_day_utc": 11, "facts": {"zone_id": "coimbatore", "failures_today": 4, "nearest_miler_within_km": 10, "nearest_miler_presence": "unknown", "milers_in_geo_index_within_30km": 26, "has_coordinates": true, "alert_already_sent_today": false}, "acceptable": ["escalate", "monitor", "notify_customer"], "ideal": "escalate", "rationale": "Riders are nearby but the backend reports no presence data \u2014 we cannot tell on-duty from off-duty, so this is NOT a coverage gap; a human should check why assignment fails."}

View File

@@ -4,7 +4,7 @@ Offline eval for the DispatchAgent assignment-failure decision
Same scoring as the stall eval (see evals/_harness.py): acceptable-rate is the Same scoring as the stall eval (see evals/_harness.py): acceptable-rate is the
headline, exact-rate secondary. The cases use the real gatherer keys headline, exact-rate secondary. The cases use the real gatherer keys
(zone_id, failures_today, nearest_available_miler_within_km, (zone_id, failures_today, nearest_miler_within_km, nearest_miler_presence,
milers_in_geo_index_within_30km, has_coordinates) so the eval milers_in_geo_index_within_30km, has_coordinates) so the eval
reflects what DispatchAgent actually sends. reflects what DispatchAgent actually sends.

View File

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