"""군산 공통 맛집 한일옥 등록. 기본은 조회, --apply로 현재 설정 DB에 반영한다. external_id가 없는 지역 공통 항목은 snapshot이 모든 군산 업장에 포함한다. 네이버 ID는 body에 보관하여 자동 수집의 (source, external_id) 갱신과 분리한다. """ import argparse import asyncio import json import sys import uuid from datetime import datetime, timezone from pathlib import Path from types import SimpleNamespace sys.path.insert(0, str(Path(__file__).resolve().parents[1])) from sqlalchemy import select from sqlalchemy.dialects.postgresql import insert from sqlalchemy.engine import URL from sqlalchemy.ext.asyncio import create_async_engine from common.database.db_session_manager import DB_SESSION_MNG from common.database.model.models import area_contents, places from common.enums import LocalContentStatus, LocalContentType, LocalSource from config.server_configs import main_db_config as cfg from services.site_payload import _local from services.snapshot import _local_contents REGION = '52군산시' NAVER_ID = '11861452' CONTENT_ID = uuid.uuid5(uuid.NAMESPACE_URL, 'web4ai:52군산시:restaurant:11861452') async def main(apply: bool): engine = create_async_engine(URL.create( 'postgresql+asyncpg', username=cfg.write_id, password=cfg.write_pw, host=cfg.write_host, port=cfg.write_port, database=cfg.name, )) try: async with engine.begin() as conn: rows = (await conn.execute(select( area_contents.local_content_id, area_contents.title, area_contents.body, ).where( area_contents.region_code == REGION, area_contents.kind == 'restaurant', area_contents.external_id.is_(None), area_contents.deleted.is_(False), ))).mappings().all() if rows and any(r['body'].get('naverPlaceId') != NAVER_ID for r in rows): raise RuntimeError('다른 군산 공통 맛집이 이미 있어 덮어쓰지 않습니다.') content_id = rows[0]['local_content_id'] if rows else CONTENT_ID now = datetime.now(timezone.utc) values = dict( local_content_id=content_id, region_code=REGION, content_type=LocalContentType.RESTAURANT.value, kind='restaurant', # 사용자 확인을 거친 수기 웹 등록. 크롤링한 값으로 표시하지 않는다. source=LocalSource.OFFICIAL_WEB.value, external_id=None, title='한일옥', body={**(rows[0]['body'] if rows else {}), 'name': '한일옥', 'searchQuery': '군산 한일옥', 'naverPlaceId': NAVER_ID, 'sourceUrl': f'https://m.place.naver.com/restaurant/{NAVER_ID}/home', 'registration': 'owner_confirmed_region_default'}, status=LocalContentStatus.PUBLISHED.value, published_at=now, collected_at=now, display_start_at=None, display_end_at=None, expires_at=None, deleted=False, updated_at=now, ) if apply: await conn.execute(insert(area_contents).values(**values).on_conflict_do_update( index_elements=[area_contents.local_content_id], set_={k: v for k, v in values.items() if k != 'local_content_id'}, )) targets = (await conn.execute(select(places.place_id, places.name).where( places.deleted.is_(False), places.region_code == REGION, ))).mappings().all() print(json.dumps({'applied': apply, 'database': cfg.name, 'region': REGION, 'contentId': str(content_id), 'naverPlaceId': NAVER_ID, 'existingPlaces': [dict(r) for r in targets]}, default=str, ensure_ascii=False)) if apply: # place_id 없는 새 군산 업장도 지역 공통 경로만으로 받는지 확인한다. snapshot = await _local_contents(SimpleNamespace(region_code=REGION)) local, _ = _local(snapshot, None, None) matches = [r for r in local['restaurants'] if r['name'] == '한일옥'] assert len(matches) == 1, '지역 공통 payload에 한일옥이 정확히 한 번 있어야 합니다.' print(json.dumps({'verifiedRegionalPayload': matches}, ensure_ascii=False)) finally: await engine.dispose() await DB_SESSION_MNG.dispose_all() if __name__ == '__main__': parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--apply', action='store_true') asyncio.run(main(parser.parse_args().apply))