ZhuLinsen/daily_stock_analysis · error · ValueError

{field_name} must be positive

Error message

{field_name} must be positive

What it means

After int conversion succeeds, _optional_positive_int rejects values <= 0 with ValueError '<field> must be positive'. Zero and negative IDs are structurally invalid for database primary keys, so the service fails before querying.

Source

Thrown at src/services/decision_signal_outcome_service.py:633

        return list(SUPPORTED_OUTCOME_HORIZONS.keys())

    def _require_existing_signal(self, signal_id: int) -> DecisionSignalRecord:
        signal_id_norm = self._optional_positive_int(signal_id, "signal_id")
        row = self.signal_repo.get(signal_id_norm)
        if row is None:
            raise DecisionSignalNotFoundError(f"Decision signal not found: {signal_id_norm}")
        return row

    @staticmethod
    def _optional_positive_int(value: Any, field_name: str) -> Optional[int]:
        if value in (None, ""):
            return None
        try:
            number = int(value)
        except (TypeError, ValueError) as exc:
            raise ValueError(f"{field_name} must be an integer") from exc
        if number <= 0:
            raise ValueError(f"{field_name} must be positive")
        return number

    @staticmethod
    def _normalize_enum(value: Any, allowed: Iterable[str], field_name: str) -> str:
        text = str(value or "").strip()
        allowed_set = set(allowed)
        if text not in allowed_set:
            allowed_text = ", ".join(sorted(allowed_set))
            raise ValueError(f"{field_name} must be one of {allowed_text}")
        return text

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

    def _normalize_horizons(self, values: Optional[List[str]]) -> Optional[List[str]]:

View on GitHub (pinned to 5159bd72e8)

Solutions

  1. Use a real positive id from list_signals / the list endpoint.
  2. Treat 0 or -1 in your code as 'not set' and pass None instead.
  3. Guard UI inputs: disable submit until a genuine id is selected.

Example fix

# before
outcome = service.evaluate_outcomes(signal_id=0)

# after
outcome = service.evaluate_outcomes(signal_id=None, stock_codes=["600519"])  # batch mode
Defensive patterns

Strategy: validation

Validate before calling

def positiveOrNone(value) -> int | None:
    if value in (None, "", 0, -1):
        return None
    n = int(value)
    if n <= 0:
        return None  # or raise in UI before the call
    return n

signal_id = positiveOrNone(raw_id)

Type guard

def isPositiveId(value: object) -> bool:
    return isinstance(value, int) and not isinstance(value, bool) and value > 0

Try / catch

try:
    outcome = service.evaluate_outcomes(signal_id=signal_id)
except ValueError as exc:
    if "must be positive" in str(exc):
        return JSONResponse(status_code=400, content={"error": "invalid_params", "message": str(exc)})
    raise

Prevention

When it happens

Trigger: Passing signal_id=0, signal_id=-5, or a string '-1' to decision-signal service methods or their API endpoints.

Common situations: Default/uninitialized numeric values (0 as sentinel) forwarded from client code; parsing errors producing -1; form defaults of 0 submitted without user input.

Related errors


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