# prediction_state.py from datetime import datetime from dateutil.relativedelta import relativedelta from typing import List, Optional, Dict, Any, TYPE_CHECKING if TYPE_CHECKING: from calendar_entry import CalendarEntry import uuid class PredictionState: """Represents the complete state of a prediction including all metadata""" def __init__(self, launch_date: Optional[datetime] = None, duration_years: Optional[int] = None, prediction_date: Optional[datetime] = None, entries: Optional[List["CalendarEntry"]] = None): self.launch_date = launch_date self.duration_years = duration_years self.prediction_date = prediction_date self.entries = entries or [] self.created_at = datetime.now() self.version = "1.0" def to_dict(self) -> Dict[str, Any]: """Convert state to dictionary for serialization""" return { 'version': self.version, 'created_at': self.created_at.isoformat(), 'launch_date': self.launch_date.strftime('%Y-%m-%d') if self.launch_date else None, 'duration_years': self.duration_years, 'prediction_date': self.prediction_date.strftime('%Y-%m-%d') if self.prediction_date else None, 'entries': [self._entry_to_dict_without_id(entry) for entry in self.entries] } def _entry_to_dict_without_id(self, entry: "CalendarEntry") -> Dict[str, Any]: """Convert entry to dictionary without ID for better readability""" return { 'start_date': entry.start_date.strftime('%Y-%m-%d'), 'end_date': entry.end_date.strftime('%Y-%m-%d'), 'keyword': entry.keyword, 'commentary': entry.commentary, 'corrected_start_date': entry.corrected_start_date.strftime('%Y-%m-%d') if entry.corrected_start_date else None, 'corrected_end_date': entry.corrected_end_date.strftime('%Y-%m-%d') if entry.corrected_end_date else None, 'time_period': entry.time_period if hasattr(entry, 'time_period') else None } @classmethod def from_dict(cls, data: Dict[str, Any]) -> 'PredictionState': """Create state from dictionary, generating new IDs for entries""" from calendar_entry import CalendarEntry state = cls() state.version = data.get('version', '1.0') # created_at can include time; others are date-only strings state.created_at = datetime.fromisoformat(data['created_at']) if data.get('created_at') else datetime.now() state.launch_date = datetime.fromisoformat(data['launch_date']) if data.get('launch_date') else None state.duration_years = data.get('duration_years') state.prediction_date = datetime.fromisoformat(data['prediction_date']) if data.get('prediction_date') else None # Create entries with new IDs state.entries = [] for entry_data in data.get('entries', []): entry = CalendarEntry( start_date=entry_data['start_date'], end_date=entry_data['end_date'], keyword=entry_data['keyword'], entry_id=str(uuid.uuid4()), # Generate new ID corrected_start_date=datetime.fromisoformat(entry_data['corrected_start_date']) if entry_data.get('corrected_start_date') else None, corrected_end_date=datetime.fromisoformat(entry_data['corrected_end_date']) if entry_data.get('corrected_end_date') else None, commentary=entry_data.get('commentary', '') ) # Set time_period if available if entry_data.get('time_period'): entry.time_period = entry_data['time_period'] state.entries.append(entry) return state