system-prompts-and-models-o.../dealix/scripts/launch_readiness_check.py
Dealix Builder 84f1ad9620 feat(launch+revenue): Private Beta Launch Ops + Revenue Launch — 14 modules + 29 endpoints + 56 tests + scripts/landing/docs
Launch Ops (5 modules) — برج إطلاق الـ Private Beta
- private_beta: 499 SAR × 7-day offer + safety notes + 6-question Arabic FAQ
- demo_flow: 12-minute minute-by-minute Arabic demo + 5 discovery Qs + 6 objection responses + close script
- outreach_messages: 4 segments × 5 prospects = 20 + per-segment Arabic messages + 3-step follow-ups + 6 reply handlers
- go_no_go: 10-gate readiness + 3 critical gates (no_secrets/live_sends_disabled/staging_health) + verdict + next-actions
- launch_scorecard: 11 event types + daily/weekly aggregation + targets (20 outreach/5 replies/3 demos/1 pilot daily)

Revenue Launch (7 modules) — تحويل Dealix إلى دخل
- offer_builder: 4 offers (Private Beta, 499 Pilot, Growth OS Pilot 1.5-3K, Free Case Study) + segment-aware recommend
- pipeline_tracker: 8-stage deterministic pipeline + add/update/summarize + revenue tracking + win rate
- outreach_sequence: re-export single source of truth from launch_ops with revenue-tier wrappers
- demo_closer: re-export from launch_ops
- pilot_delivery: 12-field intake form + 5-phase 24h delivery plan + per-service templates (First 10 / List Intel / Free Diagnostic)
- proof_pack_template: 5-line Arabic client summary + ROI estimate (pipeline_x + closed_won_x) + next-step recommendation (upsell/iterate/extend)
- payment_manual_flow: Moyasar invoice step-by-step (halalas-correct) + Arabic payment-link message + confirmation checklist; NEVER charges via API

Service Tower extensions (2 modules)
- contract_templates: re-export targeting_os contracts + new SLA outline (legal_review_required, PDPL-aware)
- vertical_service_map: 6 verticals (B2B SaaS / agencies / training-consulting / real estate / healthcare-local / retail-ecommerce) with primary+supporting services + buyer roles + common pains + winning offer

