ZhuLinsen/daily_stock_analysis · error · ValueError

{field_name} must be one of {allowed_text}

Error message

{field_name} must be one of {allowed_text}

What it means

Generic enum validator ValueError from DecisionSignalService._normalize_enum (src/services/decision_signal_service.py:1164): used for fields like status, market_phase, horizon, decision_profile. The raw value is str()-ed, stripped (NOT lowercased), and must be an exact member of the allowed frozenset; otherwise '{field_name} must be one of {sorted list}' is raised. Case errors survive because only whitespace is trimmed.

Source

Thrown at src/services/decision_signal_service.py:1164

    @staticmethod
    def _normalize_action(value: Any) -> str:
        action = str(value or "").strip().lower()
        if not action or action not in DECISION_ACTIONS:
            raise ValueError("action must be one of buy/add/hold/reduce/sell/watch/avoid/alert")
        return action

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

    @staticmethod
    def _normalize_enum(value: Any, allowed: frozenset[str], field_name: str) -> str:
        text = str(value or "").strip()
        if text not in allowed:
            allowed_text = ", ".join(sorted(allowed))
            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")

View on GitHub (pinned to 5159bd72e8)

Solutions

  1. Read the exact allowed tokens from the error message (it lists them sorted) and resend with an exact match including case.
  2. Lowercase/normalize the value client-side before the call where the enum is lowercase.
  3. Bind frontend dropdowns to the enum values, not localized labels.
  4. For LLM-produced fields, validate against the enum in the extractor and re-ask/fallback rather than persisting raw text.

Example fix

# before
service.update_signal_status(signal_id, status="Active")  # not lowercased → ValueError: status must be one of ...

# after
status = "Active".strip().lower()
service.update_signal_status(signal_id, status=status)
Defensive patterns

Strategy: type-guard

Validate before calling

def check_enum(value, allowed: frozenset, field: str):
    text = str(value or '').strip()
    if text not in allowed:
        raise ValueError(f'{field} must be one of {sorted(allowed)}')
check_enum(payload.get('status'), SIGNAL_STATUSES, 'status')

Type guard

def make_enum_guard(allowed: frozenset):
    return lambda v: str(v or '').strip() in allowed
is_valid_horizon = make_enum_guard(HORIZONS)

Prevention

When it happens

Trigger: Payloads with market_phase: 'Bull' (must be the exact token, e.g. 'bull'... check MARKET_PHASES spelling), status: 'Active' (capital A fails — only stripped, not lowercased), horizon: 'short term' vs 'short_term', decision_profile: 'aggressive ' (ok) vs 'AGGRESSIVE' (fails). Which enum is hit depends on the field being normalized.

Common situations: API consumers assuming case-insensitive enums; frontend select components exporting labels instead of values; prompt templates listing display names ('Short-term') that get persisted verbatim; enum extension without notifying clients.

Related errors


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