Exemplo n.º 1
0
def insulin_on_board_calc(type_, start_date, end_date, value,
                          scheduled_basal_rate, date, model, delay, delta):
    """ Calculates the insulin on board for a specific dose at a specific time

    Arguments:
    type_ -- String with type of dose (bolus, basal, etc)
    start_date -- the date the dose started at (datetime object)
    end_date -- the date the dose ended at (datetime object)
    value -- insulin value for dose
    scheduled_basal_rate -- basal rate scheduled during the times of dose
                            (0 for a bolus)
    date -- date the IOB is being calculated (datetime object)
    model -- list of insulin model parameters in format [DIA, peak_time]
    delay -- the time to delay the dose effect
    delta -- the differential between timeline entries

    Output:
    IOB at date
    """
    time = time_interval_since(date, start_date)

    if start_date > end_date or time < 0:
        return 0

    if len(model) == 1:  # walsh model
        if time_interval_since(end_date, start_date) <= 1.05 * delta * 60:
            return net_basal_units(
                type_, value, start_date, end_date,
                scheduled_basal_rate) * walsh_percent_effect_remaining(
                    (time / 60 - delay), model[0])
        # This will normally be for basals
        return net_basal_units(
            type_, value, start_date, end_date,
            scheduled_basal_rate) * continuous_delivery_insulin_on_board(
                start_date, end_date, date, model, delay, delta)

    # Consider doses within the delta time window as momentary
    # This will normally be for boluses or short temp basals
    if time_interval_since(end_date, start_date) <= 1.05 * delta * 60:
        return net_basal_units(
            type_, value, start_date, end_date,
            scheduled_basal_rate) * percent_effect_remaining(
                (time / 60 - delay), model[0], model[1])
    # This will normally be for basals
    return net_basal_units(
        type_, value, start_date, end_date,
        scheduled_basal_rate) * continuous_delivery_insulin_on_board(
            start_date, end_date, date, model, delay, delta)
Exemplo n.º 2
0
def glucose_effect(dose_type, dose_start_date, dose_end_date, dose_value,
                   scheduled_basal_rate, date, model, insulin_sensitivity,
                   delay, delta):
    """ Calculates the timeline of glucose effects for a specific dose

    Arguments:
    dose_type -- types of dose (basal, bolus, etc)
    dose_start_date -- datetime object representing date doses start at
    dose_end_date -- datetime object representing date dose ended at
    dose_value -- insulin value for dose
    scheduled_basal_rate -- basal rate scheduled during the time of dose
    date -- datetime object of time to calculate the effect at
    insulin_sensitivity -- sensitivity (mg/dL/U)
    delay -- the time to delay the dose effect
    delta -- the differential between timeline entries

    Output:
    Glucose effect (mg/dL)
    """
    time = time_interval_since(date, dose_start_date)
    delay *= 60
    delta *= 60

    if time < 0:
        return 0

    # Consider doses within the delta time window as momentary
    # This will normally be for boluses
    if time_interval_since(dose_end_date, dose_start_date) <= 1.05 * delta:  # pylint: disable=C0330

        if len(model) == 1:  # walsh model
            return net_basal_units(
                dose_type, dose_value, dose_start_date, dose_end_date,
                scheduled_basal_rate) * -insulin_sensitivity * (
                    1 - walsh_percent_effect_remaining(
                        (time - delay) / 60, model[0]))

        return net_basal_units(
            dose_type, dose_value, dose_start_date, dose_end_date,
            scheduled_basal_rate) * -insulin_sensitivity * (
                1 - percent_effect_remaining(
                    (time - delay) / 60, model[0], model[1]))
    # This will normally be for basals, and handles Walsh model automatically
    return net_basal_units(
        dose_type, dose_value, dose_start_date, dose_end_date,
        scheduled_basal_rate
    ) * -insulin_sensitivity * continuous_delivery_glucose_effect(
        dose_start_date, dose_end_date, date, model, delay / 60, delta / 60)
Exemplo n.º 3
0
def carbs_on_board_helper(carb_start,
                          carb_value,
                          at_time,
                          default_absorption_time,
                          delay,
                          carb_absorption_time=None):
    """
    Find partial COB for a particular carb entry

    Arguments:
    carb_start -- time of carb entry (datetime objects)
    carb_value -- grams of carbs eaten

    at_date -- date to calculate the glucose effect (datetime object)

    default_absorption_time -- absorption time to use for unspecified
                               carb entries

    delay -- the time to delay the carb effect
    carb_absorption_time -- time carbs will take to absorb (mins)

    Output:
    Carbohydrate value (g)
    """
    time = time_interval_since(at_time, carb_start)
    delay *= 60

    if time >= 0:
        value = (carb_value * (1 - parabolic_percent_absorption_at_time(
            (time - delay) / 60, carb_absorption_time
            or default_absorption_time)))
    else:
        value = 0
    return value
Exemplo n.º 4
0
    def absorption_result(builder_index):
        # absorption list structure: [observed grams absorbed, clamped grams,
        # total carbs in entry, remaining carbs, observed absorption start,
        # observed absorption end, estimated time remaining]
        observed_grams = (observed_effects[builder_index] /
                          builder_carb_sensitivities[builder_index])

        entry_grams = carb_entry_quantities[builder_index]

        time = (time_interval_since(last_effect_dates[builder_index],
                                    carb_entry_starts[builder_index]) / 60 -
                delay)
        min_predicted_grams = linearly_absorbed_carbs(
            entry_grams, time, builder_max_absorb_times[builder_index])
        clamped_grams = min(entry_grams,
                            max(min_predicted_grams, observed_grams))

        min_absorption_rate = (carb_entry_quantities[builder_index] /
                               builder_max_absorb_times[builder_index])
        estimated_time_remaining = ((entry_grams - clamped_grams) /
                                    min_absorption_rate
                                    if min_absorption_rate > 0 else 0)

        absorption = [
            observed_grams, clamped_grams, entry_grams,
            entry_grams - clamped_grams, carb_entry_starts[builder_index],
            observed_completion_dates[builder_index]
            or last_effect_dates[builder_index], estimated_time_remaining
        ]

        return absorption
Exemplo n.º 5
0
def continuous_delivery_glucose_effect(dose_start_date, dose_end_date, at_date,
                                       model, delay, delta):
    """ Calculates the percent of glucose effect at a specific time for
        a dose given over a period greater than 1.05x the delta
        (this will almost always be a basal)

    Arguments:
    dose_start_date -- the date the dose started at (datetime object)
    dose_end_date -- the date the dose ended at (datetime object)
    at_date -- date the IOB is being calculated (datetime object)
    model -- list of insulin model parameters in format [DIA, peak_time]
    delay -- the time to delay the dose effect
    delta -- the differential between timeline entries

    Output:
    Percentage of insulin remaining at the at_date
    """
    dose_duration = time_interval_since(dose_end_date, dose_start_date)
    delay *= 60
    delta *= 60

    if dose_duration < 0:
        return 0

    time = time_interval_since(at_date, dose_start_date)
    activity = 0
    dose_date = 0

    while (dose_date <= min(
            floor((time + delay) / delta) * delta, dose_duration)):
        if dose_duration > 0:
            segment = (max(0,
                           min(dose_date + delta, dose_duration) - dose_date) /
                       dose_duration)
        else:
            segment = 1

        if len(model) == 1:  # if walsh model
            activity += segment * (1 - walsh_percent_effect_remaining(
                (time - delay - dose_date) / 60, model[0]))

        else:
            activity += segment * (1 - percent_effect_remaining(
                (time - delay - dose_date) / 60, model[0], model[1]))
        dose_date += delta

    return activity
