import pytest
import pandas as pd
import numpy as np
from unittest.mock import Mock

from recommendations.Recommendations import Recommendations


@pytest.fixture
def heat_demand_predictions():
    return pd.DataFrame(
        [
            {'id': '614626+0_phase=0', 'predictions': 256.6, 'property_id': '614626',
             'recommendation_id': '0_phase=0',
             'phase': 0},
            {'id': '614626+1_phase=0', 'predictions': 256.6, 'property_id': '614626',
             'recommendation_id': '1_phase=0',
             'phase': 0},
            {'id': '614626+2_phase=0', 'predictions': 256.6, 'property_id': '614626',
             'recommendation_id': '2_phase=0',
             'phase': 0},
            {'id': '614626+3_phase=1', 'predictions': 263.1, 'property_id': '614626',
             'recommendation_id': '3_phase=1',
             'phase': 1},
            {'id': '614626+4_phase=2', 'predictions': 259.0, 'property_id': '614626',
             'recommendation_id': '4_phase=2',
             'phase': 2},
            {'id': '614626+5_phase=3', 'predictions': 250.5, 'property_id': '614626',
             'recommendation_id': '5_phase=3',
             'phase': 3},
            {'id': '614626+6_phase=3', 'predictions': 245.7, 'property_id': '614626',
             'recommendation_id': '6_phase=3',
             'phase': 3},
            {'id': '614626+7_phase=3', 'predictions': 199.7, 'property_id': '614626',
             'recommendation_id': '7_phase=3',
             'phase': 3},
            {'id': '614626+8_phase=4', 'predictions': 250.5, 'property_id': '614626',
             'recommendation_id': '8_phase=4',
             'phase': 4},
            {'id': '614626+9_phase=5', 'predictions': 139.5, 'property_id': '614626',
             'recommendation_id': '9_phase=5',
             'phase': 5}, {'id': '614626+10_phase=5', 'predictions': 139.5, 'property_id': '614626',
                           'recommendation_id': '10_phase=5', 'phase': 5},
            {'id': '614626+11_phase=5', 'predictions': 139.5, 'property_id': '614626',
             'recommendation_id': '11_phase=5', 'phase': 5},
            {'id': '614626+12_phase=5', 'predictions': 133.6, 'property_id': '614626',
             'recommendation_id': '12_phase=5', 'phase': 5},
            {'id': '614626+13_phase=5', 'predictions': 133.6, 'property_id': '614626',
             'recommendation_id': '13_phase=5', 'phase': 5},
            {'id': '614626+14_phase=5', 'predictions': 133.6, 'property_id': '614626',
             'recommendation_id': '14_phase=5', 'phase': 5},
            {'id': '614626+15_phase=5', 'predictions': 133.6, 'property_id': '614626',
             'recommendation_id': '15_phase=5', 'phase': 5},
            {'id': '614626+16_phase=5', 'predictions': 133.6, 'property_id': '614626',
             'recommendation_id': '16_phase=5', 'phase': 5},
            {'id': '614626+17_phase=5', 'predictions': 133.6, 'property_id': '614626',
             'recommendation_id': '17_phase=5', 'phase': 5},
            {'id': '614626+18_phase=5', 'predictions': 133.6, 'property_id': '614626',
             'recommendation_id': '18_phase=5', 'phase': 5},
            {'id': '614626+19_phase=5', 'predictions': 114.3, 'property_id': '614626',
             'recommendation_id': '19_phase=5', 'phase': 5},
            {'id': '614626+20_phase=5', 'predictions': 114.3, 'property_id': '614626',
             'recommendation_id': '20_phase=5', 'phase': 5},
            {'id': '614626+21_phase=5', 'predictions': 114.3, 'property_id': '614626',
             'recommendation_id': '21_phase=5', 'phase': 5},
            {'id': '614626+22_phase=5', 'predictions': 114.3, 'property_id': '614626',
             'recommendation_id': '22_phase=5', 'phase': 5},
            {'id': '614626+23_phase=5', 'predictions': 114.3, 'property_id': '614626',
             'recommendation_id': '23_phase=5', 'phase': 5},
            {'id': '614626+24_phase=5', 'predictions': 114.3, 'property_id': '614626',
             'recommendation_id': '24_phase=5', 'phase': 5},
            {'id': '614626+25_phase=5', 'predictions': 102.5, 'property_id': '614626',
             'recommendation_id': '25_phase=5', 'phase': 5},
            {'id': '614626+26_phase=5', 'predictions': 102.5, 'property_id': '614626',
             'recommendation_id': '26_phase=5', 'phase': 5},
            {'id': '614626+27_phase=5', 'predictions': 102.5, 'property_id': '614626',
             'recommendation_id': '27_phase=5', 'phase': 5},
            {'id': '614626+28_phase=5', 'predictions': 102.5, 'property_id': '614626',
             'recommendation_id': '28_phase=5', 'phase': 5},
            {'id': '614626+29_phase=5', 'predictions': 82.5, 'property_id': '614626',
             'recommendation_id': '29_phase=5', 'phase': 5},
            {'id': '614626+30_phase=5', 'predictions': 130.0, 'property_id': '614626',
             'recommendation_id': '30_phase=5', 'phase': 5},
            {'id': '614626+31_phase=5', 'predictions': 130.0, 'property_id': '614626',
             'recommendation_id': '31_phase=5', 'phase': 5},
            {'id': '614626+32_phase=5', 'predictions': 130.0, 'property_id': '614626',
             'recommendation_id': '32_phase=5', 'phase': 5},
            {'id': '614626+33_phase=5', 'predictions': 114.3, 'property_id': '614626',
             'recommendation_id': '33_phase=5', 'phase': 5},
            {'id': '614626+34_phase=5', 'predictions': 114.3, 'property_id': '614626',
             'recommendation_id': '34_phase=5', 'phase': 5},
            {'id': '614626+35_phase=5', 'predictions': 114.3, 'property_id': '614626',
             'recommendation_id': '35_phase=5', 'phase': 5},
            {'id': '614626+36_phase=5', 'predictions': 114.3, 'property_id': '614626',
             'recommendation_id': '36_phase=5', 'phase': 5},
            {'id': '614626+37_phase=5', 'predictions': 169.2, 'property_id': '614626',
             'recommendation_id': '37_phase=5', 'phase': 5},
            {'id': '614626+38_phase=5', 'predictions': 169.2, 'property_id': '614626',
             'recommendation_id': '38_phase=5', 'phase': 5},
            {'id': '614626+39_phase=5', 'predictions': 169.2, 'property_id': '614626',
             'recommendation_id': '39_phase=5', 'phase': 5},
            {'id': '614626+40_phase=5', 'predictions': 155.1, 'property_id': '614626',
             'recommendation_id': '40_phase=5', 'phase': 5},
            {'id': '614626+41_phase=5', 'predictions': 155.1, 'property_id': '614626',
             'recommendation_id': '41_phase=5', 'phase': 5},
            {'id': '614626+42_phase=5', 'predictions': 155.1, 'property_id': '614626',
             'recommendation_id': '42_phase=5', 'phase': 5},
            {'id': '614626+43_phase=5', 'predictions': 155.1, 'property_id': '614626',
             'recommendation_id': '43_phase=5', 'phase': 5},
            {'id': '614626+44_phase=5', 'predictions': 133.6, 'property_id': '614626',
             'recommendation_id': '44_phase=5', 'phase': 5},
            {'id': '614626+45_phase=5', 'predictions': 133.6, 'property_id': '614626',
             'recommendation_id': '45_phase=5', 'phase': 5},
            {'id': '614626+46_phase=5', 'predictions': 133.6, 'property_id': '614626',
             'recommendation_id': '46_phase=5', 'phase': 5},
            {'id': '614626+47_phase=5', 'predictions': 130.0, 'property_id': '614626',
             'recommendation_id': '47_phase=5', 'phase': 5},
            {'id': '614626+48_phase=5', 'predictions': 130.0, 'property_id': '614626',
             'recommendation_id': '48_phase=5', 'phase': 5},
            {'id': '614626+49_phase=5', 'predictions': 130.0, 'property_id': '614626',
             'recommendation_id': '49_phase=5', 'phase': 5},
            {'id': '614626+50_phase=5', 'predictions': 130.0, 'property_id': '614626',
             'recommendation_id': '50_phase=5', 'phase': 5},
            {'id': '614626+51_phase=5', 'predictions': 130.0, 'property_id': '614626',
             'recommendation_id': '51_phase=5', 'phase': 5},
            {'id': '614626+52_phase=5', 'predictions': 130.0, 'property_id': '614626',
             'recommendation_id': '52_phase=5', 'phase': 5},
            {'id': '614626+53_phase=5', 'predictions': 130.0, 'property_id': '614626',
             'recommendation_id': '53_phase=5', 'phase': 5},
            {'id': '614626+54_phase=5', 'predictions': 130.0, 'property_id': '614626',
             'recommendation_id': '54_phase=5', 'phase': 5},
            {'id': '614626+55_phase=5', 'predictions': 182.6, 'property_id': '614626',
             'recommendation_id': '55_phase=5', 'phase': 5},
            {'id': '614626+56_phase=5', 'predictions': 169.2, 'property_id': '614626',
             'recommendation_id': '56_phase=5', 'phase': 5},
            {'id': '614626+57_phase=5', 'predictions': 169.2, 'property_id': '614626',
             'recommendation_id': '57_phase=5', 'phase': 5}
        ]
    )


