def test_no_change(self):
        glucose = self.load_glucose_value_fixture(
            "recommend_temp_basal_no_change_glucose")
        dose = recommended_temp_basal(*glucose, *self.TARGET_RANGE,
                                      glucose[0][0], self.SUSPEND_THRESHOLD,
                                      *self.SENSITIVITY, self.WALSH_MODEL,
                                      *self.basal_rate_schedule(),
                                      self.MAX_BASAL_RATE, None)

        self.assertIsNone(dose)
    def test_start_high_end_low(self):
        glucose = self.load_glucose_value_fixture(
            "recommend_temp_basal_start_high_end_low")
        dose = recommended_temp_basal(*glucose, *self.TARGET_RANGE,
                                      glucose[0][0], self.SUSPEND_THRESHOLD,
                                      *self.SENSITIVITY, self.WALSH_MODEL,
                                      *self.basal_rate_schedule(),
                                      self.MAX_BASAL_RATE, None)

        self.assertEqual(0, dose[0])
        self.assertEqual(30, dose[1])
    def test_start_low_end_in_range(self):
        glucose = self.load_glucose_value_fixture(
            "recommend_temp_basal_start_low_end_in_range")
        dose = recommended_temp_basal(*glucose, *self.TARGET_RANGE,
                                      glucose[0][0], self.SUSPEND_THRESHOLD,
                                      *self.SENSITIVITY, self.WALSH_MODEL,
                                      *self.basal_rate_schedule(),
                                      self.MAX_BASAL_RATE, None)

        self.assertIsNone(dose)

        last_temp_basal = [
            DoseType.tempbasal, glucose[0][0] + timedelta(minutes=-11),
            glucose[0][0] + timedelta(minutes=19), 1.225
        ]

        dose = recommended_temp_basal(*glucose, *self.TARGET_RANGE,
                                      glucose[0][0], self.SUSPEND_THRESHOLD,
                                      *self.SENSITIVITY, self.WALSH_MODEL,
                                      *self.basal_rate_schedule(),
                                      self.MAX_BASAL_RATE, last_temp_basal)
        self.assertEqual(0, dose[0])
        self.assertEqual(0, dose[1])
    def test_no_input_glucose(self):
        glucose = ([], [])

        dose = recommended_temp_basal(*glucose,
                                      *self.TARGET_RANGE,
                                      datetime.now(),
                                      self.SUSPEND_THRESHOLD,
                                      *self.SENSITIVITY,
                                      self.WALSH_MODEL,
                                      *self.basal_rate_schedule(),
                                      self.MAX_BASAL_RATE,
                                      None,
                                      rate_rounder=0.025)

        self.assertIsNone(dose)
    def test_rise_after_dia(self):
        glucose = self.load_glucose_value_fixture(
            "far_future_high_bg_forecast")
        dose = recommended_temp_basal(*glucose,
                                      *self.TARGET_RANGE,
                                      glucose[0][0],
                                      self.SUSPEND_THRESHOLD,
                                      *self.SENSITIVITY,
                                      self.WALSH_MODEL,
                                      *self.basal_rate_schedule(),
                                      self.MAX_BASAL_RATE,
                                      None,
                                      rate_rounder=0.025)

        self.assertIsNone(dose)
    def test_very_low_and_rising(self):
        glucose = self.load_glucose_value_fixture(
            "recommend_temp_basal_very_low_end_in_range")
        dose = recommended_temp_basal(*glucose,
                                      *self.TARGET_RANGE,
                                      glucose[0][0],
                                      self.SUSPEND_THRESHOLD,
                                      *self.SENSITIVITY,
                                      self.WALSH_MODEL,
                                      *self.basal_rate_schedule(),
                                      self.MAX_BASAL_RATE,
                                      None,
                                      rate_rounder=0.025)

        self.assertEqual(0, dose[0])
        self.assertEqual(30, dose[1])
