mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-29 06:31:32 +00:00
fix(kanban): task_age() tolerates ISO-8601 timestamps
Prevents ValueError crash in dashboard get_board() when a task has an ISO timestamp (e.g. "2026-05-10T15:00:00Z") instead of a unix epoch int. Adds _to_epoch() helper that normalises both formats.
This commit is contained in:
parent
ca8126bd53
commit
d8ad431de8
1 changed files with 28 additions and 10 deletions
|
|
@ -4756,26 +4756,44 @@ def board_stats(conn: sqlite3.Connection) -> dict:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _safe_int(val: Optional[str]) -> Optional[int]:
|
def _to_epoch(val) -> Optional[int]:
|
||||||
"""Parse a timestamp field to int, returning None on garbage like '%s'."""
|
"""Normalise a timestamp to unix epoch seconds.
|
||||||
|
|
||||||
|
Accepts ints (pass-through), numeric strings, and ISO-8601 strings.
|
||||||
|
Returns ``None`` for ``None`` / empty values.
|
||||||
|
"""
|
||||||
if val is None:
|
if val is None:
|
||||||
return None
|
return None
|
||||||
try:
|
if isinstance(val, int):
|
||||||
|
return val
|
||||||
|
if isinstance(val, float):
|
||||||
return int(val)
|
return int(val)
|
||||||
except (ValueError, TypeError):
|
s = str(val).strip()
|
||||||
|
if not s:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return int(s)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
# ISO-8601 fallback (e.g. '2026-05-10T15:00:00Z')
|
||||||
|
try:
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
dt = datetime.fromisoformat(s.replace("Z", "+00:00"))
|
||||||
|
return int(dt.timestamp())
|
||||||
|
except (ValueError, OSError):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def task_age(task: Task) -> dict:
|
def task_age(task: Task) -> dict:
|
||||||
"""Return age metrics for a single task. All values are seconds or None."""
|
"""Return age metrics for a single task. All values are seconds or None."""
|
||||||
now = int(time.time())
|
now = int(time.time())
|
||||||
created = _safe_int(task.created_at)
|
_c = _to_epoch(task.created_at)
|
||||||
started = _safe_int(task.started_at)
|
_s = _to_epoch(task.started_at)
|
||||||
completed = _safe_int(task.completed_at)
|
_co = _to_epoch(task.completed_at)
|
||||||
age_since_created = now - created if created else None
|
age_since_created = now - _c if _c is not None else None
|
||||||
age_since_started = now - started if started else None
|
age_since_started = now - _s if _s is not None else None
|
||||||
time_to_complete = (
|
time_to_complete = (
|
||||||
completed - (started or created) if completed else None
|
_co - (_s or _c) if _co is not None else None
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
"created_age_seconds": age_since_created,
|
"created_age_seconds": age_since_created,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue