{"record":{"id":"77f02330d7fa4d62","repo":"ZhuLinsen/daily_stock_analysis","slug":"not-found-77f023","errorCode":"not_found","errorMessage":"Alert rule not found: {rule_id}","messagePattern":"Alert rule not found: (.+?)","errorType":"http","errorClass":"AlertNotFoundError","httpStatus":404,"severity":"error","filePath":"src/services/alert_service.py","lineNumber":117,"sourceCode":"\n    error_code = \"unsupported_alert_type\"\n\n\nclass AlertService:\n    \"\"\"Business logic for alert rule CRUD and dry-run evaluation.\"\"\"\n\n    def __init__(self, db_manager: Optional[DatabaseManager] = None):\n        self.db = db_manager or DatabaseManager.get_instance()\n        self.repo = AlertRepository(self.db)\n\n    def create_rule(self, payload: Dict[str, Any]) -> Dict[str, Any]:\n        fields = self._normalize_rule_payload(payload)\n        return self._serialize_rule(self.repo.create_rule(fields))\n\n    def get_rule(self, rule_id: int) -> Dict[str, Any]:\n        row = self.repo.get_rule(rule_id)\n        if row is None:\n            raise AlertNotFoundError(f\"Alert rule not found: {rule_id}\")\n        return self._serialize_rule(row)\n\n    def update_rule(self, rule_id: int, payload: Dict[str, Any]) -> Dict[str, Any]:\n        row = self.repo.get_rule(rule_id)\n        if row is None:\n            raise AlertNotFoundError(f\"Alert rule not found: {rule_id}\")\n        if not payload:\n            raise AlertServiceError(\"No fields provided for update\")\n        self._validate_rule_update_payload(payload)\n\n        merged = self._serialize_rule_base(row)\n        merged.update(payload)\n        fields = self._normalize_rule_payload(merged, source=merged.get(\"source\") or \"api\")\n        updated = self.repo.update_rule(rule_id, fields)\n        if updated is None:\n            raise AlertNotFoundError(f\"Alert rule not found: {rule_id}\")\n        return self._serialize_rule(updated)\n","sourceCodeStart":99,"sourceCodeEnd":135,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/src/services/alert_service.py#L99-L135","documentation":"AlertNotFoundError (subclass of AlertServiceError, itself a ValueError) raised in AlertService.get_rule (src/services/alert_service.py:117) when AlertRepository.get_rule returns no row for the given rule_id — i.e. no alert rule with that primary key exists (or it was deleted). It carries code 'not_found' semantics for the API layer to map to HTTP 404.","triggerScenarios":"GET /api/alerts/rules/{rule_id} with an id that never existed or was deleted; a stale rule_id held in the frontend after another tab/session removed it; an id from a different database/environment (dev id used against prod).","commonSituations":"Cached UI lists referencing deleted rules; database reset/reseed while clients keep old ids; typos in manual curl/scripts; concurrent delete-then-read races.","solutions":["List rules (list_rules / GET collection) to see current ids and re-fetch the correct one.","Handle 404/AlertNotFoundError in the client by refreshing the rule list and dropping the stale entry.","Verify you are pointed at the right environment/database for the id you hold."],"exampleFix":"# before\nrule = service.get_rule(123)  # KeyError-style crash if deleted\n\n# after\ntry:\n    rule = service.get_rule(123)\nexcept AlertNotFoundError:\n    rules = service.list_rules(page=1, page_size=50)\n    rule = next((r for r in rules['items'] if ...), None)","handlingStrategy":"try-catch","validationCode":"rules = service.list_rules(page=1, page_size=200)\nknown_ids = {r['id'] for r in rules['items']}\nif rule_id not in known_ids:\n    return not_found(f'rule {rule_id} not in current list')","typeGuard":null,"tryCatchPattern":"from src.services.alert_service import AlertNotFoundError\ntry:\n    rule = service.get_rule(rule_id)\nexcept AlertNotFoundError:\n    return JSONResponse(status_code=404, content={'detail': f'alert rule {rule_id} not found'})","preventionTips":["Map AlertNotFoundError to HTTP 404 at the API layer once, centrally.","Refresh rule lists after deletes instead of caching ids indefinitely.","Pass ids only from a list response of the same environment."],"tags":["not-found","alerts","api","crud"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}