def update_predicted_glucose_and_recommended_basal_and_bolus(
        at_date,
        glucose_dates, glucose_values,
        momentum_dates, momentum_values,
        carb_effect_dates, carb_effect_values,
        insulin_effect_dates, insulin_effect_values,
        retrospective_effect_dates, retrospective_effect_values,
        target_starts, target_ends, target_mins, target_maxes,
        suspend_threshold,
        sensitivity_starts, sensitivity_ends, sensitivity_values,
        model,
        basal_starts, basal_rates, basal_minutes,
        max_basal_rate, max_bolus,
        last_temp_basal,
        duration=30,
        continuation_interval=11,
        rate_rounder=None
        ):
    """ Generate glucose predictions, then use the predicted glucose along
        with settings and dose data to recommend a temporary basal rate and
        a bolus

    Arguments:
    at_date -- date to calculate the temp basal and bolus recommendations

    glucose_dates -- dates of glucose values (datetime)
    glucose_values -- glucose values (in mg/dL)

    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

    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)

    suspend_threshold -- value at which to suspend all insulin delivery (mg/dL)

    sensitivity_starts -- list of time objects of start times of
                          given insulin sensitivity values
    sensitivity_ends -- list of time objects of start times of
                        given insulin sensitivity values
    sensitivity_values -- list of sensitivities (mg/dL/U)

    model -- list of insulin model parameters in format [DIA, peak_time] if
             exponential model, or [DIA] if Walsh model

    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)

    max_basal_rate -- max basal rate that Loop can give (U/hr)
    max_bolus -- max bolus that Loop can give (U)

    last_temp_basal -- list of last temporary basal information in format
                       [type, start time, end time, basal rate]
    duration -- length of the temp basal (mins)
    continuation_interval -- length of time before an ongoing temp basal
                             should be continued with a new command (mins)
    rate_rounder -- the smallest fraction of a unit supported in basal
                    delivery; if None, no rounding is performed

    Output:
    The predicted glucose values, recommended temporary basal, and
    recommended bolus in the format [
        (predicted glucose times, predicted glucose values),
        temporary basal recommendation,
        bolus recommendation
    ]
    """
    assert glucose_dates, "expected to receive glucose data"

    assert target_starts and sensitivity_starts and basal_starts and model,\
        "expected to receive complete settings data"

    if (not momentum_dates
            and not carb_effect_dates
            and not insulin_effect_dates
       ):
        warnings.warn("Warning: expected to receive effect data")
        return (None, None, None)

    predicted_glucoses = predict_glucose(
        glucose_dates[-1], glucose_values[-1],
        momentum_dates, momentum_values,
        carb_effect_dates, carb_effect_values,
        insulin_effect_dates, insulin_effect_values,
        retrospective_effect_dates, retrospective_effect_values
        )

    # Dosing requires prediction entries at least as long as the insulin
    # model duration. If our prediction is shorter than that, extend it here.
    if len(model) == 1:  # Walsh model
        final_date = glucose_dates[-1] + timedelta(hours=model[0])
    else:
        final_date = glucose_dates[-1] + timedelta(minutes=model[0])

    if predicted_glucoses[0][-1] < final_date:
        predicted_glucoses[0].append(final_date)
        predicted_glucoses[1].append(predicted_glucoses[1][-1])

    pending_insulin = get_pending_insulin(
        at_date,
        basal_starts, basal_rates, basal_minutes,
        last_temp_basal
    )

    temp_basal = recommended_temp_basal(
        *predicted_glucoses,
        target_starts, target_ends, target_mins, target_maxes,
        at_date,
        suspend_threshold,
        sensitivity_starts, sensitivity_ends, sensitivity_values,
        model,
        basal_starts, basal_rates, basal_minutes,
        max_basal_rate,
        last_temp_basal,
        duration,
        continuation_interval,
        rate_rounder
        )

    bolus = recommended_bolus(
        *predicted_glucoses,
        target_starts, target_ends, target_mins, target_maxes,
        at_date,
        suspend_threshold,
        sensitivity_starts, sensitivity_ends, sensitivity_values,
        model,
        pending_insulin,
        max_bolus,
        rate_rounder
        )

    return {
        "predicted_glucose_dates": predicted_glucoses[0],
        "predicted_glucose_values": predicted_glucoses[1],
        "recommended_temp_basal": temp_basal,
        "recommended_bolus": bolus
    }