summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormatin <matin.kaufmann@gmail.com>2025-09-12 22:18:32 +0200
committermatin <matin.kaufmann@gmail.com>2025-09-12 22:18:32 +0200
commit6de01b3ee171e60e6609aea51ef7c9056736a017 (patch)
tree507de23a7db2d23725e5769249d76feab22a8717
parent95d784fb414c6270e560fc0cf7ed289765ddd3ab (diff)
bug fix: corrected start date after end date for overlapping "Sonstige"
-rw-r--r--date_calculator.py9
-rw-r--r--prediction_controller.py47
-rw-r--r--test.py60
3 files changed, 47 insertions, 69 deletions
diff --git a/date_calculator.py b/date_calculator.py
index 576d6a1..cec4d40 100644
--- a/date_calculator.py
+++ b/date_calculator.py
@@ -64,14 +64,14 @@ class DateCalculator:
last_start, last_end, last_pid = adjusted[-1]
if start <= last_end:
- # Fully contained in previous period → discard
+ # Fully contained in previous period \u2192 discard
if end <= last_end:
continue
# Overlaps head; push start to the day after last_end
new_start = last_end + timedelta(days=1)
if new_start <= end:
adjusted.append((new_start, end, period_id))
- # else new_start > end → discard
+ # else new_start > end \u2192 discard
else:
adjusted.append((start, end, period_id))
@@ -104,6 +104,11 @@ class DateCalculator:
return non_overlapping_periods
@staticmethod
+ def filter_valid_periods(periods: List[Tuple[datetime, datetime, str]]) -> List[Tuple[datetime, datetime, str]]:
+ """Filter out periods where start date is after end date"""
+ return [(start, end, period_id) for start, end, period_id in periods if start <= end]
+
+ @staticmethod
def calculate_total_days(periods: List[Tuple[datetime, datetime, str]]) -> int:
"""Calculate total days across all periods"""
return sum((end - start).days + 1 for start, end, _ in periods)
diff --git a/prediction_controller.py b/prediction_controller.py
index 916a325..168ad62 100644
--- a/prediction_controller.py
+++ b/prediction_controller.py
@@ -102,8 +102,11 @@ class PredictionController:
sorted_projects = DateCalculator.sort_periods(full_projects)
considered_projects = DateCalculator.truncate_periods(sorted_projects, self.launch_date)
+ # Filter out any invalid periods
+ valid_projects = DateCalculator.filter_valid_periods(considered_projects)
+
# Round to month boundaries
- rounded_projects, total_months = DateCalculator.round_periods(considered_projects)
+ rounded_projects, total_months = DateCalculator.round_periods(valid_projects)
return rounded_projects
@@ -114,15 +117,20 @@ class PredictionController:
sorted_projects = DateCalculator.sort_periods(half_projects)
considered_projects = DateCalculator.truncate_periods(sorted_projects, self.launch_date)
+ # Filter out any invalid periods
+ valid_projects = DateCalculator.filter_valid_periods(considered_projects)
+
# Find non-overlapping periods with full projects
non_overlapping_projects = []
- for test_interval in considered_projects:
- non_overlapping_projects.extend(
- DateCalculator.find_non_overlapping_periods(full_projects, test_interval)
- )
+ for test_interval in valid_projects:
+ non_overlapping_results = DateCalculator.find_non_overlapping_periods(full_projects, test_interval)
+ non_overlapping_projects.extend(non_overlapping_results)
+
+ # Filter valid periods again after overlap processing
+ valid_non_overlapping = DateCalculator.filter_valid_periods(non_overlapping_projects)
# Round to month boundaries
- rounded_projects, total_months = DateCalculator.round_periods(non_overlapping_projects)
+ rounded_projects, total_months = DateCalculator.round_periods(valid_non_overlapping)
return rounded_projects
@@ -134,18 +142,26 @@ class PredictionController:
sorted_events = DateCalculator.sort_periods(other_events)
considered_events = DateCalculator.truncate_periods(sorted_events, self.launch_date)
- # Adjust overlapping periods
- adjusted_events = DateCalculator.adjust_periods(considered_events)
+ # Filter out any invalid periods
+ valid_events = DateCalculator.filter_valid_periods(considered_events)
# Find non-overlapping periods with all projects
all_projects = DateCalculator.sort_periods(full_projects + half_projects)
non_overlapping_events = []
- for test_interval in adjusted_events:
- non_overlapping_events.extend(
- DateCalculator.find_non_overlapping_periods(all_projects, test_interval)
- )
+ for test_interval in valid_events:
+ non_overlapping_results = DateCalculator.find_non_overlapping_periods(all_projects, test_interval)
+ non_overlapping_events.extend(non_overlapping_results)
- return non_overlapping_events
+ # Filter valid periods again after overlap processing
+ valid_non_overlapping = DateCalculator.filter_valid_periods(non_overlapping_events)
+
+ # Adjust overlapping periods
+ adjusted_events = DateCalculator.adjust_periods(valid_non_overlapping)
+
+ # Final validation to ensure all periods are valid
+ final_valid_events = DateCalculator.filter_valid_periods(adjusted_events)
+
+ return final_valid_events
def _calculate_final_prediction(self, prediction_start: datetime,
processed_events: Dict[str, List[Tuple[datetime, datetime, str]]]) -> datetime:
@@ -179,7 +195,7 @@ class PredictionController:
return final_prediction
def _apply_corrections_to_calendar(self):
- """Apply corrected dates to calendar entries"""
+ """Apply corrected dates to calendar entries and calculate time_period"""
# Collect all corrected events from processed results
all_corrected_events = []
@@ -225,5 +241,4 @@ class PredictionController:
if launch_dt > datetime(2100, 1, 1):
errors.append("Launch date too far in the future")
- return errors
-
+ return errors \ No newline at end of file
diff --git a/test.py b/test.py
index 2b795eb..cbad19d 100644
--- a/test.py
+++ b/test.py
@@ -10,14 +10,14 @@ def test_calculate_prediction():
print("Testing refactored prediction calculation system...")
# Input event dates as strings
- event1_str = ["2023-01-01", "2023-01-10"]
+ event1_str = ["2023-01-01", "2023-01-02"]
event2_str = ["2023-01-05", "2023-01-15"]
# Input full project dates as strings
- project1_str = ["2025-01-02", "2025-02-11"]
+ project1_str = ["2023-01-01", "2024-12-31"]
# Input half project dates as strings
- project2_str = ["2025-02-01", "2025-06-30"]
+ project2_str = ["2025-01-01", "2026-12-31"]
project3_str = ["2024-05-05", "2024-06-07"]
# Initialize components
@@ -27,11 +27,11 @@ def test_calculate_prediction():
# Add entries to calendar
print("Adding calendar entries...")
+ # calendar_manager.add_entry(event2_str[0], event2_str[1], "Sonstige")
+ calendar_manager.add_entry(project1_str[0], project1_str[1], "EZ pauschal")
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], "EZ 100%")
- calendar_manager.add_entry(project2_str[0], project2_str[1], "EZ 50%")
- calendar_manager.add_entry(project3_str[0], project3_str[1], "EZ 50%")
+ calendar_manager.add_entry(project2_str[0], project2_str[1], "EZ pauschal")
+ # calendar_manager.add_entry(project3_str[0], project3_str[1], "EZ 50%")
# Test validation
print("Testing input validation...")
@@ -43,12 +43,12 @@ def test_calculate_prediction():
# Calculate prediction
print("Calculating prediction...")
- success = prediction_controller.make_prediction("2023-01-01", 2)
+ success = prediction_controller.make_prediction("2023-01-01", 1)
if success:
prediction = prediction_controller.get_prediction()
print(f"Predicted completion date: {prediction}")
-
+ print(calendar_manager.entries)
# Test event type handler
print("\nTesting EventTypeHandler...")
entries = calendar_manager.list_entries()
@@ -64,48 +64,6 @@ def test_calculate_prediction():
else:
print("Prediction calculation failed")
-def test_event_type_handler():
- """Test the EventTypeHandler functionality"""
- print("\nTesting EventTypeHandler...")
-
- # Test keyword validation
- for keyword in EventConfig.KEYWORDS:
- is_valid = EventTypeHandler.validate_event_type(keyword)
- print(f"Keyword '{keyword}' is valid: {is_valid}")
-
- # Test duration calculation
- from datetime import datetime
- start_date = datetime(2023, 1, 1)
- duration = EventTypeHandler.get_duration_for_type("EZ pauschal", start_date)
- print(f"EZ pauschal duration from {start_date}: {duration}")
-
- # Test display names
- for keyword in EventConfig.KEYWORDS:
- display_name = EventTypeHandler.get_event_type_display_name(keyword)
- print(f"'{keyword}' -> '{display_name}'")
-
-def test_date_calculator():
- """Test the refactored DateCalculator"""
- print("\nTesting DateCalculator...")
-
- from datetime import datetime
-
- # Test period sorting
- periods = [
- (datetime(2023, 3, 1), datetime(2023, 3, 10), "id1"),
- (datetime(2023, 1, 1), datetime(2023, 1, 10), "id2"),
- (datetime(2023, 2, 1), datetime(2023, 2, 10), "id3")
- ]
-
- sorted_periods = DateCalculator.sort_periods(periods)
- print(f"Sorted periods: {[(p[0].strftime('%Y-%m-%d'), p[1].strftime('%Y-%m-%d'), p[2]) for p in sorted_periods]}")
-
- # Test truncation
- launch_date = datetime(2023, 1, 15)
- truncated = DateCalculator.truncate_periods(sorted_periods, launch_date)
- print(f"Truncated periods: {[(p[0].strftime('%Y-%m-%d'), p[1].strftime('%Y-%m-%d'), p[2]) for p in truncated]}")
if __name__ == "__main__":
test_calculate_prediction()
- test_event_type_handler()
- test_date_calculator() \ No newline at end of file