Exemplo n.º 6
0
def get_pending_insulin(
        at_date,
        basal_starts, basal_rates, basal_minutes,
        last_temp_basal,
        pending_bolus_amount=None
    ):
    """ Get the pending insulin for the purposes of calculating a recommended
        bolus

    Arguments:
    at_date -- the "now" time (roughly equivalent to datetime.now)

    basal_starts -- list of times the basal rates start at
    basal_rates -- list of basal rates (U/hr)
    basal_minutes -- list of basal lengths (in mins)

    last_temp_basal -- information about the last temporary basal in the form
                       [type, start time, end time, basal rate]
    pending_bolus_amount -- amount of unconfirmed bolus insulin (U)

    Output:
    Amount of insulin that is "pending"
    """
    assert len(basal_starts) == len(basal_rates),\
        "expected input shapes to match"

    if (not basal_starts
            or not last_temp_basal
            or last_temp_basal[1] > last_temp_basal[2]
       ):
        return 0

    # if the end date for the temp basal is greater than current date,
    # find the pending insulin
    if (last_temp_basal[2] > at_date
            and last_temp_basal[0] in [DoseType.tempbasal, DoseType.basal]):
        normal_basal_rate = find_ratio_at_time(
            basal_starts, [], basal_rates, at_date
        )
        remaining_time = time_interval_since(
            last_temp_basal[2],
            at_date
        ) / 60 / 60

        remaining_units = (
            last_temp_basal[3] - normal_basal_rate
        ) * remaining_time
        pending_basal_insulin = max(0, remaining_units)

    else:
        pending_basal_insulin = 0

    if pending_bolus_amount:
        pending_bolus = pending_bolus_amount
    else:
        pending_bolus = 0

    return pending_basal_insulin + pending_bolus
Exemplo n.º 7
0
def linear_momentum_effect(
        date_list, glucose_value_list, display_list, provenance_list,
        duration=30,
        delta=5
    ):
    """ Calculates the short-term predicted momentum effect using
        linear regression

    Arguments:
    date_list -- list of datetime objects
    glucose_value_list -- list of glucose values (unit: mg/dL)
    display_list -- list of display_only booleans
    provenance_list -- list of provenances (Strings)
    duration -- the duration of the effects
    delta -- the time differential for the returned values

    Output:
    tuple with format (date_of_glucose_effect, value_of_glucose_effect)
    """
    assert len(date_list) == len(glucose_value_list) == len(display_list)\
        == len(provenance_list), "expected input shape to match"

    if (len(date_list) <= 2 or not is_continuous(date_list)
            or is_calibrated(display_list)
            or not has_single_provenance(provenance_list)
       ):
        return ([], [])

    first_time = date_list[0]
    last_time = date_list[-1]
    (start_date, end_date) = simulation_date_range_for_samples(
        [last_time], [], duration, delta
    )

    def create_times(time):
        return abs(time_interval_since(time, first_time))

    slope = linear_regression(
        list(map(create_times, date_list)), glucose_value_list
    )

    if math.isnan(slope) or math.isinf(slope):
        return ([], [])

    date = start_date
    momentum_effect_dates = []
    momentum_effect_values = []

    while date <= end_date:
        value = (max(0, time_interval_since(date, last_time)) * slope)
        momentum_effect_dates.append(date)
        momentum_effect_values.append(value)
        date += timedelta(minutes=delta)

    assert len(momentum_effect_dates) == len(momentum_effect_values),\
        "expected output shape to match"
    return (momentum_effect_dates, momentum_effect_values)
Exemplo n.º 8
0
def absorbed_carbs(start_date, carb_value, absorption_time, at_date, delay):
    """
    Find absorbed carbs using a parabolic model

    Parameters:
    start_date -- date of carb consumption (datetime object)
    carb_value -- carbs consumed
    absorption_time --  time for carbs to completely absorb (in minutes)
    at_date -- date to calculate the absorbed carbs (datetime object)
    delay -- minutes to delay the start of absorption

    Output:
    Grams of absorbed carbs
    """
    time = time_interval_since(at_date, start_date) / 60

    return parabolic_absorbed_carbs(carb_value, time - delay, absorption_time)
Exemplo n.º 9
0
def if_necessary(
        temp_basal,
        at_date,
        scheduled_basal_rate,
        last_temp_basal,
        continuation_interval
        ):
    """ Determine whether the recommendation is necessary given the
        current state of the pump

    Arguments:
    temp_basal -- recommended temp basal
    at_date -- date to calculate temp basal at (datetime)
    scheduled_basal_rate -- basal rate scheduled during "at_date"
    last_temp_basal -- the previously set temp basal
    continuation_interval -- duration of time before an ongoing temp basal
                             should be continued with a new command

    Output:
    None (if the scheduled temp basal or basal rate should be allowed to
    continue to run), cancel (if the temp basal should be cancelled),
    or the recommended temp basal (if it should be set)
    """
    # Adjust behavior for the currently active temp basal
    if (last_temp_basal
            and last_temp_basal[0] in [DoseType.tempbasal, DoseType.basal]
            and last_temp_basal[2] > at_date
       ):
        # If the last temp basal has the same rate, and has more than
        # "continuation_interval" of time remaining, don't set a new temp
        if (matches_rate(temp_basal[0], last_temp_basal[3])
                and (
                    (time_interval_since(last_temp_basal[2], at_date) / 60)
                    > continuation_interval)
           ):
            return None

        # If our new temp matches the scheduled rate, cancel the current temp
        elif matches_rate(temp_basal[0], scheduled_basal_rate):
            return Correction.cancel

    # If we recommend the in-progress scheduled basal rate, do nothing
    elif matches_rate(temp_basal[0], scheduled_basal_rate):
        return None

    return temp_basal
