feat(seo-schema-validator): back the upgraded SKILL.md with a working 5-layer pipeline

The "Upgrade Schema Validator" commit added SKILL.md referencing files that did
not exist. Implement them so the skill actually runs:

- scripts/validate_schema.py — 5-layer offline validator (L0 coverage, L1 syntax,
  L2 vocabulary/value-format, L3 rich-result, L4 consistency) with xlsx/csv/jsonl/
  json/dir/live-URL adapters. Gate = zero P0; exits 1 on failure.
- scripts/schema_rules.json — curated hotel-focused, offline rule set (edit-only
  extension point).
- scripts/make_sample.py + fixtures/sample_schema.csv — deliberately flawed fixture
  seeding ≥1 defect per layer; used to self-test.
- references/ — validation-methodology, defect-taxonomy (25 codes), hotel-type-map.
- templates/ — client-qa-report, decision-log.
- code/CLAUDE.md — redirect legacy single-URL tool to the new pipeline.

Noise control: MISSING_RECOMMENDED aggregated one-line-per-node; unexpected-property
checks opt-in via --strict. Generalized client-specific shilla-type-map → hotel-type-map.
Self-tested: default P0=5/P1=4/P2=14 FAIL, --strict --no-recommended P2=0, adapters verified.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-27 23:48:51 +09:00
parent ba88247496
commit 4f48ba3c59
12 changed files with 1636 additions and 130 deletions

View File

