{"record":{"id":"73e752296192ed75","repo":"ZhuLinsen/daily_stock_analysis","slug":"notification-quiet-hours-must-be-in-hh-mm-hh-mm-fo","errorCode":null,"errorMessage":"NOTIFICATION_QUIET_HOURS must be in HH:MM-HH:MM format","messagePattern":"NOTIFICATION_QUIET_HOURS must be in HH:MM-HH:MM format","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/notification_noise.py","lineNumber":106,"sourceCode":"def normalize_notification_severity(route_type: Optional[str], severity: Optional[str] = None) -> str:\n    \"\"\"Normalize explicit severity, or derive a default from route type.\"\"\"\n    explicit = str(severity or \"\").strip().lower()\n    if explicit in NOTIFICATION_SEVERITY_RANK:\n        return explicit\n\n    route = str(route_type or \"\").strip().lower()\n    return DEFAULT_NOTIFICATION_SEVERITY_BY_ROUTE.get(route, \"info\")\n\n\ndef parse_notification_quiet_hours(value: Optional[str]) -> Optional[Tuple[int, int]]:\n    \"\"\"Parse ``HH:MM-HH:MM`` into start/end minute-of-day values.\"\"\"\n    raw = str(value or \"\").strip()\n    if not raw:\n        return None\n\n    match = _QUIET_HOURS_RE.match(raw)\n    if not match:\n        raise ValueError(\"NOTIFICATION_QUIET_HOURS must be in HH:MM-HH:MM format\")\n\n    start_hour, start_minute, end_hour, end_minute = [int(group) for group in match.groups()]\n    return start_hour * 60 + start_minute, end_hour * 60 + end_minute\n\n\ndef validate_notification_timezone(value: Optional[str]) -> None:\n    \"\"\"Validate an optional IANA timezone name.\"\"\"\n    raw = str(value or \"\").strip()\n    if not raw:\n        return\n    if ZoneInfo is None:\n        raise ValueError(\"zoneinfo is unavailable\")\n    try:\n        ZoneInfo(raw)\n    except ZoneInfoNotFoundError as exc:\n        raise ValueError(f\"unknown timezone: {raw}\") from exc\n\n","sourceCodeStart":88,"sourceCodeEnd":124,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/src/notification_noise.py#L88-L124","documentation":"ValueError from parse_notification_quiet_hours when the NOTIFICATION_QUIET_HOURS environment value does not match the strict HH:MM-HH:MM pattern (24-hour clock). The value must be two zero-padded times joined by a single hyphen; anything else — missing hyphen, 12-hour times, seconds, non-numeric parts — is rejected at config load.","triggerScenarios":"Setting NOTIFICATION_QUIET_HOURS to values like '22:00', '22:00 - 06:00' (spaces), '9pm-6am', '22:0-06:00', '22:00-06:00:30', or '22-06'. Only strings fully matching HH:MM-HH:MM with hours 00-23 and minutes 00-59 (per the regex) parse successfully.","commonSituations":"Hand-editing .env with a human-friendly time range; copying a 12-hour format from documentation of another tool; locale-specific time strings; trailing whitespace or smart quotes after copy-paste from a chat or doc.","solutions":["Use exact zero-padded 24-hour format, e.g. NOTIFICATION_QUIET_HOURS=22:00-06:00","Remove spaces around the hyphen and any seconds/trailing components","Unset the variable entirely if you do not want quiet hours — empty is valid and disables the feature","Check for invisible characters (smart quotes, non-breaking spaces) after pasting into .env"],"exampleFix":"# before\nNOTIFICATION_QUIET_HOURS=10pm - 6am\n\n# after\nNOTIFICATION_QUIET_HOURS=22:00-06:00","handlingStrategy":"validation","validationCode":"import re\n\n_QUIET = re.compile(r\"^([01]\\d|2[0-3]):([0-5]\\d)-([01]\\d|2[0-3]):([0-5]\\d)$\")\n\ndef is_valid_quiet_hours(value: str) -> bool:\n    return bool(_QUIET.match((value or \"\").strip()))","typeGuard":null,"tryCatchPattern":"try:\n    quiet = parse_notification_quiet_hours(os.getenv(\"NOTIFICATION_QUIET_HOURS\"))\nexcept ValueError as exc:\n    log.error(\"Config error: %s (expected HH:MM-HH:MM, e.g. 22:00-06:00)\", exc)\n    raise SystemExit(2)","preventionTips":["Show the expected format in .env.example comments","Validate notification config at startup, not on first notification","Prefer unset (disabled) over malformed values in generated configs"],"tags":["config","notification","validation","time"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}