Skip to content

Commit 07d5d42

Browse files
authored
Merge pull request #196 from Yif-Yang/fix/group-task-copy-normalization
fix(sleep): avoid mutating tasks while grouping
2 parents 57b9d9d + 4e1ac00 commit 07d5d42

2 files changed

Lines changed: 29 additions & 3 deletions

File tree

skillopt_sleep/mine.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import os
1919
import re
2020
from collections import Counter
21+
from dataclasses import replace
2122
from typing import Callable, Dict, List, Optional, Set, Tuple
2223

2324
from skillopt_sleep.backend import CursorBackendError
@@ -254,10 +255,16 @@ def group_tasks_by_skill_hint(
254255
for t in tasks:
255256
observed.setdefault(t.id, set()).add((t.skill_hint or "").strip())
256257

258+
# ``dedup_tasks`` merges records in place, so operate on shallow dataclass
259+
# copies (including the only list it mutates) rather than caller-owned tasks.
260+
copied = [replace(t, source_sessions=list(t.source_sessions)) for t in tasks]
257261
groups: Dict[str, List[TaskRecord]] = {}
258-
for task in dedup_tasks(tasks):
262+
for task in dedup_tasks(copied):
259263
hints = observed[task.id]
260264
hint = next(iter(hints)) if len(hints) == 1 else ""
265+
# Keep the returned record aligned with the normalized evidence used for
266+
# routing. In particular, blank and partially observed hints stay empty.
267+
task.skill_hint = hint
261268
groups.setdefault(hint or managed_skill_name, []).append(task)
262269
return groups
263270

tests/test_sleep_engine.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2282,6 +2282,7 @@ def test_group_tasks_by_skill_hint_legacy_tasks_go_to_managed_skill(self):
22822282
def test_group_tasks_by_skill_hint_blank_hint_goes_to_managed_skill(self):
22832283
groups = group_tasks_by_skill_hint([self._task("t1", " ")], self.MANAGED)
22842284
self.assertEqual(self._ids(groups), {self.MANAGED: ["t1"]})
2285+
self.assertEqual(groups[self.MANAGED][0].skill_hint, "")
22852286

22862287
def test_group_tasks_by_skill_hint_preserves_first_seen_order(self):
22872288
groups = group_tasks_by_skill_hint(
@@ -2300,18 +2301,35 @@ def test_group_tasks_by_skill_hint_preserves_first_seen_order(self):
23002301
)
23012302

23022303
def test_group_tasks_by_skill_hint_merges_duplicate_ids_once(self):
2304+
first = self._task("t1", "alpha", session="s1")
2305+
duplicate = self._task("t1", "alpha", session="s2", outcome="success")
23032306
groups = group_tasks_by_skill_hint(
23042307
[
2305-
self._task("t1", "alpha", session="s1"),
2308+
first,
23062309
self._task("t2", "beta"),
2307-
self._task("t1", "alpha", session="s2", outcome="success"),
2310+
duplicate,
23082311
],
23092312
self.MANAGED,
23102313
)
23112314
self.assertEqual(self._ids(groups), {"alpha": ["t1"], "beta": ["t2"]})
23122315
merged = groups["alpha"][0]
23132316
self.assertEqual(merged.source_sessions, ["s1", "s2"])
23142317
self.assertEqual(merged.outcome, "success")
2318+
self.assertIsNot(merged, first)
2319+
self.assertEqual(first.source_sessions, ["s1"])
2320+
self.assertEqual(first.outcome, "unknown")
2321+
self.assertEqual(duplicate.source_sessions, ["s2"])
2322+
2323+
def test_group_tasks_by_skill_hint_normalizes_without_mutating_inputs(self):
2324+
first = self._task("t1", " alpha ", session="s1")
2325+
duplicate = self._task("t1", "alpha", session="s2")
2326+
2327+
groups = group_tasks_by_skill_hint([first, duplicate], self.MANAGED)
2328+
2329+
self.assertEqual(self._ids(groups), {"alpha": ["t1"]})
2330+
self.assertEqual(groups["alpha"][0].skill_hint, "alpha")
2331+
self.assertEqual(first.skill_hint, " alpha ")
2332+
self.assertEqual(duplicate.skill_hint, "alpha")
23152333

23162334
def test_group_tasks_by_skill_hint_conflicting_hints_go_to_managed_skill(self):
23172335
groups = group_tasks_by_skill_hint(
@@ -2326,6 +2344,7 @@ def test_group_tasks_by_skill_hint_partial_hint_evidence_goes_to_managed_skill(s
23262344
self.MANAGED,
23272345
)
23282346
self.assertEqual(self._ids(groups), {self.MANAGED: ["t1"]})
2347+
self.assertEqual(groups[self.MANAGED][0].skill_hint, "")
23292348

23302349
def test_group_tasks_by_skill_hint_hint_equal_to_managed_skill_is_one_group(self):
23312350
groups = group_tasks_by_skill_hint(

0 commit comments

Comments
 (0)