Files
our-claude-skills/custom-skills/95-ourdigital-presales-seo/DESIGN.md
Andrew Yim 5f66f57a8e Rebuild estimate engine to effort-based SOW model
Replace flat per-service ranges with OurDigital's real quoting model
(role_rate × billing_rate 0.70 × standard hours), sourced from the
06_Working Template quotes:
- rate_card.yaml: role rate card, billing/basis/terms, tools, scaling bands
- sow_templates.yaml: basic + treatment task-hour templates
- estimate.py: assemble SOW from findings, scale Technical/On-page hours by
  properties_total, 제안가 = 합계 floored to 500k
- build_deck.py: estimate slide shows module 소계 + 제안가 (point)
- findings_to_service.md / SKILL.md / DESIGN.md: synced to new model

Validated: reproduces real Basic ₩10.5M and Treatment ₩25.0M exactly;
SHR (25 properties) scales to ₩71.5M, L'Escape (1) = ₩25.0M.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 00:51:52 +09:00

159 lines
12 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.
# 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
> **Superseded (2026-05-28):** the flat per-service ranges below were replaced by an
> **effort-based** engine (role-hours × billing_rate 0.70) loaded from OurDigital's real
> quotation templates. See `references/rate_card.yaml`, `references/sow_templates.yaml`,
> and `references/findings_to_service.md`. Validated to reproduce the real Basic (₩10.5M)
> and Treatment (₩25.0M) quotes exactly. The notes below are kept as historical context.
### 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 14 populate it; Stages 56 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.