diff options
| -rw-r--r-- | calendar_gui.py | 446 | ||||
| -rw-r--r-- | calendar_manager.py | 136 | ||||
| -rw-r--r-- | date_calculator.py | 154 | ||||
| -rw-r--r-- | prediction_controller.py | 48 | ||||
| -rw-r--r-- | test.py | 40 |
5 files changed, 824 insertions, 0 deletions
diff --git a/calendar_gui.py b/calendar_gui.py new file mode 100644 index 0000000..8102a36 --- /dev/null +++ b/calendar_gui.py @@ -0,0 +1,446 @@ +import sys +from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, + QLabel, QLineEdit, QPushButton, QDateEdit, QTableWidget, + QTableWidgetItem, QHeaderView, QDialog, QFormLayout, QComboBox, + QMessageBox, QSpinBox, QAction, QFileDialog, QMenuBar) +from PyQt5.QtCore import Qt, QDate +from PyQt5.QtGui import QFont +from datetime import datetime, timedelta +from dateutil.relativedelta import relativedelta +from calendar_manager import CalendarManager +from date_calculator import DateCalculator +from prediction_controller import PredictionController + +class AddEventDialog(QDialog): + def __init__(self, keyword_list, parent=None): + super().__init__(parent) + self.setWindowTitle("Neuer Eintrag") + self.keyword_list = keyword_list + self.init_ui() + + def init_ui(self): + layout = QFormLayout() + + # Start date selector + self.start_date = QDateEdit() + self.start_date.setCalendarPopup(True) + self.start_date.setDate(QDate.currentDate()) + layout.addRow("Startdatum:", self.start_date) + + # End date selector + self.end_date = QDateEdit() + self.end_date.setCalendarPopup(True) + self.end_date.setDate(QDate.currentDate().addDays(1)) + layout.addRow("Enddatum:", self.end_date) + + # Keyword selector + self.keyword = QComboBox() + self.keyword.addItems(self.keyword_list) + self.keyword.currentTextChanged.connect(self.on_keyword_changed) + layout.addRow("Art:", self.keyword) + + # Store layout for later access + self.layout = layout + self.end_date_row = 1 # Index of end date row in the form layout + # Buttons + button_layout = QHBoxLayout() + self.save_button = QPushButton("Speichern") + self.save_button.clicked.connect(self.accept) + self.cancel_button = QPushButton("Abbrechen") + self.cancel_button.clicked.connect(self.reject) + + button_layout.addWidget(self.save_button) + button_layout.addWidget(self.cancel_button) + layout.addRow("", button_layout) + + self.setLayout(layout) + + # Handle initial keyword selection + self.on_keyword_changed(self.keyword.currentText()) + + def on_keyword_changed(self, keyword): + """Handle visibility of the end_date field based on keyword""" + # Get the widgets from the form layout + end_date_label = self.layout.itemAt(self.end_date_row, QFormLayout.LabelRole).widget() + end_date_field = self.layout.itemAt(self.end_date_row, QFormLayout.FieldRole).widget() + + if keyword == "EZ pauschal": + # Hide end date field for EZ pauschals since they have fixed 4-week duration + end_date_label.setVisible(False) + end_date_field.setVisible(False) + else: + # Show end date field for other event types + end_date_label.setVisible(True) + end_date_field.setVisible(True) + def get_data(self): + start_date = self.start_date.date().toString("yyyy-MM-dd") + keyword = self.keyword.currentText() + + if keyword == "EZ pauschal": + # For EZ pauschals, calculate end date as start + 4 weeks + start_dt = datetime.fromisoformat(start_date) + end_dt = start_dt + relativedelta(years = 2, days = -1) + end_date = end_dt.strftime("%Y-%m-%d") + else: + end_date = self.end_date.date().toString("yyyy-MM-dd") + return start_date, end_date, keyword + + +class ModifyEventDialog(QDialog): + def __init__(self, entry, keyword_list, parent=None): + super().__init__(parent) + self.setWindowTitle("Eintrag editieren") + self.entry = entry + self.keyword_list = keyword_list + self.init_ui() + + def init_ui(self): + layout = QFormLayout() + + # Start date selector + self.start_date = QDateEdit() + self.start_date.setCalendarPopup(True) + entry_start = QDate(self.entry.start_date.year, + self.entry.start_date.month, + self.entry.start_date.day) + self.start_date.setDate(entry_start) + layout.addRow("Startdatum:", self.start_date) + + # End date selector + self.end_date = QDateEdit() + self.end_date.setCalendarPopup(True) + entry_end = QDate(self.entry.end_date.year, + self.entry.end_date.month, + self.entry.end_date.day) + self.end_date.setDate(entry_end) + layout.addRow("Enddatum:", self.end_date) + + # Keyword selector + self.keyword = QComboBox() + self.keyword.addItems(self.keyword_list) + current_index = self.keyword_list.index(self.entry.keyword) if self.entry.keyword in self.keyword_list else 0 + self.keyword.setCurrentIndex(current_index) + self.keyword.currentTextChanged.connect(self.on_keyword_changed) + layout.addRow("Art:", self.keyword) + # Store layout for later access + self.layout = layout + self.end_date_row = 1 # Index of end date row in the form layout + + # Buttons + button_layout = QHBoxLayout() + self.save_button = QPushButton("Speichern") + self.save_button.clicked.connect(self.accept) + self.cancel_button = QPushButton("Abbrechen") + self.cancel_button.clicked.connect(self.reject) + + button_layout.addWidget(self.save_button) + button_layout.addWidget(self.cancel_button) + layout.addRow("", button_layout) + + self.setLayout(layout) + + # Handle initial keyword selection + self.on_keyword_changed(self.keyword.currentText()) + + def on_keyword_changed(self, keyword): + """Handle visibility of the end_date field based on keyword""" + # Get the widgets from the form layout + end_date_label = self.layout.itemAt(self.end_date_row, QFormLayout.LabelRole).widget() + end_date_field = self.layout.itemAt(self.end_date_row, QFormLayout.FieldRole).widget() + + if keyword == "EZ pauschal": + # Hide end date field for EZ pauschals since they have fixed 4-week duration + end_date_label.setVisible(False) + end_date_field.setVisible(False) + + # Update end date automatically if changing to EZ pauschal + start_dt = self.start_date.date().toPyDate() + end_dt = start_dt + relativedelta(years = 2, days = -1) + self.end_date.setDate(QDate(end_dt.year, end_dt.month, end_dt.day)) + else: + # Show end date field for other event types + end_date_label.setVisible(True) + end_date_field.setVisible(True) + def get_data(self): + start_date = self.start_date.date().toString("yyyy-MM-dd") + keyword = self.keyword.currentText() + + if keyword == "EZ pauschal": + # For EZ pauschals, calculate end date as start + 4 weeks + start_dt = datetime.fromisoformat(start_date) + end_dt = start_dt + relativedelta(years = 2, days = -1) + end_date = end_dt.strftime("%Y-%m-%d") + else: + end_date = self.end_date.date().toString("yyyy-MM-dd") + return start_date, end_date, keyword + + +class CalendarManagerGUI(QMainWindow): + def __init__(self): + super().__init__() + self.setWindowTitle("Ausfallzeitenrechner") + self.setMinimumSize(800, 600) + + # Initialize backend components + self.keyword_list = ["EZ 100%", "EZ 50%", "EZ pauschal", "Sonstige"] + self.calendar_manager = CalendarManager() + self.date_calculator = DateCalculator() + self.prediction_controller = PredictionController( + self.calendar_manager, + self.date_calculator, + self.keyword_list + ) + + self.init_ui() + self.create_menus() + + def create_menus(self): + # Create menu bar + menubar = self.menuBar() + + # File menu + file_menu = menubar.addMenu('File') + + # Save action + save_action = QAction('Save', self) + save_action.setShortcut('Ctrl+S') + save_action.triggered.connect(self.save_file) + file_menu.addAction(save_action) + + # Load action + load_action = QAction('Load', self) + load_action.setShortcut('Ctrl+O') + load_action.triggered.connect(self.load_file) + file_menu.addAction(load_action) + + # Clear action + clear_action = QAction('Clear', self) + clear_action.setShortcut('Ctrl+N') + clear_action.triggered.connect(self.clear_entries) + file_menu.addAction(clear_action) + + # Exit action + exit_action = QAction('Exit', self) + exit_action.setShortcut('Ctrl+Q') + exit_action.triggered.connect(self.close) + file_menu.addAction(exit_action) + + def init_ui(self): + central_widget = QWidget() + main_layout = QVBoxLayout() + + top_layout = QHBoxLayout() + + # Launch date input + launch_date_layout = QVBoxLayout() + launch_date_label = QLabel("Promotionsdatum:") + self.launch_date_edit = QDateEdit() + self.launch_date_edit.setCalendarPopup(True) # Enable calendar popup + self.launch_date_edit.setDate(QDate.currentDate()) + self.launch_date_edit.dateChanged.connect(self.update_prediction) # Auto-update on change + launch_date_layout.addWidget(launch_date_label) + launch_date_layout.addWidget(self.launch_date_edit) + top_layout.addLayout(launch_date_layout) + + # Duration input + duration_layout = QVBoxLayout() + duration_label = QLabel("Bewerbungszeitraum (Jahre):") + self.duration_spin = QSpinBox() + self.duration_spin.setRange(1, 99) + self.duration_spin.setValue(1) + self.duration_spin.valueChanged.connect(self.update_prediction) # Auto-update on change + duration_layout.addWidget(duration_label) + duration_layout.addWidget(self.duration_spin) + top_layout.addLayout(duration_layout) + + # Prediction result + prediction_result_layout = QVBoxLayout() + prediction_result_label = QLabel("Bewerbungsfrist:") + self.prediction_result = QDateEdit() + self.prediction_result.setReadOnly(True) + self.prediction_result.setButtonSymbols(QDateEdit.ButtonSymbols.NoButtons) + prediction_result_layout.addWidget(prediction_result_label) + prediction_result_layout.addWidget(self.prediction_result) + + top_layout.addLayout(prediction_result_layout) + + main_layout.addLayout(top_layout) + + # Events section + events_layout = QVBoxLayout() + events_title = QLabel("<h3>Ausfallzeiten</h3>") + events_layout.addWidget(events_title) + + # Add event button + add_event_button = QPushButton("Eintrag hinzufügen") + add_event_button.clicked.connect(self.add_event) + events_layout.addWidget(add_event_button) + + # Events table + self.events_table = QTableWidget() + self.events_table.setColumnCount(7) # ID (hidden), Start, End, Keyword, CorrectedStart, CorrectedEnd, Actions + self.events_table.setHorizontalHeaderLabels(["ID", "Anfangsdatum", "Enddatum", "Art", "Korr. Start", "Korr. Ende", "Aktionen"]) + self.events_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) + self.events_table.setColumnHidden(0, True) # Hide ID column + events_layout.addWidget(self.events_table) + + main_layout.addLayout(events_layout) + + # Set main layout + central_widget.setLayout(main_layout) + self.setCentralWidget(central_widget) + + # Load initial data + self.update_events_table() + self.update_prediction() # Calculate initial prediction + + def update_prediction(self): + """Update prediction whenever needed""" + try: + launch_date = self.launch_date_edit.date().toString("yyyy-MM-dd") + duration_years = self.duration_spin.value() + + self.prediction_controller.make_prediction(launch_date, duration_years) + prediction_date = self.prediction_controller.get_prediction() + + if prediction_date: + self.prediction_result.setDate(prediction_date) + + # Update table to show corrected dates + self.update_events_table() + + except Exception as e: + self.prediction_result.setText("Error in calculation") + print(f"Error calculating prediction: {str(e)}") + + def add_event(self): + dialog = AddEventDialog(self.keyword_list, self) + if dialog.exec_(): + start_date, end_date, keyword = dialog.get_data() + try: + self.calendar_manager.add_entry(start_date, end_date, keyword) + self.update_events_table() + self.update_prediction() # Auto-update prediction + except Exception as e: + QMessageBox.critical(self, "Error", f"Error adding event: {str(e)}") + + def modify_event(self, event_id): + entry = self.calendar_manager.get_entry_by_id(event_id) + if entry: + dialog = ModifyEventDialog(entry, self.keyword_list, self) + if dialog.exec_(): + start_date, end_date, keyword = dialog.get_data() + try: + self.calendar_manager.modify_entry(event_id, + datetime.fromisoformat(start_date), + datetime.fromisoformat(end_date), + keyword) + self.update_events_table() + self.update_prediction() # Auto-update prediction + except Exception as e: + QMessageBox.critical(self, "Error", f"Error modifying event: {str(e)}") + + def delete_event(self, event_id): + reply = QMessageBox.question(self, "Eintrag löschen", + "Wollen Sie diesen Eintrag löschen?", + QMessageBox.Yes | QMessageBox.No) + if reply == QMessageBox.Yes: + try: + self.calendar_manager.delete_entry(event_id) + self.update_events_table() + self.update_prediction() # Auto-update prediction + except Exception as e: + QMessageBox.critical(self, "Error", f"Error deleting event: {str(e)}") + + def update_events_table(self): + # Clear table + self.events_table.setRowCount(0) + + # Add entries to table + entries = self.calendar_manager.list_entries() + for i, entry in enumerate(entries): + self.events_table.insertRow(i) + + # Set item data + self.events_table.setItem(i, 0, QTableWidgetItem(entry.id)) + self.events_table.setItem(i, 1, QTableWidgetItem(entry.start_date.strftime("%Y-%m-%d"))) + self.events_table.setItem(i, 2, QTableWidgetItem(entry.end_date.strftime("%Y-%m-%d"))) + self.events_table.setItem(i, 3, QTableWidgetItem(entry.keyword)) + + # Corrected dates + corrected_start = entry.corrected_start_date.strftime("%Y-%m-%d") if entry.corrected_start_date else "" + corrected_end = entry.corrected_end_date.strftime("%Y-%m-%d") if entry.corrected_end_date else "" + self.events_table.setItem(i, 4, QTableWidgetItem(corrected_start)) + self.events_table.setItem(i, 5, QTableWidgetItem(corrected_end)) + + # Action buttons + actions_widget = QWidget() + actions_layout = QHBoxLayout() + actions_layout.setContentsMargins(0, 0, 0, 0) + + modify_button = QPushButton("Editieren") + delete_button = QPushButton("Löschen") + + # Use lambda with default argument to capture the correct event_id + modify_button.clicked.connect(lambda checked, eid=entry.id: self.modify_event(eid)) + delete_button.clicked.connect(lambda checked, eid=entry.id: self.delete_event(eid)) + + actions_layout.addWidget(modify_button) + actions_layout.addWidget(delete_button) + actions_widget.setLayout(actions_layout) + + self.events_table.setCellWidget(i, 6, actions_widget) + + def save_file(self): + """Save calendar entries to a JSON file""" + # if not self.calendar_manager.filename: + file_path, _ = QFileDialog.getSaveFileName(self, "Einträge speichern", "", "JSON Files (*.json)") + if not file_path: + return + self.calendar_manager.switch_file(file_path) + + try: + self.calendar_manager.save_entries() + QMessageBox.information(self, "Speichern erfolgreich", f"Einträge gespeichert in {self.calendar_manager.filename}") + except Exception as e: + QMessageBox.critical(self, "Error", f"Failed to save calendar: {str(e)}") + + def load_file(self): + """Load calendar entries from a JSON file""" + file_path, _ = QFileDialog.getOpenFileName(self, "Einträge laden", "", "JSON Files (*.json)") + if not file_path: + return + + try: + self.calendar_manager.load_file(file_path) + self.update_events_table() + self.update_prediction() # Auto-update prediction + QMessageBox.information(self, "Laden erfolgreich", f"Einträge erfolgreich von {file_path} geladen") + except Exception as e: + QMessageBox.critical(self, "Error", f"Failed to load calendar: {str(e)}") + + def clear_entries(self): + """Clear all calendar entries""" + reply = QMessageBox.question(self, "Einträge löschen", + "Alle Einträge löschen?", + QMessageBox.Yes | QMessageBox.No) + if reply == QMessageBox.Yes: + try: + self.calendar_manager.clear_entries() + self.update_events_table() + self.update_prediction() # Auto-update prediction + QMessageBox.information(self, "Erfolg", "Alle Einträge gelöscht!") + except Exception as e: + QMessageBox.critical(self, "Error", f"Failed to clear calendar: {str(e)}") + + +def main(): + app = QApplication(sys.argv) + app.setStyle('Fusion') + window = CalendarManagerGUI() + window.show() + sys.exit(app.exec_()) + + +if __name__ == "__main__": + main()
\ No newline at end of file diff --git a/calendar_manager.py b/calendar_manager.py new file mode 100644 index 0000000..b62cf5d --- /dev/null +++ b/calendar_manager.py @@ -0,0 +1,136 @@ +# calendar_manager.py +import json +import uuid +from datetime import datetime + +class CalendarEntry: + def __init__(self, start_date, end_date, keyword, entry_id=None, corrected_start_date=None, corrected_end_date=None): + self.id = entry_id if entry_id else str(uuid.uuid4()) + # Convert string dates to datetime if necessary + self.start_date = ( + datetime.fromisoformat(start_date) + if isinstance(start_date, str) + else start_date + ) + self.end_date = ( + datetime.fromisoformat(end_date) + if isinstance(end_date, str) + else end_date + ) + self.keyword = keyword + self.corrected_start_date = corrected_start_date + self.corrected_end_date = corrected_end_date + def to_dict(self): + return { + 'id': self.id, + 'start_date': self.start_date.isoformat(), + 'end_date': self.end_date.isoformat(), + 'keyword': self.keyword, + 'corrected_start_date': self.corrected_start_date.isoformat() if self.corrected_start_date else None, + 'corrected_end_date': self.corrected_end_date.isoformat() if self.corrected_end_date else None, + } + + @classmethod + def from_dict(cls, data): + return cls( + start_date=datetime.fromisoformat(data['start_date']), + end_date=datetime.fromisoformat(data['end_date']), + keyword=data['keyword'], + entry_id=data['id'], + corrected_start_date=datetime.fromisoformat(data['corrected_start_date']) if data.get('corrected_start_date') else None, + corrected_end_date=datetime.fromisoformat(data['corrected_end_date']) if data.get('corrected_end_date') else None, + ) + + def __repr__(self): + return (f"CalendarEntry(id={self.id}, start_date={self.start_date}, " + f"end_date={self.end_date}, keyword='{self.keyword}', " + f"corrected_start_date={self.corrected_start_date}, corrected_end_date={self.corrected_end_date})") + +class CalendarManager: + def __init__(self, filename=None): + self.filename = filename + self.entries = [] + if filename: + self.entries = self.add_entries_from_file(filename) + + def switch_file(self, new_filename): + """Switch to a new file.""" + self.filename = new_filename + + def load_file(self, new_filename): + """Clears current entries and loads entries from new file.""" + self.clear_entries() + self.add_entries_from_file(new_filename) + self.switch_file(new_filename) + + def add_entries_from_file(self, file_path): + """Add events from another JSON file to the current calendar.""" + try: + with open(file_path, 'r') as f: + data = json.load(f) + new_entries = [CalendarEntry.from_dict(entry) for entry in data] + self.entries.extend(new_entries) + self.save_entries() + return new_entries + except (FileNotFoundError, json.JSONDecodeError) as e: + print(f"Error reading file {file_path}: {e}") + return [] + + def clear_entries(self): + self.entries = [] + + def save_entries(self): + """Save the current list of events to the file.""" + if self.filename: + try: + with open(self.filename, 'w') as f: + json.dump([entry.to_dict() for entry in self.entries], f, indent=4) + except Exception as e: + print(f"Error writing to file {self.filename}: {e}") + + def add_entry(self, start_date, end_date, keyword): + """Add a new event to the calendar.""" + new_entry = CalendarEntry(start_date, end_date, keyword) + self.entries.append(new_entry) + self.save_entries() + return new_entry + + def modify_entry(self, entry_id, start_date=None, end_date=None, keyword=None): + """Modify an existing event by ID.""" + entry = self.get_entry_by_id(entry_id) + if entry: + if start_date: + entry.start_date = start_date + if end_date: + entry.end_date = end_date + if keyword: + entry.keyword = keyword + self.save_entries() + return entry + return None + + def delete_entry(self, entry_id): + """Delete an event by ID.""" + entry = self.get_entry_by_id(entry_id) + if entry: + self.entries.remove(entry) + self.save_entries() + return entry + return None + + def get_entry_by_id(self, entry_id): + """Get an event by its ID.""" + return next((entry for entry in self.entries if entry.id == entry_id), None) + + def list_entries(self): + """List all calendar entries.""" + return self.entries + + def correct_dates(self, list_of_events): + for start_date, end_date, original_id in list_of_events: + entry = self.get_entry_by_id(original_id) + entry.corrected_start_date = start_date + entry.corrected_end_date = end_date + + +
\ No newline at end of file diff --git a/date_calculator.py b/date_calculator.py new file mode 100644 index 0000000..df13721 --- /dev/null +++ b/date_calculator.py @@ -0,0 +1,154 @@ +# date_calculator.py +from datetime import datetime, timedelta +from dateutil.relativedelta import relativedelta +import math + +class DateCalculator: + @staticmethod + def truncate_periods(periods, launch, prediction): + considered_periods = [] + for start, end, id in periods: + # print(start) + # print(launch) + truncated_start = max(start, launch) + truncated_end = min(end, prediction) + if truncated_start <= end and truncated_start <= prediction: + considered_periods.append((truncated_start, truncated_end, id)) + return considered_periods + + @staticmethod + def round_periods(periods): + rounded_periods = [] + total_months = 0 + + for start, end, id in periods: + year_diff = end.year - start.year + month_diff = end.month - start.month + months = year_diff * 12 + month_diff + if end.day >= start.day: + months += 1 + rounded_end = start + relativedelta(months=months) - timedelta(days=1) + + rounded_periods.append((start, rounded_end, id)) + total_months += months + + return rounded_periods, total_months + + @staticmethod + def merge_periods(periods): + if not periods: + return [] + + periods.sort() + merged_periods = [] + + current_start, current_end, current_id = periods[0] + + for start, end, id in periods[1:]: + if start <= current_end + timedelta(days=1): + current_end = max(current_end, end) + else: + merged_periods.append((current_start, current_end, current_id)) + current_start, current_end, current_id = start, end, id + + merged_periods.append((current_start, current_end, current_id)) + + return merged_periods + + @staticmethod + def find_non_overlapping_periods(existing_periods, test_period): + existing_periods.sort(key=lambda x: x[0]) + + test_start, test_end, id = test_period + non_overlapping_periods = [] + + for start, end, _ in existing_periods: + if test_end < start: + non_overlapping_periods.append((test_start, test_end, id)) + return non_overlapping_periods + + elif test_start > end: + continue + + else: + if test_start < start: + non_overlapping_periods.append((test_start, start - timedelta(days=1), id)) + + test_start = end + timedelta(days=1) + + if test_start <= test_end: + non_overlapping_periods.append((test_start, test_end, id)) + + return non_overlapping_periods + + def calculate_prediction(self, launch_date, duration, **kwargs): + total_days = 0 + total_months = 0 + updated = True + prediction_start = launch_date + duration - timedelta(days = 1) + prediction = prediction_start + + events = [] + half_projects = [] + full_projects = [] + other_kwargs = {} + + for k, v in kwargs.items(): + if k == "Sonstige": + events.extend(v) + elif k == "EZ 50%": + half_projects.extend(v) + elif k == "EZ 100%": + full_projects.extend(v) + elif k == "EZ pauschal": + full_projects.extend(v) + else: + other_kwargs[k] = v + + while updated: + updated = False + considered_events = self.truncate_periods(events, launch_date, prediction) + considered_full_projects = self.truncate_periods(full_projects, launch_date, prediction) + considered_half_projects = self.truncate_periods(half_projects, launch_date, prediction) + + considered_full_projects_merged = self.merge_periods(considered_full_projects) + considered_full_projects_rounded, months = self.round_periods(considered_full_projects_merged) + considered_full_projects_merged2 = self.merge_periods(considered_full_projects_rounded) + considered_full_projects_rounded2, months = self.round_periods(considered_full_projects_merged2) + + non_overlapping_half_projects = [] + for test_interval in considered_half_projects: + non_overlapping_half_projects.extend( + self.find_non_overlapping_periods(considered_full_projects_rounded2, test_interval) + ) + + considered_half_projects_merged = self.merge_periods(non_overlapping_half_projects) + considered_half_projects_rounded, months2 = self.round_periods(considered_half_projects_merged) + considered_half_projects_merged2 = self.merge_periods(considered_half_projects_rounded) + considered_half_projects_rounded2, months2 = self.round_periods(considered_half_projects_merged2) + + all_projects_merged = self.merge_periods( + considered_full_projects_rounded2 + considered_half_projects_rounded2 + ) + merged_event_periods = self.merge_periods(considered_events) + + non_overlapping_event_periods = [] + for test_interval in merged_event_periods: + non_overlapping_event_periods.extend( + self.find_non_overlapping_periods(all_projects_merged, test_interval) + ) + + new_total_months = months + math.ceil(months2 / 2) + new_total_days = sum((end - start).days + 1 for start, end, _ in non_overlapping_event_periods) + + if new_total_days != total_days or new_total_months != total_months: + total_days = new_total_days + total_months = new_total_months + updated = True + prediction = launch_date + duration + relativedelta(months=total_months) + timedelta(days=total_days-1) + + # print(prediction, prediction_start + relativedelta(years = 6)) + if prediction > prediction_start + relativedelta(years = 6): + prediction = prediction_start + relativedelta(years = 6) + + return prediction, considered_full_projects_rounded2 + considered_half_projects_rounded2 + non_overlapping_event_periods
\ No newline at end of file diff --git a/prediction_controller.py b/prediction_controller.py new file mode 100644 index 0000000..c3be8cc --- /dev/null +++ b/prediction_controller.py @@ -0,0 +1,48 @@ +# prediction_controller.py +from dateutil.relativedelta import relativedelta +from datetime import datetime, timedelta +import math + +class PredictionController: + def __init__(self, calendar_manager, date_calculator, keyword_list): + self.calendar_manager = calendar_manager + self.date_calculator = date_calculator + self.launch_date = None + self.duration = None + self.prediction = None + self.keyword_list = keyword_list + for keyword in keyword_list: + self.keyword = [] + + def set_parameters(self, launch_date, duration_years): + self.launch_date = datetime.fromisoformat(launch_date) + self.duration = relativedelta(years=duration_years) + + def make_prediction(self, launch_date, duration_years): + self.set_parameters(launch_date, duration_years) + + prediction = self.launch_date + self.duration - timedelta(days=1) + + keyword_args = {} + + for entry in self.calendar_manager.entries: + for keyword in self.keyword_list: + if entry.keyword == keyword: + if keyword not in keyword_args: + keyword_args[keyword] = [] + keyword_args[keyword].append((entry.start_date, entry.end_date, entry.id)) + break + # print(keyword_args) + prediction, corrected_events = self.date_calculator.calculate_prediction(self.launch_date, self.duration, **keyword_args) + self.prediction = prediction + self.calendar_manager.correct_dates(corrected_events) + + def get_launch_date(self): + return self.launch_date + + def get_duration(self): + return self.duration + + def get_prediction(self): + return self.prediction + @@ -0,0 +1,40 @@ +# test.py +from date_calculator import DateCalculator +from calendar_manager import CalendarManager +from prediction_controller import PredictionController + + + +def test_calculate_prediction(): + # Input event dates as strings + event1_str = ["2023-01-01", "2023-01-10"] + event2_str = ["2023-01-05", "2023-01-15"] + + # Input full project dates as strings + project1_str = ["2025-01-02", "2025-02-11"] + + # Input half project dates as strings + project2_str = ["2025-02-01", "2025-06-30"] + project3_str = ["2024-05-05", "2024-06-07"] + + date_calculator = DateCalculator() + calendar_manager = CalendarManager() + prediction_controller = PredictionController(calendar_manager, date_calculator, ["Erziehungszeit 100%", "Erziehungszeit 50%", "Erziehungszeit pauschal", "Sonstige"]) + + calendar_manager.add_entry(event1_str[0], event1_str[1], "Sonstige") + calendar_manager.add_entry(event2_str[0], event2_str[1], "Sonstige") + calendar_manager.add_entry(project1_str[0], project1_str[1], "Erziehungszeit 100%") + calendar_manager.add_entry(project2_str[0], project2_str[1], "Erziehungszeit 50%") + calendar_manager.add_entry(project3_str[0], project3_str[1], "Erziehungszeit 50%") + + # Set launch date and duration + prediction_controller.make_prediction("2023-01-01", 2) + + prediction = prediction_controller.get_prediction() + + # Output the result + print(f"Predicted completion date: {prediction}") + print(calendar_manager.list_entries()) + +# Run the test +test_calculate_prediction()
\ No newline at end of file |
