summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormatin <matin.kaufmann@gmail.com>2025-09-30 15:13:08 +0200
committermatin <matin.kaufmann@gmail.com>2025-09-30 15:13:08 +0200
commit3054eb8c0de880655a3a01bf4c2da154e6c3ab42 (patch)
treec26f30d53156e52631897159385d3d776278b92f
parent6de01b3ee171e60e6609aea51ef7c9056736a017 (diff)
save + load includes the launch date and duration. also, increased readability of the json output
-rw-r--r--calendar_gui.py61
-rw-r--r--prediction_controller.py24
-rw-r--r--prediction_state.py77
-rw-r--r--prediction_state_service.py127
-rw-r--r--test2.py47
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