@pytest.fixture
def carbon_predictions():
    return pd.DataFrame(
        [
            {'id': '614626+0_phase=0', 'predictions': 2.2, 'property_id': '614626',
             'recommendation_id': '0_phase=0',
             'phase': 0},
            {'id': '614626+1_phase=0', 'predictions': 2.2, 'property_id': '614626',
             'recommendation_id': '1_phase=0',
             'phase': 0},
            {'id': '614626+2_phase=0', 'predictions': 2.2, 'property_id': '614626',
             'recommendation_id': '2_phase=0',
             'phase': 0},
            {'id': '614626+3_phase=1', 'predictions': 2.2, 'property_id': '614626',
             'recommendation_id': '3_phase=1',
             'phase': 1},
            {'id': '614626+4_phase=2', 'predictions': 2.2, 'property_id': '614626',
             'recommendation_id': '4_phase=2',
             'phase': 2},
            {'id': '614626+5_phase=3', 'predictions': 2.1, 'property_id': '614626',
             'recommendation_id': '5_phase=3',
             'phase': 3},
            {'id': '614626+6_phase=3', 'predictions': 2.1, 'property_id': '614626',
             'recommendation_id': '6_phase=3',
             'phase': 3},
            {'id': '614626+7_phase=3', 'predictions': 1.4, 'property_id': '614626',
             'recommendation_id': '7_phase=3',
             'phase': 3},
            {'id': '614626+8_phase=4', 'predictions': 2.1, 'property_id': '614626',
             'recommendation_id': '8_phase=4',
             'phase': 4},
            {'id': '614626+9_phase=5', 'predictions': 1.3, 'property_id': '614626',
             'recommendation_id': '9_phase=5',
             'phase': 5},
            {'id': '614626+10_phase=5', 'predictions': 1.3, 'property_id': '614626',
             'recommendation_id': '10_phase=5',
             'phase': 5},
            {'id': '614626+11_phase=5', 'predictions': 1.3, 'property_id': '614626',
             'recommendation_id': '11_phase=5',
             'phase': 5},
            {'id': '614626+12_phase=5', 'predictions': 1.2, 'property_id': '614626',
             'recommendation_id': '12_phase=5',
             'phase': 5},
            {'id': '614626+13_phase=5', 'predictions': 1.2, 'property_id': '614626',
             'recommendation_id': '13_phase=5',
             'phase': 5},
            {'id': '614626+14_phase=5', 'predictions': 1.2, 'property_id': '614626',
             'recommendation_id': '14_phase=5',
             'phase': 5},
            {'id': '614626+15_phase=5', 'predictions': 1.2, 'property_id': '614626',
             'recommendation_id': '15_phase=5',
             'phase': 5},
            {'id': '614626+16_phase=5', 'predictions': 1.2, 'property_id': '614626',
             'recommendation_id': '16_phase=5',
             'phase': 5},
            {'id': '614626+17_phase=5', 'predictions': 1.2, 'property_id': '614626',
             'recommendation_id': '17_phase=5',
             'phase': 5},
            {'id': '614626+18_phase=5', 'predictions': 1.2, 'property_id': '614626',
             'recommendation_id': '18_phase=5',
             'phase': 5},
            {'id': '614626+19_phase=5', 'predictions': 1.0, 'property_id': '614626',
             'recommendation_id': '19_phase=5',
             'phase': 5},
            {'id': '614626+20_phase=5', 'predictions': 1.0, 'property_id': '614626',
             'recommendation_id': '20_phase=5',
             'phase': 5},
            {'id': '614626+21_phase=5', 'predictions': 1.0, 'property_id': '614626',
             'recommendation_id': '21_phase=5',
             'phase': 5},
            {'id': '614626+22_phase=5', 'predictions': 1.0, 'property_id': '614626',
             'recommendation_id': '22_phase=5',
             'phase': 5},
            {'id': '614626+23_phase=5', 'predictions': 1.0, 'property_id': '614626',
             'recommendation_id': '23_phase=5',
             'phase': 5},
            {'id': '614626+24_phase=5', 'predictions': 1.0, 'property_id': '614626',
             'recommendation_id': '24_phase=5',
             'phase': 5},
            {'id': '614626+25_phase=5', 'predictions': 0.9, 'property_id': '614626',
             'recommendation_id': '25_phase=5',
             'phase': 5},
            {'id': '614626+26_phase=5', 'predictions': 0.9, 'property_id': '614626',
             'recommendation_id': '26_phase=5',
             'phase': 5},
            {'id': '614626+27_phase=5', 'predictions': 0.9, 'property_id': '614626',
             'recommendation_id': '27_phase=5',
             'phase': 5},
            {'id': '614626+28_phase=5', 'predictions': 0.9, 'property_id': '614626',
             'recommendation_id': '28_phase=5',
             'phase': 5},
            {'id': '614626+29_phase=5', 'predictions': 0.8, 'property_id': '614626',
             'recommendation_id': '29_phase=5',
             'phase': 5},
            {'id': '614626+30_phase=5', 'predictions': 1.1, 'property_id': '614626',
             'recommendation_id': '30_phase=5',
             'phase': 5},
            {'id': '614626+31_phase=5', 'predictions': 1.1, 'property_id': '614626',
             'recommendation_id': '31_phase=5',
             'phase': 5},
            {'id': '614626+32_phase=5', 'predictions': 1.1, 'property_id': '614626',
             'recommendation_id': '32_phase=5',
             'phase': 5},
            {'id': '614626+33_phase=5', 'predictions': 1.0, 'property_id': '614626',
             'recommendation_id': '33_phase=5',
             'phase': 5},
            {'id': '614626+34_phase=5', 'predictions': 1.0, 'property_id': '614626',
             'recommendation_id': '34_phase=5',
             'phase': 5},
            {'id': '614626+35_phase=5', 'predictions': 1.0, 'property_id': '614626',
             'recommendation_id': '35_phase=5',
             'phase': 5},
            {'id': '614626+36_phase=5', 'predictions': 1.0, 'property_id': '614626',
             'recommendation_id': '36_phase=5',
             'phase': 5},
            {'id': '614626+37_phase=5', 'predictions': 1.5, 'property_id': '614626',
             'recommendation_id': '37_phase=5',
             'phase': 5},
            {'id': '614626+38_phase=5', 'predictions': 1.5, 'property_id': '614626',
             'recommendation_id': '38_phase=5',
             'phase': 5},
            {'id': '614626+39_phase=5', 'predictions': 1.5, 'property_id': '614626',
             'recommendation_id': '39_phase=5',
             'phase': 5},
            {'id': '614626+40_phase=5', 'predictions': 1.4, 'property_id': '614626',
             'recommendation_id': '40_phase=5',
             'phase': 5},
            {'id': '614626+41_phase=5', 'predictions': 1.4, 'property_id': '614626',
             'recommendation_id': '41_phase=5',
             'phase': 5},
            {'id': '614626+42_phase=5', 'predictions': 1.4, 'property_id': '614626',
             'recommendation_id': '42_phase=5',
             'phase': 5},
            {'id': '614626+43_phase=5', 'predictions': 1.4, 'property_id': '614626',
             'recommendation_id': '43_phase=5',
             'phase': 5},
            {'id': '614626+44_phase=5', 'predictions': 1.2, 'property_id': '614626',
             'recommendation_id': '44_phase=5',
             'phase': 5},
            {'id': '614626+45_phase=5', 'predictions': 1.2, 'property_id': '614626',
             'recommendation_id': '45_phase=5',
             'phase': 5},
            {'id': '614626+46_phase=5', 'predictions': 1.2, 'property_id': '614626',
             'recommendation_id': '46_phase=5',
             'phase': 5},
            {'id': '614626+47_phase=5', 'predictions': 1.1, 'property_id': '614626',
             'recommendation_id': '47_phase=5',
             'phase': 5},
            {'id': '614626+48_phase=5', 'predictions': 1.1, 'property_id': '614626',
             'recommendation_id': '48_phase=5',
             'phase': 5},
            {'id': '614626+49_phase=5', 'predictions': 1.1, 'property_id': '614626',
             'recommendation_id': '49_phase=5',
             'phase': 5},
            {'id': '614626+50_phase=5', 'predictions': 1.1, 'property_id': '614626',
             'recommendation_id': '50_phase=5',
             'phase': 5},
            {'id': '614626+51_phase=5', 'predictions': 1.1, 'property_id': '614626',
             'recommendation_id': '51_phase=5',
             'phase': 5},
            {'id': '614626+52_phase=5', 'predictions': 1.1, 'property_id': '614626',
             'recommendation_id': '52_phase=5',
             'phase': 5},
            {'id': '614626+53_phase=5', 'predictions': 1.1, 'property_id': '614626',
             'recommendation_id': '53_phase=5',
             'phase': 5},
            {'id': '614626+54_phase=5', 'predictions': 1.1, 'property_id': '614626',
             'recommendation_id': '54_phase=5',
             'phase': 5},
            {'id': '614626+55_phase=5', 'predictions': 1.6, 'property_id': '614626',
             'recommendation_id': '55_phase=5',
             'phase': 5},
            {'id': '614626+56_phase=5', 'predictions': 1.5, 'property_id': '614626',
             'recommendation_id': '56_phase=5',
             'phase': 5},
            {'id': '614626+57_phase=5', 'predictions': 1.5, 'property_id': '614626',
             'recommendation_id': '57_phase=5',
             'phase': 5}
        ]
    )


@pytest.fixture
def property_instance():
    from types import SimpleNamespace
    return SimpleNamespace(
        id=614626,

        epc_record=SimpleNamespace(
            current_energy_efficiency=65,
            co2_emissions_current=2.4,
            energy_consumption_current=284,
            roof_energy_eff="Good",
            lighting_energy_eff="Good"
        ),
        roof={
            "is_loft": True,
            "insulation_thickness": "250",
            "is_valid": True,
        },
        lighting={
            "low_energy_proportion": 0.5
        }
    )


@pytest.mark.parametrize(
    "input_data, expected",
    [
        (
                [
                    {"recommendation_id": "a", "phase": 0, "sap_adjustment": 1.7},
                    {"recommendation_id": "b", "phase": 0, "sap_adjustment": 1.7},
                ],
                [{"recommendation_id": "a", "phase": 0, "sap_adjustment": 1.7}],
        ),
        (
                [
                    {"recommendation_id": "a", "phase": 1, "sap_adjustment": 2},
                    {"recommendation_id": "b", "phase": 2, "sap_adjustment": 3},
                ],
                [
                    {"recommendation_id": "a", "phase": 1, "sap_adjustment": 2},
                    {"recommendation_id": "b", "phase": 2, "sap_adjustment": 3},
                ],
        ),
    ],
)
def test_filter_phase_adjustment(input_data, expected):
    assert Recommendations._filter_phase_adjustment(input_data) == expected


@pytest.mark.parametrize(
    "sap_impact, limit, expected",
    [
        (1.0, -4, True),  # positive SAP not allowed
        (0.0, -4, False),  # zero is allowed
        (-1.0, -4, False),  # valid range
        (-3.9, -4, False),  # valid range
        (-4.0, -4, False),  # exact lower bound allowed
        (-4.1, -4, True),  # below lower bound
    ],
)
def test_check_ventilation_out_of_bounds(sap_impact, limit, expected):
    assert Recommendations._check_ventilation_out_of_bounds(
        sap_impact, limit
    ) is expected


@pytest.mark.parametrize(
    "sap_impact, limit, expected",
    [
        (1.2, -4, -1),  # positive → capped to -1
        (0.0, -4, -1),  # zero → capped to -1
        (-5.0, -4, -4),  # below limit → clamp
        (-3.0, -4, -3.0),  # already valid → unchanged
    ],
)
def test_adjust_ventilation_sap(sap_impact, limit, expected):
    assert Recommendations._adjust_ventilation_sap(
        sap_impact, limit
    ) == expected


def test_get_previous_phase_values_phase_0_starting_phase_0(property_instance):
    result = Recommendations._get_previous_phase_values(
        rec_phase=0,
        starting_phase=0,
        impact_summary=[],
        property_instance=property_instance,
    )

    assert result == {
        "sap": 65.0,
        "sap_prediction": 65.0,
        "carbon": 2.4,
        "heat_demand": 284.0,
    }


def test_get_previous_phase_values_single_rep(property_instance):
    impact_summary = [
        {
            "phase": 0,
            "representative": True,
            "sap": 66,
            "carbon": 2.2,
            "heat_demand": 260,
        }
    ]

    result = Recommendations._get_previous_phase_values(
        rec_phase=1,
        starting_phase=0,
        impact_summary=impact_summary,
        property_instance=property_instance,
    )

    assert result["sap"] == 66
    assert result["carbon"] == 2.2
    assert result["heat_demand"] == 260


def test_get_previous_phase_values_median(property_instance):
    impact_summary = [
        {"phase": 1, "representative": True, "sap": 70, "carbon": 2.0, "heat_demand": 250, "sap_prediction": 70},
        {"phase": 1, "representative": True, "sap": 74, "carbon": 1.6, "heat_demand": 230, "sap_prediction": 74},
    ]

    result = Recommendations._get_previous_phase_values(
        rec_phase=2,
        starting_phase=0,
        impact_summary=impact_summary,
        property_instance=property_instance,
    )

    assert result["sap"] == np.median([70, 74])
    assert result["carbon"] == np.median([2.0, 1.6])
    assert result["heat_demand"] == np.median([250, 230])


