Skip to content

Commit

Permalink
Merge pull request #1699 from bcgov/feat/kevin-1588
Browse files Browse the repository at this point in the history
Feat: idir fuel code count dashboard widget
  • Loading branch information
kevin-hashimoto authored Jan 16, 2025
2 parents 549996a + fcc36c7 commit d750c8d
Show file tree
Hide file tree
Showing 16 changed files with 318 additions and 25 deletions.
1 change: 1 addition & 0 deletions backend/lcfs/db/migrations/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"mv_org_compliance_report_count",
"transaction_status_view",
"mv_compliance_report_count",
"mv_fuel_code_count",
]


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"""mv for fuel code count
Revision ID: 8119d12538df
Revises: d25e7c47659e
Create Date: 2025-01-14 23:47:28.504150
"""

import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision = "8119d12538df"
down_revision = "fe03799b4018"
branch_labels = None
depends_on = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.execute(
"""
CREATE MATERIALIZED VIEW mv_fuel_code_count AS
SELECT
CASE fuel_status_id
WHEN 1 THEN 'Draft'
END as status,
COUNT(*) as count
FROM fuel_code
WHERE fuel_status_id = 1
GROUP BY fuel_status_id;
"""
)

op.execute(
"""
CREATE UNIQUE INDEX mv_fuel_code_count_idx
ON mv_fuel_code_count (status);
"""
)

op.execute(
"""
CREATE OR REPLACE FUNCTION refresh_mv_fuel_code_count()
RETURNS TRIGGER AS $$
BEGIN
REFRESH MATERIALIZED VIEW CONCURRENTLY mv_fuel_code_count;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
"""
)

op.execute(
"""
CREATE TRIGGER refresh_mv_fuel_code_count_after_change
AFTER INSERT OR UPDATE OR DELETE ON fuel_code
FOR EACH STATEMENT EXECUTE FUNCTION refresh_mv_fuel_code_count();
"""
)

# Refresh the materialized view to include existing fuel codes
op.execute(
"REFRESH MATERIALIZED VIEW CONCURRENTLY mv_fuel_code_count;"
)
# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.execute(
"DROP TRIGGER IF EXISTS refresh_mv_fuel_code_count_after_change ON fuel_code;")
op.execute("DROP FUNCTION IF EXISTS refresh_mv_fuel_code_count();")
op.execute("DROP MATERIALIZED VIEW IF EXISTS mv_fuel_code_count;")
# ### end Alembic commands ###
20 changes: 20 additions & 0 deletions backend/lcfs/db/models/fuel/FuelCodeCountView.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from sqlalchemy import Column, Integer, String
from lcfs.db.base import BaseModel


class FuelCodeCountView(BaseModel):
__tablename__ = "mv_fuel_code_count"
__table_args__ = {
"extend_existing": True,
"comment": "Materialized view for counting fuel code by status",
}

