fix(realism): use minute-precision datetime in in_active_hours
personas.in_active_hours was discarding the minute component of the active-hours window, making "09:30-17:45" behave as "09:00-17:00". Rewrote it to delegate to diurnal.in_work_hours (which uses full minute arithmetic) and updated the scheduler caller to pass the full datetime instead of now_dt.hour.
This commit is contained in:
@@ -175,7 +175,7 @@ async def pick(
|
|||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
active = [p for p in personas if in_active_hours(p, now_dt.hour)]
|
active = [p for p in personas if in_active_hours(p, now_dt)]
|
||||||
if len(active) < 2:
|
if len(active) < 2:
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"emailgen pick: source=%s mail_decky=%s only %d personas in-hours",
|
"emailgen pick: source=%s mail_decky=%s only %d personas in-hours",
|
||||||
|
|||||||
@@ -19,11 +19,13 @@ not stall the entire realism tick.
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
from datetime import datetime
|
||||||
from typing import Literal, Optional
|
from typing import Literal, Optional
|
||||||
|
|
||||||
from pydantic import BaseModel, Field, ValidationError, field_validator, model_validator
|
from pydantic import BaseModel, Field, ValidationError, field_validator, model_validator
|
||||||
|
|
||||||
from decnet.logging import get_logger
|
from decnet.logging import get_logger
|
||||||
|
from decnet.realism.diurnal import in_work_hours
|
||||||
|
|
||||||
logger = get_logger("realism.personas")
|
logger = get_logger("realism.personas")
|
||||||
|
|
||||||
@@ -132,22 +134,10 @@ def login_for(persona: str) -> str:
|
|||||||
return "user"
|
return "user"
|
||||||
|
|
||||||
|
|
||||||
def in_active_hours(persona: EmailPersona, now_hour: int) -> bool:
|
def in_active_hours(persona: EmailPersona, now: datetime) -> bool:
|
||||||
"""Return True if *now_hour* (0–23) falls in the persona's window.
|
"""Return True if *now* falls in the persona's active-hours window.
|
||||||
|
|
||||||
Format: ``"HH:MM-HH:MM"``. Wrap-around windows (``"22:00-06:00"``)
|
Delegates to :func:`decnet.realism.diurnal.in_work_hours` so minute
|
||||||
are supported. Invalid windows treat the persona as always-on so a
|
precision is preserved (``"09:30-17:45"`` is honoured correctly).
|
||||||
config typo never silences the whole fleet.
|
|
||||||
"""
|
"""
|
||||||
try:
|
return in_work_hours(persona.active_hours, now)
|
||||||
start_s, end_s = persona.active_hours.split("-")
|
|
||||||
start_h = int(start_s.split(":")[0])
|
|
||||||
end_h = int(end_s.split(":")[0])
|
|
||||||
except (ValueError, IndexError):
|
|
||||||
return True
|
|
||||||
if start_h == end_h:
|
|
||||||
return True
|
|
||||||
if start_h < end_h:
|
|
||||||
return start_h <= now_hour < end_h
|
|
||||||
# Wrap-around (e.g. 22:00-06:00).
|
|
||||||
return now_hour >= start_h or now_hour < end_h
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from decnet.realism.personas import (
|
from decnet.realism.personas import (
|
||||||
EmailPersona,
|
EmailPersona,
|
||||||
@@ -75,31 +76,52 @@ def test_uses_llms_heavily_can_be_set():
|
|||||||
assert parsed[0].uses_llms_heavily is True
|
assert parsed[0].uses_llms_heavily is True
|
||||||
|
|
||||||
|
|
||||||
|
def _dt(h: int, m: int = 0) -> datetime:
|
||||||
|
return datetime(2024, 1, 15, h, m)
|
||||||
|
|
||||||
|
|
||||||
def test_active_hours_normal_window():
|
def test_active_hours_normal_window():
|
||||||
p = EmailPersona(**_persona(active_hours="09:00-18:00"))
|
p = EmailPersona(**_persona(active_hours="09:00-18:00"))
|
||||||
assert in_active_hours(p, 12) is True
|
assert in_active_hours(p, _dt(12)) is True
|
||||||
assert in_active_hours(p, 8) is False
|
assert in_active_hours(p, _dt(8)) is False
|
||||||
assert in_active_hours(p, 18) is False
|
assert in_active_hours(p, _dt(18)) is False
|
||||||
assert in_active_hours(p, 9) is True
|
assert in_active_hours(p, _dt(9)) is True
|
||||||
|
|
||||||
|
|
||||||
def test_active_hours_wraparound_window():
|
def test_active_hours_wraparound_window():
|
||||||
p = EmailPersona(**_persona(active_hours="22:00-06:00"))
|
p = EmailPersona(**_persona(active_hours="22:00-06:00"))
|
||||||
assert in_active_hours(p, 23) is True
|
assert in_active_hours(p, _dt(23)) is True
|
||||||
assert in_active_hours(p, 0) is True
|
assert in_active_hours(p, _dt(0)) is True
|
||||||
assert in_active_hours(p, 5) is True
|
assert in_active_hours(p, _dt(5)) is True
|
||||||
assert in_active_hours(p, 7) is False
|
assert in_active_hours(p, _dt(7)) is False
|
||||||
|
|
||||||
|
|
||||||
def test_active_hours_malformed_treats_as_always_on():
|
def test_active_hours_malformed_treats_as_always_on():
|
||||||
p = EmailPersona(**_persona(active_hours="garbage"))
|
p = EmailPersona(**_persona(active_hours="garbage"))
|
||||||
assert in_active_hours(p, 0) is True
|
assert in_active_hours(p, _dt(0)) is True
|
||||||
assert in_active_hours(p, 23) is True
|
assert in_active_hours(p, _dt(23)) is True
|
||||||
|
|
||||||
|
|
||||||
def test_active_hours_equal_window_treated_as_always_on():
|
def test_active_hours_equal_window_treated_as_always_on():
|
||||||
p = EmailPersona(**_persona(active_hours="10:00-10:00"))
|
p = EmailPersona(**_persona(active_hours="10:00-10:00"))
|
||||||
assert in_active_hours(p, 5) is True
|
assert in_active_hours(p, _dt(5)) is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_active_hours_minute_precision_start_boundary():
|
||||||
|
p = EmailPersona(**_persona(active_hours="09:30-17:45"))
|
||||||
|
assert in_active_hours(p, _dt(9, 15)) is False
|
||||||
|
assert in_active_hours(p, _dt(9, 29)) is False
|
||||||
|
assert in_active_hours(p, _dt(9, 30)) is True
|
||||||
|
assert in_active_hours(p, _dt(17, 44)) is True
|
||||||
|
assert in_active_hours(p, _dt(17, 45)) is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_active_hours_minute_precision_wraparound():
|
||||||
|
p = EmailPersona(**_persona(active_hours="22:30-06:15"))
|
||||||
|
assert in_active_hours(p, _dt(22, 29)) is False
|
||||||
|
assert in_active_hours(p, _dt(22, 30)) is True
|
||||||
|
assert in_active_hours(p, _dt(6, 14)) is True
|
||||||
|
assert in_active_hours(p, _dt(6, 15)) is False
|
||||||
|
|
||||||
|
|
||||||
def test_login_for_normalises_display_name():
|
def test_login_for_normalises_display_name():
|
||||||
|
|||||||
Reference in New Issue
Block a user