{"record":{"id":"29992bfa09cc1684","repo":"ZhuLinsen/daily_stock_analysis","slug":"field-name-must-be-an-integer","errorCode":null,"errorMessage":"{field_name} must be an integer","messagePattern":"(.+?) must be an integer","errorType":"exception","errorClass":"ValueError","httpStatus":400,"severity":"error","filePath":"src/services/alert_indicators.py","lineNumber":382,"sourceCode":"        )\n    return params\n\n\ndef _direction(value: Any, allowed: frozenset[str], *, default: str) -> str:\n    direction = str(value or default).strip().lower()\n    if direction not in allowed:\n        raise ValueError(f\"invalid direction: {direction}\")\n    return direction\n\n\ndef _int_in_range(value: Any, field_name: str, *, default: int, minimum: int = 2, maximum: int = 250) -> int:\n    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,","sourceCodeStart":364,"sourceCodeEnd":400,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/src/services/alert_indicators.py#L364-L400","documentation":"Raised by _int_in_range (src/services/alert_indicators.py:382): the value converted to int successfully, but its canonical string form does not equal str(number) or f'{number}.0', i.e. it is a non-integer-shaped value. This deliberately rejects inputs like \"12.5\", 3.7, or \"1e2\" that int() would otherwise truncate, preventing silently accepted-but-wrong periods.","triggerScenarios":"parameters like {\"window\": 20.5}, {\"period\": \"9.25\"}, {\"fast_period\": 12.5}. Accepted shapes are exact ints (20), int strings (\"20\"), and float-with-.0 (20.0, \"20.0\") — because int(20.9) is 20, the guard catches the fractional part.","commonSituations":"Sliders or step-less inputs emitting fractional values; unit conversion producing decimals (e.g. 26 * 0.5); spreadsheets storing numbers as decimals (\"12,0\" in EU locale parses oddly).","solutions":["Round or floor the value deliberately on the client before submitting: Math.round(value).","Use a stepper input with step=1 for period fields.","If a decimal period ever seems meaningful, the indicator does not support it — pick an integer design instead."],"exampleFix":"// before\n{ \"alert_type\": \"ma_price_cross\", \"parameters\": { \"window\": 20.5 } }\n\n// after\n{ \"alert_type\": \"ma_price_cross\", \"parameters\": { \"window\": Math.round(20.5) /* 21 */ } }","handlingStrategy":"validation","validationCode":"def to_int_field(v):\n    if v is None or v == '':\n        return None  # default\n    f = float(v)\n    if f != int(f):\n        raise ValueError('fractional period not allowed')\n    return int(f)\nparams = {k: (to_int_field(v) if k in INT_FIELDS else v) for k, v in params.items()}","typeGuard":"def is_integer_shaped(v) -> bool:\n    if v is None or v == '':\n        return True\n    try:\n        s = str(v).strip(); n = int(v)\n    except (TypeError, ValueError):\n        return False\n    return s in {str(n), f'{n}.0'}","tryCatchPattern":"try:\n    normalize_indicator_parameters(alert_type, params)\nexcept ValueError as e:\n    if 'must be an integer' in str(e):\n        for k in INT_FIELDS & params.keys():\n            params[k] = int(float(params[k])) if str(params[k]).replace('.','',1).isdigit() else params[k]\n        normalize_indicator_parameters(alert_type, params)\n    else:\n        raise","preventionTips":["Round deliberately (Math.round) before submit instead of relying on server truncation — the server refuses truncation.","Use step=1 steppers in forms for period fields.","Watch for unit conversions that produce fractional periods."],"tags":["validation","numeric","alerts","parameters"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}