def test_compute_phase_impact_standard():
    previous = {"sap": 65, "carbon": 2.4, "heat_demand": 284}
    current = {"sap": 64, "carbon": 2.6, "heat_demand": 300}

    impact = Recommendations._compute_phase_impact(
        rec_type="loft_insulation",
        previous_phase_values=previous,
        current_phase_values=current,
    )

    # monotonicity enforced
    assert impact["sap"] == 0
    assert impact["carbon"] == 0
    assert impact["heat_demand"] == 0


def test_compute_phase_impact_mechanical_ventilation():
    previous = {"sap": 65, "carbon": 2.4, "heat_demand": 284}
    current = {"sap": 63, "carbon": 2.4, "heat_demand": 284}

    impact = Recommendations._compute_phase_impact(
        rec_type="mechanical_ventilation",
        previous_phase_values=previous,
        current_phase_values=current,
    )

    assert impact["sap"] == -2


def test_resolve_current_phase_sap_with_adjustments():
    rec = {"phase": 3, "survey": False}
    previous = {"sap": 65}
    phase_metrics = {"sap_change": 70}
    adjustments = [
        {"phase": 1, "sap_adjustment": 1.5},
        {"phase": 2, "sap_adjustment": 2.0},
    ]

    sap = Recommendations._resolve_current_phase_sap(
        rec=rec,
        previous_phase_values=previous,
        phase_energy_efficiency_metrics=phase_metrics,
        adjustments=adjustments,
    )

    assert sap == 70 - (1.5 + 2.0)


def test_validate_recommendation_updates_raises():
    rec = {
        "sap_points": None,
        "co2_equivalent_savings": None,
        "heat_demand": None,
    }

    with pytest.raises(ValueError):
        Recommendations._validate_recommendation_updates(rec)


