Skip to content

Commit

Permalink
fix: Use compliance period from the Compliance Report
Browse files Browse the repository at this point in the history
* Pass the compliance period from the report instead of reading from the front end data
* Add unique constraint on 1 TCI per Category per Period
* Change query to return a single TCI and fail otherwise
* Update tests
  • Loading branch information
dhaselhan committed Dec 19, 2024
1 parent a98abf4 commit b6d0355
Show file tree
Hide file tree
Showing 9 changed files with 87 additions and 86 deletions.
13 changes: 9 additions & 4 deletions backend/lcfs/db/models/fuel/TargetCarbonIntensity.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
from sqlalchemy import Column, Integer, Float, ForeignKey, Numeric
from sqlalchemy import Column, Integer, Float, ForeignKey, Numeric, UniqueConstraint
from lcfs.db.base import BaseModel, Auditable, EffectiveDates
from sqlalchemy.orm import relationship


class TargetCarbonIntensity(BaseModel, Auditable, EffectiveDates):
__tablename__ = "target_carbon_intensity"
__table_args__ = {
"comment": "Target carbon intensity values for various fuel categories"
}
__table_args__ = (
UniqueConstraint(
"compliance_period_id",
"fuel_category_id",
name="uq_target_carbon_intensity_compliance_fuel",
),
{"comment": "Target carbon intensity values for various fuel categories"},
)

