Skip to content

domain/energy — energy plan generation

backend/src/domain/energy.py (62 lines). Pure energy-planning functions (energy.py:1): given a set of habits with energy costs/returns, produce a 21-day schedule and its cumulative net energy.

Types

Type Fields Notes
Habit (frozen dataclass) id, name, energy_cost, energy_return; property net_energy = energy_return - energy_cost Domain-local shape, distinct from the ORM models.Habit (energy.py:8-20)
EnergyPlanItem habit_id, date One scheduled occurrence (energy.py:23-28)
EnergyPlan items: list[EnergyPlanItem], net_energy: int The full schedule (energy.py:31-36)

PLAN_DURATION_DAYS = 21 — one standard stage cycle: "Stages 1-8 each last 21 days (3 weeks). Energy plans are generated for this duration so the user has a full stage's worth of scheduled habits at a time" (energy.py:39-42).

generate_plan(habits, start_date) -> tuple[EnergyPlan, str]

The complete algorithm (backend/src/domain/energy.py:45-62):

def generate_plan(habits: Sequence[Habit], start_date: date) -> tuple[EnergyPlan, str]:
    """Generate a single-stage energy plan cycling through ``habits``.

    The plan covers :data:`PLAN_DURATION_DAYS` (21 days, one standard stage
    cycle). Returns the plan and a ``reason_code`` for auditability.
    """
    if not habits:
        raise ValueError("habits must not be empty")

    items: list[EnergyPlanItem] = []
    net_energy = 0
    for offset in range(PLAN_DURATION_DAYS):
        habit = habits[offset % len(habits)]
        plan_date = start_date + timedelta(days=offset)
        items.append(EnergyPlanItem(habit_id=habit.id, date=plan_date))
        net_energy += habit.net_energy
    plan = EnergyPlan(items=items, net_energy=net_energy)
    return plan, "generated_21_day_plan"

Rules: exactly one habit per day for 21 days, round-robin (habits[offset % len(habits)]); net_energy is the sum of each scheduled occurrence's net_energy; empty input raises ValueError; the reason code "generated_21_day_plan" is persisted alongside the plan for auditability (EnergyPlan.reason_code, backend/src/models/energy_plan.py:68).

Worked example

Habits A (cost=2, return=5 → net +3) and B (cost=4, return=3 → net −1), start_date=2026-07-01: days 0,2,4,… get A and days 1,3,5,… get B; 21 days = 11×A + 10×B; net_energy = 11·3 + 10·(−1) = 23. Items run 2026-07-01 through 2026-07-21 inclusive.

Durable storage and idempotent replay of generated plans live in data-model/habits-goals and the endpoint behavior in api/energy.


Grounded in adepthood@fbc529d, 2026-07-31.