Exemplo n.º 10
0
def dose_entries(reservoir_dates, unit_volumes):
    """ Converts a continuous, chronological sequence of reservoir values
        to a sequence of doses
    Runtime: O(n)

    Arguments:
    reservoir_dates -- list of datetime objects
    unit_volumes -- list of reservoir volumes (in units of insulin)

    Output:
    A tuple of lists in (dose_type (basal/bolus), start_dates, end_dates,
        insulin_values) format
    """
    assert len(reservoir_dates) > 1,\
        "expected input lists to contain two or more items"
    assert len(reservoir_dates) == len(unit_volumes),\
        "expected input shape to match"

    dose_types = []
    start_dates = []
    end_dates = []
    insulin_values = []

    previous_date = reservoir_dates[0]
    previous_unit_volume = unit_volumes[0]

    for i in range(1, len(reservoir_dates)):
        volume_drop = previous_unit_volume - unit_volumes[i]
        duration = time_interval_since(reservoir_dates[i], previous_date)

        if (duration > 0 and 0 <= volume_drop <=
                MAXIMUM_RESERVOIR_DROP_PER_MINUTE * duration / 60):
            dose_types.append(DoseType.tempbasal)
            start_dates.append(previous_date)
            end_dates.append(reservoir_dates[i])
            insulin_values.append(volume_drop)

        previous_date = reservoir_dates[i]
        previous_unit_volume = unit_volumes[i]

    assert len(dose_types) == len(start_dates) == len(end_dates) ==\
        len(insulin_values), "expected output shape to match"

    return (dose_types, start_dates, end_dates, insulin_values)
Exemplo n.º 11
0
    def test_time_interval_since(self):
        date = datetime.now()
        self.assertEqual(0, time_interval_since(date, date))

        self.assertEqual(
            -371, time_interval_since(date, date + timedelta(seconds=371)))
        self.assertEqual(
            123456, time_interval_since(date,
                                        date + timedelta(seconds=-123456)))
        self.assertEqual(
            -200, time_interval_since(date, date + timedelta(seconds=200)))
        self.assertEqual(
            200, time_interval_since(date, date + timedelta(seconds=-200)))
        self.assertEqual(
            86400, time_interval_since(date, date + timedelta(seconds=-86400)))
        self.assertEqual(
            -86400, time_interval_since(date, date + timedelta(seconds=86400)))
Exemplo n.º 12
0
def decay_effect(glucose_date, glucose_value, rate, duration, delta=5):
    """ Calculates a timeline of glucose effects by applying a
        linear decay to a rate of change.

    Arguments:
    glucose_date -- time of glucose value (datetime)
    glucose_value -- value at the time of glucose_date
    rate -- the glucose velocity
    duration -- the duration the effect should continue before ending
    delta -- the time differential for the returned values

    Output:
    Glucose effects in format (effect_date, effect_value)
    """
    (start_date, end_date) = simulation_date_range_for_samples([glucose_date],
                                                               [], duration,
                                                               delta)

    # The starting rate, which we will decay to 0 over the specified duration
    intercept = rate
    last_value = glucose_value
    effect_dates = [start_date]
    effect_values = [glucose_value]

    date = decay_start_date = start_date + timedelta(minutes=delta)
    slope = (-intercept / (duration - delta))

    while date < end_date:
        value = (
            last_value +
            (intercept +
             slope * time_interval_since(date, decay_start_date) / 60) * delta)

        effect_dates.append(date)
        effect_values.append(value)

        last_value = value
        date = date + timedelta(minutes=delta)

    assert len(effect_dates) == len(effect_values),\
        "expected output shapes to match"

    return (effect_dates, effect_values)
Exemplo n.º 13
0
def is_continuous(date_list, delta=5):
    """ Checks whether the collection can be considered continuous

    Arguments:
    date_list -- list of datetime objects
    delta -- the (expected) time interval between CGM values

    Output:
    Whether the collection is continuous
    """
    try:
        return (
            abs(time_interval_since(date_list[0], date_list[-1]))
            < delta * (len(date_list)) * 60
        )

    except IndexError:
        print("Out of bounds error: list doesn't contain date values")
        return False
Exemplo n.º 14
0
    def test_decay_effect_with_even_glucose(self):
        glucose_date = datetime(2016, 2, 1, 10, 15, 0)
        glucose_value = 100
        starting_effect = 2

        (dates,
         values
         ) = decay_effect(
             glucose_date, glucose_value,
             starting_effect,
             30
             )

        self.assertEqual(
            [100, 110, 118, 124, 128, 130],
            values
        )

        start_date = dates[0]
        time_deltas = []

        for time in dates:
            time_deltas.append(
                time_interval_since(time, start_date) / 60
            )

        self.assertEqual(
            [0, 5, 10, 15, 20, 25],
            time_deltas
        )

        (dates,
         values
         ) = decay_effect(
             glucose_date, glucose_value,
             -0.5,
             30
             )
        self.assertEqual(
            [100, 97.5, 95.5, 94, 93, 92.5],
            values
        )
Exemplo n.º 15
0
    def clamped_timeline(builder_index):
        entry_grams = carb_entry_quantities[builder_index]

        time = (time_interval_since(last_effect_dates[builder_index],
                                    carb_entry_starts[builder_index]) / 60 -
                delay)

        min_predicted_grams = linearly_absorbed_carbs(
            entry_grams, time, builder_max_absorb_times[builder_index])
        observed_grams = (observed_effects[builder_index] /
                          builder_carb_sensitivities[builder_index])

        output = []
        for i in range(0, len(observed_timeline_starts[builder_index])):
            output.append([
                observed_timeline_starts[builder_index][i],
                observed_timeline_ends[builder_index][i],
                observed_timeline_carb_values[builder_index][i]
            ] if (
                observed_grams >= min_predicted_grams) else [None, None, None])

        return output