def test_calculate_recommendation_impact(property_instance, heat_demand_predictions, carbon_predictions):
    #######
    # Case 3
    #######
    # Here, the solar impact falls below our threshold and so we expect a solar adjustment to increase the impact
    # above the minimum threshold

    all_predictions3 = {
        "sap_change_predictions": pd.DataFrame(
            [
                {'id': '614626+0_phase=0', 'predictions': 66.7, 'property_id': '614626',
                 'recommendation_id': '0_phase=0',
                 'phase': 0},
                {'id': '614626+1_phase=0', 'predictions': 66.7, 'property_id': '614626',
                 'recommendation_id': '1_phase=0',
                 'phase': 0},
                {'id': '614626+2_phase=0', 'predictions': 66.7, 'property_id': '614626',
                 'recommendation_id': '2_phase=0',
                 'phase': 0},
                {'id': '614626+3_phase=1', 'predictions': 65.3, 'property_id': '614626',
                 'recommendation_id': '3_phase=1',
                 'phase': 1},
                {'id': '614626+4_phase=2', 'predictions': 66.3, 'property_id': '614626',
                 'recommendation_id': '4_phase=2',
                 'phase': 2},
                {'id': '614626+5_phase=3', 'predictions': 67.3, 'property_id': '614626',
                 'recommendation_id': '5_phase=3',
                 'phase': 3},
                {'id': '614626+6_phase=3', 'predictions': 68.1, 'property_id': '614626',
                 'recommendation_id': '6_phase=3',
                 'phase': 3},
                {'id': '614626+7_phase=3', 'predictions': 70.1, 'property_id': '614626',
                 'recommendation_id': '7_phase=3',
                 'phase': 3},
                {'id': '614626+8_phase=4', 'predictions': 67.3, 'property_id': '614626',
                 'recommendation_id': '8_phase=4',
                 'phase': 4},
                {'id': '614626+9_phase=5', 'predictions': 85.3, 'property_id': '614626',
                 'recommendation_id': '9_phase=5',
                 'phase': 5}, {'id': '614626+10_phase=5', 'predictions': 85.3, 'property_id': '614626',
                               'recommendation_id': '10_phase=5', 'phase': 5},
                {'id': '614626+11_phase=5', 'predictions': 85.3, 'property_id': '614626',
                 'recommendation_id': '11_phase=5', 'phase': 5},
                {'id': '614626+12_phase=5', 'predictions': 85.5, 'property_id': '614626',
                 'recommendation_id': '12_phase=5', 'phase': 5},
                {'id': '614626+13_phase=5', 'predictions': 85.5, 'property_id': '614626',
                 'recommendation_id': '13_phase=5', 'phase': 5},
                {'id': '614626+14_phase=5', 'predictions': 85.5, 'property_id': '614626',
                 'recommendation_id': '14_phase=5', 'phase': 5},
                {'id': '614626+15_phase=5', 'predictions': 85.5, 'property_id': '614626',
                 'recommendation_id': '15_phase=5', 'phase': 5},
                {'id': '614626+16_phase=5', 'predictions': 85.5, 'property_id': '614626',
                 'recommendation_id': '16_phase=5', 'phase': 5},
                {'id': '614626+17_phase=5', 'predictions': 85.5, 'property_id': '614626',
                 'recommendation_id': '17_phase=5', 'phase': 5},
                {'id': '614626+18_phase=5', 'predictions': 85.5, 'property_id': '614626',
                 'recommendation_id': '18_phase=5', 'phase': 5},
                {'id': '614626+19_phase=5', 'predictions': 86.4, 'property_id': '614626',
                 'recommendation_id': '19_phase=5', 'phase': 5},
                {'id': '614626+20_phase=5', 'predictions': 86.4, 'property_id': '614626',
                 'recommendation_id': '20_phase=5', 'phase': 5},
                {'id': '614626+21_phase=5', 'predictions': 86.4, 'property_id': '614626',
                 'recommendation_id': '21_phase=5', 'phase': 5},
                {'id': '614626+22_phase=5', 'predictions': 86.4, 'property_id': '614626',
                 'recommendation_id': '22_phase=5', 'phase': 5},
                {'id': '614626+23_phase=5', 'predictions': 86.4, 'property_id': '614626',
                 'recommendation_id': '23_phase=5', 'phase': 5},
                {'id': '614626+24_phase=5', 'predictions': 86.4, 'property_id': '614626',
                 'recommendation_id': '24_phase=5', 'phase': 5},
                {'id': '614626+25_phase=5', 'predictions': 86.7, 'property_id': '614626',
                 'recommendation_id': '25_phase=5', 'phase': 5},
                {'id': '614626+26_phase=5', 'predictions': 86.7, 'property_id': '614626',
                 'recommendation_id': '26_phase=5', 'phase': 5},
                {'id': '614626+27_phase=5', 'predictions': 86.7, 'property_id': '614626',
                 'recommendation_id': '27_phase=5', 'phase': 5},
                {'id': '614626+28_phase=5', 'predictions': 86.7, 'property_id': '614626',
                 'recommendation_id': '28_phase=5', 'phase': 5},
                {'id': '614626+29_phase=5', 'predictions': 83.8, 'property_id': '614626',
                 'recommendation_id': '29_phase=5', 'phase': 5},
                {'id': '614626+30_phase=5', 'predictions': 85.4, 'property_id': '614626',
                 'recommendation_id': '30_phase=5', 'phase': 5},
                {'id': '614626+31_phase=5', 'predictions': 85.4, 'property_id': '614626',
                 'recommendation_id': '31_phase=5', 'phase': 5},
                {'id': '614626+32_phase=5', 'predictions': 85.4, 'property_id': '614626',
                 'recommendation_id': '32_phase=5', 'phase': 5},
                {'id': '614626+33_phase=5', 'predictions': 86.4, 'property_id': '614626',
                 'recommendation_id': '33_phase=5', 'phase': 5},
                {'id': '614626+34_phase=5', 'predictions': 86.4, 'property_id': '614626',
                 'recommendation_id': '34_phase=5', 'phase': 5},
                {'id': '614626+35_phase=5', 'predictions': 86.4, 'property_id': '614626',
                 'recommendation_id': '35_phase=5', 'phase': 5},
                {'id': '614626+36_phase=5', 'predictions': 86.4, 'property_id': '614626',
                 'recommendation_id': '36_phase=5', 'phase': 5},
                {'id': '614626+37_phase=5', 'predictions': 81.2, 'property_id': '614626',
                 'recommendation_id': '37_phase=5', 'phase': 5},
                {'id': '614626+38_phase=5', 'predictions': 81.2, 'property_id': '614626',
                 'recommendation_id': '38_phase=5', 'phase': 5},
                {'id': '614626+39_phase=5', 'predictions': 81.2, 'property_id': '614626',
                 'recommendation_id': '39_phase=5', 'phase': 5},
                {'id': '614626+40_phase=5', 'predictions': 83.4, 'property_id': '614626',
                 'recommendation_id': '40_phase=5', 'phase': 5},
                {'id': '614626+41_phase=5', 'predictions': 83.4, 'property_id': '614626',
                 'recommendation_id': '41_phase=5', 'phase': 5},
                {'id': '614626+42_phase=5', 'predictions': 83.4, 'property_id': '614626',
                 'recommendation_id': '42_phase=5', 'phase': 5},
                {'id': '614626+43_phase=5', 'predictions': 83.4, 'property_id': '614626',
                 'recommendation_id': '43_phase=5', 'phase': 5},
                {'id': '614626+44_phase=5', 'predictions': 85.5, 'property_id': '614626',
                 'recommendation_id': '44_phase=5', 'phase': 5},
                {'id': '614626+45_phase=5', 'predictions': 85.5, 'property_id': '614626',
                 'recommendation_id': '45_phase=5', 'phase': 5},
                {'id': '614626+46_phase=5', 'predictions': 85.5, 'property_id': '614626',
                 'recommendation_id': '46_phase=5', 'phase': 5},
                {'id': '614626+47_phase=5', 'predictions': 85.4, 'property_id': '614626',
                 'recommendation_id': '47_phase=5', 'phase': 5},
                {'id': '614626+48_phase=5', 'predictions': 85.4, 'property_id': '614626',
                 'recommendation_id': '48_phase=5', 'phase': 5},
                {'id': '614626+49_phase=5', 'predictions': 85.4, 'property_id': '614626',
                 'recommendation_id': '49_phase=5', 'phase': 5},
                {'id': '614626+50_phase=5', 'predictions': 85.4, 'property_id': '614626',
                 'recommendation_id': '50_phase=5', 'phase': 5},
                {'id': '614626+51_phase=5', 'predictions': 85.4, 'property_id': '614626',
                 'recommendation_id': '51_phase=5', 'phase': 5},
                {'id': '614626+52_phase=5', 'predictions': 85.4, 'property_id': '614626',
                 'recommendation_id': '52_phase=5', 'phase': 5},
                {'id': '614626+53_phase=5', 'predictions': 85.4, 'property_id': '614626',
                 'recommendation_id': '53_phase=5', 'phase': 5},
                {'id': '614626+54_phase=5', 'predictions': 85.4, 'property_id': '614626',
                 'recommendation_id': '54_phase=5', 'phase': 5},
                {'id': '614626+55_phase=5', 'predictions': 79.4, 'property_id': '614626',
                 'recommendation_id': '55_phase=5', 'phase': 5},
                {'id': '614626+56_phase=5', 'predictions': 81.2, 'property_id': '614626',
                 'recommendation_id': '56_phase=5', 'phase': 5},
                {'id': '614626+57_phase=5', 'predictions': 81.2, 'property_id': '614626',
                 'recommendation_id': '57_phase=5', 'phase': 5}]
        ),
        "heat_demand_predictions": heat_demand_predictions,
        "carbon_change_predictions": carbon_predictions,
        "hotwater_kwh_predictions": pd.DataFrame([]),
        "heating_kwh_predictions": pd.DataFrame([]),
    }

    recommendations3 = {
        614626: [
            [
                {
                    'phase': 0,
                    'type': 'loft_insulation',
                    'measure_type': 'loft_insulation',
                    'sap_points': 0,
                    'survey': False,
                    'recommendation_id': '0_phase=0',
                    'co2_equivalent_savings': np.float64(0.19999999999999973),
                    'heat_demand': np.float64(27.399999999999977)},
            ],
            [
                {
                    'phase': 1,
                    'type': 'mechanical_ventilation',
                    'measure_type': 'mechanical_ventilation',
                    'sap_points': np.float64(-1.4000000000000057),
                    'heat_demand': np.float64(-6.5),
                    'kwh_savings': 0,
                    'co2_equivalent_savings': np.float64(0.0),
                    'energy_cost_savings': 0,
                    'innovation_rate': 0.0,
                    'recommendation_id': '3_phase=1', 'efficiency': 0}
            ],
            [
                {
                    'phase': 2,
                    'type': 'low_energy_lighting',
                    'measure_type': 'low_energy_lighting',
                    'already_installed': False, 'sap_points': 5, 'kwh_savings': 164.25,
                    'energy_cost_savings': 45.480824999999996, 'co2_equivalent_savings': np.float64(0.0),
                    'total': 10.5, 'contingency': 2.73,
                    'contingency_rate': 0.26, 'labour_hours': 1, 'labour_days': 0.125, 'survey': True,
                    'recommendation_id': '4_phase=2', 'efficiency': 10.5, 'heat_demand': np.float64(4.100000000000023)}
            ],
            [
                {
                    'type': 'heating', 'measure_type': 'roomstat_programmer_trvs', 'phase': 3,
                    'total': 70,
                    'contingency': 7.0, 'contingency_rate': 0.1, 'subtotal': 58.333333333333336,
                    'vat': 11.666666666666664,
                    'labour_hours': 0.5, 'labour_days': 1,
                    'sap_points': np.float64(1.0), 'already_installed': False,
                    'innovation_rate': 0.0,
                    'recommendation_id': '5_phase=3', 'efficiency': 70,
                    'co2_equivalent_savings': np.float64(0.10000000000000009), 'heat_demand': np.float64(8.5)
                },
                {
                    'type': 'heating', 'phase': 3, 'measure_type': 'time_temperature_zone_control',
                    'total': 604.5840000000001, 'contingency': 60.45840000000001, 'contingency_rate': 0.1,
                    'subtotal': 571.32,
                    'vat': 33.264, 'labour_hours': 3.08, 'labour_days': np.float64(1.0),
                    'sap_points': np.float64(1.8), 'already_installed': False,
                    'recommendation_id': '6_phase=3', 'efficiency': 604.5840000000001,
                    'co2_equivalent_savings': np.float64(0.10000000000000009),
                    'heat_demand': np.float64(13.300000000000011)
                },
                {
                    'phase': 3, 'type': 'heating', 'measure_type': 'air_source_heat_pump',
                    'sap_points': np.float64(3.8),
                    'already_installed': False,
                    'total': 17144.924,
                    'contingency': 4195.5434000000005, 'contingency_rate': 0.35, 'vat': 33.264, 'labour_hours': 83.08,
                    'labour_days': np.float64(11.0), 'innovation_rate': 0, 'recommendation_id': '7_phase=3',
                    'efficiency': 17144.924, 'co2_equivalent_savings': np.float64(0.8000000000000003),
                    'heat_demand': np.float64(59.30000000000001)
                }
            ],
            [
                {'phase': 4, 'type': 'secondary_heating', 'measure_type': 'secondary_heating',
                 'sap_points': np.float64(0.0), 'already_installed': False, 'total': 60.0, 'contingency': 6.0,
                 'contingency_rate': 0.1, 'subtotal': 50.0, 'vat': 10.0, 'labour_hours': 6.0,
                 'labour_days': np.float64(1.0), 'innovation_rate': 0.0,
                 'recommendation_id': '8_phase=4', 'efficiency': 60.0, 'co2_equivalent_savings': np.float64(0.0),
                 'heat_demand': np.float64(0.0)}
            ],
            [
                {
                    'phase': 5,
                    'type': 'solar_pv',
                    'measure_type': 'solar_pv',
                    'starting_u_value': None, 'new_u_value': None,
                    'sap_points': np.float64(16.0), 'already_installed': False,
                    'total': 5892.21, 'subtotal': 5892.21, 'contingency': 883.8315,
                    'contingency_rate': 0.15, 'vat': 0, 'labour_hours': 48,
                    'labour_days': 2, 'has_battery': False,
                    'initial_ac_kwh_per_year': np.float64(4844.465553999999),
                    'innovation_rate': 0.0, 'recommendation_id': '29_phase=5',
                    'efficiency': np.float64(368.263125)
                }
            ]
        ]
    }

    representative_recommendations3 = {
        614626: [
            {
                'phase': 0,
                'type': 'loft_insulation',
                'measure_type': 'loft_insulation',
                'sap_points': 0,
                'already_installed': False,
                'total': 1029.0, 'contingency': 102.9,
                'contingency_rate': 0.1, 'labour_hours': 8, 'labour_days': 1, 'survey': False,
                'innovation_rate': 0.0,
                'recommendation_id': '0_phase=0', 'efficiency': np.float64(6052.801176470587),
                'co2_equivalent_savings': np.float64(0.19999999999999973),
                'heat_demand': np.float64(27.399999999999977)
            },
            {
                'phase': 1,
                'type': 'mechanical_ventilation',
                'measure_type': 'mechanical_ventilation',
                'starting_u_value': None, 'new_u_value': None,
                'already_installed': False,
                'sap_points': np.float64(-1.4000000000000057),
                'heat_demand': np.float64(-6.5), 'kwh_savings': 0,
                'co2_equivalent_savings': np.float64(0.0),
                'energy_cost_savings': 0, 'total': 560.0,
                'labour_hours': 8, 'labour_days': 1.0,
                'innovation_rate': 0.0,
                'recommendation_id': '3_phase=1', 'efficiency': 0},
            {
                'phase': 2,
                'type': 'low_energy_lighting', 'measure_type': 'low_energy_lighting',
                'already_installed': False, 'sap_points': 5, 'kwh_savings': 164.25,
                'energy_cost_savings': 45.480824999999996, 'co2_equivalent_savings': np.float64(0.0),
                'total': 10.5, 'contingency': 2.73,
                'contingency_rate': 0.26, 'labour_hours': 1, 'labour_days': 0.125, 'survey': True,
                'innovation_rate': 0.0, 'recommendation_id': '4_phase=2', 'efficiency': 10.5,
                'heat_demand': np.float64(4.100000000000023)
            },
            {
                'type': 'heating', 'measure_type':
                'roomstat_programmer_trvs', 'phase': 3,
                'total': 70,
                'contingency': 7.0, 'contingency_rate': 0.1, 'subtotal': 58.333333333333336,
                'vat': 11.666666666666664, 'labour_hours': 0.5, 'labour_days': 1, 'starting_u_value': None,
                'new_u_value': None, 'sap_points': np.float64(1.0), 'already_installed': False,
                'innovation_rate': 0.0,
                'recommendation_id': '5_phase=3', 'efficiency': 70,
                'co2_equivalent_savings': np.float64(0.10000000000000009),
                'heat_demand': np.float64(8.5)
            },
            {
                'phase': 4, 'type': 'secondary_heating', 'measure_type': 'secondary_heating',
                'sap_points': np.float64(0.0), 'already_installed': False, 'total': 60.0, 'contingency': 6.0,
                'contingency_rate': 0.1, 'subtotal': 50.0, 'vat': 10.0, 'labour_hours': 6.0,
                'labour_days': np.float64(1.0),
                'recommendation_id': '8_phase=4', 'efficiency': 60.0, 'co2_equivalent_savings': np.float64(0.0),
                'heat_demand': np.float64(0.0)},
            {
                'phase': 5,
                'type': 'solar_pv',
                'measure_type': 'solar_pv',

                'starting_u_value': None, 'new_u_value': None,
                'sap_points': np.float64(16.0), 'already_installed': False,
                'total': 5892.21, 'subtotal': 5892.21, 'contingency': 883.8315,
                'contingency_rate': 0.15, 'vat': 0, 'labour_hours': 48,
                'labour_days': 2, 'has_battery': False,
                'innovation_rate': 0.0, 'recommendation_id': '29_phase=5',
                'efficiency': np.float64(368.263125)
            }
        ]
    }

    recommendations_with_impact3, impact_summary3, adjustments3 = (
        Recommendations.calculate_recommendation_impact(
            property_instance=property_instance,
            all_predictions=all_predictions3,
            recommendations=recommendations3,
            representative_recommendations=representative_recommendations3,
            debug=True
        )
    )

    # We expect adjustments for loft insulation, lighting and solar

    assert adjustments3 == [
        {'recommendation_id': '0_phase=0', 'phase': 0, 'sap_adjustment': np.float64(1.7)},
        {'recommendation_id': '4_phase=2', 'phase': 2, 'sap_adjustment': np.float64(4.0)},
        {'recommendation_id': '29_phase=5', 'phase': 5, 'sap_adjustment': np.float64(-2.5)}
    ]

    # Check the impact has slowed through to solar - the final on the impact summary. The 5
    # point prediction isn't associated to the prediction from the model so the adjustment
    # should be

    df = all_predictions3["sap_change_predictions"]
    raw_prediction = 83.8
    # We expect 1.7 decrease from loft, 4 decrease from lighting, and 2.5 increase from solar
    # for a total of a 3.2 decrease
    expected_adjusted_prediction = raw_prediction - 3.2

    assert impact_summary3[-1]["sap"] == expected_adjusted_prediction


