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:
@@ -0,0 +1,74 @@
|
||||
# Defect Taxonomy
|
||||
|
||||
Every code `validate_schema.py` can emit, its default severity, and what to do.
|
||||
The validator writes these to `defect_log.csv` (columns: `entry_id, url, node_type,
|
||||
layer, code, severity, message, status, owner, note`) and `results.json`.
|
||||
|
||||
## Severity model
|
||||
|
||||
| Severity | Definition | Owner action |
|
||||
|---|---|---|
|
||||
| **P0** | Blocker — breaks parsing, blocks the rich result, or ships wrong/placeholder data. **Fails the gate.** | Must fix before the entry reaches client review. |
|
||||
| **P1** | Real defect, doesn't block the rich result. | Fix before launch; track in the triage log. |
|
||||
| **P2** | Optimization — recommended properties, formatting, orphan URLs. | Backlog; fix opportunistically. |
|
||||
|
||||
`--strict` promotes vocabulary/format warnings (and unknown types) from P2 to P1 and
|
||||
turns on `UNEXPECTED_PROPERTY`. `--no-recommended` drops `MISSING_RECOMMENDED` entirely.
|
||||
**Neither changes the gate — the gate is always "zero P0."**
|
||||
|
||||
## Code reference
|
||||
|
||||
### Layer 0 — Coverage
|
||||
| Code | Sev | Trigger | Fix |
|
||||
|---|---|---|---|
|
||||
| `COVERAGE_MISSING` | P1 | Inventory URL has no authored entry. | Author the entry, or remove the URL from the inventory. |
|
||||
| `COVERAGE_ORPHAN` | P2 | Entry URL isn't in the inventory. | Fix the URL typo, or update the canonical list. |
|
||||
|
||||
### Layer 1 — Syntax
|
||||
| Code | Sev | Trigger | Fix |
|
||||
|---|---|---|---|
|
||||
| `INVALID_JSON` | P0 | JSON does not parse. | Fix the JSON (trailing comma, unquoted key, smart quotes). |
|
||||
| `NO_SCHEMA_IN_HTML` | P0 | Live page has no `ld+json` block (Mode B). | Confirm the tag deployed and renders. |
|
||||
| `MISSING_CONTEXT` | P1 | No top-level `@context`. | Add `"@context": "https://schema.org"`. |
|
||||
| `WRONG_CONTEXT` | P1 | `@context` isn't schema.org. | Correct the context URL. |
|
||||
| `NO_TYPE` | P1 | No `@type` anywhere in the entry. | Add the intended `@type`. |
|
||||
| `ENCODING_CORRUPTION` | P1 | Replacement char `<60>` present. | Re-export as UTF-8; check the source pipeline. |
|
||||
| `FETCH_ERROR` | P1 | Live URL could not be fetched (Mode B). | Check the URL/network; retry. |
|
||||
|
||||
### Layer 2 — Vocabulary & value formats
|
||||
| Code | Sev (strict) | Trigger | Fix |
|
||||
|---|---|---|---|
|
||||
| `UNKNOWN_TYPE` | P2 (P1) | `@type` not in the curated rule set. | If intended, add it to `schema_rules.json`; else correct the type. |
|
||||
| `UNEXPECTED_PROPERTY` | — (P1) | Property unknown for a known type (**strict only**). | Remove the typo'd property, or add it to the type's `allowed`. |
|
||||
| `BAD_URL` | P2 (P1) | A URL property isn't an `http(s)` URL. | Use an absolute URL. |
|
||||
| `BAD_DATE` | P2 (P1) | A date property isn't ISO-8601. | Use `YYYY-MM-DD` (or full datetime). |
|
||||
| `BAD_LANG` | P2 (P1) | `inLanguage`/`availableLanguage` isn't a BCP-47 code. | Use `ko`, `en`, `ja`, `zh`, … |
|
||||
| `BAD_CURRENCY` | P2 (P1) | `priceCurrency` isn't a 3-letter ISO-4217 code. | Use `KRW`/`USD`, not `₩`/`$`. |
|
||||
| `BAD_NUMBER` | P2 (P1) | A numeric property isn't numeric. | Remove units/commas; keep digits. |
|
||||
|
||||
### Layer 3 — Rich-result requirements
|
||||
| Code | Sev | Trigger | Fix |
|
||||
|---|---|---|---|
|
||||
| `MISSING_REQUIRED` | P0 | A Google-required property is absent. | Add the property — the rich result is blocked without it. |
|
||||
| `MISSING_RECOMMENDED` | P2 | Recommended properties absent (one line per node, lists all). | Add what applies to improve eligibility/appearance. |
|
||||
|
||||
### Layer 4 — Consistency
|
||||
| Code | Sev | Trigger | Fix |
|
||||
|---|---|---|---|
|
||||
| `PLACEHOLDER_TEXT` | P0 | Boilerplate token in a string (`예시`, `수정필요`, `lorem`, `{{`, …). | Replace with real content. |
|
||||
| `NAP_PHONE_MISMATCH` | P0 | Same business, different `telephone` across entries. | Reconcile to the canonical phone. |
|
||||
| `NAP_ADDRESS_MISMATCH` | P0 | Same business, different `streetAddress`. | Reconcile to the canonical address. |
|
||||
| `DUPLICATE_ID` | P1 | One `@id` defined ≥2× with differing content. | Make definitions identical, or split the `@id`. |
|
||||
| `DANGLING_ID` | P1 | `{"@id": …}` reference to a node never defined. | Define the node, or fix the reference. |
|
||||
| `GEO_SWAPPED` | P1 | latitude/longitude transposed (swapping fixes it). | Swap the values. |
|
||||
| `GEO_OUT_OF_RANGE` | P1 | Coordinates impossible (lat∉[-90,90] or lon∉[-180,180]). | Correct the coordinates. |
|
||||
| `DUPLICATE_DESCRIPTION` | P1 | Same description reused across ≥3 entries. | Write distinct descriptions per page. |
|
||||
|
||||
## Triage workflow
|
||||
|
||||
1. Sort `defect_log.csv` by severity (already sorted P0→P1→P2 on write).
|
||||
2. **P0:** assign an owner, fix, re-run. No P0 may survive into client review.
|
||||
3. **P1:** set `owner` + `status`, decide fix-now vs accept; log accepted ones in
|
||||
`templates/decision-log.md`.
|
||||
4. **P2:** schedule into the optimization backlog.
|
||||
5. Re-run after fixes and confirm no regressions before advancing the stage gate.
|
||||
Reference in New Issue
Block a user