{"record":{"id":"4eb7d58493db004f","repo":"sansan0/TrendRadar","slug":"period-period-key-start-end","errorCode":null,"errorMessage":"period '{period_key}' 缺少 start 或 end 字段","messagePattern":"period '(.+?)' 缺少 start 或 end 字段","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"trendradar/core/scheduler.py","lineNumber":348,"sourceCode":"        # day_plan 引用完整性\n        for day, plan_key in timeline[\"week_map\"].items():\n            if plan_key not in timeline[\"day_plans\"]:\n                raise ValueError(\n                    f\"week_map[{day}] 引用了不存在的 day_plan: {plan_key}\"\n                )\n\n        # period 引用完整性\n        for plan_key, plan in timeline[\"day_plans\"].items():\n            for period_key in plan.get(\"periods\", []):\n                if period_key not in timeline[\"periods\"]:\n                    raise ValueError(\n                        f\"day_plan[{plan_key}] 引用了不存在的 period: {period_key}\"\n                    )\n\n        # 时间格式校验\n        for period_key, period in timeline[\"periods\"].items():\n            if \"start\" not in period or \"end\" not in period:\n                raise ValueError(\n                    f\"period '{period_key}' 缺少 start 或 end 字段\"\n                )\n            self._validate_hhmm(period[\"start\"], f\"{period_key}.start\")\n            self._validate_hhmm(period[\"end\"], f\"{period_key}.end\")\n            if period[\"start\"] == period[\"end\"]:\n                raise ValueError(\n                    f\"period '{period_key}' 的 start 与 end 不能相同: {period['start']}\"\n                )\n\n        # 检查冲突策略下的重叠\n        policy = timeline.get(\"overlap\", {}).get(\"policy\", \"error_on_overlap\")\n        if policy == \"error_on_overlap\":\n            self._check_period_overlaps(timeline)\n\n    def _check_period_overlaps(self, timeline: Dict[str, Any]) -> None:\n        \"\"\"\n        检查每个日计划中的时间段是否存在重叠\n","sourceCodeStart":330,"sourceCodeEnd":366,"githubUrl":"https://github.com/sansan0/TrendRadar/blob/8ee26026ba6c11dec41a95fb3895a7162876caa1/trendradar/core/scheduler.py#L330-L366","documentation":"Raised by _validate_timeline when a period's start and end are identical. A zero-length period is rejected because the scheduler's range semantics (start inclusive, end exclusive) would make it never match, hiding a likely config mistake. Format validity of each field is checked separately just before this comparison.","triggerScenarios":"periods: {noon: {start: \"12:00\", end: \"12:00\"}} — intended as a one-shot instant but encoded as equal boundaries; copy-paste where end was not updated after start.","commonSituations":"Attempting to schedule a single-moment trigger; placeholder values left in both fields; timezone shifts applied equally to both fields after copy-paste.","solutions":["Set end strictly after start (or before it for a cross-midnight span), e.g. \"12:00\"-\"12:01\" for a near-instant window.","If you meant 'all day', use \"00:00\"-\"23:59\" or the default config instead.","Double-check copy-pasted periods where end was meant to be edited."],"exampleFix":"# before\nperiods:\n  ping: {start: \"12:00\", end: \"12:00\"}\n# after\nperiods:\n  ping: {start: \"12:00\", end: \"12:05\"}","handlingStrategy":"validation","validationCode":"for key, p in timeline.get(\"periods\", {}).items():\n    if p[\"start\"] == p[\"end\"]:\n        raise ValueError(f\"period '{key}': start == end ({p['start']}); use a real span\")","typeGuard":"def is_nonzero_period(p: dict) -> bool:\n    return p.get(\"start\") != p.get(\"end\")","tryCatchPattern":"try:\n    scheduler = Scheduler(schedule_config, timeline_data)\nexcept ValueError as e:\n    if \"start 与 end 不能相同\" in str(e):\n        raise SystemExit(f\"timeline config error: {e}\")\n    raise","preventionTips":["Never use equal start/end; for a near-instant window use a 1-minute span.","For all-day coverage use 00:00-23:59 or the default config."],"tags":["scheduler","config","time-range","validation"],"backgroundTag":null,"analyzedSha":"8ee26026ba6c11dec41a95fb3895a7162876caa1","analyzedAt":"2026-08-15T01:42:18.084Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}