Files
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

12 KiB
Raw Permalink Blame History

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.xlsxreview gate
6 Deliverables short client PDF + branded PPTX deck render_pdf.sh (Chrome), build_deck.py (python-pptx) client-brief.pdf, sales-deck.pptxreview 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)

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)

{
  "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.