{"record":{"id":"107723346b503690","repo":"ZhuLinsen/daily_stock_analysis","slug":"invalid-direction-direction","errorCode":null,"errorMessage":"invalid direction: {direction}","messagePattern":"invalid direction: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/agent/events.py","lineNumber":497,"sourceCode":"    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:\n            raise ValueError(f\"invalid change_pct: {rule.get('change_pct')}\") from exc\n        if change_pct <= 0:\n            raise ValueError(\"change_pct must be > 0\")\n    elif alert_type == AlertType.VOLUME_SPIKE:\n        try:","sourceCodeStart":479,"sourceCodeEnd":515,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/src/agent/events.py#L479-L515","documentation":"Raised for PRICE_CROSS rules whose 'direction' field is not 'above' or 'below' (case-insensitive after .lower(), default 'above'). A price-cross alert needs to know whether it fires when price crosses above or below the threshold, so any other word makes the rule unexecutable.","triggerScenarios":"validate_event_alert_rule with alert_type='price_cross' and direction set to 'up', 'down', 'higher', '>', 'greater', or '' (empty string fails; only omission is safe and defaults to 'above'). Values are lowercased first, so 'Above' passes.","commonSituations":"Confusion with PRICE_CHANGE_PERCENT which uses 'up'/'down' vocabulary; LLM or user writing 'crosses above' prose; copying a PRICE_CHANGE_PERCENT rule template and only changing alert_type.","solutions":["Set direction to 'above' or 'below' for price_cross rules (or omit it to default to 'above').","When converting rule types between price_cross and price_change_percent, remap direction vocabulary ('up'->'above', 'down'->'below').","Add an enum check in the rule-building UI/LLM extractor for the direction field per alert type."],"exampleFix":"// before\nrule = {\"stock_code\": \"600519\", \"alert_type\": \"price_cross\", \"direction\": \"up\", \"price\": 1800}\n# ValueError: invalid direction: up\n\n// after\nrule = {\"stock_code\": \"600519\", \"alert_type\": \"price_cross\", \"direction\": \"above\", \"price\": 1800}\nvalidate_event_alert_rule(rule)","handlingStrategy":"validation","validationCode":"def valid_cross_direction(raw) -> bool:\n    return str(raw if raw is not None else \"above\").lower() in {\"above\", \"below\"}","typeGuard":"def is_valid_price_cross_rule(rule: dict) -> bool:\n    return (\n        str(rule.get(\"alert_type\", \"\")).lower() == \"price_cross\"\n        and valid_cross_direction(rule.get(\"direction\"))\n    )","tryCatchPattern":"try:\n    validate_event_alert_rule(rule)\nexcept ValueError as e:\n    if str(e).startswith(\"invalid direction\"):\n        rule[\"direction\"] = \"above\" if rule.get(\"direction\") in (\"up\", \"higher\") else \"below\"\n        validate_event_alert_rule(rule)","preventionTips":["Remember the vocabulary per type: price_cross=above/below, price_change_percent=up/down.","When converting rule types, remap direction too.","Omit direction to accept the default instead of guessing."],"tags":["validation","event-monitor","direction","price-cross"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}