2d40e4caa6
how-it-works.md answers what "Your profile" in the settings is for: salary type is the real lever (monthly spends vacation first, hourly spends parental-benefit days first), employment status only ever warns about SGI, and the vacation floor bounds carry-forward rather than capping spending. dubbeldagar.md answers the question that was asked. The 60 are combined per child, and the code already treats them that way. They are not a separate pot, though: on a doubled date each parent spends one of their own days, so leaving them out of the total would overstate what the family has. The per-parent dubbeldagar_left the scraper stores is never read by the allocator — flagged, not changed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ei7gCZzdd751uZtPzb1Pep
137 lines
8.2 KiB
Markdown
137 lines
8.2 KiB
Markdown
# How it works: "Your profile" in Settings
|
|
|
|
"Your profile" (per parent, `ParentProfileInput` in `frontend/src/components/inputs/ParentProfileInput.tsx`)
|
|
holds the facts the planner needs about *you* to decide how to spend your days — as opposed to
|
|
facts about the child (birth date) or the household (workspace). Two of its fields actively change
|
|
the plan the engine produces. One is a warning-only signal. One field exists but nothing reads it.
|
|
|
|
This file explains each field: what it means, what the planner does with it, and what happens if
|
|
you leave it wrong.
|
|
|
|
---
|
|
|
|
## Salary type — `monthly` or `hourly`
|
|
|
|
**What it means.** Whether your employer pays you a fixed monthly salary regardless of which days
|
|
you work, or pays you by the hour for hours actually worked.
|
|
|
|
**What the planner does with it.** This is the one field that changes *which resource gets spent
|
|
first* on an ordinary working day. `backend/app/entities/personal_data.py:21` stores it;
|
|
`backend/app/models/personal_data.py:18` validates it on the wire as `monthly` or `hourly`.
|
|
|
|
The decision lives in `_assign_tier3` in `backend/app/services/allocation_service.py:988-1024`
|
|
(the "tier-3 interior" resolution — see `PLANNING_ENGINE_SPEC.md` §3.3). For an ordinary spendable
|
|
day that isn't free (weekend/holiday) and isn't next to a red-day cluster:
|
|
|
|
- **`monthly`** → spend a **vacation** day first; a parental-benefit (FP) day only fills what
|
|
vacation can't cover (`allocation_service.py:1011-1017`).
|
|
- **`hourly`** → spend an **FP** day first; vacation is only a backstop when FP is exhausted
|
|
(`allocation_service.py:1019-1024`).
|
|
|
|
The reasoning (`PLANNING_ENGINE_SPEC.md` §3.3): a monthly-salaried parent's pay doesn't change
|
|
whether they take a vacation day or not — they lose nothing by spending vacation, so spending it
|
|
first *preserves* FP days for later, when they're worth more (more weeks of leave, or protection
|
|
against the age-4 cutoff). An hourly-paid parent has no salary to protect on a vacation day — no
|
|
work, no pay — so there's nothing to gain by spending vacation first; FP goes first because it's the
|
|
only resource that pays anything on that date, and vacation is kept in reserve as a fallback.
|
|
|
|
The same field also gates two tips in `backend/app/services/tips_service.py`: "wrap the weekend
|
|
with vacation" (`tips_service.py:418-446`, gated at line 422) and "wrap the red days with vacation"
|
|
(`tips_service.py:449-...`, gated at line 456) both `continue` past any parent whose `salary_type`
|
|
isn't `monthly` (lines 422, 456). Both tips only make sense for a monthly salary — they suggest
|
|
spending vacation to protect pay that only a monthly salary actually pays out on those days.
|
|
|
|
**What happens if it's wrong.** The plan itself doesn't become invalid — it just optimises for the
|
|
wrong thing. Set as `hourly` when you're actually salaried, the planner will burn FP days on
|
|
ordinary weeks that you could have covered with vacation for free, running out of FP sooner than
|
|
necessary. Set as `monthly` when you're actually hourly, it will spend your vacation on days you'd
|
|
have been paid nothing for anyway, needlessly depleting a budget you could have kept.
|
|
|
|
---
|
|
|
|
## Employment status — `employed`, `jobseeking`, or `none`
|
|
|
|
**What it means.** Whether you currently have a job, are registered as a jobseeker (with
|
|
Arbetsförmedlingen), or neither.
|
|
|
|
**What the planner does with it.** This field is **advisory only — it never blocks or reshapes a
|
|
plan.** It feeds a single SGI (sjukpenninggrundande inkomst) protection check.
|
|
|
|
SGI is protected automatically for the child's first 12 months (`sgi_protected_first_months` in
|
|
`backend/app/config.py:46`, default 12). After that, a parent who is neither employed nor a
|
|
registered jobseeker needs to take parental-benefit days on at least
|
|
`sgi_min_days_per_week_after_age_1` days per week (`config.py:47`, default 5) to keep their SGI
|
|
from being reassessed downward. `sgi_at_risk` in `backend/app/utils/tip_predicates.py:125-153`
|
|
implements this: if `employment_status` is `employed` or `jobseeking`, it returns `False`
|
|
immediately (line 141) — protected regardless of how few FP days are planned. Only `none` triggers
|
|
the intensity check. The allocator's own warning path,
|
|
`_sgi_warnings` in `allocation_service.py:2139-...`, applies the identical
|
|
`employment_status in ("employed", "jobseeking")` skip at line 2154, and the resulting tip
|
|
(`tips_service.py:398-415`, `_rule_sgi_minimum_after_age_1`) is `severity="warning"`, not an error.
|
|
|
|
**Why only a warning:** the app has no way to verify someone's actual employment or jobseeker
|
|
registration — that lives with the employer and Arbetsförmedlingen, not in this planner. So it
|
|
warns "you may be at risk of losing SGI protection" and lets you plan around it; it never refuses
|
|
to save a plan or force more FP days into a window because of this field.
|
|
|
|
**What happens if it's wrong.** Set to `employed`/`jobseeking` when you're actually not working or
|
|
registered: you lose the SGI warning, and the app gives no signal that your parental-benefit rate
|
|
could be reassessed downward after age 1. Set to `none` when you actually are employed or
|
|
registered: you'll see a spurious warning telling you to plan more FP days than you actually need
|
|
to protect SGI. Either way, the plan's day allocation itself is untouched.
|
|
|
|
---
|
|
|
|
## Minimum vacation days per year — `min_vacation_days_per_year`
|
|
|
|
**What it means.** The semesterlagen floor: the number of vacation days you must *take* each
|
|
calendar year. Sweden's statutory minimum is 20 (`min_vacation_days_per_year: int = 20` in
|
|
`backend/app/entities/personal_data.py:28` and `backend/app/config.py:63`); some employers set a
|
|
higher floor, so it's editable per parent rather than hardcoded.
|
|
|
|
**What the planner does with it.** It bounds two things, both about *not saving too much*, never
|
|
about *not spending enough*:
|
|
|
|
- **Vacation carry-forward.** `_carry_vacation_forward_remedy` in `allocation_service.py:1467-1492`
|
|
only offers to bank vacation days above this floor for a future year — the floor itself must stay
|
|
taken, not banked (`allocation_service.py:1480-1482`).
|
|
- **"Use less vacation" surplus advice.** When FP days are expiring and vacation could be swapped
|
|
in to save them, `allocation_service.py:2100-2137` only proposes reducing vacation usage down to
|
|
this floor, never below it (line 2115).
|
|
|
|
Deliberately **not** used as a cap on how much vacation you may spend. `PlanningService.
|
|
_vacation_days_left`'s docstring is explicit about this
|
|
(`backend/app/services/planning_service.py:1219-1224`):
|
|
|
|
> `PersonalData.min_vacation_days_per_year` is deliberately NOT used here: ... it is a semesterlagen
|
|
> floor on days that must be *taken* each year (bounding what may be saved forward), not a cap on
|
|
> how much may be spent — turning it into a spending cap here would reject perfectly legal plans.
|
|
|
|
**What happens if it's wrong.** Set too low, the carry-forward and "use less vacation" advice may
|
|
suggest banking or swapping away days you're actually required to take, risking a semesterlagen
|
|
violation the app itself won't catch (it only enforces the number you gave it). Set too high, the
|
|
planner will keep more vacation "reserved" than necessary and under-suggest carrying days forward
|
|
or swapping vacation in for expiring FP — conservative, not wrong, but leaves value on the table.
|
|
|
|
---
|
|
|
|
## What's stored but not used: `personnummer_enc`
|
|
|
|
The profile can optionally hold an encrypted personal-identity-number field
|
|
(`personnummer_enc` in `backend/app/entities/personal_data.py:29`). Storing it is **discouraged** —
|
|
the entity's own docstring says so (`personal_data.py:7-8`) and recommends `None` if you can avoid
|
|
it. It's persisted and loaded like any other field
|
|
(`backend/app/repositories_impl/sqlite/personal_data_repository.py`), but **no service reads it** —
|
|
it plays no role in planning, tips, or SGI logic. There's no benefit to filling it in.
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
| Field | Changes the plan? | How |
|
|
|---|---|---|
|
|
| `salary_type` | Yes | Decides whether vacation or FP is spent first on an ordinary day |
|
|
| `employment_status` | No — warning only | Gates one SGI warning after the child turns 1 |
|
|
| `min_vacation_days_per_year` | Yes, at the margins | Bounds vacation carry-forward and surplus advice; never a spending cap |
|
|
| `personnummer_enc` | No | Stored, never read |
|