{"record":{"id":"6ce3ab2b546567ea","repo":"ZhuLinsen/daily_stock_analysis","slug":"unsupported-technical-alert-type-alert-type","errorCode":null,"errorMessage":"unsupported technical alert_type: {alert_type}","messagePattern":"unsupported technical alert_type: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":400,"severity":"error","filePath":"src/services/alert_indicators.py","lineNumber":88,"sourceCode":"            \"signal_period\": _int_in_range(parameters.get(\"signal_period\"), \"signal_period\", default=9),\n        }\n        return _ensure_required_bars_fetchable(alert_type, normalized)\n    if alert_type == \"kdj_cross\":\n        normalized = {\n            \"direction\": _direction(parameters.get(\"direction\"), CROSS_DIRECTIONS, default=\"bullish_cross\"),\n            \"period\": _int_in_range(parameters.get(\"period\"), \"period\", default=9),\n            \"k_period\": _int_in_range(parameters.get(\"k_period\"), \"k_period\", default=3),\n            \"d_period\": _int_in_range(parameters.get(\"d_period\"), \"d_period\", default=3),\n        }\n        return _ensure_required_bars_fetchable(alert_type, normalized)\n    if alert_type == \"cci_threshold\":\n        normalized = {\n            \"direction\": _direction(parameters.get(\"direction\"), ABOVE_BELOW_DIRECTIONS, default=\"above\"),\n            \"period\": _int_in_range(parameters.get(\"period\"), \"period\", default=14),\n            \"threshold\": _finite_float(parameters.get(\"threshold\"), \"threshold\"),\n        }\n        return _ensure_required_bars_fetchable(alert_type, normalized)\n    raise ValueError(f\"unsupported technical alert_type: {alert_type}\")\n\n\ndef compute_required_bars(alert_type: str, params: Dict[str, Any]) -> int:\n    if alert_type == \"ma_price_cross\":\n        return int(params[\"window\"]) + 1\n    if alert_type == \"rsi_threshold\":\n        return int(params[\"period\"]) + 1\n    if alert_type == \"macd_cross\":\n        return int(params[\"slow_period\"]) + int(params[\"signal_period\"]) + 1\n    if alert_type == \"kdj_cross\":\n        return int(params[\"period\"]) + int(params[\"k_period\"]) + int(params[\"d_period\"]) + 1\n    if alert_type == \"cci_threshold\":\n        return int(params[\"period\"]) + 1\n    raise ValueError(f\"unsupported technical alert_type: {alert_type}\")\n\n\ndef compute_requested_days(alert_type: str, params: Dict[str, Any]) -> int:\n    required_bars = compute_required_bars(alert_type, params)","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/src/services/alert_indicators.py#L70-L106","documentation":"Raised at the end of normalize_indicator_parameters (src/services/alert_indicators.py:88) when alert_type does not match any of the five supported technical indicators: ma_price_cross, rsi_threshold, macd_cross, kdj_cross, cci_threshold. It is an exhaustive-match error: any other string falls through all if-branches to this raise, with the offending alert_type interpolated into the message.","triggerScenarios":"Creating/updating a technical alert with a typo (\"macdcross\", \"rsi\", \"cci_threashold\"), a non-technical type routed to the wrong normalizer (e.g. \"price_above\" reaching the indicator path), or a new indicator name added elsewhere but not implemented here.","commonSituations":"Frontend dropdown values out of sync with backend enum; copy-paste from docs with a renamed indicator; upstream code adding an alert_type without extending this dispatcher (and compute_required_bars which must stay in sync).","solutions":["Use one of the five supported alert_type values exactly: ma_price_cross, rsi_threshold, macd_cross, kdj_cross, cci_threshold.","Check for typos/whitespace/case in the submitted alert_type string.","If you need a new indicator, implement its branch in both normalize_indicator_parameters and compute_required_bars, plus an evaluator, before accepting the type at the API.","Validate alert_type against the supported set at the API schema layer to return 422 instead of a 500-flavored ValueError."],"exampleFix":"// before\n{ \"alert_type\": \"macdcross\", \"parameters\": { \"fast_period\": 12, \"slow_period\": 26 } }\n\n// after\n{ \"alert_type\": \"macd_cross\", \"parameters\": { \"fast_period\": 12, \"slow_period\": 26 } }","handlingStrategy":"type-guard","validationCode":"SUPPORTED = {'ma_price_cross', 'rsi_threshold', 'macd_cross', 'kdj_cross', 'cci_threshold'}\nalert_type = (alert_type or '').strip()\nif alert_type not in SUPPORTED:\n    raise ValueError(f'alert_type must be one of {sorted(SUPPORTED)}')","typeGuard":"SUPPORTED_ALERT_TYPES = frozenset({'ma_price_cross','rsi_threshold','macd_cross','kdj_cross','cci_threshold'})\ndef is_supported_alert_type(t: str) -> bool:\n    return isinstance(t, str) and t in SUPPORTED_ALERT_TYPES","tryCatchPattern":"try:\n    normalize_indicator_parameters(alert_type, params)\nexcept ValueError as e:\n    if 'unsupported technical alert_type' in str(e):\n        return bad_request(f'unknown alert_type: {alert_type!r}')\n    raise","preventionTips":["Drive the frontend dropdown from the same constant the backend validates with.","When adding an indicator, update normalize_indicator_parameters AND compute_required_bars in the same change.","Strip/validate alert_type at the schema layer to fail with 422 before business logic."],"tags":["validation","alerts","dispatch","enum"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}