지운 것 — 앞으로의 개발에 쓸 데가 없다. - solution/backend/demo_site.html: 어떤 스크립트도 만들지 않는 고아 산출물이고, 손으로 쓴 HTML 이라 "백엔드는 HTML 을 만들지 않는다" 와도 어긋난다. - solution/README.md: front/ · admin/.env · 사이트당 rooms/index.html·sitemap.xml 처럼 지금은 전부 틀린 서술이었다. 살아 있는 두 가지(빌더 CSR vs 발행물 SSG 대비표, 하이드레이션 블롭에 미검증 값이 샜던 실측)는 ARCHITECTURE 로 옮겼다. - docs/API_USAGE.md 의 Claude 개발비 집계: 2026-08-27 스냅샷과 재집계 스크립트는 일회성 지출 기록이라 제품 원가와 성격이 다르다. 문서를 외부 API 원가 하나로 좁혔다. 고친 것 — 코드를 따라가지 못하던 서술. - 코드 경로가 solution/backend 로 옮겨진 뒤 `backend/...` 로 남아 있던 포인터 전부. 가리키는 자리가 없는 경로는 문서가 아니라 함정이다. - ARCHITECTURE: 트리의 front→frontend, 컨테이너 표에 api-admin(:9801)·admin(:3002) 추가. - ★ ARCHITECTURE·AGENTS 의 "admin 전용 라우터가 0개" 는 사실이 아니었다. /v1/admin/local-content 가 admin 전용인데 :9800 에도 마운트돼 있다 — 포트를 가른 논리에 아직 남은 구멍이라 그렇게 적었다. - DECISIONS: 결론난 것을 미결로 두면 함정이 된다. 작업 큐(2026-08-27 결론), 날씨 캐시 TTL 1시간, jobs 테이블, media 조회 API, 수집 체인을 결론으로 옮기고 네이버 플레이스 대 TourAPI 실측(2026-08-31)을 1-1 에 이었다. - API_USAGE: 어댑터가 다 붙고 TourAPI 키도 나왔다. "실호출 0건" 은 낡은 서술이었다. - backend/README: 16→17 테이블(jobs), 없어진 alters/, MockAdapter 만이라는 서술, cd backend 경로, media·local 라우터 누락.
9.0 KiB
수집·LLM·SEO/AEO 동작 구조
이 문서는 사업장 등록부터 정적 사이트 발행까지의 현재 구현을 설명한다.
1. 전체 흐름
업종·상호 입력
→ 외부 장소 검색
→ 사용자 동일 업소 확인
→ 채널 URL 등록 또는 발견
→ 사용자 채널 확인
→ 확정 URL 크롤링
→ 정보·사진 검토
→ Gemini 콘텐츠 생성
→ 템플릿 편집
→ 정적 빌드 및 발행 검수
→ 발행
핵심 원칙은 다음과 같다.
- 검증된 사업장만 수집하고 발행한다.
- 확정된 채널 URL만 크롤링한다.
- 자동 수집값은 사용자 승인 전까지 사이트에 노출하지 않는다.
- LLM은 확인된 사실을 표현할 뿐 새로운 사실을 만들지 않는다.
- JSON-LD와 실제 화면 내용이 다르면 발행하지 않는다.
2. 사업장 확인
사용자가 업종과 상호를 입력하면 네이버 지역검색 등의 외부 장소 API로 후보를 조회한다. 사용자가 자기 사업장을 선택하면 주소, 좌표와 외부 식별자를 저장하고 verified_at을 기록한다.
검증되지 않은 사업장은 수집과 발행이 제한된다.
주요 코드:
solution/backend/services/place_service.pysolution/backend/router/v1/place/
3. 채널 URL 확보
직접 등록
사용자가 네이버 플레이스 URL 등을 입력한다. 사용자가 직접 가져온 URL은 해당 사용자의 채널 확인으로 처리한다.
자동 발견
추가 채널까지 AI로 찾기 옵션을 켜고 자동 찾기를 실행하면 Perplexity Sonar를 한 번 호출한다.
- 입력: 상호, 주소, 업종
- 출력: 채널 URL 후보
- 검색 대상: 야놀자, 여기어때, 네이버 플레이스 등 허용된 도메인
- 제외 대상: 서비스 홈, 목록, 블로그·카페 후기
Perplexity 응답의 영업시간, 가격 같은 본문 정보는 사용하지 않는다. URL 후보만 place_links에 미확정 상태로 저장한다.
사용자가 내 채널 맞아요로 확인한 링크만 크롤링과 사이트 노출에 사용한다.
주요 코드:
solution/backend/services/external/perplexity.pysolution/backend/services/prompts/channel_discovery.pysolution/frontend/src/features/onboarding/useCollectFlow.tssolution/frontend/src/features/onboarding/ChannelConfirmPanel.tsx
4. 크롤링
수집 잡은 다음 순서로 실행된다.
채널 발견
→ 확정 링크 조회
→ URL별 어댑터 선택
→ 채널 데이터 파싱
→ 업종 스키마 적용
→ fact·사진 후보 저장
업종마다 허용하는 정보가 다르다.
- 카페: 메뉴, 음료, 좌석, 영업시간 등
- 음식점: 메뉴, 음식, 룸, 예약 관련 정보 등
- 숙박: 객실, 입·퇴실, 인원, 편의시설 등
- 관광·체험: 프로그램, 장비, 운영 정보 등
정확한 필드 목록은 solution/backend/common/category_schema/(업종별 JSON + 로더)가 관리한다. 어댑터가 값을 찾더라도 업종 스키마에 없는 항목은 정식 fact로 사용하지 않는다.
주요 코드:
solution/backend/services/collect_service.pysolution/backend/services/collector/solution/backend/common/category_schema/solution/backend/services/fact_service.py
5. 정보 승인
크롤링 결과는 후보 상태로 저장된다.
UNVERIFIED: 아직 사용자가 확인하지 않은 정보PENDING_OWNER: 기존 노출값과 다른 재수집 후보VERIFIED: 사용자가 확인한 정보CORRECTED: 사용자가 직접 수정한 정보
발행 스냅샷에는 VERIFIED, CORRECTED 등 발행 가능한 상태만 포함한다. 재수집이 사용자가 수정한 값을 자동으로 덮어쓰지 않는다.
6. LLM 사용 위치
Gemini Vision
수집한 사진에 다음 정보를 생성한다.
- 업종별 사진 라벨
- 접근성용 alt 문구
- 신뢰도
결과는 입력 순서가 아닌 ref로 연결한다. 신뢰도가 낮은 결과는 사람 확인 대상으로 남긴다.
관련 파일:
solution/backend/services/external/gemini.pysolution/backend/services/prompts/vision.pysolution/backend/services/vision_service.py
Gemini Text
확인된 fact만 이용해 다음 콘텐츠를 만든다.
- 사업장 소개문
- 검색 결과용 meta description
- FAQ
- 각 문장의 근거 fact key
생성 후 ground_check가 다음 항목을 코드로 검사한다.
- 근거에 없는 숫자와 가격
- 근거에 없는 시설
false,불가,없음값의 반대 표현- 과장 또는 홍보성 표현
- 근거 fact key가 없는 FAQ
검사를 통과하지 못한 결과는 반려 사유와 함께 저장한다.
관련 파일:
solution/backend/services/external/gemini_text.pysolution/backend/services/prompts/copy.pysolution/backend/services/copy_service.py
7. 정적 빌드와 발행
발행 가능한 데이터로 snapshot 생성
→ 렌더러용 payload 생성
→ 정적 HTML 생성
→ 실제 HTML과 JSON-LD 비교
→ 발행 게이트 검사
→ site_version 저장
→ 발행
방문자가 보는 페이지는 DB를 실시간 조회하지 않는 정적 HTML이다. 검색 크롤러도 JavaScript 실행 없이 주요 정보를 읽을 수 있다.
★ 사이트 하나 = 한 장이다(2026-08-31 결정, 라우터 없음). 근거는 ARCHITECTURE.md 5절.
검수 실패 시 버전은 FAILED로 남고 publish_logs에 사유를 기록한다. 검수 게이트를 우회하는 발행 옵션은 없다.
관련 파일:
solution/backend/services/build_service.pysolution/backend/services/snapshot.pysolution/backend/services/site_payload.pysolution/backend/services/publish_gate.pysolution/site/
8. SEO 구현
발행본에는 다음 항목을 생성한다.
- 정적 HTML
- title과 meta description
- canonical URL
- Open Graph 메타 태그
- 이미지 alt 문구
robots.txt·sitemap.xml— ★ 오리진 루트에만 굽는다(사이트별로 만들지 않는다). 크롤러는 robots.txt 를 오리진 루트에서만 읽고(RFC 9309), 사이트맵은 전 사이트를 한 파일에 담는다- 사업장 JSON-LD
- FAQPage JSON-LD
- 공식 채널
sameAs datePublished,dateModified
관련 파일:
solution/site/src/seo/head.tssolution/site/src/seo/jsonld.tssolution/site/src/seo/robots.tssolution/site/src/seo/sitemap.ts
9. AEO 구현
답변형 검색 서비스가 사업장을 식별하고 답변할 수 있도록 다음 신호를 제공한다.
- 검증된 상호, 주소와 좌표
- 업종별 구조화 데이터
- 질문형 FAQ와 FAQPage JSON-LD
- 화면에 표시되는 핵심 답변 블록
- 확인된 사실을 정리한
llms.txt - 사실 출처와 검증 시각
- 공식 채널
sameAs - 발행일과 수정일
JSON-LD 값은 화면에도 동일하게 존재해야 한다. 렌더러가 양쪽을 비교하고 불일치하면 발행 게이트가 거절한다.
관련 파일:
solution/site/src/seo/llms.tssolution/site/src/seo/verify.tssolution/site/src/sections/AnswerBlock.tsxsolution/backend/services/publish_gate.py
10. SEO·AEO 진단 점수
내부 운영 화면(admin)은 현재 저장 정보와 최신 발행본을 기준으로 SEO와 AEO 준비도를 각각 100점으로 계산한다.
SEO 항목:
- 정적 빌드
- 도메인과 canonical
- robots, sitemap, JSON-LD
- 주소와 전화번호
- 확인된 정보량
- alt가 있는 이미지
- 고유 콘텐츠
- 동일 업소 검증
AEO 항목:
- 사업장 엔티티 식별
- 좌표와 지역 문맥
- 확인된 사실량
- FAQ
- JSON-LD와
llms.txt - 출처와 검증 시각
- 최신 발행본
이 점수는 Google, Naver 또는 외부 SEO 도구의 공식 점수가 아니다. 우리 솔루션이 통제할 수 있는 검색 준비도 지표이며 순위나 AI 인용을 보장하지 않는다.
관련 파일:
solution/backend/services/seo_audit.pysolution/backend/router/v1/site/site.pyadmin/frontend/src/pages/SeoAuditPage.tsx
API:
GET /v1/place/{place_id}/site/audit
11. 블로그 기능 범위
현재 블로그 글 자동 작성과 네이버 블로그 자동 발행은 구현되어 있지 않다.
현재 지원 범위는 공식 블로그 URL을 채널로 등록하고, 사용자가 확인한 뒤 사이트 푸터에 공식 채널 링크로 표시하는 것이다. 블로그·카페 후기는 자동 수집의 공식 사실 출처로 사용하지 않는다.
12. 외부 API와 과금 발생 지점
| 단계 | 외부 서비스 | 호출 조건 |
|---|---|---|
| 사업장 검색 | 네이버 지역검색 또는 카카오 로컬 | 사업장 후보 검색 |
| 채널 발견 | Perplexity Sonar | 사용자가 추가 채널 발견 옵션을 선택한 경우 |
| 사진 분석 | Gemini Vision | 분석할 사진이 있는 경우 |
| 소개문·FAQ | Gemini Text | 확인된 근거 fact가 있는 경우 |
일반 크롤링과 정적 빌드는 별도의 LLM 호출을 하지 않는다. 채널 발견 옵션은 해당 발견 요청 한 번에만 적용되며 이후 크롤링이나 재수집으로 자동 승계되지 않는다.