Routers (2 new) — 29 endpoints
- /api/v1/launch/* — 11 endpoints (private-beta/offer, demo/flow, outreach/{first-20, message, followup}, go-no-go, readiness, scorecard/{event, daily, weekly, demo})
- /api/v1/revenue-launch/* — 18 endpoints (offers + offers/recommend, outreach/{first-20, followup}, demo-flow, pipeline/{schema, summarize}, pilot-delivery/{intake-form, 24h-plan, first-10, list-intelligence, free-diagnostic}, payment/{invoice-instructions, link-message, confirmation-checklist}, proof-pack/{template, client-summary, next-step})

Tests (2 new files, 56 tests)
- test_launch_ops.py: 25 tests (Private Beta offer essentials + Arabic FAQ; demo flow 12-min structure; first-20 segments × 5; outreach Arabic + drafts only; followup steps differ; reply handlers include unsubscribe; go/no-go critical gates block; scorecard aggregation + verdict)
- test_revenue_launch.py: 31 tests (offers correct prices, no_live_charge=True; segment-aware recommends; pipeline 8 stages + add/update/summarize + win rate; outreach v2 Arabic; intake fields; 24h plan 5 phases; invoice halalas correct; payment confirmation blocks premature delivery; proof pack 5 lines + 3 next-step paths)

Scripts (1 new)
- scripts/launch_readiness_check.py: runs 10 gates locally + optional --staging-url; pretty/JSON output; critical gates determine GO/NO-GO/FIX-THEN-GO verdict

Landing pages (2 new, RTL Arabic)
- list-intelligence.html — List Intelligence service detail (499–1,500 SAR)
- growth-os.html — Growth OS Monthly subscription page (2,999 SAR/month)

Docs (1 new + 1 updated)
- REVENUE_TODAY_PLAYBOOK.md (Arabic) — 12-section playbook: offers, segments, messages, demo, pipeline, 24h delivery, Moyasar manual flow, proof pack, daily targets, go/no-go, what-not-to-do, next-step
- DEALIX_100_PERCENT_LAUNCH_PLAN.md — added §40 Launch Ops + §41 Revenue Launch + §42 Service Tower extensions + §43 Scripts

Test results
- 56/56 new tests pass
- Full suite: 824 passed, 2 skipped (missing API keys, unrelated)
- 0 existing tests broken

Safety integration
- All offers: live_send_allowed=False, no_live_charge=True, approval_required=True
- 10-gate go/no-go BLOCKS launch if no_secrets/live_sends_disabled/staging_health fail
- Moyasar: invoice/payment-link manual only; NEVER calls live charge API
- Payment confirmation checklist blocks delivery before invoice paid status
- All outreach messages: drafts only, follow-ups capped at 3, opt-out honored immediately
- 6 verticals mapped to safe service stacks; LinkedIn always Lead Forms (never scraping)

Integration with previous layers
- Launch Ops uses platform_services tool_gateway, intelligence_layer command_feed, security_curator redaction
- Revenue Launch uses targeting_os contractability + service_tower offers + intelligence_layer simulator
- Pipeline tracker integrates with action_ledger for stage transitions
- Proof Pack template references intelligence_layer proof metrics + service_excellence ROI

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 17:28:08 +03:00

233 lines
8.5 KiB
Python

#!/usr/bin/env python3
"""Dealix Launch Readiness — 10-gate Go/No-Go check.
Runs locally + against an optional staging URL. Reports which gates pass/fail
and what the next concrete actions are.
Usage:
python scripts/launch_readiness_check.py
python scripts/launch_readiness_check.py --staging-url https://staging.example
"""
from __future__ import annotations
import argparse
import json
import os
import re
import subprocess
import sys
import urllib.error
import urllib.request
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent
SECRET_PATTERNS = (
r"ghp_[A-Za-z0-9]{20,}",
r"github_pat_[A-Za-z0-9_]{20,}",
r"sk-[A-Za-z0-9]{30,}",
r"sk-ant-[A-Za-z0-9_\-]{20,}",
r"AKIA[A-Z0-9]{16}",
r"AIza[A-Za-z0-9_\-]{30,}",
r"EAA[A-Za-z0-9]{30,}",
r"-----BEGIN (?:RSA |EC |OPENSSH |)PRIVATE KEY-----",
)
EXCLUDE_DIRS = (".git", ".venv", "node_modules", "__pycache__", ".pytest_cache")
def gate_tests_passed() -> tuple[bool, str]:
"""Run pytest with --noconftest on the new layer tests as a quick proxy."""
try:
result = subprocess.run(
[sys.executable, "-m", "pytest",
"tests/unit/test_launch_ops.py",
"tests/unit/test_revenue_launch.py",
"tests/unit/test_security_curator.py",
"--noconftest", "--no-cov", "-q", "-p", "no:cacheprovider"],
cwd=REPO_ROOT, capture_output=True, text=True, timeout=60,
)
ok = result.returncode == 0
last_line = (result.stdout or "").strip().splitlines()[-1:]
msg = last_line[0] if last_line else "no output"
return ok, msg
except Exception as exc: # noqa: BLE001
return False, f"pytest error: {exc}"
def gate_routes_check() -> tuple[bool, str]:
"""Run scripts/print_routes.py — should not raise."""
try:
result = subprocess.run(
[sys.executable, "scripts/print_routes.py"],
cwd=REPO_ROOT, capture_output=True, text=True, timeout=30,
)
if result.returncode == 0:
n_routes = result.stdout.count("/api/v1")
return True, f"{n_routes} v1 routes"
return False, f"exit={result.returncode}"
except Exception as exc: # noqa: BLE001
return False, f"err: {exc}"
def gate_no_secrets() -> tuple[bool, str]:
"""Scan repo for secret patterns. Skips known-safe directories."""
findings: list[str] = []
pat = re.compile("|".join(SECRET_PATTERNS))
for path in REPO_ROOT.rglob("*"):
if not path.is_file():
continue
if any(part in EXCLUDE_DIRS for part in path.parts):
continue
# Skip docs that intentionally mention patterns as examples.
if path.suffix in {".md", ".lock", ".pyc", ".png", ".jpg", ".jpeg",
".gif", ".woff", ".woff2", ".ttf"}:
continue
try:
text = path.read_text(encoding="utf-8", errors="ignore")
except Exception: # noqa: BLE001
continue
if pat.search(text):
findings.append(str(path.relative_to(REPO_ROOT)))
if len(findings) >= 3:
break
return (not findings), (
"clean" if not findings else f"FOUND in: {', '.join(findings)}"
)
def gate_staging_health(staging_url: str | None) -> tuple[bool, str]:
"""Hit /health on staging if a URL is provided."""
if not staging_url:
return False, "no --staging-url provided"
url = staging_url.rstrip("/") + "/health"
try:
req = urllib.request.Request(url, headers={"User-Agent": "Dealix/Readiness"})
with urllib.request.urlopen(req, timeout=10) as resp: # nosec
return resp.status == 200, f"status={resp.status}"
except Exception as exc: # noqa: BLE001
return False, f"err: {exc}"
def gate_supabase_staging() -> tuple[bool, str]:
"""We can only check whether SUPABASE_URL is configured, not connectivity."""
if os.getenv("SUPABASE_URL") and os.getenv("SUPABASE_SERVICE_ROLE_KEY"):
return True, "env vars configured"
return False, "SUPABASE_URL or SERVICE_ROLE_KEY not set in env"
def gate_service_catalog(staging_url: str | None) -> tuple[bool, str]:
if not staging_url:
return False, "no --staging-url provided"
url = staging_url.rstrip("/") + "/api/v1/services/catalog"
try:
req = urllib.request.Request(url, headers={"User-Agent": "Dealix/Readiness"})
with urllib.request.urlopen(req, timeout=10) as resp: # nosec
data = json.loads(resp.read().decode("utf-8", errors="ignore"))
total = int(data.get("total", 0))
return total >= 4, f"{total} services"
except Exception as exc: # noqa: BLE001
return False, f"err: {exc}"
def gate_private_beta_page() -> tuple[bool, str]:
p = REPO_ROOT / "landing" / "private-beta.html"
if not p.exists():
return False, "missing"
text = p.read_text(encoding="utf-8", errors="ignore")
has_cta = ("احجز" in text) or ("ابدأ" in text) or ("احصل" in text)
has_pilot = ("Pilot" in text) or ("بايلوت" in text)
return (has_cta and has_pilot), (
"ok" if has_cta and has_pilot else "missing CTA or Pilot mention"
)
def gate_first_20_ready() -> tuple[bool, str]:
"""Soft check: a tracker doc/sheet may exist as evidence."""
candidates = [
REPO_ROOT / "docs" / "FIRST_20_OUTREACH_MESSAGES.md",
REPO_ROOT / "docs" / "REVENUE_TODAY_PLAYBOOK.md",
]
found = [str(c.relative_to(REPO_ROOT)) for c in candidates if c.exists()]
return bool(found), ", ".join(found) or "no first-20 doc/sheet found"
def gate_live_sends_disabled() -> tuple[bool, str]:
"""Verify env flags for live sends are NOT set to true."""
flags = [
"WHATSAPP_ALLOW_LIVE_SEND",
"GMAIL_ALLOW_LIVE_SEND",
"CALENDAR_ALLOW_LIVE_INSERT",
"MOYASAR_ALLOW_LIVE_CHARGE",
"GBP_ALLOW_LIVE_REPLY",
]
enabled = [f for f in flags if os.getenv(f, "false").lower() == "true"]
return (not enabled), (
"all disabled" if not enabled else f"ENABLED: {', '.join(enabled)}"
)
def gate_payment_manual_ready() -> tuple[bool, str]:
"""Soft check: payment-manual flow module is present + accessible."""
p = REPO_ROOT / "auto_client_acquisition" / "revenue_launch" / "payment_manual_flow.py"
return p.exists(), ("module present" if p.exists() else "missing")
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--staging-url", default=None,
help="Optional staging URL for live checks")
parser.add_argument("--json", action="store_true",
help="Emit JSON instead of pretty output")
args = parser.parse_args()
print("Dealix Launch Readiness — 10 Gates")
print("" * 60)
gates = [
("tests_passed", gate_tests_passed()),
("routes_check", gate_routes_check()),
("no_secrets", gate_no_secrets()),
("staging_health", gate_staging_health(args.staging_url)),
("supabase_staging", gate_supabase_staging()),
("service_catalog", gate_service_catalog(args.staging_url)),
("private_beta_page", gate_private_beta_page()),
("first_20_ready", gate_first_20_ready()),
("live_sends_disabled", gate_live_sends_disabled()),
("payment_manual_ready", gate_payment_manual_ready()),
]
passed = sum(1 for _, (ok, _) in gates if ok)
total = len(gates)
pct = round(100 * passed / total, 1)
if args.json:
out = {
"passed": passed, "total": total, "pct": pct,
"gates": [{"id": gid, "passed": ok, "info": info}
for gid, (ok, info) in gates],
}
print(json.dumps(out, ensure_ascii=False, indent=2))
else:
for gid, (ok, info) in gates:
mark = "" if ok else "🔴"
print(f"{mark} {gid:<24} {info}")
print("" * 60)
critical = ("no_secrets", "live_sends_disabled", "staging_health")
critical_failed = [gid for gid, (ok, _) in gates
if gid in critical and not ok]
if critical_failed:
verdict = f"🔴 NO-GO — critical gates failed: {', '.join(critical_failed)}"
elif pct >= 70:
verdict = f"✅ GO (Private Beta) — {passed}/{total} = {pct}%"
else:
verdict = f"🟡 FIX-THEN-GO — only {passed}/{total} = {pct}%"
print(verdict)
return 0 if passed == total else (1 if passed < 7 else 0)
if __name__ == "__main__":
sys.exit(main())