Exemplo n.º 16
0
def insulin_correction(
        prediction_dates, prediction_values,
        target_starts, target_ends, target_mins, target_maxes,
        at_date,
        suspend_threshold_value,
        sensitivity_value,
        model
        ):
    """ Computes a total insulin amount necessary to correct a glucose
        differential at a given sensitivity

    prediction_dates -- dates glucose values were predicted (datetime)
    prediction_values -- predicted glucose values (mg/dL)

    target_starts -- start times for given target ranges (datetime)
    target_ends -- stop times for given target ranges (datetime)
    target_mins -- the lower bounds of target ranges (mg/dL)
    target_maxes -- the upper bounds of target ranges (mg/dL)

    at_date -- date to calculate correction
    suspend_threshold -- value to suspend all insulin delivery at (mg/dL)
    sensitivity_value -- the sensitivity (mg/dL/U)
    model -- list of insulin model parameters in format [DIA, peak_time] if
             exponential model, or [DIA] if Walsh model

    Output:
    A list of insulin correction information. All lists have the type as the
    first index, and may include additional information based on the type.

        Types:
        - entirely_below_range
            Structure: [type, glucose value to be corrected, minimum target,
                        units of correction insulin]
        - suspend
            Structure: [type, min glucose value]
        - in_range
            Structure: [type]
        - above_range
            Structure: [type, minimum predicted glucose value,
                        glucose value to be corrected, minimum target,
                        units of correction insulin]
    """
    assert len(prediction_dates) == len(prediction_values),\
        "expected input shapes to match"

    assert len(target_starts) == len(target_ends) == len(target_mins)\
        == len(target_maxes), "expected input shapes to match"

    (min_glucose,
     eventual_glucose,
     correcting_glucose,
     min_correction_units
     ) = ([], None, None, None)

    # only calculate a correction if the prediction is between
    # "now" and now + DIA
    if len(model) == 1:  # if Walsh model
        date_range = [at_date,
                      at_date + timedelta(hours=model[0])
                      ]
    else:
        date_range = [at_date,
                      at_date + timedelta(minutes=model[0])
                      ]

    # if we don't know the suspend threshold, it defaults to the lower
    # bound of the correction range at the time the "loop" is being run at
    if not suspend_threshold_value:
        suspend_threshold_value = find_ratio_at_time(
            target_starts,
            target_ends,
            target_mins,
            at_date
            )

    # For each prediction above target, determine the amount of insulin
    # necessary to correct glucose based on the modeled effectiveness of
    # the insulin at that time
    for i in range(0, len(prediction_dates)):
        if not is_time_between(
                date_range[0],
                date_range[1],
                prediction_dates[i]
            ):
            continue

        # If any predicted value is below the suspend threshold,
        # return immediately
        if prediction_values[i] < suspend_threshold_value:
            return [Correction.suspend, prediction_values[i]]

        # Update range statistics
        if not min_glucose or prediction_values[i] < min_glucose[1]:
            min_glucose = [prediction_dates[i], prediction_values[i]]

        eventual_glucose = [prediction_dates[i], prediction_values[i]]
        predicted_glucose_value = prediction_values[i]
        time = time_interval_since(
            prediction_dates[i],
            at_date
            ) / 60

        average_target = (
            find_ratio_at_time(
                target_starts,
                target_ends,
                target_maxes,
                prediction_dates[i]
                ) +
            find_ratio_at_time(
                target_starts,
                target_ends,
                target_mins,
                prediction_dates[i]
                )
            ) / 2
        # Compute the target value as a function of time since the dose started
        target_value = target_glucose_value(
            (time /
             (
                 (60 * model[0]) if len(model) == 1
                 else model[0]
             )
            ),
            suspend_threshold_value,
            average_target
        )

        # Compute the dose required to bring this prediction to target:
        # dose = (Glucose delta) / (% effect × sensitivity)
        if len(model) == 1:  # if Walsh model
            percent_effected = 1 - walsh_percent_effect_remaining(
                time,
                model[0]
            )
        else:
            percent_effected = 1 - percent_effect_remaining(
                time,
                model[0],
                model[1]
            )
        effected_sensitivity = percent_effected * sensitivity_value

        # calculate the Units needed to correct that predicted glucose value
        correction_units = insulin_correction_units(
            predicted_glucose_value,
            target_value,
            effected_sensitivity
        )

        if not correction_units or correction_units <= 0:
            continue

        # Update the correction only if we've found a new minimum
        if min_correction_units:
            if correction_units >= min_correction_units:
                continue

        correcting_glucose = [prediction_dates[i], prediction_values[i]]
        min_correction_units = correction_units

    if not eventual_glucose or not min_glucose:
        return None

    # Choose either the minimum glucose or eventual glucose as correction delta
    min_glucose_targets = [
        find_ratio_at_time(
            target_starts,
            target_ends,
            target_mins,
            min_glucose[0]
        ),
        find_ratio_at_time(
            target_starts,
            target_ends,
            target_maxes,
            min_glucose[0]
        )
    ]
    eventual_glucose_targets = [
        find_ratio_at_time(
            target_starts,
            target_ends,
            target_mins,
            eventual_glucose[0]
            ),
        find_ratio_at_time(
            target_starts,
            target_ends,
            target_maxes,
            eventual_glucose[0]
        )
    ]

    # Treat the mininum glucose when both are below range
    if (min_glucose[1] < min_glucose_targets[0]
            and eventual_glucose[1] < min_glucose_targets[0]
       ):
        time = time_interval_since(min_glucose[0], at_date) / 60
        # For time = 0, assume a small amount effected.
        # This will result in large (negative) unit recommendation
        # rather than no recommendation at all.
        if len(model) == 1:
            percent_effected = max(
                sys.float_info.epsilon,
                1 - walsh_percent_effect_remaining(time, model[0])
                )
        else:
            percent_effected = max(
                sys.float_info.epsilon,
                1 - percent_effect_remaining(time, model[0], model[1])
                )

        units = insulin_correction_units(
            min_glucose[1],
            sum(min_glucose_targets) / len(min_glucose_targets),
            sensitivity_value * percent_effected
            )

        if not units:
            return None

        # we're way below target
        return [
            Correction.entirely_below_range,
            min_glucose[1],
            min_glucose_targets[0],
            units
            ]

    # we're above target
    elif (eventual_glucose[1] > eventual_glucose_targets[1]
          and min_correction_units
          and correcting_glucose
         ):
        return [
            Correction.above_range,
            min_glucose[1],
            correcting_glucose[1],
            eventual_glucose_targets[0],
            min_correction_units
            ]
    # we're in range
    else:
        return [Correction.in_range]
Exemplo n.º 17
0
def dynamic_absorbed_carbs(
    carb_start,
    carb_value,
    absorption_dict,
    observed_timeline,
    at_date,
    carb_absorption_time,
    delay,
    delta,
):
    """
    Find partial absorbed carbs for a particular carb entry *dynamically*

    Arguments:
    carb_start -- time of carb entry (datetime objects)
    carb_value -- grams of carbs eaten

    absorption_dict -- list of absorption information
                       (computed via map_)
    observed_timeline -- list of carb absorption info at various times
                         (computed via map_)

    at_date -- date to calculate the glucose effect (datetime object)

    carb_absorption_time -- time carbs will take to absorb (mins)

    delay -- the time to delay the carb effect

    Output:
    Carbohydrate value (g)
    """

    # We have to have absorption info for dynamic calculation
    if (at_date < carb_start or not absorption_dict):
        return carb_math.absorbed_carbs(
            carb_start,
            carb_value,
            carb_absorption_time,
            at_date,
            delay,
        )
    # Less than minimum observed; calc based on min absorption rate
    if observed_timeline and None in observed_timeline[0]:
        time = time_interval_since(at_date, carb_start) / 60 - delay
        estimated_date_duration = (
            time_interval_since(absorption_dict[5], absorption_dict[4]) / 60 +
            absorption_dict[6])
        return carb_math.linearly_absorbed_carbs(absorption_dict[2], time,
                                                 estimated_date_duration)

    if (not observed_timeline  # no absorption was observed (empty list)
            or not observed_timeline[len(observed_timeline) - 1]
            or at_date > observed_timeline[len(observed_timeline) - 1][1]):
        # Predict absorption for remaining carbs, post-observation
        total = absorption_dict[3]  # these are the still-unabsorbed carbs
        time = time_interval_since(at_date, absorption_dict[5]) / 60
        absorption_time = absorption_dict[6]

        return absorption_dict[1] + carb_math.linearly_absorbed_carbs(
            total, time, absorption_time)

    sum_ = 0

    # There was observed absorption
    def filter_dates(sub_timeline):
        return sub_timeline[0] + timedelta(minutes=delta) <= at_date

    before_timelines = list(filter(filter_dates, observed_timeline))

    if before_timelines:
        last = before_timelines.pop()
        observation_interval = (last[1] - last[0]).total_seconds()
        if observation_interval > 0:
            # find the minutes of overlap between calculation_interval
            # and observation_interval
            calculation_interval = (last[1] -
                                    min(last[0], at_date)).total_seconds()
            sum_ += (calculation_interval / observation_interval * last[2])

    for dict_ in before_timelines:
        sum_ += dict_[2]

    return min(sum_, absorption_dict[0])
