Files
F-r-ldraledigplannerare/backend/app/models/analytics.py
T
jacobh2 cc3ced905e Link planned days to their wish, fix the age-4 cap, drop the planning goal
Three changes that together unblock treating a calendar period as one
editable object.

Migration 008 adds `planned_leave_day.wish_id` (nullable, ON DELETE SET NULL)
so a persisted day knows which `leave_wish` produced it, `is_pinned` to mark a
day the user hand-edited, and `leave_wish.target_days` so a period drawn on a
calendar need not be a whole number of weeks. Apply-time identity still keys on
day_type/is_dubbeldag only, so a wish_id-only difference is a metadata update
rather than a collision.

The age-4 savable ceiling was applying the per-child figure of 96 to each
parent's pool, modelling a two-parent family as able to save 192 days past the
child's fourth birthday. `resolve_age4_savable_cap` is now the single place
that decides 48 (couple with two parents) versus 96 (everyone else), and both
AnalyticsService and the allocator read it.

Migration 009 drops `workspace.planning_goal` and `leave_wish.goal_override`.
The allocator's tier-3 policy collapses to one rule; `salary_type` still
branches, since that reflects what a vacation day actually costs a parent
rather than a user preference.
2026-09-15 20:28:06 +02:00

61 lines
1.9 KiB
Python

"""Pydantic response schemas for analytics endpoints."""
from datetime import date
from uuid import UUID
from pydantic import BaseModel
from app.services.analytics_service import Age4Projection, WorkspaceOverview
class Age4ProjectionResponse(BaseModel):
personal_data_uuid: UUID
child_id: UUID
birth_date: date
fourth_birthday: date
total_days: int
days_used: int
planned_days_before_b4: int
projected_left_at_b4: int
savable_after_b4: int
expiring_at_b4: int
savable_cap_at_b4: int
@classmethod
def from_entity(cls, p: Age4Projection) -> "Age4ProjectionResponse":
return cls(
personal_data_uuid=p.personal_data_uuid,
child_id=p.child_id,
birth_date=p.birth_date,
fourth_birthday=p.fourth_birthday,
total_days=p.total_days,
days_used=p.days_used,
planned_days_before_b4=p.planned_days_before_b4,
projected_left_at_b4=p.projected_left_at_b4,
savable_after_b4=p.savable_after_b4,
expiring_at_b4=p.expiring_at_b4,
savable_cap_at_b4=p.savable_cap_at_b4,
)
class WorkspaceOverviewResponse(BaseModel):
workspace_id: UUID
child_id: UUID
parent_projections: list[Age4ProjectionResponse]
combined_days_used: int
combined_days_left: int
combined_expiring_at_b4: int
@classmethod
def from_entity(cls, overview: WorkspaceOverview) -> "WorkspaceOverviewResponse":
return cls(
workspace_id=overview.workspace_id,
child_id=overview.child_id,
parent_projections=[
Age4ProjectionResponse.from_entity(p) for p in overview.parent_projections
],
combined_days_used=overview.combined_days_used,
combined_days_left=overview.combined_days_left,
combined_expiring_at_b4=overview.combined_expiring_at_b4,
)