{"record":{"id":"02131244f807e4d3","repo":"ZhuLinsen/daily_stock_analysis","slug":"validation-error-021312","errorCode":"validation_error","errorMessage":"unsupported portfolio alert_type: {alert_type}","messagePattern":"unsupported portfolio alert_type: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":400,"severity":"error","filePath":"src/services/portfolio_alerts.py","lineNumber":84,"sourceCode":"\n\n@dataclass\nclass StaticAlertEvaluation:\n    \"\"\"Runtime placeholder for skipped/degraded expansion results.\"\"\"\n\n    stock_code: str\n    alert_type: str\n    message: str\n    record_status: str = \"skipped\"\n    metadata: Dict[str, Any] = field(default_factory=dict)\n    description: str = \"\"\n\n\ndef normalize_portfolio_alert_parameters(alert_type: str, parameters: Dict[str, Any]) -> Dict[str, Any]:\n    \"\"\"Normalize P6 portfolio alert parameters.\"\"\"\n\n    if alert_type not in PORTFOLIO_ALERT_TYPES:\n        raise ValueError(f\"unsupported portfolio alert_type: {alert_type}\")\n    if not isinstance(parameters, dict):\n        raise ValueError(\"parameters must be an object\")\n\n    if alert_type == \"portfolio_stop_loss\":\n        mode = str(parameters.get(\"mode\") or \"near\").strip().lower()\n        if mode not in {\"near\", \"breach\"}:\n            raise ValueError(\"portfolio_stop_loss mode must be near or breach\")\n        return {\"mode\": mode}\n\n    return {}\n\n\ndef portfolio_effective_target(target: str) -> str:\n    target_text = str(target or \"all\").strip() or \"all\"\n    return \"account:all\" if target_text == \"all\" else f\"account:{target_text}\"\n\n\ndef normalize_batch_target_scope_target(target_scope: str, target: str) -> str:","sourceCodeStart":66,"sourceCodeEnd":102,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/src/services/portfolio_alerts.py#L66-L102","documentation":"ValueError(f'unsupported portfolio alert_type: {alert_type}') with code validation_error is raised by normalize_portfolio_alert_parameters (src/services/portfolio_alerts.py:89) when alert_type is not in PORTFOLIO_ALERT_TYPES = {'portfolio_stop_loss','portfolio_concentration','portfolio_drawdown','portfolio_price_stale'}. It is the gatekeeping check for P6 portfolio alert parameter normalization.","triggerScenarios":"Creating/updating a portfolio alert with any other type string — e.g. 'stop_loss' (missing portfolio_ prefix), 'portfolio_pnl', or a market alert type like 'market_light_status' passed to the portfolio API.","commonSituations":"Renamed alert types across versions, clients abbreviating the prefixed names, or confusion between the market-alert and portfolio-alert type namespaces.","solutions":["Use one of: portfolio_stop_loss, portfolio_concentration, portfolio_drawdown, portfolio_price_stale","Check the PORTFOLIO_ALERT_TYPES frozenset at src/services/portfolio_alerts.py:20 and align client enums","If a market-level alert was intended, use the market alert API with MARKET_ALERT_TYPES instead"],"exampleFix":"# before\nnormalize_portfolio_alert_parameters(\"stop_loss\", {\"mode\": \"near\"})\n\n# after\nnormalize_portfolio_alert_parameters(\"portfolio_stop_loss\", {\"mode\": \"near\"})","handlingStrategy":"type-guard","validationCode":"from src.services.portfolio_alerts import PORTFOLIO_ALERT_TYPES\n\nif alert_type not in PORTFOLIO_ALERT_TYPES:\n    raise ValueError(f\"choose from {sorted(PORTFOLIO_ALERT_TYPES)}\")","typeGuard":"from src.services.portfolio_alerts import PORTFOLIO_ALERT_TYPES\n\ndef is_valid_portfolio_alert_type(alert_type: str) -> bool:\n    return isinstance(alert_type, str) and alert_type in PORTFOLIO_ALERT_TYPES","tryCatchPattern":"try:\n    params = normalize_portfolio_alert_parameters(alert_type, parameters)\nexcept ValueError as exc:\n    if \"unsupported portfolio alert_type\" in str(exc):\n        return api_error(400, str(exc))","preventionTips":["Always use the portfolio_ prefixed type names","Import PORTFOLIO_ALERT_TYPES instead of duplicating the list","Keep market and portfolio alert type enums separate in client code"],"tags":["validation","alerts","portfolio","api-contract"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}