ZhuLinsen/daily_stock_analysis · error · ValueError

invalid ttl_hours: {ttl_hours}

Error message

invalid ttl_hours: {ttl_hours}

What it means

Raised when an optional 'ttl_hours' field on an alert rule is present but cannot be coerced to float (TypeError for None/list/dict, ValueError for non-numeric strings). ttl_hours controls how long the rule lives before expiring; it is optional, but a present value must be numeric so remove_expired and TTL arithmetic work.

Source

Thrown at src/agent/events.py:490

    try:
        alert_type = AlertType(rule.get("alert_type", ""))
    except ValueError as exc:
        raise ValueError(f"invalid alert_type: {rule.get('alert_type')}") from exc
    _ensure_runtime_supported_alert_type(alert_type)

    status = rule.get("status")
    if status is not None:
        try:
            AlertStatus(status)
        except ValueError as exc:
            raise ValueError(f"invalid status: {status}") from exc

    ttl_hours = rule.get("ttl_hours")
    if ttl_hours is not None:
        try:
            ttl_value = float(ttl_hours)
        except (TypeError, ValueError) as exc:
            raise ValueError(f"invalid ttl_hours: {ttl_hours}") from exc
        if ttl_value <= 0:
            raise ValueError("ttl_hours must be > 0")

    if alert_type == AlertType.PRICE_CROSS:
        direction = str(rule.get("direction", "above")).lower()
        if direction not in {"above", "below"}:
            raise ValueError(f"invalid direction: {direction}")
        try:
            price = float(rule.get("price"))
        except (TypeError, ValueError) as exc:
            raise ValueError(f"invalid price: {rule.get('price')}") from exc
        if price <= 0:
            raise ValueError("price must be > 0")
    elif alert_type == AlertType.PRICE_CHANGE_PERCENT:
        direction = str(rule.get("direction", "up")).lower()
        if direction not in {"up", "down"}:
            raise ValueError(f"invalid direction: {direction}")
        try:

View on GitHub (pinned to 5159bd72e8)

Solutions

  1. Provide ttl_hours as a number (or numeric string) of hours, e.g. 24 or '24.0'.
  2. Convert human units (days/minutes) to hours before building the rule dict.
  3. Omit the key if you want the default TTL behavior.

Example fix

// before
rule = {"stock_code": "AAPL", "alert_type": "volume_spike", "ttl_hours": "24h"}
# ValueError: invalid ttl_hours: 24h

// after
rule = {"stock_code": "AAPL", "alert_type": "volume_spike", "ttl_hours": 24}
validate_event_alert_rule(rule)
Defensive patterns

Strategy: validation

Validate before calling

def valid_ttl(raw) -> bool:
    if raw is None:
        return True
    try:
        float(raw)
        return True
    except (TypeError, ValueError):
        return False

Type guard

def has_numeric_ttl(rule: dict) -> bool:
    return valid_ttl(rule.get("ttl_hours"))

Try / catch

try:
    validate_event_alert_rule(rule)
except ValueError as e:
    if str(e).startswith("invalid ttl_hours"):
        rule.pop("ttl_hours", None) or reject(rule)

Prevention

When it happens

Trigger: validate_event_alert_rule with rule['ttl_hours'] = 'forever', None, [24], {}, or a string like '24h' (float('24h') raises). Numeric strings such as '24' or 24.0 are fine. Omitting the key skips the check.

Common situations: Rules authored with human units ('1 day', '24h') instead of hours as a number; JSON round-trips where null replaced an omitted key is safe here, but empty string '' fails; LLM-generated TTLs in prose form.

Related errors


AI-assisted analysis of ZhuLinsen/daily_stock_analysis@5159bd72e8 (2026-08-15). Data as JSON: /api/errors/3d964989ca16e9e7. Report an issue: GitHub.