ZhuLinsen/daily_stock_analysis · error · ValueError
{field_name} must be at most {max_length} characters
Error message
{field_name} must be at most {max_length} characters What it means
_optional_public_text sanitizes free-text fields with sanitize_decision_signal_text and enforces a per-field max_length; over-length input raises ValueError '<field> must be at most N characters'. This bounds user-visible text stored on decision-signal outcome records.
Source
Thrown at src/services/decision_signal_outcome_service.py:669
def _normalize_horizons(self, values: Optional[List[str]]) -> Optional[List[str]]:
if not values:
return None
out: List[str] = []
for value in values:
horizon = self._normalize_enum(value, HORIZONS, "horizon")
if horizon not in out:
out.append(horizon)
return out
@staticmethod
def _optional_public_text(value: Any, field_name: str, *, max_length: int) -> Optional[str]:
if value in (None, ""):
return None
text = sanitize_decision_signal_text(value)
if not text:
return None
if len(text) > max_length:
raise ValueError(f"{field_name} must be at most {max_length} characters")
return text
@staticmethod
def _serialize_outcome(row: DecisionSignalOutcomeRecord) -> Dict[str, Any]:
return {
"id": row.id,
"signal_id": row.signal_id,
"horizon": row.horizon,
"engine_version": row.engine_version,
"eval_status": row.eval_status,
"outcome": row.outcome,
"direction_expected": row.direction_expected,
"direction_correct": row.direction_correct,
"unable_reason": row.unable_reason,
"anchor_date": row.anchor_date.isoformat() if row.anchor_date else None,
"eval_window_days": row.eval_window_days,
"start_price": row.start_price,
"end_close": row.end_close,View on GitHub (pinned to 5159bd72e8)
Solutions
- Shorten the text to within the field's documented max_length.
- Truncate client-side before submit: text[:max_length].
- For long content, store it elsewhere (report/attachment) and keep only a short reference here.
Example fix
# before service.update_outcome_note(signal_id=1, note=very_long_text) # after MAX = 200 # field's max_length service.update_outcome_note(signal_id=1, note=very_long_text[:MAX])
Defensive patterns
Strategy: validation
Validate before calling
def clampText(text: str | None, max_length: int) -> str | None:
if not text:
return None
return text.strip()[:max_length]
note = clampText(raw_note, max_length=200) # match field's max_length
service.update_outcome_note(signal_id=1, note=note) Type guard
def fitsMaxLength(text: str | None, max_length: int) -> bool:
return text is None or len(str(text).strip()) <= max_length Try / catch
try:
service.update_outcome_note(signal_id=1, note=note)
except ValueError as exc:
if "must be at most" in str(exc):
return JSONResponse(status_code=400, content={"error": "text_too_long", "message": str(exc)})
raise Prevention
- Set maxlength on text inputs matching each field's max_length.
- Trim and measure after sanitization, not before — sanitization can change length.
- Store long content elsewhere and keep short references here.
When it happens
Trigger: Submitting notes/reason/metadata text fields on decision-signal outcome endpoints longer than the field's max_length (e.g. >limit characters after sanitization).
Common situations: Pasting long analyst commentary into a notes field; concatenated auto-generated text exceeding the cap; sanitization collapsing whitespace but still leaving the text over limit.
Related errors
- {field_name} must be an integer
- {field_name} must be positive
- {field_name} must be one of {allowed_text}
- eval_window_days must be a positive integer
- not_found
AI-assisted analysis of ZhuLinsen/daily_stock_analysis@5159bd72e8 (2026-08-15).
Data as JSON: /api/errors/a401c00600f332ce.
Report an issue: GitHub.