feat(seo-schema-generator): merge site-extraction + source-to-schema into one skill

Unify the two schema-generation scenarios into a single slot-17 skill, both
feeding one claims register -> build -> validate(16) pipeline:

- Mode 1 (existing site): NEW scripts/extract_site_claims.py turns URLs / local
  HTML / a directory into a claims register. Existing JSON-LD -> CONFIRMED;
  title/OpenGraph -> PENDING (never auto-shipped). + site-extraction-methodology.md
  and bundled fixtures/site/ demo pages.
- Mode 2 (not-yet-published site): land the source-to-schema engine
  (build_schema_drafts.py, type_templates.json, claims/source registers, 3 refs,
  sample_claims.csv) from the Desktop builder.
- Rewrite SKILL.md (v2.0) around the two-mode framing; the claims register is the
  shared pivot. Only CONFIRMED, non-conflicting claims become schema; unfilled
  template slots are pruned, never emitted as placeholders.
- Retire the old template-fill generator (code/ + desktop/); update root CLAUDE.md.

Self-tested both chains end-to-end: Mode 2 sample -> build -> validate PASS (P0=0);
Mode 1 fixtures -> extract -> build -> validate PASS (P0=0), JSON-LD round-trips with
nested address intact. Fixed two adapter bugs (nested node promotion; relative-path URI).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-28 00:38:40 +09:00
parent 3a8edebfef
commit 1706a820fe
39 changed files with 1536 additions and 1599 deletions

View File

@@ -0,0 +1,59 @@
# Entity → Schema Type Map (source-to-schema, pre-launch)
Maps the source/entity kinds collected in steps 12 to schema.org types, and lists the
Google rich-result requirements each type must satisfy. Required/recommended columns are
kept in sync with `16-seo-schema-validator/scripts/schema_rules.json` so drafts pass the gate.
## Type map
| Source / entity | Type | Google required | Key recommended | Cardinality |
|-----------------|------|-----------------|-----------------|-------------|
| Corporate/legal (DART, official About) | `Organization` | `name`, `url` | `logo`, `sameAs`, `address`, `contactPoint` | 1 per legal entity |
| Per-language site | `WebSite` | `name`, `url` | `inLanguage`, `publisher` | 1 per language |
| Hotel property | `Hotel` | `name`, `address` | `telephone`, `priceRange`, `geo`, `image`, `brand` | 1 per property × language |
| Executive / person | `Person` | `name` | `jobTitle`, `worksFor`, `sameAs`, `url` | 1 per person |
| Recruitment posting | `JobPosting` | `title`, `description`, `datePosted`, `hiringOrganization`, `jobLocation` | `validThrough`, `employmentType`, `baseSalary` | 1 per open role |
| Official video | `VideoObject` | `name`, `description`, `thumbnailUrl`, `uploadDate` | `duration`, `contentUrl`, `embedUrl` | 1 per featured video |
| FAQ / press-kit Q&A | `FAQPage` | `mainEntity` (Question→Answer) | `inLanguage`, `url` | 1 per FAQ page |
| Site navigation | `BreadcrumbList` | `itemListElement` | — | 1 per page |
Note: `JobPosting` and `VideoObject` were added to the validator rule set for this workflow,
since recruitment sites and the official YouTube channel are listed source types.
## Address requirement nuance (avoid false P0)
`PostalAddress` requires only `addressLocality` + `addressCountry` as the context-safe minimum.
`streetAddress` is **recommended** (P2), because it is expected for `Hotel`/`LocalBusiness` NAP
but NOT required by Google for `JobPosting.jobLocation` (city/region/country suffices). For
hotels, the L4 NAP-consistency check still enforces a complete, identical street address across
all pages of a property — so the street address signal is not lost where it matters.
## Brand-tier rule (Shilla)
- The Shilla, Shilla Monogram → `Hotel`
- Shilla Stay → `Hotel` (preferred; it is lodging, not a generic LocalBusiness)
- Always set `brand.name` (e.g. "The Shilla", "Shilla Monogram", "Shilla Stay") and link
`parentOrganization.@id` back to the single `Organization` node.
## @id entity graph (critical for pre-launch coherence)
Author stable `@id` URIs and cross-reference them so search engines read the portfolio as one
connected entity graph rather than disconnected snippets:
```
Organization @id = https://www.shillahotels.com/#org
▲ parentOrganization ▲ publisher ▲ hiringOrganization ▲ worksFor / publisher
│ │ │ │
Hotel (per property) WebSite (per lang) JobPosting Person / VideoObject
```
Rules
- One `Organization` node, one `@id`, referenced by every other node via `@id`.
- `@id` must be an absolute, stable URI (the provisional production URL + a `#fragment`).
- Every `@id` referenced must also be defined somewhere in the dataset (the validator's L4
checks for dangling `@id` references).
## When NOT to author schema
- Authenticated/transactional pages (mypage, login, booking cart) → no schema.
- Thin or duplicate pages → no schema until content exists.
- Any entity whose facts are still `conflict`/`PENDING` → resolve first (builder excludes them).

