#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 테스트 place에 야놀자 크롤링으로 넣은 객실 fact/사진을 **수동으로 검수·승인**해서, 실제 미리보기 렌더러(site_payload.to_site_payload → UnitsSection)에 뜨는지 확인하는 1회성 스크립트. ★ 이것도 운영 파이프라인이 아니다 — 사람이 눈으로 확인하려고 이번 테스트 place 하나에만 쓰는 수동 검수 도구다. 실제 서비스에서 크롤링 값을 이렇게 자동 승인하면 안 된다 (fact는 사람이 [맞아요]를 눌러야, 사진은 Vision/사람이 봐야 승인 상태가 된다). 사용법 (solution/backend 에서, 가상환경 안): PGSSLMODE=disable DB_PASSWORD=... python scripts/verify_yanolja_test_place.py --place-id """ from __future__ import annotations import argparse import asyncio import json import os import sys import uuid sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) os.environ.setdefault("APP_ENV", "local") from sqlalchemy import select # noqa: E402 from common.database.db_session_manager import DB_SESSION_MNG # noqa: E402 from common.database.model.models import places, place_facts, place_photos # noqa: E402 from common.enums import DBWRType, ErrorType, FactStatus, MediaStatus # noqa: E402 from common.models.gmodel import UserInfo # noqa: E402 from common.utils.gtime import GTime # noqa: E402 from crud.fact_crud import FactCRUD # noqa: E402 from crud.media_crud import MediaCRUD # noqa: E402 from crud.place_crud import PlaceCRUD # noqa: E402 from crud.site_crud import SiteCRUD # noqa: E402 from crud.job_crud import JobQueue # noqa: E402 from router.v1.fact.protocol import Req_TransitionFact # noqa: E402 from services.fact_service import FactService # noqa: E402 from services.site_service import SiteService # noqa: E402 # 검수 전 상태(우리가 이번에 넣은 값들)만 건드린다. _PENDING_FACT_STATUSES = (FactStatus.UNVERIFIED.value, FactStatus.PENDING_OWNER.value) async def _load_place(place_id: str): stmt = select(places).where(places.place_id == uuid.UUID(place_id), places.deleted == False) # noqa: E712 err, row = await DB_SESSION_MNG.execute_lambda( places.DBType(), DBWRType.DB_READ.value, lambda s: DB_SESSION_MNG.execute(s, stmt) ) if err != ErrorType.SUCCESS or not row: return None return row[0] async def _unit_scope_facts(place_id: str): stmt = select(place_facts).where( place_facts.place_id == uuid.UUID(place_id), place_facts.deleted == False, # noqa: E712 place_facts.unit_id.is_not(None), place_facts.status.in_(_PENDING_FACT_STATUSES), ) err, rows = await DB_SESSION_MNG.execute_lambda( place_facts.DBType(), DBWRType.DB_READ.value, lambda s: DB_SESSION_MNG.execute(s, stmt) ) return rows if err == ErrorType.SUCCESS else [] async def _unit_scope_pending_photos(place_id: str): stmt = select(place_photos).where( place_photos.place_id == uuid.UUID(place_id), place_photos.deleted == False, # noqa: E712 place_photos.unit_id.is_not(None), place_photos.status == MediaStatus.PENDING_REVIEW.value, ) err, rows = await DB_SESSION_MNG.execute_lambda( place_photos.DBType(), DBWRType.DB_READ.value, lambda s: DB_SESSION_MNG.execute(s, stmt) ) return rows if err == ErrorType.SUCCESS else [] async def run(place_id: str) -> None: place = await _load_place(place_id) if place is None: raise SystemExit(f"place를 찾을 수 없습니다: {place_id}") actor = UserInfo(user_id=str(place.owner_user_id), id="yanolja_test_verify", role=1) fact_service = FactService(FactCRUD(), PlaceCRUD()) media_crud = MediaCRUD() facts = await _unit_scope_facts(place_id) print(f"[검수 대상 fact] {len(facts)}건") verified = 0 for f in facts: res = await fact_service.transition( actor, place_id, str(f.fact_id), Req_TransitionFact(status=FactStatus.VERIFIED) ) if res.result.success: verified += 1 else: print(f" ! {f.key}({f.fact_id}) 전이 실패: {res.result.desc}") print(f" -> VERIFIED 처리 {verified}/{len(facts)}건") photos = await _unit_scope_pending_photos(place_id) print(f"[승인 대상 사진] {len(photos)}건") approved = 0 now = GTime.UTC() for p in photos: alt_text = p.label or "객실 사진" run_err, _rowcount = await DB_SESSION_MNG.execute_lambda_claim( place_photos.DBType(), lambda s, m=p.media_id, a=alt_text: media_crud.apply_vision( s, m, p.label, a, None, MediaStatus.APPROVED.value, now ), ) if run_err == ErrorType.SUCCESS: approved += 1 print(f" -> APPROVED 처리 {approved}/{len(photos)}건 (alt_text 채움)") site_service = SiteService(SiteCRUD(), PlaceCRUD(), JobQueue()) payload = await site_service.preview_payload(actor, place_id) units = (payload or {}).get("units", []) print(f"\n[/preview 재확인] units {len(units)}개") for u in units: print(f" - {u.get('name')}: fact {len(u.get('facts', []))}개, media {len(u.get('mediaIds', []))}장") if not units: print(" (여전히 비어 있습니다 — payload 구조나 상태값을 다시 확인하세요)") print(json.dumps(payload, ensure_ascii=False, default=str)[:2000]) await DB_SESSION_MNG.dispose_all() def main() -> None: parser = argparse.ArgumentParser(description="테스트 place의 야놀자 객실 데이터를 검수·승인해서 미리보기에 반영") parser.add_argument("--place-id", required=True) args = parser.parse_args() asyncio.run(run(args.place_id)) if __name__ == "__main__": main()