def test_loft_adjustment_flows_to_solar(property_instance, heat_demand_predictions, carbon_predictions):
    ########################
    # Case 1
    ########################
    # Just an adjustment to loft insulation

    sap_change_predictions = pd.DataFrame(
        [
            {'id': '614626+0_phase=0', 'predictions': 66.7, 'property_id': '614626',
             'recommendation_id': '0_phase=0',
             'phase': 0},
            {'id': '614626+1_phase=0', 'predictions': 66.7, 'property_id': '614626',
             'recommendation_id': '1_phase=0',
             'phase': 0},
            {'id': '614626+2_phase=0', 'predictions': 66.7, 'property_id': '614626',
             'recommendation_id': '2_phase=0',
             'phase': 0},
            {'id': '614626+3_phase=1', 'predictions': 65.3, 'property_id': '614626',
             'recommendation_id': '3_phase=1',
             'phase': 1},
            {'id': '614626+4_phase=2', 'predictions': 66.3, 'property_id': '614626',
             'recommendation_id': '4_phase=2',
             'phase': 2},
            {'id': '614626+5_phase=3', 'predictions': 67.3, 'property_id': '614626',
             'recommendation_id': '5_phase=3',
             'phase': 3},
            {'id': '614626+6_phase=3', 'predictions': 68.1, 'property_id': '614626',
             'recommendation_id': '6_phase=3',
             'phase': 3},
            {'id': '614626+7_phase=3', 'predictions': 70.1, 'property_id': '614626',
             'recommendation_id': '7_phase=3',
             'phase': 3},
            {'id': '614626+8_phase=4', 'predictions': 67.3, 'property_id': '614626',
             'recommendation_id': '8_phase=4',
             'phase': 4},
            {'id': '614626+9_phase=5', 'predictions': 85.3, 'property_id': '614626',
             'recommendation_id': '9_phase=5',
             'phase': 5}, {'id': '614626+10_phase=5', 'predictions': 85.3, 'property_id': '614626',
                           'recommendation_id': '10_phase=5', 'phase': 5},
            {'id': '614626+11_phase=5', 'predictions': 85.3, 'property_id': '614626',
             'recommendation_id': '11_phase=5', 'phase': 5},
            {'id': '614626+12_phase=5', 'predictions': 85.5, 'property_id': '614626',
             'recommendation_id': '12_phase=5', 'phase': 5},
            {'id': '614626+13_phase=5', 'predictions': 85.5, 'property_id': '614626',
             'recommendation_id': '13_phase=5', 'phase': 5},
            {'id': '614626+14_phase=5', 'predictions': 85.5, 'property_id': '614626',
             'recommendation_id': '14_phase=5', 'phase': 5},
            {'id': '614626+15_phase=5', 'predictions': 85.5, 'property_id': '614626',
             'recommendation_id': '15_phase=5', 'phase': 5},
            {'id': '614626+16_phase=5', 'predictions': 85.5, 'property_id': '614626',
             'recommendation_id': '16_phase=5', 'phase': 5},
            {'id': '614626+17_phase=5', 'predictions': 85.5, 'property_id': '614626',
             'recommendation_id': '17_phase=5', 'phase': 5},
            {'id': '614626+18_phase=5', 'predictions': 85.5, 'property_id': '614626',
             'recommendation_id': '18_phase=5', 'phase': 5},
            {'id': '614626+19_phase=5', 'predictions': 86.4, 'property_id': '614626',
             'recommendation_id': '19_phase=5', 'phase': 5},
            {'id': '614626+20_phase=5', 'predictions': 86.4, 'property_id': '614626',
             'recommendation_id': '20_phase=5', 'phase': 5},
            {'id': '614626+21_phase=5', 'predictions': 86.4, 'property_id': '614626',
             'recommendation_id': '21_phase=5', 'phase': 5},
            {'id': '614626+22_phase=5', 'predictions': 86.4, 'property_id': '614626',
             'recommendation_id': '22_phase=5', 'phase': 5},
            {'id': '614626+23_phase=5', 'predictions': 86.4, 'property_id': '614626',
             'recommendation_id': '23_phase=5', 'phase': 5},
            {'id': '614626+24_phase=5', 'predictions': 86.4, 'property_id': '614626',
             'recommendation_id': '24_phase=5', 'phase': 5},
            {'id': '614626+25_phase=5', 'predictions': 86.7, 'property_id': '614626',
             'recommendation_id': '25_phase=5', 'phase': 5},
            {'id': '614626+26_phase=5', 'predictions': 86.7, 'property_id': '614626',
             'recommendation_id': '26_phase=5', 'phase': 5},
            {'id': '614626+27_phase=5', 'predictions': 86.7, 'property_id': '614626',
             'recommendation_id': '27_phase=5', 'phase': 5},
            {'id': '614626+28_phase=5', 'predictions': 86.7, 'property_id': '614626',
             'recommendation_id': '28_phase=5', 'phase': 5},
            {'id': '614626+29_phase=5', 'predictions': 83.8, 'property_id': '614626',
             'recommendation_id': '29_phase=5', 'phase': 5},
            {'id': '614626+30_phase=5', 'predictions': 85.4, 'property_id': '614626',
             'recommendation_id': '30_phase=5', 'phase': 5},
            {'id': '614626+31_phase=5', 'predictions': 85.4, 'property_id': '614626',
             'recommendation_id': '31_phase=5', 'phase': 5},
            {'id': '614626+32_phase=5', 'predictions': 85.4, 'property_id': '614626',
             'recommendation_id': '32_phase=5', 'phase': 5},
            {'id': '614626+33_phase=5', 'predictions': 86.4, 'property_id': '614626',
             'recommendation_id': '33_phase=5', 'phase': 5},
            {'id': '614626+34_phase=5', 'predictions': 86.4, 'property_id': '614626',
             'recommendation_id': '34_phase=5', 'phase': 5},
            {'id': '614626+35_phase=5', 'predictions': 86.4, 'property_id': '614626',
             'recommendation_id': '35_phase=5', 'phase': 5},
            {'id': '614626+36_phase=5', 'predictions': 86.4, 'property_id': '614626',
             'recommendation_id': '36_phase=5', 'phase': 5},
            {'id': '614626+37_phase=5', 'predictions': 81.2, 'property_id': '614626',
             'recommendation_id': '37_phase=5', 'phase': 5},
            {'id': '614626+38_phase=5', 'predictions': 81.2, 'property_id': '614626',
             'recommendation_id': '38_phase=5', 'phase': 5},
            {'id': '614626+39_phase=5', 'predictions': 81.2, 'property_id': '614626',
             'recommendation_id': '39_phase=5', 'phase': 5},
            {'id': '614626+40_phase=5', 'predictions': 83.4, 'property_id': '614626',
             'recommendation_id': '40_phase=5', 'phase': 5},
            {'id': '614626+41_phase=5', 'predictions': 83.4, 'property_id': '614626',
             'recommendation_id': '41_phase=5', 'phase': 5},
            {'id': '614626+42_phase=5', 'predictions': 83.4, 'property_id': '614626',
             'recommendation_id': '42_phase=5', 'phase': 5},
            {'id': '614626+43_phase=5', 'predictions': 83.4, 'property_id': '614626',
             'recommendation_id': '43_phase=5', 'phase': 5},
            {'id': '614626+44_phase=5', 'predictions': 85.5, 'property_id': '614626',
             'recommendation_id': '44_phase=5', 'phase': 5},
            {'id': '614626+45_phase=5', 'predictions': 85.5, 'property_id': '614626',
             'recommendation_id': '45_phase=5', 'phase': 5},
            {'id': '614626+46_phase=5', 'predictions': 85.5, 'property_id': '614626',
             'recommendation_id': '46_phase=5', 'phase': 5},
            {'id': '614626+47_phase=5', 'predictions': 85.4, 'property_id': '614626',
             'recommendation_id': '47_phase=5', 'phase': 5},
            {'id': '614626+48_phase=5', 'predictions': 85.4, 'property_id': '614626',
             'recommendation_id': '48_phase=5', 'phase': 5},
            {'id': '614626+49_phase=5', 'predictions': 85.4, 'property_id': '614626',
             'recommendation_id': '49_phase=5', 'phase': 5},
            {'id': '614626+50_phase=5', 'predictions': 85.4, 'property_id': '614626',
             'recommendation_id': '50_phase=5', 'phase': 5},
            {'id': '614626+51_phase=5', 'predictions': 85.4, 'property_id': '614626',
             'recommendation_id': '51_phase=5', 'phase': 5},
            {'id': '614626+52_phase=5', 'predictions': 85.4, 'property_id': '614626',
             'recommendation_id': '52_phase=5', 'phase': 5},
            {'id': '614626+53_phase=5', 'predictions': 85.4, 'property_id': '614626',
             'recommendation_id': '53_phase=5', 'phase': 5},
            {'id': '614626+54_phase=5', 'predictions': 85.4, 'property_id': '614626',
             'recommendation_id': '54_phase=5', 'phase': 5},
            {'id': '614626+55_phase=5', 'predictions': 79.4, 'property_id': '614626',
             'recommendation_id': '55_phase=5', 'phase': 5},
            {'id': '614626+56_phase=5', 'predictions': 81.2, 'property_id': '614626',
             'recommendation_id': '56_phase=5', 'phase': 5},
            {'id': '614626+57_phase=5', 'predictions': 81.2, 'property_id': '614626',
             'recommendation_id': '57_phase=5', 'phase': 5}]
    )

    all_predictions = {
        "sap_change_predictions": sap_change_predictions,
        "heat_demand_predictions": heat_demand_predictions,
        "carbon_change_predictions": carbon_predictions,
        "hotwater_kwh_predictions": pd.DataFrame([]),
        "heating_kwh_predictions": pd.DataFrame([]),
    }

    recommendations = {
        614626: [
            [
                {
                    'phase': 0, 'type': 'loft_insulation',
                    'measure_type': 'loft_insulation',
                    'sap_points': 0,
                    'already_installed': False,
                    'contingency_rate': 0.1, 'labour_hours': 8, 'labour_days': 1, 'survey': False,
                    'innovation_rate': 0.0,
                    'recommendation_id': '0_phase=0', 'efficiency': np.float64(6052.801176470587),
                    'co2_equivalent_savings': np.float64(0.19999999999999973),
                    'heat_demand': np.float64(27.399999999999977)},
            ],
            [
                {
                    'phase': 1,
                    'type': 'mechanical_ventilation', 'measure_type': 'mechanical_ventilation',
                    'already_installed': False,
                    'sap_points': np.float64(-1.4000000000000057),
                    'heat_demand': np.float64(-6.5), 'kwh_savings': 0, 'co2_equivalent_savings': np.float64(0.0),
                    'energy_cost_savings': 0, 'total': 560.0, 'labour_hours': 8, 'labour_days': 1.0,
                    'innovation_rate': 0.0,
                    'recommendation_id': '3_phase=1', 'efficiency': 0}
            ],
            [
                {'phase': 2, 'type': 'low_energy_lighting', 'measure_type': 'low_energy_lighting',
                 'new_u_value': None,
                 'already_installed': False, 'sap_points': 1, 'kwh_savings': 164.25,
                 'energy_cost_savings': 45.480824999999996, 'co2_equivalent_savings': np.float64(0.0),
                 'contingency_rate': 0.26, 'labour_hours': 1, 'labour_days': 0.125, 'survey': True,
                 'innovation_rate': 0.0,
                 'recommendation_id': '4_phase=2', 'efficiency': 10.5, 'heat_demand': np.float64(4.100000000000023)}
            ],
            [
                {
                    'type': 'heating', 'measure_type': 'roomstat_programmer_trvs', 'phase': 3,
                    'total': 70,
                    'contingency': 7.0, 'contingency_rate': 0.1, 'subtotal': 58.333333333333336,
                    'vat': 11.666666666666664,
                    'labour_hours': 0.5, 'labour_days': 1, 'starting_u_value': None, 'new_u_value': None,
                    'sap_points': np.float64(1.0), 'already_installed': False,
                    'recommendation_id': '5_phase=3', 'efficiency': 70,
                    'co2_equivalent_savings': np.float64(0.10000000000000009), 'heat_demand': np.float64(8.5)
                },
                {
                    'type': 'heating', 'phase': 3, 'measure_type': 'time_temperature_zone_control',
                    'total': 604.5840000000001, 'contingency': 60.45840000000001, 'contingency_rate': 0.1,
                    'subtotal': 571.32,
                    'vat': 33.264, 'labour_hours': 3.08, 'labour_days': np.float64(1.0), 'starting_u_value': None,
                    'new_u_value': None, 'sap_points': np.float64(1.8), 'already_installed': False,
                    'recommendation_id': '6_phase=3',
                    'co2_equivalent_savings': np.float64(0.10000000000000009),
                    'heat_demand': np.float64(13.300000000000011)
                },
                {
                    'phase': 3, 'type': 'heating', 'measure_type': 'air_source_heat_pump',
                    'starting_u_value': None, 'new_u_value': None, 'sap_points': np.float64(3.8),
                    'already_installed': False,
                    'total': 17144.924,
                    'contingency': 4195.5434000000005, 'contingency_rate': 0.35, 'vat': 33.264, 'labour_hours': 83.08,
                    'labour_days': np.float64(11.0), 'innovation_rate': 0, 'recommendation_id': '7_phase=3',
                    'efficiency': 17144.924, 'co2_equivalent_savings': np.float64(0.8000000000000003),
                    'heat_demand': np.float64(59.30000000000001)
                }
            ],
            [
                {'phase': 4, 'type': 'secondary_heating', 'measure_type': 'secondary_heating',
                 'starting_u_value': None, 'new_u_value': None,
                 'sap_points': np.float64(0.0), 'already_installed': False, 'total': 60.0, 'contingency': 6.0,
                 'contingency_rate': 0.1, 'subtotal': 50.0, 'vat': 10.0, 'labour_hours': 6.0,
                 'labour_days': np.float64(1.0),
                 'recommendation_id': '8_phase=4', 'efficiency': 60.0, 'co2_equivalent_savings': np.float64(0.0),
                 'heat_demand': np.float64(0.0)}
            ],
            [
                {
                    'phase': 5, 'type': 'solar_pv',
                    'measure_type': 'solar_pv',
                    'starting_u_value': None, 'new_u_value': None,
                    'sap_points': np.float64(16.0), 'already_installed': False,
                    'total': 5892.21, 'subtotal': 5892.21, 'contingency': 883.8315,
                    'contingency_rate': 0.15, 'vat': 0, 'labour_hours': 48,
                    'labour_days': 2, 'has_battery': False,
                    'initial_ac_kwh_per_year': np.float64(4844.465553999999),
                    'innovation_rate': 0.0, 'recommendation_id': '29_phase=5',
                    'efficiency': np.float64(368.263125)
                }
            ]
        ]
    }

    representative_recommendations = {
        614626: [
            {
                'phase': 0, 'type': 'loft_insulation',
                'measure_type': 'loft_insulation',
                'starting_u_value': np.float64(0.17), 'new_u_value': np.float64(0.14), 'sap_points': 0,
                'already_installed': False,
                'contingency_rate': 0.1, 'labour_hours': 8, 'labour_days': 1, 'survey': False,
                'innovation_rate': 0.0,
                'recommendation_id': '0_phase=0', 'efficiency': np.float64(6052.801176470587),
                'co2_equivalent_savings': np.float64(0.19999999999999973),
                'heat_demand': np.float64(27.399999999999977)
            },
            {
                'phase': 1,
                'type': 'mechanical_ventilation',
                'measure_type': 'mechanical_ventilation',
                'starting_u_value': None, 'new_u_value': None,
                'already_installed': False,
                'sap_points': np.float64(-1.4000000000000057),
                'heat_demand': np.float64(-6.5), 'kwh_savings': 0,
                'co2_equivalent_savings': np.float64(0.0),
                'energy_cost_savings': 0, 'total': 560.0,
                'labour_hours': 8, 'labour_days': 1.0,
                'recommendation_id': '3_phase=1', 'efficiency': 0},
            {
                'phase': 2, 'type': 'low_energy_lighting', 'measure_type': 'low_energy_lighting',
                'starting_u_value': None,
                'new_u_value': None, 'already_installed': False, 'sap_points': 1, 'kwh_savings': 164.25,
                'energy_cost_savings': 45.480824999999996, 'co2_equivalent_savings': np.float64(0.0),
                'contingency_rate': 0.26, 'labour_hours': 1, 'labour_days': 0.125, 'survey': True,
                'innovation_rate': 0.0, 'recommendation_id': '4_phase=2', 'efficiency': 10.5,
                'heat_demand': np.float64(4.100000000000023)
            },
            {
                'type': 'heating', 'measure_type': 'roomstat_programmer_trvs', 'phase': 3,
                'total': 70,
                'contingency': 7.0, 'contingency_rate': 0.1, 'subtotal': 58.333333333333336,
                'vat': 11.666666666666664, 'labour_hours': 0.5, 'labour_days': 1, 'starting_u_value': None,
                'new_u_value': None, 'sap_points': np.float64(1.0), 'already_installed': False,
                'recommendation_id': '5_phase=3', 'efficiency': 70,
                'co2_equivalent_savings': np.float64(0.10000000000000009), 'heat_demand': np.float64(8.5)
            },
            {
                'phase': 4, 'type': 'secondary_heating', 'measure_type': 'secondary_heating',
                'starting_u_value': None, 'new_u_value': None,
                'sap_points': np.float64(0.0), 'already_installed': False, 'total': 60.0, 'contingency': 6.0,
                'contingency_rate': 0.1, 'subtotal': 50.0, 'vat': 10.0, 'labour_hours': 6.0,
                'recommendation_id': '8_phase=4', 'efficiency': 60.0, 'co2_equivalent_savings': np.float64(0.0),
                'heat_demand': np.float64(0.0)},
            {
                'phase': 5, 'type': 'solar_pv',
                'measure_type': 'solar_pv',
                'starting_u_value': None, 'new_u_value': None,
                'sap_points': np.float64(16.0), 'already_installed': False,
                'total': 5892.21, 'subtotal': 5892.21, 'contingency': 883.8315,
                'contingency_rate': 0.15, 'vat': 0, 'labour_hours': 48,
                'labour_days': 2, 'has_battery': False,
                'recommendation_id': '29_phase=5',
            }
        ]
    }

    recommendations_with_impact, impact_summary, adjustments = (
        Recommendations.calculate_recommendation_impact(
            property_instance=property_instance,
            all_predictions=all_predictions,
            recommendations=recommendations,
            representative_recommendations=representative_recommendations,
            debug=True
        )
    )

    # We expect an adjustment to be made for loft insulation, reducing the impact by
    # 1.7
    assert adjustments == [{'recommendation_id': '0_phase=0', 'phase': 0, 'sap_adjustment': np.float64(1.7)}]

    # We expect that adjustment to flow through to the final recommendation so that the solar recommendation has
    # a 1.7 sap point reduction in impact

    final_impact_summary = impact_summary[-1]
    assert float(final_impact_summary["sap"]) == 82.1
    assert float(final_impact_summary["sap_prediction"]) == 83.8
    assert final_impact_summary["measure_type"] == "solar_pv"
    assert recommendations_with_impact[0][0]["sap_points"] == 0