View File

@@ -0,0 +1,76 @@
# Site-Extraction Methodology (Mode 1 — from an existing website)
How to turn an existing site into a claims register, and why this mode is *easier*
than Mode 2 (collected sources) but still must not blindly trust what it scrapes.
Pair skill: extraction is `scripts/extract_site_claims.py`; the build engine and the
QA gate are shared with Mode 2. See `source-to-schema-methodology.md` for Mode 2.
## Why a website is the easy case (and where it still bites)
A published site has a **single source of truth** — the pages themselves — so there is
little to reconcile. The risks are different from Mode 2:
| Risk on an existing site | What it causes | Countermeasure |
|---|---|---|
| Trusting inferred meta as fact | wrong/old values shipped as schema | meta/OG seeded **PENDING**, never auto-shipped |
| Existing JSON-LD is partial or stale | gaps, outdated facts | extracted as CONFIRMED but **spot-checked** at review |
| Many near-identical pages | duplicate descriptions, bloated register | one entity per real thing; let Layer 4 catch dupes |
| JS-rendered schema not in raw HTML | "nothing extracted" | use a rendered snapshot / live fetch, or fall to Mode 2 |
## The 5 steps
### 1. Choose the pages
Pick the canonical page per entity (home, about/company, each property/location, key
product/FAQ pages). One representative page per entity is enough to seed it; you don't
need the whole crawl.
### 2. Extract
Run the adapter on URLs, local `.html` files, or a directory (offline):
```bash
python scripts/extract_site_claims.py https://site/ https://site/about --out site_claims
python scripts/extract_site_claims.py ./snapshot/ --out site_claims # offline
```
It produces two tiers of claims:
- **Existing JSON-LD → `CONFIRMED` (authority 1).** The site already published these
facts about itself; flattened to dotted-path claims.
- **`<title>` / meta description / OpenGraph / `<html lang>` / canonical → `PENDING`
(authority 2).** Inferred, not authoritative. These will **not** ship until confirmed.
### 3. Review the register (the critical human step)
Open `site_claims/claims_register.csv`:
- **Spot-check CONFIRMED rows** — extraction is faithful, but the site's own JSON-LD can
be wrong/stale. Correct values; clear nothing silently.
- **Confirm or drop PENDING rows** — set `status=CONFIRMED` only for facts you've verified;
delete the rest. PENDING rows are excluded by the builder by design.
- **Add what the page didn't expose** — telephone, full address, `geo`, `sameAs`,
`priceRange`. The richest schema usually needs facts no single page renders.
- Set `conflict=Y` on any value you're unsure about to keep it out until resolved.
### 4. Build
```bash
python scripts/build_schema_drafts.py site_claims/claims_register.csv --out drafts_out
```
Unfilled slots are pruned; only CONFIRMED, non-conflicting claims become schema. Read
`drafts_out/build_report.md` for everything excluded and why.
### 5. Validate (the gate)
```bash
python ../16-seo-schema-validator/scripts/validate_schema.py \
drafts_out/schema_drafts_dataset.csv --out qa_out
```
Gate = **zero P0**. Fix P0, re-build, re-validate, then open client review against the
report (not raw JSON).
## When NOT to use Mode 1
If the existing site **already has good, complete JSON-LD**, you don't need to regenerate
it — **audit it in place** with `16-seo-schema-validator` Mode B
(`validate_schema.py --live <URL>`). Mode 1 is for sites whose pages carry the *facts* but
not yet the *structured data*, or whose schema needs a rebuild.
## entity_id convention
The adapter assigns `prefix:slug` ids (`org:`, `site:`, `hotel:`, `dining:`, `page:`, …)
derived from each node's `@id` fragment or page URL. Rename them to stable, human ids
during review (e.g. `hotel:theshilla-seoul`) so re-runs and Mode 2 additions line up.

View File

