"""네이버 탐색 준비 상태를 **사람이 읽게** 찍는다. 레포 루트에서 (백엔드 venv 를 쓴다 — httpx·pydantic 이 거기 있다): solution/backend/.venv/bin/python geo/scripts/check_naver_eo.py solution/backend/.venv/bin/python geo/scripts/check_naver_eo.py --place "스테이,머뭄" solution/backend/.venv/bin/python geo/scripts/check_naver_eo.py --slug butter solution/backend/.venv/bin/python geo/scripts/check_naver_eo.py --json (도구에 물릴 때) ★ **컨테이너 안에서는 못 돈다.** `geo/` 는 백엔드 이미지에 들어가지 않는다 — 넣으려면 `solution/backend/Dockerfile` 을 고쳐야 하고, 백엔드 코드는 고치지 않기로 했다(2026-09-11). 서버에서도 레포 체크아웃 + 백엔드 venv 로 돌린다. 정기 실행이 필요해지면 `geo/Dockerfile` 로 자기 이미지를 갖는 것이 이 제약 아래서의 길이다(geo/README.md). ★ 판정은 여기서 하지 않는다 — `geo.naver.probe()` 가 한다. 이 파일은 그 결과를 줄로 바꾸는 일만 한다. 판정을 화면 코드에 두면 라우터가 붙을 때 기준이 둘로 갈린다. ★ 오리진 기본값은 `site_payload.publish_origin()` 이다. 발행 호스트를 새 env 로 또 두면 canonical 과 갈린다(AGENTS.md '발행 호스트는 두 곳에 있고 같아야 한다'). """ import argparse import asyncio import json import os import sys # ★ 두 자리를 얹는다: 레포 루트(`geo` 패키지) + `solution/backend`(도메인 코드). # admin/backend 가 PYTHONPATH 로 같은 일을 한다 — geo 도 백엔드 코드를 복제하지 않는다. _ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) for _path in (_ROOT, os.path.join(_ROOT, "solution", "backend")): if _path not in sys.path: sys.path.insert(0, _path) # ★ APP_ENV=test 면 .env 를 읽지 않는다(실키가 테스트로 새는 경로를 막아 뒀다). # 이 스크립트는 실제 네이버 API 를 부르므로 local 이어야 한다 — 다른 스크립트도 같다. os.environ.setdefault("APP_ENV", "local") from geo.naver import FAIL, OK, SKIP, WARN, probe # noqa: E402 from services.site_payload import publish_origin # noqa: E402 MARK = {OK: " ✓", WARN: " !", FAIL: " ✗", SKIP: " ·"} async def main() -> int: parser = argparse.ArgumentParser(description="네이버 탐색 준비 상태 점검") parser.add_argument("origin", nargs="?", default=None, help=f"확인할 오리진(기본 {publish_origin()})") parser.add_argument("--place", help="상호명 — 색인·역방향 링크 점검에 쓴다") parser.add_argument("--slug", help="통보 경로를 확인할 사이트(기본: 사이트맵의 첫 사이트)") parser.add_argument("--json", action="store_true", help="사람이 아니라 도구가 읽을 형태로") args = parser.parse_args() report = await probe( args.origin or publish_origin(), place_name=args.place, slug=args.slug, ) if args.json: print(json.dumps(report.as_dict(), ensure_ascii=False, indent=2)) return 0 if report.ok else 1 print(f"[네이버 점검] {report.origin}\n") for f in report.findings: print(f"{MARK[f.status]} {f.label}" + (f" — {f.detail}" if f.detail else "")) passed = [f for f in report.findings if f.status == OK] print(f"\n[결과] 통과 {len(passed)} · 주의 {len(report.warned)} · 실패 {len(report.failed)}") for title, rows in (("먼저 고칠 것", report.failed), ("확인이 필요한 것", report.warned)): if not rows: continue print(f"\n{title}:") for f in rows: print(f" - {f.label}: {f.detail}" + (f"\n → {f.fix}" if f.fix else "")) return 0 if report.ok else 1 if __name__ == "__main__": sys.exit(asyncio.run(main()))