{"record":{"id":"21c085144f4ad016","repo":"sansan0/TrendRadar","slug":"day-plan-plan-key-ranges-i-0-rang","errorCode":null,"errorMessage":"day_plan '{plan_key}' 中时间段 '{ranges[i][0]}' ({ranges[i][1]}-{ranges[i][2]}) 与 '{ranges[j][0]}' ({ranges[j][1]}-{ranges[j][2]}) 存在重叠。请调整时间段，或将 overlap.policy 设为 'last_wins'","messagePattern":"day_plan '(.+?)' 中时间段 '(.+?)' \\((.+?)-(.+?)\\) 与 '(.+?)' \\((.+?)-(.+?)\\) 存在重叠。请调整时间段，或将 overlap\\.policy 设为 'last_wins'","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"trendradar/core/scheduler.py","lineNumber":390,"sourceCode":"            period_keys = plan.get(\"periods\", [])\n            if len(period_keys) <= 1:\n                continue\n\n            # 收集每个时间段的范围\n            ranges = []\n            for pk in period_keys:\n                p = periods.get(pk, {})\n                if \"start\" in p and \"end\" in p:\n                    ranges.append((pk, p[\"start\"], p[\"end\"]))\n\n            # 两两检查重叠\n            for i in range(len(ranges)):\n                for j in range(i + 1, len(ranges)):\n                    if self._ranges_overlap(\n                        ranges[i][1], ranges[i][2],\n                        ranges[j][1], ranges[j][2],\n                    ):\n                        raise ValueError(\n                            f\"day_plan '{plan_key}' 中时间段 '{ranges[i][0]}' \"\n                            f\"({ranges[i][1]}-{ranges[i][2]}) 与 '{ranges[j][0]}' \"\n                            f\"({ranges[j][1]}-{ranges[j][2]}) 存在重叠。\"\n                            f\"请调整时间段，或将 overlap.policy 设为 'last_wins'\"\n                        )\n\n    @staticmethod\n    def _ranges_overlap(s1: str, e1: str, s2: str, e2: str) -> bool:\n        \"\"\"检查两个时间范围是否重叠（支持跨日）\"\"\"\n        def to_minutes(t: str) -> int:\n            h, m = t.split(\":\")\n            return int(h) * 60 + int(m)\n\n        def expand_range(start: str, end: str) -> List[tuple]:\n            \"\"\"将时间范围展开为分钟段列表，跨日时拆分为两段\"\"\"\n            s = to_minutes(start)\n            e = to_minutes(end)\n            if s <= e:","sourceCodeStart":372,"sourceCodeEnd":408,"githubUrl":"https://github.com/sansan0/TrendRadar/blob/8ee26026ba6c11dec41a95fb3895a7162876caa1/trendradar/core/scheduler.py#L372-L408","documentation":"Duplicate coverage of scheduler.py:390's message — same raise site, same meaning as error 78's underlying check but reached via the runtime path: _find_active_period found multiple candidate periods containing the current HH:MM and overlap.policy is 'error_on_overlap'. The scheduler lists the conflicting period names and times and tells the user how to opt into 'last_wins'.","triggerScenarios":"resolve() called at a time of day inside two periods of the active day plan (e.g. 11:30 with 08:00-12:00 and 11:00-13:00 both active); default policy. Note the startup validator (_check_period_overlaps) normally catches this earlier, so reaching the runtime raise means validation was skipped or the timeline mutated afterwards.","commonSituations":"Dynamically built or patched timelines that bypass _validate_timeline; periods added at runtime by orchestration code; cross-midnight spans overlapping early-morning periods.","solutions":["Fix the period boundaries so they do not overlap.","Set overlap: {policy: last_wins} to let the last-listed period win (warning logged, no error).","If building timelines programmatically, run _validate_timeline before entering the scheduling loop."],"exampleFix":"# before\nperiods:\n  a: {start: \"08:00\", end: \"12:00\"}\n  b: {start: \"11:00\", end: \"13:00\"}\n# after\nperiods:\n  a: {start: \"08:00\", end: \"11:00\"}\n  b: {start: \"11:00\", end: \"13:00\"}\n# or: overlap: {policy: last_wins}","handlingStrategy":"validation","validationCode":"# reuse the pairwise overlap lint from startup validation before resolve()\nfor plan in timeline[\"day_plans\"].values():\n    keys = plan.get(\"periods\", [])\n    for i in range(len(keys)):\n        for j in range(i + 1, len(keys)):\n            if ranges_overlap(timeline[\"periods\"][keys[i]], timeline[\"periods\"][keys[j]]):\n                raise ValueError(f\"periods {keys[i]}/{keys[j]} overlap\")\ncfg = scheduler.resolve()","typeGuard":null,"tryCatchPattern":"try:\n    cfg = scheduler.resolve()\nexcept ValueError as e:\n    if \"时间段重叠冲突\" in str(e):\n        # deterministic config bug: fix periods or set overlap.policy=last_wins\n        raise SystemExit(f\"schedule config error: {e}\")\n    raise","preventionTips":["Always validate timelines (startup validator) before the scheduling loop; the runtime check is a backstop, not the primary guard.","Avoid mutating timeline data after scheduler construction."],"tags":["scheduler","config","overlap","runtime"],"backgroundTag":null,"analyzedSha":"8ee26026ba6c11dec41a95fb3895a7162876caa1","analyzedAt":"2026-08-15T01:42:18.084Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}