@@ -0,0 +1,62 @@
# 출처 권위 위계 · 출처추적(Provenance) · 충돌 해소
미발행 사이트 스키마 저작의 성패는 "사실을 스키마로 굳히기 전에 단일 확정값을 만들 수 있는가"에 달려 있다. 그 판단 규칙을 명문화한다.
## 1. 출처 권위 위계 (authority rank)
값이 충돌할 때 **상위 권위 출처가 이긴다.** 클레임 레지스터의 `authority` 열에 1~5로 기록한다.
| 순위 | 출처 유형 | 신뢰 대상(어떤 사실에 권위) |
|:---:|-----------|------------------------------|
| **1** | 기업공시(DART), 사업자등록 정보 | 법인명·설립일·본사주소·대표자 (법적 사실) |
| **1** | 공식 홈페이지/IR, 프레스킷 | 브랜드 표기·연락처·URL·로고 (공식 표기) |
| **2** | 지속가능경영보고서, 사보, 공식 발간물 | 서사·정책·시설 스펙 |
| **2** | Wikidata, 위키백과 | 엔티티 식별자(Q-ID)·sameAs·국제 표기 |
| **3** | 주요 미디어 기사 | 사건·인용·맥락 (교차검증용) |
| **4** | 인물정보/집계 사이트, 소셜 | 보조·후보값 (단독 근거 불가) |
규칙
- **법적 사실**(법인명/설립일/주소)은 순위 1(공시) 우선.
- **공식 표기**(브랜드명/전화/URL)는 순위 1(공식 채널) 우선.
- **국제 식별/연결**(sameAs, 외국어 표기)은 Wikidata 우선.
- 순위 4 단독으로는 CONFIRMED 불가 → 상위 출처로 교차검증 필수.
## 2. 출처추적(provenance)을 남기는 이유
발행된 페이지 기반 작업은 "그 페이지가 근거"라는 자명한 출처가 있다. 미발행 작업은 그렇지 않으므로 **모든 클레임에 출처를 명시**해야 한다.
- `source_ids` — 소스 레지스터의 출처 ID(파이프로 복수). 예: `S-DART|S-OFF`
- 효용:
1. 충돌 시 권위 비교의 근거가 된다.
2. 럭셔리 브랜드 특성상 **사실 오류는 PR/법적 리스크** — 근거 추적이 방어선.
3. 런칭 후 사실 변경 시 어느 클레임을 갱신할지 즉시 특정 가능.
## 3. 충돌 해소 절차
```
값 충돌 발견
├─ 권위 순위가 다른가? ──예──▶ 상위 출처 채택, 하위는 note에 기록, status=CONFIRMED
└─ 동순위 충돌인가?
├─ 최신성(retrieved_date) 우선 적용 가능? ──예──▶ 최신 채택
└─ 판단 불가 ──▶ conflict=Y 유지, status=PENDING
→ 빌더가 자동 제외하고 build_report에 보고
→ 고객/이해관계자 질의로 확정 (최소 단위 질문)
```
원칙: **충돌이 미해소면 스키마에 넣지 않는다.** 모순된 사실로 만든 스키마는 NAP 불일치·KG 혼선으로 직결된다. 비우는 편이 틀리는 것보다 낫다.
## 4. 엔티티 정합(reconciliation) — 동명 함정
- 모든 핵심 엔티티는 **Wikidata Q-ID**로 못박는다(예: 호텔 법인 vs 동명 역사·지명).
- `sameAs`에는 검증된 식별 URL만: Wikidata, 위키백과, 공식 소셜.
- 미디어 기사 URL은 sameAs가 아님(엔티티 식별자가 아니라 언급).
- Knowledge Panel이 이미 있으면 그 표기를 공식 표기와 대조해 일치시킨다.
## 5. CONFIRMED 승격 체크리스트
- [ ] 값이 정규화됨(전화 E.164 / 날짜 ISO 8601 / 언어 BCP-47 / 국가 ISO alpha-2)
- [ ] `source_ids` 1개 이상, 핵심 사실은 권위 순위 1~2
- [ ] 동일 속성 충돌 없음(`conflict` 비어 있음)
- [ ] 엔티티는 `sameAs`/Q-ID로 식별 정합 완료
- 위 충족 시에만 `status=CONFIRMED` → 빌더가 스키마로 채택.

View File