Exemplo n.º 18
0
def annotate_individual_dose(dose_type,
                             dose_start_date,
                             dose_end_date,
                             value,
                             basal_start_times,
                             basal_rates,
                             basal_minutes,
                             convert_to_units_hr=True):
    """ Annotates a dose with the context of the scheduled basal rate
        If the dose crosses a schedule boundary, it will be split into
        multiple doses so each dose has a single scheduled basal rate.

    Arguments:
    dose_type -- type of dose (basal, bolus, etc)
    dose_start_date -- start date of the dose (datetime obj)
    dose_end_date -- end date of the dose (datetime obj)
    value -- actual basal rate of dose in U/hr (if a basal)
             or the value of the bolus in U
    basal_start_times -- list of times the basal rates start at
    basal_rates -- list of basal rates(U/hr)
    basal_minutes -- list of basal lengths (in mins)
    convert_to_units_hr -- set to True if you want to convert a dose to U/hr
        (ex: 0.05 U given from 1/1/01 1:00:00 to 1/1/01 1:05:00 -> 0.6 U/hr)

    Output:
    Tuple with properties of doses, annotated with the current basal rates
    """
    if dose_type not in [DoseType.basal, DoseType.tempbasal, DoseType.suspend]:
        return ([dose_type], [dose_start_date], [dose_end_date], [value], [0])

    output_types = []
    output_start_dates = []
    output_end_dates = []
    output_values = []
    output_scheduled_basal_rates = []

    # these are the lists containing the scheduled basal value(s) within
    # the temp basal's duration
    (sched_basal_starts, sched_basal_ends, sched_basal_rates) = between(
        basal_start_times,
        basal_rates,
        basal_minutes,
        dose_start_date,
        dose_end_date,
    )

    for i in range(0, len(sched_basal_starts)):
        if i == 0:
            start_date = dose_start_date
        else:
            start_date = sched_basal_starts[i]

        if i == len(sched_basal_starts) - 1:
            end_date = dose_end_date
        else:
            end_date = sched_basal_starts[i + 1]

        output_types.append(dose_type)
        output_start_dates.append(start_date)
        output_end_dates.append(end_date)

        if convert_to_units_hr:
            output_values.append(
                0 if dose_type == DoseType.suspend else value /
                (time_interval_since(dose_end_date, dose_start_date) / 60 /
                 60))
        else:
            output_values.append(value)

        output_scheduled_basal_rates.append(sched_basal_rates[i])

    assert len(output_types) == len(output_start_dates) ==\
        len(output_end_dates) == len(output_values) ==\
        len(output_scheduled_basal_rates), "expected output shapes to match"

    return (output_types, output_start_dates, output_end_dates, output_values,
            output_scheduled_basal_rates)
Exemplo n.º 19
0
def between(basal_start_times,
            basal_rates,
            basal_minutes,
            start_date,
            end_date,
            repeat_interval=24):
    """ Returns a slice of scheduled basal rates that occur between two dates

    Arguments:
    basal_start_times -- list of times the basal rates start at
    basal_rates -- list of basal rates(U/hr)
    basal_minutes -- list of basal lengths (in mins)
    start_date -- start date of the range (datetime obj)
    end_date -- end date of the range (datetime obj)
    repeat_interval -- the duration over which the rates repeat themselves
                       (24 hours by default)

    Output:
    Tuple in format (basal_start_times, basal_rates, basal_minutes) within
    the range of dose_start_date and dose_end_date
    """
    timezone_info = start_date.tzinfo
    if start_date > end_date:
        return ([], [], [])

    reference_time_interval = timedelta(hours=basal_start_times[0].hour,
                                        minutes=basal_start_times[0].minute,
                                        seconds=basal_start_times[0].second)
    max_time_interval = (reference_time_interval +
                         timedelta(hours=repeat_interval))

    start_offset = schedule_offset(start_date, basal_start_times[0])

    end_offset = (start_offset +
                  timedelta(seconds=time_interval_since(end_date, start_date)))

    # if a dose is crosses days, split it into separate doses
    if end_offset > max_time_interval:
        boundary_date = start_date + (max_time_interval - start_offset)
        (start_times_1, end_times_1,
         basal_rates_1) = between(basal_start_times,
                                  basal_rates,
                                  basal_minutes,
                                  start_date,
                                  boundary_date,
                                  repeat_interval=repeat_interval)
        (start_times_2, end_times_2,
         basal_rates_2) = between(basal_start_times,
                                  basal_rates,
                                  basal_minutes,
                                  boundary_date,
                                  end_date,
                                  repeat_interval=repeat_interval)

        return (start_times_1 + start_times_2, end_times_1 + end_times_2,
                basal_rates_1 + basal_rates_2)

    start_index = 0
    end_index = len(basal_start_times)

    for (i, start_time) in enumerate(basal_start_times):
        start_time = timedelta(hours=start_time.hour,
                               minutes=start_time.minute,
                               seconds=start_time.second)
        if start_offset >= start_time:
            start_index = i
        if end_offset < start_time:
            end_index = i
            break

    reference_date = start_date - start_offset
    reference_date = datetime(year=reference_date.year,
                              month=reference_date.month,
                              day=reference_date.day,
                              hour=reference_date.hour,
                              minute=reference_date.minute,
                              second=reference_date.second,
                              tzinfo=timezone_info)

    if start_index > end_index:
        return ([], [], [])

    (output_start_times, output_end_times, output_basal_rates) = ([], [], [])

    for i in range(start_index, end_index):
        end_time = (timedelta(hours=basal_start_times[i + 1].hour,
                              minutes=basal_start_times[i + 1].minute,
                              seconds=basal_start_times[i + 1].second)
                    if i + 1 < len(basal_start_times) else max_time_interval)

        output_start_times.append(
            reference_date + timedelta(hours=basal_start_times[i].hour,
                                       minutes=basal_start_times[i].minute,
                                       seconds=basal_start_times[i].second))
        output_end_times.append(reference_date + end_time)
        output_basal_rates.append(basal_rates[i])

    assert len(output_start_times) == len(output_end_times) ==\
        len(output_basal_rates), "expected output shape to match"

    return (output_start_times, output_end_times, output_basal_rates)
