Files
our-claude-skills/custom-skills/16-seo-schema-validator/references/defect-taxonomy.md
Andrew Yim 4f48ba3c59 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>
2026-05-27 23:48:51 +09:00

75 lines
4.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.