""" Context assembler — converts raw user signals into a PromptContext for LLM tip generation. Usage: from ml.features.context import build_context ctx = build_context(tasks, hour_of_day=9, day_of_week=2) """ from __future__ import annotations from dataclasses import dataclass, field @dataclass class TaskSignal: id: str content: str priority: int = 1 # 1–4 (Todoist scale) is_overdue: bool = False task_age_days: float = 0.0 due_date: str | None = None @dataclass class PromptContext: tasks: list[dict] = field(default_factory=list) hour_of_day: int = 12 day_of_week: int = 0 extra: dict = field(default_factory=dict) def build_context( tasks: list[TaskSignal], hour_of_day: int = 12, day_of_week: int = 0, extra: dict | None = None, ) -> PromptContext: """ Assemble user signals into a PromptContext. Signals are sorted so overdue + high-priority tasks appear first, giving the LLM the most actionable context at the top of the prompt. """ sorted_tasks = sorted( tasks, key=lambda t: (not t.is_overdue, -t.priority, -t.task_age_days), ) task_dicts = [ { "id": t.id, "content": t.content, "priority": t.priority, "is_overdue": t.is_overdue, "task_age_days": round(t.task_age_days, 1), "due_date": t.due_date, } for t in sorted_tasks ] return PromptContext( tasks=task_dicts, hour_of_day=hour_of_day, day_of_week=day_of_week, extra=extra or {}, )