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

  1. Shorten the text to within the field's documented max_length.
  2. Truncate client-side before submit: text[:max_length].
  3. 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

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


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