diff options
| author | matin <matin.kaufmann@gmail.com> | 2025-09-12 17:24:18 +0200 |
|---|---|---|
| committer | matin <matin.kaufmann@gmail.com> | 2025-09-12 17:24:18 +0200 |
| commit | a1094ac2e0762073dc527d11e8555ae12b208294 (patch) | |
| tree | 5a0cab6a6bbd6c34a1f806e8d9482ed941411b8d | |
| parent | 39654f7140ebfeafc2473ceb7e4739ed6d03fc1c (diff) | |
(hopefully) fixed bug with overlapping time periods
| -rw-r--r-- | calendar_manager.py | 7 | ||||
| -rw-r--r-- | date_calculator.py | 131 |
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 |
