{"record":{"id":"82276a3c71cd2f37","repo":"ZhuLinsen/daily_stock_analysis","slug":"field-name-must-be-a-finite-positive-number","errorCode":null,"errorMessage":"{field_name} must be a finite positive number","messagePattern":"(.+?) must be a finite positive number","errorType":"validation","errorClass":"ValueError","httpStatus":400,"severity":"error","filePath":"src/services/decision_signal_service.py","lineNumber":1255,"sourceCode":"        text = sanitize_decision_signal_text(value)\n        return text or None\n\n    @staticmethod\n    def _optional_float(value: Any, field_name: str) -> Optional[float]:\n        if value in (None, \"\"):\n            return None\n        try:\n            return float(value)\n        except (TypeError, ValueError) as exc:\n            raise ValueError(f\"{field_name} must be a number\") from exc\n\n    @classmethod\n    def _optional_price_float(cls, value: Any, field_name: str) -> Optional[float]:\n        number = cls._optional_float(value, field_name)\n        if number is None:\n            return None\n        if not math.isfinite(number) or number <= 0:\n            raise ValueError(f\"{field_name} must be a finite positive number\")\n        return number\n\n    @staticmethod\n    def _validate_entry_range(fields: Dict[str, Any]) -> None:\n        entry_low = fields.get(\"entry_low\")\n        entry_high = fields.get(\"entry_high\")\n        if entry_low is not None and entry_high is not None and entry_low > entry_high:\n            raise ValueError(\"entry_low must be less than or equal to entry_high\")\n\n    @staticmethod\n    def _optional_int(value: Any, field_name: str) -> Optional[int]:\n        if value in (None, \"\"):\n            return None\n        try:\n            return int(value)\n        except (TypeError, ValueError) as exc:\n            raise ValueError(f\"{field_name} must be an integer\") from exc\n","sourceCodeStart":1237,"sourceCodeEnd":1273,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/src/services/decision_signal_service.py#L1237-L1273","documentation":"ValueError from DecisionSignalService._optional_price_float (src/services/decision_signal_service.py:1255): price fields (entry_low, entry_high, target prices) must be finite and strictly positive after float coercion. Rejects NaN, ±inf (math.isfinite check), zero, and negatives. NaN is notable because float('nan') succeeds in _optional_float but dies here.","triggerScenarios":"entry_low: 0 (placeholder for 'no data'), entry_low: -1, NaN values propagated from upstream pandas computations (ffill on empty series → NaN), infinity from division by zero in derived price math, or negative values from bad scraping.","commonSituations":"Missing-price sentinels of 0/-1 in flat files; pandas/polars pipelines letting NaN/inf leak into payload dicts; per-share prices corrupted by unit mix-ups (cents vs dollars producing negatives after adjustment); math like (a-b)/b with b=0 yielding inf.","solutions":["Drop non-finite/non-positive prices before the call: convert NaN/inf/<=0 to None and omit the field.","Fix the upstream computation: guard divisions, use result if pd.notna(result) and result > 0 else None.","Replace 0/-1 sentinel conventions with explicit nulls at the ingestion boundary.","If a genuine zero price is legitimate in your domain, that is not supported — raise it with maintainers rather than bypassing."],"exampleFix":"# before\nentry_low = float(df['low'].iloc[0])  # may be NaN → ValueError\nservice.create_signal({..., \"entry_low\": entry_low})\n\n# after\nimport math\nraw_low = df['low'].iloc[0]\nentry_low = float(raw_low) if raw_low is not None and math.isfinite(float(raw_low)) and float(raw_low) > 0 else None\nservice.create_signal({..., \"entry_low\": entry_low})","handlingStrategy":"validation","validationCode":"import math\ndef as_price(v):\n    if v in (None, ''):\n        return None\n    try:\n        n = float(v)\n    except (TypeError, ValueError):\n        return None\n    return n if math.isfinite(n) and n > 0 else None\npayload['entry_low'] = as_price(payload.get('entry_low'))\npayload['entry_high'] = as_price(payload.get('entry_high'))","typeGuard":"def is_valid_price(v) -> bool:\n    if v in (None, ''):\n        return True\n    try:\n        n = float(v)\n    except (TypeError, ValueError):\n        return False\n    return math.isfinite(n) and n > 0","tryCatchPattern":null,"preventionTips":["Filter NaN/inf/<=0 out of pandas-derived prices before building payloads.","Guard upstream divisions that can yield inf; use pd.notna checks.","Replace 0/-1 'missing price' sentinels with explicit nulls at the edge."],"tags":["decision-signal","validation","price","nan","range"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}