fix(intelligent-eval): parse minute-level time slots (1h windows)
All checks were successful
CI / test (push) Successful in 3m54s
All checks were successful
CI / test (push) Successful in 3m54s
planner 对短窗口(1h)产出分钟级时段(如 0-20min/20-40min/40-60min), 但 parse_time_slot 只支持小时级(8-10h),分钟格式解析失败返回 None → is_slot_due=False → 审批后评估永不入队、不触发 worker。 - parse_time_slot 支持 h/min 后缀,统一换算成小时(float)返回 - is_slot_due/get_current_slot/count_sessions_in_slot 用 timedelta(hours=float) 兼容两种格式;decision._parse_time_slot 类型标注同步 float 测试:+1(分钟格式时段解析与到期判断),898 passed
This commit is contained in:
parent
9d87ecf736
commit
60c54a67e4
@ -45,7 +45,7 @@ class Decision:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _parse_time_slot(time_slot: str) -> Optional[tuple[int, int]]:
|
def _parse_time_slot(time_slot: str) -> Optional[tuple[float, float]]:
|
||||||
"""Thin wrapper delegating to :func:`agenteval.intelligent_eval.domain.parse_time_slot`."""
|
"""Thin wrapper delegating to :func:`agenteval.intelligent_eval.domain.parse_time_slot`."""
|
||||||
from agenteval.intelligent_eval.domain import parse_time_slot as _impl
|
from agenteval.intelligent_eval.domain import parse_time_slot as _impl
|
||||||
return _impl(time_slot)
|
return _impl(time_slot)
|
||||||
|
|||||||
@ -21,13 +21,26 @@ from agenteval.storage.db import (
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def parse_time_slot(time_slot: str) -> Optional[tuple[int, int]]:
|
def parse_time_slot(time_slot: str) -> Optional[tuple[float, float]]:
|
||||||
"""Parse "8-10h" -> (8, 10). Returns None on bad format."""
|
"""Parse a slot into (start_hours, end_hours), both in hours.
|
||||||
|
|
||||||
|
Supports hour-level ("8-10h" -> (8, 10)) and minute-level ("0-20min" ->
|
||||||
|
(0, 1/3)) slots. planner 对短窗口(如 1h)会用分钟级时段,长窗口用
|
||||||
|
小时级;统一换算成小时(float)供上层判断。Returns None on bad format.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
parts = time_slot.replace("h", "").split("-")
|
raw = time_slot.strip().lower()
|
||||||
|
factor = 1.0
|
||||||
|
if raw.endswith("min"):
|
||||||
|
raw = raw[:-3]
|
||||||
|
factor = 1.0 / 60
|
||||||
|
elif raw.endswith("h"):
|
||||||
|
raw = raw[:-1]
|
||||||
|
factor = 1.0
|
||||||
|
parts = raw.split("-")
|
||||||
if len(parts) != 2:
|
if len(parts) != 2:
|
||||||
return None
|
return None
|
||||||
return (int(parts[0]), int(parts[1]))
|
return (int(parts[0]) * factor, int(parts[1]) * factor)
|
||||||
except (ValueError, AttributeError):
|
except (ValueError, AttributeError):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
@ -2,9 +2,6 @@
|
|||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
import pytest
|
|
||||||
from sqlmodel import Session, select
|
|
||||||
|
|
||||||
from agenteval.intelligent_eval import task_queue
|
from agenteval.intelligent_eval import task_queue
|
||||||
from agenteval.intelligent_eval.models import IntelligentEvalStatus
|
from agenteval.intelligent_eval.models import IntelligentEvalStatus
|
||||||
from agenteval.storage.db import (
|
from agenteval.storage.db import (
|
||||||
@ -13,6 +10,7 @@ from agenteval.storage.db import (
|
|||||||
IntelligentEvalTaskQueueDB,
|
IntelligentEvalTaskQueueDB,
|
||||||
utc_now,
|
utc_now,
|
||||||
)
|
)
|
||||||
|
from sqlmodel import Session, select
|
||||||
|
|
||||||
|
|
||||||
def test_is_slot_due():
|
def test_is_slot_due():
|
||||||
@ -28,6 +26,25 @@ def test_is_slot_due():
|
|||||||
assert task_queue._is_slot_due({}, timedelta(hours=1)) is False
|
assert task_queue._is_slot_due({}, timedelta(hours=1)) is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_is_slot_due_minute_format():
|
||||||
|
"""Minute-level slots (1h window: "0-20min") must be parsed and due on time."""
|
||||||
|
from agenteval.intelligent_eval import domain
|
||||||
|
|
||||||
|
# 0-20min: due at 0min, not before
|
||||||
|
assert task_queue._is_slot_due({"time_slot": "0-20min"}, timedelta(minutes=-1)) is False
|
||||||
|
assert task_queue._is_slot_due({"time_slot": "0-20min"}, timedelta(minutes=0)) is True
|
||||||
|
assert task_queue._is_slot_due({"time_slot": "0-20min"}, timedelta(minutes=5)) is True
|
||||||
|
# 20-40min: not due before 20min, due at 20min
|
||||||
|
assert task_queue._is_slot_due({"time_slot": "20-40min"}, timedelta(minutes=19)) is False
|
||||||
|
assert task_queue._is_slot_due({"time_slot": "20-40min"}, timedelta(minutes=20)) is True
|
||||||
|
|
||||||
|
# parse both formats consistently (hours)
|
||||||
|
assert domain.parse_time_slot("8-10h") == (8.0, 10.0)
|
||||||
|
assert domain.parse_time_slot("0-20min") == (0.0, 1.0 / 3)
|
||||||
|
assert domain.parse_time_slot("20-40min") == (1.0 / 3, 2.0 / 3)
|
||||||
|
assert domain.parse_time_slot("bad") is None
|
||||||
|
|
||||||
|
|
||||||
def test_calculate_session_deficit(db_session: Session):
|
def test_calculate_session_deficit(db_session: Session):
|
||||||
"""Test session deficit calculation."""
|
"""Test session deficit calculation."""
|
||||||
# Create eval with plan
|
# Create eval with plan
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user