## ZinZen algorithm
This document contains a high-level overview of the scheduling algorithm.
### 1) The Calendar is created
`Calendar::new` allocates an index space of fixed-size **slots** running from the
start to the end date, plus a one-day buffer at each end. All time↔index
conversion lives in `models/time_grid.rs` (`SLOTS_PER_HOUR`, `SLOTS_PER_DAY`,
`datetime_at`, `index_at`, ...); today one slot is one hour.
The Calendar is stored as an ordered `Vec<CalendarInterval>`. Each interval is
either `Claimable(HashSet<activity_index>)` (activities that could still take
that span) or `Occupied(activity_index, goal_id)`.
### 2) Activities are created from Goals/Budgets
Each Goal (simple or budget) is translated to one or more `Activity`s by
`activity_generator.rs`. Each Activity carries the set of `compatible_intervals`
that satisfy its Goal/Budget constraints, a `min_block_size`, a `duration_left`,
and a cached `flex`.
After this point the `activity_placer` knows nothing about dates/times — it
reasons purely about integer indices, block sizes and interval lengths. It
picks an Activity and calls `Calendar::register`, which splits the affected
intervals and records the claim; conflict counts are derived from how many
activities claim an interval.
### 3) Simple Goal Activities are scheduled
```
loop {
Calculate flex of each Activity.
If there is an Activity with flex == 1, schedule that,
else schedule the Activity with the highest flexibility
in the place with the least conflict with other Activities.
}
```
### 4) Budget Goal Activities are calculated
Same loop as in 3, run for the min-day, min-week and top-up-week budget phases.
### 5) Calendar print
`Calendar::print_new` consolidates intervals by claiming activity, splits them
on day boundaries (`SLOTS_PER_DAY`), and groups sequential slots occupied by the
same Activity into a `Task`. The whole pipeline (`run_scheduler`) returns
`Result<FinalTasks, SchedulerError>`; invalid input is reported as a typed error
rather than a panic.
## Why this algorithm?
If you'r not convinced this algorithm is good - please suggest a better one. I'm all ears!
Here's a short explanation / test case to show that giving priority to least flexible Steps is wrong:
<img src="/documentation/functional/the-wrong-way-of-scheduling.png" alt="The-wrong-way" width="800"/>
To make it extra clear, here is the correct way of scheduling:
<img src="/documentation/functional/the-correct-way-of-scheduling.png" alt="The-correct-way" width="800"/>