Files
Andrew Yim 1706a820fe 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>
2026-05-28 00:38:40 +09:00

88 lines
7.7 KiB
Python

#!/usr/bin/env python3
"""
make_sample.py — generate fixtures/sample_claims.csv
A small, realistic claims register for the Shilla context. It exercises:
- 5 entity types: Organization, WebSite, Hotel, Person, JobPosting, VideoObject
- dotted nested paths (address.*, geo.*) and an array (sameAs[])
- @id cross-references between entities (Hotel.parentOrganization -> org:shilla)
- the EXCLUSION gate: one PENDING claim, one CONFLICT claim, one EMPTY value
Run, then: python scripts/build_schema_drafts.py fixtures/sample_claims.csv
"""
import csv, os
ROWS = [
# entity_id, entity_type, property, value, lang, url, source_ids, authority, confidence, conflict, status, note
# ---- Organization (DART + official + Wikidata) ----
("org:shilla", "Organization", "@id", "https://www.shillahotels.com/#org", "", "", "", "1", "high", "", "CONFIRMED", ""),
("org:shilla", "Organization", "name", "The Shilla Hotels & Resorts", "", "", "S-OFF|S-WIKI", "1", "high", "", "CONFIRMED", ""),
("org:shilla", "Organization", "legalName", "주식회사 호텔신라", "", "", "S-DART", "1", "high", "", "CONFIRMED", "DART 법인명"),
("org:shilla", "Organization", "url", "https://www.shillahotels.com/", "", "", "S-OFF", "1", "high", "", "CONFIRMED", ""),
("org:shilla", "Organization", "foundingDate", "1973-05-09", "", "", "S-DART", "1", "high", "", "CONFIRMED", ""),
("org:shilla", "Organization", "sameAs", "https://www.wikidata.org/wiki/Q494845|https://en.wikipedia.org/wiki/The_Shilla", "", "", "S-WIKI|S-WD", "2", "high", "", "CONFIRMED", "array via pipe"),
("org:shilla", "Organization", "address.streetAddress", "동호로 249", "", "", "S-DART", "1", "high", "", "CONFIRMED", ""),
("org:shilla", "Organization", "address.addressLocality", "중구", "", "", "S-DART", "1", "high", "", "CONFIRMED", ""),
("org:shilla", "Organization", "address.addressRegion", "서울", "", "", "S-DART", "1", "high", "", "CONFIRMED", ""),
("org:shilla", "Organization", "address.addressCountry", "KR", "", "", "S-DART", "1", "high", "", "CONFIRMED", ""),
# ---- WebSite (per-language) ----
("site:ko", "WebSite", "@id", "https://www.shillahotels.com/ko#website", "ko", "https://www.shillahotels.com/ko/", "S-OFF", "1", "high", "", "CONFIRMED", ""),
("site:ko", "WebSite", "name", "신라호텔", "ko", "", "S-OFF", "1", "high", "", "CONFIRMED", ""),
("site:ko", "WebSite", "url", "https://www.shillahotels.com/ko/", "ko", "", "S-OFF", "1", "high", "", "CONFIRMED", ""),
("site:ko", "WebSite", "inLanguage", "ko", "ko", "", "S-OFF", "1", "high", "", "CONFIRMED", ""),
("site:ko", "WebSite", "publisher.@id", "https://www.shillahotels.com/#org", "ko", "", "", "1", "high", "", "CONFIRMED", "ref to org"),
# ---- Hotel (property; @id ref back to org) ----
("hotel:theshilla-seoul", "Hotel", "@id", "https://www.shillahotels.com/ko/theshilla/seoul#hotel", "ko", "https://www.shillahotels.com/ko/theshilla/seoul/index.do", "S-OFF|S-BROCH", "1", "high", "", "CONFIRMED", ""),
("hotel:theshilla-seoul", "Hotel", "name", "The Shilla Seoul", "ko", "", "S-OFF", "1", "high", "", "CONFIRMED", ""),
("hotel:theshilla-seoul", "Hotel", "telephone", "+82-2-2233-3131", "ko", "", "S-OFF|S-GBP", "1", "high", "", "CONFIRMED", ""),
("hotel:theshilla-seoul", "Hotel", "priceRange", "$$$$", "ko", "", "S-OFF", "2", "med", "", "CONFIRMED", ""),
("hotel:theshilla-seoul", "Hotel", "brand.name", "The Shilla", "ko", "", "S-OFF", "1", "high", "", "CONFIRMED", ""),
("hotel:theshilla-seoul", "Hotel", "parentOrganization.@id", "https://www.shillahotels.com/#org", "ko", "", "", "1", "high", "", "CONFIRMED", "entity graph link"),
("hotel:theshilla-seoul", "Hotel", "address.streetAddress", "동호로 249", "ko", "", "S-OFF|S-GBP", "1", "high", "", "CONFIRMED", ""),
("hotel:theshilla-seoul", "Hotel", "address.addressLocality", "서울", "ko", "", "S-OFF", "1", "high", "", "CONFIRMED", ""),
("hotel:theshilla-seoul", "Hotel", "address.addressCountry", "KR", "ko", "", "S-OFF", "1", "high", "", "CONFIRMED", ""),
("hotel:theshilla-seoul", "Hotel", "geo.latitude", "37.5564", "ko", "", "S-GBP", "1", "high", "", "CONFIRMED", ""),
("hotel:theshilla-seoul", "Hotel", "geo.longitude", "127.0058", "ko", "", "S-GBP", "1", "high", "", "CONFIRMED", ""),
# ---- Person (executive bio) ----
("person:ceo", "Person", "@id", "https://www.shillahotels.com/#ceo", "", "", "S-DART|S-NEWS", "1", "high", "", "CONFIRMED", ""),
("person:ceo", "Person", "name", "이부진", "", "", "S-DART", "1", "high", "", "CONFIRMED", ""),
("person:ceo", "Person", "jobTitle", "대표이사 사장", "", "", "S-DART", "1", "high", "", "CONFIRMED", ""),
("person:ceo", "Person", "worksFor.@id", "https://www.shillahotels.com/#org", "", "", "", "1", "high", "", "CONFIRMED", ""),
# ---- JobPosting (recruitment site) ----
("job:fo-manager", "JobPosting", "title", "프런트오피스 매니저", "ko", "https://recruit.shilla.net/job/1234", "S-RECRUIT", "1", "high", "", "CONFIRMED", ""),
("job:fo-manager", "JobPosting", "description", "더 신라 서울 프런트오피스 운영 총괄 및 VIP 응대.", "ko", "", "S-RECRUIT", "1", "high", "", "CONFIRMED", ""),
("job:fo-manager", "JobPosting", "datePosted", "2026-05-01", "ko", "", "S-RECRUIT", "1", "high", "", "CONFIRMED", ""),
("job:fo-manager", "JobPosting", "employmentType", "FULL_TIME", "ko", "", "S-RECRUIT", "1", "high", "", "CONFIRMED", ""),
("job:fo-manager", "JobPosting", "hiringOrganization.@id", "https://www.shillahotels.com/#org", "ko", "", "", "1", "high", "", "CONFIRMED", ""),
("job:fo-manager", "JobPosting", "jobLocation.addressLocality", "서울", "ko", "", "S-RECRUIT", "1", "high", "", "CONFIRMED", ""),
("job:fo-manager", "JobPosting", "jobLocation.addressCountry", "KR", "ko", "", "S-RECRUIT", "1", "high", "", "CONFIRMED", ""),
# ---- VideoObject (official YouTube) ----
("video:brand-film", "VideoObject", "name", "The Shilla — Authentic Indulgence", "", "https://www.youtube.com/watch?v=XXXX", "S-YT", "1", "high", "", "CONFIRMED", ""),
("video:brand-film", "VideoObject", "description", "더 신라 브랜드 필름.", "", "", "S-YT", "1", "high", "", "CONFIRMED", ""),
("video:brand-film", "VideoObject", "thumbnailUrl", "https://i.ytimg.com/vi/XXXX/maxresdefault.jpg", "", "", "S-YT", "1", "high", "", "CONFIRMED", ""),
("video:brand-film", "VideoObject", "uploadDate", "2025-11-20", "", "", "S-YT", "1", "high", "", "CONFIRMED", ""),
("video:brand-film", "VideoObject", "duration", "PT1M45S", "", "", "S-YT", "1", "high", "", "CONFIRMED", ""),
("video:brand-film", "VideoObject", "publisher.@id", "https://www.shillahotels.com/#org", "", "", "", "1", "high", "", "CONFIRMED", ""),
# ---- EXCLUSION GATE demonstrations (these must NOT appear in drafts) ----
("hotel:theshilla-seoul", "Hotel", "image", "https://example.com/seoul.jpg", "ko", "", "S-OFF", "3", "low", "", "PENDING", "이미지 최종본 미확정"),
("org:shilla", "Organization", "telephone", "+82-2-2233-3131", "", "", "S-OFF", "2", "med", "Y", "CONFIRMED", "대표번호 vs IR번호 출처 충돌"),
("person:ceo", "Person", "image", "", "", "", "", "", "", "", "CONFIRMED", "값 공란 -> 제외"),
]
HEADERS = ["entity_id", "entity_type", "property", "value", "lang", "url",
"source_ids", "authority", "confidence", "conflict", "status", "note"]
def main():
here = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
out = os.path.join(here, "fixtures", "sample_claims.csv")
os.makedirs(os.path.dirname(out), exist_ok=True)
with open(out, "w", encoding="utf-8-sig", newline="") as f:
w = csv.writer(f)
w.writerow(HEADERS)
w.writerows(ROWS)
print("Wrote", out, f"({len(ROWS)} claim rows)")
if __name__ == "__main__":
main()