Rework balances around per-parent days left and deduce the partner from FK
Manual entry now takes one parent's sjukpenningnivå, lägstanivå and dubbeldagar left (plus optional reserved days) and derives the rest in build_snapshot, shared with the scraper. The scraper also reads the family panel and exposes a partner deduction; with two parents it is saved as a 'deduced' snapshot (migration 002). Parent count is the single source of entitlement: one member gets 480 days and 96 savable at age 4, two get 240 and 48 each. Workspaces are capped at two parents. Also lands the in-flight squash of migrations 002–011 into the initial schema and the removal of planning_mode. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -41,7 +41,6 @@ from app.repositories.personal_data_repository import PersonalDataRepository
|
||||
from app.repositories.planned_leave_repository import PlannedLeaveRepository
|
||||
from app.repositories.tip_dismissal_repository import TipDismissalRepository
|
||||
from app.repositories.vacation_days_repository import VacationDaysRepository
|
||||
from app.repositories.workspace_repository import WorkspaceRepository
|
||||
from app.utils.errors import NotFoundError
|
||||
from app.utils.tip_predicates import (
|
||||
age4_pooling_loss,
|
||||
@@ -91,10 +90,6 @@ class TipsContext:
|
||||
vacation_by_parent: dict[UUID, list[VacationDays]]
|
||||
leave_wishes: list[LeaveWish]
|
||||
today: date # injected for deterministic testing
|
||||
# 'solo' | 'couple' — see `app.utils.tip_predicates.resolve_age4_savable_cap`.
|
||||
# Defaulted for backward compatibility with call sites/tests that don't
|
||||
# care about the age-4 cap split.
|
||||
workspace_planning_mode: str = "solo"
|
||||
|
||||
|
||||
# Type alias for a rule function.
|
||||
@@ -161,7 +156,7 @@ async def _rule_preserve_for_age_4(ctx: TipsContext) -> Tip | None:
|
||||
return None
|
||||
child = ctx.children[0]
|
||||
fourth_bday = child.birth_date + _relativedelta(years=4)
|
||||
savable_cap = resolve_age4_savable_cap(ctx.workspace_planning_mode, len(ctx.parents))
|
||||
savable_cap = resolve_age4_savable_cap(len(ctx.parents))
|
||||
for parent in ctx.parents:
|
||||
leave = ctx.leave_by_parent.get(parent.uuid)
|
||||
if leave is None:
|
||||
@@ -192,10 +187,14 @@ async def _rule_preserve_for_age_4(ctx: TipsContext) -> Tip | None:
|
||||
|
||||
|
||||
async def _rule_age4_pooling_loss(ctx: TipsContext) -> Tip | None:
|
||||
"""Warn a couple when one parent is over the per-parent age-4 cap while
|
||||
the other has unused headroom — days that could have been pooled but
|
||||
won't be, because `resolve_age4_savable_cap` splits the ceiling per
|
||||
parent once a workspace is in `couple` mode (see `age4_pooling_loss`).
|
||||
"""Warn a family when one parent is over the per-parent age-4 cap while
|
||||
|
||||
another has unused headroom — days that could have been pooled but won't
|
||||
be, because `resolve_age4_savable_cap` splits the ceiling per parent as
|
||||
soon as the child has 2+ parents (see `age4_pooling_loss`). This fires
|
||||
for any 2+-parent family — the per-parent cut (and the pooling loss it
|
||||
can cause) is a legal fact about the child having two parents, purely a
|
||||
function of parent count.
|
||||
|
||||
Advice order is the substance of this tip: taking more leave before the
|
||||
birthday converts entitlement into actual time at home (strictly
|
||||
@@ -207,7 +206,7 @@ async def _rule_age4_pooling_loss(ctx: TipsContext) -> Tip | None:
|
||||
exact transfer figure can't be known, so the tip degrades to a hedged
|
||||
"if you both have days left" phrasing instead of guessing a number.
|
||||
"""
|
||||
if ctx.workspace_planning_mode != "couple" or not ctx.children:
|
||||
if len(ctx.parents) < 2 or not ctx.children:
|
||||
return None
|
||||
|
||||
child = ctx.children[0]
|
||||
@@ -215,7 +214,7 @@ async def _rule_age4_pooling_loss(ctx: TipsContext) -> Tip | None:
|
||||
return None # first couple of years: too early to usefully act on this
|
||||
|
||||
fourth_bday = child.birth_date + _relativedelta(years=4)
|
||||
per_parent_cap = resolve_age4_savable_cap(ctx.workspace_planning_mode, len(ctx.parents))
|
||||
per_parent_cap = resolve_age4_savable_cap(len(ctx.parents))
|
||||
per_child_cap = settings.rules.max_savable_days_at_age_4
|
||||
|
||||
projected_left_by_parent: dict[UUID, int] = {}
|
||||
@@ -358,7 +357,7 @@ async def _rule_stretch_time_at_home(ctx: TipsContext) -> Tip | None:
|
||||
fourth_bday = child.birth_date + _relativedelta(years=4)
|
||||
today = ctx.today
|
||||
window_end = today + timedelta(weeks=4)
|
||||
savable_cap = resolve_age4_savable_cap(ctx.workspace_planning_mode, len(ctx.parents))
|
||||
savable_cap = resolve_age4_savable_cap(len(ctx.parents))
|
||||
|
||||
for parent in ctx.parents:
|
||||
parent_window_days = [
|
||||
@@ -604,7 +603,6 @@ class TipsService:
|
||||
pd_repo: PersonalDataRepository,
|
||||
vacation_repo: VacationDaysRepository,
|
||||
leave_wish_repo: LeaveWishRepository,
|
||||
workspace_repo: WorkspaceRepository,
|
||||
dismissal_repo: TipDismissalRepository,
|
||||
) -> None:
|
||||
self._leave = leave_repo
|
||||
@@ -613,7 +611,6 @@ class TipsService:
|
||||
self._parents = pd_repo
|
||||
self._vacation = vacation_repo
|
||||
self._leave_wishes = leave_wish_repo
|
||||
self._workspace_repo = workspace_repo
|
||||
self._dismissals = dismissal_repo
|
||||
|
||||
async def get_tips(self, workspace_id: UUID) -> list[Tip]:
|
||||
@@ -725,9 +722,6 @@ class TipsService:
|
||||
personal_data_uuid=pd.uuid, workspace_id=workspace_id
|
||||
)
|
||||
|
||||
ws = await self._workspace_repo.get_by_id(workspace_id)
|
||||
planning_mode = ws.planning_mode if ws else "solo"
|
||||
|
||||
workspace_ctx = TipsContext(
|
||||
workspace_id=workspace_id,
|
||||
children=[],
|
||||
@@ -737,7 +731,6 @@ class TipsService:
|
||||
vacation_by_parent=vacation_by_parent,
|
||||
leave_wishes=leave_wishes,
|
||||
today=today,
|
||||
workspace_planning_mode=planning_mode,
|
||||
)
|
||||
|
||||
child_contexts: list[TipsContext] = []
|
||||
@@ -758,7 +751,6 @@ class TipsService:
|
||||
vacation_by_parent=vacation_by_parent,
|
||||
leave_wishes=leave_wishes,
|
||||
today=today,
|
||||
workspace_planning_mode=planning_mode,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user