def test_lighting_and_loft_adjustment_combined(property_instance, heat_demand_predictions, carbon_predictions):
    ########################
    # Case 2
    ########################
    # Example case with both a loft insulation and lighting adjustment
    # lighting now has a SAP point impact of 5 - the affected recommendation is
    # recommendation_id=4_phase=2
    all_predictions2 = {
        "sap_change_predictions": pd.DataFrame(
            [
                {'id': '614626+0_phase=0', 'predictions': 66.7, 'property_id': '614626',
                 'recommendation_id': '0_phase=0',
                 'phase': 0},
                {'id': '614626+1_phase=0', 'predictions': 66.7, 'property_id': '614626',
                 'recommendation_id': '1_phase=0',
                 'phase': 0},
                {'id': '614626+2_phase=0', 'predictions': 66.7, 'property_id': '614626',
                 'recommendation_id': '2_phase=0',
                 'phase': 0},
                {'id': '614626+3_phase=1', 'predictions': 65.3, 'property_id': '614626',
                 'recommendation_id': '3_phase=1',
                 'phase': 1},
                {'id': '614626+4_phase=2', 'predictions': 71.3, 'property_id': '614626',
                 'recommendation_id': '4_phase=2',
                 'phase': 2},
                {'id': '614626+5_phase=3', 'predictions': 72.3, 'property_id': '614626',
                 'recommendation_id': '5_phase=3',
                 'phase': 3},
                {'id': '614626+6_phase=3', 'predictions': 73.1, 'property_id': '614626',
                 'recommendation_id': '6_phase=3',
                 'phase': 3},
                {'id': '614626+7_phase=3', 'predictions': 75.1, 'property_id': '614626',
                 'recommendation_id': '7_phase=3',
                 'phase': 3},
                {'id': '614626+8_phase=4', 'predictions': 72.3, 'property_id': '614626',
                 'recommendation_id': '8_phase=4',
                 'phase': 4},
                {'id': '614626+9_phase=5', 'predictions': 90.3, 'property_id': '614626',
                 'recommendation_id': '9_phase=5',
                 'phase': 5}, {'id': '614626+10_phase=5', 'predictions': 85.3, 'property_id': '614626',
                               'recommendation_id': '10_phase=5', 'phase': 5},
                {'id': '614626+11_phase=5', 'predictions': 90.3, 'property_id': '614626',
                 'recommendation_id': '11_phase=5', 'phase': 5},
                {'id': '614626+12_phase=5', 'predictions': 90.5, 'property_id': '614626',
                 'recommendation_id': '12_phase=5', 'phase': 5},
                {'id': '614626+13_phase=5', 'predictions': 90.5, 'property_id': '614626',
                 'recommendation_id': '13_phase=5', 'phase': 5},
                {'id': '614626+14_phase=5', 'predictions': 90.5, 'property_id': '614626',
                 'recommendation_id': '14_phase=5', 'phase': 5},
                {'id': '614626+15_phase=5', 'predictions': 90.5, 'property_id': '614626',
                 'recommendation_id': '15_phase=5', 'phase': 5},
                {'id': '614626+16_phase=5', 'predictions': 90.5, 'property_id': '614626',
                 'recommendation_id': '16_phase=5', 'phase': 5},
                {'id': '614626+17_phase=5', 'predictions': 90.5, 'property_id': '614626',
                 'recommendation_id': '17_phase=5', 'phase': 5},
                {'id': '614626+18_phase=5', 'predictions': 90.5, 'property_id': '614626',
                 'recommendation_id': '18_phase=5', 'phase': 5},
                {'id': '614626+19_phase=5', 'predictions': 91.4, 'property_id': '614626',
                 'recommendation_id': '19_phase=5', 'phase': 5},
                {'id': '614626+20_phase=5', 'predictions': 91.4, 'property_id': '614626',
                 'recommendation_id': '20_phase=5', 'phase': 5},
                {'id': '614626+21_phase=5', 'predictions': 91.4, 'property_id': '614626',
                 'recommendation_id': '21_phase=5', 'phase': 5},
                {'id': '614626+22_phase=5', 'predictions': 91.4, 'property_id': '614626',
                 'recommendation_id': '22_phase=5', 'phase': 5},
                {'id': '614626+23_phase=5', 'predictions': 91.4, 'property_id': '614626',
                 'recommendation_id': '23_phase=5', 'phase': 5},
                {'id': '614626+24_phase=5', 'predictions': 91.4, 'property_id': '614626',
                 'recommendation_id': '24_phase=5', 'phase': 5},
                {'id': '614626+25_phase=5', 'predictions': 91.7, 'property_id': '614626',
                 'recommendation_id': '25_phase=5', 'phase': 5},
                {'id': '614626+26_phase=5', 'predictions': 91.7, 'property_id': '614626',
                 'recommendation_id': '26_phase=5', 'phase': 5},
                {'id': '614626+27_phase=5', 'predictions': 91.7, 'property_id': '614626',
                 'recommendation_id': '27_phase=5', 'phase': 5},
                {'id': '614626+28_phase=5', 'predictions': 91.7, 'property_id': '614626',
                 'recommendation_id': '28_phase=5', 'phase': 5},
                {'id': '614626+29_phase=5', 'predictions': 88.8, 'property_id': '614626',
                 'recommendation_id': '29_phase=5', 'phase': 5},
                {'id': '614626+30_phase=5', 'predictions': 90.4, 'property_id': '614626',
                 'recommendation_id': '30_phase=5', 'phase': 5},
                {'id': '614626+31_phase=5', 'predictions': 90.4, 'property_id': '614626',
                 'recommendation_id': '31_phase=5', 'phase': 5},
                {'id': '614626+32_phase=5', 'predictions': 90.4, 'property_id': '614626',
                 'recommendation_id': '32_phase=5', 'phase': 5},
                {'id': '614626+33_phase=5', 'predictions': 91.4, 'property_id': '614626',
                 'recommendation_id': '33_phase=5', 'phase': 5},
                {'id': '614626+34_phase=5', 'predictions': 91.4, 'property_id': '614626',
                 'recommendation_id': '34_phase=5', 'phase': 5},
                {'id': '614626+35_phase=5', 'predictions': 91.4, 'property_id': '614626',
                 'recommendation_id': '35_phase=5', 'phase': 5},
                {'id': '614626+36_phase=5', 'predictions': 91.4, 'property_id': '614626',
                 'recommendation_id': '36_phase=5', 'phase': 5},
                {'id': '614626+37_phase=5', 'predictions': 86.2, 'property_id': '614626',
                 'recommendation_id': '37_phase=5', 'phase': 5},
                {'id': '614626+38_phase=5', 'predictions': 86.2, 'property_id': '614626',
                 'recommendation_id': '38_phase=5', 'phase': 5},
                {'id': '614626+39_phase=5', 'predictions': 86.2, 'property_id': '614626',
                 'recommendation_id': '39_phase=5', 'phase': 5},
                {'id': '614626+40_phase=5', 'predictions': 88.4, 'property_id': '614626',
                 'recommendation_id': '40_phase=5', 'phase': 5},
                {'id': '614626+41_phase=5', 'predictions': 88.4, 'property_id': '614626',
                 'recommendation_id': '41_phase=5', 'phase': 5},
                {'id': '614626+42_phase=5', 'predictions': 88.4, 'property_id': '614626',
                 'recommendation_id': '42_phase=5', 'phase': 5},
                {'id': '614626+43_phase=5', 'predictions': 88.4, 'property_id': '614626',
                 'recommendation_id': '43_phase=5', 'phase': 5},
                {'id': '614626+44_phase=5', 'predictions': 90.5, 'property_id': '614626',
                 'recommendation_id': '44_phase=5', 'phase': 5},
                {'id': '614626+45_phase=5', 'predictions': 90.5, 'property_id': '614626',
                 'recommendation_id': '45_phase=5', 'phase': 5},
                {'id': '614626+46_phase=5', 'predictions': 90.5, 'property_id': '614626',
                 'recommendation_id': '46_phase=5', 'phase': 5},
                {'id': '614626+47_phase=5', 'predictions': 90.4, 'property_id': '614626',
                 'recommendation_id': '47_phase=5', 'phase': 5},
                {'id': '614626+48_phase=5', 'predictions': 90.4, 'property_id': '614626',
                 'recommendation_id': '48_phase=5', 'phase': 5},
                {'id': '614626+49_phase=5', 'predictions': 90.4, 'property_id': '614626',
                 'recommendation_id': '49_phase=5', 'phase': 5},
                {'id': '614626+50_phase=5', 'predictions': 90.4, 'property_id': '614626',
                 'recommendation_id': '50_phase=5', 'phase': 5},
                {'id': '614626+51_phase=5', 'predictions': 90.4, 'property_id': '614626',
                 'recommendation_id': '51_phase=5', 'phase': 5},
                {'id': '614626+52_phase=5', 'predictions': 90.4, 'property_id': '614626',
                 'recommendation_id': '52_phase=5', 'phase': 5},
                {'id': '614626+53_phase=5', 'predictions': 90.4, 'property_id': '614626',
                 'recommendation_id': '53_phase=5', 'phase': 5},
                {'id': '614626+54_phase=5', 'predictions': 90.4, 'property_id': '614626',
                 'recommendation_id': '54_phase=5', 'phase': 5},
                {'id': '614626+55_phase=5', 'predictions': 84.4, 'property_id': '614626',
                 'recommendation_id': '55_phase=5', 'phase': 5},
                {'id': '614626+56_phase=5', 'predictions': 86.2, 'property_id': '614626',
                 'recommendation_id': '56_phase=5', 'phase': 5},
                {'id': '614626+57_phase=5', 'predictions': 86.2, 'property_id': '614626',
                 'recommendation_id': '57_phase=5', 'phase': 5}]
        ),
        "heat_demand_predictions": heat_demand_predictions,
        "carbon_change_predictions": carbon_predictions,
        "hotwater_kwh_predictions": pd.DataFrame([]),
        "heating_kwh_predictions": pd.DataFrame([]),
    }

    recommendations2 = {
        614626: [
            [
                {
                    'phase': 0,
                    'type': 'loft_insulation',
                    'measure_type': 'loft_insulation',
                    'sap_points': 0,
                    'survey': False,
                    'innovation_rate': 0.0,
                    'recommendation_id': '0_phase=0', 'efficiency': np.float64(6052.801176470587),
                    'co2_equivalent_savings': np.float64(0.19999999999999973),
                    'heat_demand': np.float64(27.399999999999977)},
            ],
            [
                {
                    'phase': 1,
                    'type': 'mechanical_ventilation', 'measure_type': 'mechanical_ventilation',
                    'starting_u_value': None,
                    'new_u_value': None, 'already_installed': False, 'sap_points': np.float64(-1.4000000000000057),
                    'heat_demand': np.float64(-6.5), 'kwh_savings': 0, 'co2_equivalent_savings': np.float64(0.0),
                    'energy_cost_savings': 0, 'total': 560.0, 'labour_hours': 8, 'labour_days': 1.0,
                    'recommendation_id': '3_phase=1',
                }
            ],
            [
                {
                    'phase': 2, 'type': 'low_energy_lighting', 'measure_type': 'low_energy_lighting',
                    'new_u_value': None,
                    'already_installed': False, 'sap_points': 5, 'kwh_savings': 164.25,
                    'energy_cost_savings': 45.480824999999996, 'co2_equivalent_savings': np.float64(0.0),
                    'total': 10.5, 'contingency': 2.73,
                    'contingency_rate': 0.26, 'labour_hours': 1, 'labour_days': 0.125, 'survey': True,
                    'innovation_rate': 0.0,
                    'recommendation_id': '4_phase=2', 'efficiency': 10.5, 'heat_demand': np.float64(4.100000000000023)}
            ],
            [
                {
                    'type': 'heating', 'measure_type': 'roomstat_programmer_trvs', 'phase': 3,
                    'total': 70,
                    'contingency': 7.0, 'contingency_rate': 0.1, 'subtotal': 58.333333333333336,
                    'vat': 11.666666666666664,
                    'labour_hours': 0.5, 'labour_days': 1, 'starting_u_value': None, 'new_u_value': None,
                    'sap_points': np.float64(1.0), 'already_installed': False,
                    'recommendation_id': '5_phase=3',
                    'co2_equivalent_savings': np.float64(0.10000000000000009), 'heat_demand': np.float64(8.5)},
                {
                    'type': 'heating', 'phase': 3, 'measure_type': 'time_temperature_zone_control',
                    'total': 604.5840000000001, 'contingency': 60.45840000000001, 'contingency_rate': 0.1,
                    'subtotal': 571.32,
                    'vat': 33.264, 'labour_hours': 3.08, 'labour_days': np.float64(1.0), 'starting_u_value': None,
                    'new_u_value': None, 'sap_points': np.float64(1.8), 'already_installed': False,
                    'innovation_rate': 0.0,
                    'recommendation_id': '6_phase=3', 'efficiency': 604.5840000000001,
                    'co2_equivalent_savings': np.float64(0.10000000000000009),
                    'heat_demand': np.float64(13.300000000000011)},
                {
                    'phase': 3, 'type': 'heating', 'measure_type': 'air_source_heat_pump',
                    'starting_u_value': None, 'new_u_value': None, 'sap_points': np.float64(3.8),
                    'already_installed': False,
                    'total': 17144.924,
                    'contingency': 4195.5434000000005, 'contingency_rate': 0.35, 'vat': 33.264, 'labour_hours': 83.08,
                    'labour_days': np.float64(11.0), 'innovation_rate': 0, 'recommendation_id': '7_phase=3',
                    'efficiency': 17144.924, 'co2_equivalent_savings': np.float64(0.8000000000000003),
                    'heat_demand': np.float64(59.30000000000001)}
            ],
            [
                {
                    'phase': 4, 'type': 'secondary_heating', 'measure_type': 'secondary_heating',
                    'starting_u_value': None, 'new_u_value': None,
                    'sap_points': np.float64(0.0), 'already_installed': False, 'total': 60.0, 'contingency': 6.0,
                    'contingency_rate': 0.1, 'subtotal': 50.0, 'vat': 10.0, 'labour_hours': 6.0,
                    'labour_days': np.float64(1.0), 'innovation_rate': 0.0,
                    'recommendation_id': '8_phase=4', 'efficiency': 60.0, 'co2_equivalent_savings': np.float64(0.0),
                    'heat_demand': np.float64(0.0)
                }
            ],
            [
                {
                    'phase': 5, 'type': 'solar_pv',
                    'measure_type': 'solar_pv',
                    'starting_u_value': None, 'new_u_value': None,
                    'sap_points': np.float64(16.0), 'already_installed': False,
                    'total': 5892.21, 'subtotal': 5892.21, 'contingency': 883.8315,
                    'contingency_rate': 0.15, 'vat': 0, 'labour_hours': 48,
                    'labour_days': 2, 'has_battery': False,
                    'innovation_rate': 0.0, 'recommendation_id': '29_phase=5',
                    'efficiency': np.float64(368.263125)
                }
            ]
        ]
    }

    representative_recommendations2 = {
        614626: [
            {
                'phase': 0,
                'type': 'loft_insulation',
                'measure_type': 'loft_insulation',
                'sap_points': 0,
                'survey': False,
                'recommendation_id': '0_phase=0',
            },
            {
                'phase': 1,
                'type': 'mechanical_ventilation',
                'measure_type': 'mechanical_ventilation',
                'sap_points': np.float64(-1.4000000000000057),
                'recommendation_id': '3_phase=1'
            },
            {
                'phase': 2,
                'type': 'low_energy_lighting',
                'measure_type': 'low_energy_lighting',
                'sap_points': 5,
                'survey': True,
                'recommendation_id': '4_phase=2',
            },
            {
                'type': 'heating',
                'measure_type': 'roomstat_programmer_trvs',
                'phase': 3,
                'sap_points': np.float64(1.0),
                'recommendation_id': '5_phase=3',
            },
            {
                'phase': 4,
                'type': 'secondary_heating',
                'measure_type': 'secondary_heating',
                'sap_points': np.float64(0.0),
                'recommendation_id': '8_phase=4',
            },
            {
                'phase': 5,
                'type': 'solar_pv',
                'measure_type': 'solar_pv',
                'sap_points': np.float64(16.0),
                'recommendation_id': '29_phase=5',
            }
        ]
    }

    recommendations_with_impact2, impact_summary2, adjustments2 = (
        Recommendations.calculate_recommendation_impact(
            property_instance=property_instance,
            all_predictions=all_predictions2,
            recommendations=recommendations2,
            representative_recommendations=representative_recommendations2,
            debug=True
        )
    )

    assert adjustments2 == [
        {'recommendation_id': '0_phase=0', 'phase': 0, 'sap_adjustment': np.float64(1.7)},
        {'recommendation_id': '4_phase=2', 'phase': 2, 'sap_adjustment': np.float64(4.0)},
        {'recommendation_id': '5_phase=3', 'phase': 3, 'sap_adjustment': np.float64(1.0)},
        {'recommendation_id': '6_phase=3', 'phase': 3, 'sap_adjustment': np.float64(1.0000000000000027)}
    ]


