{"record":{"id":"d804d607f577c204","repo":"ZhuLinsen/daily_stock_analysis","slug":"field-name-must-be-positive","errorCode":null,"errorMessage":"{field_name} must be positive","messagePattern":"(.+?) must be positive","errorType":"validation","errorClass":"ValueError","httpStatus":400,"severity":"error","filePath":"src/services/decision_signal_outcome_service.py","lineNumber":633,"sourceCode":"        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)\n\n    def _normalize_horizons(self, values: Optional[List[str]]) -> Optional[List[str]]:","sourceCodeStart":615,"sourceCodeEnd":651,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/src/services/decision_signal_outcome_service.py#L615-L651","documentation":"After int conversion succeeds, _optional_positive_int rejects values <= 0 with ValueError '<field> must be positive'. Zero and negative IDs are structurally invalid for database primary keys, so the service fails before querying.","triggerScenarios":"Passing signal_id=0, signal_id=-5, or a string '-1' to decision-signal service methods or their API endpoints.","commonSituations":"Default/uninitialized numeric values (0 as sentinel) forwarded from client code; parsing errors producing -1; form defaults of 0 submitted without user input.","solutions":["Use a real positive id from list_signals / the list endpoint.","Treat 0 or -1 in your code as 'not set' and pass None instead.","Guard UI inputs: disable submit until a genuine id is selected."],"exampleFix":"# before\noutcome = service.evaluate_outcomes(signal_id=0)\n\n# after\noutcome = service.evaluate_outcomes(signal_id=None, stock_codes=[\"600519\"])  # batch mode","handlingStrategy":"validation","validationCode":"def positiveOrNone(value) -> int | None:\n    if value in (None, \"\", 0, -1):\n        return None\n    n = int(value)\n    if n <= 0:\n        return None  # or raise in UI before the call\n    return n\n\nsignal_id = positiveOrNone(raw_id)","typeGuard":"def isPositiveId(value: object) -> bool:\n    return isinstance(value, int) and not isinstance(value, bool) and value > 0","tryCatchPattern":"try:\n    outcome = service.evaluate_outcomes(signal_id=signal_id)\nexcept ValueError as exc:\n    if \"must be positive\" in str(exc):\n        return JSONResponse(status_code=400, content={\"error\": \"invalid_params\", \"message\": str(exc)})\n    raise","preventionTips":["Never use 0 or -1 as 'unset' sentinels across the API boundary — use None/omitted field.","Validate id > 0 in form handlers before submit.","Use ge=1 constraints on query/path params in FastAPI signatures."],"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"}