{"record":{"id":"8b9f2b35040cf599","repo":"ZhuLinsen/daily_stock_analysis","slug":"field-name-must-be-finite","errorCode":null,"errorMessage":"{field_name} must be finite","messagePattern":"(.+?) must be finite","errorType":"exception","errorClass":"ValueError","httpStatus":400,"severity":"error","filePath":"src/services/alert_indicators.py","lineNumber":394,"sourceCode":"    raw_value = default if value is None or value == \"\" else value\n    try:\n        number = int(raw_value)\n    except (TypeError, ValueError) as exc:\n        raise ValueError(f\"invalid {field_name}: {value}\") from exc\n    if str(raw_value).strip() not in {str(number), f\"{number}.0\"}:\n        raise ValueError(f\"{field_name} must be an integer\")\n    if number < minimum or number > maximum:\n        raise ValueError(f\"{field_name} must be between {minimum} and {maximum}\")\n    return number\n\n\ndef _finite_float(value: Any, field_name: str) -> float:\n    try:\n        number = float(value)\n    except (TypeError, ValueError) as exc:\n        raise ValueError(f\"invalid {field_name}: {value}\") from exc\n    if not isfinite(number):\n        raise ValueError(f\"{field_name} must be finite\")\n    return number\n\n\ndef _float_in_range(\n    value: Any,\n    field_name: str,\n    *,\n    minimum: float,\n    maximum: float,\n) -> float:\n    number = _finite_float(value, field_name)\n    if number < minimum or number > maximum:\n        raise ValueError(f\"{field_name} must be between {minimum:g} and {maximum:g}\")\n    return number\n\n\ndef _calculate_rsi(close: pd.Series, period: int) -> pd.Series:\n    delta = close.diff()","sourceCodeStart":376,"sourceCodeEnd":412,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/src/services/alert_indicators.py#L376-L412","documentation":"Raised by _finite_float (src/services/alert_indicators.py:394) when a float field converts successfully but is not finite — i.e. float('inf'), float('-inf'), or float('nan'). math.isfinite guards indicator thresholds against values that would make every comparison ('above'/'below') meaningless or always-false.","triggerScenarios":"threshold values of Infinity/-Infinity/NaN in JSON-ish payloads (Python float('nan'), or strings \"inf\"/\"nan\"/\"NaN\" that float() accepts); NaN slipping in from a pandas computation (e.g. threshold derived from a rolling stat on sparse data) and forwarded without checking.","commonSituations":"Computing thresholds from data (max/min of an empty or all-NaN series yields NaN); JSON serializers that emit Infinity for unbounded values; user typing 'inf' in a numeric field.","solutions":["Compute thresholds defensively: use a fallback constant when the derived stat is NaN (check pd.notna / math.isfinite first).","Reject or clamp non-finite values at the API boundary before persistence.","Never let float('inf') through as an 'unbounded' sentinel — pick a large finite bound instead."],"exampleFix":"# before\nthreshold = df['close'].max()  # NaN when df is empty\nparams = {'period': 14, 'threshold': threshold}\n\n# after\nraw = df['close'].max()\nthreshold = raw if pd.notna(raw) and math.isfinite(raw) else 100.0\nparams = {'period': 14, 'threshold': threshold}","handlingStrategy":"validation","validationCode":"import math\nt = params.get('threshold')\ntry:\n    t = float(t)\nexcept (TypeError, ValueError):\n    raise ValueError('threshold must be numeric')\nif not math.isfinite(t):\n    raise ValueError('threshold must be finite (NaN/inf not allowed)')\nparams['threshold'] = t","typeGuard":"import math\ndef is_finite_number(v) -> bool:\n    try:\n        return math.isfinite(float(v))\n    except (TypeError, ValueError):\n        return False","tryCatchPattern":"try:\n    normalize_indicator_parameters(alert_type, params)\nexcept ValueError as e:\n    if 'must be finite' in str(e):\n        return bad_request('threshold must be a finite number')\n    raise","preventionTips":["Check pd.notna()/math.isfinite on any data-derived threshold before persisting it.","Use large finite bounds (1e9) instead of float('inf') sentinels.","Disallow NaN/Infinity at JSON parse settings (Python json.loads accepts them by default — reject explicitly)."],"tags":["validation","numeric","nan","alerts"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}