from datetime import datetime from typing import List, Optional from pydantic import BaseModel, Field from typing_extensions import Literal class WorkDetail(BaseModel): group_name: str duration_seconds: float class WorkSession(BaseModel): start_time: datetime end_time: datetime details: List[WorkDetail] synchronized: bool = False id: Optional[str] = None # ID work item в YouTrack youtrack_created_at: Optional[datetime] = None # Время создания в YouTrack для сопоставления description: Optional[str] = None # Описание для сопоставления (опционально) class Config: json_encoders = { datetime: lambda v: v.isoformat() } class TrackedTask(BaseModel): task_id: str project_id: Optional[str] = None estimated_seconds: Optional[float] = None start_time: datetime end_time: Optional[datetime] = None sessions: List[WorkSession] = Field(default_factory=list) @property def total_worked_seconds(self) -> float: return sum( detail.duration_seconds for session in self.sessions for detail in session.details ) @property def remaining_seconds(self) -> Optional[float]: if self.estimated_seconds is None: return None return max(0.0, self.estimated_seconds - self.total_worked_seconds) class WindowPolicy(BaseModel): group_name: str window_patterns: List[str] = Field(default_factory=list) process_names: List[str] = Field(default_factory=list) policy_type: Literal["work", "blocked"] class TrackerConfig(BaseModel): tracking_enabled: bool = False current_task_id: Optional[str] = None window_policies: List[WindowPolicy] = Field(default_factory=list) @staticmethod def default_policies() -> List[WindowPolicy]: return [ WindowPolicy( group_name="IDE", window_patterns=["PyCharm", "IntelliJ", "Visual Studio Code", "Code", "Sublime", "Vim", "Neovim"], process_names=["pycharm64.exe", "idea64.exe", "code.exe", "sublime_text.exe", "nvim.exe", "vim.exe"], policy_type="work" ), WindowPolicy( group_name="Gaming & Distractions", window_patterns=["Steam", "Discord", "YouTube", "Twitch", "Spotify", "Netflix"], process_names=["steam.exe", "discord.exe", "spotify.exe"], policy_type="blocked" ), ] def __init__(self, **data): super().__init__(**data) if not self.window_policies: self.window_policies = self.default_policies() class YouTrackTimeTrackerEntryDuration(BaseModel): minutes: int class YouTrackTimeTrackerEntry(BaseModel): id: str duration: YouTrackTimeTrackerEntryDuration @property def minutes(self) -> int: return self.duration.minutes