Initial commit

This commit is contained in:
2026-06-01 16:48:21 +02:00
commit 53d157535a
159 changed files with 13641 additions and 0 deletions
View File
+24
View File
@@ -0,0 +1,24 @@
"""Domain entity: BreakPeriod.
A user-defined window the family wants to be home together (e.g. summer, winter
holidays). Drives the parent-swap tip. Lengths and dates are entirely family-specific;
seeded with editable defaults.
"""
from dataclasses import dataclass
from datetime import date, datetime
from uuid import UUID
@dataclass
class BreakPeriod:
"""A user-defined window for the family to be home together."""
id: UUID
workspace_id: UUID
name: str # 'Summer', 'Winter holidays', ...
start_date: date
end_date: date
target_weeks: int # desired weeks home together for this break
created_at: datetime | None = None
updated_at: datetime | None = None
+15
View File
@@ -0,0 +1,15 @@
"""Domain entity: Child (per-child entitlement anchor)."""
from dataclasses import dataclass
from datetime import date, datetime
from uuid import UUID
@dataclass
class Child:
id: UUID
workspace_id: UUID
name: str
birth_date: date # use expected_birth_date (due date) if unborn
preschool_start_date: date | None = None # drives the preschool-day reserve tip
created_at: datetime | None = None
+44
View File
@@ -0,0 +1,44 @@
"""Domain entity: ParentalLeaveFetch.
Represents a snapshot of a parent's Försäkringskassan data — either scraped via
the headless-browser flow or entered manually. The `source` field records the
provenance so the UI can display freshness information ("Manually entered on …").
tier_data is stored as a JSON string in the DB and should be decoded on load.
"""
from dataclasses import dataclass, field
from datetime import datetime
from uuid import UUID
@dataclass
class TierData:
"""Breakdown of days by compensation tier."""
sjukpenning_total: int
sjukpenning_used: int
sjukpenning_left: int
lagsta_niva_total: int
lagsta_niva_used: int
lagsta_niva_left: int
@dataclass
class ParentalLeaveFetch:
id: UUID
personal_data_uuid: UUID
workspace_id: UUID
child_id: UUID
source: str # 'scraped' | 'manual'
fetched_at: datetime
total_days: int
days_used: int
days_left: int
reserved_days_total: int
reserved_days_left: int
transferable_days_left: int
dubbeldagar_left: int
tier_data: TierData
created_at: datetime | None = field(default=None)
updated_at: datetime | None = field(default=None)
+25
View File
@@ -0,0 +1,25 @@
"""Domain entity: PersonalData (parent profile).
A PersonalData represents a parent in the planning workspace. It intentionally
exists without a User login so that solo planning for a partner who hasn't
registered is possible (see ARCHITECTURE.md §3.0).
personnummer storage is discouraged (see §11). If kept, personnummer_enc must
be keyed HMAC / authenticated encryption — never a plain SHA-256.
"""
from dataclasses import dataclass, field
from datetime import datetime
from uuid import UUID
@dataclass
class PersonalData:
uuid: UUID
workspace_id: UUID
name: str
salary_type: str = "monthly" # 'monthly' | 'hourly'
employment_status: str = "employed" # 'employed' | 'jobseeking' | 'none'
personnummer_enc: str | None = None # encrypted/HMAC; prefer None
created_at: datetime | None = field(default=None)
deleted_at: datetime | None = field(default=None)
+18
View File
@@ -0,0 +1,18 @@
"""Domain entity: PlannedLeaveDay (a single calendar day of planned leave)."""
from dataclasses import dataclass
from datetime import date, datetime
from uuid import UUID
@dataclass
class PlannedLeaveDay:
id: UUID
workspace_id: UUID
child_id: UUID
parent_personal_data_uuid: UUID
date: date
day_type: str # 'sjukpenning' | 'lagsta' | 'vacation'
is_dubbeldag: bool = False # True → counts against the 60-day dubbeldagar quota
created_at: datetime | None = None
updated_at: datetime | None = None
+15
View File
@@ -0,0 +1,15 @@
"""Domain entity: User (login account, linked 1:1 to PersonalData)."""
from dataclasses import dataclass
from datetime import datetime
from uuid import UUID
@dataclass
class User:
id: UUID
personal_data_uuid: UUID # FK to PersonalData; workspace derived from it
email: str
hashed_password: str # argon2 hash; never plain SHA
created_at: datetime
is_active: bool = True
+29
View File
@@ -0,0 +1,29 @@
"""Domain entity: VacationDays.
This is the REFERENCE entity — copy this shape for the other entities
(parental_leave, planned_leave, child, break_period, ...). A domain entity is a
plain dataclass with no framework or persistence concerns.
"""
from dataclasses import dataclass
from datetime import datetime
from uuid import UUID
@dataclass
class VacationDays:
"""Manual vacation (semester) entry per parent per year.
Swedish semester is typically 25 statutory days/year; days above 20 may be
saved up to 5 years. `saved_days_in` carries those over so the tips engine
can warn before they expire.
"""
id: UUID
personal_data_uuid: UUID
workspace_id: UUID
year: int
total_days: int # entitlement for this year
saved_days_in: int = 0 # carried over from prior years
created_at: datetime | None = None
updated_at: datetime | None = None
+13
View File
@@ -0,0 +1,13 @@
"""Domain entity: Workspace (shared planning space for 1–2 parents)."""
from dataclasses import dataclass
from datetime import datetime
from uuid import UUID
@dataclass
class Workspace:
id: UUID
name: str
planning_goal: str = "balanced" # 'max_time' | 'max_money' | 'balanced'
created_at: datetime | None = None
@@ -0,0 +1,22 @@
"""Domain entity: WorkspaceInvitation (single-use partner invite).
token_hash stores SHA-256(raw_token). The raw token is returned to the caller
once and never persisted. Redemption compares SHA-256(presented_token) against
this hash.
"""
from dataclasses import dataclass
from datetime import datetime
from uuid import UUID
@dataclass
class WorkspaceInvitation:
id: UUID
workspace_id: UUID
token_hash: str # SHA-256 of the one-time token; raw token never stored
invited_by_user_id: UUID
status: str # 'pending' | 'accepted' | 'revoked' | 'expired'
expires_at: datetime
created_at: datetime
accepted_at: datetime | None = None