ZhuLinsen/daily_stock_analysis · error · ValueError

trigger_source is required

Error message

trigger_source is required

What it means

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'.

Source

Thrown at src/services/decision_signal_service.py:1182

            raise ValueError(f"{field_name} must be one of {allowed_text}")
        return text

    @classmethod
    def _normalize_optional_enum(
        cls,
        value: Any,
        allowed: frozenset[str],
        field_name: str,
    ) -> Optional[str]:
        if value in (None, ""):
            return None
        return cls._normalize_enum(value, allowed, field_name)

    @staticmethod
    def _normalize_trigger_source(value: Any) -> str:
        text = DecisionSignalService._public_text(value, "trigger_source", max_length=64, required=True)
        if not text:
            raise ValueError("trigger_source is required")
        return text

    @classmethod
    def _normalize_optional_trigger_source(cls, value: Any) -> Optional[str]:
        if value in (None, ""):
            return None
        return cls._normalize_trigger_source(value)

    @staticmethod
    def _optional_text(value: Any, field_name: str, *, max_length: int) -> Optional[str]:
        if value is None:
            return None
        text = str(value).strip()
        if not text:
            return None
        if len(text) > max_length:
            raise ValueError(f"{field_name} must be at most {max_length} characters")
        return text

View on GitHub (pinned to 5159bd72e8)

Solutions

  1. Set an explicit non-empty trigger_source per producer, e.g. 'scheduled_scan', 'manual', 'price_alert' (≤64 chars).
  2. Default it at the call site: payload.setdefault('trigger_source', 'manual') before create_signal.
  3. If sanitization is eating the value, send plain text without markup/control characters.
  4. Add it to the client-side required-field validation.

Example fix

# before
service.create_signal({"stock_code": "600519", "market": "cn", "action": "buy", "trigger_source": ""})  # ValueError

# after
service.create_signal({"stock_code": "600519", "market": "cn", "action": "buy", "trigger_source": "scheduled_scan"})
Defensive patterns

Strategy: validation

Validate before calling

trigger = str(payload.get('trigger_source') or '').strip()
if not trigger:
    payload['trigger_source'] = 'manual'  # or reject early with a clear client-side error

Type guard

def has_trigger_source(payload: dict) -> bool:
    return bool(str(payload.get('trigger_source') or '').strip())

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of ZhuLinsen/daily_stock_analysis@5159bd72e8 (2026-08-15). Data as JSON: /api/errors/5656436fc7f0a95c. Report an issue: GitHub.