{"record":{"id":"5656436fc7f0a95c","repo":"ZhuLinsen/daily_stock_analysis","slug":"trigger-source-is-required","errorCode":null,"errorMessage":"trigger_source is required","messagePattern":"trigger_source is required","errorType":"validation","errorClass":"ValueError","httpStatus":400,"severity":"error","filePath":"src/services/decision_signal_service.py","lineNumber":1182,"sourceCode":"            raise ValueError(f\"{field_name} must be one of {allowed_text}\")\n        return text\n\n    @classmethod\n    def _normalize_optional_enum(\n        cls,\n        value: Any,\n        allowed: frozenset[str],\n        field_name: str,\n    ) -> Optional[str]:\n        if value in (None, \"\"):\n            return None\n        return cls._normalize_enum(value, allowed, field_name)\n\n    @staticmethod\n    def _normalize_trigger_source(value: Any) -> str:\n        text = DecisionSignalService._public_text(value, \"trigger_source\", max_length=64, required=True)\n        if not text:\n            raise ValueError(\"trigger_source is required\")\n        return text\n\n    @classmethod\n    def _normalize_optional_trigger_source(cls, value: Any) -> Optional[str]:\n        if value in (None, \"\"):\n            return None\n        return cls._normalize_trigger_source(value)\n\n    @staticmethod\n    def _optional_text(value: Any, field_name: str, *, max_length: int) -> Optional[str]:\n        if value is None:\n            return None\n        text = str(value).strip()\n        if not text:\n            return None\n        if len(text) > max_length:\n            raise ValueError(f\"{field_name} must be at most {max_length} characters\")\n        return text","sourceCodeStart":1164,"sourceCodeEnd":1200,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/src/services/decision_signal_service.py#L1164-L1200","documentation":"ValueError from DecisionSignalService._normalize_trigger_source (src/services/decision_signal_service.py:1182): after running the value through _public_text (sanitize + max_length=64), the text is empty, so trigger_source — a required field — is effectively missing. Empty-but-present input, whitespace-only input, or input that sanitization reduces to empty all land here; None input fails earlier inside _public_text with 'trigger_source is required'.","triggerScenarios":"create-signal payloads with trigger_source: '', '   ', or content consisting only of characters stripped by sanitize_decision_signal_text (e.g. control chars/markup). Bulk pipelines templating trigger_source from a variable that is occasionally blank.","commonSituations":"Optional-looking field actually required; ingestion code using a per-source constant that a refactor renamed to None/''; sanitization stripping HTML wrappers leaving nothing; test fixtures omitting the field.","solutions":["Set an explicit non-empty trigger_source per producer, e.g. 'scheduled_scan', 'manual', 'price_alert' (≤64 chars).","Default it at the call site: payload.setdefault('trigger_source', 'manual') before create_signal.","If sanitization is eating the value, send plain text without markup/control characters.","Add it to the client-side required-field validation."],"exampleFix":"# before\nservice.create_signal({\"stock_code\": \"600519\", \"market\": \"cn\", \"action\": \"buy\", \"trigger_source\": \"\"})  # ValueError\n\n# after\nservice.create_signal({\"stock_code\": \"600519\", \"market\": \"cn\", \"action\": \"buy\", \"trigger_source\": \"scheduled_scan\"})","handlingStrategy":"validation","validationCode":"trigger = str(payload.get('trigger_source') or '').strip()\nif not trigger:\n    payload['trigger_source'] = 'manual'  # or reject early with a clear client-side error","typeGuard":"def has_trigger_source(payload: dict) -> bool:\n    return bool(str(payload.get('trigger_source') or '').strip())","tryCatchPattern":null,"preventionTips":["Use short fixed literals per producer ('scheduled_scan', 'manual') so trigger_source is never blank.","Don't build payloads with falsy-filtering dict comprehensions; use `is not None`.","Include trigger_source in client-side required-field checks."],"tags":["decision-signal","validation","required-field","trigger-source"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}