From 38acbe95c90d53665f43a6de3bdd139d859d6060 Mon Sep 17 00:00:00 2001 From: Avery Date: Tue, 15 Oct 2024 14:16:36 -0700 Subject: [PATCH] Do not round up incident cost (#5180) * Do not round up incident cost * Fixes typing * Fixes typing. * Fixes test * Removes unused import. * Fixes tests * Fixes function annotations. * Fixes function annotations --- src/dispatch/incident_cost/service.py | 26 +++++++++---------- .../test_incident_cost_service.py | 13 ++++------ 2 files changed, 17 insertions(+), 22 deletions(-) diff --git a/src/dispatch/incident_cost/service.py b/src/dispatch/incident_cost/service.py index 66b75886e097..9d21ad16868e 100644 --- a/src/dispatch/incident_cost/service.py +++ b/src/dispatch/incident_cost/service.py @@ -146,11 +146,9 @@ def update_incident_response_cost_for_incident_type( def calculate_response_cost( hourly_rate, total_response_time_seconds, incident_review_hours=0 -) -> int: - """Calculates and rounds up the incident response cost.""" - return math.ceil( - ((total_response_time_seconds / SECONDS_IN_HOUR) + incident_review_hours) * hourly_rate - ) +) -> float: + """Calculates the incident response cost.""" + return ((total_response_time_seconds / SECONDS_IN_HOUR) + incident_review_hours) * hourly_rate def get_default_incident_response_cost( @@ -235,7 +233,7 @@ def fetch_incident_events( def calculate_incident_response_cost_with_cost_model( incident: Incident, db_session: SessionLocal -) -> int: +) -> float: """Calculates the cost of an incident using the incident's cost model. This function aggregates all new incident costs based on plugin activity since the last incident cost update. @@ -246,7 +244,7 @@ def calculate_incident_response_cost_with_cost_model( db_session: The database session. Returns: - int: The incident response cost in dollars. + float: The incident response cost in dollars. """ participants_total_response_time_seconds = 0 @@ -304,12 +302,12 @@ def calculate_incident_response_cost_with_cost_model( total_response_time_seconds=participants_total_response_time_seconds, ) - return incident.total_cost + amount + return float(incident.total_cost) + amount def get_participant_role_time_seconds( incident: Incident, participant_role: ParticipantRole, start_at: datetime -) -> int: +) -> float: """Calculates the time spent by a participant in an incident role starting from a given time. The participant's time spent in the incident role is adjusted based on the role's engagement multiplier. @@ -320,7 +318,7 @@ def get_participant_role_time_seconds( start_at: Only time spent after this will be considered. Returns: - int: The time spent by the participant in the incident role in seconds. + float: The time spent by the participant in the incident role in seconds. """ if participant_role.renounced_at and participant_role.renounced_at < start_at: # skip calculating already-recorded activity @@ -372,7 +370,7 @@ def get_participant_role_time_seconds( # TODO(mvilanova): adjust based on incident priority if participant_role_time_hours > HOURS_IN_DAY: days, hours = divmod(participant_role_time_hours, HOURS_IN_DAY) - participant_role_time_hours = math.ceil(((days * HOURS_IN_DAY) / 3) + hours) + participant_role_time_hours = ((days * HOURS_IN_DAY) / 3) + hours # we make the assumption that participants spend more or less time based on their role # and we adjust the time spent based on that @@ -410,7 +408,7 @@ def get_total_participant_roles_time_seconds(incident: Incident, start_at: datet def calculate_incident_response_cost_with_classic_model( incident: Incident, db_session: SessionLocal, incident_review: bool = False -) -> int: +) -> float: """Calculates the cost of an incident using the classic incident cost model. This function aggregates all new incident costs since the last incident cost update. If this is the first time performing cost calculation for this incident, it computes the total costs from the incident's creation. @@ -421,7 +419,7 @@ def calculate_incident_response_cost_with_classic_model( incident_review: Whether to add the incident review costs in this calculation. Returns: - int: The incident response cost in dollars. + float: The incident response cost in dollars. """ last_update = incident.created_at incident_review_hours = 0 @@ -456,7 +454,7 @@ def calculate_incident_response_cost_with_classic_model( incident_review_hours=incident_review_hours, ) - return incident_response_cost.amount + amount + return float(incident_response_cost.amount) + amount def calculate_incident_response_cost( diff --git a/tests/incident_cost/test_incident_cost_service.py b/tests/incident_cost/test_incident_cost_service.py index 7ca6a0c2296c..46e9682d1029 100644 --- a/tests/incident_cost/test_incident_cost_service.py +++ b/tests/incident_cost/test_incident_cost_service.py @@ -109,7 +109,7 @@ def test_calculate_incident_response_cost_with_cost_model( ): """Tests that the incident cost is calculated correctly when a cost model is enabled.""" from datetime import timedelta - import math + from decimal import Decimal from dispatch.incident_cost.service import update_incident_response_cost, get_hourly_rate from dispatch.incident_cost_type import service as incident_cost_type_service from dispatch.participant_activity.service import ( @@ -150,15 +150,12 @@ def test_calculate_incident_response_cost_with_cost_model( participants_total_response_time_seconds += activity.ended_at - activity.started_at hourly_rate = get_hourly_rate(incident.project) expected_incident_cost = ( - math.ceil( - (participants_total_response_time_seconds.seconds / SECONDS_IN_HOUR) * hourly_rate - ) - + orig_total_incident_cost - ) + participants_total_response_time_seconds.seconds / SECONDS_IN_HOUR + ) * hourly_rate + orig_total_incident_cost assert cost - assert cost == expected_incident_cost - assert cost == incident.total_cost + assert cost == Decimal(expected_incident_cost).quantize(cost) + assert cost == Decimal(incident.total_cost).quantize(cost) def test_calculate_incident_response_cost_with_cost_model__no_enabled_plugins(