Skip to content

Commit

Permalink
Merge branch 'release-0.2.0' into feat/kevin-1456
Browse files Browse the repository at this point in the history
  • Loading branch information
kevin-hashimoto authored Dec 18, 2024
2 parents ce726f7 + 38c46c1 commit 01026ee
Show file tree
Hide file tree
Showing 33 changed files with 682 additions and 175 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""Add Default CI to Categories
Revision ID: 851e09cf8661
Revises: 5b374dd97469
Create Date: 2024-12-17 23:58:07.462215
"""

import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision = "851e09cf8661"
down_revision = "5b374dd97469"
branch_labels = None
depends_on = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column(
"fuel_category",
sa.Column(
"default_carbon_intensity",
sa.Numeric(precision=10, scale=2),
nullable=True,
comment="Default carbon intensity of the fuel category",
),
)

# Populate default values for existing records
op.execute(
"""
UPDATE "fuel_category" SET "default_carbon_intensity" = 88.83 WHERE "description" = 'Jet fuel';
"""
)
op.execute(
"""
UPDATE "fuel_category" SET "default_carbon_intensity" = 100.21 WHERE "description" = 'Diesel';
"""
)
op.execute(
"""
UPDATE "fuel_category" SET "default_carbon_intensity" = 93.67 WHERE "description" = 'Gasoline';
"""
)

# Now set the column to NOT NULL after populating defaults
op.alter_column(
"fuel_category",
"default_carbon_intensity",
existing_type=sa.Numeric(precision=10, scale=2),
nullable=False,
)

# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column("fuel_category", "default_carbon_intensity")
# ### end Alembic commands ###
11 changes: 9 additions & 2 deletions backend/lcfs/db/models/fuel/FuelCategory.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from sqlalchemy import Column, Integer, Text, Enum
from sqlalchemy import Column, Integer, Text, Enum, Float, Numeric
from lcfs.db.base import BaseModel, Auditable, DisplayOrder, EffectiveDates
from sqlalchemy.orm import relationship

Expand All @@ -25,7 +25,14 @@ class FuelCategory(BaseModel, Auditable, DisplayOrder, EffectiveDates):
nullable=False,
comment="Name of the fuel category",
)
description = Column(Text, nullable=True, comment="Description of the fuel categor")
description = Column(
Text, nullable=True, comment="Description of the fuel category"
)
default_carbon_intensity = Column(
Numeric(10, 2),
nullable=False,
comment="Default carbon intensity of the fuel category",
)

energy_effectiveness_ratio = relationship("EnergyEffectivenessRatio")
target_carbon_intensities = relationship(
Expand Down
11 changes: 7 additions & 4 deletions backend/lcfs/db/seeders/common/seed_fuel_data.json
Original file line number Diff line number Diff line change
Expand Up @@ -249,17 +249,20 @@
{
"fuel_category_id": 1,
"category": "Gasoline",
"description": "Gasoline"
"description": "Gasoline",
"default_carbon_intensity": 93.67
},
{
"fuel_category_id": 2,
"category": "Diesel",
"description": "Diesel"
"description": "Diesel",
"default_carbon_intensity": 100.21
},
{
"fuel_category_id": 3,
"category": "Jet fuel",
"description": "Jet fuel"
"description": "Jet fuel",
"default_carbon_intensity": 88.83
}
],
"end_use_types": [
Expand Down Expand Up @@ -1092,4 +1095,4 @@
"display_order": 4
}
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,12 @@ async def expected_uses(dbsession):
@pytest.fixture
async def fuel_categories(dbsession):
fuel_categories = [
FuelCategory(fuel_category_id=998, category="Gasoline"),
FuelCategory(fuel_category_id=999, category="Diesel"),
FuelCategory(
fuel_category_id=998, category="Gasoline", default_carbon_intensity=0
),
FuelCategory(
fuel_category_id=999, category="Diesel", default_carbon_intensity=0
),
]

dbsession.add_all(fuel_categories)
Expand Down
6 changes: 1 addition & 5 deletions backend/lcfs/tests/compliance_report/test_summary_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -810,8 +810,7 @@ async def test_calculate_fuel_quantities_renewable(
):
# Create a mock repository
mock_repo.aggregate_fuel_supplies.return_value = {"gasoline": 200.0}
mock_repo.aggregate_other_uses.return_value = {"diesel": 75.0}
mock_repo.aggregate_allocation_agreements.return_value = {"jet-fuel": 25.0}
mock_repo.aggregate_other_uses.return_value = {"diesel": 75.0, "jet-fuel": 25.0}

# Define test inputs
compliance_report_id = 2
Expand All @@ -830,7 +829,4 @@ async def test_calculate_fuel_quantities_renewable(
mock_repo.aggregate_other_uses.assert_awaited_once_with(
compliance_report_id, fossil_derived
)
mock_repo.aggregate_allocation_agreements.assert_awaited_once_with(
compliance_report_id
)
assert result == {"gasoline": 200.0, "diesel": 75.0, "jet-fuel": 25.0}
92 changes: 89 additions & 3 deletions backend/lcfs/tests/fuel_code/test_fuel_code_repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,9 @@ async def test_get_fuel_type_by_id_not_found(fuel_code_repo, mock_db):

@pytest.mark.anyio
async def test_get_fuel_categories(fuel_code_repo, mock_db):
mock_fc = FuelCategory(fuel_category_id=1, category="Renewable")
mock_fc = FuelCategory(
fuel_category_id=1, category="Renewable", default_carbon_intensity=0
)
mock_result = MagicMock()
mock_result.scalars.return_value.all.return_value = [mock_fc]
mock_db.execute.return_value = mock_result
Expand All @@ -157,12 +159,14 @@ async def test_get_fuel_categories(fuel_code_repo, mock_db):

@pytest.mark.anyio
async def test_get_fuel_category_by_name(fuel_code_repo, mock_db):
mock_fc = FuelCategory(fuel_category_id=2, category="Fossil")
mock_fc = FuelCategory(
fuel_category_id=2, category="Fossil", default_carbon_intensity=0
)
mock_result = MagicMock()
mock_result.scalar_one_or_none.return_value = mock_fc
mock_db.execute.return_value = mock_result

result = await fuel_code_repo.get_fuel_category_by_name("Fossil")
result = await fuel_code_repo.get_fuel_category_by(category="Fossil")
assert result == mock_fc


Expand Down Expand Up @@ -646,6 +650,88 @@ async def test_get_standardized_fuel_data(fuel_code_repo, mock_db):
assert result.uci == 5.0


@pytest.mark.anyio
async def test_get_standardized_fuel_data_unrecognized(fuel_code_repo, mock_db):
# Mock an unrecognized fuel type
mock_fuel_type = FuelType(
fuel_type_id=1,
fuel_type="UnknownFuel",
default_carbon_intensity=None,
unrecognized=True,
)

# Mock a fuel category with a default CI
mock_fuel_category = FuelCategory(
fuel_category_id=2, category="SomeCategory", default_carbon_intensity=93.67
)

# The repo uses get_one to get the fuel type.
mock_db.get_one.return_value = mock_fuel_type

# Mock the repo method to get the fuel category
fuel_code_repo.get_fuel_category_by = AsyncMock(return_value=mock_fuel_category)

# Setup side effects for subsequent queries:
# Energy Density
energy_density_result = MagicMock(
scalars=MagicMock(
return_value=MagicMock(
first=MagicMock(return_value=EnergyDensity(density=35.0))
)
)
)
# EER
eer_result = MagicMock(
scalars=MagicMock(
return_value=MagicMock(
first=MagicMock(return_value=EnergyEffectivenessRatio(ratio=2.0))
)
)
)
# Target Carbon Intensities
tci_result = MagicMock(
scalars=MagicMock(
return_value=MagicMock(
all=MagicMock(
return_value=[TargetCarbonIntensity(target_carbon_intensity=50.0)]
)
)
)
)
# Additional Carbon Intensity
aci_result = MagicMock(
scalars=MagicMock(
return_value=MagicMock(
one_or_none=MagicMock(
return_value=AdditionalCarbonIntensity(intensity=5.0)
)
)
)
)

# Set the side_effect for the mock_db.execute calls in the order they're invoked
mock_db.execute.side_effect = [
energy_density_result,
eer_result,
tci_result,
aci_result,
]

result = await fuel_code_repo.get_standardized_fuel_data(
fuel_type_id=1, fuel_category_id=2, end_use_id=3, compliance_period="2024"
)

# Since fuel_type is unrecognized, it should use the FuelCategory's default CI
assert result.effective_carbon_intensity == 93.67
assert result.target_ci == 50.0
assert result.eer == 2.0
assert result.energy_density == 35.0
assert result.uci == 5.0

# Ensure get_fuel_category_by was called once with the correct parameter
fuel_code_repo.get_fuel_category_by.assert_awaited_once_with(fuel_category_id=2)


@pytest.mark.anyio
async def test_get_additional_carbon_intensity(fuel_code_repo, mock_db):
aci = AdditionalCarbonIntensity(additional_uci_id=1, intensity=10.0)
Expand Down
90 changes: 84 additions & 6 deletions backend/lcfs/tests/fuel_supply/test_fuel_supplies_validation.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,30 @@
from unittest.mock import MagicMock, AsyncMock

import pytest
from fastapi import Request
from fastapi.exceptions import RequestValidationError

from lcfs.web.api.fuel_supply.repo import FuelSupplyRepository
from lcfs.web.api.fuel_code.repo import FuelCodeRepository
from lcfs.web.api.fuel_supply.schema import FuelSupplyCreateUpdateSchema
from lcfs.web.api.fuel_supply.validation import FuelSupplyValidation


@pytest.fixture
def fuel_supply_validation():
# Mock repositories
mock_fs_repo = MagicMock(spec=FuelSupplyRepository)
request = MagicMock(spec=Request)
mock_fc_repo = MagicMock(spec=FuelCodeRepository)

# Create the validation instance with mocked repositories
validation = FuelSupplyValidation(
request=request, fs_repo=mock_fs_repo
fs_repo=mock_fs_repo,
fc_repo=mock_fc_repo,
)
return validation, mock_fs_repo
return validation, mock_fs_repo, mock_fc_repo


@pytest.mark.anyio
async def test_check_duplicate(fuel_supply_validation):
validation, mock_fs_repo = fuel_supply_validation
validation, mock_fs_repo, _ = fuel_supply_validation
fuel_supply_data = FuelSupplyCreateUpdateSchema(
compliance_report_id=1,
fuel_type_id=1,
Expand All @@ -29,9 +33,83 @@ async def test_check_duplicate(fuel_supply_validation):
quantity=2000,
units="L",
)

mock_fs_repo.check_duplicate = AsyncMock(return_value=True)

result = await validation.check_duplicate(fuel_supply_data)

assert result is True
mock_fs_repo.check_duplicate.assert_awaited_once_with(fuel_supply_data)


@pytest.mark.anyio
async def test_validate_other_recognized_type(fuel_supply_validation):
validation, _, mock_fc_repo = fuel_supply_validation
# Mock a recognized fuel type (unrecognized = False)
mock_fc_repo.get_fuel_type_by_id = AsyncMock(
return_value=MagicMock(unrecognized=False)
)

fuel_supply_data = FuelSupplyCreateUpdateSchema(
compliance_report_id=1,
fuel_type_id=1, # Some recognized type ID
fuel_category_id=1,
provision_of_the_act_id=1,
quantity=2000,
units="L",
)

# Should not raise any error as fuel_type_other is not needed for recognized type
await validation.validate_other(fuel_supply_data)


@pytest.mark.anyio
async def test_validate_other_unrecognized_type_with_other(fuel_supply_validation):
validation, _, mock_fc_repo = fuel_supply_validation
# Mock an unrecognized fuel type
mock_fc_repo.get_fuel_type_by_id = AsyncMock(
return_value=MagicMock(unrecognized=True)
)

# Provide fuel_type_other since it's unrecognized
fuel_supply_data = FuelSupplyCreateUpdateSchema(
compliance_report_id=1,
fuel_type_id=99, # Assume 99 is unrecognized "Other" type
fuel_category_id=1,
provision_of_the_act_id=1,
quantity=2000,
units="L",
fuel_type_other="Some other fuel",
)

# Should not raise an error since fuel_type_other is provided
await validation.validate_other(fuel_supply_data)


@pytest.mark.anyio
async def test_validate_other_unrecognized_type_missing_other(fuel_supply_validation):
validation, _, mock_fc_repo = fuel_supply_validation
# Mock an unrecognized fuel type
mock_fc_repo.get_fuel_type_by_id = AsyncMock(
return_value=MagicMock(unrecognized=True)
)

# Missing fuel_type_other
fuel_supply_data = FuelSupplyCreateUpdateSchema(
compliance_report_id=1,
fuel_type_id=99, # Assume 99 is unrecognized "Other" type
fuel_category_id=1,
provision_of_the_act_id=1,
quantity=2000,
units="L",
)

# Should raise RequestValidationError since fuel_type_other is required
with pytest.raises(RequestValidationError) as exc:
await validation.validate_other(fuel_supply_data)

# Assert that the error message is as expected
errors = exc.value.errors()
assert len(errors) == 1
assert errors[0]["loc"] == ("fuelTypeOther",)
assert "required when using Other" in errors[0]["msg"]
Loading

0 comments on commit 01026ee

Please sign in to comment.