from fastapi import APIRouter, Depends from app.core.config import settings from app.repositories.pricing import find_effective_price_rule from app.repositories.scheme_seats import get_scheme_version_seat_by_seat_id, list_scheme_version_seats from app.repositories.scheme_versions import get_current_scheme_version from app.repositories.schemes import get_scheme_record_by_scheme_id from app.schemas.pricing_diagnostics import ( PricingCoverageSummaryResponse, PricingExplainMatchedRule, PricingExplainResponse, UnpricedSeatItem, UnpricedSeatsResponse, ) from app.security.auth import require_api_key router = APIRouter() def _build_unpriced_reason(*, seat_id: str | None) -> tuple[str, str]: if not seat_id: return ( "missing_seat_id", "Seat has no seat_id, so price resolution is impossible.", ) return ( "no_price_rule", "No effective price rule was found for this seat.", ) @router.get( f"{settings.api_v1_prefix}/schemes/{{scheme_id}}/pricing/coverage", response_model=PricingCoverageSummaryResponse, ) async def get_pricing_coverage_summary( scheme_id: str, role: str = Depends(require_api_key), ): scheme = await get_scheme_record_by_scheme_id(scheme_id) version = await get_current_scheme_version( scheme_id=scheme.scheme_id, current_version_number=scheme.current_version_number, ) seats = await list_scheme_version_seats(version.scheme_version_id) priced_seats = 0 unpriced_seats = 0 for seat in seats: if not seat.seat_id: unpriced_seats += 1 continue try: await find_effective_price_rule( scheme_id=scheme.scheme_id, seat_id=seat.seat_id, group_id=seat.group_id, sector_id=seat.sector_id, ) priced_seats += 1 except Exception: unpriced_seats += 1 total_seats = len(seats) coverage_percent = round((priced_seats / total_seats) * 100, 2) if total_seats else 0.0 return PricingCoverageSummaryResponse( scheme_id=scheme.scheme_id, scheme_version_id=version.scheme_version_id, total_seats=total_seats, priced_seats=priced_seats, unpriced_seats=unpriced_seats, coverage_percent=coverage_percent, ) @router.get( f"{settings.api_v1_prefix}/schemes/{{scheme_id}}/pricing/unpriced-seats", response_model=UnpricedSeatsResponse, ) async def get_unpriced_seats( scheme_id: str, role: str = Depends(require_api_key), ): scheme = await get_scheme_record_by_scheme_id(scheme_id) version = await get_current_scheme_version( scheme_id=scheme.scheme_id, current_version_number=scheme.current_version_number, ) seats = await list_scheme_version_seats(version.scheme_version_id) items: list[UnpricedSeatItem] = [] for seat in seats: if seat.seat_id: try: await find_effective_price_rule( scheme_id=scheme.scheme_id, seat_id=seat.seat_id, group_id=seat.group_id, sector_id=seat.sector_id, ) continue except Exception: pass reason_code, reason_message = _build_unpriced_reason(seat_id=seat.seat_id) items.append( UnpricedSeatItem( seat_record_id=seat.seat_record_id, seat_id=seat.seat_id, element_id=seat.element_id, sector_id=seat.sector_id, group_id=seat.group_id, row_label=seat.row_label, seat_number=seat.seat_number, reason_code=reason_code, reason_message=reason_message, ) ) return UnpricedSeatsResponse( scheme_id=scheme.scheme_id, scheme_version_id=version.scheme_version_id, total=len(items), items=items, ) @router.get( f"{settings.api_v1_prefix}/schemes/{{scheme_id}}/pricing/explain/{{seat_id}}", response_model=PricingExplainResponse, ) async def explain_pricing_for_seat( scheme_id: str, seat_id: str, role: str = Depends(require_api_key), ): scheme = await get_scheme_record_by_scheme_id(scheme_id) version = await get_current_scheme_version( scheme_id=scheme.scheme_id, current_version_number=scheme.current_version_number, ) seat = await get_scheme_version_seat_by_seat_id( scheme_version_id=version.scheme_version_id, seat_id=seat_id, ) if not seat.seat_id: reason_code, reason_message = _build_unpriced_reason(seat_id=seat.seat_id) return PricingExplainResponse( scheme_id=scheme.scheme_id, scheme_version_id=version.scheme_version_id, seat_id=seat.seat_id, element_id=seat.element_id, sector_id=seat.sector_id, group_id=seat.group_id, row_label=seat.row_label, seat_number=seat.seat_number, has_price=False, reason_code=reason_code, reason_message=reason_message, matched_rule=None, ) try: matched_rule_level, rule = await find_effective_price_rule( scheme_id=scheme.scheme_id, seat_id=seat.seat_id, group_id=seat.group_id, sector_id=seat.sector_id, ) return PricingExplainResponse( scheme_id=scheme.scheme_id, scheme_version_id=version.scheme_version_id, seat_id=seat.seat_id, element_id=seat.element_id, sector_id=seat.sector_id, group_id=seat.group_id, row_label=seat.row_label, seat_number=seat.seat_number, has_price=True, reason_code="ok", reason_message="Effective price rule resolved successfully.", matched_rule=PricingExplainMatchedRule( matched_rule_level=matched_rule_level, matched_target_ref=rule["target_ref"], pricing_category_id=rule["pricing_category_id"], amount=str(rule["amount"]), currency=rule["currency"], ), ) except Exception: reason_code, reason_message = _build_unpriced_reason(seat_id=seat.seat_id) return PricingExplainResponse( scheme_id=scheme.scheme_id, scheme_version_id=version.scheme_version_id, seat_id=seat.seat_id, element_id=seat.element_id, sector_id=seat.sector_id, group_id=seat.group_id, row_label=seat.row_label, seat_number=seat.seat_number, has_price=False, reason_code=reason_code, reason_message=reason_message, matched_rule=None, )