def test_mechanical_ventilation_sap_floor(property_instance):
    rec = {
        "type": "mechanical_ventilation",
        "recommendation_id": "mv_test",
        "phase": 1,
    }

    previous_phase_values = {"sap": 2.0}
    current_phase_values = {"sap": 0.5}  # model prediction already below 1
    property_phase_impact = {"sap": -1.5, "carbon": 0, "heat_demand": 0}
    adjustments = []

    updated_impact, updated_current, updated_adjustments = (
        Recommendations._apply_measure_specific_rules(
            rec=rec,
            property_phase_impact=property_phase_impact,
            previous_phase_values=previous_phase_values,
            current_phase_values=current_phase_values,
            adjustments=adjustments,
            property_instance=property_instance,
            model_predicted_sap=0
        )
    )

    # SAP should be clamped to minimum 1
    assert updated_current["sap"] == 1.0

    # Original final SAP would have been 0.5 → so adjustment = 1 - 0.5 = 0.5
    assert updated_adjustments == [
        {
            "recommendation_id": "mv_test",
            "phase": 1,
            "sap_adjustment": 0.5,
        }
    ]

    # Impact should now reflect new clamped SAP
    assert updated_impact["sap"] == -1.0  # 2.0 → 1.0


def test_mechanical_ventilation_no_floor_adjustment(property_instance):
    rec = {
        "type": "mechanical_ventilation",
        "recommendation_id": "mv_test",
        "phase": 1,
    }

    previous_phase_values = {"sap": 5.0}
    current_phase_values = {"sap": 3.0}
    property_phase_impact = {"sap": -2.0, "carbon": 0, "heat_demand": 0}
    adjustments = []

    updated_impact, updated_current, updated_adjustments = (
        Recommendations._apply_measure_specific_rules(
            rec=rec,
            property_phase_impact=property_phase_impact,
            previous_phase_values=previous_phase_values,
            current_phase_values=current_phase_values,
            adjustments=adjustments,
            property_instance=property_instance,
            model_predicted_sap=0
        )
    )

    # No adjustment expected
    assert updated_adjustments == []

    # SAP unchanged
    assert updated_current["sap"] == 3.0
    assert updated_impact["sap"] == -2.0


