103 lines
3.7 KiB
Python
103 lines
3.7 KiB
Python
"""FetchDataService — stable contract for parental-leave data acquisition.
|
|
|
|
All features (analytics, tips, planning) depend on this interface, never on
|
|
FK's DOM or a specific scraper. Two concrete implementations:
|
|
- ManualFetchService — always ships, always works (manual user entry)
|
|
- FKScraperService — behind settings.scraper_enabled guard (pending legal review)
|
|
|
|
See ARCHITECTURE.md §2.3 for the legal/ToS gate before shipping scraping.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from abc import ABC, abstractmethod
|
|
from datetime import datetime
|
|
from uuid import UUID, uuid4
|
|
|
|
from app.entities.parental_leave import ParentalLeaveFetch, TierData
|
|
from app.repositories.parental_leave_repository import ParentalLeaveRepository
|
|
from app.utils.errors import ValidationError
|
|
|
|
|
|
class FetchDataService(ABC):
|
|
@abstractmethod
|
|
async def get_latest(
|
|
self, personal_data_uuid: UUID, child_id: UUID
|
|
) -> ParentalLeaveFetch | None:
|
|
"""Return the most recent snapshot for this parent+child, or None."""
|
|
|
|
@abstractmethod
|
|
async def list_history(
|
|
self, personal_data_uuid: UUID, child_id: UUID
|
|
) -> list[ParentalLeaveFetch]:
|
|
"""Return all historical snapshots, newest first."""
|
|
|
|
|
|
class ManualFetchService(FetchDataService):
|
|
"""Persist user-entered FK data as a snapshot with source='manual'."""
|
|
|
|
def __init__(self, repo: ParentalLeaveRepository) -> None:
|
|
self._repo = repo
|
|
|
|
async def save_manual_entry(
|
|
self,
|
|
personal_data_uuid: UUID,
|
|
workspace_id: UUID,
|
|
child_id: UUID,
|
|
total_days: int,
|
|
days_used: int,
|
|
reserved_days_total: int,
|
|
reserved_days_left: int,
|
|
transferable_days_left: int,
|
|
dubbeldagar_left: int,
|
|
sjukpenning_total: int,
|
|
sjukpenning_used: int,
|
|
lagsta_niva_total: int,
|
|
lagsta_niva_used: int,
|
|
) -> ParentalLeaveFetch:
|
|
"""Validate and persist a manual data-entry snapshot."""
|
|
days_left = total_days - days_used
|
|
if days_left < 0:
|
|
raise ValidationError("days_used cannot exceed total_days")
|
|
if reserved_days_left > reserved_days_total:
|
|
raise ValidationError("reserved_days_left cannot exceed reserved_days_total")
|
|
|
|
tier_data = TierData(
|
|
sjukpenning_total=sjukpenning_total,
|
|
sjukpenning_used=sjukpenning_used,
|
|
sjukpenning_left=sjukpenning_total - sjukpenning_used,
|
|
lagsta_niva_total=lagsta_niva_total,
|
|
lagsta_niva_used=lagsta_niva_used,
|
|
lagsta_niva_left=lagsta_niva_total - lagsta_niva_used,
|
|
)
|
|
now = datetime.now()
|
|
fetch = ParentalLeaveFetch(
|
|
id=uuid4(),
|
|
personal_data_uuid=personal_data_uuid,
|
|
workspace_id=workspace_id,
|
|
child_id=child_id,
|
|
source="manual",
|
|
fetched_at=now,
|
|
total_days=total_days,
|
|
days_used=days_used,
|
|
days_left=days_left,
|
|
reserved_days_total=reserved_days_total,
|
|
reserved_days_left=reserved_days_left,
|
|
transferable_days_left=transferable_days_left,
|
|
dubbeldagar_left=dubbeldagar_left,
|
|
tier_data=tier_data,
|
|
created_at=now,
|
|
updated_at=now,
|
|
)
|
|
return await self._repo.create(fetch)
|
|
|
|
async def get_latest(
|
|
self, personal_data_uuid: UUID, child_id: UUID
|
|
) -> ParentalLeaveFetch | None:
|
|
return await self._repo.get_latest_by_parent(personal_data_uuid, child_id)
|
|
|
|
async def list_history(
|
|
self, personal_data_uuid: UUID, child_id: UUID
|
|
) -> list[ParentalLeaveFetch]:
|
|
return await self._repo.list_history(personal_data_uuid, child_id)
|