summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--calendar_gui.py776
-rw-r--r--calendar_manager.py286
-rw-r--r--date_calculator.py306
-rw-r--r--prediction_controller.py133
-rw-r--r--prediction_storage.py151
-rw-r--r--test.py133
6 files changed, 1021 insertions, 764 deletions
diff --git a/calendar_gui.py b/calendar_gui.py
index c111db5..ef2f359 100644
--- a/calendar_gui.py
+++ b/calendar_gui.py
@@ -1,389 +1,389 @@
-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 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("Add New Event")
- 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("Start Date:", 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("End Date:", self.end_date)
-
- # Keyword selector
- self.keyword = QComboBox()
- self.keyword.addItems(self.keyword_list)
- layout.addRow("Keyword:", self.keyword)
-
- # Buttons
- button_layout = QHBoxLayout()
- self.save_button = QPushButton("Save")
- self.save_button.clicked.connect(self.accept)
- self.cancel_button = QPushButton("Cancel")
- 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)
-
- def get_data(self):
- start_date = self.start_date.date().toString("yyyy-MM-dd")
- end_date = self.end_date.date().toString("yyyy-MM-dd")
- keyword = self.keyword.currentText()
-
- return start_date, end_date, keyword
-
-
-class ModifyEventDialog(QDialog):
- def __init__(self, entry, keyword_list, parent=None):
- super().__init__(parent)
- self.setWindowTitle("Modify Event")
- 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("Start Date:", 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("End Date:", 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)
- layout.addRow("Keyword:", self.keyword)
-
- # Buttons
- button_layout = QHBoxLayout()
- self.save_button = QPushButton("Save")
- self.save_button.clicked.connect(self.accept)
- self.cancel_button = QPushButton("Cancel")
- 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)
-
- def get_data(self):
- start_date = self.start_date.date().toString("yyyy-MM-dd")
- end_date = self.end_date.date().toString("yyyy-MM-dd")
- keyword = self.keyword.currentText()
-
- return start_date, end_date, keyword
-
-
-class CalendarManagerGUI(QMainWindow):
- def __init__(self):
- super().__init__()
- self.setWindowTitle("Calendar Manager")
- self.setMinimumSize(800, 600)
-
- # Initialize backend components
- self.keyword_list = ["full_project", "half_project", "event"]
- 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):
- self.setWindowTitle('Calendar Manager GUI')
- self.setMinimumSize(800, 600)
-
- central_widget = QWidget()
- main_layout = QVBoxLayout()
-
- top_layout = QHBoxLayout()
-
- # Launch date input
- launch_date_layout = QVBoxLayout()
- launch_date_label = QLabel("Launch Date:")
- 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("Duration (years):")
- 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("Predicted Completion Date:")
- 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>Calendar Events</h3>")
- events_layout.addWidget(events_title)
-
- # Add event button
- add_event_button = QPushButton("Add Event")
- 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", "Start Date", "End Date", "Keyword", "Corrected Start", "Corrected End", "Actions"])
- 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, "Delete Event",
- "Are you sure you want to delete this event?",
- 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("Modify")
- delete_button = QPushButton("Delete")
-
- # 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, "Save Calendar", "", "JSON Files (*.json)")
- if not file_path:
- return
- self.calendar_manager.switch_file(file_path)
-
- try:
- self.calendar_manager.save_entries()
- QMessageBox.information(self, "Save Successful", f"Calendar saved to {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, "Load Calendar", "", "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, "Load Successful", f"Calendar loaded from {file_path}")
- 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, "Clear Calendar",
- "Are you sure you want to clear all events?",
- 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, "Success", "Calendar cleared successfully")
- 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__":
+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 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("Add New Event")
+ 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("Start Date:", 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("End Date:", self.end_date)
+
+ # Keyword selector
+ self.keyword = QComboBox()
+ self.keyword.addItems(self.keyword_list)
+ layout.addRow("Keyword:", self.keyword)
+
+ # Buttons
+ button_layout = QHBoxLayout()
+ self.save_button = QPushButton("Save")
+ self.save_button.clicked.connect(self.accept)
+ self.cancel_button = QPushButton("Cancel")
+ 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)
+
+ def get_data(self):
+ start_date = self.start_date.date().toString("yyyy-MM-dd")
+ end_date = self.end_date.date().toString("yyyy-MM-dd")
+ keyword = self.keyword.currentText()
+
+ return start_date, end_date, keyword
+
+
+class ModifyEventDialog(QDialog):
+ def __init__(self, entry, keyword_list, parent=None):
+ super().__init__(parent)
+ self.setWindowTitle("Modify Event")
+ 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("Start Date:", 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("End Date:", 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)
+ layout.addRow("Keyword:", self.keyword)
+
+ # Buttons
+ button_layout = QHBoxLayout()
+ self.save_button = QPushButton("Save")
+ self.save_button.clicked.connect(self.accept)
+ self.cancel_button = QPushButton("Cancel")
+ 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)
+
+ def get_data(self):
+ start_date = self.start_date.date().toString("yyyy-MM-dd")
+ end_date = self.end_date.date().toString("yyyy-MM-dd")
+ keyword = self.keyword.currentText()
+
+ return start_date, end_date, keyword
+
+
+class CalendarManagerGUI(QMainWindow):
+ def __init__(self):
+ super().__init__()
+ self.setWindowTitle("Calendar Manager")
+ self.setMinimumSize(800, 600)
+
+ # Initialize backend components
+ self.keyword_list = ["full_project", "half_project", "event"]
+ 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):
+ self.setWindowTitle('Calendar Manager GUI')
+ self.setMinimumSize(800, 600)
+
+ central_widget = QWidget()
+ main_layout = QVBoxLayout()
+
+ top_layout = QHBoxLayout()
+
+ # Launch date input
+ launch_date_layout = QVBoxLayout()
+ launch_date_label = QLabel("Launch Date:")
+ 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("Duration (years):")
+ 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("Predicted Completion Date:")
+ 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>Calendar Events</h3>")
+ events_layout.addWidget(events_title)
+
+ # Add event button
+ add_event_button = QPushButton("Add Event")
+ 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", "Start Date", "End Date", "Keyword", "Corrected Start", "Corrected End", "Actions"])
+ 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, "Delete Event",
+ "Are you sure you want to delete this event?",
+ 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("Modify")
+ delete_button = QPushButton("Delete")
+
+ # 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, "Save Calendar", "", "JSON Files (*.json)")
+ if not file_path:
+ return
+ self.calendar_manager.switch_file(file_path)
+
+ try:
+ self.calendar_manager.save_entries()
+ QMessageBox.information(self, "Save Successful", f"Calendar saved to {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, "Load Calendar", "", "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, "Load Successful", f"Calendar loaded from {file_path}")
+ 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, "Clear Calendar",
+ "Are you sure you want to clear all events?",
+ 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, "Success", "Calendar cleared successfully")
+ 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
index b62cf5d..be19b2f 100644
--- a/calendar_manager.py
+++ b/calendar_manager.py
@@ -1,136 +1,152 @@
-# 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
-
-
+# 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, commentary=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
+ self.commentary = commentary
+
+ 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,
+ 'commentary': self.commentary,
+ }
+
+ @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,
+ commentary=data.get('commentary'),
+ )
+
+ 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}, "
+ f"commentary={repr(self.commentary)})")
+
+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, commentary=None):
+ """Add a new event to the calendar."""
+ new_entry = CalendarEntry(start_date, end_date, keyword, commentary=commentary)
+ 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, commentary=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
+ if commentary is not None: # Allow setting empty string as commentary
+ entry.commentary = commentary
+ self.save_entries()
+ return entry
+ return None
+
+ def add_commentary(self, entry_id, commentary):
+ """Add or update commentary for an existing entry."""
+ entry = self.get_entry_by_id(entry_id)
+ if entry:
+ entry.commentary = commentary
+ 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
index e6c20fc..402a58c 100644
--- a/date_calculator.py
+++ b/date_calculator.py
@@ -1,154 +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 == "event":
- events.extend(v)
- elif k == "half_project":
- half_projects.extend(v)
- elif k == "full_project":
- full_projects.extend(v)
- elif k == "two_years":
- 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)
-
+# 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 == "event":
+ events.extend(v)
+ elif k == "half_project":
+ half_projects.extend(v)
+ elif k == "full_project":
+ full_projects.extend(v)
+ elif k == "two_years":
+ 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
index 4d5c1ed..0188e2e 100644
--- a/prediction_controller.py
+++ b/prediction_controller.py
@@ -1,48 +1,85 @@
-# 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
-
- 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
-
+# prediction_controller.py
+from dateutil.relativedelta import relativedelta
+from datetime import datetime, timedelta
+import math
+from prediction_storage import PredictionStorage
+
+class PredictionController:
+ def __init__(self, calendar_manager, date_calculator, keyword_list, prediction_storage=None):
+ 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
+ self.prediction_storage = prediction_storage
+ for keyword in keyword_list:
+ self.keyword = []
+
+ def set_parameters(self, launch_date, duration_years):
+ self.launch_date = datetime.fromisoformat(launch_date) if isinstance(launch_date, str) else launch_date
+ self.duration = relativedelta(years=duration_years)
+
+ def make_prediction(self, launch_date, duration_years, description=None):
+ 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
+
+ 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)
+
+ # Store the prediction if we have a storage object
+ if self.prediction_storage:
+ self.prediction_storage.add_prediction(
+ launch_date=self.launch_date,
+ duration_years=duration_years,
+ predicted_date=prediction,
+ keyword_args=keyword_args,
+ description=description
+ )
+
+ return prediction
+
+ def get_launch_date(self):
+ return self.launch_date
+
+ def get_duration(self):
+ return self.duration
+
+ def get_prediction(self):
+ return self.prediction
+
+ def get_all_predictions(self):
+ """Get all stored predictions."""
+ if self.prediction_storage:
+ return self.prediction_storage.list_predictions()
+ return []
+
+ def get_prediction_by_id(self, prediction_id):
+ """Get a specific prediction by ID."""
+ if self.prediction_storage:
+ return self.prediction_storage.get_prediction_by_id(prediction_id)
+ return None
+
+ def search_predictions(self, start_date=None, end_date=None, keyword=None):
+ """Search predictions by date range or keyword."""
+ if self.prediction_storage:
+ return self.prediction_storage.search_predictions(start_date, end_date, keyword)
+ return []
+
+ def update_prediction_description(self, prediction_id, description):
+ """Update a prediction's description."""
+ if self.prediction_storage:
+ return self.prediction_storage.update_prediction_description(prediction_id, description)
+ return None \ No newline at end of file
diff --git a/prediction_storage.py b/prediction_storage.py
new file mode 100644
index 0000000..95688c9
--- /dev/null
+++ b/prediction_storage.py
@@ -0,0 +1,151 @@
+# prediction_storage.py
+import json
+import uuid
+from datetime import datetime
+
+class Prediction:
+ def __init__(self, launch_date, duration_years, predicted_date, keyword_args=None, prediction_id=None, timestamp=None, description=None):
+ self.id = prediction_id if prediction_id else str(uuid.uuid4())
+ self.launch_date = launch_date if isinstance(launch_date, datetime) else datetime.fromisoformat(launch_date)
+ self.duration_years = duration_years
+ self.predicted_date = predicted_date if isinstance(predicted_date, datetime) else datetime.fromisoformat(predicted_date)
+ self.keyword_args = keyword_args or {}
+ self.timestamp = timestamp if timestamp else datetime.now()
+ self.description = description
+
+ def to_dict(self):
+ # Convert keyword_args dates to ISO format strings
+ serialized_keyword_args = {}
+ for keyword, events in self.keyword_args.items():
+ serialized_events = []
+ for event in events:
+ # Assuming each event is a tuple of (start_date, end_date, id)
+ start_date = event[0].isoformat() if isinstance(event[0], datetime) else event[0]
+ end_date = event[1].isoformat() if isinstance(event[1], datetime) else event[1]
+ serialized_events.append((start_date, end_date, event[2]))
+ serialized_keyword_args[keyword] = serialized_events
+
+ return {
+ 'id': self.id,
+ 'launch_date': self.launch_date.isoformat(),
+ 'duration_years': self.duration_years,
+ 'predicted_date': self.predicted_date.isoformat(),
+ 'keyword_args': serialized_keyword_args,
+ 'timestamp': self.timestamp.isoformat(),
+ 'description': self.description
+ }
+
+ @classmethod
+ def from_dict(cls, data):
+ # Deserialize keyword_args dates from ISO format strings
+ keyword_args = {}
+ for keyword, events in data.get('keyword_args', {}).items():
+ deserialized_events = []
+ for event in events:
+ # Convert string dates back to datetime objects
+ start_date = datetime.fromisoformat(event[0]) if isinstance(event[0], str) else event[0]
+ end_date = datetime.fromisoformat(event[1]) if isinstance(event[1], str) else event[1]
+ deserialized_events.append((start_date, end_date, event[2]))
+ keyword_args[keyword] = deserialized_events
+
+ return cls(
+ launch_date=data['launch_date'],
+ duration_years=data['duration_years'],
+ predicted_date=data['predicted_date'],
+ keyword_args=keyword_args,
+ prediction_id=data['id'],
+ timestamp=datetime.fromisoformat(data['timestamp']),
+ description=data.get('description')
+ )
+
+ def __repr__(self):
+ return (f"Prediction(id={self.id}, launch_date={self.launch_date}, "
+ f"duration_years={self.duration_years}, predicted_date={self.predicted_date}, "
+ f"timestamp={self.timestamp}, description={repr(self.description)})")
+
+class PredictionStorage:
+ def __init__(self, filename=None):
+ self.filename = filename
+ self.predictions = []
+ if filename:
+ self.load_predictions()
+
+ def load_predictions(self):
+ """Load predictions from file."""
+ if not self.filename:
+ return
+
+ try:
+ with open(self.filename, 'r') as f:
+ data = json.load(f)
+ self.predictions = [Prediction.from_dict(pred) for pred in data]
+ except (FileNotFoundError, json.JSONDecodeError) as e:
+ print(f"Error reading predictions file {self.filename}: {e}")
+ self.predictions = []
+
+ def save_predictions(self):
+ """Save predictions to file."""
+ if not self.filename:
+ return
+
+ try:
+ with open(self.filename, 'w') as f:
+ json.dump([pred.to_dict() for pred in self.predictions], f, indent=4)
+ except Exception as e:
+ print(f"Error writing to predictions file {self.filename}: {e}")
+
+ def add_prediction(self, launch_date, duration_years, predicted_date, keyword_args=None, description=None):
+ """Add a new prediction."""
+ prediction = Prediction(
+ launch_date=launch_date,
+ duration_years=duration_years,
+ predicted_date=predicted_date,
+ keyword_args=keyword_args,
+ description=description
+ )
+ self.predictions.append(prediction)
+ self.save_predictions()
+ return prediction
+
+ def get_prediction_by_id(self, prediction_id):
+ """Get a prediction by its ID."""
+ return next((pred for pred in self.predictions if pred.id == prediction_id), None)
+
+ def list_predictions(self):
+ """List all predictions."""
+ return self.predictions
+
+ def search_predictions(self, start_date=None, end_date=None, keyword=None):
+ """Search predictions by date range or keyword."""
+ results = self.predictions
+
+ if start_date:
+ start_dt = start_date if isinstance(start_date, datetime) else datetime.fromisoformat(start_date)
+ results = [p for p in results if p.predicted_date >= start_dt]
+
+ if end_date:
+ end_dt = end_date if isinstance(end_date, datetime) else datetime.fromisoformat(end_date)
+ results = [p for p in results if p.predicted_date <= end_dt]
+
+ if keyword:
+ results = [p for p in results if keyword in p.keyword_args]
+
+ return results
+
+ def delete_prediction(self, prediction_id):
+ """Delete a prediction by ID."""
+ prediction = self.get_prediction_by_id(prediction_id)
+ if prediction:
+ self.predictions.remove(prediction)
+ self.save_predictions()
+ return prediction
+ return None
+
+ def update_prediction_description(self, prediction_id, description):
+ """Update a prediction's description."""
+ prediction = self.get_prediction_by_id(prediction_id)
+ if prediction:
+ prediction.description = description
+ self.save_predictions()
+ return prediction
+ return None \ No newline at end of file
diff --git a/test.py b/test.py
index a2812c2..73225ce 100644
--- a/test.py
+++ b/test.py
@@ -1,40 +1,93 @@
-# 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, ["full_project", "half_project", "event"])
-
- calendar_manager.add_entry(event1_str[0], event1_str[1], "event")
- calendar_manager.add_entry(event2_str[0], event2_str[1], "event")
- calendar_manager.add_entry(project1_str[0], project1_str[1], "full_project")
- calendar_manager.add_entry(project2_str[0], project2_str[1], "half_project")
- calendar_manager.add_entry(project3_str[0], project3_str[1], "half_project")
-
- # 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
+# test.py
+from date_calculator import DateCalculator
+from calendar_manager import CalendarManager
+from prediction_controller import PredictionController
+from prediction_storage import PredictionStorage
+
+def test_updated_functionality():
+ # Create necessary objects
+ date_calculator = DateCalculator()
+ calendar_manager = CalendarManager()
+ prediction_storage = PredictionStorage("predictions.json")
+ prediction_controller = PredictionController(
+ calendar_manager,
+ date_calculator,
+ ["full_project", "half_project", "event"],
+ prediction_storage
+ )
+
+ # Add calendar entries with commentary
+ calendar_manager.add_entry(
+ "2023-01-01",
+ "2023-01-10",
+ "event",
+ commentary="Annual company meeting"
+ )
+
+ calendar_manager.add_entry(
+ "2023-01-05",
+ "2023-01-15",
+ "event",
+ commentary="Product launch conference"
+ )
+
+ calendar_manager.add_entry(
+ "2025-01-02",
+ "2025-02-11",
+ "full_project",
+ commentary="Website redesign project"
+ )
+
+ # Test adding commentary to existing entry
+ entries = calendar_manager.list_entries()
+ project_entry = entries[2] # Get the third entry (full_project)
+ calendar_manager.add_commentary(project_entry.id, "Updated: Major redesign with new branding")
+
+ # Make prediction with description
+ prediction_controller.make_prediction(
+ "2023-01-01",
+ 2,
+ description="Initial project timeline prediction"
+ )
+
+ # Make another prediction
+ prediction_controller.make_prediction(
+ "2023-02-01",
+ 1,
+ description="Revised timeline after scope changes"
+ )
+
+ # Retrieve and display stored predictions
+ all_predictions = prediction_controller.get_all_predictions()
+ print("\nAll stored predictions:")
+ for pred in all_predictions:
+ print(f"- {pred.id}: {pred.launch_date.strftime('%Y-%m-%d')} to {pred.predicted_date.strftime('%Y-%m-%d')} ({pred.duration_years} years)")
+ print(f" Description: {pred.description}")
+
+ # Search predictions
+ if all_predictions:
+ # Update description of first prediction
+ first_pred_id = all_predictions[0].id
+ prediction_controller.update_prediction_description(
+ first_pred_id,
+ "Updated: Initial project timeline with adjusted parameters"
+ )
+
+ # Search by date range
+ date_filtered = prediction_controller.search_predictions(
+ start_date="2024-01-01",
+ end_date="2025-12-31"
+ )
+ print("\nPredictions ending in 2024-2025:")
+ for pred in date_filtered:
+ print(f"- {pred.id}: ends on {pred.predicted_date.strftime('%Y-%m-%d')}")
+
+ # Display calendar entries with commentary
+ print("\nCalendar entries with commentary:")
+ for entry in calendar_manager.list_entries():
+ print(f"- {entry.keyword}: {entry.start_date.strftime('%Y-%m-%d')} to {entry.end_date.strftime('%Y-%m-%d')}")
+ if entry.commentary:
+ print(f" Commentary: {entry.commentary}")
+
+if __name__ == "__main__":
+ test_updated_functionality() \ No newline at end of file