From 275148b06a11164a48020c27a2faa6bd11d5cb74 Mon Sep 17 00:00:00 2001 From: Kevin Hashimoto Date: Thu, 2 Jan 2025 09:29:05 -0800 Subject: [PATCH 1/4] fix: end use validation --- backend/lcfs/web/api/fuel_supply/schema.py | 2 +- frontend/src/assets/locales/en/fuelSupply.json | 4 ++-- frontend/src/views/FuelSupplies/_schema.jsx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/lcfs/web/api/fuel_supply/schema.py b/backend/lcfs/web/api/fuel_supply/schema.py index 60592dffe..134b87968 100644 --- a/backend/lcfs/web/api/fuel_supply/schema.py +++ b/backend/lcfs/web/api/fuel_supply/schema.py @@ -117,7 +117,7 @@ class FuelSupplyCreateUpdateSchema(BaseSchema): compliance_period: Optional[str] = None fuel_type_id: int fuel_category_id: int - end_use_id: Optional[int] = None + end_use_id: int provision_of_the_act_id: int quantity: int units: str diff --git a/frontend/src/assets/locales/en/fuelSupply.json b/frontend/src/assets/locales/en/fuelSupply.json index 92ed0968f..15ba14b59 100644 --- a/frontend/src/assets/locales/en/fuelSupply.json +++ b/frontend/src/assets/locales/en/fuelSupply.json @@ -16,7 +16,7 @@ "fuelType": "Fuel type", "fuelTypeOther": "Fuel type other", "fuelCategoryId": "Fuel category", - "endUse": "End use", + "endUseId": "End use", "provisionOfTheActId": "Determining carbon intensity", "fuelCode": "Fuel code", "quantity": "Quantity supplied", @@ -33,4 +33,4 @@ "validateMsg": { "isRequired": "{{field}} is required" } -} +} \ No newline at end of file diff --git a/frontend/src/views/FuelSupplies/_schema.jsx b/frontend/src/views/FuelSupplies/_schema.jsx index ba1b52df9..1d59a83a0 100644 --- a/frontend/src/views/FuelSupplies/_schema.jsx +++ b/frontend/src/views/FuelSupplies/_schema.jsx @@ -193,7 +193,7 @@ export const fuelSupplyColDefs = (optionsData, errors, warnings) => [ { field: 'endUseType', headerComponent: RequiredHeader, - headerName: i18n.t('fuelSupply:fuelSupplyColLabels.endUse'), + headerName: i18n.t('fuelSupply:fuelSupplyColLabels.endUseId'), cellEditorParams: (params) => ({ options: [ ...new Set( From 3926f56f94e5764c978879c02c98348ced2ee4dc Mon Sep 17 00:00:00 2001 From: Kevin Hashimoto Date: Thu, 2 Jan 2025 12:25:06 -0800 Subject: [PATCH 2/4] fix: add missing eer data --- .../db/seeders/common/seed_fuel_data.json | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/backend/lcfs/db/seeders/common/seed_fuel_data.json b/backend/lcfs/db/seeders/common/seed_fuel_data.json index 57b1178c4..1ff36aa84 100644 --- a/backend/lcfs/db/seeders/common/seed_fuel_data.json +++ b/backend/lcfs/db/seeders/common/seed_fuel_data.json @@ -377,6 +377,10 @@ "end_use_type_id": 23, "type": "Other (i.e. road transportation)", "intended_use": true + }, + { + "end_use_type_id": 24, + "type": "Any" } ], "unit_of_measures": [ @@ -487,6 +491,7 @@ "eer_id": 1, "fuel_category_id": 1, "fuel_type_id": 2, + "end_use_type_id": 24, "ratio": 0.9 }, { @@ -521,12 +526,14 @@ "eer_id": 6, "fuel_category_id": 1, "fuel_type_id": 13, + "end_use_type_id": 24, "ratio": 0.9 }, { "eer_id": 7, "fuel_category_id": 2, "fuel_type_id": 2, + "end_use_type_id": 24, "ratio": 0.9 }, { @@ -610,18 +617,21 @@ "eer_id": 19, "fuel_category_id": 2, "fuel_type_id": 13, + "end_use_type_id": 24, "ratio": 0.9 }, { "eer_id": 20, "fuel_category_id": 3, "fuel_type_id": 3, + "end_use_type_id": 24, "ratio": 2.5 }, { "eer_id": 21, "fuel_category_id": 3, "fuel_type_id": 11, + "end_use_type_id": 24, "ratio": 1.0 }, { @@ -686,6 +696,97 @@ "fuel_type_id": 7, "end_use_type_id": 23, "ratio": 0.9 + }, + { + "eer_id": 31, + "fuel_category_id": 2, + "fuel_type_id": 1, + "end_use_type_id": 24, + "ratio": 1.0 + }, + { + "eer_id": 32, + "fuel_category_id": 2, + "fuel_type_id": 5, + "end_use_type_id": 24, + "ratio": 1.0 + }, + { + "eer_id": 33, + "fuel_category_id": 3, + "fuel_type_id": 6, + "end_use_type_id": 24, + "ratio": 1.0 + }, + { + "eer_id": 34, + "fuel_category_id": 1, + "fuel_type_id": 14, + "end_use_type_id": 24, + "ratio": 1.0 + }, + { + "eer_id": 35, + "fuel_category_id": 1, + "fuel_type_id": 15, + "end_use_type_id": 24, + "ratio": 1.0 + }, + { + "eer_id": 36, + "fuel_category_id": 2, + "fuel_type_id": 16, + "end_use_type_id": 24, + "ratio": 1.0 + }, + { + "eer_id": 37, + "fuel_category_id": 1, + "fuel_type_id": 17, + "end_use_type_id": 24, + "ratio": 1.0 + }, + { + "eer_id": 38, + "fuel_category_id": 3, + "fuel_type_id": 18, + "end_use_type_id": 24, + "ratio": 1.0 + }, + { + "eer_id": 39, + "fuel_category_id": 1, + "fuel_type_id": 19, + "end_use_type_id": 24, + "ratio": 1.0 + }, + { + "eer_id": 40, + "fuel_category_id": 2, + "fuel_type_id": 19, + "end_use_type_id": 24, + "ratio": 1.0 + }, + { + "eer_id": 41, + "fuel_category_id": 3, + "fuel_type_id": 19, + "end_use_type_id": 24, + "ratio": 1.0 + }, + { + "eer_id": 42, + "fuel_category_id": 2, + "fuel_type_id": 20, + "end_use_type_id": 24, + "ratio": 1.0 + }, + { + "eer_id": 43, + "fuel_category_id": 1, + "fuel_type_id": 4, + "end_use_type_id": 24, + "ratio": 1.0 } ], "energy_densities": [ From a200ce6ac8d0927b6d65241f6248a2e3ae6fea9e Mon Sep 17 00:00:00 2001 From: Kevin Hashimoto Date: Thu, 2 Jan 2025 14:49:06 -0800 Subject: [PATCH 3/4] feat: auto select end use --- backend/lcfs/web/api/fuel_supply/services.py | 15 +++++--- .../FuelSupplies/AddEditFuelSupplies.jsx | 37 ++++++++++++++++--- frontend/src/views/FuelSupplies/_schema.jsx | 10 ++--- 3 files changed, 44 insertions(+), 18 deletions(-) diff --git a/backend/lcfs/web/api/fuel_supply/services.py b/backend/lcfs/web/api/fuel_supply/services.py index 67a2b7efa..3eb146e1e 100644 --- a/backend/lcfs/web/api/fuel_supply/services.py +++ b/backend/lcfs/web/api/fuel_supply/services.py @@ -67,13 +67,15 @@ def fuel_type_row_mapper(self, compliance_period, fuel_types, row): ) eer = EnergyEffectivenessRatioSchema( eer_id=row_data["eer_id"], - energy_effectiveness_ratio=round(row_data["energy_effectiveness_ratio"], 2), + energy_effectiveness_ratio=round( + row_data["energy_effectiveness_ratio"], 2), fuel_category=fuel_category, end_use_type=end_use_type, ) tci = TargetCarbonIntensitySchema( target_carbon_intensity_id=row_data["target_carbon_intensity_id"], - target_carbon_intensity=round(row_data["target_carbon_intensity"], 2), + target_carbon_intensity=round( + row_data["target_carbon_intensity"], 2), reduction_target_percentage=round( row_data["reduction_target_percentage"], 2 ), @@ -94,7 +96,8 @@ def fuel_type_row_mapper(self, compliance_period, fuel_types, row): ) # Find the existing fuel type if it exists existing_fuel_type = next( - (ft for ft in fuel_types if ft.fuel_type == row_data["fuel_type"]), None + (ft for ft in fuel_types if ft.fuel_type == + row_data["fuel_type"]), None ) if existing_fuel_type: @@ -135,8 +138,7 @@ def fuel_type_row_mapper(self, compliance_period, fuel_types, row): ( e for e in existing_fuel_type.eer_ratios - if e.end_use_type == row_data["end_use_type"] - and e.fuel_category == fuel_category + if e.eer_id == eer.eer_id ), None, ) @@ -258,7 +260,8 @@ async def get_fuel_supplies_paginated( size=pagination.size, total=total_count, total_pages=( - math.ceil(total_count / pagination.size) if total_count > 0 else 0 + math.ceil(total_count / + pagination.size) if total_count > 0 else 0 ), ), fuel_supplies=[ diff --git a/frontend/src/views/FuelSupplies/AddEditFuelSupplies.jsx b/frontend/src/views/FuelSupplies/AddEditFuelSupplies.jsx index 2c3c90356..5c6e4ad69 100644 --- a/frontend/src/views/FuelSupplies/AddEditFuelSupplies.jsx +++ b/frontend/src/views/FuelSupplies/AddEditFuelSupplies.jsx @@ -103,7 +103,7 @@ export const AddEditFuelSupplies = () => { item.fuelType?.fuelType === 'Other' ? item.fuelTypeOther : null, provisionOfTheAct: item.provisionOfTheAct?.name, fuelCode: item.fuelCode?.fuelCode, - endUse: item.endUse?.type || 'Any', + endUse: item.endUse?.type, id: uuid() })) setRowData([...updatedRowData, { id: uuid() }]) @@ -140,7 +140,7 @@ export const AddEditFuelSupplies = () => { item.fuelType?.fuelType === 'Other' ? item.fuelTypeOther : null, provisionOfTheAct: item.provisionOfTheAct?.name, fuelCode: item.fuelCode?.fuelCode, - endUse: item.endUse?.type || 'Any', + endUse: item.endUse?.type, id: uuid() })) setRowData(updatedRowData) @@ -161,12 +161,39 @@ export const AddEditFuelSupplies = () => { (item) => item.fuelCategory ) + const endUseTypes = selectedFuelType.eerRatios.map( + (item) => item.endUseType + ) + // Set to null if multiple options, otherwise use first item - const categoryValue = fuelCategoryOptions.length === 1 - ? fuelCategoryOptions[0] - : null + const categoryValue = + fuelCategoryOptions.length === 1 ? fuelCategoryOptions[0] : null + const endUseValue = + endUseTypes.length === 1 ? endUseTypes[0].type : null params.node.setDataValue('fuelCategory', categoryValue) + params.node.setDataValue('endUseType', endUseValue) + } + } + + if (params.column.colId === 'fuelCategory') { + const selectedFuelType = optionsData?.fuelTypes?.find( + (obj) => params.node.data.fuelType === obj.fuelType + ) + + if (selectedFuelType) { + const endUseTypes = selectedFuelType.eerRatios + .filter( + (item) => + item.fuelCategory.fuelCategory === params.data.fuelCategory + ) + .map((item) => item.endUseType) + + // Set to null if multiple options, otherwise use first item + const endUseValue = + endUseTypes.length === 1 ? endUseTypes[0].type : null + + params.node.setDataValue('endUseType', endUseValue) } } }, diff --git a/frontend/src/views/FuelSupplies/_schema.jsx b/frontend/src/views/FuelSupplies/_schema.jsx index 1d59a83a0..9cbe65cbe 100644 --- a/frontend/src/views/FuelSupplies/_schema.jsx +++ b/frontend/src/views/FuelSupplies/_schema.jsx @@ -206,7 +206,7 @@ export const fuelSupplyColDefs = (optionsData, errors, warnings) => [ ?.map((item) => item.endUseType?.type) .sort() ) - ].filter((item) => item != null) || ['Any'], + ].filter((item) => item != null), multiple: false, disableCloseOnSelect: false, freeSolo: false, @@ -219,14 +219,10 @@ export const fuelSupplyColDefs = (optionsData, errors, warnings) => [ cellStyle: (params) => StandardCellWarningAndErrors(params, errors, warnings), suppressKeyboardEvent, - valueGetter: (params) => { - return params.colDef?.cellEditorParams(params).options.length < 1 - ? 'Any' - : params.data?.endUseType?.type - }, + valueGetter: (params) => params.data.endUseType?.type, editable: (params) => { const cellParams = params.colDef?.cellEditorParams(params) - return cellParams.options.length > 0 + return cellParams.options.length > 1 }, valueSetter: (params) => { if (params.newValue) { From 031ebdf91006bc8a60fa2ff64f2087da077902aa Mon Sep 17 00:00:00 2001 From: Kevin Hashimoto Date: Thu, 2 Jan 2025 15:23:57 -0800 Subject: [PATCH 4/4] fix: backend tests --- .../fuel_supply/test_fuel_supplies_repo.py | 4 +++- .../test_fuel_supplies_services.py | 23 +++++++++++++------ .../test_fuel_supplies_validation.py | 4 ++++ .../fuel_supply/test_fuel_supplies_view.py | 6 ++++- 4 files changed, 28 insertions(+), 9 deletions(-) diff --git a/backend/lcfs/tests/fuel_supply/test_fuel_supplies_repo.py b/backend/lcfs/tests/fuel_supply/test_fuel_supplies_repo.py index e87050cfd..f74a763c2 100644 --- a/backend/lcfs/tests/fuel_supply/test_fuel_supplies_repo.py +++ b/backend/lcfs/tests/fuel_supply/test_fuel_supplies_repo.py @@ -74,6 +74,7 @@ async def test_check_duplicate(fuel_supply_repo, mock_db_session): compliance_report_id=1, fuel_type_id=1, fuel_category_id=1, + end_use_id=24, provision_of_the_act_id=1, quantity=1000, units="L", @@ -82,7 +83,8 @@ async def test_check_duplicate(fuel_supply_repo, mock_db_session): # Set up the mock chain using regular MagicMock since the chained methods are sync mock_result_chain = MagicMock() mock_result_chain.scalars = MagicMock(return_value=mock_result_chain) - mock_result_chain.first = MagicMock(return_value=MagicMock(spec=FuelSupply)) + mock_result_chain.first = MagicMock( + return_value=MagicMock(spec=FuelSupply)) # Define an async execute function that returns our mock chain async def mock_execute(*args, **kwargs): diff --git a/backend/lcfs/tests/fuel_supply/test_fuel_supplies_services.py b/backend/lcfs/tests/fuel_supply/test_fuel_supplies_services.py index 9b5114a84..7f6407694 100644 --- a/backend/lcfs/tests/fuel_supply/test_fuel_supplies_services.py +++ b/backend/lcfs/tests/fuel_supply/test_fuel_supplies_services.py @@ -67,13 +67,15 @@ def fuel_supply_service(): @pytest.mark.anyio async def test_get_fuel_supply_options(fuel_supply_service): service, mock_repo, mock_fuel_code_repo = fuel_supply_service - mock_repo.get_fuel_supply_table_options = AsyncMock(return_value={"fuel_types": []}) + mock_repo.get_fuel_supply_table_options = AsyncMock( + return_value={"fuel_types": []}) compliance_period = "2023" response = await service.get_fuel_supply_options(compliance_period) assert isinstance(response, FuelTypeOptionsResponse) - mock_repo.get_fuel_supply_table_options.assert_awaited_once_with(compliance_period) + mock_repo.get_fuel_supply_table_options.assert_awaited_once_with( + compliance_period) # Asynchronous test for get_fuel_supply_list @@ -90,7 +92,8 @@ async def test_get_fuel_supply_list(fuel_supply_service): response = await service.get_fuel_supply_list(compliance_report_id) assert isinstance(response, FuelSuppliesSchema) - mock_repo.get_fuel_supply_list.assert_awaited_once_with(compliance_report_id) + mock_repo.get_fuel_supply_list.assert_awaited_once_with( + compliance_report_id) @pytest.mark.anyio @@ -102,6 +105,7 @@ async def test_update_fuel_supply_not_found(fuel_supply_action_service): compliance_report_id=1, fuel_type_id=1, fuel_category_id=1, + end_use_id=24, provision_of_the_act_id=1, quantity=2000, units="L", @@ -274,8 +278,10 @@ async def test_create_fuel_supply(fuel_supply_action_service): "fuelCode": "FUEL123", "carbonIntensity": 15.0, }, - provisionOfTheAct={"provisionOfTheActId": 1, "name": "Act Provision"}, - endUseType={"endUseTypeId": 1, "type": "Transport", "subType": "Personal"}, + provisionOfTheAct={"provisionOfTheActId": 1, + "name": "Act Provision"}, + endUseType={"endUseTypeId": 1, + "type": "Transport", "subType": "Personal"}, units="L", compliancePeriod="2024", ) @@ -290,7 +296,8 @@ async def test_create_fuel_supply(fuel_supply_action_service): ) mock_density = MagicMock(spec=EnergyDensity) mock_density.density = 30.0 - mock_fuel_code_repo.get_energy_density = AsyncMock(return_value=mock_density) + mock_fuel_code_repo.get_energy_density = AsyncMock( + return_value=mock_density) user_type = UserTypeEnum.SUPPLIER @@ -315,6 +322,7 @@ async def test_delete_fuel_supply(fuel_supply_action_service): group_uuid="some-uuid", fuel_type_id=1, fuel_category_id=1, + end_use_id=24, provision_of_the_act_id=1, quantity=1000, units="L", @@ -338,5 +346,6 @@ async def test_delete_fuel_supply(fuel_supply_action_service): assert response.success is True assert response.message == "Marked as deleted." - mock_repo.get_latest_fuel_supply_by_group_uuid.assert_awaited_once_with("some-uuid") + mock_repo.get_latest_fuel_supply_by_group_uuid.assert_awaited_once_with( + "some-uuid") mock_repo.create_fuel_supply.assert_awaited_once() diff --git a/backend/lcfs/tests/fuel_supply/test_fuel_supplies_validation.py b/backend/lcfs/tests/fuel_supply/test_fuel_supplies_validation.py index eefa25c91..3fa217dd4 100644 --- a/backend/lcfs/tests/fuel_supply/test_fuel_supplies_validation.py +++ b/backend/lcfs/tests/fuel_supply/test_fuel_supplies_validation.py @@ -29,6 +29,7 @@ async def test_check_duplicate(fuel_supply_validation): compliance_report_id=1, fuel_type_id=1, fuel_category_id=1, + end_use_id=24, provision_of_the_act_id=1, quantity=2000, units="L", @@ -54,6 +55,7 @@ async def test_validate_other_recognized_type(fuel_supply_validation): compliance_report_id=1, fuel_type_id=1, # Some recognized type ID fuel_category_id=1, + end_use_id=24, provision_of_the_act_id=1, quantity=2000, units="L", @@ -76,6 +78,7 @@ async def test_validate_other_unrecognized_type_with_other(fuel_supply_validatio compliance_report_id=1, fuel_type_id=99, # Assume 99 is unrecognized "Other" type fuel_category_id=1, + end_use_id=24, provision_of_the_act_id=1, quantity=2000, units="L", @@ -99,6 +102,7 @@ async def test_validate_other_unrecognized_type_missing_other(fuel_supply_valida compliance_report_id=1, fuel_type_id=99, # Assume 99 is unrecognized "Other" type fuel_category_id=1, + end_use_id=24, provision_of_the_act_id=1, quantity=2000, units="L", diff --git a/backend/lcfs/tests/fuel_supply/test_fuel_supplies_view.py b/backend/lcfs/tests/fuel_supply/test_fuel_supplies_view.py index 81fa58b7a..9fa2efa2e 100644 --- a/backend/lcfs/tests/fuel_supply/test_fuel_supplies_view.py +++ b/backend/lcfs/tests/fuel_supply/test_fuel_supplies_view.py @@ -47,6 +47,7 @@ async def test_save_fuel_supply_row_create( "compliance_report_id": 1, "fuel_type_id": 1, "fuel_category_id": 1, + "end_use_id": 24, "provision_of_the_act_id": 1, "quantity": 1000, "units": "L", @@ -104,6 +105,7 @@ async def test_save_fuel_supply_row_update( "fuel_supply_id": 123, "fuel_type_id": 1, "fuel_category_id": 1, + "end_use_id": 24, "provision_of_the_act_id": 1, "quantity": 1000, "units": "L", @@ -160,6 +162,7 @@ async def test_save_fuel_supply_row_delete( "fuel_supply_id": 123, "fuel_type_id": 1, "fuel_category_id": 1, + "end_use_id": 24, "provision_of_the_act_id": 1, "quantity": 1000, "units": "L", @@ -191,7 +194,8 @@ async def test_save_fuel_supply_row_delete( assert response.status_code == status.HTTP_201_CREATED data = response.json() - assert data == {"success": True, "message": "Fuel supply row deleted successfully"} + assert data == {"success": True, + "message": "Fuel supply row deleted successfully"} @pytest.mark.anyio