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
- Read the exact allowed tokens from the error message (it lists them sorted) and resend with an exact match including case.
- Lowercase/normalize the value client-side before the call where the enum is lowercase.
- Bind frontend dropdowns to the enum values, not localized labels.
- 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
- Enums are exact-match after strip only — match case exactly; lowercase client-side for lowercase enums.
- Bind UI dropdowns to enum values, not display labels.
- Validate LLM-produced enum fields in the extractor and retry/fallback on mismatch.
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
- market must be one of cn, hk, us, jp, kr, tw
- action must be one of buy/add/hold/reduce/sell/watch/avoid/a
- Desktop backend origin is invalid
- Desktop window is unavailable
- Desktop share image capture returned an empty image
AI-assisted analysis of ZhuLinsen/daily_stock_analysis@5159bd72e8 (2026-08-15).
Data as JSON: /api/errors/4035a71370a7c54e.
Report an issue: GitHub.