Exemplo n.º 20
0
def overlay_basal_schedule(dose_types, starts, ends, values, basal_start_times,
                           basal_rates, basal_minutes, starting_at, ending_at,
                           inserting_basal_entries):
    """ Applies the current basal schedule to a collection of reconciled doses
        in chronological order

    Arguments:
    dose_types -- types of doses (basal, bolus, etc)
    starts -- datetime objects of times doses started at
    ends -- datetime objects of times doses ended at
    values -- amounts, in U/hr (if a basal) or U (if bolus) of insulin in doses
    basal_start_times -- list of times the basal rates start at
    basal_rates -- list of basal rates(U/hr)
    basal_minutes -- list of basal lengths (in mins)
    starting_at -- start of interval to overlay the basal schedule
                   (datetime object)
    ending_at -- end of interval to overlay the basal schedule
                 (datetime object)
    inserting_basal_entries -- whether basal doses should be created from the
                               schedule. Pass true only for pump models that do
                               not report their basal rates in event history.

    Output:
    Tuple with dose properties in range (start_interval,
    end_interval), overlayed with the basal schedule. It returns *four* dose
    properties, and does *not* return scheduled_basal_rates
    """
    assert len(dose_types) == len(starts) == len(ends) == len(values),\
        "expected input shapes to match"

    (out_dose_types, out_starts, out_ends, out_values) = ([], [], [], [])

    last_basal = []
    if inserting_basal_entries:
        last_basal = [DoseType.tempbasal, starting_at, starting_at, 0]

    for (i, type_) in enumerate(dose_types):
        if type_ in [DoseType.tempbasal, DoseType.basal, DoseType.suspend]:
            if ending_at and ends[i] > ending_at:
                continue

            if last_basal:
                if inserting_basal_entries:
                    (sched_basal_starts, sched_basal_ends,
                     sched_basal_rates) = between(basal_start_times,
                                                  basal_rates, basal_minutes,
                                                  last_basal[2], starts[i])
                    for j in range(0, len(sched_basal_starts)):
                        start = max(last_basal[2], sched_basal_starts[j])
                        end = min(starts[i], sched_basal_ends[j])

                        if time_interval_since(end, start)\
                                < sys.float_info.epsilon:
                            continue

                        out_dose_types.append(DoseType.basal)
                        out_starts.append(start)
                        out_ends.append(end)
                        out_values.append(sched_basal_rates[j])

            last_basal = [dose_types[i], starts[i], ends[i], values[i]]

            if last_basal:
                out_dose_types.append(last_basal[0])
                out_starts.append(last_basal[1])
                out_ends.append(last_basal[2])
                out_values.append(last_basal[3])

        elif type_ == DoseType.resume:
            assert "No resume events should be present in reconciled doses"

        elif type_ == DoseType.bolus:
            out_dose_types.append(dose_types[i])
            out_starts.append(starts[i])
            out_ends.append(ends[i])
            out_values.append(values[i])

    assert len(out_dose_types) == len(out_starts) == len(out_ends)\
        == len(out_values), "expected output shape to match"

    return (out_dose_types, out_starts, out_ends, out_values)
Exemplo n.º 21
0
def is_continuous(reservoir_dates, unit_volumes, start, end, maximum_duration):
    """ Whether a span of chronological reservoir values is considered
        continuous and therefore reliable.

    Reservoir values of 0 are automatically considered unreliable due to
    the assumption that an unknown amount of insulin can be delivered after
    the 0 marker.

    Arguments:
    reservoir_dates -- list of datetime objects that correspond by index to
                        unit_volumes
    unit_volumes -- volume of reservoir in units, corresponds by index to
                    reservoir_dates
    start -- datetime object that is start of the interval which to validate
             continuity
    end -- datetime object that is end of the interval which to validate
             continuity
    maximum_duration -- the maximum interval to consider reliable for a
                        reservoir-derived dose

    Variable names:
    start_date -- the beginning of the interval in which to validate
                   continuity
    end_date -- the end of the interval in which to validate continuity

    Outputs:
    Whether the reservoir values meet the critera for continuity
    """
    try:
        first_date_value = reservoir_dates[0]
        first_volume_value = unit_volumes[0]
    except IndexError:
        return False

    if end < start:
        return False

    start_date = start
    # The first value has to be at least as old as the start date
    # as a reference point.
    if first_date_value > start_date:
        return False

    last_date_value = first_date_value
    last_volume_value = first_volume_value

    for i in range(0, len(unit_volumes)):  # pylint: disable=C0200
        # Volume and interval validation only applies for values in
        # the specified range
        if reservoir_dates[i] < start_date or reservoir_dates[i] > end:
            last_date_value = reservoir_dates[i]
            last_volume_value = unit_volumes[i]
            continue
        # We can't trust 0. What else was delivered?
        if unit_volumes[i] <= 0:
            return False
        # Rises in reservoir volume indicate a rewind + prime, and primes
        # can be easily confused with boluses.
        # Small rises (1 U) can be ignored as they're indicative of a
        # mixed-precision sequence.
        if unit_volumes[i] > last_volume_value + 1:
            return False
        # Ensure no more than the maximum interval has passed
        if (time_interval_since(reservoir_dates[i], last_date_value) >
                maximum_duration * 60):
            return False

        last_date_value = reservoir_dates[i]
        last_volume_value = unit_volumes[i]

    return True
Exemplo n.º 22
0
def update_retrospective_glucose_effect(
        glucose_dates, glucose_values,
        carb_effect_dates, carb_effect_values,
        counteraction_starts, counteraction_ends, counteraction_values,
        recency_interval,
        retrospective_correction_grouping_interval,
        now_time,
        effect_duration=60,
        delta=5
        ):
    """
    Generate an effect based on how large the discrepancy is between the
    current glucose and its predicted value.

    Arguments:
    glucose_dates -- time of glucose value (datetime)
    glucose_values -- value at the time of glucose_date

    carb_effect_dates -- date the carb effects occur at (datetime)
    carb_effect_values -- value of carb effect

    counteraction_starts -- start times for counteraction effects
    counteraction_ends -- end times for counteraction effects
    counteraction_values -- values of counteraction effects

    recency_interval -- amount of time since a given date that data should be
                        considered valid
    retrospective_correction_grouping_interval -- interval over which to
        aggregate changes in glucose for retrospective correction

    now_time -- the time the loop is being run at
    effect_duration -- the length of time to calculate the retrospective
                       glucose effect out to
    delta -- time interval between glucose values (mins)

    Output:
    Retrospective glucose effect information in format
    (retrospective_effect_dates, retrospective_effect_values)
    """
    assert len(glucose_dates) == len(glucose_values),\
        "expected input shapes to match"

    assert len(carb_effect_dates) == len(carb_effect_values),\
        "expected input shapes to match"

    assert len(counteraction_starts) == len(counteraction_ends)\
        == len(counteraction_values), "expected input shapes to match"

    if not glucose_dates or not carb_effect_dates or not counteraction_starts:
        return ([], [])

    (discrepancy_starts, discrepancy_values) = subtracting(
        counteraction_starts, counteraction_ends, counteraction_values,
        carb_effect_dates, [], carb_effect_values,
        delta
        )

    retrospective_glucose_discrepancies_summed = combined_sums(
                discrepancy_starts, discrepancy_starts, discrepancy_values,
                retrospective_correction_grouping_interval * 1.01
                )

    # Our last change should be recent, otherwise clear the effects
    if (time_interval_since(
            now_time,
            retrospective_glucose_discrepancies_summed[1][-1])
            > recency_interval * 60
       ):
        return ([], [])

    discrepancy_time = max(
        0,
        retrospective_correction_grouping_interval
        )

    velocity = (
        retrospective_glucose_discrepancies_summed[2][-1]
        / discrepancy_time
        )

    return decay_effect(
        glucose_dates[-1], glucose_values[-1],
        velocity,
        effect_duration
        )
