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
- Use a real positive id from list_signals / the list endpoint.
- Treat 0 or -1 in your code as 'not set' and pass None instead.
- 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
- Never use 0 or -1 as 'unset' sentinels across the API boundary — use None/omitted field.
- Validate id > 0 in form handlers before submit.
- Use ge=1 constraints on query/path params in FastAPI signatures.
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
- {field_name} must be an integer
- eval_window_days must be a positive integer
- {field_name} must be one of {allowed_text}
- {field_name} must be at most {max_length} characters
- unsupported_report_type
AI-assisted analysis of ZhuLinsen/daily_stock_analysis@5159bd72e8 (2026-08-15).
Data as JSON: /api/errors/d804d607f577c204.
Report an issue: GitHub.