@@ -0,0 +1,167 @@
# Source-to-Schema 표준 프로세스 (미발행 사이트용 Schema 저작)
> 대상: 아직 발행되지 않은 웹사이트의 구조화 데이터(JSON-LD)를 **텍스트 소스로부터 저작**하는 작업.
> 짝 스킬: 생성/저작은 `17-seo-schema-generator`(본 문서 = Mode 2 소스 기반), 검증은 `16-seo-schema-validator`.
---
## 0. 왜 이 작업이 "발행된 페이지 기반"보다 어려운가
발행된 페이지 기반 스키마는 **DOM이라는 단일 진실원본(single source of truth)** 이 이미 있고, 거기서 *추출*만 하면 된다. 미발행 사이트는 그 진실원본이 존재하지 않기 때문에 다음 네 가지 난점이 동시에 발생한다.
| # | 난점 | 결과적으로 생기는 결함 | 대응 원칙 |
|---|------|----------------------|-----------|
| 1 | 사실이 여러 출처에 흩어져 있고 서로 **충돌** (DART vs 위키 vs 브로셔) | NAP 불일치, 값 모순 | **출처 권위 위계**로 단일 값 확정 |
| 2 | 붙일 **URL이 아직 없음** | placeholder/TODO 누출 (최다 P0) | 확정값 없으면 **키 자체를 생성하지 않음** |
| 3 | 엔티티 식별을 **사람이 수동**으로 (어느 "신라"인가) | 잘못된 sameAs, 엔티티 혼선 | **Wikidata/Wikipedia 정합(reconciliation)** |
| 4 | 무엇을 만들지 **범위 자체가 미정** | 누락/과잉 엔티티 | **엔티티-타입 맵**으로 범위 선확정 |
**결론적 설계 원칙**: 스키마로 굳히기 *전에* "출처 → 클레임(claim) 확정"을 먼저 끝낸다. 정제되지 않은 사실을 곧장 JSON-LD에 부으면 모든 충돌·공백이 그대로 스키마 결함이 된다. 그래서 본 프로세스의 중심축은 **클레임 레지스터(claims register)** — 출처가 추적되고 충돌이 해소된 사실 대장 — 이며, **CONFIRMED 클레임만 스키마가 된다.**
---
## 9단계 표준 프로세스
각 단계는 목적 / 입력 / 절차 / 산출 / 완료기준(AC) / 리스크로 명세한다.
### 1단계. 온라인 정통 소스(authentic source) 수집
- **목적**: 권위 있는 1차 출처를 폭넓게 확보한다.
- **입력**: 대상 기업/브랜드명, 법인명, 도메인.
- **절차**: 다음을 수집·기록 → `templates/source-register.csv`
- 기업공시(**DART**) — 법인명/설립일/주소/대표자 (법적 사실의 최상위 권위)
- **공식 홈페이지**(About/푸터/IR), 지속가능경영보고서, 뉴스룸/미디어, 뉴스레터
- **Wikidata / 위키백과** — 엔티티 식별자(Q-ID)와 sameAs 후보
- 채용 사이트 — JobPosting 원천
- 공식 **YouTube 채널** — VideoObject 원천
- 공식 소셜/디지털 채널 — sameAs 후보
- 주요 미디어 기사, 인물정보 사이트 — 보조/교차검증용
- **산출**: 소스 레지스터(출처별 1행, 권위순위·언어·커버 엔티티 기록).
- **AC**: 각 핵심 엔티티에 대해 **최소 2개 독립 출처** 확보(교차검증 가능).
- **리스크**: 비공식·오래된 출처를 1차로 오인 → 권위순위를 반드시 명시.
### 2단계. 오프라인 콘텐츠 수집
- **목적**: 온라인에 없는 1차 사실(브랜드 서사, 시설 스펙, 공식 표기) 확보.
- **입력**: 브로셔 PDF, 사보/경영보고서, 프레스 킷, 보도자료 모음, 발간물.
- **절차**: 파일을 소스 레지스터에 등록(파일 경로·발행일·언어). PDF는 텍스트 추출(스캔본은 OCR) 후 출처 표기 유지.
- **산출**: 오프라인 출처도 동일 레지스터에 통합.
- **AC**: 모든 오프라인 소스에 `retrieved_date``authority` 기재.
- **리스크**: PDF 추출 시 인코딩 깨짐/표 붕괴 → 추출 직후 육안 점검.
### 3단계. 정규화 & 정제(distill)
- **목적**: 수집물을 **클레임 단위**로 분해하고 노이즈·중복·빈값을 제거한다.
- **입력**: 1·2단계 소스.
- **절차**:
1. 각 소스에서 사실을 (엔티티, 속성, 값, 출처) 단위로 추출 → `templates/claims-register.csv`
2. 동일 (엔티티, 속성)의 **중복 통합**, 빈값/노이즈 제거
3. 출처 충돌 시 `conflict=Y`로 표시(해소 전까지 스키마 진입 차단)
4. 표기 정규화: 전화(E.164), 날짜(ISO 8601), 언어코드(BCP-47), 국가(ISO 3166-1 alpha-2)
- **산출**: 1차 클레임 레지스터(아직 미확정 포함).
- **AC**: 모든 핵심 속성이 단일 정규화 값 후보를 가짐(또는 conflict/pending로 명시).
- **리스크**: 정규화 누락이 다운스트림 VAL 결함으로 직결 → 3단계에서 포맷 확정.
### 4단계. 텍스트 분석 & Knowledge Graph 교차검증
- **목적**: 클레임을 외부 KG와 대조해 **이중 점검**하고, 동시에 현황·개선과제를 도출한다.
- **입력**: 1차 클레임 레지스터.
- **절차**:
1. **엔티티 정합**: Wikidata Q-ID, 위키백과, Google Knowledge Panel과 대조 → `sameAs` 확정
2. 핵심 사실(설립일, 법인명, 대표자)을 KG 값과 비교 → 불일치는 `conflict`
3. KG에 **존재하지 않거나 빈약한 엔티티**를 식별 → *개선과제 자료*로 별도 기록(런칭 후 KG 강화 목표)
4. 충돌·미확정을 권위 위계로 해소 → `status=CONFIRMED` 승격
- **산출**: 확정 클레임 레지스터 + **KG 현황/개선과제 메모**(별도 컨설팅 산출물로 활용).
- **AC**: 모든 핵심 엔티티에 검증된 `sameAs` 1개 이상; conflict 0건.
- **리스크**: 동명 엔티티 오정합(예: "신라" 왕조 vs 호텔) → Q-ID로 못박기.
- 참고: `references/source-authority-hierarchy.md`
### 5단계. 유형별 활용 스키마 타입 분류
- **목적**: 어떤 엔티티에 어떤 schema.org 타입을 쓸지 범위를 확정한다.
- **입력**: 확정 클레임 + 페이지/엔티티 목록.
- **절차**: 엔티티·소스 유형을 타입에 매핑(아래는 본 스킬 기본 매핑).
| 소스/엔티티 | schema.org 타입 |
|-------------|-----------------|
| 법인·브랜드(공시/공식) | `Organization` |
| 언어별 사이트 | `WebSite` |
| 호텔 프로퍼티 | `Hotel` (= LocalBusiness 계열) |
| 임원/인물 | `Person` |
| 채용공고 | `JobPosting` |
| 공식 영상 | `VideoObject` |
| FAQ/프레스킷 Q&A | `FAQPage` |
| 사이트 내비게이션 | `BreadcrumbList` |
- **산출**: 엔티티-타입 맵(엔티티별 타입 + 필수 속성 목록).
- **AC**: 모든 대상 엔티티에 타입과 Google 필수 속성 목록이 배정됨.
- **리스크**: 과잉 타입 부여(불필요한 타입은 검증 부담만 가중) → "리치결과 가치 있는 타입" 우선.
- 참고: `references/entity-and-type-map.md`
### 6단계. 타입별 템플릿 설정 & 초안 추출 (자동화)
- **목적**: 확정 클레임 + 타입 템플릿으로 JSON-LD 초안을 **자동 생성**한다.
- **입력**: 확정 클레임 레지스터(`status=CONFIRMED`), `scripts/type_templates.json`.
- **절차**:
```bash
python scripts/build_schema_drafts.py path/to/claims_register.csv \
--templates scripts/type_templates.json --out drafts_out
```
- CONFIRMED·비충돌 클레임만 스키마가 됨. PENDING/REJECTED/conflict/공란은 **제외 후 보고**.
- 미충족 슬롯은 **키 자체를 삭제**(placeholder 누출 원천 차단).
- 엔티티 간 `@id` 참조로 엔티티 그래프 형성(Hotel→parentOrganization 등).
- **산출**: `drafts/*.jsonld`, 검증기 입력용 `schema_drafts_dataset.csv`, `build_report.md`(제외 클레임 목록).
- **AC**: 초안에 placeholder/빈 객체 0건; 모든 제외 클레임이 보고서에 사유와 함께 기재.
- **리스크**: 템플릿 누락 타입은 건너뜀(보고됨) → 5단계 맵과 템플릿 동기화.
### 7단계. 리뷰·검토·수정 + 리뷰 가이드
- **목적**: 초안을 사람·고객이 검토하되, **원본 JSON이 아니라 결함 리포트**로 검토하게 한다.
- **입력**: 6단계 초안 + 검증기 결과.
- **절차**:
1. 초안을 **즉시 검증기에 통과**(아래 8단계)시켜 P0=0 게이트부터 확보
2. 남은 항목을 `templates/review-guide.md` 기준으로 검토(사실 정확성·표기·번역)
3. 고객 검토는 P0가 0인 깨끗한 초안에 대해서만, 결함 리포트 기준으로 진행
- **산출**: 수정 반영 초안 + 리뷰 가이드 체크 결과.
- **AC**: 모든 P0 해소; 사실 정확성 검토 서명(저작자·검수자).
- **리스크**: 사람이 원본 JSON을 직접 보면 "오류 과다" 문제 재발 → 반드시 리포트 기반 검토.
- 참고: `templates/review-guide.md`
### 8단계. 수정 초안의 rich result 적격성 점검
- **목적**: 리치결과 적격성을 (1) 오프라인 게이트 + (2) Google 온라인 테스트로 이중 확인.
- **입력**: 수정 초안 데이터셋.
- **절차**:
```bash
# 오프라인 게이트 (반드시 zero P0)
python ../16-seo-schema-validator/scripts/validate_schema.py drafts_out/schema_drafts_dataset.csv --out qa_out
```
- 게이트 PASS 후, **표본 엔트리**를 Google Rich Results Test에 통과(이 런타임은 오프라인이라 온라인 테스트는 사용자가 수행)시켜 캡처.
- **산출**: 검증기 리포트(Gate PASS) + Rich Results Test 표본 통과 캡처.
- **AC**: P0=0, P1 트리아지 완료, 온라인 테스트 표본 green.
- **리스크**: 오프라인 규칙은 호텔 도메인 큐레이션 부분집합 → 온라인 표본 검사로 보완.
### 9단계. 발행 후 유효성 검증 & KG 변화 측정
- **목적**: 배포된 스키마가 저작 초안과 일치하는지 확인하고, 4단계의 KG 개선과제 달성도를 측정한다.
- **입력**: 라이브 URL.
- **절차**:
1. 검증기 **Mode B**(라이브 URL)로 렌더링된 스키마 재검증 → `seo-comprehensive-audit` 4단계 연계
2. GSC "리치 결과" 리포트 모니터링(신규 오류 0 유지)
3. 4단계 KG 메모 대비 Knowledge Panel/Wikidata 노출·정확도 변화 측정
- **산출**: 발행 후 검증 리포트 + KG 변화 측정(전/후 비교).
- **AC**: 라이브 스키마 = 저작 초안; GSC 신규 구조화데이터 오류 0; KG 개선과제 진척 기록.
- **리스크**: 렌더링 단계에서 JS로 스키마 누락/변형 → Mode B로 실측.
---
## 스테이지 게이트 (설계→개발→테스트→안정화→런칭 후)
| 게이트 | 단계 | DoD(완료 정의) |
|--------|------|----------------|
| **G1 설계** | 1·2·5 | 소스 레지스터 완료(엔티티당 ≥2출처), 엔티티-타입 맵 확정(타입+필수속성) |
| **G2 개발** | 3·4·6 | 클레임 레지스터 CONFIRMED·conflict 0, 빌더 실행 → 초안 placeholder 0 |
| **G3 테스트** | 7·8 | 검증기 **zero P0**, P1 트리아지(`decision-log`), 사실정확성 검수 서명 |
| **G4 안정화** | 8 | Google Rich Results Test 표본 green, 재실행 무회귀 |
| **G5 런칭 후** | 9 | 라이브=초안 일치, GSC 신규오류 0, KG 변화 측정 기록 |
---
## 산출물 일람
- `templates/source-register.csv` — 1·2단계 출처 대장
- `templates/claims-register.csv` — 3·4단계 사실 대장(스키마의 원천)
- `templates/review-guide.md` — 7단계 검토 기준
- `scripts/type_templates.json` — 6단계 타입별 JSON-LD 템플릿
- `scripts/build_schema_drafts.py` — 6단계 초안 자동 생성
- (검증) `16-seo-schema-validator` — 7·8·9단계 게이트