@@ -0,0 +1,200 @@
{
"_meta": {
"version": "1.0",
"scope": "Curated, hotel-focused subset of schema.org + Google rich-result requirements.",
"intent": "Self-contained offline rules (the runtime cannot reach schema.org or Google). Unknown types/properties degrade to warnings, never hard errors, to avoid false positives. To support a new type or tighten a rule, edit THIS file only.",
"sources": "schema.org/Hotel, schema.org/LocalBusiness, Google Search Central 'Structured data' rich-result docs (as of 2025)."
},
"valid_contexts": [
"https://schema.org",
"http://schema.org",
"https://schema.org/",
"http://schema.org/",
"https://www.schema.org",
"http://www.schema.org"
],
"global_properties": [
"@context", "@type", "@id", "@graph", "@reverse",
"name", "alternateName", "legalName", "description", "disambiguatingDescription",
"url", "image", "logo", "sameAs", "identifier", "mainEntityOfPage",
"additionalType", "subjectOf", "potentialAction", "inLanguage"
],
"known_types": {
"Organization": {
"required": ["name", "url"],
"recommended": ["logo", "sameAs", "contactPoint", "address"],
"allowed": ["legalName", "foundingDate", "parentOrganization", "subOrganization", "brand", "telephone", "email", "founder", "numberOfEmployees", "memberOf", "hasMerchantReturnPolicy", "member"]
},
"Corporation": {
"required": ["name", "url"],
"recommended": ["logo", "sameAs", "address"],
"allowed": ["legalName", "foundingDate", "parentOrganization", "tickerSymbol", "telephone", "email", "brand"]
},
"WebSite": {
"required": ["name", "url"],
"recommended": ["publisher", "potentialAction", "inLanguage"],
"allowed": ["alternateName", "about", "copyrightHolder", "copyrightYear"]
},
"WebPage": {
"required": ["name"],
"recommended": ["url", "isPartOf", "primaryImageOfPage", "breadcrumb", "datePublished", "dateModified"],
"allowed": ["about", "mentions", "speakable", "lastReviewed", "reviewedBy", "significantLink"]
},
"LocalBusiness": {
"required": ["name", "address"],
"recommended": ["telephone", "openingHoursSpecification", "geo", "image", "url", "priceRange", "aggregateRating"],
"allowed": ["email", "openingHours", "paymentAccepted", "currenciesAccepted", "areaServed", "hasMap", "department", "menu", "review", "containedInPlace", "containsPlace", "amenityFeature"]
},
"Hotel": {
"required": ["name", "address"],
"recommended": ["telephone", "image", "priceRange", "geo", "url", "starRating", "aggregateRating", "checkinTime", "checkoutTime"],
"allowed": ["email", "amenityFeature", "petsAllowed", "numberOfRooms", "availableLanguage", "containedInPlace", "containsPlace", "makesOffer", "brand", "currenciesAccepted", "smokingAllowed", "openingHoursSpecification", "audience", "review"]
},
"LodgingBusiness": {
"required": ["name", "address"],
"recommended": ["telephone", "image", "priceRange", "geo", "url", "starRating", "aggregateRating", "checkinTime", "checkoutTime"],
"allowed": ["email", "amenityFeature", "petsAllowed", "numberOfRooms", "availableLanguage", "containedInPlace", "containsPlace", "makesOffer", "currenciesAccepted", "smokingAllowed"]
},
"Resort": {
"required": ["name", "address"],
"recommended": ["telephone", "image", "priceRange", "geo", "url", "starRating", "aggregateRating"],
"allowed": ["email", "amenityFeature", "numberOfRooms", "containedInPlace", "containsPlace", "checkinTime", "checkoutTime"]
},
"Restaurant": {
"required": ["name", "address"],
"recommended": ["servesCuisine", "priceRange", "telephone", "menu", "openingHoursSpecification", "image", "url", "geo", "acceptsReservations"],
"allowed": ["email", "hasMenu", "starRating", "aggregateRating", "review", "containedInPlace", "smokingAllowed"]
},
"FoodEstablishment": {
"required": ["name", "address"],
"recommended": ["servesCuisine", "priceRange", "telephone", "menu", "openingHoursSpecification"],
"allowed": ["email", "hasMenu", "acceptsReservations", "containedInPlace"]
},
"BarOrPub": {
"required": ["name", "address"],
"recommended": ["telephone", "openingHoursSpecification", "priceRange", "servesCuisine"],
"allowed": ["menu", "hasMenu", "image", "url"]
},
"FAQPage": {
"required": ["mainEntity"],
"recommended": [],
"allowed": ["about", "headline", "datePublished", "dateModified"]
},
"Question": {
"required": ["name", "acceptedAnswer"],
"recommended": [],
"allowed": ["text", "answerCount", "suggestedAnswer", "upvoteCount", "author"]
},
"Answer": {
"required": ["text"],
"recommended": [],
"allowed": ["url", "upvoteCount", "author", "dateCreated"]
},
"BreadcrumbList": {
"required": ["itemListElement"],
"recommended": [],
"allowed": ["numberOfItems", "itemListOrder"]
},
"ItemList": {
"required": ["itemListElement"],
"recommended": [],
"allowed": ["numberOfItems", "itemListOrder"]
},
"ListItem": {
"required": ["position"],
"recommended": ["item", "name"],
"allowed": ["url", "image", "nextItem", "previousItem"]
},
"Product": {
"required": ["name"],
"recommended": ["image", "offers", "brand", "aggregateRating", "review", "description", "sku"],
"allowed": ["gtin", "gtin13", "gtin8", "gtin12", "mpn", "color", "material", "category", "audience", "isVariantOf", "additionalProperty", "hasMerchantReturnPolicy"]
},
"Offer": {
"required": ["price", "priceCurrency"],
"recommended": ["availability", "url", "validFrom", "priceValidUntil"],
"allowed": ["itemCondition", "seller", "eligibleRegion", "priceSpecification", "shippingDetails", "availabilityStarts"]
},
"AggregateOffer": {
"required": ["lowPrice", "priceCurrency"],
"recommended": ["highPrice", "offerCount"],
"allowed": ["offers", "availability"]
},
"Article": {
"required": ["headline"],
"recommended": ["author", "datePublished", "image", "dateModified", "publisher"],
"allowed": ["articleBody", "articleSection", "wordCount", "keywords", "speakable"]
},
"NewsArticle": {
"required": ["headline"],
"recommended": ["author", "datePublished", "image", "dateModified", "publisher"],
"allowed": ["articleBody", "dateline", "printSection"]
},
"BlogPosting": {
"required": ["headline"],
"recommended": ["author", "datePublished", "image", "dateModified", "publisher"],
"allowed": ["articleBody", "keywords", "wordCount"]
},
"Event": {
"required": ["name", "startDate", "location"],
"recommended": ["endDate", "offers", "performer", "image", "eventStatus", "eventAttendanceMode", "organizer"],
"allowed": ["doorTime", "previousStartDate", "typicalAgeRange", "maximumAttendeeCapacity"]
},
"Review": {
"required": ["reviewRating", "author"],
"recommended": ["datePublished", "reviewBody", "itemReviewed"],
"allowed": ["publisher", "name"]
},
"AggregateRating": {
"required": ["ratingValue"],
"recommended": ["reviewCount", "ratingCount", "bestRating"],
"allowed": ["worstRating", "itemReviewed"]
},
"MemberProgram": {
"required": ["name"],
"recommended": ["hasTiers", "hostingOrganization", "url"],
"allowed": ["description", "membershipPointsEarned"]
}
},
"container_types": [
"PostalAddress", "GeoCoordinates", "GeoShape", "ImageObject", "VideoObject",
"ContactPoint", "OpeningHoursSpecification", "Rating", "QuantitativeValue",
"MonetaryAmount", "PriceSpecification", "Brand", "EntryPoint", "Place",
"OfferCatalog", "ReserveAction", "OrderAction", "SearchAction", "ViewAction",
"MeetingRoom", "Room", "HotelRoom", "Suite", "LocationFeatureSpecification",
"MemberProgramTier", "MobileApplication", "WebApplication", "SoftwareApplication",
"Menu", "MenuItem", "MenuSection", "Country", "AdministrativeArea", "Duration",
"PropertyValue", "Person", "Audience", "Language"
],
"value_formats": {
"url_props": ["url", "logo", "sameAs", "image", "contentUrl", "thumbnailUrl", "target", "urlTemplate", "installUrl", "menu", "hasMap", "downloadUrl", "embedUrl"],
"date_props": ["datePublished", "dateModified", "dateCreated", "startDate", "endDate", "validFrom", "validThrough", "priceValidUntil", "foundingDate", "uploadDate", "availabilityStarts", "availabilityEnds", "lastReviewed", "previousStartDate"],
"lang_props": ["inLanguage", "availableLanguage"],
"currency_props": ["priceCurrency", "currenciesAccepted"],
"number_props": ["price", "lowPrice", "highPrice", "ratingValue", "reviewCount", "ratingCount", "bestRating", "worstRating", "position", "numberOfRooms", "maxValue", "minValue", "offerCount"]
},
"valid_currencies": ["KRW", "USD", "EUR", "JPY", "CNY", "GBP", "HKD", "SGD", "THB", "AUD", "CAD", "CHF", "TWD", "MYR", "PHP", "VND", "IDR", "INR"],
"valid_language_codes": ["ko", "en", "ja", "zh", "zh-CN", "zh-TW", "zh-Hans", "zh-Hant", "ko-KR", "en-US", "en-GB", "ja-JP", "fr", "de", "es", "ru", "th", "vi", "id", "ms"],
"placeholder_tokens": [
"lorem ipsum", "lorem", "ipsum", "dolor sit", "todo", "tbd", "fixme",
"xxx", "yyy", "zzz", "placeholder", "insert here", "insert text",
"example.com", "your-domain", "yourdomain", "changeme", "sample text",
"{{", "}}", "<insert", "[insert", "n/a", "샘플", "예시", "여기에",
"변경필요", "수정필요", "입력필요", "내용입력", "테스트", "임시"
],
"geo": {
"lat_min": -90.0, "lat_max": 90.0,
"lon_min": -180.0, "lon_max": 180.0,
"kr_lat_range": [33.0, 39.0],
"kr_lon_range": [124.0, 132.0]
}
}