Files
svg-backend/backend/app/services/scheme_validation.py

108 lines
3.6 KiB
Python

from __future__ import annotations
from collections import Counter
from app.repositories.pricing import find_effective_price_rule
from app.repositories.scheme_groups import list_scheme_version_groups
from app.repositories.scheme_seats import list_scheme_version_seats
from app.repositories.scheme_sectors import list_scheme_version_sectors
async def build_scheme_validation_report(
*,
scheme_id: str,
scheme_version_id: str,
) -> dict:
sectors = await list_scheme_version_sectors(scheme_version_id)
groups = await list_scheme_version_groups(scheme_version_id)
seats = await list_scheme_version_seats(scheme_version_id)
seat_ids = [row.seat_id for row in seats if row.seat_id]
duplicate_seat_ids = sorted([seat_id for seat_id, count in Counter(seat_ids).items() if count > 1])
seats_without_price: list[str] = []
seats_without_sector_or_group: list[str] = []
seats_with_missing_contract: list[str] = []
priced_seats_count = 0
for seat in seats:
if not seat.seat_id or not seat.element_id:
seats_with_missing_contract.append(seat.element_id or seat.seat_id or "unknown")
continue
if not seat.sector_id and not seat.group_id:
seats_without_sector_or_group.append(seat.seat_id)
try:
await find_effective_price_rule(
scheme_id=scheme_id,
seat_id=seat.seat_id,
group_id=seat.group_id,
sector_id=seat.sector_id,
)
priced_seats_count += 1
except Exception:
seats_without_price.append(seat.seat_id)
errors: list[dict] = []
warnings: list[dict] = []
if duplicate_seat_ids:
errors.append(
{
"code": "duplicate_seat_ids",
"message": "Duplicate seat_id values found",
"items": duplicate_seat_ids,
}
)
if seats_with_missing_contract:
errors.append(
{
"code": "missing_seat_contract",
"message": "Some seats have missing contract fields",
"items": seats_with_missing_contract,
}
)
if seats_without_sector_or_group:
warnings.append(
{
"code": "seats_without_sector_or_group",
"message": "Some seats do not belong to sector or group",
"items": sorted(seats_without_sector_or_group),
}
)
if seats_without_price:
warnings.append(
{
"code": "seats_without_price",
"message": "Some seats have no pricing rule",
"items": sorted(seats_without_price),
}
)
is_publishable = len(errors) == 0
return {
"summary": {
"sectors_count": len(sectors),
"groups_count": len(groups),
"seats_count": len(seats),
"priced_seats_count": priced_seats_count,
"unpriced_seats_count": len(seats_without_price),
"duplicate_seat_ids_count": len(duplicate_seat_ids),
"seats_with_missing_contract_count": len(seats_with_missing_contract),
"is_publishable": is_publishable,
},
"errors": errors,
"warnings": warnings,
"issues": {
"duplicate_seat_ids": duplicate_seat_ids,
"seats_without_price": sorted(seats_without_price),
"seats_without_sector_or_group": sorted(seats_without_sector_or_group),
"seats_with_missing_contract": seats_with_missing_contract,
},
}