Flag the superseded sections of the design docs
ARCHITECTURE.md and PLANNING_ENGINE_SPEC.md still described the planning goal, the per-wish goal override, and a per-parent age-4 ceiling of 96 days as current. Rather than rewrite two long documents that remain useful as history, each stale section now carries a SUPERSEDED banner naming what is true instead. The banners sit above the code fences they annotate, not inside them.
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
# Planning Engine — Design Spec
|
||||
|
||||
> Parts of this document are superseded — search for **SUPERSEDED** for the current behaviour.
|
||||
|
||||
**Status:** Draft for review. No code written yet.
|
||||
**Author:** Senior (design) — implementation to be handed to Sonnet subagents, phase by phase.
|
||||
**Supersedes:** nothing. **Extends:** ARCHITECTURE.md §6 (tips engine) and §6.5 (age-4 projection).
|
||||
@@ -18,6 +20,8 @@ That decision is the product. ARCHITECTURE.md §6 already states the principle:
|
||||
> Because the app does not compute kronor, the "money" dimension is expressed qualitatively through
|
||||
> two related levers: **weekly intensity** and **SGI protection**.
|
||||
|
||||
> **SUPERSEDED (2026-09-15).** `planning_goal` was removed entirely (migration 009). The engine now applies a single policy based solely on `salary_type`.
|
||||
|
||||
So the engine must not have a fixed spend order. Choosing semester vs föräldrapenning on a given day
|
||||
*is* the optimisation, and it is driven by `planning_goal` (`max_time` / `max_money` / `balanced`)
|
||||
crossed with the parent's `salary_type` (`monthly` / `hourly`).
|
||||
@@ -64,6 +68,8 @@ is **zero today — `select count(*) from break_period` returns 0.** If we defer
|
||||
`LeaveWish`. Rejected — the materialised rows are derivable, and persisting derived state invites
|
||||
drift.
|
||||
|
||||
> **SUPERSEDED (2026-09-15).** `goal_override` was removed entirely (migration 009).
|
||||
|
||||
```python
|
||||
# backend/app/entities/leave_wish.py
|
||||
@dataclass
|
||||
@@ -125,6 +131,8 @@ ALTER TABLE workspace ADD COLUMN planning_mode TEXT NOT NULL DEFAULT 'solo'
|
||||
|
||||
### 2.3 No change to `planned_leave_day`
|
||||
|
||||
> **SUPERSEDED (2026-09-15).** `planned_leave_day` gained `wish_id` (migration 008) linking days back to the wish that produced them, and `is_pinned` to mark hand-edited days the allocator should not retype.
|
||||
|
||||
The proposal writes ordinary `planned_leave_day` rows on apply (`day_type` stays
|
||||
`'sjukpenning' | 'lagsta' | 'vacation'`). The existing calendar, dashboard and analytics then work
|
||||
unchanged. This is deliberate: the engine produces *nothing the user could not have entered by hand*.
|
||||
@@ -148,6 +156,8 @@ step that runs before funding, for that subset of wishes.
|
||||
|
||||
### 3.1 Input
|
||||
|
||||
> **SUPERSEDED (2026-09-15).** `planning_goal` was removed entirely (migration 009).
|
||||
|
||||
```python
|
||||
@dataclass(frozen=True)
|
||||
class AllocationInput:
|
||||
@@ -186,6 +196,8 @@ year-wrap/vacation-straddle rules above without any change.
|
||||
|
||||
### 3.3 Choose a strategy per window — dominance first, goal only for ties
|
||||
|
||||
> **SUPERSEDED (2026-09-15).** `planning_goal` was removed entirely (migration 009). The allocator now applies a single policy based solely on `salary_type`, skipping the goal-driven tier-3 choice entirely.
|
||||
|
||||
A naive design makes `planning_goal` a hard switch between "spend semester" and "spend FP". That is
|
||||
wrong, and the Jullov case shows why:
|
||||
|
||||
@@ -213,6 +225,8 @@ salary-type independent, so red-day-cluster vacation is favourable for hourly pa
|
||||
bracketing a bare Sat/Sun with Fri+Mon vacation buys nothing — but it does **not** justify suppressing
|
||||
red-day-cluster vacation.
|
||||
|
||||
> **SUPERSEDED (2026-09-15).** Tier 3 with goal-dependent choices was removed entirely. All tier-3 decisions now depend solely on `salary_type`.
|
||||
|
||||
**Tier 3 — goal-dependent interior.** Ordinary working days with no red-day adjacency, where semester
|
||||
vs FP is a real tradeoff. Here, and only here, consult the resolved goal
|
||||
(`wish.goal_override or planning_goal`):
|
||||
@@ -230,6 +244,8 @@ vs FP is a real tradeoff. Here, and only here, consult the resolved goal
|
||||
then resolved through the tiers above. This remains the single biggest lever in the app — per §6.1,
|
||||
two halves of length L yield ≈2×L weeks of family time (3+3 weeks ≈ 6 weeks together).
|
||||
|
||||
> **SUPERSEDED (2026-09-15).** `goal_override` was removed entirely (migration 009). This test is no longer applicable.
|
||||
|
||||
**Corollary worth testing:** because tiers 1–2 are goal-independent, setting `goal_override =
|
||||
'max_money'` on a red-day-dense window like Jullov should produce **the same allocation** as
|
||||
`max_time` would. The override is a UI convenience for the user's mental model, not a semantic
|
||||
@@ -274,6 +290,8 @@ Constraints — all hard unless marked:
|
||||
being modelled correctly.)*
|
||||
- **Dubbeldagar**: only within the child's first 15 months, ≤60 total, consumes **both** parents'
|
||||
pools for that date. Only reachable via `parent_mode == 'both'`.
|
||||
> **SUPERSEDED (2026-09-15).** The age-4 savable cap was incorrectly applied as 96 per parent. It is now 48 per parent in couple mode with two parents, and 96 otherwise.
|
||||
|
||||
- **Age-4 is an objective *before* B4 and a hard cap *after* it** (§9.1). Before the 4th birthday,
|
||||
days above 96 that remain will be *lost*, so the allocator mildly **prefers** spending before B4 and
|
||||
warns when `projected_left_at_b4 > 96`. From B4 onward the available pool is hard-capped at
|
||||
@@ -338,6 +356,8 @@ with existing planned days unless `replace: true` is passed. Routers stay thin;
|
||||
|
||||
## 5. Test matrix (the acceptance criteria)
|
||||
|
||||
> **SUPERSEDED (2026-09-15).** Several test cases are outdated: (1) Tests 1, 3–4 assume `planning_goal` is operative (removed migration 009); (2) Test 14 refers to `goal_override` (removed); (3) Tests 8 and 12 use the incorrect 96-day-per-parent cap (now 48 per parent in couple mode with two parents, 96 otherwise).
|
||||
|
||||
Against the **pure** `Allocator`, with a frozen `today`. Target ≥90% on new backend code.
|
||||
|
||||
| # | Scenario | Expected |
|
||||
@@ -432,6 +452,8 @@ test matrix lives. Phases A–E each end with `graphify update .` per CLAUDE.md.
|
||||
1. **Horizon runs past age 4.** Wishes may extend beyond the 4th birthday and the app must not block
|
||||
that. Consequence — the FP budget becomes **time-dependent**, which the allocator must model:
|
||||
|
||||
> **SUPERSEDED (2026-09-15).** The 96-day cap is now 48 per parent in couple mode with two parents, 96 otherwise.
|
||||
|
||||
```
|
||||
FP available for a day d: d < fourth_birthday → full remaining pool
|
||||
d >= fourth_birthday → min(remaining, 96)
|
||||
@@ -450,6 +472,8 @@ test matrix lives. Phases A–E each end with `graphify update .` per CLAUDE.md.
|
||||
reserve is **user-configurable** (`preschool_planning_days_per_year`, default 6 — preschools
|
||||
differ), so it needs a per-child settings field, not just the config default.
|
||||
|
||||
> **SUPERSEDED (2026-09-15).** `goal_override` was removed entirely (migration 009).
|
||||
|
||||
4. **Wish-level goal override: yes, as a nullable field now, no UI in v1.** `LeaveWish.goal_override:
|
||||
str | None`; the allocator resolves `goal = wish.goal_override or input.planning_goal`. Costs one
|
||||
field and one test, and avoids a second migration when the UI arrives.
|
||||
@@ -517,6 +541,8 @@ open.
|
||||
One new field on `LeaveWish`, added to the trailing defaulted group so every existing construction
|
||||
site (fixtures, the repo, the service) keeps compiling unchanged:
|
||||
|
||||
> **SUPERSEDED (2026-09-15).** `goal_override` was removed entirely (migration 009).
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class LeaveWish:
|
||||
@@ -650,6 +676,8 @@ one collision key are unchanged.
|
||||
know about the 15-month rule at all.
|
||||
- **Reserved days.** No interaction — reserved days are a transfer constraint on `parent_swap`
|
||||
composition (§3.5), orthogonal to which window was picked.
|
||||
> **SUPERSEDED (2026-09-15).** `planned_leave_day` gained `wish_id` (migration 008) linking days back to the wish that produced them, and `is_pinned` to mark hand-edited days the allocator should not retype.
|
||||
|
||||
- **Applied flexible wishes participate in re-search.** On a subsequent `propose()` call (after the
|
||||
family edits wishes), a previously-applied flexible wish is re-searched from scratch within its bound
|
||||
and may land in a different window if the occupied set has changed (other wishes added, edited or
|
||||
|
||||
Reference in New Issue
Block a user