{"record":{"id":"3d964989ca16e9e7","repo":"ZhuLinsen/daily_stock_analysis","slug":"invalid-ttl-hours-ttl-hours","errorCode":null,"errorMessage":"invalid ttl_hours: {ttl_hours}","messagePattern":"invalid ttl_hours: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/agent/events.py","lineNumber":490,"sourceCode":"    try:\n        alert_type = AlertType(rule.get(\"alert_type\", \"\"))\n    except ValueError as exc:\n        raise ValueError(f\"invalid alert_type: {rule.get('alert_type')}\") from exc\n    _ensure_runtime_supported_alert_type(alert_type)\n\n    status = rule.get(\"status\")\n    if status is not None:\n        try:\n            AlertStatus(status)\n        except ValueError as exc:\n            raise ValueError(f\"invalid status: {status}\") from exc\n\n    ttl_hours = rule.get(\"ttl_hours\")\n    if ttl_hours is not None:\n        try:\n            ttl_value = float(ttl_hours)\n        except (TypeError, ValueError) as exc:\n            raise ValueError(f\"invalid ttl_hours: {ttl_hours}\") from exc\n        if ttl_value <= 0:\n            raise ValueError(\"ttl_hours must be > 0\")\n\n    if alert_type == AlertType.PRICE_CROSS:\n        direction = str(rule.get(\"direction\", \"above\")).lower()\n        if direction not in {\"above\", \"below\"}:\n            raise ValueError(f\"invalid direction: {direction}\")\n        try:\n            price = float(rule.get(\"price\"))\n        except (TypeError, ValueError) as exc:\n            raise ValueError(f\"invalid price: {rule.get('price')}\") from exc\n        if price <= 0:\n            raise ValueError(\"price must be > 0\")\n    elif alert_type == AlertType.PRICE_CHANGE_PERCENT:\n        direction = str(rule.get(\"direction\", \"up\")).lower()\n        if direction not in {\"up\", \"down\"}:\n            raise ValueError(f\"invalid direction: {direction}\")\n        try:","sourceCodeStart":472,"sourceCodeEnd":508,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/src/agent/events.py#L472-L508","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Provide ttl_hours as a number (or numeric string) of hours, e.g. 24 or '24.0'.","Convert human units (days/minutes) to hours before building the rule dict.","Omit the key if you want the default TTL behavior."],"exampleFix":"// before\nrule = {\"stock_code\": \"AAPL\", \"alert_type\": \"volume_spike\", \"ttl_hours\": \"24h\"}\n# ValueError: invalid ttl_hours: 24h\n\n// after\nrule = {\"stock_code\": \"AAPL\", \"alert_type\": \"volume_spike\", \"ttl_hours\": 24}\nvalidate_event_alert_rule(rule)","handlingStrategy":"validation","validationCode":"def valid_ttl(raw) -> bool:\n    if raw is None:\n        return True\n    try:\n        float(raw)\n        return True\n    except (TypeError, ValueError):\n        return False","typeGuard":"def has_numeric_ttl(rule: dict) -> bool:\n    return valid_ttl(rule.get(\"ttl_hours\"))","tryCatchPattern":"try:\n    validate_event_alert_rule(rule)\nexcept ValueError as e:\n    if str(e).startswith(\"invalid ttl_hours\"):\n        rule.pop(\"ttl_hours\", None) or reject(rule)","preventionTips":["Author ttl_hours as plain numbers, never strings with units.","Convert '1 day' style input to hours before building the rule.","Leave the key absent rather than sending null-ish strings."],"tags":["validation","event-monitor","ttl","type-coercion"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}