Skip to content

Commit c4b51c0

Browse files
authored
fix(scheduler): check social cooldown before debounce; keep no-fallback on pure debounce (tests pass)
1 parent d8fcbce commit c4b51c0

File tree

1 file changed

+10
-14
lines changed

1 file changed

+10
-14
lines changed

src/ohip/nudge_scheduler.py

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,9 @@
33
Engagement Scheduler (spec §9, §5, §6, §7)
44
55
Change log (2025-08-19):
6-
- If the **top-ranked** candidate is suppressed due to **debounce**, do NOT fall
7-
back to the next candidate — return None for this cycle to avoid “nagging”.
8-
- Still allow fallback when the top candidate is suppressed due to **cooldown**
9-
(social cooldown) or other non-debounce reasons. This preserves behavior for
10-
tests that expect object interaction when shoulder is blocked by policy/safety.
6+
- Evaluate **social cooldown before debounce** for human-touch candidates.
7+
If cooldown blocks the top candidate, we DO fall back to the next candidate.
8+
- Preserve "no fallback on pure debounce for the top candidate" to avoid nagging.
119
"""
1210

1311
from __future__ import annotations
@@ -163,12 +161,10 @@ def decide(
163161
return nudge
164162

165163
# If the **top** candidate was blocked due to **debounce**, do not fall back.
166-
# Return None to avoid appearing as “nagging” by switching targets immediately.
167164
if idx == 0 and reason == "debounce":
168165
return None
169166

170167
# For cooldown or other reasons, try the next candidate.
171-
# (E.g., social cooldown should still allow an object-interaction nudge.)
172168
continue
173169

174170
return None
@@ -242,8 +238,8 @@ def _candidate_to_nudge_with_reason(
242238
) -> Tuple[Optional[Nudge], str]:
243239
"""
244240
Convert a ranked candidate into a Nudge while enforcing:
245-
- social cooldown (no repeated touches too quickly);
246-
- debouncing of identical nudges within window;
241+
- **social cooldown** (checked first for human targets);
242+
- **debouncing** of identical nudges within window;
247243
- consent gate for social touch;
248244
- assign GREEN vs. YELLOW nudge level per spec logic.
249245
@@ -255,14 +251,14 @@ def _candidate_to_nudge_with_reason(
255251
category = str(a.get("category", "object"))
256252
risk_level: SafetyLevel = a["_risk_level"]
257253

258-
# Debounce identical nudges:
259-
if self.cooldowns.debounce(name, xyz, self.policy.debounce_window_s):
260-
return None, "debounce"
261-
262-
# Social cooldown: only for human-target nudges (e.g., shoulder).
254+
# --- Social cooldown FIRST for human targets ---
263255
if category == "human" and self.cooldowns.social_cooldown_active(self.policy.social_cooldown_s):
264256
return None, "cooldown"
265257

258+
# --- Debounce (applies to any category) ---
259+
if self.cooldowns.debounce(name, xyz, self.policy.debounce_window_s):
260+
return None, "debounce"
261+
266262
# Determine nudge level:
267263
if category == "human" and name == "shoulder":
268264
# Consent handling for social touch.

0 commit comments

Comments
 (0)