diff options
| -rw-r--r-- | calendar_gui.py | 61 | ||||
| -rw-r--r-- | prediction_controller.py | 24 | ||||
| -rw-r--r-- | prediction_state.py | 77 | ||||
| -rw-r--r-- | prediction_state_service.py | 127 | ||||
| -rw-r--r-- | test2.py | 47 |
5 files changed, 315 insertions, 21 deletions
diff --git a/calendar_gui.py b/calendar_gui.py index 75f77fb..8863b8e 100644 --- a/calendar_gui.py +++ b/calendar_gui.py @@ -13,7 +13,6 @@ from date_calculator import DateCalculator from prediction_controller import PredictionController from event_type_handler import EventTypeHandler from date_service import DateService -from file_service import FileService from config import EventConfig class EventDialog(QDialog): @@ -200,6 +199,8 @@ class CalendarManagerGUI(QMainWindow): load_action.triggered.connect(self.load_file) file_menu.addAction(load_action) + # Note: Saving/Loading now operate on complete prediction state + # Clear action clear_action = QAction('Einträge löschen', self) clear_action.setShortcut('Ctrl+N') @@ -408,38 +409,58 @@ class CalendarManagerGUI(QMainWindow): self.events_table.setCellWidget(i, 6, actions_widget) def save_file(self): - """Save calendar entries to a JSON file""" - file_path, _ = QFileDialog.getSaveFileName(self, "Einträge speichern", "", EventConfig.FILE_FILTER) + """Save the complete prediction state to a .prediction.json file""" + file_path, _ = QFileDialog.getSaveFileName( + self, + "Zustand speichern", + "", + "Prediction State (*.prediction.json)" + ) if not file_path: return - - # Ensure .json extension - file_path = FileService.ensure_json_extension(file_path) - self.calendar_manager.switch_file(file_path) - + try: - if self.calendar_manager.save_entries(): - QMessageBox.information(self, "Speichern erfolgreich", f"Einträge gespeichert in {self.calendar_manager.filename}") + if self.prediction_controller.save_complete_state(file_path): + target = file_path if file_path.endswith('.prediction.json') else file_path + '.prediction.json' + QMessageBox.information(self, "Speichern erfolgreich", f"Komplettzustand gespeichert in {target}") else: - QMessageBox.critical(self, "Error", "Failed to save calendar entries") + QMessageBox.critical(self, "Error", "Speichern des Komplettzustands fehlgeschlagen") except Exception as e: - QMessageBox.critical(self, "Error", f"Failed to save calendar: {str(e)}") + QMessageBox.critical(self, "Error", f"Fehler beim Speichern des Komplettzustands: {str(e)}") def load_file(self): - """Load calendar entries from a JSON file""" - file_path, _ = QFileDialog.getOpenFileName(self, "Einträge laden", "", EventConfig.FILE_FILTER) + """Load the complete prediction state from a .prediction.json file""" + file_path, _ = QFileDialog.getOpenFileName( + self, + "Zustand laden", + "", + "Prediction State (*.prediction.json)" + ) if not file_path: return - + try: - if self.calendar_manager.load_file(file_path): + success = self.prediction_controller.load_complete_state(file_path) + if success: + # Reflect restored parameters into UI + launch_dt = self.prediction_controller.get_launch_date() + duration = self.prediction_controller.get_duration() + if launch_dt: + self.launch_date_edit.setDate(launch_dt) + if duration is not None and hasattr(duration, 'years'): + self.duration_spin.setValue(max(1, duration.years)) + + # Update views self.update_events_table() - self.update_prediction() # Auto-update prediction - QMessageBox.information(self, "Laden erfolgreich", f"Einträge erfolgreich von {file_path} geladen") + prediction_date = self.prediction_controller.get_prediction() + if prediction_date: + self.prediction_result.setDate(prediction_date) + QMessageBox.information(self, "Laden erfolgreich", f"Komplettzustand von {file_path} geladen") else: - QMessageBox.warning(self, "Warning", "No entries loaded from file") + QMessageBox.warning(self, "Warnung", "Komplettzustand konnte nicht geladen werden") except Exception as e: - QMessageBox.critical(self, "Error", f"Failed to load calendar: {str(e)}") + QMessageBox.critical(self, "Error", f"Fehler beim Laden des Komplettzustands: {str(e)}") + def clear_entries(self): """Clear all calendar entries""" diff --git a/prediction_controller.py b/prediction_controller.py index 168ad62..e822f2e 100644 --- a/prediction_controller.py +++ b/prediction_controller.py @@ -241,4 +241,26 @@ class PredictionController: if launch_dt > datetime(2100, 1, 1): errors.append("Launch date too far in the future") - return errors
\ No newline at end of file + return errors + + def save_complete_state(self, filename: str) -> bool: + """Save the complete prediction state including all calculated data""" + from prediction_state_service import PredictionStateService + return PredictionStateService.save_prediction_state( + self.calendar_manager, self, filename + ) + + def load_complete_state(self, filename: str) -> bool: + """Load complete prediction state and restore controller""" + from prediction_state_service import PredictionStateService + + state = PredictionStateService.load_prediction_state(filename) + if state: + success = PredictionStateService.restore_from_state( + state, self.calendar_manager, self + ) + if success and state.prediction_date: + # Restore prediction date directly if available + self.prediction = state.prediction_date + return success + return False
\ No newline at end of file diff --git a/prediction_state.py b/prediction_state.py new file mode 100644 index 0000000..966b3c0 --- /dev/null +++ b/prediction_state.py @@ -0,0 +1,77 @@ +# 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
\ No newline at end of file diff --git a/prediction_state_service.py b/prediction_state_service.py new file mode 100644 index 0000000..a84ae8d --- /dev/null +++ b/prediction_state_service.py @@ -0,0 +1,127 @@ +# prediction_state_service.py +import json +import os +from typing import Optional, List +from datetime import datetime +from prediction_state import PredictionState +from calendar_manager import CalendarManager +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from prediction_controller import PredictionController + +class PredictionStateService: + """Service for saving and loading complete prediction states""" + + DEFAULT_STATE_EXTENSION = '.prediction.json' + + @staticmethod + def save_prediction_state(calendar_manager: CalendarManager, + prediction_controller, + filename: str) -> bool: + """Save complete prediction state to file""" + try: + # Ensure proper extension + if not filename.endswith(PredictionStateService.DEFAULT_STATE_EXTENSION): + filename += PredictionStateService.DEFAULT_STATE_EXTENSION + + # Create prediction state + state = PredictionState( + launch_date=prediction_controller.get_launch_date(), + duration_years=prediction_controller.get_duration().years if prediction_controller.get_duration() else None, + prediction_date=prediction_controller.get_prediction(), + entries=calendar_manager.list_entries() + ) + + # Ensure directory exists + os.makedirs(os.path.dirname(filename) if os.path.dirname(filename) else '.', exist_ok=True) + + # Save to file with pretty formatting + with open(filename, 'w', encoding='utf-8') as f: + json.dump(state.to_dict(), f, indent=4, ensure_ascii=False) + + return True + + except Exception as e: + print(f"Error saving prediction state to file {filename}: {e}") + return False + + @staticmethod + def load_prediction_state(filename: str) -> Optional[PredictionState]: + """Load prediction state from file""" + try: + if not os.path.exists(filename): + print(f"File {filename} does not exist") + return None + + with open(filename, 'r', encoding='utf-8') as f: + data = json.load(f) + + return PredictionState.from_dict(data) + + except (FileNotFoundError, json.JSONDecodeError) as e: + print(f"Error loading prediction state from file {filename}: {e}") + return None + except Exception as e: + print(f"Unexpected error loading prediction state from file {filename}: {e}") + return None + + @staticmethod + def restore_from_state(state: PredictionState, + calendar_manager: CalendarManager, + prediction_controller) -> bool: + """Restore calendar manager and prediction controller from state""" + try: + # Clear current entries and load from state + calendar_manager.clear_entries() + calendar_manager.entries.extend(state.entries) + + # Restore prediction controller state if available + if state.launch_date and state.duration_years: + success = prediction_controller.set_parameters( + state.launch_date.isoformat(), + state.duration_years + ) + if not success: + print("Warning: Could not restore prediction parameters") + + return True + + except Exception as e: + print(f"Error restoring from state: {e}") + return False + + @staticmethod + def get_state_summary(filename: str): + """Get summary information about a state file without loading entries""" + try: + if not os.path.exists(filename): + return None + + with open(filename, 'r', encoding='utf-8') as f: + data = json.load(f) + + return { + 'version': data.get('version', 'Unknown'), + 'created_at': data.get('created_at'), + 'launch_date': data.get('launch_date'), + 'duration_years': data.get('duration_years'), + 'prediction_date': data.get('prediction_date'), + 'entry_count': len(data.get('entries', [])) + } + + except Exception as e: + print(f"Error getting state summary from {filename}: {e}") + return None + + @staticmethod + def list_state_files(directory: str = '.') -> List[str]: + """List all prediction state files in a directory""" + try: + files = [] + for filename in os.listdir(directory): + if filename.endswith(PredictionStateService.DEFAULT_STATE_EXTENSION): + files.append(os.path.join(directory, filename)) + return sorted(files) + except Exception as e: + print(f"Error listing state files in {directory}: {e}") + return []
\ No newline at end of file diff --git a/test2.py b/test2.py new file mode 100644 index 0000000..08b5e7f --- /dev/null +++ b/test2.py @@ -0,0 +1,47 @@ +# Example usage and testing +def demo_state_management(): + """Demonstrate the enhanced state management functionality""" + from calendar_manager import CalendarManager + from prediction_controller import PredictionController + from date_calculator import DateCalculator + + # Create components + calendar_manager = CalendarManager() + date_calculator = DateCalculator() + prediction_controller = PredictionController(calendar_manager, date_calculator) + + # Add some sample entries + calendar_manager.add_entry("2024-01-01", "2024-06-30", "EZ 100%", "Sample project") + calendar_manager.add_entry("2024-07-01", "2024-12-31", "EZ 50%", "Part-time work") + calendar_manager.add_entry("2024-03-15", "2024-03-20", "Sonstige", "Conference") + + # Make a prediction + success = prediction_controller.make_prediction("2023-01-01", 2) + if success: + print("Prediction calculated successfully") + + # Save complete state + state_filename = "example_prediction_state.prediction.json" + if prediction_controller.save_complete_state(state_filename): + print(f"State saved to {state_filename}") + + # Load state into new components + new_calendar = CalendarManager() + new_controller = PredictionController(new_calendar, date_calculator) + + if new_controller.load_complete_state(state_filename): + print("State loaded successfully") + print(f"Loaded {len(new_calendar.entries)} entries") + print(f"Launch date: {new_controller.get_launch_date()}") + print(f"Prediction: {new_controller.get_prediction()}") + + # Display entries with their metadata + for entry in new_calendar.entries: + print(f"Entry: {entry.keyword} ({entry.start_date.date()} to {entry.end_date.date()})") + if hasattr(entry, 'time_period') and entry.time_period: + print(f" Time period: {entry.time_period}") + if entry.corrected_start_date: + print(f" Corrected: {entry.corrected_start_date.date()} to {entry.corrected_end_date.date()}") + +if __name__ == "__main__": + demo_state_management()
\ No newline at end of file |
