from fastapi import APIRouter, Depends, HTTPException, status 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 ( ExplainMatchedRule, ExplainSeatPriceResponse, PricingCoverageResponse, UnpricedSeatItem, UnpricedSeatListResponse, ) from app.security.auth import require_api_key router = APIRouter() @router.get( f"{settings.api_v1_prefix}/schemes/{{scheme_id}}/pricing/coverage", response_model=PricingCoverageResponse, ) async def get_pricing_coverage( 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 HTTPException as exc: if exc.status_code != status.HTTP_404_NOT_FOUND: raise unpriced_seats += 1 total_seats = len(seats) coverage_percent = round((priced_seats / total_seats) * 100, 2) if total_seats else 100.0 return PricingCoverageResponse( 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=UnpricedSeatListResponse, ) 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 not 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="missing_seat_id", reason_message="Seat has no seat_id, so price resolution is not possible.", ) ) 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, ) except HTTPException as exc: if exc.status_code != status.HTTP_404_NOT_FOUND: raise 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="no_price_rule", reason_message="No effective price rule was found for this seat.", ) ) return UnpricedSeatListResponse( 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=ExplainSeatPriceResponse, ) async def explain_seat_price( 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: return ExplainSeatPriceResponse( scheme_id=scheme.scheme_id, scheme_version_id=version.scheme_version_id, seat_id=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="missing_seat_id", reason_message="Seat has no seat_id, so price resolution is not possible.", 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, ) matched_rule = ExplainMatchedRule( 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"], ) return ExplainSeatPriceResponse( 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=matched_rule, ) except HTTPException as exc: if exc.status_code != status.HTTP_404_NOT_FOUND: raise return ExplainSeatPriceResponse( 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="no_price_rule", reason_message="No effective price rule was found for this seat.", matched_rule=None, )