Exemplo n.º 23
0
def plot_multiple_relative_graphs(
        dates, values,
        x_label=None, y_label=None, title=None,
        line_color=None, fill_color=None, file_name=None, grid=False):
    """ Plot an Loop-style effects graph, with the x-axis ticks
        being the relative time since the first value (ex: 4 hours) AND there
        being multiple lines on the same graph

    dates -- lists of dates to plot at (datetime)
        ex: [
                [1:00, 2:00],
                [1:00, 2:00]
            ]
    values -- lists of integer values to plot
        ex: ex: [
                [2, 4],
                [0, -20]
            ]

    Optional parameters:
        x_label -- the x-axis label
        y_label -- the y-axis label
        title -- the title of the graph
        line_color -- color of the line that is graphed
        fill_color -- the color of the fill under the graph (defaults to
                      no fill)
        file_name -- name to save the plot as (if no name is specified, the
                     graph is not saved)
        grid -- set to True to enable a grid on the graph
        line_style -- see pyplot documentation for the line style options
        scatter -- plot points as a scatter plot instead of a line
    """
    assert len(dates) == len(values)

    font = {
        'family': 'DejaVu Sans',
        'weight': 'bold',
        'size': 15
    }
    plt.rc('font', **font)

    figure_size_inches = (15, 7)
    fig, ax = plt.subplots(figsize=figure_size_inches)

    coord_color = "#c0c0c0"
    ax.spines['bottom'].set_color(coord_color)
    ax.spines['top'].set_color(coord_color)
    ax.spines['left'].set_color(coord_color)
    ax.spines['right'].set_color(coord_color)
    ax.xaxis.label.set_color(coord_color)
    ax.tick_params(axis='x', colors=coord_color)
    ax.yaxis.label.set_color(coord_color)
    ax.tick_params(axis='y', colors=coord_color)
    ax.spines['right'].set_visible(False)
    ax.spines['left'].set_visible(False)

    relative_dates = []
    # convert from exact dates to relative dates
    for date_list in dates:
        relative_date_list = []
        for date in date_list:
            relative_date_list.append(
                time_interval_since(date, date_list[0]) / 3600
            )
        relative_dates.append(relative_date_list)

    x_ticks_duplicates = [date.hour - dates[0][0].hour for date in dates[0]]
    x_ticks = list(OrderedDict.fromkeys(x_ticks_duplicates))
    labels = ["%d" % x1 for x1 in x_ticks]
    plt.xticks(x_ticks, labels)

    if x_label:
        ax.set_xlabel(x_label)
    if y_label:
        ax.set_ylabel(y_label)
    if title:
        plt.title(title, loc="left", fontweight='bold')

    for (date_list, value_list) in zip(dates, values):
        ax.plot(
            date_list, value_list, color=line_color or "#f09a37", lw=4, ls="-"
        )
    if fill_color:
        plt.fill_between(
            relative_dates, values, color=fill_color or "#f09a37", alpha=0.5
        )
    if grid:
        plt.grid(grid)
    if file_name:
        plt.savefig(file_name + ".png")
    plt.show()
Exemplo n.º 24
0
def counteraction_effects(
        dates, glucose_values, displays, provenances,
        effect_dates, effect_values
    ):
    """ Calculates a timeline of effect velocity (glucose/time) observed
        in glucose readings that counteract the specified effects.

    Arguments:
    dates -- list of datetime objects of dates of glucose values
    glucose_values -- list of glucose values (unit: mg/dL)
    displays -- list of display_only booleans
    provenances -- list of provenances (Strings)

    effect_dates -- list of datetime objects associated with a glucose effect
    effect_values -- list of values associated with a glucose effect

    Output:
    An array of velocities describing the change in glucose samples
    compared to the specified effects
    """
    assert len(dates) == len(glucose_values) == len(displays)\
        == len(provenances), "expected input shape to match"
    assert len(effect_dates) == len(effect_values),\
        "expected input shape to match"

    if not dates or not effect_dates:
        return ([], [], [])

    effect_index = 0
    start_glucose = glucose_values[0]
    start_date = dates[0]
    start_prov = provenances[0]
    start_display = displays[0]

    start_dates = []
    end_dates = []
    velocities = []

    for i in range(1, len(dates)):
        # Find a valid change in glucose, requiring identical
        # provenance and no calibration
        glucose_change = glucose_values[i] - start_glucose
        time_interval = time_interval_since(dates[i], start_date)

        if time_interval <= 4 * 60:
            continue

        if (not start_prov == provenances[i]
                or start_display
                or displays[i]
           ):
            start_glucose = glucose_values[i]
            start_date = dates[i]
            start_prov = provenances[i]
            start_display = displays[i]
            continue

        start_effect_date = None
        start_effect_value = None
        end_effect_date = None
        end_effect_value = None

        for j in range(effect_index, len(effect_dates)):
            # if one of the start_effect properties doesn't exist and
            # the glucose effect at position "j" will happen after the
            # starting glucose date, then make start_effect equal to that
            # effect
            if (not start_effect_date
                    and effect_dates[j] >= start_date
               ):
                start_effect_date = effect_dates[j]
                start_effect_value = effect_values[j]

            elif (not end_effect_date
                  and effect_dates[j] >= dates[i]
                 ):
                end_effect_date = effect_dates[j]
                end_effect_value = effect_values[j]
                break

            effect_index += 1

        if end_effect_value is None:
            continue
        effect_change = end_effect_value - start_effect_value

        discrepancy = glucose_change - effect_change

        average_velocity = discrepancy / time_interval * 60

        start_dates.append(start_date)
        end_dates.append(dates[i])
        velocities.append(average_velocity)

        start_glucose = glucose_values[i]
        start_date = dates[i]
        start_prov = provenances[i]
        start_display = displays[i]

    assert len(start_dates) == len(end_dates) == len(velocities),\
        "expected output shape to match"
    return (start_dates, end_dates, velocities)
