Files
svg-backend/backend/app/services/publish_readiness.py
greebo 7b6c12f924 feat(backend): add publish readiness endpoint and enforce publish gate contract
add backend endpoint for publish readiness checks

enforce publish gate contract before version publication
make publish preconditions explicit and consistent for clients
2026-03-19 20:15:48 +03:00

87 lines
2.8 KiB
Python

from __future__ import annotations
from app.repositories.scheme_seats import list_scheme_version_seats
from app.repositories.scheme_version_pricing import (
find_effective_snapshot_price_rule,
list_scheme_version_snapshot_categories,
list_scheme_version_snapshot_rules,
)
from app.services.scheme_validation import build_scheme_validation_report
async def build_publish_readiness(
*,
scheme_id: str,
scheme_version_id: str,
require_full_pricing_coverage: bool,
) -> dict:
validation = await build_scheme_validation_report(
scheme_id=scheme_id,
scheme_version_id=scheme_version_id,
)
seats = await list_scheme_version_seats(scheme_version_id)
snapshot_categories = await list_scheme_version_snapshot_categories(scheme_version_id)
snapshot_rules = await list_scheme_version_snapshot_rules(scheme_version_id)
snapshot_available = len(snapshot_categories) > 0 or len(snapshot_rules) > 0
priced_seats = 0
unpriced_seats = 0
for seat in seats:
if not seat.seat_id:
unpriced_seats += 1
continue
if not snapshot_available:
unpriced_seats += 1
continue
try:
await find_effective_snapshot_price_rule(
scheme_version_id=scheme_version_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
validation_publishable = bool(validation["summary"]["is_publishable"])
full_pricing_coverage = unpriced_seats == 0
pricing_gate_passed = full_pricing_coverage if require_full_pricing_coverage else True
is_ready_to_publish = (
validation_publishable
and snapshot_available
and pricing_gate_passed
)
return {
"validation_summary": validation["summary"],
"pricing_coverage": {
"total_seats": total_seats,
"priced_seats": priced_seats,
"unpriced_seats": unpriced_seats,
"coverage_percent": coverage_percent,
},
"snapshot": {
"available": snapshot_available,
"categories_count": len(snapshot_categories),
"rules_count": len(snapshot_rules),
},
"readiness": {
"validation_publishable": validation_publishable,
"snapshot_available": snapshot_available,
"require_full_pricing_coverage": require_full_pricing_coverage,
"full_pricing_coverage": full_pricing_coverage,
"pricing_gate_passed": pricing_gate_passed,
"is_ready_to_publish": is_ready_to_publish,
},
}