diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 67c84f42..7426882f 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -7,6 +7,9 @@ "qwtel.sqlite-viewer", "yoavbls.pretty-ts-errors", "github.vscode-github-actions", - "ms-vsliveshare.vsliveshare" + "ms-vsliveshare.vsliveshare", + "ms-python.python", + "eamodio.gitlens", + "MS-vsliveshare.vsliveshare" ] } diff --git a/heat-stack/app/components/ui/heat/CaseSummaryComponents/AnalysisHeader.tsx b/heat-stack/app/components/ui/heat/CaseSummaryComponents/AnalysisHeader.tsx index 2ea31225..b82a1b7e 100644 --- a/heat-stack/app/components/ui/heat/CaseSummaryComponents/AnalysisHeader.tsx +++ b/heat-stack/app/components/ui/heat/CaseSummaryComponents/AnalysisHeader.tsx @@ -33,13 +33,13 @@ export function AnalysisHeader({ usage_data }: { usage_data: UsageDataSchema}) { // 3312125.0171753373 // ]]) - // Extract the summary_output from usage_data - const summaryOutputs = usage_data?.summary_output; + // Extract the heat_load_output from usage_data + const summaryOutputs = usage_data?.heat_load_output; - const totalRecords = usage_data?.billing_records?.length || "-" + const totalRecords = usage_data?.processed_energy_bills?.length || "-" // Calculate the number of billing periods included in Heating calculations - const heatingAnalysisTypeRecords = usage_data?.billing_records?.filter( + const heatingAnalysisTypeRecords = usage_data?.processed_energy_bills?.filter( (billingRecord) => billingRecord.analysis_type === 1, // Do wee need this code instead? (billingRecord) => billingRecord.analysis_type !== "NOT_ALLOWED_IN_CALCULATIONS", ); diff --git a/heat-stack/app/components/ui/heat/CaseSummaryComponents/EnergyUseHistoryChart.tsx b/heat-stack/app/components/ui/heat/CaseSummaryComponents/EnergyUseHistoryChart.tsx index 1c440be0..40e3c51c 100644 --- a/heat-stack/app/components/ui/heat/CaseSummaryComponents/EnergyUseHistoryChart.tsx +++ b/heat-stack/app/components/ui/heat/CaseSummaryComponents/EnergyUseHistoryChart.tsx @@ -59,9 +59,9 @@ export function EnergyUseHistoryChart({ usage_data }: { usage_data: UsageDataSch const [billingRecords, setBillingRecords] = useState([]) useEffect(() => { - if (usage_data?.billing_records) { + if (usage_data?.processed_energy_bills) { // Process the billing records directly without converting from Map - setBillingRecords(usage_data.billing_records) + setBillingRecords(usage_data.processed_energy_bills) } }, [usage_data]) diff --git a/heat-stack/app/routes/_heat+/single.tsx b/heat-stack/app/routes/_heat+/single.tsx index 4b3a4c4f..25be9d32 100644 --- a/heat-stack/app/routes/_heat+/single.tsx +++ b/heat-stack/app/routes/_heat+/single.tsx @@ -227,7 +227,7 @@ export async function action({ request, params }: ActionFunctionArgs) { from rules_engine import parser from rules_engine.pydantic_models import ( FuelType, - SummaryInput, + HeatLoadInput, TemperatureInput ) from rules_engine import engine @@ -302,7 +302,7 @@ export async function action({ request, params }: ActionFunctionArgs) { from rules_engine import parser from rules_engine.pydantic_models import ( FuelType, - SummaryInput, + HeatLoadInput, TemperatureInput ) from rules_engine import engine, helpers @@ -313,7 +313,7 @@ export async function action({ request, params }: ActionFunctionArgs) { # two new geocode parameters may be needed for design temp: # watch out for helpers.get_design_temp( addressMatches[0].geographies.counties[0]['STATE'] , addressMatches[0].geographies.counties[0]['COUNTY'] county_id) # in addition to latitude and longitude from GeocodeUtil.ts object . - # pack the get_design_temp output into summary_input + # pack the get_design_temp output into heat_load_input """ summaryInputFromJs = summaryInputJs.as_object_map().values()._mapping @@ -323,7 +323,7 @@ export async function action({ request, params }: ActionFunctionArgs) { naturalGasInputRecords = parser.parse_gas_bill(csvDataJs, parser.NaturalGasCompany.NATIONAL_GRID) design_temp_looked_up = helpers.get_design_temp(state_id, county_id) - summaryInput = SummaryInput( **summaryInputFromJs, design_temperature=design_temp_looked_up) + summaryInput = HeatLoadInput( **summaryInputFromJs, design_temperature=design_temp_looked_up) temperatureInput = TemperatureInput(**temperatureInputFromJs) @@ -335,7 +335,7 @@ export async function action({ request, params }: ActionFunctionArgs) { // type Analytics = z.infer; const foo: any = executeGetAnalyticsFromFormJs(parsedAndValidatedFormSchema, convertedDatesTIWD, uploadedTextFile, state_id, county_id).toJs() - //console.log("foo billing records [0]", foo.get('billing_records')[0] ) + //console.log("foo billing records [0]", foo.get('processed_energy_bills')[0] ) /** * second time and after, when table is modified, this becomes entrypoint @@ -344,22 +344,16 @@ export async function action({ request, params }: ActionFunctionArgs) { from rules_engine import parser from rules_engine.pydantic_models import ( FuelType, - SummaryInput, + HeatLoadInput, TemperatureInput, - NormalizedBillingPeriodRecordBase + ProcessedEnergyBillInput ) from rules_engine import engine, helpers - # def get_outputs_normalized( - # summary_input: SummaryInput, - # dhw_input: Optional[DhwInput], - # temperature_input: TemperatureInput, - # billing_periods: list[NormalizedBillingPeriodRecordBase], - # ) def executeRoundtripAnalyticsFromForm(summaryInputJs, temperatureInputJs, userAdjustedData, state_id, county_id): """ - "billing_records" is the "roundtripping" parameter to be passed as userAdjustedData. + "processed_energy_bills" is the "roundtripping" parameter to be passed as userAdjustedData. """ summaryInputFromJs = summaryInputJs.as_object_map().values()._mapping @@ -367,17 +361,17 @@ export async function action({ request, params }: ActionFunctionArgs) { design_temp_looked_up = helpers.get_design_temp(state_id, county_id) # expect 1 for middlesex county: print("design temp check ",design_temp_looked_up, state_id, county_id) - summaryInput = SummaryInput( **summaryInputFromJs, design_temperature=design_temp_looked_up) + summaryInput = HeatLoadInput( **summaryInputFromJs, design_temperature=design_temp_looked_up) temperatureInput = TemperatureInput(**temperatureInputFromJs) # third step, re-run of the table data - userAdjustedDataFromJsToPython = [NormalizedBillingPeriodRecordBase(**record) for record in userAdjustedData['billing_records'] ] + userAdjustedDataFromJsToPython = [ProcessedEnergyBillInput(**record) for record in userAdjustedData['processed_energy_bills'] ] # print("py", userAdjustedDataFromJsToPython[0]) outputs2 = engine.get_outputs_normalized(summaryInput, None, temperatureInput, userAdjustedDataFromJsToPython) - # print("py2", outputs2.billing_records[0]) + # print("py2", outputs2.processed_energy_bills[0]) return outputs2.model_dump(mode="json") executeRoundtripAnalyticsFromForm `) @@ -390,7 +384,7 @@ Traceback (most recent call last): File "", line 32, */ /* For - 'billing_records' => [ + 'processed_energy_bills' => [ Map(9) { 'period_start_date' => '2020-10-02', 'period_end_date' => '2020-11-04', @@ -403,22 +397,22 @@ Traceback (most recent call last): File "", line 32, 'whole_home_heat_loss_rate' => undefined }, */ - const gasBillDataWithUserAdjustments = foo; /* billing_records is untested here */ + const gasBillDataWithUserAdjustments = foo; /* processed_energy_bills is untested here */ - const billingRecords = foo.get('billing_records') + const billingRecords = foo.get('processed_energy_bills') billingRecords.forEach((record: any) => { record.set('inclusion_override', true); }); - // foo.set('billing_records', null) - // foo.set('billing_records', billingRecords) - //console.log("(after customization) gasBillDataWithUserAdjustments billing records[0]", gasBillDataWithUserAdjustments.get('billing_records')[0]) + // foo.set('processed_energy_bills', null) + // foo.set('processed_energy_bills', billingRecords) + //console.log("(after customization) gasBillDataWithUserAdjustments billing records[0]", gasBillDataWithUserAdjustments.get('processed_energy_bills')[0]) /* why is inclusion_override still false after roundtrip */ const foo2: any = executeRoundtripAnalyticsFromFormJs(parsedAndValidatedFormSchema, convertedDatesTIWD, gasBillDataWithUserAdjustments, state_id, county_id).toJs() - // console.log("foo2 billing records[0]", foo2.get('billing_records')[0]); + // console.log("foo2 billing records[0]", foo2.get('processed_energy_bills')[0]); // console.log("foo2", foo2); - // console.log("(after round trip) gasBillDataWithUserAdjustments billing records[0]", gasBillDataWithUserAdjustments.get('billing_records')[0]) + // console.log("(after round trip) gasBillDataWithUserAdjustments billing records[0]", gasBillDataWithUserAdjustments.get('processed_energy_bills')[0]) // const otherResult = executePy(summaryInput, convertedDatesTIWD, exampleNationalGridCSV); @@ -496,22 +490,22 @@ export default function Inputs() { /** * Where temp1 is a temporary variable with the main Map of Maps (or undefined if page not yet submitted). * - * temp1.get('summary_output'): Map(9) { estimated_balance_point → 61.5, other_fuel_usage → 0.2857142857142857, average_indoor_temperature → 67, difference_between_ti_and_tbp → 5.5, design_temperature → 1, whole_home_heat_loss_rate → 48001.81184312083, standard_deviation_of_heat_loss_rate → 0.08066745182677547, average_heat_load → 3048115.0520381727, maximum_heat_load → 3312125.0171753373 } + * temp1.get('heat_load_output'): Map(9) { estimated_balance_point → 61.5, other_fuel_usage → 0.2857142857142857, average_indoor_temperature → 67, difference_between_ti_and_tbp → 5.5, design_temperature → 1, whole_home_heat_loss_rate → 48001.81184312083, standard_deviation_of_heat_loss_rate → 0.08066745182677547, average_heat_load → 3048115.0520381727, maximum_heat_load → 3312125.0171753373 } */ /* @ts-ignore */ - // console.log("Summary Output", lastResult !== undefined ? JSON.parse(lastResult.data, reviver)?.get('summary_output'): undefined) + // console.log("Summary Output", lastResult !== undefined ? JSON.parse(lastResult.data, reviver)?.get('heat_load_output'): undefined) /** * Where temp1 is a temporary variable with the main Map of Maps (or undefined if page not yet submitted). - * temp1.get('billing_records') + * temp1.get('processed_energy_bills') * Array(25) [ Map(9), Map(9), Map(9), Map(9), Map(9), Map(9), Map(9), Map(9), Map(9), Map(9), … ] - * temp1.get('billing_records')[0] + * temp1.get('processed_energy_bills')[0] * Map(9) { period_start_date → "2020-10-02", period_end_date → "2020-11-04", usage → 29, analysis_type_override → null, inclusion_override → true, analysis_type → 0, default_inclusion_by_calculation → false, eliminated_as_outlier → false, whole_home_heat_loss_rate → null } - * temp1.get('billing_records')[0].get('period_start_date') + * temp1.get('processed_energy_bills')[0].get('period_start_date') * "2020-10-02" */ /* @ts-ignore */ - // console.log("EnergyUseHistoryChart table data", lastResult !== undefined ? JSON.parse(lastResult.data, reviver)?.get('billing_records'): undefined) + // console.log("EnergyUseHistoryChart table data", lastResult !== undefined ? JSON.parse(lastResult.data, reviver)?.get('processed_energy_bills'): undefined) /** * Where temp1 is a temporary variable with the main Map of Maps (or undefined if page not yet submitted). diff --git a/heat-stack/app/utils/pyodide.test.ts b/heat-stack/app/utils/pyodide.test.ts index 04c9e18b..197fce8a 100644 --- a/heat-stack/app/utils/pyodide.test.ts +++ b/heat-stack/app/utils/pyodide.test.ts @@ -142,7 +142,7 @@ test('pyodide solves climate change', async () => { // https://github.com/codeforboston/home-energy-analysis-tool/blob/main/python/src/rules_engine/parser.py#L60 /* 2) get_outputs_natural_gas( - summary_input: SummaryInput, + heat_load_input: HeatLoadInput, temperature_input: TemperatureInput, natural_gas_billing_input: NaturalGasBillingInput, ) -> RulesEngineResult @@ -153,7 +153,7 @@ test('pyodide solves climate change', async () => { /** * TODO: * - Match up our types with rules engine types at https://github.com/codeforboston/home-energy-analysis-tool/blob/main/python/src/rules_engine/pydantic_models.py#L57 - * - Use those to create summary_input (in the same file) + * - Use those to create heat_load_input (in the same file) * - Use those to create temperature_input (in the same file) * - Use those to create natural_gas_billing_input (in the same file) */ @@ -223,7 +223,7 @@ test('pyodide solves climate change', async () => { from rules_engine import parser from rules_engine.pydantic_models import ( FuelType, - SummaryInput, + HeatLoadInput, TemperatureInput ) from rules_engine import engine @@ -236,7 +236,7 @@ test('pyodide solves climate change', async () => { naturalGasInputRecords = parser.parse_gas_bill(csvDataJs, parser.NaturalGasCompany.NATIONAL_GRID) - summaryInput = SummaryInput(**summaryInputFromJs) + summaryInput = HeatLoadInput(**summaryInputFromJs) temperatureInput = TemperatureInput(**temperatureInputFromJs) outputs = engine.get_outputs_natural_gas(summaryInput,temperatureInput, naturalGasInputRecords) @@ -275,7 +275,7 @@ test('pyodide solves climate change', async () => { console.log(result.toJs().get('balance_point_graph')) /* prettify-ignore */ - const expectedRecordsToGoInTheTable = '{"dataType":"Map","value":[["summary_output",{"dataType":"Map","value":[["estimated_balance_point",59.5],["other_fuel_usage",8.666666666666666],["average_indoor_temperature",68],["difference_between_ti_and_tbp",8.5],["design_temperature",9.5],["whole_home_heat_loss_rate",47972.03453453454],["standard_deviation_of_heat_loss_rate",0.07742772585617895],["average_heat_load",2494545.795795796],["maximum_heat_load",2902308.0893393396]]}],["balance_point_graph",{"dataType":"Map","value":[["records",[{"dataType":"Map","value":[["balance_point",60],["heat_loss_rate",41034.85407876231],["change_in_heat_loss_rate",0],["percent_change_in_heat_loss_rate",0],["standard_deviation",0.3967358807600794]]},{"dataType":"Map","value":[["balance_point",60.5],["heat_loss_rate",38592.303240740745],["change_in_heat_loss_rate",-2442.550838021569],["percent_change_in_heat_loss_rate",-6.32911392405064],["standard_deviation",0.39673588076007943]]},{"dataType":"Map","value":[["balance_point",59.5],["heat_loss_rate",43807.47935435436],["change_in_heat_loss_rate",2772.625275592043],["percent_change_in_heat_loss_rate",6.329113924050622],["standard_deviation",0.3967358807600795]]},{"dataType":"Map","value":[["balance_point",60.5],["heat_loss_rate",42226.71012849585],["change_in_heat_loss_rate",-2672.576590411132],["percent_change_in_heat_loss_rate",-6.329113924050639],["standard_deviation",0.30164495413734566]]},{"dataType":"Map","value":[["balance_point",59.5],["heat_loss_rate",47933.022308022315],["change_in_heat_loss_rate",3033.7355891153347],["percent_change_in_heat_loss_rate",6.3291139240506284],["standard_deviation",0.3016449541373457]]},{"dataType":"Map","value":[["balance_point",60.5],["heat_loss_rate",46671.62698412699],["change_in_heat_loss_rate",-2953.9004420333513],["percent_change_in_heat_loss_rate",-6.329113924050628],["standard_deviation",0.15298851745396608]]},{"dataType":"Map","value":[["balance_point",59.5],["heat_loss_rate",52978.60360360361],["change_in_heat_loss_rate",3353.0761774432685],["percent_change_in_heat_loss_rate",6.329113924050636],["standard_deviation",0.1529885174539661]]},{"dataType":"Map","value":[["balance_point",60.5],["heat_loss_rate",44137.56613756614],["change_in_heat_loss_rate",-2793.516844149759],["percent_change_in_heat_loss_rate",-6.329113924050642],["standard_deviation",0.10782787516463575]]},{"dataType":"Map","value":[["balance_point",59.5],["heat_loss_rate",50102.10210210211],["change_in_heat_loss_rate",3171.0191203862123],["percent_change_in_heat_loss_rate",6.329113924050639],["standard_deviation",0.10782787516463574]]},{"dataType":"Map","value":[["balance_point",59.5],["heat_loss_rate",50102.10210210211],["change_in_heat_loss_rate",3171.0191203862123],["percent_change_in_heat_loss_rate",6.329113924050639],["standard_deviation",0.10782787516463574]]},{"dataType":"Map","value":[["balance_point",59],["heat_loss_rate",53732.689210950084],["change_in_heat_loss_rate",3630.587108847976],["percent_change_in_heat_loss_rate",6.756756756756753],["standard_deviation",0.10782787516463577]]},{"dataType":"Map","value":[["balance_point",60],["heat_loss_rate",44935.829817158934],["change_in_heat_loss_rate",-3036.2047173756073],["percent_change_in_heat_loss_rate",-6.756756756756764],["standard_deviation",0.07742772585617896]]},{"dataType":"Map","value":[["balance_point",59],["heat_loss_rate",51448.26892109501],["change_in_heat_loss_rate",3476.2343865604707],["percent_change_in_heat_loss_rate",6.756756756756752],["standard_deviation",0.07742772585617896]]}]]]}],["billing_records",[{"dataType":"Map","value":[["period_start_date","2020-10-02"],["period_end_date","2020-11-04"],["usage",29],["analysis_type_override",null],["inclusion_override",false],["analysis_type",0],["default_inclusion_by_calculation",false],["eliminated_as_outlier",false]]},{"dataType":"Map","value":[["period_start_date","2020-11-05"],["period_end_date","2020-12-03"],["usage",36],["analysis_type_override",null],["inclusion_override",false],["analysis_type",1],["default_inclusion_by_calculation",true],["eliminated_as_outlier",true]]},{"dataType":"Map","value":[["period_start_date","2020-12-04"],["period_end_date","2021-01-07"],["usage",97],["analysis_type_override",null],["inclusion_override",false],["analysis_type",1],["default_inclusion_by_calculation",true],["eliminated_as_outlier",false]]},{"dataType":"Map","value":[["period_start_date","2021-01-08"],["period_end_date","2021-02-05"],["usage",105],["analysis_type_override",null],["inclusion_override",false],["analysis_type",1],["default_inclusion_by_calculation",true],["eliminated_as_outlier",false]]},{"dataType":"Map","value":[["period_start_date","2021-02-06"],["period_end_date","2021-03-05"],["usage",98],["analysis_type_override",null],["inclusion_override",false],["analysis_type",1],["default_inclusion_by_calculation",true],["eliminated_as_outlier",false]]},{"dataType":"Map","value":[["period_start_date","2021-03-06"],["period_end_date","2021-04-06"],["usage",66],["analysis_type_override",null],["inclusion_override",false],["analysis_type",0],["default_inclusion_by_calculation",false],["eliminated_as_outlier",false]]},{"dataType":"Map","value":[["period_start_date","2021-04-07"],["period_end_date","2021-05-05"],["usage",22],["analysis_type_override",null],["inclusion_override",false],["analysis_type",0],["default_inclusion_by_calculation",false],["eliminated_as_outlier",false]]},{"dataType":"Map","value":[["period_start_date","2021-05-06"],["period_end_date","2021-06-07"],["usage",19],["analysis_type_override",null],["inclusion_override",false],["analysis_type",0],["default_inclusion_by_calculation",false],["eliminated_as_outlier",false]]},{"dataType":"Map","value":[["period_start_date","2021-06-08"],["period_end_date","2021-07-06"],["usage",7],["analysis_type_override",null],["inclusion_override",false],["analysis_type",-1],["default_inclusion_by_calculation",true],["eliminated_as_outlier",false]]},{"dataType":"Map","value":[["period_start_date","2021-07-07"],["period_end_date","2021-08-04"],["usage",10],["analysis_type_override",null],["inclusion_override",false],["analysis_type",-1],["default_inclusion_by_calculation",true],["eliminated_as_outlier",false]]},{"dataType":"Map","value":[["period_start_date","2021-08-05"],["period_end_date","2021-09-08"],["usage",11],["analysis_type_override",null],["inclusion_override",false],["analysis_type",-1],["default_inclusion_by_calculation",true],["eliminated_as_outlier",false]]},{"dataType":"Map","value":[["period_start_date","2021-09-09"],["period_end_date","2021-10-05"],["usage",8],["analysis_type_override",null],["inclusion_override",false],["analysis_type",0],["default_inclusion_by_calculation",false],["eliminated_as_outlier",false]]},{"dataType":"Map","value":[["period_start_date","2021-10-06"],["period_end_date","2021-11-03"],["usage",13],["analysis_type_override",null],["inclusion_override",false],["analysis_type",0],["default_inclusion_by_calculation",false],["eliminated_as_outlier",false]]},{"dataType":"Map","value":[["period_start_date","2021-11-04"],["period_end_date","2021-12-06"],["usage",41],["analysis_type_override",null],["inclusion_override",false],["analysis_type",1],["default_inclusion_by_calculation",true],["eliminated_as_outlier",true]]},{"dataType":"Map","value":[["period_start_date","2021-12-07"],["period_end_date","2022-01-05"],["usage",86],["analysis_type_override",null],["inclusion_override",false],["analysis_type",1],["default_inclusion_by_calculation",true],["eliminated_as_outlier",false]]},{"dataType":"Map","value":[["period_start_date","2022-01-06"],["period_end_date","2022-02-03"],["usage",132],["analysis_type_override",null],["inclusion_override",false],["analysis_type",1],["default_inclusion_by_calculation",true],["eliminated_as_outlier",true]]},{"dataType":"Map","value":[["period_start_date","2022-02-04"],["period_end_date","2022-03-07"],["usage",116],["analysis_type_override",null],["inclusion_override",false],["analysis_type",1],["default_inclusion_by_calculation",true],["eliminated_as_outlier",true]]},{"dataType":"Map","value":[["period_start_date","2022-03-08"],["period_end_date","2022-04-04"],["usage",49],["analysis_type_override",null],["inclusion_override",false],["analysis_type",0],["default_inclusion_by_calculation",false],["eliminated_as_outlier",false]]},{"dataType":"Map","value":[["period_start_date","2022-04-05"],["period_end_date","2022-05-05"],["usage",39],["analysis_type_override",null],["inclusion_override",false],["analysis_type",0],["default_inclusion_by_calculation",false],["eliminated_as_outlier",false]]},{"dataType":"Map","value":[["period_start_date","2022-05-06"],["period_end_date","2022-06-06"],["usage",20],["analysis_type_override",null],["inclusion_override",false],["analysis_type",0],["default_inclusion_by_calculation",false],["eliminated_as_outlier",false]]},{"dataType":"Map","value":[["period_start_date","2022-06-07"],["period_end_date","2022-07-05"],["usage",9],["analysis_type_override",null],["inclusion_override",false],["analysis_type",-1],["default_inclusion_by_calculation",true],["eliminated_as_outlier",false]]},{"dataType":"Map","value":[["period_start_date","2022-07-06"],["period_end_date","2022-08-03"],["usage",7],["analysis_type_override",null],["inclusion_override",false],["analysis_type",-1],["default_inclusion_by_calculation",true],["eliminated_as_outlier",false]]},{"dataType":"Map","value":[["period_start_date","2022-08-04"],["period_end_date","2022-09-03"],["usage",8],["analysis_type_override",null],["inclusion_override",false],["analysis_type",-1],["default_inclusion_by_calculation",true],["eliminated_as_outlier",false]]},{"dataType":"Map","value":[["period_start_date","2022-09-04"],["period_end_date","2022-10-03"],["usage",8],["analysis_type_override",null],["inclusion_override",false],["analysis_type",0],["default_inclusion_by_calculation",false],["eliminated_as_outlier",false]]},{"dataType":"Map","value":[["period_start_date","2022-10-04"],["period_end_date","2022-11-03"],["usage",19],["analysis_type_override",null],["inclusion_override",false],["analysis_type",0],["default_inclusion_by_calculation",false],["eliminated_as_outlier",false]]}]]]}' + const expectedRecordsToGoInTheTable = '{"dataType":"Map","value":[["heat_load_output",{"dataType":"Map","value":[["estimated_balance_point",59.5],["other_fuel_usage",8.666666666666666],["average_indoor_temperature",68],["difference_between_ti_and_tbp",8.5],["design_temperature",9.5],["whole_home_heat_loss_rate",47972.03453453454],["standard_deviation_of_heat_loss_rate",0.07742772585617895],["average_heat_load",2494545.795795796],["maximum_heat_load",2902308.0893393396]]}],["balance_point_graph",{"dataType":"Map","value":[["records",[{"dataType":"Map","value":[["balance_point",60],["heat_loss_rate",41034.85407876231],["change_in_heat_loss_rate",0],["percent_change_in_heat_loss_rate",0],["standard_deviation",0.3967358807600794]]},{"dataType":"Map","value":[["balance_point",60.5],["heat_loss_rate",38592.303240740745],["change_in_heat_loss_rate",-2442.550838021569],["percent_change_in_heat_loss_rate",-6.32911392405064],["standard_deviation",0.39673588076007943]]},{"dataType":"Map","value":[["balance_point",59.5],["heat_loss_rate",43807.47935435436],["change_in_heat_loss_rate",2772.625275592043],["percent_change_in_heat_loss_rate",6.329113924050622],["standard_deviation",0.3967358807600795]]},{"dataType":"Map","value":[["balance_point",60.5],["heat_loss_rate",42226.71012849585],["change_in_heat_loss_rate",-2672.576590411132],["percent_change_in_heat_loss_rate",-6.329113924050639],["standard_deviation",0.30164495413734566]]},{"dataType":"Map","value":[["balance_point",59.5],["heat_loss_rate",47933.022308022315],["change_in_heat_loss_rate",3033.7355891153347],["percent_change_in_heat_loss_rate",6.3291139240506284],["standard_deviation",0.3016449541373457]]},{"dataType":"Map","value":[["balance_point",60.5],["heat_loss_rate",46671.62698412699],["change_in_heat_loss_rate",-2953.9004420333513],["percent_change_in_heat_loss_rate",-6.329113924050628],["standard_deviation",0.15298851745396608]]},{"dataType":"Map","value":[["balance_point",59.5],["heat_loss_rate",52978.60360360361],["change_in_heat_loss_rate",3353.0761774432685],["percent_change_in_heat_loss_rate",6.329113924050636],["standard_deviation",0.1529885174539661]]},{"dataType":"Map","value":[["balance_point",60.5],["heat_loss_rate",44137.56613756614],["change_in_heat_loss_rate",-2793.516844149759],["percent_change_in_heat_loss_rate",-6.329113924050642],["standard_deviation",0.10782787516463575]]},{"dataType":"Map","value":[["balance_point",59.5],["heat_loss_rate",50102.10210210211],["change_in_heat_loss_rate",3171.0191203862123],["percent_change_in_heat_loss_rate",6.329113924050639],["standard_deviation",0.10782787516463574]]},{"dataType":"Map","value":[["balance_point",59.5],["heat_loss_rate",50102.10210210211],["change_in_heat_loss_rate",3171.0191203862123],["percent_change_in_heat_loss_rate",6.329113924050639],["standard_deviation",0.10782787516463574]]},{"dataType":"Map","value":[["balance_point",59],["heat_loss_rate",53732.689210950084],["change_in_heat_loss_rate",3630.587108847976],["percent_change_in_heat_loss_rate",6.756756756756753],["standard_deviation",0.10782787516463577]]},{"dataType":"Map","value":[["balance_point",60],["heat_loss_rate",44935.829817158934],["change_in_heat_loss_rate",-3036.2047173756073],["percent_change_in_heat_loss_rate",-6.756756756756764],["standard_deviation",0.07742772585617896]]},{"dataType":"Map","value":[["balance_point",59],["heat_loss_rate",51448.26892109501],["change_in_heat_loss_rate",3476.2343865604707],["percent_change_in_heat_loss_rate",6.756756756756752],["standard_deviation",0.07742772585617896]]}]]]}],["processed_energy_bills",[{"dataType":"Map","value":[["period_start_date","2020-10-02"],["period_end_date","2020-11-04"],["usage",29],["analysis_type_override",null],["inclusion_override",false],["analysis_type",0],["default_inclusion_by_calculation",false],["eliminated_as_outlier",false]]},{"dataType":"Map","value":[["period_start_date","2020-11-05"],["period_end_date","2020-12-03"],["usage",36],["analysis_type_override",null],["inclusion_override",false],["analysis_type",1],["default_inclusion_by_calculation",true],["eliminated_as_outlier",true]]},{"dataType":"Map","value":[["period_start_date","2020-12-04"],["period_end_date","2021-01-07"],["usage",97],["analysis_type_override",null],["inclusion_override",false],["analysis_type",1],["default_inclusion_by_calculation",true],["eliminated_as_outlier",false]]},{"dataType":"Map","value":[["period_start_date","2021-01-08"],["period_end_date","2021-02-05"],["usage",105],["analysis_type_override",null],["inclusion_override",false],["analysis_type",1],["default_inclusion_by_calculation",true],["eliminated_as_outlier",false]]},{"dataType":"Map","value":[["period_start_date","2021-02-06"],["period_end_date","2021-03-05"],["usage",98],["analysis_type_override",null],["inclusion_override",false],["analysis_type",1],["default_inclusion_by_calculation",true],["eliminated_as_outlier",false]]},{"dataType":"Map","value":[["period_start_date","2021-03-06"],["period_end_date","2021-04-06"],["usage",66],["analysis_type_override",null],["inclusion_override",false],["analysis_type",0],["default_inclusion_by_calculation",false],["eliminated_as_outlier",false]]},{"dataType":"Map","value":[["period_start_date","2021-04-07"],["period_end_date","2021-05-05"],["usage",22],["analysis_type_override",null],["inclusion_override",false],["analysis_type",0],["default_inclusion_by_calculation",false],["eliminated_as_outlier",false]]},{"dataType":"Map","value":[["period_start_date","2021-05-06"],["period_end_date","2021-06-07"],["usage",19],["analysis_type_override",null],["inclusion_override",false],["analysis_type",0],["default_inclusion_by_calculation",false],["eliminated_as_outlier",false]]},{"dataType":"Map","value":[["period_start_date","2021-06-08"],["period_end_date","2021-07-06"],["usage",7],["analysis_type_override",null],["inclusion_override",false],["analysis_type",-1],["default_inclusion_by_calculation",true],["eliminated_as_outlier",false]]},{"dataType":"Map","value":[["period_start_date","2021-07-07"],["period_end_date","2021-08-04"],["usage",10],["analysis_type_override",null],["inclusion_override",false],["analysis_type",-1],["default_inclusion_by_calculation",true],["eliminated_as_outlier",false]]},{"dataType":"Map","value":[["period_start_date","2021-08-05"],["period_end_date","2021-09-08"],["usage",11],["analysis_type_override",null],["inclusion_override",false],["analysis_type",-1],["default_inclusion_by_calculation",true],["eliminated_as_outlier",false]]},{"dataType":"Map","value":[["period_start_date","2021-09-09"],["period_end_date","2021-10-05"],["usage",8],["analysis_type_override",null],["inclusion_override",false],["analysis_type",0],["default_inclusion_by_calculation",false],["eliminated_as_outlier",false]]},{"dataType":"Map","value":[["period_start_date","2021-10-06"],["period_end_date","2021-11-03"],["usage",13],["analysis_type_override",null],["inclusion_override",false],["analysis_type",0],["default_inclusion_by_calculation",false],["eliminated_as_outlier",false]]},{"dataType":"Map","value":[["period_start_date","2021-11-04"],["period_end_date","2021-12-06"],["usage",41],["analysis_type_override",null],["inclusion_override",false],["analysis_type",1],["default_inclusion_by_calculation",true],["eliminated_as_outlier",true]]},{"dataType":"Map","value":[["period_start_date","2021-12-07"],["period_end_date","2022-01-05"],["usage",86],["analysis_type_override",null],["inclusion_override",false],["analysis_type",1],["default_inclusion_by_calculation",true],["eliminated_as_outlier",false]]},{"dataType":"Map","value":[["period_start_date","2022-01-06"],["period_end_date","2022-02-03"],["usage",132],["analysis_type_override",null],["inclusion_override",false],["analysis_type",1],["default_inclusion_by_calculation",true],["eliminated_as_outlier",true]]},{"dataType":"Map","value":[["period_start_date","2022-02-04"],["period_end_date","2022-03-07"],["usage",116],["analysis_type_override",null],["inclusion_override",false],["analysis_type",1],["default_inclusion_by_calculation",true],["eliminated_as_outlier",true]]},{"dataType":"Map","value":[["period_start_date","2022-03-08"],["period_end_date","2022-04-04"],["usage",49],["analysis_type_override",null],["inclusion_override",false],["analysis_type",0],["default_inclusion_by_calculation",false],["eliminated_as_outlier",false]]},{"dataType":"Map","value":[["period_start_date","2022-04-05"],["period_end_date","2022-05-05"],["usage",39],["analysis_type_override",null],["inclusion_override",false],["analysis_type",0],["default_inclusion_by_calculation",false],["eliminated_as_outlier",false]]},{"dataType":"Map","value":[["period_start_date","2022-05-06"],["period_end_date","2022-06-06"],["usage",20],["analysis_type_override",null],["inclusion_override",false],["analysis_type",0],["default_inclusion_by_calculation",false],["eliminated_as_outlier",false]]},{"dataType":"Map","value":[["period_start_date","2022-06-07"],["period_end_date","2022-07-05"],["usage",9],["analysis_type_override",null],["inclusion_override",false],["analysis_type",-1],["default_inclusion_by_calculation",true],["eliminated_as_outlier",false]]},{"dataType":"Map","value":[["period_start_date","2022-07-06"],["period_end_date","2022-08-03"],["usage",7],["analysis_type_override",null],["inclusion_override",false],["analysis_type",-1],["default_inclusion_by_calculation",true],["eliminated_as_outlier",false]]},{"dataType":"Map","value":[["period_start_date","2022-08-04"],["period_end_date","2022-09-03"],["usage",8],["analysis_type_override",null],["inclusion_override",false],["analysis_type",-1],["default_inclusion_by_calculation",true],["eliminated_as_outlier",false]]},{"dataType":"Map","value":[["period_start_date","2022-09-04"],["period_end_date","2022-10-03"],["usage",8],["analysis_type_override",null],["inclusion_override",false],["analysis_type",0],["default_inclusion_by_calculation",false],["eliminated_as_outlier",false]]},{"dataType":"Map","value":[["period_start_date","2022-10-04"],["period_end_date","2022-11-03"],["usage",19],["analysis_type_override",null],["inclusion_override",false],["analysis_type",0],["default_inclusion_by_calculation",false],["eliminated_as_outlier",false]]}]]]}' // how you do "dir()"" in python // console.log(Object.getOwnPropertyNames(result.toJs())); @@ -352,7 +352,7 @@ test('pyodide solves climate change', async () => { // // https://github.com/codeforboston/home-energy-analysis-tool/blob/main/python/src/rules_engine/parser.py#L60 // /* 2) get_outputs_natural_gas( -// summary_input: SummaryInput, +// heat_load_input: HeatLoadInput, // temperature_input: TemperatureInput, // natural_gas_billing_input: NaturalGasBillingInput, // ) -> RulesEngineResult @@ -363,7 +363,7 @@ test('pyodide solves climate change', async () => { // /** // * TODO: // * - Match up our types with rules engine types at https://github.com/codeforboston/home-energy-analysis-tool/blob/main/python/src/rules_engine/pydantic_models.py#L57 -// * - Use those to create summary_input (in the same file) +// * - Use those to create heat_load_input (in the same file) // * - Use those to create temperature_input (in the same file) // * - Use those to create natural_gas_billing_input (in the same file) // */ diff --git a/heat-stack/app/utils/pyodide.util.js b/heat-stack/app/utils/pyodide.util.js index c43b1edd..dea0cbf2 100644 --- a/heat-stack/app/utils/pyodide.util.js +++ b/heat-stack/app/utils/pyodide.util.js @@ -48,7 +48,7 @@ class PyodideUtil { console.log("Makin SI name:"+ si.name); let f = this.pyodideModule.runPython(` def f(s): - return SummaryInput(**s) + return HeatLoadInput(**s) f `); let fr = f(si); @@ -58,10 +58,10 @@ class PyodideUtil { getSIO() { let r = this.pyodideModule.runPython(` - from rules_engine.pydantic_models import SummaryInput + from rules_engine.pydantic_models import HeatLoadInput from pyodide.ffi import to_js import js - SummaryInput`); + HeatLoadInput`); return r; } @@ -83,8 +83,8 @@ class PyodideUtil { NaturalGasBillingInput, NormalizedBillingPeriodRecordInput, OilPropaneBillingInput, - SummaryInput, - SummaryOutput, + HeatLoadInput, + HeatLoadOutput, TemperatureInput, ) @@ -105,7 +105,7 @@ class PyodideUtil { t = t.to_py(default_converter=default_converter) - summa_inpoot = SummaryInput(**svm) + summa_inpoot = HeatLoadInput(**svm) tempinz = TemperatureInput(**t) diff --git a/heat-stack/types/index.ts b/heat-stack/types/index.ts index b934a472..e55f34cf 100644 --- a/heat-stack/types/index.ts +++ b/heat-stack/types/index.ts @@ -63,7 +63,7 @@ export const balancePointGraphSchema = z.object({ records: z.array(balancePointGraphRecordSchema), }) -// Define the schema for the 'summary_output' key +// Define the schema for the 'heat_load_output' key export const summaryOutputSchema = z.object({ // rulesEngineVersion: z.string(), // TODO estimated_balance_point: z.number(), @@ -98,7 +98,7 @@ export const naturalGasUsageSchema = z.map( ) // Define the schema for one billing record -export const oneBillingRecordSchema = z.object({ +export const oneProcessedEnergyBillSchema = z.object({ period_start_date: z.string(), period_end_date: z.string(), usage: z.number(), @@ -121,12 +121,12 @@ export const oneBillingRecordSchema = z.object({ whole_home_heat_loss_rate: z.number(), }); -// Define the schema for the 'billing_records' list -export const allBillingRecordsSchema = z.array(oneBillingRecordSchema); +// Define the schema for the 'processed_energy_bills' list +export const allProcessedEnergyBillsSchema = z.array(oneProcessedEnergyBillSchema); // Define the schema for the 'usage_data' key export const usageDataSchema = z.object({ - summary_output: summaryOutputSchema, + heat_load_output: summaryOutputSchema, balance_point_graph: balancePointGraphSchema, - billing_records: allBillingRecordsSchema, + processed_energy_bills: allProcessedEnergyBillsSchema, }) diff --git a/heat-stack/types/types.ts b/heat-stack/types/types.ts index ba6039d8..6437463d 100644 --- a/heat-stack/types/types.ts +++ b/heat-stack/types/types.ts @@ -3,8 +3,8 @@ import { type balancePointGraphRecordSchema, type balancePointGraphSchema, type summaryOutputSchema, - type oneBillingRecordSchema, - type allBillingRecordsSchema, + type oneProcessedEnergyBillSchema, + type allProcessedEnergyBillsSchema, type usageDataSchema, type naturalGasUsageSchema } from './index.ts' @@ -14,6 +14,6 @@ export type NaturalGasUsageDataSchema = z.infer; export type BalancePoointGraphRecordSchema = z.infer; export type BalancePintGraphSchema = z.infer; export type SummaryOutputSchema = z.infer; -export type BillingRecordSchema = z.infer; -export type BillingRecordsSchema = z.infer; +export type BillingRecordSchema = z.infer; +export type BillingRecordsSchema = z.infer; export type UsageDataSchema = z.infer; \ No newline at end of file diff --git a/python/setup-python.sh b/python/setup-python.sh new file mode 100755 index 00000000..72f54326 --- /dev/null +++ b/python/setup-python.sh @@ -0,0 +1,4 @@ +python -m venv .venv +source .venv/bin/activate +pip install -e . +pip install -r requirements-dev.txt \ No newline at end of file diff --git a/python/src/rules_engine/engine.py b/python/src/rules_engine/engine.py index 3ad2514d..1424ee15 100644 --- a/python/src/rules_engine/engine.py +++ b/python/src/rules_engine/engine.py @@ -16,19 +16,19 @@ Constants, DhwInput, FuelType, + HeatLoadInput, + HeatLoadOutput, NaturalGasBillingInput, - NormalizedBillingPeriodRecord, - NormalizedBillingPeriodRecordBase, OilPropaneBillingInput, + ProcessedEnergyBill, + ProcessedEnergyBillInput, RulesEngineResult, - SummaryInput, - SummaryOutput, TemperatureInput, ) def get_outputs_oil_propane( - summary_input: SummaryInput, + heat_load_input: HeatLoadInput, dhw_input: Optional[DhwInput], temperature_input: TemperatureInput, oil_propane_billing_input: OilPropaneBillingInput, @@ -36,13 +36,13 @@ def get_outputs_oil_propane( """ Analyze the heat load for a home that is using oil or propane as its current heating system fuel. """ - billing_periods: list[NormalizedBillingPeriodRecordBase] = [] + processed_energy_bill_inputs: list[ProcessedEnergyBillInput] = [] last_date = oil_propane_billing_input.preceding_delivery_date for input_val in oil_propane_billing_input.records: start_date = last_date + timedelta(days=1) - billing_periods.append( - NormalizedBillingPeriodRecordBase( + processed_energy_bill_inputs.append( + ProcessedEnergyBillInput( period_start_date=start_date, period_end_date=input_val.period_end_date, usage=input_val.gallons, @@ -52,23 +52,23 @@ def get_outputs_oil_propane( last_date = input_val.period_end_date return get_outputs_normalized( - summary_input, dhw_input, temperature_input, billing_periods + heat_load_input, dhw_input, temperature_input, processed_energy_bill_inputs ) def get_outputs_natural_gas( - summary_input: SummaryInput, + heat_load_input: HeatLoadInput, temperature_input: TemperatureInput, natural_gas_billing_input: NaturalGasBillingInput, ) -> RulesEngineResult: """ Analyze the heat load for a home that is using natural gas as its current heating system fuel. """ - billing_periods: list[NormalizedBillingPeriodRecordBase] = [] + processed_energy_bill_inputs: list[ProcessedEnergyBillInput] = [] for input_val in natural_gas_billing_input.records: - billing_periods.append( - NormalizedBillingPeriodRecordBase( + processed_energy_bill_inputs.append( + ProcessedEnergyBillInput( period_start_date=input_val.period_start_date, period_end_date=input_val.period_end_date, usage=input_val.usage_therms, @@ -77,58 +77,62 @@ def get_outputs_natural_gas( ) return get_outputs_normalized( - summary_input, None, temperature_input, billing_periods + heat_load_input, + None, + temperature_input, + processed_energy_bill_inputs, ) def get_outputs_normalized( - summary_input: SummaryInput, + heat_load_input: HeatLoadInput, dhw_input: Optional[DhwInput], temperature_input: TemperatureInput, - billing_periods: list[NormalizedBillingPeriodRecordBase], + processed_energy_bill_inputs: list[ProcessedEnergyBillInput], ) -> RulesEngineResult: """ Analyze the heat load for a home based on normalized, fuel-type-agnostic billing records. """ initial_balance_point = 60 - intermediate_billing_periods = convert_to_intermediate_billing_periods( - temperature_input=temperature_input, - billing_periods=billing_periods, - fuel_type=summary_input.fuel_type, + intermediate_processed_energy_bills = ( + convert_to_intermediate_processed_energy_bills( + temperature_input=temperature_input, + processed_energy_bill_inputs=processed_energy_bill_inputs, + fuel_type=heat_load_input.fuel_type, + ) ) - home = Home( - summary_input=summary_input, - billing_periods=intermediate_billing_periods, + home = Home.calculate( + heat_load_input=heat_load_input, + intermediate_energy_bills=intermediate_processed_energy_bills, dhw_input=dhw_input, initial_balance_point=initial_balance_point, ) - home.calculate() average_indoor_temperature = get_average_indoor_temperature( - thermostat_set_point=summary_input.thermostat_set_point, - setback_temperature=summary_input.setback_temperature, - setback_hours_per_day=summary_input.setback_hours_per_day, + thermostat_set_point=heat_load_input.thermostat_set_point, + setback_temperature=heat_load_input.setback_temperature, + setback_hours_per_day=heat_load_input.setback_hours_per_day, ) average_heat_load = get_average_heat_load( design_set_point=Constants.DESIGN_SET_POINT, avg_indoor_temp=average_indoor_temperature, balance_point=home.balance_point, - design_temp=summary_input.design_temperature, + design_temp=heat_load_input.design_temperature, ua=home.avg_ua, ) maximum_heat_load = get_maximum_heat_load( design_set_point=Constants.DESIGN_SET_POINT, - design_temp=summary_input.design_temperature, + design_temp=heat_load_input.design_temperature, ua=home.avg_ua, ) - summary_output = SummaryOutput( + heat_load_output = HeatLoadOutput( estimated_balance_point=home.balance_point, other_fuel_usage=home.avg_non_heating_usage, average_indoor_temperature=average_indoor_temperature, difference_between_ti_and_tbp=average_indoor_temperature - home.balance_point, - design_temperature=summary_input.design_temperature, + design_temperature=heat_load_input.design_temperature, whole_home_heat_loss_rate=home.avg_ua, standard_deviation_of_heat_loss_rate=home.stdev_pct, average_heat_load=average_heat_load, @@ -137,33 +141,33 @@ def get_outputs_normalized( balance_point_graph = home.balance_point_graph - billing_records = [] - for billing_period in intermediate_billing_periods: - billing_record = NormalizedBillingPeriodRecord( - period_start_date=billing_period.input.period_start_date, - period_end_date=billing_period.input.period_end_date, - usage=billing_period.input.usage, - inclusion_override=billing_period.input.inclusion_override, - analysis_type=billing_period.analysis_type, - default_inclusion=billing_period.default_inclusion, - eliminated_as_outlier=billing_period.eliminated_as_outlier, - whole_home_heat_loss_rate=billing_period.ua, + processed_energy_bills = [] + for intermediate_energy_bill in intermediate_processed_energy_bills: + processed_energy_bill = ProcessedEnergyBill( + period_start_date=intermediate_energy_bill.input.period_start_date, + period_end_date=intermediate_energy_bill.input.period_end_date, + usage=intermediate_energy_bill.input.usage, + inclusion_override=intermediate_energy_bill.input.inclusion_override, + analysis_type=intermediate_energy_bill.analysis_type, + default_inclusion=intermediate_energy_bill.default_inclusion, + eliminated_as_outlier=intermediate_energy_bill.eliminated_as_outlier, + whole_home_heat_loss_rate=intermediate_energy_bill.ua, ) - billing_records.append(billing_record) + processed_energy_bills.append(processed_energy_bill) result = RulesEngineResult( - summary_output=summary_output, + heat_load_output=heat_load_output, balance_point_graph=balance_point_graph, - billing_records=billing_records, + processed_energy_bills=processed_energy_bills, ) return result -def convert_to_intermediate_billing_periods( +def convert_to_intermediate_processed_energy_bills( temperature_input: TemperatureInput, - billing_periods: list[NormalizedBillingPeriodRecordBase], + processed_energy_bill_inputs: list[ProcessedEnergyBillInput], fuel_type: FuelType, -) -> list[ProcessedBill]: +) -> list[IntermediateEnergyBill]: """ Converts temperature data and billing period inputs into internal classes used for heat loss calculations. @@ -171,41 +175,46 @@ def convert_to_intermediate_billing_periods( """ # Build a list of lists of temperatures, where each list of temperatures contains all the temperatures # in the corresponding billing period - intermediate_billing_periods = [] + intermediate_processed_energy_bill_inputs = [] default_inclusion = False - for billing_period in billing_periods: + for processed_energy_bill_input in processed_energy_bill_inputs: # the HEAT Excel sheet is inclusive of the temperatures that fall on both the start and end dates start_idx = bisect.bisect_left( - temperature_input.dates, billing_period.period_start_date + temperature_input.dates, processed_energy_bill_input.period_start_date ) end_idx = ( - bisect.bisect_left(temperature_input.dates, billing_period.period_end_date) + bisect.bisect_left( + temperature_input.dates, processed_energy_bill_input.period_end_date + ) + 1 ) if fuel_type == FuelType.GAS: analysis_type, default_inclusion = _date_to_analysis_type_natural_gas( - billing_period.period_end_date + processed_energy_bill_input.period_end_date ) elif fuel_type == FuelType.OIL or fuel_type == FuelType.PROPANE: analysis_type, default_inclusion = _date_to_analysis_type_oil_propane( - billing_period.period_start_date, billing_period.period_end_date + processed_energy_bill_input.period_start_date, + processed_energy_bill_input.period_end_date, ) else: raise ValueError("Unsupported fuel type.") - intermediate_billing_period = ProcessedBill( - input=billing_period, + intermediate_energy_bill = IntermediateEnergyBill( + input=processed_energy_bill_input, avg_temps=temperature_input.temperatures[start_idx:end_idx], - usage=billing_period.usage, + usage=processed_energy_bill_input.usage, analysis_type=analysis_type, default_inclusion=default_inclusion, - inclusion_override=billing_period.inclusion_override, + inclusion_override=processed_energy_bill_input.inclusion_override, + ) + intermediate_processed_energy_bill_inputs.append( + intermediate_energy_bill ) - intermediate_billing_periods.append(intermediate_billing_period) - return intermediate_billing_periods + return intermediate_processed_energy_bill_inputs def _date_to_analysis_type_oil_propane( @@ -399,28 +408,30 @@ class Home: standard deviation of the UA values across them. """ - def __init__( + def _init( self, - summary_input: SummaryInput, - billing_periods: list[ProcessedBill], + heat_load_input: HeatLoadInput, + intermediate_energy_bills: list[IntermediateEnergyBill], dhw_input: Optional[DhwInput], initial_balance_point: float = 60, - ): - self.fuel_type = summary_input.fuel_type - self.heat_sys_efficiency = summary_input.heating_system_efficiency - self.thermostat_set_point = summary_input.thermostat_set_point + ) -> None: + self.fuel_type = heat_load_input.fuel_type + self.heat_sys_efficiency = heat_load_input.heating_system_efficiency + self.thermostat_set_point = heat_load_input.thermostat_set_point self.balance_point = initial_balance_point self.dhw_input = dhw_input - self._initialize_billing_periods(billing_periods) + self._initialize_processed_energy_bill_inputs(intermediate_energy_bills) - def _initialize_billing_periods(self, billing_periods: list[ProcessedBill]) -> None: - self.bills_winter = [] - self.bills_summer = [] - self.bills_shoulder = [] + def _initialize_processed_energy_bill_inputs( + self, intermediate_energy_bills: list[IntermediateEnergyBill] + ) -> None: + self.winter_processed_energy_bills = [] + self.summer_processed_energy_bills = [] + self.shoulder_processed_energy_bills = [] # winter months 1 (ALLOWED_HEATING_USAGE); summer months -1 (ALLOWED_NON_HEATING_USAGE); shoulder months 0 (NOT_ALLOWED...) - for billing_period in billing_periods: - billing_period.set_initial_balance_point(self.balance_point) + for processed_energy_bill in intermediate_energy_bills: + processed_energy_bill.set_initial_balance_point(self.balance_point) """ The UI depicts billing period usage as several distinctive icons on the left hand column of the screen; "analysis_type" @@ -430,17 +441,17 @@ def _initialize_billing_periods(self, billing_periods: list[ProcessedBill]) -> N The following code implements this algorithm and adds bills accordingly to winter, summer, or shoulder (i.e. excluded) lists """ - _analysis_type = billing_period.analysis_type - _default_inclusion = billing_period.default_inclusion + _analysis_type = processed_energy_bill.analysis_type + _default_inclusion = processed_energy_bill.default_inclusion # Only bills deemed ALLOWED by the AnalysisType algorithm can be included/excluded by the user # if ( # _analysis_type == AnalysisType.ALLOWED_HEATING_USAGE # or _analysis_type == AnalysisType.ALLOWED_NON_HEATING_USAGE # ): - # if billing_period.inclusion_override: + # if processed_energy_bill_input.inclusion_override: # # The user has requested we override an inclusion algorithm decision - # if billing_period.winter_cusp_month == True: + # if processed_energy_bill_input.winter_cusp_month == True: # # This bill is on the cusp of winter; the user has requested we include it # _analysis_type = AnalysisType.ALLOWED_HEATING_USAGE # else: @@ -448,38 +459,38 @@ def _initialize_billing_periods(self, billing_periods: list[ProcessedBill]) -> N # _analysis_type = AnalysisType.NOT_ALLOWED_IN_CALCULATIONS # else: # # The user has chosen to not override our automatic calculations, even for a winter cusp month - # if billing_period.winter_cusp_month == True: + # if processed_energy_bill_input.winter_cusp_month == True: # _analysis_type = AnalysisType.NOT_ALLOWED_IN_CALCULATIONS # Assign the bill to the appropriate list for winter or summer calculations - if billing_period.inclusion_override: + if processed_energy_bill.inclusion_override: _default_inclusion = not _default_inclusion if ( _analysis_type == AnalysisType.ALLOWED_HEATING_USAGE and _default_inclusion ): - self.bills_winter.append(billing_period) + self.winter_processed_energy_bills.append(processed_energy_bill) elif ( _analysis_type == AnalysisType.ALLOWED_NON_HEATING_USAGE and _default_inclusion ): - self.bills_summer.append(billing_period) + self.summer_processed_energy_bills.append(processed_energy_bill) else: # the rest are excluded from calculations - self.bills_shoulder.append(billing_period) + self.shoulder_processed_energy_bills.append(processed_energy_bill) self._calculate_avg_summer_usage() self._calculate_avg_non_heating_usage() - for billing_period in self.bills_winter: - self.initialize_ua(billing_period) + for processed_energy_bill in self.winter_processed_energy_bills: + self.initialize_ua(processed_energy_bill) def _calculate_avg_summer_usage(self) -> None: """ Calculate average daily summer usage """ - summer_usage_total = sum([bp.usage for bp in self.bills_summer]) - summer_days = sum([bp.days for bp in self.bills_summer]) + summer_usage_total = sum([bp.usage for bp in self.summer_processed_energy_bills]) + summer_days = sum([bp.days for bp in self.summer_processed_energy_bills]) if summer_days != 0: self.avg_summer_usage = summer_usage_total / summer_days else: @@ -506,7 +517,7 @@ def _calculate_balance_point_and_ua( stdev_pct_max: float = 0.10, max_stdev_pct_diff: float = 0.01, next_balance_point_sensitivity: float = 0.5, - ) -> None: + ) -> any: """ Calculates the estimated balance point and UA coefficient for the home, removing UA outliers based on a normalized standard @@ -515,7 +526,10 @@ def _calculate_balance_point_and_ua( self.balance_point_graph = BalancePointGraph(records=[]) - self.uas = [billing_period.ua for billing_period in self.bills_winter] + self.uas = [ + processed_energy_bill.ua + for processed_energy_bill in self.winter_processed_energy_bills + ] self.avg_ua = sts.mean(self.uas) self.stdev_pct = sts.pstdev(self.uas) / self.avg_ua @@ -532,14 +546,17 @@ def _calculate_balance_point_and_ua( self._refine_balance_point(initial_balance_point_sensitivity) while self.stdev_pct > stdev_pct_max: - outliers = [abs(bill.ua - self.avg_ua) for bill in self.bills_winter] + outliers = [abs(bill.ua - self.avg_ua) for bill in self.winter_processed_energy_bills] biggest_outlier = max(outliers) biggest_outlier_idx = outliers.index(biggest_outlier) - outlier = self.bills_winter.pop( + outlier = self.winter_processed_energy_bills.pop( biggest_outlier_idx ) # removes the biggest outlier outlier.eliminated_as_outlier = True - uas_i = [billing_period.ua for billing_period in self.bills_winter] + uas_i = [ + processed_energy_bill.ua + for processed_energy_bill in self.winter_processed_energy_bills + ] avg_ua_i = sts.mean(uas_i) stdev_pct_i = sts.pstdev(uas_i) / avg_ua_i if ( @@ -548,7 +565,7 @@ def _calculate_balance_point_and_ua( < max_stdev_pct_diff ): # if it's a small enough change # add the outlier back in - self.bills_winter.append( + self.winter_processed_energy_bills.append( outlier ) # then it's not worth removing it, and we exit outlier.eliminated_as_outlier = False @@ -575,11 +592,11 @@ def _refine_balance_point(self, balance_point_sensitivity: float) -> None: break # may want to raise some kind of warning as well period_hdds_i = [ - period_hdd(bill.avg_temps, bp_i) for bill in self.bills_winter + period_hdd(bill.avg_temps, bp_i) for bill in self.winter_processed_energy_bills ] uas_i = [ bill.partial_ua / period_hdds_i[n] - for n, bill in enumerate(self.bills_winter) + for n, bill in enumerate(self.winter_processed_energy_bills) ] avg_ua_i = sts.mean(uas_i) stdev_pct_i = sts.pstdev(uas_i) / avg_ua_i @@ -614,51 +631,74 @@ def _refine_balance_point(self, balance_point_sensitivity: float) -> None: ) self.balance_point_graph.records.append(balance_point_graph_row) - for n, bill in enumerate(self.bills_winter): + for n, bill in enumerate(self.winter_processed_energy_bills): bill.total_hdd = period_hdds_i[n] bill.ua = uas_i[n] if len(directions_to_check) == 2: directions_to_check.pop(-1) + @classmethod def calculate( - self, + cls, + heat_load_input: HeatLoadInput, + intermediate_energy_bills: list[IntermediateEnergyBill], + dhw_input: Optional[DhwInput], + initial_balance_point: float = 60, initial_balance_point_sensitivity: float = 0.5, stdev_pct_max: float = 0.10, max_stdev_pct_diff: float = 0.01, next_balance_point_sensitivity: float = 0.5, - ) -> None: + ) -> "Home": """ For this Home, calculates avg non heating usage and then the estimated balance point and UA coefficient for the home, removing UA outliers based on a normalized standard deviation threshold. """ - self._calculate_avg_non_heating_usage() - self._calculate_balance_point_and_ua( + home_instance = object.__new__(cls) + home_instance._init( + heat_load_input=heat_load_input, + intermediate_energy_bills=intermediate_energy_bills, + dhw_input=dhw_input, + initial_balance_point=initial_balance_point, + ) + home_instance._calculate_avg_non_heating_usage() + home_instance._calculate_balance_point_and_ua( initial_balance_point_sensitivity, stdev_pct_max, max_stdev_pct_diff, next_balance_point_sensitivity, ) - def initialize_ua(self, billing_period: ProcessedBill) -> None: + return home_instance + + def initialize_ua( + self, intermediate_energy_bill: IntermediateEnergyBill + ) -> None: """ Average heating usage, partial UA, initial UA. requires that self.home have non heating usage calculated. """ - billing_period.avg_heating_usage = ( - billing_period.usage / billing_period.days + intermediate_energy_bill.avg_heating_usage = ( + intermediate_energy_bill.usage / intermediate_energy_bill.days ) - self.avg_non_heating_usage - billing_period.partial_ua = self.calculate_partial_ua(billing_period) - billing_period.ua = billing_period.partial_ua / billing_period.total_hdd + intermediate_energy_bill.partial_ua = self.calculate_partial_ua( + intermediate_energy_bill + ) + intermediate_energy_bill.ua = ( + intermediate_energy_bill.partial_ua + / intermediate_energy_bill.total_hdd + ) - def calculate_partial_ua(self, billing_period: ProcessedBill) -> float: + def calculate_partial_ua( + self, intermediate_energy_bill: IntermediateEnergyBill + ) -> float: """ The portion of UA that is not dependent on the balance point """ return ( - billing_period.days - * billing_period.avg_heating_usage # gallons or therms + intermediate_energy_bill.days + * intermediate_energy_bill.avg_heating_usage # gallons or therms * self.fuel_type.value # therm or gallon to BTU * self.heat_sys_efficiency # unitless / 24 @@ -667,13 +707,13 @@ def calculate_partial_ua(self, billing_period: ProcessedBill) -> float: ) -class ProcessedBill: +class IntermediateEnergyBill: """ An internal class storing data whence heating usage per billing period is calculated. """ - input: NormalizedBillingPeriodRecordBase + input: ProcessedEnergyBillInput avg_heating_usage: float balance_point: float partial_ua: float @@ -683,7 +723,7 @@ class ProcessedBill: def __init__( self, - input: NormalizedBillingPeriodRecordBase, + input: ProcessedEnergyBillInput, avg_temps: list[float], usage: float, analysis_type: AnalysisType, diff --git a/python/src/rules_engine/pydantic_models.py b/python/src/rules_engine/pydantic_models.py index fce42087..7a10fdd0 100644 --- a/python/src/rules_engine/pydantic_models.py +++ b/python/src/rules_engine/pydantic_models.py @@ -56,7 +56,7 @@ def validate_fuel_type(value: Any) -> FuelType: ) -class SummaryInput(BaseModel): +class HeatLoadInput(BaseModel): """From Summary Tab""" # design_temperature_override: float @@ -141,7 +141,7 @@ def overall_end_date(self) -> datetime: return max_date -class NormalizedBillingPeriodRecordBase(BaseModel): +class ProcessedEnergyBillInput(BaseModel): """ Base class for a normalized billing period record. @@ -156,7 +156,7 @@ class NormalizedBillingPeriodRecordBase(BaseModel): inclusion_override: bool = Field(frozen=True) -class NormalizedBillingPeriodRecord(NormalizedBillingPeriodRecordBase): +class ProcessedEnergyBill(ProcessedEnergyBillInput): """ Derived class for holding a normalized billing period record. @@ -180,7 +180,7 @@ class TemperatureInput(BaseModel): temperatures: list[float] -class SummaryOutput(BaseModel): +class HeatLoadOutput(BaseModel): """From Summary tab""" estimated_balance_point: float = Field( @@ -221,9 +221,9 @@ class BalancePointGraph(BaseModel): class RulesEngineResult(BaseModel): - summary_output: SummaryOutput + heat_load_output: HeatLoadOutput balance_point_graph: BalancePointGraph - billing_records: list[NormalizedBillingPeriodRecord] + processed_energy_bills: list[ProcessedEnergyBill] @dataclass diff --git a/python/src/rules_engine/refactor.md b/python/src/rules_engine/refactor.md index a103f0e5..f13b00fb 100644 --- a/python/src/rules_engine/refactor.md +++ b/python/src/rules_engine/refactor.md @@ -13,16 +13,16 @@ home variables used 1. Remove temporary_rules_engine.py 2. Rename rules-engine to "python" 3. Rename NormalizedBillingRecordBase class to BillingInput -4. Rename BillingPeriod to ProcessedBill and billing_period to processed_bill -5. Combine get_outputs_normalized and convert_to_intermediate_billing_record and get rid of NormalizedBillingRecord. There is only one place NormalizedBillingRecord is used and combining code +4. Rename BillingPeriod to IntermediateEnergyBill and processed_energy_bill_input to processed_energy_bill_intermediate +5. Combine get_outputs_normalized and convert_to_intermediate_processed_energy_bill and get rid of NormalizedBillingRecord. There is only one place NormalizedBillingRecord is used and combining code gets rid of the need for the class. - Change ``` - billing_periods: list[NormalizedBillingPeriodRecordBase] = [] + processed_energy_bill_inputs: list[ProcessedEnergyBillInput] = [] for input_val in natural_gas_billing_input.records: - billing_periods.append( - NormalizedBillingPeriodRecordBase( + processed_energy_bill_inputs.append( + ProcessedEnergyBillInput( period_start_date=input_val.period_start_date, period_end_date=input_val.period_end_date, usage=input_val.usage_therms, @@ -31,17 +31,17 @@ gets rid of the need for the class. ) return get_outputs_normalized( - summary_input, None, temperature_input, billing_periods + heat_load_input, None, temperature_input, processed_energy_bill_inputs ) def get_outputs_normalized - loops through billing_periods and does a bunch of stuff + loops through processed_energy_bill_inputs and does a bunch of stuff ``` to ``` inputBill = - NormalizedBillingPeriodRecordBase( + ProcessedEnergyBillInput( period_start_date=input_val.period_start_date, period_end_date=input_val.period_end_date, usage=input_val.usage_therms, @@ -55,7 +55,7 @@ to inputBill.start_date, inputBill.end_date ) - processedBill = ProcessedBill( + processedBill = IntermediateEnergyBill( input = inputBill, avg_temps = avg_temps, default_analysis_type = default_analysis_type @@ -93,12 +93,12 @@ avg_non_heating_usage = _get_avg_non_heating_usage ( ``` ================== - change -`for billing_period in billing_periods ...` => +`for processed_energy_bill_input in processed_energy_bill_inputs ...` => to ``` -for billing_period in billing_periods - { bills_summer, bills_winter, bills_shoulder } - =_categorize_bills_by_season (billing_periods) +for processed_energy_bill_input in processed_energy_bill_inputs + { summer_processed_energy_bills, winter_processed_energy_bills, shoulder_processed_energy_bills } + =_categorize_bills_by_season (processed_energy_bill_inputs) ``` ================== - Change @@ -116,7 +116,7 @@ to ``` ================== - Change -`self.initialize_ua(billing_period)` => `_set_ua(billing_period,avg_non_heating_usage)` +`self.initialize_ua(processed_energy_bill_input)` => `_set_ua(processed_energy_bill_input,avg_non_heating_usage)` - Change?? Parameters are never set ``` @@ -158,7 +158,7 @@ self._calculate_balance_point_and_ua( stdev_pct_max, max_stdev_pct_diff, next_balance_point_sensitivity, - bills_winter) + winter_processed_energy_bills) ``` ================== diff --git a/python/tests/test_rules_engine/generate_example_data.py b/python/tests/test_rules_engine/generate_example_data.py index 72fc9c40..5693d8a2 100644 --- a/python/tests/test_rules_engine/generate_example_data.py +++ b/python/tests/test_rules_engine/generate_example_data.py @@ -90,7 +90,7 @@ def generate_summary_json(workbook: Any, working_directory: Path) -> str: return fuel_type -def generate_billing_record_input_csv( +def generate_processed_energy_bill_input_csv( workbook: Any, fuel_type: str, working_directory: Path ) -> None: """ @@ -192,6 +192,6 @@ def generate_billing_record_input_csv( continue print("Processing test directory:", working_directory) fuel_type = generate_summary_json(workbook, working_directory) - generate_billing_record_input_csv(workbook, fuel_type, working_directory) + generate_processed_energy_bill_input_csv(workbook, fuel_type, working_directory) workbook.close() del workbook diff --git a/python/tests/test_rules_engine/test_engine.py b/python/tests/test_rules_engine/test_engine.py index ca63f742..cfd1723e 100644 --- a/python/tests/test_rules_engine/test_engine.py +++ b/python/tests/test_rules_engine/test_engine.py @@ -10,14 +10,14 @@ BalancePointGraph, DhwInput, FuelType, + HeatLoadInput, + HeatLoadOutput, NaturalGasBillingInput, - NormalizedBillingPeriodRecordBase, - SummaryInput, - SummaryOutput, + ProcessedEnergyBillInput, TemperatureInput, ) -dummy_billing_period_record = NormalizedBillingPeriodRecordBase( +_dummy_processed_energy_bill_input = ProcessedEnergyBillInput( period_start_date=datetime(2024, 1, 1), period_end_date=datetime(2024, 2, 1), usage=1.0, @@ -26,34 +26,36 @@ @pytest.fixture() -def sample_billing_periods() -> list[engine.ProcessedBill]: - billing_periods = [ - engine.ProcessedBill( - dummy_billing_period_record, +def sample_intermediate_energy_bill_inputs() -> ( + list[engine.IntermediateEnergyBill] +): + intermediate_energy_bill_inputs = [ + engine.IntermediateEnergyBill( + _dummy_processed_energy_bill_input, [28, 29, 30, 29], 50, AnalysisType.ALLOWED_HEATING_USAGE, True, False, ), - engine.ProcessedBill( - dummy_billing_period_record, + engine.IntermediateEnergyBill( + _dummy_processed_energy_bill_input, [32, 35, 35, 38], 45, AnalysisType.ALLOWED_HEATING_USAGE, True, False, ), - engine.ProcessedBill( - dummy_billing_period_record, + engine.IntermediateEnergyBill( + _dummy_processed_energy_bill_input, [41, 43, 42, 42], 30, AnalysisType.ALLOWED_HEATING_USAGE, True, False, ), - engine.ProcessedBill( - dummy_billing_period_record, + engine.IntermediateEnergyBill( + _dummy_processed_energy_bill_input, [72, 71, 70, 69], 0.96, AnalysisType.NOT_ALLOWED_IN_CALCULATIONS, @@ -61,46 +63,48 @@ def sample_billing_periods() -> list[engine.ProcessedBill]: False, ), ] - return billing_periods + return intermediate_energy_bill_inputs @pytest.fixture() -def sample_billing_periods_with_outlier() -> list[engine.ProcessedBill]: - billing_periods = [ - engine.ProcessedBill( - dummy_billing_period_record, +def sample_intermediate_energy_bill_inputs_with_outlier() -> ( + list[engine.IntermediateEnergyBill] +): + intermediate_energy_bill_inputs = [ + engine.IntermediateEnergyBill( + _dummy_processed_energy_bill_input, [41.7, 41.6, 32, 25.4], 60, AnalysisType.ALLOWED_HEATING_USAGE, True, False, ), - engine.ProcessedBill( - dummy_billing_period_record, + engine.IntermediateEnergyBill( + _dummy_processed_energy_bill_input, [28, 29, 30, 29], 50, AnalysisType.ALLOWED_HEATING_USAGE, True, False, ), - engine.ProcessedBill( - dummy_billing_period_record, + engine.IntermediateEnergyBill( + _dummy_processed_energy_bill_input, [32, 35, 35, 38], 45, AnalysisType.ALLOWED_HEATING_USAGE, True, False, ), - engine.ProcessedBill( - dummy_billing_period_record, + engine.IntermediateEnergyBill( + _dummy_processed_energy_bill_input, [41, 43, 42, 42], 30, AnalysisType.ALLOWED_HEATING_USAGE, True, False, ), - engine.ProcessedBill( - dummy_billing_period_record, + engine.IntermediateEnergyBill( + _dummy_processed_energy_bill_input, [72, 71, 70, 69], 0.96, AnalysisType.NOT_ALLOWED_IN_CALCULATIONS, @@ -109,11 +113,11 @@ def sample_billing_periods_with_outlier() -> list[engine.ProcessedBill]: ), ] - return billing_periods + return intermediate_energy_bill_inputs @pytest.fixture() -def sample_summary_inputs() -> SummaryInput: +def sample_heat_load_inputs() -> HeatLoadInput: heat_sys_efficiency = 0.88 living_area = 1000 @@ -122,7 +126,7 @@ def sample_summary_inputs() -> SummaryInput: setback_hours_per_day = 8 fuel_type = FuelType.GAS design_temperature = 60 - summary_input = SummaryInput( + heat_load_input = HeatLoadInput( living_area=living_area, fuel_type=fuel_type, heating_system_efficiency=heat_sys_efficiency, @@ -131,7 +135,7 @@ def sample_summary_inputs() -> SummaryInput: setback_hours_per_day=setback_hours_per_day, design_temperature=design_temperature, ) - return summary_input + return heat_load_input @pytest.fixture() @@ -190,8 +194,8 @@ def sample_temp_inputs() -> TemperatureInput: @pytest.fixture() -def sample_normalized_billing_periods() -> list[NormalizedBillingPeriodRecordBase]: - billing_periods_dict: Any = [ +def sample_normalized_processed_energy_bill_inputs() -> list[ProcessedEnergyBillInput]: + processed_energy_bill_inputs_dict: Any = [ { "period_start_date": "2022-12-01", "period_end_date": "2022-12-04", @@ -236,21 +240,21 @@ def sample_normalized_billing_periods() -> list[NormalizedBillingPeriodRecordBas }, ] - # billing_periods = [ - # NormalizedBillingPeriodRecordBase(**x) for x in billing_periods_dict + # processed_energy_bill_inputs = [ + # ProcessedEnergyBillInput(**x) for x in processed_energy_bill_inputs_dict # ] - billing_periods = [ - NormalizedBillingPeriodRecordBase( + processed_energy_bill_inputs = [ + ProcessedEnergyBillInput( period_start_date=datetime.fromisoformat(x["period_start_date"]), period_end_date=datetime.fromisoformat(x["period_end_date"]), usage=x["usage"], inclusion_override=x["inclusion_override"], ) - for x in billing_periods_dict + for x in processed_energy_bill_inputs_dict ] - return billing_periods + return processed_energy_bill_inputs @pytest.mark.parametrize( @@ -312,17 +316,15 @@ def test_get_average_indoor_temperature(): assert engine.get_average_indoor_temperature(set_temp, setback, setback_hrs) == 66 -def test_bp_ua_estimates(sample_summary_inputs, sample_billing_periods): - home = engine.Home( - sample_summary_inputs, - sample_billing_periods, +def test_bp_ua_estimates(sample_heat_load_inputs, sample_intermediate_energy_bill_inputs): + home = engine.Home.calculate( + sample_heat_load_inputs, + sample_intermediate_energy_bill_inputs, dhw_input=None, initial_balance_point=58, ) - home.calculate() - - ua_1, ua_2, ua_3 = [bill.ua for bill in home.bills_winter] + ua_1, ua_2, ua_3 = [bill.ua for bill in home.winter_processed_energy_bills] assert home.balance_point == 60.5 assert ua_1 == approx(1455.03, abs=0.01) @@ -332,18 +334,18 @@ def test_bp_ua_estimates(sample_summary_inputs, sample_billing_periods): assert home.stdev_pct == approx(0.0463, abs=0.01) -def test_bp_ua_with_outlier(sample_summary_inputs, sample_billing_periods_with_outlier): - home = engine.Home( - sample_summary_inputs, - sample_billing_periods_with_outlier, +def test_bp_ua_with_outlier( + sample_heat_load_inputs, sample_intermediate_energy_bill_inputs_with_outlier +): + home = engine.Home.calculate( + sample_heat_load_inputs, + sample_intermediate_energy_bill_inputs_with_outlier, dhw_input=None, initial_balance_point=58, ) - home.calculate() - - # expect that ua_1 is considered an outlier and not used in bills_winter - ua_2, ua_3, ua_4 = [bill.ua for bill in home.bills_winter] + # expect that ua_1 is considered an outlier and not used in winter_processed_energy_bills + ua_2, ua_3, ua_4 = [bill.ua for bill in home.winter_processed_energy_bills] assert home.balance_point == 60.5 assert ua_2 == approx(1455.03, abs=0.01) @@ -353,50 +355,50 @@ def test_bp_ua_with_outlier(sample_summary_inputs, sample_billing_periods_with_o assert home.stdev_pct == approx(0.0463, abs=0.01) -def test_convert_to_intermediate_billing_periods( - sample_temp_inputs, sample_normalized_billing_periods +def test_convert_to_intermediate_processed_energy_bills( + sample_temp_inputs, sample_normalized_processed_energy_bill_inputs ): - results = engine.convert_to_intermediate_billing_periods( + results = engine.convert_to_intermediate_processed_energy_bills( sample_temp_inputs, - sample_normalized_billing_periods, + sample_normalized_processed_energy_bill_inputs, FuelType.GAS, ) expected_results = [ - engine.ProcessedBill( - dummy_billing_period_record, + engine.IntermediateEnergyBill( + _dummy_processed_energy_bill_input, [41.7, 41.6, 32, 25.4], 60, AnalysisType.ALLOWED_HEATING_USAGE, False, False, ), - engine.ProcessedBill( - dummy_billing_period_record, + engine.IntermediateEnergyBill( + _dummy_processed_energy_bill_input, [28, 29, 30, 29], 50, AnalysisType.ALLOWED_HEATING_USAGE, False, False, ), - engine.ProcessedBill( - dummy_billing_period_record, + engine.IntermediateEnergyBill( + _dummy_processed_energy_bill_input, [32, 35, 35, 38], 45, AnalysisType.ALLOWED_HEATING_USAGE, False, False, ), - engine.ProcessedBill( - dummy_billing_period_record, + engine.IntermediateEnergyBill( + _dummy_processed_energy_bill_input, [41, 43, 42, 42], 30, AnalysisType.ALLOWED_HEATING_USAGE, False, False, ), - engine.ProcessedBill( - dummy_billing_period_record, + engine.IntermediateEnergyBill( + _dummy_processed_energy_bill_input, [72, 71, 70, 69], 0.96, AnalysisType.ALLOWED_HEATING_USAGE, @@ -415,26 +417,32 @@ def test_convert_to_intermediate_billing_periods( def test_get_outputs_normalized( - sample_summary_inputs, sample_temp_inputs, sample_normalized_billing_periods + sample_heat_load_inputs, + sample_temp_inputs, + sample_normalized_processed_energy_bill_inputs, ): rules_engine_result = engine.get_outputs_normalized( - sample_summary_inputs, + sample_heat_load_inputs, None, sample_temp_inputs, - sample_normalized_billing_periods, + sample_normalized_processed_energy_bill_inputs, ) - assert rules_engine_result.summary_output.estimated_balance_point == 60.5 - assert rules_engine_result.summary_output.whole_home_heat_loss_rate == approx( + assert rules_engine_result.heat_load_output.estimated_balance_point == 60.5 + assert rules_engine_result.heat_load_output.whole_home_heat_loss_rate == approx( 1519.72, abs=1 ) assert ( - rules_engine_result.summary_output.standard_deviation_of_heat_loss_rate + rules_engine_result.heat_load_output.standard_deviation_of_heat_loss_rate == approx(0.0463, abs=0.01) ) - assert rules_engine_result.billing_records[0].usage == 60 - assert rules_engine_result.billing_records[0].whole_home_heat_loss_rate != None - assert rules_engine_result.billing_records[5].whole_home_heat_loss_rate == None + assert rules_engine_result.processed_energy_bills[0].usage == 60 + assert ( + rules_engine_result.processed_energy_bills[0].whole_home_heat_loss_rate != None + ) + assert ( + rules_engine_result.processed_energy_bills[5].whole_home_heat_loss_rate == None + ) @pytest.mark.parametrize( diff --git a/python/tests/test_rules_engine/test_natural_gas.py b/python/tests/test_rules_engine/test_natural_gas.py index d778ae6a..963788a2 100644 --- a/python/tests/test_rules_engine/test_natural_gas.py +++ b/python/tests/test_rules_engine/test_natural_gas.py @@ -89,7 +89,7 @@ def test_balance_point_natural_gas(data: Example) -> None: data.summary, data.temperature_data, data.natural_gas_usage ) assert data.summary.estimated_balance_point == approx( - rules_engine_result.summary_output.estimated_balance_point, abs=0.1 + rules_engine_result.heat_load_output.estimated_balance_point, abs=0.1 ) @@ -97,7 +97,7 @@ def test_whole_home_heat_loss_rate_natural_gas(data: Example) -> None: rules_engine_result = engine.get_outputs_natural_gas( data.summary, data.temperature_data, data.natural_gas_usage ) - assert rules_engine_result.summary_output.whole_home_heat_loss_rate == approx( + assert rules_engine_result.heat_load_output.whole_home_heat_loss_rate == approx( data.summary.whole_home_heat_loss_rate, abs=1 ) @@ -107,7 +107,7 @@ def test_standard_deviation_of_heat_loss_rate_natural_gas(data: Example) -> None data.summary, data.temperature_data, data.natural_gas_usage ) assert ( - rules_engine_result.summary_output.standard_deviation_of_heat_loss_rate + rules_engine_result.heat_load_output.standard_deviation_of_heat_loss_rate == approx(data.summary.standard_deviation_of_heat_loss_rate, abs=0.01) ) @@ -116,7 +116,7 @@ def test_difference_between_ti_and_tbp_natural_gas(data: Example) -> None: rules_engine_result = engine.get_outputs_natural_gas( data.summary, data.temperature_data, data.natural_gas_usage ) - assert rules_engine_result.summary_output.difference_between_ti_and_tbp == approx( + assert rules_engine_result.heat_load_output.difference_between_ti_and_tbp == approx( data.summary.difference_between_ti_and_tbp, abs=0.1 ) @@ -125,7 +125,7 @@ def test_average_heat_load_natural_gas(data: Example) -> None: rules_engine_result = engine.get_outputs_natural_gas( data.summary, data.temperature_data, data.natural_gas_usage ) - assert rules_engine_result.summary_output.average_heat_load == approx( + assert rules_engine_result.heat_load_output.average_heat_load == approx( data.summary.average_heat_load, abs=1 ) @@ -134,7 +134,7 @@ def test_design_temperature_natural_gas(data: Example) -> None: rules_engine_result = engine.get_outputs_natural_gas( data.summary, data.temperature_data, data.natural_gas_usage ) - assert rules_engine_result.summary_output.design_temperature == approx( + assert rules_engine_result.heat_load_output.design_temperature == approx( data.summary.design_temperature, abs=0.1 ) @@ -143,18 +143,18 @@ def test_maximum_heat_load_natural_gas(data: Example) -> None: rules_engine_result = engine.get_outputs_natural_gas( data.summary, data.temperature_data, data.natural_gas_usage ) - assert rules_engine_result.summary_output.maximum_heat_load == approx( + assert rules_engine_result.heat_load_output.maximum_heat_load == approx( data.summary.maximum_heat_load, abs=1 ) -def test_billing_records_whole_home_heat_loss_rate(data: Example) -> None: +def test_processed_energy_bills_whole_home_heat_loss_rate(data: Example) -> None: rules_engine_result = engine.get_outputs_natural_gas( data.summary, data.temperature_data, data.natural_gas_usage ) data_iter = iter(data.natural_gas_usage.records) - for result in rules_engine_result.billing_records: + for result in rules_engine_result.processed_energy_bills: example = next(data_iter) whole_home_heat_loss_rate = ( example.whole_home_heat_loss_rate diff --git a/python/tests/test_rules_engine/test_utils.py b/python/tests/test_rules_engine/test_utils.py index 32a6c81b..08efa0b8 100644 --- a/python/tests/test_rules_engine/test_utils.py +++ b/python/tests/test_rules_engine/test_utils.py @@ -5,17 +5,17 @@ from rules_engine.pydantic_models import ( FuelType, + HeatLoadInput, + HeatLoadOutput, NaturalGasBillingInput, NaturalGasBillingRecordInput, OilPropaneBillingInput, OilPropaneBillingRecordInput, - SummaryInput, - SummaryOutput, TemperatureInput, ) - -class Summary(SummaryInput, SummaryOutput): +# TODO: Need to be reviewed +class Summary(HeatLoadInput, HeatLoadOutput): """ Holds summary.json information alongside a string referring to a local weather station. @@ -98,7 +98,7 @@ def load_fuel_billing_example_input( # Choose the correct billing period heat loss (aka "ua") # column based on the estimated balance point provided in - # SummaryOutput + # HeatLoadOutput ua_column_name = None # First we will look for an exact match to the value of # the estimated balance point