{"record":{"id":"2a5e1e346d0fd045","repo":"ZhuLinsen/daily_stock_analysis","slug":"field-name-must-be-an-integer-2a5e1e","errorCode":null,"errorMessage":"{field_name} must be an integer","messagePattern":"(.+?) must be an integer","errorType":"validation","errorClass":"ValueError","httpStatus":400,"severity":"error","filePath":"src/services/decision_signal_outcome_service.py","lineNumber":631,"sourceCode":"        if horizon:\n            return [horizon]\n        return list(SUPPORTED_OUTCOME_HORIZONS.keys())\n\n    def _require_existing_signal(self, signal_id: int) -> DecisionSignalRecord:\n        signal_id_norm = self._optional_positive_int(signal_id, \"signal_id\")\n        row = self.signal_repo.get(signal_id_norm)\n        if row is None:\n            raise DecisionSignalNotFoundError(f\"Decision signal not found: {signal_id_norm}\")\n        return row\n\n    @staticmethod\n    def _optional_positive_int(value: Any, field_name: str) -> Optional[int]:\n        if value in (None, \"\"):\n            return None\n        try:\n            number = int(value)\n        except (TypeError, ValueError) as exc:\n            raise ValueError(f\"{field_name} must be an integer\") from exc\n        if number <= 0:\n            raise ValueError(f\"{field_name} must be positive\")\n        return number\n\n    @staticmethod\n    def _normalize_enum(value: Any, allowed: Iterable[str], field_name: str) -> str:\n        text = str(value or \"\").strip()\n        allowed_set = set(allowed)\n        if text not in allowed_set:\n            allowed_text = \", \".join(sorted(allowed_set))\n            raise ValueError(f\"{field_name} must be one of {allowed_text}\")\n        return text\n\n    @classmethod\n    def _normalize_optional_enum(cls, value: Any, allowed: Iterable[str], field_name: str) -> Optional[str]:\n        if value in (None, \"\"):\n            return None\n        return cls._normalize_enum(value, allowed, field_name)","sourceCodeStart":613,"sourceCodeEnd":649,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/src/services/decision_signal_outcome_service.py#L613-L649","documentation":"_optional_positive_int converts an optional numeric field with int(value); if value is neither None/'' nor int-convertible (e.g. a non-numeric string, a dict, a list), TypeError/ValueError is caught and re-raised as ValueError '<field> must be an integer'. It validates API query/path params like signal_id before they reach the repository.","triggerScenarios":"Calling decision-signal endpoints/services with signal_id=\"abc\", signal_id=[1], or a float string like \"1.5\" — int(\"1.5\") raises ValueError; passing an object with no __int__ raises TypeError.","commonSituations":"String IDs from URL paths or query strings not validated upstream; JSON payloads where the field is typed as string; frontend sending \"1.0\".","solutions":["Send the field as a plain integer (or integer string like \"42\").","Coerce and validate on the client before the call: use int() and confirm it is whole.","For float strings, convert via int(float(value)) first if fractional input is legitimate."],"exampleFix":"# before\nresult = service._optional_positive_int(\"12.5\", \"signal_id\")\n\n# after\nresult = service._optional_positive_int(int(float(\"12.5\")), \"signal_id\")  # 12","handlingStrategy":"type-guard","validationCode":"def toOptionalInt(value) -> int | None:\n    if value in (None, \"\"):\n        return None\n    try:\n        return int(str(value).strip())\n    except (TypeError, ValueError):\n        raise HTTPException(400, f\"{value!r} is not an integer\")\n\nsignal_id = toOptionalInt(raw_id)  # before calling the service","typeGuard":"def isIntLike(value: object) -> bool:\n    if value in (None, \"\"):\n        return True\n    try:\n        int(value)\n        return True\n    except (TypeError, ValueError):\n        return False","tryCatchPattern":"try:\n    result = service.evaluate_outcomes(signal_id=raw_id)\nexcept ValueError as exc:\n    if \"must be an integer\" in str(exc):\n        return JSONResponse(status_code=400, content={\"error\": \"invalid_params\", \"message\": str(exc)})\n    raise","preventionTips":["Declare id fields as int in Pydantic request models so FastAPI validates first.","Avoid float strings ('1.5') for ids; convert via int(float(x)) only when fractions are meaningful.","Keep URL-path ids as plain integers."],"tags":["decision-signal","validation","parameter-validation"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}