from __future__ import annotations import json from pathlib import Path from app.repositories.scheme_artifacts import list_scheme_artifacts 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.publish_preview_cache import ( get_latest_publish_preview_artifact, save_publish_preview_artifact, ) from app.services.scheme_validation import build_scheme_validation_report from app.services.structure_diff import build_structure_diff def _serialize_artifacts(artifacts_rows: list) -> dict: return { "total": len(artifacts_rows), "items": [ { "artifact_id": row.artifact_id, "artifact_type": row.artifact_type, "artifact_variant": row.artifact_variant, "status": row.status, "storage_path": row.storage_path, "meta_json": row.meta_json, "created_at": row.created_at.isoformat(), } for row in artifacts_rows ], } async def build_publish_preview_bundle( *, scheme_id: str, scheme_version_id: str, baseline_override_scheme_version_id: str | None = None, ) -> dict: validation = await build_scheme_validation_report( scheme_id=scheme_id, scheme_version_id=scheme_version_id, ) structure_diff = await build_structure_diff( scheme_id=scheme_id, draft_scheme_version_id=scheme_version_id, baseline_override_scheme_version_id=baseline_override_scheme_version_id, ) artifacts_rows = await list_scheme_artifacts(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) priced = 0 unpriced = 0 snapshot_available = len(snapshot_rules) > 0 or len(snapshot_categories) > 0 for seat in seats: if not seat.seat_id: unpriced += 1 continue if not snapshot_available: unpriced += 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 += 1 except Exception: unpriced += 1 artifacts = _serialize_artifacts(artifacts_rows) pricing_coverage = { "snapshot_available": snapshot_available, "snapshot_categories_count": len(snapshot_categories), "snapshot_rules_count": len(snapshot_rules), "total_seats": len(seats), "priced_seats": priced, "unpriced_seats": unpriced, } summary = { "is_publishable": validation["summary"]["is_publishable"], "has_structure_changes": any(value > 0 for value in structure_diff["summary"].values()), "has_artifacts": len(artifacts_rows) > 0, "has_unpriced_seats": unpriced > 0, "snapshot_available": snapshot_available, } return { "artifacts": artifacts, "validation": validation, "structure_diff": structure_diff, "pricing_coverage": pricing_coverage, "summary": summary, } async def get_or_build_publish_preview_bundle( *, scheme_id: str, scheme_version_id: str, baseline_override_scheme_version_id: str | None = None, refresh: bool = False, ) -> dict: if not refresh: artifact = await get_latest_publish_preview_artifact( scheme_version_id=scheme_version_id, baseline_scheme_version_id=baseline_override_scheme_version_id, ) if artifact: path = Path(artifact.storage_path) if path.exists(): return json.loads(path.read_text(encoding="utf-8")) payload = await build_publish_preview_bundle( scheme_id=scheme_id, scheme_version_id=scheme_version_id, baseline_override_scheme_version_id=baseline_override_scheme_version_id, ) save_result = await save_publish_preview_artifact( scheme_id=scheme_id, scheme_version_id=scheme_version_id, payload=payload, baseline_scheme_version_id=payload["structure_diff"]["baseline_scheme_version_id"], ) artifacts_rows = await list_scheme_artifacts(scheme_version_id=scheme_version_id) payload["artifacts"] = _serialize_artifacts(artifacts_rows) payload["summary"]["has_artifacts"] = payload["artifacts"]["total"] > 0 payload["summary"]["preview_cache_cleanup"] = save_result["cleanup"] artifact = save_result["artifact"] path = Path(artifact.storage_path) path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8") return payload