o2o-site-AEO/solution/backend/scripts/apply_crawled_facts.py
김성경 a093921b62 feat(backend,site): NOL 수집 및 숙박 안내 구조화와 지역 콘텐츠 연동
수집한 객실·이용 정보가 발행 화면에 연결되지 않던 경로를 보완하고, 숙소 소개와 지역 맛집 표시를 개선한다.

- NOL 브라우저 수집 어댑터와 수집·반영 스크립트 추가
- 크롤링 fact 즉시 노출 및 직접 입력·정정값 보호
- 이용안내 항목별 구조화와 기존 표 연결, 원문 UI 비표시
- 군산 한일옥 고정 등록과 지역 맛집 탐색·보강 경로 추가
- 숙소 소개 요약, 히어로 문구, 지역 콘텐츠·목업 표시 개선

검증: 작업 트리 기준 site 타입·린트·빌드 및 안내 렌더링 테스트 통과, PC·모바일 화면 확인. 스테이징 diff 공백 검사 통과. 사용자 요청에 따라 현재 스테이징된 55개 파일만 포함하며 미스테이징 문서·테스트 등은 제외.
2026-09-14 19:40:04 +09:00

74 lines
3.1 KiB
Python

"""기존 크롤링 후보를 현재 기록 정책으로 다시 적용한다. --apply 없이는 조회만 한다."""
import argparse
import asyncio
import json
import sys
import uuid
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from sqlalchemy import select
from common.database.db_session_manager import DB_SESSION_MNG
from common.database.model.models import place_facts, places
from common.enums import DBWRType, ErrorType, FactStatus, SourceType
from common.models.gmodel import UserInfo
from crud.fact_crud import FactCRUD
from crud.place_crud import PlaceCRUD
from router.v1.fact.protocol import Req_UpsertFact
from services.fact_service import FactService
async def main(place_id, apply):
try:
err, rows = await DB_SESSION_MNG.execute_lambda(
places.DBType(), DBWRType.DB_READ.value,
lambda s: DB_SESSION_MNG.execute(s, select(places).where(
places.place_id == place_id, places.deleted.is_(False))),
)
if err != ErrorType.SUCCESS or len(rows or []) != 1:
raise RuntimeError('사업장 조회 실패')
place = rows[0]
err, facts = await DB_SESSION_MNG.execute_lambda(
place_facts.DBType(), DBWRType.DB_READ.value,
lambda s: DB_SESSION_MNG.execute(s, select(place_facts).where(
place_facts.place_id == place_id, place_facts.deleted.is_(False),
place_facts.source_type == SourceType.CRAWL.value,
place_facts.status.in_([FactStatus.UNVERIFIED.value, FactStatus.PENDING_OWNER.value]),
).order_by(place_facts.collected_at.desc())),
)
if err != ErrorType.SUCCESS:
raise RuntimeError('크롤링 후보 조회 실패')
service = FactService(FactCRUD(), PlaceCRUD())
actor = UserInfo(user_id=str(place.owner_user_id), id='crawl-policy', role=1)
seen = set()
result = []
for fact in facts or []:
key = (fact.unit_id, fact.key)
if key in seen:
continue
seen.add(key)
row = {'unitId': str(fact.unit_id), 'key': fact.key, 'value': fact.value}
if apply:
res = await service.upsert_fact(actor, str(place_id), Req_UpsertFact(
key=fact.key, value=fact.value, unit_id=fact.unit_id,
source_type=SourceType.CRAWL, source_url=fact.source_url,
expires_at=fact.expires_at,
))
if not res.result.success:
raise RuntimeError(f'{fact.key}: {res.result.desc}')
row['status'] = res.fact.status
result.append(row)
print(json.dumps({'applied': apply, 'place': place.name, 'facts': result},
ensure_ascii=False, default=str))
finally:
await DB_SESSION_MNG.dispose_all()
if __name__ == '__main__':
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--place-id', required=True, type=uuid.UUID)
parser.add_argument('--apply', action='store_true')
args = parser.parse_args()
asyncio.run(main(args.place_id, args.apply))