Skip to content

Commit a069ff7

Browse files
authored
Merge pull request #134 from LennartvdM/codex/add-debug-trace-to-mk2-scheduling
Add optional MK2 debug trace output
2 parents e789b0b + 44d873f commit a069ff7

3 files changed

Lines changed: 99 additions & 6 deletions

File tree

engines/engine_mk2.py

Lines changed: 87 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ class DayPlan:
5454
day_name: str
5555
day_type: str
5656
activities: List[Activity]
57+
friction: float = 1.0
58+
target_minutes: Optional[Dict[str, int]] = None
5759

5860

5961
OUTDOOR_ACTIVITIES = {"outdoor_run", "bike_ride", "park_visit", "hiking", "outdoor_walk"}
@@ -357,6 +359,10 @@ def _generate_week_activities(
357359
special = self._calendar_provider.get_special_period_effects(current_date)
358360
activities = self.apply_special_period_effects(activities, special)
359361

362+
target_minutes: Dict[str, int] = {}
363+
for activity in activities:
364+
target_minutes[activity.name] = target_minutes.get(activity.name, 0) + activity.base_duration_minutes
365+
360366
for activity in activities:
361367
self._apply_friction(activity, daily_friction)
362368
if activity.name == "sleep":
@@ -368,7 +374,16 @@ def _generate_week_activities(
368374
activity.actual_duration,
369375
)
370376

371-
week_schedule.append(DayPlan(current_date, day_name, day_type, activities))
377+
week_schedule.append(
378+
DayPlan(
379+
current_date,
380+
day_name,
381+
day_type,
382+
activities,
383+
friction=daily_friction,
384+
target_minutes=target_minutes,
385+
)
386+
)
372387

373388
return week_schedule
374389

@@ -536,6 +551,7 @@ def generate_complete_week(
536551
week_seed: int,
537552
templates: Optional[Dict[str, ActivityTemplate]] = None,
538553
yearly_budget: Optional[YearlyBudget] = None,
554+
debug: bool = False,
539555
) -> Dict[str, object]:
540556
random.seed(week_seed)
541557
templates = templates or DEFAULT_TEMPLATES
@@ -545,11 +561,50 @@ def generate_complete_week(
545561
{f"{plan.day_name} ({plan.date.isoformat()})": plan.activities for plan in week_plans}
546562
)
547563

564+
debug_trace: Optional[Dict[str, Any]] = None
565+
debug_days: Dict[str, Dict[str, Any]] = {}
566+
if debug:
567+
for plan in week_plans:
568+
debug_days[plan.day_name] = {
569+
"date": plan.date.isoformat(),
570+
"classification": plan.day_type,
571+
"friction": plan.friction,
572+
"target_minutes": dict(plan.target_minutes or {}),
573+
"activities_before_compression": [
574+
{
575+
"name": activity.name,
576+
"base": activity.base_duration_minutes,
577+
"waste_multiplier": activity.waste_multiplier,
578+
"optional": activity.optional,
579+
"priority": activity.priority,
580+
}
581+
for activity in plan.activities
582+
],
583+
}
584+
debug_trace = {
585+
"profile": profile.name,
586+
"budget": asdict(profile.budget),
587+
"per_day": debug_days,
588+
}
589+
548590
compression_metadata: Dict[str, Dict[str, object]] = {}
549591
for plan in week_plans:
550592
compressed, metadata = self._compress_day_if_needed(plan.activities)
551593
plan.activities = compressed
552594
compression_metadata[plan.date.isoformat()] = metadata
595+
if debug:
596+
day_debug = debug_days.get(plan.day_name)
597+
if day_debug is not None:
598+
day_debug["activities_after_compression"] = [
599+
{
600+
"name": activity.name,
601+
"base": activity.base_duration_minutes,
602+
"actual": activity.actual_duration,
603+
"optional": activity.optional,
604+
"priority": activity.priority,
605+
}
606+
for activity in plan.activities
607+
]
553608

554609
normalized_inputs: List[Dict[str, Any]] = []
555610
for plan in week_plans:
@@ -559,6 +614,20 @@ def generate_complete_week(
559614
event.day = plan.day_name
560615
events = self.fill_free_time(events)
561616
events = self.apply_micro_jitter(events)
617+
if debug:
618+
day_debug = debug_days.get(plan.day_name)
619+
if day_debug is not None:
620+
day_debug["events"] = [
621+
{
622+
"activity": event.activity.name
623+
if isinstance(event.activity, Activity)
624+
else event.activity,
625+
"start_minutes": event.start_minutes,
626+
"end_minutes": event.end_minutes,
627+
"duration": max(0, event.end_minutes - event.start_minutes),
628+
}
629+
for event in events
630+
]
562631
day_offset = (plan.date - start_date).days
563632
weekday_index = plan.date.weekday()
564633
for event in events:
@@ -609,7 +678,18 @@ def generate_complete_week(
609678
)
610679
summary_hours = self._generate_summary(events_payload)
611680

612-
return {
681+
weekly_totals_minutes: Dict[str, int] = {}
682+
if debug:
683+
for event in events_payload:
684+
activity_name = str(event.get("activity"))
685+
duration = int(event.get("duration_minutes", 0) or 0)
686+
weekly_totals_minutes[activity_name] = (
687+
weekly_totals_minutes.get(activity_name, 0) + duration
688+
)
689+
if debug_trace is not None:
690+
debug_trace["weekly_totals_from_events"] = weekly_totals_minutes
691+
692+
result: Dict[str, Any] = {
613693
"person": profile.name,
614694
"week_start": start_date.isoformat(),
615695
"events": events_payload,
@@ -623,6 +703,11 @@ def generate_complete_week(
623703
},
624704
}
625705

706+
if debug and debug_trace is not None:
707+
result["debug_trace"] = debug_trace
708+
709+
return result
710+
626711
def select_profile(self, archetype: str) -> Tuple[PersonProfile, Dict[str, ActivityTemplate]]:
627712
archetype = archetype.lower()
628713
if archetype not in self._profile_factory:

engines/web_adapter.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -281,13 +281,17 @@ def mk1_run_web(archetype: str, week_start: Optional[str], seed: Any) -> SchemaP
281281
return _ensure_schema(payload, rig="default", seed=seed_value, archetype=archetype_key or "office")
282282

283283

284-
def mk2_run_calendar_web(archetype: str, week_start: Optional[str], seed: Any) -> SchemaPayload:
284+
def mk2_run_calendar_web(
285+
archetype: str, week_start: Optional[str], seed: Any, debug: bool = False
286+
) -> SchemaPayload:
285287
archetype_key = str(archetype or "office").strip().lower()
286288
seed_value = _coerce_seed(seed)
287289
start_date = _coerce_start_date(week_start) or date.today()
288290

289291
profile, templates = _MK2_RIG.select_profile(archetype_key)
290-
result = _MK2_RIG.generate_complete_week(profile, start_date, seed_value, templates, None)
292+
result = _MK2_RIG.generate_complete_week(
293+
profile, start_date, seed_value, templates, None, debug=debug
294+
)
291295

292296
payload: MutableMapping[str, Any] = dict(result)
293297
payload.setdefault("issues", [])
@@ -307,6 +311,7 @@ def mk2_run_workforce_web(
307311
week_start: Optional[str],
308312
seed: Any,
309313
yearly_budget: Optional[Mapping[str, Any]],
314+
debug: bool = False,
310315
) -> SchemaPayload:
311316
archetype_key = str(archetype or "office").strip().lower()
312317
seed_value = _coerce_seed(seed)
@@ -315,7 +320,9 @@ def mk2_run_workforce_web(
315320
profile, templates = _MK2_RIG.select_profile(archetype_key)
316321
budget = _build_yearly_budget(yearly_budget)
317322

318-
result = _MK2_RIG.generate_complete_week(profile, start_date, seed_value, templates, budget)
323+
result = _MK2_RIG.generate_complete_week(
324+
profile, start_date, seed_value, templates, budget, debug=debug
325+
)
319326

320327
payload: MutableMapping[str, Any] = dict(result)
321328
payload.setdefault("issues", [])

rigs/workforce_rig.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,10 @@ def generate_complete_week(
101101
week_seed: int,
102102
templates: Optional[Dict[str, ActivityTemplate]] = None,
103103
yearly_budget: Optional[YearlyBudget] = None,
104+
debug: bool = False,
104105
) -> Dict[str, object]:
105106
"""Delegate generation to the configured engine."""
106107

107108
return self._engine.generate_complete_week(
108-
profile, start_date, week_seed, templates, yearly_budget
109+
profile, start_date, week_seed, templates, yearly_budget, debug=debug
109110
)

0 commit comments

Comments
 (0)