{"record":{"id":"44652b7c48788f28","repo":"ZhuLinsen/daily_stock_analysis","slug":"field-name-must-be-one-of-allowed-text","errorCode":null,"errorMessage":"{field_name} must be one of {allowed_text}","messagePattern":"(.+?) must be one of (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":400,"severity":"error","filePath":"src/services/decision_signal_outcome_service.py","lineNumber":642,"sourceCode":"    @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]]:\n        if not values:\n            return None\n        out: List[str] = []\n        for value in values:\n            horizon = self._normalize_enum(value, HORIZONS, \"horizon\")\n            if horizon not in out:\n                out.append(horizon)\n        return out\n","sourceCodeStart":624,"sourceCodeEnd":660,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/src/services/decision_signal_outcome_service.py#L624-L660","documentation":"_normalize_enum stringifies, strips, and checks membership in the allowed set; failures raise ValueError '<field> must be one of <sorted allowed list>'. It validates enum-ish params (action, market, source_type, statuses, horizon) on decision-signal outcome APIs.","triggerScenarios":"Passing action=\"buy\" when the allowed set uses \"buy\"/\"sell\"/\"hold\" variants that differ (e.g. \"BUY \" ok, \"accumulate\" not), market=\"cn\" vs allowed \"ashare\"/\"hk\"/\"us\", or horizon=\"1mo\" when only defined horizon keys are accepted.","commonSituations":"Client enum drift after a backend adds/renames allowed values; casing/whitespace differences usually survive normalization but synonyms do not; hardcoded enums in frontend out of sync with SUPPORTED_OUTCOME_HORIZONS / decision action sets.","solutions":["Read the error message: it lists the exact allowed values sorted; use one verbatim.","Source allowed values from the backend (SUPPORTED_OUTCOME_HORIZONS, normalize_decision_action's accepted set) instead of hardcoding.","Trim/case-normalize user input before sending, but do not invent synonyms."],"exampleFix":"# before\nservice.evaluate_outcomes(action=\"accumulate\", market=\"china\")\n\n# after\nservice.evaluate_outcomes(action=\"buy\", market=\"ashare\")","handlingStrategy":"type-guard","validationCode":"ALLOWED_ACTIONS = {\"buy\", \"sell\", \"hold\"}  # keep in sync with backend\naction = (raw_action or \"\").strip().lower()\nif action and action not in ALLOWED_ACTIONS:\n    raise HTTPException(400, f\"action must be one of {sorted(ALLOWED_ACTIONS)}\")\nservice.evaluate_outcomes(action=action or None)","typeGuard":"def isAllowedEnum(value: object, allowed: set[str]) -> bool:\n    text = str(value or \"\").strip()\n    return not text or text in allowed","tryCatchPattern":"try:\n    result = service.evaluate_outcomes(action=action, market=market)\nexcept ValueError as exc:\n    if \"must be one of\" in str(exc):\n        return JSONResponse(status_code=400, content={\"error\": \"invalid_enum\", \"message\": str(exc)})\n    raise","preventionTips":["Derive allowed values from backend constants (SUPPORTED_OUTCOME_HORIZONS etc.), not hardcoded copies.","Use select inputs, not free text, for enum fields.","Add contract tests that fail when backend enums change."],"tags":["decision-signal","enum-validation","parameter-validation"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}