{"record":{"id":"96f2c1c99a4b59ec","repo":"ZhuLinsen/daily_stock_analysis","slug":"ttl-hours-must-be-0","errorCode":null,"errorMessage":"ttl_hours must be > 0","messagePattern":"ttl_hours must be > 0","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/agent/events.py","lineNumber":492,"sourceCode":"    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:\n            change_pct = float(rule.get(\"change_pct\"))\n        except (TypeError, ValueError) as exc:","sourceCodeStart":474,"sourceCodeEnd":510,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/src/agent/events.py#L474-L510","documentation":"Raised when 'ttl_hours' parses as a float but is zero or negative. A non-positive TTL would make a rule expire immediately or in the past, which is meaningless for an alert monitor, so validation rejects it up front rather than silently creating a dead rule.","triggerScenarios":"validate_event_alert_rule with ttl_hours = 0, -1, 0.0, '-0.5', or '-24'. NaN technically slips past float() but 0/negative are the concrete triggers; boundary values like 1e-9 pass but are dubious.","commonSituations":"UI defaulting an unset numeric input to 0; arithmetic that computes ttl_hours = expiry - now and goes negative when expiry is in the past; sentinel values like -1 reused from another config schema meaning 'infinite'.","solutions":["Set ttl_hours to a positive number of hours (e.g. 24).","If 'no expiry' was intended, check whether the runtime supports omitting ttl_hours instead of using 0/-1 sentinels.","Guard computed TTLs: clamp or reject when the computed value is <= 0 before submitting the rule."],"exampleFix":"// before\nrule = {\"stock_code\": \"AAPL\", \"alert_type\": \"volume_spike\", \"ttl_hours\": -1}\n# ValueError: ttl_hours must be > 0\n\n// after\nrule = {\"stock_code\": \"AAPL\", \"alert_type\": \"volume_spike\", \"ttl_hours\": 24}\nvalidate_event_alert_rule(rule)","handlingStrategy":"validation","validationCode":"def positive_ttl(raw) -> bool:\n    try:\n        return raw is None or float(raw) > 0\n    except (TypeError, ValueError):\n        return False","typeGuard":"def has_positive_ttl(rule: dict) -> bool:\n    return positive_ttl(rule.get(\"ttl_hours\"))","tryCatchPattern":"try:\n    validate_event_alert_rule(rule)\nexcept ValueError as e:\n    if \"ttl_hours must be\" in str(e):\n        rule[\"ttl_hours\"] = DEFAULT_TTL_HOURS  # or reject","preventionTips":["Never use 0/-1 sentinels for 'no expiry'.","Sanity-check computed TTLs (expiry - now) before submission.","Default UI numeric inputs to a real TTL like 24, not 0."],"tags":["validation","event-monitor","ttl","range-check"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}