summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormatin <matin.kaufmann@gmail.com>2025-09-12 17:24:18 +0200
committermatin <matin.kaufmann@gmail.com>2025-09-12 17:24:18 +0200
commita1094ac2e0762073dc527d11e8555ae12b208294 (patch)
tree5a0cab6a6bbd6c34a1f806e8d9482ed941411b8d
parent39654f7140ebfeafc2473ceb7e4739ed6d03fc1c (diff)
(hopefully) fixed bug with overlapping time periods
-rw-r--r--calendar_manager.py7
-rw-r--r--date_calculator.py131
2 files changed, 56 insertions, 82 deletions
diff --git a/calendar_manager.py b/calendar_manager.py
index 9ef6596..f6ec0db 100644
--- a/calendar_manager.py
+++ b/calendar_manager.py
@@ -4,7 +4,7 @@ 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: str = ""):
+ def __init__(self, start_date, end_date, keyword, entry_id=None, corrected_start_date=None, corrected_end_date=None, time_period=None, commentary: str = ""):
self.id = entry_id if entry_id else str(uuid.uuid4())
# Convert string dates to datetime if necessary
self.start_date = (
@@ -20,6 +20,7 @@ class CalendarEntry:
self.keyword = keyword
self.corrected_start_date = corrected_start_date
self.corrected_end_date = corrected_end_date
+ self.time_period = time_period
self.commentary = commentary
def to_dict(self):
return {
@@ -135,9 +136,13 @@ class CalendarManager:
for entry in self.entries:
entry.corrected_start_date = None
entry.corrected_end_date = None
+ entry.time_period = None
for start_date, end_date, original_id in list_of_events:
entry = self.get_entry_by_id(original_id)
+ if not entry:
+ continue
+
entry.corrected_start_date = start_date
entry.corrected_end_date = end_date
diff --git a/date_calculator.py b/date_calculator.py
index fe74827..bca7dda 100644
--- a/date_calculator.py
+++ b/date_calculator.py
@@ -1,11 +1,14 @@
-# date_calculator.py
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
import math
class DateCalculator:
@staticmethod
- def truncate_periods(periods, launch, prediction):
+ def sort_periods(periods):
+ return sorted(periods, key=lambda p: (p[0], p[1]))
+
+ @staticmethod
+ def truncate_periods(periods, launch):
considered_periods = []
for start, end, id in periods:
# print(start)
@@ -19,8 +22,14 @@ class DateCalculator:
def round_periods(periods):
rounded_periods = []
total_months = 0
+
+ last_end = None
for start, end, id in periods:
+ if last_end and start <= last_end:
+ start = last_end + timedelta(days=1)
+ if start > end:
+ continue
year_diff = end.year - start.year
month_diff = end.month - start.month
months = year_diff * 12 + month_diff
@@ -30,29 +39,9 @@ class DateCalculator:
rounded_periods.append((start, rounded_end, id))
total_months += months
+ last_end = rounded_end
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 adjust_periods(periods):
@@ -63,9 +52,6 @@ class DateCalculator:
if not periods:
return []
- # Sort by start date, then end date for stability
- periods = sorted(periods, key=lambda p: (p[0], p[1]))
-
adjusted = []
for start, end, pid in periods:
if not adjusted:
@@ -87,10 +73,9 @@ class DateCalculator:
adjusted.append((start, end, pid))
return adjusted
-
+
@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 = []
@@ -113,19 +98,16 @@ class DateCalculator:
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
+
+ @staticmethod
+ def calculate_prediction(launch_date, duration, **kwargs):
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)
@@ -137,51 +119,38 @@ class DateCalculator:
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
+
+ events = DateCalculator.sort_periods(events)
+ half_projects = DateCalculator.sort_periods(half_projects)
+ full_projects = DateCalculator.sort_periods(full_projects)
+
+ considered_events = DateCalculator.truncate_periods(events, launch_date)
+ considered_full_projects = DateCalculator.truncate_periods(full_projects, launch_date)
+ considered_half_projects = DateCalculator.truncate_periods(half_projects, 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)
)
- merged_event_periods = self.adjust_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
+ total_months = months + math.ceil(months2 / 2)
+ total_days = sum((end - start).days + 1 for start, end, _ in non_overlapping_event_periods)
+ prediction = launch_date + duration + relativedelta(months=total_months) + timedelta(days=total_days-1)
+
+ prediction = min(prediction, prediction_start + relativedelta(years = 6))
+
+ return prediction, considered_full_projects_rounded + considered_half_projects_rounded + non_overlapping_event_periods \ No newline at end of file