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:
@@ -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.
|
||||
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.
|
||||
# The milers:locations geo index keeps last-known positions indefinitely, so
|
||||
# Presence, as reported by the Go backend's miler_status:<id> key. The
|
||||
# milers:locations geo index keeps last-known positions indefinitely, so
|
||||
# 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_UNAVAILABLE_STATUSES = {"break", "offline", "busy", "onbreak", "off_duty", "unavailable"}
|
||||
|
||||
AVAILABLE, UNAVAILABLE, UNKNOWN = "available", "unavailable", "unknown"
|
||||
|
||||
|
||||
class DispatchAgent(SpecializedAgent):
|
||||
@@ -138,25 +146,34 @@ class DispatchAgent(SpecializedAgent):
|
||||
# Redis GEO lookup #
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
async def _miler_is_available(self, miler_id) -> bool:
|
||||
"""Liveness check against the backend-owned miler_status:<id> key
|
||||
({"userid": .., "status": "Available" | "Break" | ...}). Missing or
|
||||
unparseable → not available: geo-index presence alone is not evidence."""
|
||||
async def _miler_presence(self, miler_id) -> str:
|
||||
"""AVAILABLE / UNAVAILABLE / UNKNOWN from the backend-owned
|
||||
miler_status:<id> key ({"userid": .., "status": "Available"|"Break"|..}).
|
||||
|
||||
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:
|
||||
raw = await self._redis.get(f"miler_status:{miler_id}")
|
||||
if not raw:
|
||||
return False
|
||||
status = json.loads(raw).get("status", "")
|
||||
return str(status).lower() in MILER_AVAILABLE_STATUSES
|
||||
return UNKNOWN
|
||||
status = str(json.loads(raw).get("status", "")).lower()
|
||||
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(
|
||||
self, lat: float, lon: float, radius_km: int = 10
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""GEORADIUS sweep on milers:locations filtered to milers whose status
|
||||
key says they are available. Returns the nearest available miler (with
|
||||
how many index entries were skipped as stale/off-duty), or None."""
|
||||
"""GEORADIUS sweep on milers:locations, preferring a miler the backend
|
||||
reports Available. If none is confirmed Available but some have no
|
||||
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:
|
||||
results = await self._redis.georadius(
|
||||
"milers:locations",
|
||||
@@ -168,21 +185,32 @@ class DispatchAgent(SpecializedAgent):
|
||||
if not results:
|
||||
return None
|
||||
|
||||
first_unknown = None
|
||||
for candidate in results:
|
||||
if await self._miler_is_available(candidate):
|
||||
miler_info = await self._redis.hgetall(f"miler:{candidate}")
|
||||
return {
|
||||
"miler_id": candidate,
|
||||
"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)),
|
||||
"stale_candidates_skipped": len(results) - 1,
|
||||
}
|
||||
presence = await self._miler_presence(candidate)
|
||||
if presence == AVAILABLE:
|
||||
return await self._miler_record(candidate, AVAILABLE, results)
|
||||
if presence == UNKNOWN and first_unknown is None:
|
||||
first_unknown = candidate
|
||||
|
||||
if first_unknown is not None:
|
||||
return await self._miler_record(first_unknown, UNKNOWN, results)
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.warning(f"Redis GEORADIUS error: {e}")
|
||||
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]:
|
||||
"""Raw geo-index membership (any status) — reported separately so the
|
||||
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
|
||||
|
||||
nearest_km = None
|
||||
nearest_presence = None
|
||||
if has_coords:
|
||||
for radius in (10, 20, 30):
|
||||
try:
|
||||
if await self._find_zone(lat, lon, radius_km=radius):
|
||||
nearest_km = radius
|
||||
break
|
||||
found = await self._find_zone(lat, lon, radius_km=radius)
|
||||
except Exception as e:
|
||||
logger.warning(f"assignment facts: GEORADIUS {radius}km failed: {e}")
|
||||
break
|
||||
if found:
|
||||
nearest_km = radius
|
||||
nearest_presence = found.get("presence", UNKNOWN)
|
||||
break
|
||||
|
||||
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:
|
||||
facts["nearest_available_miler_within_km"] = "none within 30km"
|
||||
facts["nearest_miler_within_km"] = "none within 30km"
|
||||
facts["nearest_miler_presence"] = "none found"
|
||||
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".
|
||||
if has_coords:
|
||||
|
||||
21
core/llm.py
21
core/llm.py
@@ -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.
|
||||
- "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,
|
||||
the time of day, and whether coordinates were even available. "nearest_available_miler_within_km" counts
|
||||
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
|
||||
means riders exist here but nobody is on duty — a staffing gap, not a systemic fault. A single failure with
|
||||
an available rider nearby is usually transient; repeated failures with no available rider is a coverage
|
||||
gap; repeated failures *despite* available riders nearby is not a coverage gap and warrants a human.
|
||||
Weigh how many times assignment has failed in this zone today, how far the nearest rider is and whether
|
||||
that rider is actually on duty, the time of day, and whether coordinates were even available.
|
||||
|
||||
Read the rider facts carefully:
|
||||
- "nearest_miler_within_km": distance to the nearest rider in the location index.
|
||||
- "nearest_miler_presence": "available" = the backend confirms that rider is on duty;
|
||||
"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]."""
|
||||
|
||||
_ASSIGNMENT_SCHEMA = {
|
||||
|
||||
@@ -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": "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": "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": "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": "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": "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": "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": "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": "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": "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": "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": "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, "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, "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, "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, "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, "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, "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, "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, "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, "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_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."}
|
||||
|
||||
@@ -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
|
||||
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
|
||||
reflects what DispatchAgent actually sends.
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user