diff --git a/cara/apps/calculator/model_generator.py b/cara/apps/calculator/model_generator.py index 6c545152..006ec25c 100644 --- a/cara/apps/calculator/model_generator.py +++ b/cara/apps/calculator/model_generator.py @@ -458,14 +458,10 @@ class FormData: present_intervals = [] - def hours2time(hours: float): - # Convert times like 14.5 to strings, like "14:30" - return f"{int(np.floor(hours)):02d}:{int(np.round((hours % 1) * 60)):02d}" - # def add_interval(start, end): current_time = start - LOG.debug(f"starting time march at {hours2time(current_time/60)} to {hours2time(finish/60)}") + LOG.debug(f"starting time march at {_hours2timestring(current_time/60)} to {_hours2timestring(finish/60)}") # As we step through the breaks. For each break there are 6 important cases # we must cover. Let S=start; E=end; Bs=Break start; Be=Break end: @@ -480,8 +476,8 @@ class FormData: if current_time >= finish: break - LOG.debug(f"handling break {hours2time(current_break[0]/60)}-{hours2time(current_break[1]/60)} " - f" (current time: {hours2time(current_time/60)})") + LOG.debug(f"handling break {_hours2timestring(current_break[0]/60)}-{_hours2timestring(current_break[1]/60)} " + f" (current time: {_hours2timestring(current_time/60)})") break_s, break_e = current_break case1 = finish <= break_s @@ -494,22 +490,22 @@ class FormData: if case1: LOG.debug(f"case 1: interval entirely before break") present_intervals.append((current_time / 60, finish / 60)) - LOG.debug(f" + added interval {hours2time(present_intervals[-1][0])} " - f"- {hours2time(present_intervals[-1][1])}") + LOG.debug(f" + added interval {_hours2timestring(present_intervals[-1][0])} " + f"- {_hours2timestring(present_intervals[-1][1])}") current_time = finish elif case2: LOG.debug(f"case 2: interval straddles start of break") present_intervals.append((current_time / 60, break_s / 60)) - LOG.debug(f" + added interval {hours2time(present_intervals[-1][0])} " - f"- {hours2time(present_intervals[-1][1])}") + LOG.debug(f" + added interval {_hours2timestring(present_intervals[-1][0])} " + f"- {_hours2timestring(present_intervals[-1][1])}") current_time = break_e elif case3: LOG.debug(f"case 3: break entirely inside interval") # We add the bit before the break, but not the bit afterwards, # as it may hit another break. present_intervals.append((current_time / 60, break_s / 60)) - LOG.debug(f" + added interval {hours2time(present_intervals[-1][0])} " - f"- {hours2time(present_intervals[-1][1])}") + LOG.debug(f" + added interval {_hours2timestring(present_intervals[-1][0])} " + f"- {_hours2timestring(present_intervals[-1][1])}") current_time = break_e elif case4: LOG.debug(f"case 4: interval entirely inside break") @@ -652,6 +648,11 @@ WINDOWS_TYPES = {'window_sliding', 'window_hinged', 'not-applicable'} COFFEE_OPTIONS_INT = {'coffee_break_0': 0, 'coffee_break_1': 1, 'coffee_break_2': 2, 'coffee_break_4': 4} +def _hours2timestring(hours: float): + # Convert times like 14.5 to strings, like "14:30" + return f"{int(np.floor(hours)):02d}:{int(np.round((hours % 1) * 60)):02d}" + + def time_string_to_minutes(time: str) -> minutes_since_midnight: """ Converts time from string-format to an integer number of minutes after 00:00 diff --git a/cara/apps/calculator/report_generator.py b/cara/apps/calculator/report_generator.py index b7b1551a..ea22cbb6 100644 --- a/cara/apps/calculator/report_generator.py +++ b/cara/apps/calculator/report_generator.py @@ -1,6 +1,6 @@ import base64 import dataclasses -from datetime import datetime +from datetime import datetime, timedelta import io import typing import zlib @@ -126,18 +126,20 @@ def img2base64(img_data) -> str: def plot(times, concentrations, model: models.ExposureModel): fig = plt.figure() ax = fig.add_subplot(1, 1, 1) - ax.plot(times, concentrations, lw=2, color='#1f77b4', label='Concentration') + datetimes = [datetime(1970, 1, 1) + timedelta(hours=time) for time in times] + ax.plot(datetimes, concentrations, lw=2, color='#1f77b4', label='Concentration') ax.spines['right'].set_visible(False) ax.spines['top'].set_visible(False) - ax.set_xlabel('Time (hour of day)') + ax.set_xlabel('Time of day') ax.set_ylabel('Concentration ($q/m^3$)') ax.set_title('Concentration of infectious quanta') + ax.xaxis.set_major_formatter(matplotlib.dates.DateFormatter("%H:%M")) # Plot presence of exposed person for i, (presence_start, presence_finish) in enumerate(model.exposed.presence.boundaries()): plt.fill_between( - times, concentrations, 0, + datetimes, concentrations, 0, where=(np.array(times) > presence_start) & (np.array(times) < presence_finish), color="#1f77b4", alpha=0.1, label="Presence of exposed person(s)" if i == 0 else "" @@ -240,19 +242,21 @@ def comparison_plot(scenarios: typing.Dict[str, models.ExposureModel]): if times is None: t_start, t_end = model_start_end(model) times = np.linspace(t_start, t_end, resolution) + datetimes = [datetime(1970, 1, 1) + timedelta(hours=time) for time in times] concentrations = [model.concentration_model.concentration(time) for time in times] if name in dash_styled_scenarios: - ax.plot(times, concentrations, label=name, linestyle='--') + ax.plot(datetimes, concentrations, label=name, linestyle='--') else: - ax.plot(times, concentrations, label=name, linestyle='-', alpha=0.5) + ax.plot(datetimes, concentrations, label=name, linestyle='-', alpha=0.5) # Place a legend outside of the axes itself. ax.legend(bbox_to_anchor=(1.05, 1), loc='upper left') ax.spines['right'].set_visible(False) ax.spines['top'].set_visible(False) + ax.xaxis.set_major_formatter(matplotlib.dates.DateFormatter("%H:%M")) - ax.set_xlabel('Time (hour of day)') + ax.set_xlabel('Time of day') ax.set_ylabel('Concentration ($q/m^3$)') ax.set_title('Concentration of infectious quanta') diff --git a/cara/tests/apps/calculator/test_model_generator.py b/cara/tests/apps/calculator/test_model_generator.py index 58c34092..6c5050e4 100644 --- a/cara/tests/apps/calculator/test_model_generator.py +++ b/cara/tests/apps/calculator/test_model_generator.py @@ -3,6 +3,7 @@ import dataclasses import pytest from cara.apps.calculator import model_generator +from cara.apps.calculator.model_generator import _hours2timestring from cara.apps.calculator.model_generator import minutes_since_midnight from cara import models from cara import data @@ -272,13 +273,8 @@ def time2mins(time: str) -> minutes_since_midnight: return minutes_since_midnight(int(time.split(':')[0]) * 60 + int(time.split(':')[1])) -def hours2time(hours: float): - # Convert times like 14.5 to strings, like "14:30" - return f"{int(np.floor(hours)):02d}:{int(np.round((hours % 1) * 60)):02d}" - - def assert_boundaries(interval, boundaries_in_time_string_form): - boundaries = [(hours2time(start), hours2time(end)) + boundaries = [(_hours2timestring(start), _hours2timestring(end)) for start, end in interval.boundaries()] assert boundaries == boundaries_in_time_string_form