From a55e77d1b06417c7d9b5d1037418574816fd817d Mon Sep 17 00:00:00 2001 From: Andrew Yim Date: Wed, 27 May 2026 22:58:22 +0900 Subject: [PATCH] Add design spec for ourdigital-presales-seo skill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Standardizes the pre-sales SEO + Knowledge Graph diagnostic (origin: Sono Hotels & Resorts) into a reusable skill with findings→rate-card estimate, editable PPTX sales deck, and Notion SEO Audit DB archiving. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../95-ourdigital-presales-seo/DESIGN.md | 152 ++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 custom-skills/95-ourdigital-presales-seo/DESIGN.md diff --git a/custom-skills/95-ourdigital-presales-seo/DESIGN.md b/custom-skills/95-ourdigital-presales-seo/DESIGN.md new file mode 100644 index 0000000..2d46093 --- /dev/null +++ b/custom-skills/95-ourdigital-presales-seo/DESIGN.md @@ -0,0 +1,152 @@ +# Design — `ourdigital-presales-seo` skill + +- **Status**: Approved design (2026-05-27). Ready for implementation planning. +- **Author**: OurDigital (andrew.yim@ourdigital.org) +- **Origin**: Standardizes the Sono Hotels & Resorts pre-sales diagnostic (`~/Workspaces/shr-workspace/audits/2026-05-27-presales/`) into a reusable skill, adding estimate generation and a sales slide deck. + +## 1. Purpose & scope + +A single OurDigital Claude Code skill that runs a **pre-sales SEO + entity diagnostic** for any prospect domain and produces, as **confirmed step-by-step stages**: +1. Technical / on-page scan +2. Knowledge Graph / entity analysis (Korean market, Naver-aware) +3. Consolidated opportunity brief +4. **Rate-card-based cost estimate (견적)** +5. **Editable PPTX sales-briefing deck** + short client PDF + +**Public-data only** by default (no client GSC/GA4/GTM access assumed) — suited to prospecting where access isn't yet granted. + +### Non-goals (YAGNI) +- Not a full post-contract audit (that stays with `seo-comprehensive-audit`). +- No fixed 3-tier package output (user chose findings→rate-card line items only). +- No automated sending/emailing of deliverables. +- No fully-autonomous mode — execution is step-by-step gated. + +## 2. Invocation & inputs (Stage 0) + +Invoked as `/ourdigital-presales-seo` (optionally with a domain arg). Stage 0 gathers: + +| Input | Req | Default | +|---|:---:|---| +| `domain` | ✅ | — | +| `brand_name` + `aliases[]` (for KG; e.g. 앤/& /EN variants) | ✅ | derived from site ``/og | +| `sub_brands[]`, `properties[]` (entity targets) | — | auto-extracted from crawl URL patterns | +| `competitors[]` | — | from `references/competitor_sets.md` by vertical | +| `market` / `language` | — | `South Korea` / `ko` (Naver-aware) | +| `output_dir` | — | account workspace if exists, else `seo-workspace/prospecting/<date>-<prospect>/` (see §7) | +| `vertical` | — | inferred (hotel/resort default rubric) | + +Stage 0 also runs a **preflight tool check** (Firecrawl, DataForSEO, `GOOGLE_KG_API_KEY`, headless Chrome, `python-pptx`) and reports any missing capability with its fallback. + +## 3. Pipeline (step-by-step; each stage presents results and WAITS for user confirmation) + +| Stage | Actions | Primary tools | Output | +|---|---|---|---| +| 0 Scope | inputs, folders, preflight | — | `scope.md`, folders | +| 1 Discovery | robots.txt, sitemap status, `firecrawl_map` inventory, scale estimate, URL hygiene (`/test`, params, dup `/sb`↔`/brand_loc`) | Firecrawl, WebFetch | `data/urls.json`, scan §1 | +| 2 Technical/on-page | JSON-LD extraction, meta/title/H1 duplication, hreflang completeness, CWV (Lighthouse) | Firecrawl scrape, DataForSEO Lighthouse | `01_technical-onpage-scan.md`, `data/cwv_lighthouse.json` | +| 3 KG/entity | KG API (ko) over master/parent/legacy/membership/sub-brand/property/competitor sets; live SERP panel verification | `kg_query.py` (Google KG API), DataForSEO SERP | `02_knowledge-graph-entity.md`, `data/kg_*.json`, `data/serp_panels_findings.md` | +| 4 Brief | synthesize, severity ranking, competitive gap table | — | `03_presales-opportunity-brief.md` | +| 5 Estimate | findings→rate card mapping → ranged 견적 | `estimate.py` + `rate_card.yaml` | `05_estimate_ko.md`, `05_estimate.xlsx` — **review gate** | +| 6 Deliverables | short client PDF + branded PPTX deck | `render_pdf.sh` (Chrome), `build_deck.py` (python-pptx) | `client-brief.pdf`, `sales-deck.pptx` — **review before send** | +| 7 Archive | push consolidated report (03 brief + estimate summary) to the OurDigital SEO Audit DB — **standard final stage** | `notion_writer.py` | Notion row in SEO Audit Log | + +Each stage appends to the shared **`findings.json`** data contract (§5), the integration seam between analysis and the estimate/deck generators. + +**Archive target (standard):** every run archives to the OurDigital SEO Audit database +`2c8581e5-8a1e-8035-880b-e38cefc2f3ef` +(https://www.notion.so/dintelligence/2c8581e58a1e8035880be38cefc2f3ef). Row title `<프로스펙트> SEO 사전진단 (Pre-sales) — <YYYY-MM-DD>`; set `Target URL`, `Audit Date`, `Account Code`. This is the system of record for prospect + client audits alike. + +## 4. Component breakdown (units & interfaces) + +- **`SKILL.md`** — orchestration: stage definitions, per-stage gating, Korean-first output rules, tool fallbacks, sandbox-disable notes for KG/Chrome/Notion network calls. +- **`scripts/kg_query.py`** — IN: entity list (group,label,query) + lang + key (env). OUT: `kg_raw.json`, `kg_flat.json`, console summary (score/type/lodging-flag/own-entity). Generalized from the SHR script (entities parameterized, not hardcoded). +- **`scripts/estimate.py`** — IN: `findings.json` + `rate_card.yaml` + rules. OUT: `05_estimate_ko.md` + `05_estimate.xlsx` (항목·상세·수량·단가 range·금액; one-time + monthly subtotals; `OD-YYYY-NNN`; disclaimer) **and `data/estimate.json`** (selected line items + totals, consumed by `build_deck.py`). +- **`scripts/build_deck.py`** — IN: `findings.json` + `estimate.json` + `deck_theme`. OUT: `sales-deck.pptx` (9 slides, §6). python-pptx. +- **`scripts/render_pdf.sh`** — IN: client-brief HTML. OUT: PDF via headless Chrome (Korean system fonts). +- **`references/rate_card.yaml`** — single source of OurDigital service rates (see §5.1). +- **`references/findings_to_service.md`** — finding-class → severity → service-line rubric (§5.2). +- **`references/competitor_sets.md`** — default KR competitor benchmarks by vertical (hotel/resort seeded: 롯데/신라/조선/한화/켄싱턴). +- **`templates/`** — `01/02/03` md, `client_brief.html`, `estimate_OD.md`, `deck_theme.py`. + +## 5. Estimate logic + +### 5.1 `rate_card.yaml` (from `ourdigital-backoffice`) +```yaml +quote_prefix: OD # OD-YYYY-NNN +currency: KRW +services: + technical_audit: {label: "Technical Audit / 기술 SEO 진단", unit: one_time, min: 3000000, max: 5000000} + technical_remediation: {label: "기술 개선 실행", unit: project, min: 3000000, max: 8000000} + onpage_entity: {label: "On-Page / Entity Optimization", unit: monthly, min: 1500000, max: 3000000} + schema_build: {label: "구조화 데이터 구축(1회)", unit: one_time, min: 2000000, max: 4000000} + local_seo: {label: "Local SEO", unit: monthly, min: 1000000, max: 2000000} + gtm_setup: {label: "GTM Setup", unit: project, min: 2000000, max: 4000000} + ga4_impl: {label: "GA4 Implementation", unit: project, min: 1500000, max: 3000000} + dashboard: {label: "Dashboard Development", unit: project, min: 3000000, max: 6000000} +``` +*(Values mirror the backoffice rate card; treated as estimate ranges. Skill reads this file — no hardcoded prices.)* + +### 5.2 `findings_to_service.md` rubric (finding class → service line) +| Finding class (from `findings.json`) | Service line(s) | Scope driver | +|---|---|---| +| broken sitemap / low crawl-coverage / SPA rendering / CWV poor | `technical_audit` + `technical_remediation` | site size, # templates | +| missing/weak schema, entity gaps, sub-brand/property entities, Wikipedia/sameAs | `schema_build` (one-time) + `onpage_entity` (retainer) | # sub-brands + # properties | +| property local packs / GBP-URL mismatch | `local_seo` | # properties | +| no GSC/GA4, measurement gaps | `ga4_impl` and/or `dashboard`, `gtm_setup` | — | +| duplicate meta / title i18n / hreflang / content confusion | `onpage_entity` | # templates | + +`estimate.py` selects line items per detected findings, scales `qty` by drivers (e.g., property count → local-SEO months/scope), sums one-time vs monthly, and renders the 견적. Always includes the disclaimer: *ranges; finalized after a precise diagnostic with GSC/GA4 access.* + +## 6. PPTX deck spec (`build_deck.py`, 9 slides) +1. Title — prospect + "검색 가시성 사전 진단" + date + OurDigital +2. 한눈에 보기 — asset strength vs search-visibility gap +3. Finding 1 — 크롤/색인 (sitemap, discoverable-URL count) +4. Finding 2 — Core Web Vitals +5. Finding 3 — 엔티티/브랜드 인식 (entity type, name split, legacy contamination) +6. Finding 4 — 서브브랜드/프로퍼티 엔티티 + 경쟁 벤치마크 table +7. 개선 로드맵 — Phase 0 (긴급 기술) / 1 (엔티티) / 2 (콘텐츠·로컬) +8. 예상 견적 — rate-card line items + ranges + disclaimer +9. 다음 단계 / CTA — 30분 미팅 · 정밀 진단 · 파일럿 + +Branding from `ourdigital-brand-guide` (colors/fonts/logo); fallback theme = navy `#11243d` / accent `#1b6fb3` (the SHR brief styling). Slides are content-populated from `findings.json` + `estimate.json`, leaving text editable. + +## 7. Output routing +Default: if `~/Workspaces/<slug>-workspace/` exists → `…/audits/<YYYY-MM-DD>-presales/`; else `~/Workspaces/seo-workspace/prospecting/<YYYY-MM-DD>-<prospect>/` (per global routing rule). Overridable at Stage 0. `data/` holds raw artifacts; audit md + deck/PDF at top level. + +## 8. Dependencies & documented fallbacks +| Capability | Tool | Fallback / gotcha | +|---|---|---| +| URL inventory | Firecrawl `map` | OurSEO `crawl_website` **caps ~60 pages** regardless of `max_pages`; broken sitemap limits discovery — report the discoverable count as a finding | +| Page signals | Firecrawl `scrape` (json) | re-scrape if cache returns empty | +| SERP panels / CWV | DataForSEO `serp_organic_live_advanced`, `on_page_lighthouse` | — | +| Entity DB | Google KG Search API | needs `GOOGLE_KG_API_KEY` / `GOOGLE_API_KEY`; sandbox-disable for network | +| Client PDF | headless Chrome `--print-to-pdf` | needs Korean system font (AppleSDGothicNeo present on macOS); sandbox-disable | +| Deck | `python-pptx` | install if missing | +| Notion archive | `notion_writer.py` → DB `2c8581e5-8a1e-8035-880b-e38cefc2f3ef` | use `--properties` with `Target URL`/`Audit Date`/`Account Code` only; `Audit ID` is a read-only **formula** (do not set); `Site`/`Found Date` from old docs are wrong property names | + +## 9. Data contract — `findings.json` (analysis ↔ generators seam) +```jsonc +{ + "prospect": {"name": "", "domain": "", "aliases": [], "vertical": ""}, + "discovery": {"sitemap_status": 500, "robots_sitemap_declared": false, + "discoverable_urls": 96, "estimated_pages": "thousands", + "url_hygiene": ["test_page_exposed", "dup_path_scheme", "param_urls"]}, + "technical": {"cwv": {"lcp_ms":0,"cls":0,"ttfb_ms":0,"perf":0}, + "schema": {"org": "bare|complete|none", "hotel_on_property": true}, + "meta_dupe": true, "title_i18n_mismatch": true, "hreflang": "incomplete"}, + "entity": {"panel": "company|hotel|none", "name_split": true, "legacy_contamination": true, + "subbrands_with_entity": 0, "properties_with_entity": 0, + "competitor_benchmark": [{"name":"","score":0,"type":"","wikipedia":false}]}, + "findings": [{"id":"", "class":"", "severity":"critical|high|medium", "evidence":"", "recommended_services":[]}] +} +``` +Stages 1–4 populate it; Stages 5–6 consume it. This is the key isolation boundary: generators never re-crawl. + +## 10. Validation +- Dry-run on the SHR data (already collected) → estimate + deck must reproduce sensible output. +- `kg_query.py` unit check: known entity (롯데호텔) returns LodgingBusiness + high score. +- Deck opens in PowerPoint/Keynote; Korean renders; placeholders editable. +- Estimate totals = sum of selected line items; disclaimer present. + +## 11. Future (out of scope now) +3-tier package view; auto Naver SERP module; multi-language decks; CRM hand-off.