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>
153 lines
11 KiB
Markdown
153 lines
11 KiB
Markdown
# 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 `<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) **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.
|