`backend` 옆에 `front` 가 있을 이유가 없었다. negosium 의 negodata/front 를 그대로 베꼈고 그게 왜 front 인지는 따져보지 않았다 — 근거 없이 들여온 이름이라 바로잡는다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019uYhHQdssRubirPirrdJJC
8.8 KiB
수집·LLM·SEO/AEO 동작 구조
이 문서는 사업장 등록부터 정적 사이트 발행까지의 현재 구현을 설명한다.
1. 전체 흐름
업종·상호 입력
→ 외부 장소 검색
→ 사용자 동일 업소 확인
→ 채널 URL 등록 또는 발견
→ 사용자 채널 확인
→ 확정 URL 크롤링
→ 정보·사진 검토
→ Gemini 콘텐츠 생성
→ 템플릿 편집
→ 정적 빌드 및 발행 검수
→ 발행
핵심 원칙은 다음과 같다.
- 검증된 사업장만 수집하고 발행한다.
- 확정된 채널 URL만 크롤링한다.
- 자동 수집값은 사용자 승인 전까지 사이트에 노출하지 않는다.
- LLM은 확인된 사실을 표현할 뿐 새로운 사실을 만들지 않는다.
- JSON-LD와 실제 화면 내용이 다르면 발행하지 않는다.
2. 사업장 확인
사용자가 업종과 상호를 입력하면 네이버 지역검색 등의 외부 장소 API로 후보를 조회한다. 사용자가 자기 사업장을 선택하면 주소, 좌표와 외부 식별자를 저장하고 verified_at을 기록한다.
검증되지 않은 사업장은 수집과 발행이 제한된다.
주요 코드:
backend/services/place_service.pybackend/router/v1/place/
3. 채널 URL 확보
직접 등록
사용자가 네이버 플레이스 URL 등을 입력한다. 사용자가 직접 가져온 URL은 해당 사용자의 채널 확인으로 처리한다.
자동 발견
추가 채널까지 AI로 찾기 옵션을 켜고 자동 찾기를 실행하면 Perplexity Sonar를 한 번 호출한다.
- 입력: 상호, 주소, 업종
- 출력: 채널 URL 후보
- 검색 대상: 야놀자, 여기어때, 네이버 플레이스 등 허용된 도메인
- 제외 대상: 서비스 홈, 목록, 블로그·카페 후기
Perplexity 응답의 영업시간, 가격 같은 본문 정보는 사용하지 않는다. URL 후보만 place_links에 미확정 상태로 저장한다.
사용자가 내 채널 맞아요로 확인한 링크만 크롤링과 사이트 노출에 사용한다.
주요 코드:
backend/services/external/perplexity.pybackend/services/prompts/channel_discovery.pysolution/frontend/src/features/onboarding/useCollectFlow.tssolution/frontend/src/features/onboarding/ChannelConfirmPanel.tsx
4. 크롤링
수집 잡은 다음 순서로 실행된다.
채널 발견
→ 확정 링크 조회
→ URL별 어댑터 선택
→ 채널 데이터 파싱
→ 업종 스키마 적용
→ fact·사진 후보 저장
업종마다 허용하는 정보가 다르다.
- 카페: 메뉴, 음료, 좌석, 영업시간 등
- 음식점: 메뉴, 음식, 룸, 예약 관련 정보 등
- 숙박: 객실, 입·퇴실, 인원, 편의시설 등
- 관광·체험: 프로그램, 장비, 운영 정보 등
정확한 필드 목록은 backend/common/category_schema.py가 관리한다. 어댑터가 값을 찾더라도 업종 스키마에 없는 항목은 정식 fact로 사용하지 않는다.
주요 코드:
backend/services/collect_service.pybackend/services/collector/backend/common/category_schema.pybackend/services/fact_service.py
5. 정보 승인
크롤링 결과는 후보 상태로 저장된다.
UNVERIFIED: 아직 사용자가 확인하지 않은 정보PENDING_OWNER: 기존 노출값과 다른 재수집 후보VERIFIED: 사용자가 확인한 정보CORRECTED: 사용자가 직접 수정한 정보
발행 스냅샷에는 VERIFIED, CORRECTED 등 발행 가능한 상태만 포함한다. 재수집이 사용자가 수정한 값을 자동으로 덮어쓰지 않는다.
6. LLM 사용 위치
Gemini Vision
수집한 사진에 다음 정보를 생성한다.
- 업종별 사진 라벨
- 접근성용 alt 문구
- 신뢰도
결과는 입력 순서가 아닌 ref로 연결한다. 신뢰도가 낮은 결과는 사람 확인 대상으로 남긴다.
관련 파일:
backend/services/external/gemini.pybackend/services/prompts/vision.pybackend/services/vision_service.py
Gemini Text
확인된 fact만 이용해 다음 콘텐츠를 만든다.
- 사업장 소개문
- 검색 결과용 meta description
- FAQ
- 각 문장의 근거 fact key
생성 후 ground_check가 다음 항목을 코드로 검사한다.
- 근거에 없는 숫자와 가격
- 근거에 없는 시설
false,불가,없음값의 반대 표현- 과장 또는 홍보성 표현
- 근거 fact key가 없는 FAQ
검사를 통과하지 못한 결과는 반려 사유와 함께 저장한다.
관련 파일:
backend/services/external/gemini_text.pybackend/services/prompts/copy.pybackend/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에 사유를 기록한다. 검수 게이트를 우회하는 발행 옵션은 없다.
관련 파일:
backend/services/build_service.pybackend/services/snapshot.pybackend/services/site_payload.pybackend/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.tsxbackend/services/publish_gate.py
10. SEO·AEO 진단 점수
관리자 화면은 현재 저장 정보와 최신 발행본을 기준으로 SEO와 AEO 준비도를 각각 100점으로 계산한다.
SEO 항목:
- 정적 빌드
- 도메인과 canonical
- robots, sitemap, JSON-LD
- 주소와 전화번호
- 확인된 정보량
- alt가 있는 이미지
- 고유 콘텐츠
- 동일 업소 검증
AEO 항목:
- 사업장 엔티티 식별
- 좌표와 지역 문맥
- 확인된 사실량
- FAQ
- JSON-LD와
llms.txt - 출처와 검증 시각
- 최신 발행본
이 점수는 Google, Naver 또는 외부 SEO 도구의 공식 점수가 아니다. 우리 솔루션이 통제할 수 있는 검색 준비도 지표이며 순위나 AI 인용을 보장하지 않는다.
관련 파일:
backend/services/seo_audit.pybackend/router/v1/site/site.pysolution/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 호출을 하지 않는다. 채널 발견 옵션은 해당 발견 요청 한 번에만 적용되며 이후 크롤링이나 재수집으로 자동 승계되지 않는다.