def test_mechanical_ventilation_exactly_one_no_adjustment(property_instance):
    # Test when SAP = 1
    rec = {
        "type": "mechanical_ventilation",
        "recommendation_id": "mv_test",
        "phase": 1,
    }

    previous_phase_values = {"sap": 2.0}
    current_phase_values = {"sap": 1.0}
    property_phase_impact = {"sap": -1.0, "carbon": 0, "heat_demand": 0}
    adjustments = []

    updated_impact, updated_current, updated_adjustments = (
        Recommendations._apply_measure_specific_rules(
            rec=rec,
            property_phase_impact=property_phase_impact,
            previous_phase_values=previous_phase_values,
            current_phase_values=current_phase_values,
            adjustments=adjustments,
            property_instance=property_instance,
            model_predicted_sap=0
        )
    )

    # Exactly 1 → no adjustment
    assert updated_adjustments == []
    assert updated_current["sap"] == 1.0
    assert updated_impact["sap"] == -1.0


def test_mechanical_ventilation_sap_zero_no_adjustment(property_instance):
    # Test when SAP = 0
    rec = {
        "type": "mechanical_ventilation",
        "recommendation_id": "mv_test",
        "phase": 1,
    }

    previous_phase_values = {'phase': 0, 'representative': True, 'recommendation_id': '0_phase=0',
                             'measure_type': 'flat_roof_insulation', 'sap': 68.0, 'carbon': np.float64(0.5),
                             'heat_demand': np.float64(300.1), 'sap_prediction': np.float64(71.7)}
    current_phase_values = {'sap': 68.0, 'carbon': np.float64(0.5), 'heat_demand': np.float64(307.0)}
    property_phase_impact = {'sap': 0, 'carbon': 0, 'heat_demand': np.float64(-6.899999999999977)}
    adjustments = []

    updated_impact, updated_current, updated_adjustments = (
        Recommendations._apply_measure_specific_rules(
            rec=rec,
            property_phase_impact=property_phase_impact,
            previous_phase_values=previous_phase_values,
            current_phase_values=current_phase_values,
            adjustments=adjustments,
            property_instance=property_instance,
            model_predicted_sap=0
        )
    )

    # SAP is already at 0 → no adjustment expected
    assert updated_adjustments == []
    assert updated_current["sap"] == 68.0
    assert updated_impact["sap"] == 0


def test_mv_valid_negative_no_adjustment(property_instance):
    rec = {"type": "mechanical_ventilation", "recommendation_id": "mv", "phase": 1}

    previous = {"sap": 70.0}
    current = {"sap": 67.0}
    impact = {"sap": -3.0, "carbon": 0, "heat_demand": 0}
    adjustments = []

    updated_impact, updated_current, updated_adjustments = (
        Recommendations._apply_measure_specific_rules(
            rec, impact, previous, current, adjustments, property_instance, 0
        )
    )

    assert updated_adjustments == []
    assert updated_current["sap"] == 67.0
    assert updated_impact["sap"] == -3.0


def test_mv_zero_impact_allowed(property_instance):
    rec = {"type": "mechanical_ventilation", "recommendation_id": "mv", "phase": 1}

    previous = {"sap": 68.0, "sap_prediction": 71.7}
    current = {"sap": 68.0}
    impact = {"sap": 0.0, "carbon": 0, "heat_demand": 0}
    adjustments = []

    updated_impact, updated_current, updated_adjustments = (
        Recommendations._apply_measure_specific_rules(
            rec, impact, previous, current, adjustments, property_instance, 0
        )
    )

    assert updated_adjustments == []
    assert updated_current["sap"] == 68.0
    assert updated_impact["sap"] == 0.0


def test_mv_positive_impact_corrected(property_instance):
    rec = {"type": "mechanical_ventilation", "recommendation_id": "mv", "phase": 1}

    previous = {"sap": 60.0}
    current = {"sap": 61.0}
    impact = {"sap": 1.0, "carbon": 0, "heat_demand": 0}
    adjustments = []

    updated_impact, updated_current, updated_adjustments = (
        Recommendations._apply_measure_specific_rules(
            rec, impact, previous, current, adjustments, property_instance, 0
        )
    )

    assert len(updated_adjustments) == 1
    assert updated_current["sap"] == previous["sap"] + updated_impact["sap"]
    assert updated_impact["sap"] <= 0


def test_mv_below_lower_bound_corrected(property_instance):
    rec = {"type": "mechanical_ventilation", "recommendation_id": "mv", "phase": 1}

    previous = {"sap": 70.0}
    current = {"sap": 64.0}
    impact = {"sap": -6.0, "carbon": 0, "heat_demand": 0}
    adjustments = []

    updated_impact, updated_current, updated_adjustments = (
        Recommendations._apply_measure_specific_rules(
            rec, impact, previous, current, adjustments, property_instance, 0
        )
    )

    assert len(updated_adjustments) == 1
    assert updated_impact["sap"] >= -4


def test_mv_floor_triggered(property_instance):
    rec = {"type": "mechanical_ventilation", "recommendation_id": "mv", "phase": 1}

    previous = {"sap": 2.0}
    current = {"sap": 0.5}
    impact = {"sap": -1.5, "carbon": 0, "heat_demand": 0}
    adjustments = []

    updated_impact, updated_current, updated_adjustments = (
        Recommendations._apply_measure_specific_rules(
            rec, impact, previous, current, adjustments, property_instance, 0
        )
    )

    assert updated_current["sap"] == 1.0
    assert updated_adjustments[0]["sap_adjustment"] > 0


def test_mv_exactly_one_no_floor(property_instance):
    rec = {"type": "mechanical_ventilation", "recommendation_id": "mv", "phase": 1}

    previous = {"sap": 2.0}
    current = {"sap": 1.0}
    impact = {"sap": -1.0, "carbon": 0, "heat_demand": 0}
    adjustments = []

    updated_impact, updated_current, updated_adjustments = (
        Recommendations._apply_measure_specific_rules(
            rec, impact, previous, current, adjustments, property_instance, 0
        )
    )

    assert updated_adjustments == []
    assert updated_current["sap"] == 1.0


def test_lighting_no_cap(property_instance):
    rec = {"type": "low_energy_lighting", "recommendation_id": "led", "phase": 1,
           "co2_equivalent_savings": 0}

    previous = {"sap": 60.0, "carbon": 2.0}
    current = {"sap": 61.0, "carbon": 2.0}
    impact = {"sap": 1.0, "carbon": 0, "heat_demand": 0}
    adjustments = []

    updated_impact, updated_current, updated_adjustments = (
        Recommendations._apply_measure_specific_rules(
            rec, impact, previous, current, adjustments, property_instance, 0
        )
    )

    assert updated_adjustments == []


def test_filter_phase_adjustments():
    example_adjustments = [
        {'recommendation_id': '0_phase=0', 'phase': 0, 'sap_adjustment': np.float64(1.7)},
        {'recommendation_id': '4_phase=2', 'phase': 2, 'sap_adjustment': np.float64(4.0)},
        {'recommendation_id': '5_phase=3', 'phase': 3, 'sap_adjustment': np.float64(1.0)},
        {'recommendation_id': '6_phase=3', 'phase': 3, 'sap_adjustment': np.float64(1.0000000000000027)}
    ]

    res = Recommendations._filter_phase_adjustment(example_adjustments)

    assert res == [
        {'recommendation_id': '0_phase=0', 'phase': 0, 'sap_adjustment': np.float64(1.7)},
        {'recommendation_id': '4_phase=2', 'phase': 2, 'sap_adjustment': np.float64(4.0)},
        {'recommendation_id': '6_phase=3', 'phase': 3, 'sap_adjustment': np.float64(1.0000000000000027)}
    ]
