summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--calendar_gui.py18
-rw-r--r--prediction_controller.py200
2 files changed, 61 insertions, 157 deletions
diff --git a/calendar_gui.py b/calendar_gui.py
index ab0cf65..8e05b37 100644
--- a/calendar_gui.py
+++ b/calendar_gui.py
@@ -218,25 +218,25 @@ class CalendarManagerGUI(QMainWindow):
# File menu
file_menu = menubar.addMenu('Start')
- # Save action
- save_action = QAction('Speichern', self)
- save_action.setShortcut('Ctrl+S')
- save_action.setIcon(self.style().standardIcon(QStyle.SP_DialogSaveButton))
- save_action.triggered.connect(self.save_file)
- file_menu.addAction(save_action)
-
# Load action
load_action = QAction('Laden', self)
load_action.setShortcut('Ctrl+L')
load_action.setIcon(self.style().standardIcon(QStyle.SP_DialogOpenButton))
load_action.triggered.connect(self.load_file)
file_menu.addAction(load_action)
+
+ # Save action
+ save_action = QAction('Speichern', self)
+ save_action.setShortcut('Ctrl+S')
+ save_action.setIcon(self.style().standardIcon(QStyle.SP_DialogSaveButton))
+ save_action.triggered.connect(self.save_file)
+ file_menu.addAction(save_action)
# Export PDF action
export_pdf_action = QAction('Als PDF exportieren', self)
- export_pdf_action.setShortcut('Ctrl+P')
+ export_pdf_action.setShortcut('Ctrl+E')
# Fallback icon; SP_DialogPrintButton may not exist in some Qt styles
- export_pdf_action.setIcon(self.style().standardIcon(QStyle.SP_DialogOpenButton))
+ export_pdf_action.setIcon(self.style().standardIcon(QStyle.SP_FileIcon))
export_pdf_action.triggered.connect(self.export_pdf)
file_menu.addAction(export_pdf_action)
diff --git a/prediction_controller.py b/prediction_controller.py
index e822f2e..bb30ef7 100644
--- a/prediction_controller.py
+++ b/prediction_controller.py
@@ -52,163 +52,67 @@ class PredictionController:
categorized_events = EventTypeHandler.categorize_events(self.calendar_manager.entries)
# Process events according to business rules
- processed_events = self._process_events_by_type(categorized_events)
-
- # Calculate final prediction
- self.prediction = self._calculate_final_prediction(prediction_start, processed_events)
+ full_projects = []
+ half_projects = []
+ events = []
+
+ for event_type in ["EZ 100%", "EZ pauschal"]:
+ if event_type in categorized_events:
+ full_projects.extend(categorized_events[event_type])
+
+ if "EZ 50%" in categorized_events:
+ half_projects.extend(categorized_events["EZ 50%"])
+
+ if "Sonstige" in categorized_events:
+ events.extend(categorized_events["Sonstige"])
+
+ sorted_projects = DateCalculator.sort_periods(full_projects)
+ sorted_half_projects = DateCalculator.sort_periods(half_projects)
+ sorted_events = DateCalculator.sort_periods(events)
+
+ considered_events = DateCalculator.truncate_periods(sorted_events, self.launch_date)
+ considered_full_projects = DateCalculator.truncate_periods(sorted_projects, self.launch_date)
+ considered_half_projects = DateCalculator.truncate_periods(sorted_half_projects, self.launch_date)
+
+ considered_full_projects_rounded, months = DateCalculator.round_periods(considered_full_projects)
+
+ non_overlapping_half_projects = []
+ for test_interval in considered_half_projects:
+ non_overlapping_half_projects.extend(
+ DateCalculator.find_non_overlapping_periods(considered_full_projects_rounded, test_interval)
+ )
+
+ considered_half_projects_rounded, months2 = DateCalculator.round_periods(non_overlapping_half_projects)
+
+ all_projects_merged = DateCalculator.sort_periods(considered_full_projects_rounded + considered_half_projects_rounded)
+ merged_event_periods = DateCalculator.adjust_periods(considered_events)
+
+ non_overlapping_event_periods = []
+ for test_interval in merged_event_periods:
+ non_overlapping_event_periods.extend(
+ DateCalculator.find_non_overlapping_periods(all_projects_merged, test_interval)
+ )
+
+ total_months = months + math.ceil(months2 / 2)
+ total_days = sum((end - start).days + 1 for start, end, _ in non_overlapping_event_periods)
+ prediction = self.launch_date + self.duration + relativedelta(months=total_months) + timedelta(days=total_days-1)
+
+ # Calculate final prediction
+ max_prediction = prediction_start + EventConfig.get_max_prediction_duration()
+ self.prediction = min(prediction, max_prediction)
+
+ # Collect corrected events from all categories
+ all_corrected_events = considered_full_projects_rounded + considered_half_projects_rounded + non_overlapping_event_periods
# Apply corrections to calendar entries
- self._apply_corrections_to_calendar()
+ self.calendar_manager.correct_dates(all_corrected_events)
return True
except Exception as e:
print(f"Error calculating prediction: {e}")
return False
-
- def _process_events_by_type(self, categorized_events: Dict[str, List[Tuple[datetime, datetime, str]]]) -> Dict[str, List[Tuple[datetime, datetime, str]]]:
- """Process events according to business rules"""
- processed = {}
-
- # Process full-time projects first (EZ 100% and EZ pauschal)
- full_projects = []
- for event_type in ["EZ 100%", "EZ pauschal"]:
- if event_type in categorized_events:
- full_projects.extend(categorized_events[event_type])
-
- if full_projects:
- processed["full_projects"] = self._process_full_projects(full_projects)
-
- # Process half-time projects (EZ 50%)
- if "EZ 50%" in categorized_events:
- processed["half_projects"] = self._process_half_projects(
- categorized_events["EZ 50%"],
- processed.get("full_projects", [])
- )
-
- # Process other events (Sonstige)
- if "Sonstige" in categorized_events:
- processed["other_events"] = self._process_other_events(
- categorized_events["Sonstige"],
- processed.get("full_projects", []),
- processed.get("half_projects", [])
- )
-
- return processed
-
- def _process_full_projects(self, full_projects: List[Tuple[datetime, datetime, str]]) -> List[Tuple[datetime, datetime, str]]:
- """Process full-time projects"""
- # Sort and truncate periods
- sorted_projects = DateCalculator.sort_periods(full_projects)
- considered_projects = DateCalculator.truncate_periods(sorted_projects, self.launch_date)
-
- # Filter out any invalid periods
- valid_projects = DateCalculator.filter_valid_periods(considered_projects)
-
- # Round to month boundaries
- rounded_projects, total_months = DateCalculator.round_periods(valid_projects)
-
- return rounded_projects
-
- def _process_half_projects(self, half_projects: List[Tuple[datetime, datetime, str]],
- full_projects: List[Tuple[datetime, datetime, str]]) -> List[Tuple[datetime, datetime, str]]:
- """Process half-time projects"""
- # Sort and truncate periods
- sorted_projects = DateCalculator.sort_periods(half_projects)
- considered_projects = DateCalculator.truncate_periods(sorted_projects, self.launch_date)
-
- # Filter out any invalid periods
- valid_projects = DateCalculator.filter_valid_periods(considered_projects)
-
- # Find non-overlapping periods with full projects
- non_overlapping_projects = []
- for test_interval in valid_projects:
- non_overlapping_results = DateCalculator.find_non_overlapping_periods(full_projects, test_interval)
- non_overlapping_projects.extend(non_overlapping_results)
-
- # Filter valid periods again after overlap processing
- valid_non_overlapping = DateCalculator.filter_valid_periods(non_overlapping_projects)
-
- # Round to month boundaries
- rounded_projects, total_months = DateCalculator.round_periods(valid_non_overlapping)
-
- return rounded_projects
-
- def _process_other_events(self, other_events: List[Tuple[datetime, datetime, str]],
- full_projects: List[Tuple[datetime, datetime, str]],
- half_projects: List[Tuple[datetime, datetime, str]]) -> List[Tuple[datetime, datetime, str]]:
- """Process other events (Sonstige)"""
- # Sort and truncate periods
- sorted_events = DateCalculator.sort_periods(other_events)
- considered_events = DateCalculator.truncate_periods(sorted_events, self.launch_date)
-
- # Filter out any invalid periods
- valid_events = DateCalculator.filter_valid_periods(considered_events)
-
- # Find non-overlapping periods with all projects
- all_projects = DateCalculator.sort_periods(full_projects + half_projects)
- non_overlapping_events = []
- for test_interval in valid_events:
- non_overlapping_results = DateCalculator.find_non_overlapping_periods(all_projects, test_interval)
- non_overlapping_events.extend(non_overlapping_results)
-
- # Filter valid periods again after overlap processing
- valid_non_overlapping = DateCalculator.filter_valid_periods(non_overlapping_events)
-
- # Adjust overlapping periods
- adjusted_events = DateCalculator.adjust_periods(valid_non_overlapping)
-
- # Final validation to ensure all periods are valid
- final_valid_events = DateCalculator.filter_valid_periods(adjusted_events)
- return final_valid_events
-
- def _calculate_final_prediction(self, prediction_start: datetime,
- processed_events: Dict[str, List[Tuple[datetime, datetime, str]]]) -> datetime:
- """Calculate the final prediction date"""
- # Calculate months from projects
- total_months = 0
-
- # Full projects count as full months
- if "full_projects" in processed_events:
- total_months += DateCalculator.calculate_total_months(processed_events["full_projects"])
-
- # Half projects count as half months
- if "half_projects" in processed_events:
- half_months = DateCalculator.calculate_total_months(processed_events["half_projects"])
- total_months += math.ceil(half_months / 2)
-
- # Other events count as days
- total_days = 0
- if "other_events" in processed_events:
- total_days = DateCalculator.calculate_total_days(processed_events["other_events"])
-
- # Calculate final prediction
- final_prediction = (prediction_start +
- relativedelta(months=total_months) +
- timedelta(days=total_days))
-
- # Apply maximum limit
- max_prediction = prediction_start + EventConfig.get_max_prediction_duration()
- final_prediction = DateCalculator.min_date(final_prediction, max_prediction)
-
- return final_prediction
-
- def _apply_corrections_to_calendar(self):
- """Apply corrected dates to calendar entries and calculate time_period"""
- # Collect all corrected events from processed results
- all_corrected_events = []
-
- # Get processed events from the prediction calculation
- categorized_events = EventTypeHandler.categorize_events(self.calendar_manager.entries)
- processed_events = self._process_events_by_type(categorized_events)
-
- # Collect corrected events from all categories
- for event_type, events in processed_events.items():
- all_corrected_events.extend(events)
-
- # Apply corrections to calendar entries
- self.calendar_manager.correct_dates(all_corrected_events)
def get_launch_date(self) -> Optional[datetime]:
"""Get the launch date"""