status = Column(
String,
primary_key=True,
comment="Status name (e.g. draft, approved, deleted)"
)
count = Column(
Integer,
comment="Count of fuel code for this status"
)
8 changes: 8 additions & 0 deletions backend/lcfs/db/seeders/dev/fuel_code_seeder.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,11 @@ def create_fuel_entry(
effective_date,
expiration_date,
fuel_type_id,
fuel_status_id=2,
):
return {
**base_fuel_data, # Extend with the base fields
"fuel_status_id": fuel_status_id,
"fuel_suffix": fuel_suffix,
"company": company,
"carbon_intensity": carbon_intensity,
Expand All @@ -49,6 +51,7 @@ def create_fuel_entry(
async def seed_fuel_codes(session):
fuel_codes_to_seed = [
create_fuel_entry(
fuel_status_id=1,
fuel_suffix="102.5",
company="Neste Oil Singapore",
carbon_intensity=37.21,
Expand All @@ -57,6 +60,7 @@ async def seed_fuel_codes(session):
fuel_type_id=5,
),
create_fuel_entry(
fuel_status_id=1,
fuel_suffix="124.4",
company="Ag Processing Inc.",
carbon_intensity=3.62,
Expand All @@ -65,6 +69,7 @@ async def seed_fuel_codes(session):
fuel_type_id=1,
),
create_fuel_entry(
fuel_status_id=1,
fuel_suffix="125.4",
company="Archer Daniels Midland",
carbon_intensity=-2.14,
Expand All @@ -73,6 +78,7 @@ async def seed_fuel_codes(session):
fuel_type_id=1,
),
create_fuel_entry(
fuel_status_id=3,
fuel_suffix="138.5",
company="ADM Agri-Industries Company",
carbon_intensity=4.26,
Expand All @@ -81,6 +87,7 @@ async def seed_fuel_codes(session):
fuel_type_id=1,
),
create_fuel_entry(
fuel_status_id=3,
fuel_suffix="143.4",
company="Green Plains Otter Tail LLC",
carbon_intensity=44.06,
Expand All @@ -89,6 +96,7 @@ async def seed_fuel_codes(session):
fuel_type_id=4,
),
create_fuel_entry(
fuel_status_id=3,
fuel_suffix="251.2",
company="Incobrasa Industries, Ltd.",
carbon_intensity=0.35,
Expand Down
14 changes: 14 additions & 0 deletions backend/lcfs/web/api/dashboard/repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from lcfs.db.models.compliance.ComplianceReportCountView import (
ComplianceReportCountView,
)
from lcfs.db.models.fuel.FuelCodeCountView import FuelCodeCountView

logger = structlog.get_logger(__name__)

Expand Down Expand Up @@ -89,3 +90,16 @@ async def get_compliance_report_counts(self):
return {
"pending_reviews": row.pending_reviews
}

@repo_handler
async def get_fuel_code_counts(self):
query = select(
FuelCodeCountView.count
).where(FuelCodeCountView.status == "Draft")

result = await self.db.execute(query)
row = result.fetchone()

return {
"draft_fuel_codes": getattr(row, "count", 0)
}
4 changes: 4 additions & 0 deletions backend/lcfs/web/api/dashboard/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,7 @@ class OrgComplianceReportCountsSchema(BaseSchema):

class ComplianceReportCountsSchema(BaseSchema):
pending_reviews: int = Field(default=0)


class FuelCodeCountsSchema(BaseSchema):
draft_fuel_codes: int = Field(default=0)
13 changes: 12 additions & 1 deletion backend/lcfs/web/api/dashboard/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
TransactionCountsSchema,
OrganizarionTransactionCountsSchema,
OrgComplianceReportCountsSchema,
ComplianceReportCountsSchema
ComplianceReportCountsSchema,
FuelCodeCountsSchema
)

logger = structlog.get_logger(__name__)
Expand Down Expand Up @@ -66,3 +67,13 @@ async def get_compliance_report_counts(
return ComplianceReportCountsSchema(
pending_reviews=counts.get("pending_reviews", 0)
)

@service_handler
async def get_fuel_code_counts(
self
) -> FuelCodeCountsSchema:
counts = await self.repo.get_fuel_code_counts()

return FuelCodeCountsSchema(
draft_fuel_codes=counts.get("draft_fuel_codes", 0)
)
17 changes: 16 additions & 1 deletion backend/lcfs/web/api/dashboard/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
TransactionCountsSchema,
OrganizarionTransactionCountsSchema,
OrgComplianceReportCountsSchema,
ComplianceReportCountsSchema
ComplianceReportCountsSchema,
FuelCodeCountsSchema
)
from lcfs.db.models.user.Role import RoleEnum

Expand Down Expand Up @@ -73,3 +74,17 @@ async def get_compliance_report_counts(
):
"""Endpoint to retrieve count of compliance reports pending review"""
return await service.get_compliance_report_counts()


@router.get(
"/fuel-code-counts",
response_model=FuelCodeCountsSchema
)
@view_handler([RoleEnum.ANALYST])
async def get_fuel_code_counts(
request: Request,
service: DashboardServices = Depends(),
):
"""Endpoint to retrieve count of compliance reports pending review"""

return await service.get_fuel_code_counts()
58 changes: 39 additions & 19 deletions backend/lcfs/web/api/fuel_code/repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,8 @@ async def get_transport_mode(self, transport_mode_id: int) -> TransportMode:

@repo_handler
async def get_transport_mode_by_name(self, mode_name: str) -> TransportMode:
query = select(TransportMode).where(TransportMode.transport_mode == mode_name)
query = select(TransportMode).where(
TransportMode.transport_mode == mode_name)
result = await self.db.execute(query)
transport_mode = result.scalar_one()

Expand Down Expand Up @@ -247,7 +248,8 @@ async def get_energy_densities(self) -> List[EnergyDensity]:
async def get_energy_density(self, fuel_type_id) -> EnergyDensity:
"""Get the energy density for the specified fuel_type_id"""

stmt = select(EnergyDensity).where(EnergyDensity.fuel_type_id == fuel_type_id)
stmt = select(EnergyDensity).where(
EnergyDensity.fuel_type_id == fuel_type_id)
result = await self.db.execute(stmt)
energy_density = result.scalars().first()

Expand Down Expand Up @@ -300,7 +302,8 @@ async def get_fuel_codes_paginated(
List[FuelCodeSchema]: A list of fuel codes matching the query.
"""
delete_status = await self.get_fuel_status_by_status("Deleted")
conditions = [FuelCode.fuel_status_id != delete_status.fuel_code_status_id]
conditions = [FuelCode.fuel_status_id !=
delete_status.fuel_code_status_id]

for filter in pagination.filters:

Expand Down Expand Up @@ -341,20 +344,27 @@ async def get_fuel_codes_paginated(
field = get_field_for_filter(FuelCode, filter.field)

conditions.append(
apply_filter_conditions(field, filter_value, filter_option, filter_type)
apply_filter_conditions(
field, filter_value, filter_option, filter_type)
)

# setup pagination
offset = 0 if (pagination.page < 1) else (pagination.page - 1) * pagination.size
offset = 0 if (pagination.page < 1) else (
pagination.page - 1) * pagination.size
limit = pagination.size
# Construct the select query with options for eager loading
query = (
select(FuelCode)
.join(FuelCode.fuel_code_status) # Add explicit join for status
.join(FuelCode.fuel_code_prefix) # Add explicit join for prefix
.join(FuelCode.fuel_type) # Add explicit join for fuel type
.options(
joinedload(FuelCode.fuel_code_status),
joinedload(FuelCode.fuel_code_prefix),
joinedload(FuelCode.fuel_type).joinedload(FuelType.provision_1),
joinedload(FuelCode.fuel_type).joinedload(FuelType.provision_2),
contains_eager(FuelCode.fuel_code_status),
contains_eager(FuelCode.fuel_code_prefix),
contains_eager(FuelCode.fuel_type).joinedload(
FuelType.provision_1),
contains_eager(FuelCode.fuel_type).joinedload(
FuelType.provision_2),
joinedload(FuelCode.feedstock_fuel_transport_modes).joinedload(
FeedstockFuelTransportMode.feedstock_fuel_transport_mode
),
Expand Down Expand Up @@ -382,7 +392,8 @@ async def get_fuel_codes_paginated(

# Execute the main query to retrieve all fuel codes
result = await self.db.execute(
query.offset(offset).limit(limit).order_by(FuelCode.create_date.desc())
query.offset(offset).limit(limit).order_by(
FuelCode.create_date.desc())
)
fuel_codes = result.unique().scalars().all()
return fuel_codes, total_count
Expand Down Expand Up @@ -417,8 +428,10 @@ async def get_fuel_code(self, fuel_code_id: int) -> FuelCode:
joinedload(FuelCode.finished_fuel_transport_modes).joinedload(
FinishedFuelTransportMode.finished_fuel_transport_mode
),
joinedload(FuelCode.fuel_type).joinedload(FuelType.provision_1),
joinedload(FuelCode.fuel_type).joinedload(FuelType.provision_2),
joinedload(FuelCode.fuel_type).joinedload(
FuelType.provision_1),
joinedload(FuelCode.fuel_type).joinedload(
FuelType.provision_2),
)
.where(FuelCode.fuel_code_id == fuel_code_id)
)
Expand All @@ -428,7 +441,8 @@ async def get_fuel_code_status(
self, fuel_code_status: FuelCodeStatusEnum
) -> FuelCodeStatus:
return await self.db.scalar(
select(FuelCodeStatus).where(FuelCodeStatus.status == fuel_code_status)
select(FuelCodeStatus).where(
FuelCodeStatus.status == fuel_code_status)
)

@repo_handler
Expand Down Expand Up @@ -486,7 +500,8 @@ async def get_contact_email_by_company_and_name(
.where(
and_(
func.lower(FuelCode.company) == func.lower(company),
func.lower(FuelCode.contact_name) == func.lower(contact_name),
func.lower(FuelCode.contact_name) == func.lower(
contact_name),
),
func.lower(FuelCode.contact_email).like(
func.lower(contact_email + "%")
Expand Down Expand Up @@ -528,8 +543,10 @@ async def get_fuel_code_by_code_prefix(
.options(
joinedload(FuelCode.fuel_code_status),
joinedload(FuelCode.fuel_code_prefix),
joinedload(FuelCode.fuel_type).joinedload(FuelType.provision_1),
joinedload(FuelCode.fuel_type).joinedload(FuelType.provision_2),
joinedload(FuelCode.fuel_type).joinedload(
FuelType.provision_1),
joinedload(FuelCode.fuel_type).joinedload(
FuelType.provision_2),
joinedload(FuelCode.feedstock_fuel_transport_modes).joinedload(
FeedstockFuelTransportMode.feedstock_fuel_transport_mode
),
Expand Down Expand Up @@ -670,7 +687,8 @@ async def get_next_available_sub_version_fuel_code_by_prefix(
)
result = (
await self.db.execute(
query, {"input_version": int(input_version), "prefix_id": prefix_id}
query, {"input_version": int(
input_version), "prefix_id": prefix_id}
)
).scalar_one_or_none()
return self.format_decimal(result)
Expand All @@ -692,8 +710,10 @@ async def get_latest_fuel_codes(self) -> List[FuelCodeSchema]:
joinedload(FuelCode.finished_fuel_transport_modes).joinedload(
FinishedFuelTransportMode.finished_fuel_transport_mode
),
joinedload(FuelCode.fuel_type).joinedload(FuelType.provision_1),
joinedload(FuelCode.fuel_type).joinedload(FuelType.provision_2),
joinedload(FuelCode.fuel_type).joinedload(
FuelType.provision_1),
joinedload(FuelCode.fuel_type).joinedload(
FuelType.provision_2),
)
.filter(FuelCodeStatus.status != FuelCodeStatusEnum.Deleted)
)
Expand Down
Loading

0 comments on commit d750c8d

Please sign in to comment.