Exemplo n.º 25
0
def predict_glucose(starting_date,
                    starting_glucose,
                    momentum_dates=[],
                    momentum_values=None,
                    carb_effect_dates=[],
                    carb_effect_values=None,
                    insulin_effect_dates=[],
                    insulin_effect_values=None,
                    correction_effect_dates=[],
                    correction_effect_values=None):
    """ Calculates a timeline of predicted glucose values
        from a variety of effects timelines.

    Each effect timeline:
     - Is given equal weight (exception: momentum effect timeline)
     - Can be of arbitrary size and start date
     - Should be in ascending order
     - Should have aligning dates with any overlapping timelines to ensure
       a smooth result

    Parameters:
    starting_date -- time of starting_glucose (datetime object)
    starting_glucose -- glucose value to use in predictions

    momentum_dates -- times of calculated momentums (datetime)
    momentum_values -- values (mg/dL) of momentums

    carb_effect_dates -- times of carb effects (datetime)
    carb_effect -- values (mg/dL) of effects from carbs

    insulin_effect_dates -- times of insulin effects (datetime)
    insulin_effect -- values (mg/dL) of effects from insulin

    correction_effect_dates -- times of retrospective effects (datetime)
    correction_effect -- values (mg/dL) retrospective glucose effects

    Output:
    Glucose predictions in form (prediction_times, prediction_glucose_values)
    """
    if momentum_dates:
        assert len(momentum_dates) == len(momentum_values),\
            "expected input shapes to match"

    if carb_effect_dates:
        assert len(carb_effect_dates) == len(carb_effect_values),\
            "expected input shapes to match"

    if insulin_effect_dates:
        assert len(insulin_effect_dates) == len(insulin_effect_values),\
            "expected input shapes to match"

    if correction_effect_dates:
        assert len(correction_effect_dates) == len(correction_effect_values),\
            "expected input shapes to match"

    # if we didn't get any effect data, we won't predict the glucose
    if (not momentum_dates and not carb_effect_dates
            and not insulin_effect_dates and not correction_effect_dates):
        return ([], [])

    merged_dates = sorted(
        list(
            dict.fromkeys(momentum_dates + carb_effect_dates +
                          insulin_effect_dates + correction_effect_dates)))

    merged_values = [0 for i in merged_dates]

    if carb_effect_dates:
        previous_effect_value = carb_effect_values[0] or 0
        for i in range(0, len(carb_effect_dates)):
            value = carb_effect_values[i]
            list_index = merged_dates.index(carb_effect_dates[i])
            merged_values[list_index] = (value - previous_effect_value)

            previous_effect_value = value

    if insulin_effect_dates:
        previous_effect_value = insulin_effect_values[0] or 0
        for i in range(0, len(insulin_effect_dates)):
            value = insulin_effect_values[i]
            list_index = merged_dates.index(insulin_effect_dates[i])
            merged_values[list_index] = (merged_values[list_index] + value -
                                         previous_effect_value)

            previous_effect_value = value

    if correction_effect_dates:
        previous_effect_value = correction_effect_values[0] or 0
        for i in range(0, len(correction_effect_dates)):
            value = correction_effect_values[i]
            list_index = merged_dates.index(correction_effect_dates[i])
            merged_values[list_index] = (merged_values[list_index] + value -
                                         previous_effect_value)

            previous_effect_value = value

    # Blend the momentum effect linearly into the summed effect list
    if len(momentum_dates) > 1:
        previous_effect_value = momentum_values[0]

        # The blend begins delta minutes after after the last glucose (1.0)
        # and ends at the last momentum point (0.0)
        # This assumes the first one occurs on/before the starting glucose
        blend_count = len(momentum_dates) - 2
        time_delta = time_interval_since(momentum_dates[1], momentum_dates[0])
        # The difference between the first momentum value
        # and the starting glucose value
        momentum_offset = time_interval_since(starting_date, momentum_dates[0])

        blend_slope = 1 / blend_count
        blend_offset = (momentum_offset / time_delta * blend_slope)

        for i in range(0, len(momentum_dates)):
            value = momentum_values[i]
            date = momentum_dates[i]
            merge_index = merged_dates.index(date)

            effect_value_change = value - previous_effect_value

            split = min(
                1,
                max(0, (len(momentum_dates) - i) / blend_count - blend_slope +
                    blend_offset))
            effect_blend = ((1 - split) * merged_values[merge_index])
            momentum_blend = split * effect_value_change

            merged_values[merge_index] = effect_blend + momentum_blend
            previous_effect_value = value

    predicted_dates = [starting_date]
    predicted_values = [starting_glucose]

    for i in range(0, len(merged_dates)):
        if merged_dates[i] > starting_date:
            last_value = predicted_values[-1]

            predicted_dates.append(merged_dates[i])
            predicted_values.append(last_value + merged_values[i])

    assert len(predicted_dates) == len(predicted_values),\
        "expected output shapes to match"
    return (predicted_dates, predicted_values)
Exemplo n.º 26
0
 def create_times(time):
     return abs(time_interval_since(time, first_time))
Exemplo n.º 27
0
def hours(start_date, end_date):
    """ Find hours between two dates for the purposes of calculating basal
        delivery
    """
    return abs(time_interval_since(end_date, start_date))/3600  # secs -> hrs
Exemplo n.º 28
0
def dynamic_carbs_on_board_helper(carb_start,
                                  carb_value,
                                  absorption_dict,
                                  observed_timeline,
                                  at_date,
                                  default_absorption_time,
                                  delay,
                                  delta,
                                  carb_absorption_time=None):
    """
    Find partial COB for a particular carb entry *dynamically*

    Arguments:
    carb_start -- time of carb entry (datetime objects)
    carb_value -- grams of carbs eaten

    absorption_dict -- list of absorption information
                       (computed via map_)
    observed_timeline -- list of carb absorption info at various times
                         (computed via map_)

    at_date -- date to calculate the glucose effect (datetime object)

    default_absorption_time -- absorption time to use for unspecified
                               carb entries

    delay -- the time to delay the carb effect
    carb_absorption_time -- time carbs will take to absorb (mins)

    Output:
    Carbohydrate value (g)
    """

    # We have to have absorption info for dynamic calculation
    if (at_date < carb_start - timedelta(minutes=delta)
            or not absorption_dict):
        return carb_math.carbs_on_board_helper(carb_start, carb_value, at_date,
                                               default_absorption_time, delay,
                                               carb_absorption_time)

    # Less than minimum observed; calc based on min absorption rate
    if observed_timeline and None in observed_timeline[0]:
        time = time_interval_since(at_date, carb_start) / 60 - delay
        estimated_date_duration = (
            time_interval_since(absorption_dict[5], absorption_dict[4]) / 60 +
            absorption_dict[6])
        return carb_math.linear_unabsorbed_carbs(absorption_dict[2], time,
                                                 estimated_date_duration)

    if (not observed_timeline  # no absorption was observed (empty list)
            or not observed_timeline[len(observed_timeline) - 1]
            or at_date > observed_timeline[len(observed_timeline) - 1][1]):
        # Predict absorption for remaining carbs, post-observation
        total = absorption_dict[3]  # these are the still-unabsorbed carbs
        time = time_interval_since(at_date, absorption_dict[5]) / 60
        absorption_time = absorption_dict[6]

        return carb_math.linear_unabsorbed_carbs(total, time, absorption_time)

    # There was observed absorption
    total = carb_value

    def partial_absorption(dict_):
        if dict_[1] > at_date:
            return 0
        return dict_[2]

    for dict_ in observed_timeline:
        total -= partial_absorption(dict_)

    return max(total, 0)