Extract ourdigital-estimate-engine; presales-seo now calls it
New skill 96-ourdigital-estimate-engine: method-aware quoting engine (effort / coaching / procurement) with universal rate_card + per-service catalog. Real catalogs: seo (effort), education (coaching); stubs: digital_ads, digital_branding. Validated to reproduce real quotes — SEO basic ₩10.5M / treatment ₩25.0M, SHR chain ₩29.5M, L'Escape basic ₩10.5M, GA4/GTM coaching ₩1,570,000, procurement +15%. Refactor 95-ourdigital-presales-seo: remove rate_card.yaml, sow_templates.yaml, estimate.py (migrated to engine); add findings_to_scope.py; Stage 5 now maps findings→scope.json and calls the engine CLI. build_deck/kg_query unchanged; end-to-end validated on SHR (29.5M) + deck renders engine estimate.json. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Map an SEO findings.json → estimate-engine scope.json (service=seo, effort).
|
||||
|
||||
Stage 5 of ourdigital-presales-seo: keeps the SEO-specific mapping here; the engine
|
||||
(96-ourdigital-estimate-engine) owns the costing. Then call:
|
||||
python <engine>/scripts/estimate.py --rate-card <engine>/references/rate_card.yaml \
|
||||
--catalog-dir <engine>/catalog --scope scope.json --out-dir <engagement>
|
||||
|
||||
Usage:
|
||||
python findings_to_scope.py --findings data/findings.json --out data/scope.json [--tier auto] [--seq N]
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description="SEO findings.json -> engine scope.json")
|
||||
ap.add_argument("--findings", required=True)
|
||||
ap.add_argument("--out", required=True)
|
||||
ap.add_argument("--tier", default="auto", help="auto|smb|basic|treatment")
|
||||
ap.add_argument("--billing", type=float, default=None)
|
||||
ap.add_argument("--seq", type=int, default=1)
|
||||
args = ap.parse_args()
|
||||
|
||||
with open(args.findings, encoding="utf-8") as fh:
|
||||
f = json.load(fh)
|
||||
e = f.get("entity", {})
|
||||
p = f.get("prospect", {})
|
||||
severity = sorted({x.get("severity") for x in f.get("findings", []) if x.get("severity")})
|
||||
|
||||
scope = {
|
||||
"service": "seo",
|
||||
"method": "effort",
|
||||
"tier": args.tier,
|
||||
"billing_rate": args.billing,
|
||||
"signals": {
|
||||
"properties_total": e.get("properties_total", 0),
|
||||
"subbrands_total": e.get("subbrands_total", 0),
|
||||
"vertical": p.get("vertical", ""),
|
||||
"severity": severity,
|
||||
},
|
||||
"prospect": {
|
||||
"name": p.get("name", ""), "domain": p.get("domain", ""),
|
||||
"audit_date": p.get("audit_date", ""), "account_code": p.get("account_code", ""),
|
||||
},
|
||||
"seq": args.seq,
|
||||
}
|
||||
with open(args.out, "w", encoding="utf-8") as fh:
|
||||
json.dump(scope, fh, ensure_ascii=False, indent=2)
|
||||
print(f"scope -> {args.out} | service=seo tier={args.tier} "
|
||||
f"properties={scope['signals']['properties_total']} subbrands={scope['signals']['subbrands_total']} "
|
||||
f"vertical={scope['signals']['vertical']!r}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user