target_carbon_intensity_id = Column(Integer, primary_key=True, autoincrement=True)
compliance_period_id = Column(
Expand Down
28 changes: 8 additions & 20 deletions backend/lcfs/tests/fuel_code/test_fuel_code_repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -579,17 +579,16 @@ async def test_get_energy_effectiveness_ratio(fuel_code_repo, mock_db):


@pytest.mark.anyio
async def test_get_target_carbon_intensities(fuel_code_repo, mock_db):
async def test_get_target_carbon_intensity(fuel_code_repo, mock_db):
tci = TargetCarbonIntensity(
target_carbon_intensity_id=1, target_carbon_intensity=50.0
)
mock_result = MagicMock()
mock_result.scalars.return_value.all.return_value = [tci]
mock_result.scalar_one.return_value = tci
mock_db.execute.return_value = mock_result

result = await fuel_code_repo.get_target_carbon_intensities(1, "2024")
assert len(result) == 1
assert result[0] == tci
result = await fuel_code_repo.get_target_carbon_intensity(1, "2024")
assert result == tci


@pytest.mark.anyio
Expand Down Expand Up @@ -618,14 +617,8 @@ async def test_get_standardized_fuel_data(fuel_code_repo, mock_db):
),
# target carbon intensities
MagicMock(
scalars=MagicMock(
return_value=MagicMock(
all=MagicMock(
return_value=[
TargetCarbonIntensity(target_carbon_intensity=50.0)
]
)
)
scalar_one=MagicMock(
return_value=TargetCarbonIntensity(target_carbon_intensity=50.0)
)
),
# additional carbon intensity
Expand Down Expand Up @@ -656,7 +649,6 @@ async def test_get_standardized_fuel_data_unrecognized(fuel_code_repo, mock_db):
mock_fuel_type = FuelType(
fuel_type_id=1,
fuel_type="UnknownFuel",
default_carbon_intensity=None,
unrecognized=True,
)

Expand Down Expand Up @@ -690,12 +682,8 @@ async def test_get_standardized_fuel_data_unrecognized(fuel_code_repo, mock_db):
)
# Target Carbon Intensities
tci_result = MagicMock(
scalars=MagicMock(
return_value=MagicMock(
all=MagicMock(
return_value=[TargetCarbonIntensity(target_carbon_intensity=50.0)]
)
)
scalar_one=MagicMock(
return_value=TargetCarbonIntensity(target_carbon_intensity=50.0)
)
)
# Additional Carbon Intensity
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
FUEL_SUPPLY_EXCLUDE_FIELDS = {
"id",
"fuel_supply_id",
"compliance_period",
"deleted",
"group_uuid",
"user_type",
Expand Down Expand Up @@ -129,14 +128,12 @@ def create_sample_fs_data():
fuel_category_id=1,
end_use_id=1,
fuel_code_id=1,
compliance_period="2024", # Schema-only field
quantity=1000.0,
quantity=1000,
units="L",
energy_density=35.0,
group_uuid=str(uuid4()),
version=0,
provisionOfTheActId=123,
exportDate=datetime.now().date(),
provision_of_the_act_id=123,
)


Expand Down Expand Up @@ -184,7 +181,9 @@ async def test_create_fuel_supply_success(
mock_repo.create_fuel_supply.return_value = created_supply

# Call the method under test
result = await fuel_supply_action_service.create_fuel_supply(fe_data, user_type)
result = await fuel_supply_action_service.create_fuel_supply(
fe_data, user_type, "2024"
)

# Assign mocked related objects for schema validation
result.fuel_type = {
Expand All @@ -201,7 +200,7 @@ async def test_create_fuel_supply_success(
fuel_category_id=fe_data.fuel_category_id,
end_use_id=fe_data.end_use_id,
fuel_code_id=fe_data.fuel_code_id,
compliance_period=fe_data.compliance_period,
compliance_period="2024",
)
mock_repo.create_fuel_supply.assert_awaited_once()
# Ensure compliance units were calculated correctly
Expand Down Expand Up @@ -266,7 +265,9 @@ async def test_update_fuel_supply_success_existing_report(
mock_repo.update_fuel_supply.return_value = updated_supply

# Call the method under test
result = await fuel_supply_action_service.update_fuel_supply(fe_data, user_type)
result = await fuel_supply_action_service.update_fuel_supply(
fe_data, user_type, "2024"
)

# Assign mocked related objects for schema validation
result.fuel_type = {
Expand Down Expand Up @@ -348,7 +349,9 @@ async def test_update_fuel_supply_create_new_version(
mock_repo.create_fuel_supply.return_value = new_supply

# Call the method under test
result = await fuel_supply_action_service.update_fuel_supply(fe_data, user_type)
result = await fuel_supply_action_service.update_fuel_supply(
fe_data, user_type, "2024"
)

# Assign mocked related objects for schema validation
result.fuel_type = {
Expand Down Expand Up @@ -489,7 +492,7 @@ async def test_populate_fuel_supply_fields(

# Call the method under test
populated_supply = await fuel_supply_action_service._populate_fuel_supply_fields(
fuel_supply, fe_data
fuel_supply, fe_data, "2024"
)

# Assertions
Expand All @@ -507,13 +510,13 @@ async def test_populate_fuel_supply_fields(
fuel_category_id=fuel_supply.fuel_category_id,
end_use_id=fuel_supply.end_use_id,
fuel_code_id=fuel_supply.fuel_code_id,
compliance_period=fe_data.compliance_period,
compliance_period="2024",
)


@pytest.mark.anyio
@pytest.mark.parametrize("case", test_cases)
async def test_compliance_units_calculation(
async def test_create_compliance_units_calculation(
case, fuel_supply_action_service, mock_repo, mock_fuel_code_repo
):
fe_data = FuelSupplyCreateUpdateSchema(
Expand All @@ -522,14 +525,12 @@ async def test_compliance_units_calculation(
fuel_category_id=2, # Adjusted to match the mock fuel_category
end_use_id=1,
fuel_code_id=1,
compliance_period="2024", # Schema-only field
quantity=case["input"]["quantity"],
units=case["input"]["units"],
energy_density=case["input"]["energy_density"],
group_uuid=str(uuid4()),
version=0,
provisionOfTheActId=123,
exportDate=datetime.now().date(),
provision_of_the_act_id=123,
)

# Mock standardized fuel data
Expand All @@ -541,9 +542,6 @@ async def test_compliance_units_calculation(
uci=None,
)

# Exclude invalid fields and set related objects
fe_data_dict = fe_data.model_dump(exclude=FUEL_SUPPLY_EXCLUDE_FIELDS)

# Mock the create_fuel_supply method to perform actual calculation
async def create_fuel_supply_side_effect(fuel_supply):
fuel_supply.fuel_supply_id = 1
Expand All @@ -561,7 +559,7 @@ async def create_fuel_supply_side_effect(fuel_supply):

# Call the service to create the fuel supply
result = await fuel_supply_action_service.create_fuel_supply(
fe_data, UserTypeEnum.SUPPLIER
fe_data, UserTypeEnum.SUPPLIER, "2024"
)

# Assign mocked related objects for schema validation
Expand All @@ -582,7 +580,7 @@ async def create_fuel_supply_side_effect(fuel_supply):
fuel_category_id=fe_data.fuel_category_id,
end_use_id=fe_data.end_use_id,
fuel_code_id=fe_data.fuel_code_id,
compliance_period=fe_data.compliance_period,
compliance_period="2024",
)
mock_repo.create_fuel_supply.assert_awaited_once()

Expand Down
24 changes: 5 additions & 19 deletions backend/lcfs/tests/fuel_supply/test_fuel_supplies_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ async def test_update_fuel_supply_not_found(fuel_supply_action_service):
user_type = UserTypeEnum.SUPPLIER

with pytest.raises(HTTPException) as exc_info:
await service.update_fuel_supply(fs_data, user_type)
await service.update_fuel_supply(fs_data, user_type, "2024")

assert exc_info.value.status_code == 404
assert exc_info.value.detail == "Fuel supply record not found."
Expand All @@ -133,7 +133,6 @@ async def test_update_fuel_supply_success(fuel_supply_action_service):
end_use_id=1,
quantity=1000,
units="L",
fuel_type_other=None,
ci_of_fuel=10.5,
energy_density=30.0,
eer=1.0,
Expand Down Expand Up @@ -226,7 +225,7 @@ async def test_update_fuel_supply_success(fuel_supply_action_service):
user_type = UserTypeEnum.SUPPLIER

# Call the service method
response = await service.update_fuel_supply(fs_data, user_type)
response = await service.update_fuel_supply(fs_data, user_type, "2024")

# Assertions
assert isinstance(response, FuelSupplyResponseSchema)
Expand All @@ -245,7 +244,7 @@ async def test_update_fuel_supply_success(fuel_supply_action_service):
fuel_category_id=fs_data.fuel_category_id,
end_use_id=fs_data.end_use_id,
fuel_code_id=fs_data.fuel_code_id,
compliance_period=fs_data.compliance_period,
compliance_period="2024",
)
mock_repo.update_fuel_supply.assert_awaited_once_with(existing_fuel_supply)

Expand All @@ -263,19 +262,6 @@ async def test_create_fuel_supply(fuel_supply_action_service):
fuel_type_other=None,
units="L",
)
new_fuel_supply = FuelSupply(
compliance_report_id=1,
fuel_type_id=1,
fuel_category_id=1,
provision_of_the_act_id=1,
quantity=2000,
units="L",
fuel_type_other=None,
group_uuid=str(uuid.uuid4()),
version=0,
user_type=UserTypeEnum.SUPPLIER,
action_type=ActionTypeEnum.CREATE,
)
mock_repo.create_fuel_supply = AsyncMock(
return_value=MagicMock(
fuel_supply_id=1,
Expand Down Expand Up @@ -310,7 +296,7 @@ async def test_create_fuel_supply(fuel_supply_action_service):

user_type = UserTypeEnum.SUPPLIER

response = await service.create_fuel_supply(fs_data, user_type)
response = await service.create_fuel_supply(fs_data, user_type, "2024")

assert isinstance(response, FuelSupplyResponseSchema)
mock_repo.create_fuel_supply.assert_awaited_once()
Expand All @@ -319,7 +305,7 @@ async def test_create_fuel_supply(fuel_supply_action_service):
fuel_category_id=fs_data.fuel_category_id,
end_use_id=fs_data.end_use_id,
fuel_code_id=fs_data.fuel_code_id,
compliance_period=fs_data.compliance_period,
compliance_period="2024",
)


Expand Down
13 changes: 11 additions & 2 deletions backend/lcfs/web/api/compliance_report/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,19 @@ async def validate_organization_access(self, compliance_report_id: int):
)

organization_id = compliance_report.organization_id
user_organization_id = self.request.user.organization.organization_id if self.request.user.organization else None
user_organization_id = (
self.request.user.organization.organization_id
if self.request.user.organization
else None
)

if not user_has_roles(self.request.user, [RoleEnum.GOVERNMENT]) and organization_id != user_organization_id:
if (
not user_has_roles(self.request.user, [RoleEnum.GOVERNMENT])
and organization_id != user_organization_id
):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="User does not have access to this compliance report.",
)

return compliance_report
15 changes: 5 additions & 10 deletions backend/lcfs/web/api/fuel_code/repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -806,9 +806,9 @@ async def get_energy_effectiveness_ratio(
return energy_effectiveness_ratio

@repo_handler
async def get_target_carbon_intensities(
async def get_target_carbon_intensity(
self, fuel_category_id: int, compliance_period: str
) -> List[TargetCarbonIntensity]:
) -> TargetCarbonIntensity:

compliance_period_id_subquery = (
select(CompliancePeriod.compliance_period_id)
Expand All @@ -830,7 +830,7 @@ async def get_target_carbon_intensities(
)
result = await self.db.execute(stmt)

return result.scalars().all()
return result.scalar_one()

@repo_handler
async def get_standardized_fuel_data(
Expand Down Expand Up @@ -876,15 +876,10 @@ async def get_standardized_fuel_data(
eer = energy_effectiveness.ratio if energy_effectiveness else 1.0

# Fetch target carbon intensity (TCI)
target_ci = None
target_carbon_intensities = await self.get_target_carbon_intensities(
target_carbon_intensity = await self.get_target_carbon_intensity(
fuel_category_id, compliance_period
)
if target_carbon_intensities:
target_ci = next(
(tci.target_carbon_intensity for tci in target_carbon_intensities),
0.0,
)
target_ci = target_carbon_intensity.target_carbon_intensity

# Additional Carbon Intensity (UCI)
uci = await self.get_additional_carbon_intensity(fuel_type_id, end_use_id)
Expand Down
Loading

0 comments on commit b6d0355

Please sign in to comment.