0005 가 도메인 스키마를 걷어내고 표 이름을 옮겼는데, 문자열로 표 이름을 들고 있던 자리들이
따라오지 않았다. import 도 타입검사도 pyflakes 도 못 잡는 종류라 전부 **실행되는 순간에만**
터졌고, 그동안 pytest 는 569건이 통째로 죽어 있어 아무것도 못 잡고 있었다.
**init.sql 이 새 DB 를 옛 구조로 세우고 있었다**
64ce467 이 이 파일에 94줄을 더하기만 하고 삭제를 0줄 했다. 그래서 이 파일 한 벌로 세운 DB 는
`place.place_links`·`job.jobs` 를 갖고 ORM 은 `public.place_channels`·`public.jobs` 를 찾는다 —
기동은 정상이고 첫 쿼리에서 죽는다. "init.sql 은 새 DB 를 세우는 전체 DDL 이고 계속 최신을
유지한다"(migrations/README.md)는 계약이 깨져 있었다.
- public 한 벌 · 표 14개로 다시 썼다. 옛 스키마가 있는 DB 에서 다시 돌면 RAISE EXCEPTION 으로
멈춘다 — 그대로 두면 public 에 빈 표가 생기고 0005 가 "relation already exists" 로 실패해
데이터가 옛 스키마에 갇힌다
- 말미에 **마이그레이션 기준선**을 심는다. 없으면 새 DB 에서 migrate.py 가 0001 부터 다시 돌다가
`schema "local" does not exist` 로 죽는다
**운영 버그 둘** — 두 DB(새로 세운 것 · 마이그레이션으로 따라온 것)를 pg_dump 로 찍어 비교해 찾았다
- `upsert_weather` 의 ON CONFLICT 술어에 `kind IS NULL` 이 빠져 **날씨 캐시 저장이 계속 실패**하고
있었다(0007 이 인덱스에 그 조건을 더했다). 캐시라 화면이 안 죽고 로그에만 남았다.
포스트그레스는 술어가 인덱스 술어를 함의하는지 보고 아니면 "no unique or exclusion constraint
matching" 으로 거절한다 — 컬럼도 표도 멀쩡해서 눈으로는 원인이 안 보인다
- ORM 의 `area_contents` 인덱스 정의가 0004·0007·0008 을 하나도 안 따라왔다. 테스트 DB 는 이
모델로 세워지므로 **테스트가 운영과 다른 제약 아래에서 돌고 있었다**
**0009** — 두 DB 비교에서 나온 어긋남 셋(데이터는 안 건드린다)
- `idx_site_contents_site` 가 기존 DB 에만 없었다(0003 이 유니크만 걸었다) — 섹션 조회가 시퀀셜 스캔
- `places.external_place_id` VARCHAR(32) → (64). ORM 은 64 다 — 긴 id 가 잘리면 동일 업소 판정이 틀린다
- RENAME 이 안 따라간 PK 제약 이름 9개(`facts_pkey` → `place_facts_pkey` …)
**테스트를 살린다**
- conftest 의 TRUNCATE 가 표 이름을 **손으로 나열**하고 있었다. 0005 가 이름을 옮기자 전 테스트가
`relation "place_aliases" does not exist` 로 죽었다 — 이제 ORM 메타데이터에서 뽑아 다시 어긋날 수 없다
- `test_schema_ddl` 이 모델 표를 `"None.users"` 로 조회해 **한 표도 비교하지 않고 통과**하고 있었다.
init.sql 이 조용히 어긋난 동안 이 테스트는 초록이었다. 비교한 표 수를 세는 단언을 더한다
- 테스트 SQL 15곳의 옛 표 이름, `_run_worker` 1틱 문제(수집 뒤 따라오는 LOCAL_SYNC 를 집어 가
정작 기다리던 잡이 PENDING 으로 남았다), 지역 캐시 픽스처(읽는 코드가 옳게 거르는데 테스트가 빨개졌다)
**문서**
- `docs/DATA_MODEL.md` 신설 — 표 14개가 무엇을 담고 누가 쓰는지, 값 하나가 DB 에서 페이지까지
가는 길, 두 번 도는 게이트, **DB 에 없는 것**
- `SERVERS.md` DB 절을 마이그레이션 체계로. 배포에 `migrate.py` 를 넣는다 — 코드만 갈면 컨테이너는
정상으로 뜨고 가게 등록·수집·발행만 죽는다
- ARCHITECTURE 2절의 프리렌더 컨테이너가 `solution-frontend` 로 적혀 있었다. 굽는 건
`solution-prerender` 고 전자는 운영에서 뜨지도 않는다 — AGENTS.md 가 함정으로 적어 둔 그 혼동을
문서가 만들고 있었다
- 옛 표 이름 잔재(`place_links`·`local_contents`·`job.jobs`·`company.users`·`fact.facts`·`ai_check_results`)
검증: 빈 컨테이너에 init.sql 로 세운 DB ↔ 마이그레이션으로 따라온 DB 를 `pg_dump --schema-only`
로 비교 — 표·인덱스·제약·컬럼 전부 동일. pytest 583건 중 581 통과(남은 2건은 `.env` 누수·
레이트리밋 카운터로 환경 문제다). 구글 로그인 21건 포함.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
17 KiB
Web4AI 개발 방향 — v19 설계서와 현재 구현 비교
기준일: 2026-08-31
비교 대상:Web4AI_SW설계서_및_개발일정_v19.pdf(22쪽)와 이 저장소의 현재 코드·문서
목적: 설계서를 그대로 복제하는 것이 아니라, 현재 제품에서 유지할 결정, 방향을 다시 정할 결정, 추가할 개발 항목을 구분한다.
설계서 표지는 파일명과 달리 v18 · 2026.08.30으로 표기되어 있다. 아래에서는 전달받은 파일을 편의상 “v19 설계서”라고 부르되, 계약·일정 확정 전 문서 버전부터 확인해야 한다.
1. 결론
현재 프로젝트는 설계서 전체의 축소판이 아니라, 설계서의 Site AEO(A1~A8) 일부를 소상공인용 제품으로 먼저 구현한 별도 MVP에 가깝다.
- 현재 강점은
사업장 확인 → 허용된 소스 수집 → fact 승인 → 근거 기반 문구 생성 → 정적 HTML 발행 → IndexNow가 실제 코드와 테스트로 연결되어 있다는 점이다. - 가장 큰 공백은 Brand AEO 전체(B1~B9), 규제 검사(A4), 소유권 검증(A1), **원본 변경·AI 크롤러 재방문 추적(A9)**이다.
- 가장 큰 방향 충돌은 타겟 업종, Playwright 크롤링, 배포 도메인, 마이크로서비스·공통 인프라다. 이 항목은 “미구현”으로 보고 바로 만들면 안 되고 제품·법무·운영 결정을 먼저 내려야 한다.
- 권장 방향은 현 구조를 버리고 5계층/2엔진으로 즉시 재작성하는 것이 아니다. 현재 시스템을 Site AEO MVP 기준선으로 유지하고, Brand AEO를 경계가 분명한 모듈로 붙인 뒤 부하와 조직 규모가 실제 분리를 요구할 때 서비스로 분리한다.
현재 범위의 대략적인 위치
| 설계서 영역 | 현재 판단 |
|---|---|
| Site AEO A1~A9 | 부분 구현 — A3·A5·A6·A7 일부와 A8 중심 |
| Brand AEO B1~B9 | 미구현 — 준비도 자체 점수만 있으며 실제 AI 응답 측정은 없음 |
| 운영 콘솔 15개 화면 | 부분 구현 — 사업장·빌더·지역정보·SEO/AEO 준비도 중심 |
| 계약 A~G / BFF | 미구현 — 화면이 FastAPI 를 직접 호출 (BFF 없음) |
| 25테이블 append-only Fact Graph | 다른 모델로 구현 — 승인 후보/노출값 중심의 key-value fact 모델 |
| 8개 스프린트 일정 | 현재 코드에 바로 적용 불가 — 이미 구현된 것과 방향 충돌 항목이 섞여 있어 재산정 필요 |
2. 방향이 다른 부분
아래는 단순히 덜 만든 기능이 아니라, 설계서와 현재 프로젝트가 서로 다른 결정을 내린 항목이다.
| 항목 | v19 설계서 | 현재 프로젝트 | 권장 판단 |
|---|---|---|---|
| 제품 범위 | Site AEO + Brand AEO 이원 플랫폼 | 상호명 기반 소상공인 정적 홈페이지 생성·발행 | 현재 제품을 Site AEO MVP로 명시하고 Brand AEO 확장 여부를 별도 마일스톤으로 승인 |
| 1차 업종 | 숙박, 법무법인, 성형외과 | 숙박, 카페, 음식점, 관광체험 | 반드시 사업 결정 필요. 법률·의료를 넣으면 데이터 스키마뿐 아니라 규제·승인·감사 체계가 선행되어야 함 |
| 입력 방식 | 고객 원본 사이트를 depth 3·최대 200페이지 크롤 | 공식 API, 사용자 확정 URL, 정적 HTML; 플랫폼 우회 수집 금지 | 현재의 출처·동의·robots 원칙 유지. 대규모 원본 사이트 수집은 별도 제품 모드로 분리 |
| 동적 크롤링 | 정적 우선, Playwright 폴백 | 봇 탐지 우회로 변질될 수 있어 HeadlessAdapter 미등록·금지 | 포괄적인 Playwright 폴백은 채택하지 않음. 소유권이 검증된 고객 도메인에만 허용할지 법무·보안 결정 후 제한적으로 검토 |
| 발행 도메인 | 고객 도메인 서브패스 권장, 서브도메인 차선 | 기본 web4ai.o2osolution.ai/s/<slug>, custom domain 경로 미완성 |
설계서 방향이 검색 권위 측면에서 더 적합. 고객 도메인 연결·소유권 검증을 우선 과제로 추가 |
| 백엔드 구조 | Site/Brand 엔진, BFF, 엔진별 DB 분리 | 코드 한 벌 + 진입점 둘(:9800 사장님 / :9801 내부), 단일 PostgreSQL | 청중별 분리는 포트로 끝냈다. 엔진별 물리 분리는 트래픽·팀 소유권 근거가 생긴 뒤 수행 |
| 작업 인프라 | Temporal, Redis, Celery 등 공통 인프라 | PostgreSQL 잡 큐 + lease + dead-letter | 현재 DB 큐 유지. 동일 책임의 인프라를 중복 도입하지 않음. 장기 워크플로 보상·분산 추적 요구가 확인될 때 Temporal 재평가 |
| Fact Graph | 엔티티·predicate·snapshot, append-only | 업종 스키마 기반 key-value fact, 후보/노출/이력 상태 | 현재 모델은 발행 안전성에 적합. Brand 측정 재현성에 필요한 snapshot과 entity 관계만 점진적으로 확장 |
| 점수 | Site AEO Score + 실제 4개 AI 엔진 기반 AVS | 내부 데이터 기반 SEO/AEO 준비도 점수 | 이름과 의미를 분리 유지. 실제 측정 전 현재 점수를 AVS/가시성 점수라고 부르지 않음 |
| 운영 사용자 | Reviewer/Owner 권한과 15개 통합 화면 | USER/OWNER/DEVELOPER, 사장님 앱(:3000)과 내부 콘솔(:3002)이 갈려 있다 | 규제 기능 도입 전 Reviewer 역할·서버 계산 allowed_actions 추가 |
| 이미지 | 원본 사진·영상이 EEAT 근거 | 이미지 호스팅을 현재 non-goal로 두고 외부 URL 사용 | 저작권 결론과 소유자 업로드 저장소가 먼저. 크롤 이미지 재게시를 전제로 개발하지 않음 |
3. 설계서 항목별 구현 차이
3-1. Site AEO A1~A9
| 단계 | 현재 상태 | 코드 근거 | 추가할 것 |
|---|---|---|---|
| A1 사이트 진단·소유권 검증 | 일부 | 사업장 동일 업소 확인과 verified_at, SEO 진단은 있으나 DNS TXT/meta/well-known 검증은 없음 |
도메인 소유권 challenge, 만료·재검증, 발행 차단 정책 |
| A2 크롤·추출 | 일부 | collector registry, StaticHtmlAdapter, TourAPI, 네이버 장소 조회, 사용자 확정 링크 |
허용 도메인용 crawl run/document 기록, 원본 hash, 제한·재시도·수집 보고서 |
| A3 Fact Graph | 부분 구현 | facts, 업종 스키마, 출처·신뢰도·상태 전이, 승인 후보 모델 |
source URL의 selector/snippet, entity 관계, 측정용 불변 snapshot |
| A4 규제·과장 검사 | 기초만 존재 | 생성 문구의 과장·근거 없는 숫자/시설 검사는 있으나 업종별 법규 3단 분류와 승인 감사는 없음 | 외부화된 규칙, SAFE/REVIEW/BLOCK 판정, 규칙 버전, 근거 snippet, Reviewer 승인 로그 |
| A5 AEO 콘텐츠 생성 | 구현 | Gemini Text, 소개문·meta·FAQ, 근거 fact key, ground check | 질문은행과 생성 페이지의 연결, 질문형 콘텐츠 단위의 버저닝 |
| A6 JSON-LD | 구현 | 업종별 JSON-LD, FAQPage, Breadcrumb, WebPage, sameAs | 법률·의료 도입 시 타입·자격·저자 스키마 확장 |
| A7 3-way 일치성 | 부분 구현 | JSON-LD↔표시 텍스트 검증, 생성문↔fact 근거 검사, publish gate | 원본↔발행본 SimHash 중복도, 모든 사실 문장의 fact ID 역참조 보고서 |
| A8 배포 | 대부분 구현 | 프리렌더 정적 HTML, canonical, sitemap, robots, llms.txt, IndexNow, nginx/Azure 경로 | 고객 도메인 서브패스·서브도메인 연결, TLS/DNS 자동화, Search Console 제출 자동화 여부 |
| A9 모니터링·변경 감지 | 미구현 | AI_CHECK enum 만 있고 worker handler·보고 모듈 없음. 표(ai_check_results)는 한 번도 안 써서 마이그레이션 0006 이 뗐다 — 붙일 때 다시 만든다 |
CDN 크롤러 로그 수집, 원본 hash 비교, fact 만료/재검토, 재생성 큐 |
3-2. Brand AEO B1~B9
현재 seo_audit.py의 점수는 “발행 준비도”다. 설계서의 Brand AEO처럼 ChatGPT·Gemini·Claude·Perplexity 응답을 정기 호출하여 브랜드 언급·인용·순위·사실성을 측정하지 않는다.
필요한 기능은 다음 순서가 적절하다.
- B1 질문은행: 업종×지역×의도 질문, 버전, 활성 세트, 고객 편집 이력
- B2 측정 스케줄: 주기, 엔진, 반복 횟수, 비용 상한, 중지 조건
- B3~B4 엔진 어댑터와 원문 보존: 모델·버전·프롬프트·응답·citation 정규화
- B5 분석: 브랜드 alias 언급, 인용 URL, 추천 위치, 감성, fact snapshot 대조
- B8 이원 점수: 원계열, 반복 평균, 신뢰구간, MA4를 모두 보존하고 준비도 점수와 별도 표시
- B6~B7 개선 루프: EEAT 결손과 외부 인용원을 제안하되, 자동 발행하지 않고 fact 확인/A4/A7을 재통과
- B9 리포트: 주간·월간 리포트, 모델 교체 마커, 비용과 데이터 결손 표시
100문항×4엔진×3회라는 설계서 기본값은 테넌트당 주 1,200회 호출이다. 현재 제품 원가 상한인 사이트당 약 $1과 충돌할 가능성이 높으므로, 구현 전 모델별 실측 단가와 파일럿 질문 수를 다시 계산해야 한다. 초기에는 10~20개 핵심 질문, 1~2개 엔진, 반복 3회로 시작하고 통계적 유효성과 비용을 함께 측정하는 편이 안전하다.
3-3. 콘솔·계약·데이터
- 내부 콘솔(
admin/frontend)에는 사업장 목록/상세, 지역 콘텐츠, SEO 진단이 있고 빌더는 사장님 앱(solution/frontend)에 있다. 설계서의 질문 빌더·규제 승인·AI 퍼포먼스·랭킹·인용출처·정기 리포트 화면은 없다. - 화면이 API 를 직접 호출한다. 설계서의 BFF, 서비스 JWT 교환, 섹션별 부분 실패, 계약 A~G는 없다.
- DB에는 현재 17개 ORM 모델이 있으며 설계서의
document,predicate_def,entity,fact_snapshot,compliance_rule,review,publication_question,index_state,regeneration_request,event_outbox,audit_log등에 해당하는 완성 모델은 없다. - 현재 fact는 수정 잠금과 후보 이력을 보존하지만, 설계서가 요구하는 전체 append-only 불변식·스냅샷 재현성 모델과 같지는 않다.
계약 A~G를 한 번에 33개 REST/7개 이벤트로 만드는 것보다, 먼저 아래 세 계약만 버전 고정하는 것을 권장한다.
FactSnapshot: 측정 당시의 발행 사실을 재현할 수 있는 읽기 전용 계약QuestionSet: 생성 대상과 측정 대상을 같은 question ID로 연결하는 계약Publication: 발행 URL과 question ID를 연결해 인용 성과를 귀속하는 계약
이 세 계약이 있어야 Brand AEO 결과가 단순한 “브랜드가 나왔다”를 넘어 “어떤 질문을 겨냥한 어떤 페이지가 인용됐다”까지 설명할 수 있다.
4. 권장 개발 우선순위
P0 — 개발 전에 확정할 결정
- 제품 범위: Site AEO 소상공인 MVP를 유지할지, 법률·의료와 Brand AEO를 이번 제품 범위에 포함할지
- 1차 파일럿: Stay 머뭄 1곳 우선인지, 3업종 동시인지
- 도메인 전략: 고객 서브패스 / 고객 서브도메인 / 플랫폼 공용 경로의 지원 우선순위
- 크롤 정책: 소유권 검증 고객 도메인의 JS 렌더링 허용 범위. 플랫폼 robots·봇 차단 우회 금지는 유지
- 이미지 권리: 소유자 업로드만 허용할지, 기존 플랫폼 사진 재게시를 허용할지
- 비용 예산: Brand 측정의 테넌트당 주간 호출·금액 상한
- 문서 버전: 전달 파일의 파일명 v19와 표지 v18 불일치 해소
P1 — 현재 Site AEO를 설계서 수준으로 닫기
- 도메인 소유권 검증과 만료 시 발행 차단
- crawl run/document와 원본 hash 저장
- A7 SimHash 중복도 검사 및 fact 역참조 리포트
- A9 원본 변경 감지, fact 만료/검토, AI 크롤러 방문 로그
- 고객 도메인 연결, TLS/DNS 운영 절차
- 서버 계산
allowed_actions(앱 경계 분리는 2026-08-31 완료)
완료 기준은 “페이지가 만들어진다”가 아니라, 소유권이 확인된 원본에서 출처가 남는 fact를 만들고 두 게이트를 통과해 고객 도메인에 발행되며, 변경과 재방문을 관측할 수 있다는 것이다.
P2 — 규제 업종을 넣는 경우에만 선행
- Reviewer 역할과 승인 워크벤치
- 외부화된 업종별 규칙과 버전 관리
- SAFE / NEEDS_EVIDENCE / PROHIBITED 3단 판정
- 규칙 ID·근거 snippet·판정 모델·승인자·시각을 남기는 감사 로그
- 성형외과 사전심의 상태와 자격/면허 fact 모델
- 법률·의료 전문가의 규칙 승인 및 변경 절차
A4가 완성되기 전에는 법무법인·성형외과 콘텐츠 자동 발행을 열지 않는다.
P3 — Brand AEO 최소 측정 루프
- 질문은행과 publication-question 연결
- fact snapshot
- 1~2개 AI 엔진 어댑터와 응답 원문·모델 버전 저장
- 언급·인용 URL·추천 위치·사실성 분석
- 반복 측정, 신뢰구간, MA4, 비용 집계
- 읽기 전용 퍼포먼스·인용출처 화면
처음부터 자동 EEAT 재생성까지 닫지 말고, 먼저 같은 질문을 반복 측정했을 때 지표가 의사결정에 쓸 만큼 안정적인지 검증한다.
P4 — 개선 폐루프와 운영 확장
- EEAT 결손 → 고객 확인 요청 / 재생성 요청 분기
- 재생성 요청의 A4·A7 재통과
- 정기 리포트와 외부 채널 전략
- BFF의 섹션별 부분 실패와 서비스 JWT
- 데이터·트래픽·팀 소유권이 임계에 도달하면 Site/Brand 저장소 및 배포 단위 분리
5. 재작성하지 않고 유지할 현재 구현
설계서와 다르더라도 아래는 현재 제품에 맞고 이미 안전장치가 있으므로 유지하는 편이 낫다.
- PostgreSQL 기반 잡 큐의 원자적 claim, lease, dedupe, dead-letter
VERIFIED/CORRECTED만 발행하고 재수집 후보가 정정값을 덮지 않는 상태 모델- 백엔드는 payload만 만들고 프론트 프리렌더러가 정적 HTML을 생성하는 경계
- JSON-LD와 화면값 불일치 시 발행을 막는 게이트
- 외부 API 키가 없어도 해당 어댑터만 비활성화하는 구성
- robots.txt와 약관을 우회하지 않는 수집 원칙
- 현재 SEO/AEO 점수를 “준비도”로 명시하는 정직한 표현
6. 일정 재구성 제안
설계서의 S1~S8은 신규 구축 기준이라 현재 저장소에 그대로 적용하면 이미 끝난 기반 작업을 반복하고, 미결 정책을 코드로 먼저 굳히게 된다. 다음과 같이 게이트 중심으로 다시 잡는다.
| 마일스톤 | 목표 | 종료 조건 |
|---|---|---|
| M0 방향 확정 | 범위·업종·도메인·크롤·비용 결정 | P0 결정 기록과 승인 |
| M1 Site 완결 | A1/A7/A9 공백과 고객 도메인 보완 | 소유권→발행→변경감지 E2E 통과 |
| M2 규제 게이트 | 법률·의료를 할 경우 A4 구축 | 전문가 승인 룰셋과 감사 가능한 차단/승인 |
| M3 Visibility 파일럿 | 질문은행·snapshot·최소 엔진 측정 | 반복 측정의 비용·분산·인용 검출 정확도 보고 |
| M4 개선 루프 | 측정 결과를 안전한 재생성 요청으로 연결 | 고객 확인 또는 A4/A7 재통과 후 발행 |
| M5 플랫폼화 | 콘솔/BFF/서비스 분리 | 실제 트래픽·팀 소유권 기준 충족 시에만 수행 |
주차 추정치는 P0의 업종 수, AI 엔진 수, 외부 전문가 검토 가능일이 정해진 뒤 산정한다. 특히 3개 업종 동시 개발과 4개 엔진×3회 측정을 전제로 한 기존 8스프린트 일정은 현재 인력·비용 정보 없이 확정 일정으로 취급하면 안 된다.
7. 바로 만들 백로그
| 우선순위 | 에픽 | 대표 산출물 |
|---|---|---|
| 1 | 소유권 검증 | challenge 테이블/API, DNS/meta/well-known 검증기, 만료 정책, 발행 게이트 |
| 2 | 수집 재현성 | crawl run, document hash, source selector/snippet, 변경 비교 |
| 3 | 일치성 강화 | SimHash, fact 역참조 커버리지, 실패 사유 UI |
| 4 | 발행 관측 | crawler visit/index 상태, CDN 로그 적재, 재수집/재생성 조건 |
| 5 | 고객 도메인 | 서브패스/서브도메인 연결, canonical·sitemap 검증, TLS/DNS 운영 |
| 6 | 측정 계약 | question set, fact snapshot, publication-question 연결 |
| 7 | Visibility 파일럿 | 엔진 어댑터, 원문 로그, mention/citation/position/factuality 분석, 비용 상한 |
| 8 | 운영 화면 | 소유권·수집·게이트·발행 상태부터 추가, 이후 질문/성과/인용 화면 |
| 조건부 | 규제 업종 | 규칙 저장소, Reviewer, 승인 워크벤치, 감사 로그, 사전심의 |
8. 관련 현재 문서
이 문서는 비교와 향후 방향만 다룬다. 현재 제품 원칙과 구현 상세는 중복해서 관리하지 않는다.
- 제품 범위와 non-goal: PRODUCT.md
- 현재 수집·생성·발행 흐름: COLLECTION_SEO_AEO_FLOW.md
- 법무·데이터·작업 큐 결정: DECISIONS.md
- 데이터 소스 실측: DATA_SOURCE_RESEARCH.md
- 배포와 도메인 운영: DEPLOY.md
- 외부 API 비용: API_USAGE.md