cc3ced905e
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.
104 lines
4.4 KiB
Python
104 lines
4.4 KiB
Python
"""Application configuration (pydantic-settings).
|
|
|
|
`Settings` loads from environment variables (prefix ``APP_``) and an optional
|
|
``.env`` file, falling back to the defaults below for local dev. Keep secrets
|
|
(JWT_SECRET, HMAC_PEPPER, scraper credentials, ...) in the environment / a
|
|
secret store, never in source.
|
|
|
|
The Swedish parental-leave rule values live in the nested, frozen ``LeaveRules``
|
|
model. They are domain defaults rather than routine deployment config, but are
|
|
modelled as settings so they're typed, validated, and overridable should a rule
|
|
year change (e.g. ``APP_RULES__MAX_SAVABLE_DAYS_AT_AGE_4=96``). NOTE: these are
|
|
the common-case figures — children born before 2014, multiples, and single
|
|
custody differ and should become explicit config when tackled.
|
|
"""
|
|
|
|
from pydantic import BaseModel, ConfigDict
|
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
|
|
|
|
class LeaveRules(BaseModel):
|
|
"""Frozen Swedish föräldrapenning rule defaults (single source of truth)."""
|
|
|
|
model_config = ConfigDict(frozen=True)
|
|
|
|
total_days_per_child: int = 480
|
|
sjukpenning_days: int = 390 # income-based tier
|
|
lagsta_niva_days: int = 90 # flat low-rate tier
|
|
|
|
days_per_parent: int = 240 # default joint-custody split
|
|
reserved_days_per_parent: int = 90 # non-transferable ("reserverade dagar")
|
|
|
|
max_dubbeldagar: int = 60 # both parents, same child-day
|
|
dubbeldagar_months_limit: int = 15 # only within the child's first 15 months
|
|
|
|
max_savable_days_at_age_4: int = 96 # age-4 ceiling — per CHILD (pooled across parents)
|
|
# Age-4 ceiling — per PARENT. The 96-day figure above is a per-child pool;
|
|
# when a child has two parents, each parent's own savable balance is
|
|
# capped at half of that (48), not 96 each (which would let a two-parent
|
|
# family save 192 combined). `app.utils.tip_predicates.resolve_age4_savable_cap`
|
|
# is the single place that decides which of the two applies.
|
|
max_savable_days_per_parent_at_age_4: int = 48
|
|
hard_expiry_age_years: int = 12
|
|
|
|
sgi_protected_first_months: int = 12
|
|
sgi_min_days_per_week_after_age_1: int = 5
|
|
|
|
# Default reserve for preschool planning-day ("studiedagar") closures — the
|
|
# per-child value is user-editable; this is just the seeded default.
|
|
preschool_planning_days_per_year: int = 6
|
|
|
|
# Swedish statutory semester entitlement (semesterlagen). Used by
|
|
# `AllocationInputLoader` to default a vacation-year the user has not
|
|
# entered yet — "not entered" must never be read as "zero days".
|
|
statutory_vacation_days_per_year: int = 25
|
|
|
|
# Semesterlagen requires at least this many vacation days to be *taken*
|
|
# each year; only days above it may be saved (up to 5 years). An employer
|
|
# may set a higher floor, so this is only the seeded default — the
|
|
# per-parent value on PersonalData.min_vacation_days_per_year is what the
|
|
# allocator actually enforces (PLANNING_ENGINE_SPEC.md §9.2).
|
|
min_vacation_days_per_year: int = 20
|
|
|
|
@property
|
|
def transferable_days_per_parent(self) -> int:
|
|
return self.days_per_parent - self.reserved_days_per_parent
|
|
|
|
|
|
class Settings(BaseSettings):
|
|
model_config = SettingsConfigDict(
|
|
env_prefix="APP_",
|
|
env_nested_delimiter="__",
|
|
env_file=".env",
|
|
extra="ignore",
|
|
)
|
|
|
|
# 'sqlite' for local dev, 'postgres' for production (swap repository impls).
|
|
database_type: str = "sqlite"
|
|
sqlite_path: str = "./foraldraledig.db"
|
|
|
|
# Lock CORS down to the frontend origin(s).
|
|
cors_origins: list[str] = ["http://localhost:5173"]
|
|
|
|
# Auth — change jwt_secret in production (min 32 chars).
|
|
jwt_secret: str = "dev-secret-change-in-production-min-32-chars"
|
|
jwt_algorithm: str = "HS256"
|
|
jwt_access_token_expire_minutes: int = 15
|
|
jwt_refresh_token_expire_days: int = 7
|
|
|
|
log_level: str = "DEBUG"
|
|
|
|
# FK scraper — legal/ToS review completed (2026-06). Default is False (conservative).
|
|
# Set APP_SCRAPER_ENABLED=true in the environment or backend/.env to enable.
|
|
# Playwright chromium binaries must be installed: `playwright install chromium`.
|
|
scraper_enabled: bool = False
|
|
scraper_headless: bool = True
|
|
scraper_session_ttl_seconds: int = 180 # auto-cancel awaiting_scan/authenticating sessions
|
|
# Set to None to disable debug artifact saving. Files may contain personal data.
|
|
scraper_debug_dir: str | None = "./debug/scraper"
|
|
|
|
rules: LeaveRules = LeaveRules()
|
|
|
|
|
|
settings = Settings()
|