From d62c4b9b194ad6233107cf71a87870441b2b0d4b Mon Sep 17 00:00:00 2001 From: Kaufmann Date: Wed, 2 Apr 2025 11:16:56 +0200 Subject: Vorstellung des Ausfallzeitenrechners im JF von Abt. 2.5. Initiale Kritik: Kommentarfeld, speichern als PDF, Datumsformat --- date_calculator.py | 154 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 date_calculator.py (limited to 'date_calculator.py') 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 -- cgit v1.1