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) <noreply@anthropic.com>
11 KiB
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:
- Technical / on-page scan
- Knowledge Graph / entity analysis (Korean market, Naver-aware)
- Consolidated opportunity brief
- Rate-card-based cost estimate (견적)
- 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 <title>/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) anddata/estimate.json(selected line items + totals, consumed bybuild_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/03md,client_brief.html,estimate_OD.md,deck_theme.py.
5. Estimate logic
5.1 rate_card.yaml (from ourdigital-backoffice)
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)
- Title — prospect + "검색 가시성 사전 진단" + date + OurDigital
- 한눈에 보기 — asset strength vs search-visibility gap
- Finding 1 — 크롤/색인 (sitemap, discoverable-URL count)
- Finding 2 — Core Web Vitals
- Finding 3 — 엔티티/브랜드 인식 (entity type, name split, legacy contamination)
- Finding 4 — 서브브랜드/프로퍼티 엔티티 + 경쟁 벤치마크 table
- 개선 로드맵 — Phase 0 (긴급 기술) / 1 (엔티티) / 2 (콘텐츠·로컬)
- 예상 견적 — rate-card line items + ranges + disclaimer
- 다음 단계 / 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)
{
"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.pyunit 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.