diff --git a/dealix/api/main.py b/dealix/api/main.py
index b5ccee52..619e828d 100644
--- a/dealix/api/main.py
+++ b/dealix/api/main.py
@@ -34,6 +34,7 @@ from api.routers import (
health,
innovation,
intelligence_layer,
+ launch_ops,
leads,
meeting_intelligence,
model_router,
@@ -44,6 +45,7 @@ from api.routers import (
prospect,
public,
revenue,
+ revenue_launch,
revenue_os,
sales,
sectors,
@@ -170,6 +172,8 @@ def create_app() -> FastAPI:
app.include_router(targeting_os.router)
app.include_router(service_tower.router)
app.include_router(service_excellence.router)
+ app.include_router(launch_ops.router)
+ app.include_router(revenue_launch.router)
app.include_router(public.router)
app.include_router(admin.router)
diff --git a/dealix/api/routers/launch_ops.py b/dealix/api/routers/launch_ops.py
new file mode 100644
index 00000000..65f16d8c
--- /dev/null
+++ b/dealix/api/routers/launch_ops.py
@@ -0,0 +1,135 @@
+"""Launch Ops router — Private Beta + Demo + Outreach + Go/No-Go + Scorecard."""
+
+from __future__ import annotations
+
+from typing import Any
+
+from fastapi import APIRouter, Body
+
+from auto_client_acquisition.launch_ops import (
+ build_12_min_demo_flow,
+ build_close_script,
+ build_daily_launch_scorecard,
+ build_discovery_questions,
+ build_first_20_segments,
+ build_followup_message,
+ build_launch_readiness,
+ build_objection_responses,
+ build_outreach_message,
+ build_private_beta_offer,
+ build_private_beta_safety_notes,
+ build_reply_handlers,
+ build_weekly_launch_scorecard,
+ decide_go_no_go,
+ private_beta_faq,
+ record_launch_event,
+)
+
+router = APIRouter(prefix="/api/v1/launch", tags=["launch-ops"])
+
+
+# ── Private Beta ─────────────────────────────────────────────
+@router.get("/private-beta/offer")
+async def private_beta_offer() -> dict[str, Any]:
+ return {
+ "offer": build_private_beta_offer(),
+ "safety": build_private_beta_safety_notes(),
+ "faq": private_beta_faq(),
+ }
+
+
+# ── Demo flow ────────────────────────────────────────────────
+@router.get("/demo/flow")
+async def demo_flow() -> dict[str, Any]:
+ return {
+ "flow": build_12_min_demo_flow(),
+ "discovery_questions": build_discovery_questions(),
+ "objections": build_objection_responses(),
+ "close": build_close_script(),
+ }
+
+
+# ── Outreach ─────────────────────────────────────────────────
+@router.get("/outreach/first-20")
+async def outreach_first_20() -> dict[str, Any]:
+ segments = build_first_20_segments()
+ sample_messages = {
+ s["id"]: build_outreach_message(s["id"])
+ for s in segments["segments"]
+ }
+ return {
+ **segments,
+ "sample_messages": sample_messages,
+ "reply_handlers": build_reply_handlers(),
+ }
+
+
+@router.post("/outreach/message")
+async def outreach_message(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
+ return build_outreach_message(
+ segment_id=payload.get("segment_id", ""),
+ name=payload.get("name", "[الاسم]"),
+ )
+
+
+@router.post("/outreach/followup")
+async def outreach_followup(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
+ return build_followup_message(
+ segment_id=payload.get("segment_id", ""),
+ step=int(payload.get("step", 1)),
+ name=payload.get("name", "[الاسم]"),
+ )
+
+
+# ── Go / No-Go ───────────────────────────────────────────────
+@router.post("/go-no-go")
+async def go_no_go(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]:
+ return decide_go_no_go(statuses=payload.get("statuses"))
+
+
+@router.get("/readiness")
+async def readiness() -> dict[str, Any]:
+ """Readiness with all gates assumed False (use POST /go-no-go for real status)."""
+ return build_launch_readiness(statuses={})
+
+
+# ── Scorecard ────────────────────────────────────────────────
+@router.post("/scorecard/event")
+async def scorecard_event(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
+ try:
+ return record_launch_event(
+ event_type=payload.get("event_type", ""),
+ customer_id=payload.get("customer_id"),
+ notes=payload.get("notes"),
+ )
+ except ValueError as exc:
+ return {"error": str(exc)}
+
+
+@router.post("/scorecard/daily")
+async def scorecard_daily(
+ events: list[dict[str, Any]] = Body(default_factory=list, embed=True),
+) -> dict[str, Any]:
+ return build_daily_launch_scorecard(events=events)
+
+
+@router.post("/scorecard/weekly")
+async def scorecard_weekly(
+ events: list[dict[str, Any]] = Body(default_factory=list, embed=True),
+) -> dict[str, Any]:
+ return build_weekly_launch_scorecard(events=events)
+
+
+@router.get("/scorecard/demo")
+async def scorecard_demo() -> dict[str, Any]:
+ """Demo scorecard with synthetic events."""
+ demo_events = [
+ {"event_type": "outreach_sent"} for _ in range(15)
+ ] + [
+ {"event_type": "reply_received"} for _ in range(4)
+ ] + [
+ {"event_type": "demo_booked"} for _ in range(2)
+ ] + [
+ {"event_type": "blocked_action"} for _ in range(6)
+ ]
+ return build_daily_launch_scorecard(events=demo_events)
diff --git a/dealix/api/routers/revenue_launch.py b/dealix/api/routers/revenue_launch.py
new file mode 100644
index 00000000..ec1eaf49
--- /dev/null
+++ b/dealix/api/routers/revenue_launch.py
@@ -0,0 +1,182 @@
+"""Revenue Launch router — paid offer + pipeline + delivery + payment + proof."""
+
+from __future__ import annotations
+
+from typing import Any
+
+from fastapi import APIRouter, Body
+
+from auto_client_acquisition.revenue_launch import (
+ build_24h_delivery_plan,
+ build_499_pilot_offer,
+ build_case_study_free_offer,
+ build_client_intake_form,
+ build_client_summary,
+ build_first_10_opportunities_delivery,
+ build_first_20_segments_v2,
+ build_followup_1,
+ build_followup_2,
+ build_growth_diagnostic_delivery,
+ build_growth_os_pilot_offer,
+ build_list_intelligence_delivery,
+ build_moyasar_invoice_instructions,
+ build_next_step_recommendation,
+ build_outreach_message_v2,
+ build_payment_confirmation_checklist,
+ build_payment_link_message,
+ build_pipeline_schema,
+ build_private_beta_offer,
+ build_private_beta_proof_pack,
+ build_reply_handlers_v2,
+ demo_12_min,
+ demo_close_script,
+ demo_discovery,
+ demo_objections,
+ recommend_offer_for_segment,
+ summarize_pipeline,
+)
+
+router = APIRouter(prefix="/api/v1/revenue-launch", tags=["revenue-launch"])
+
+
+# ── Offers ───────────────────────────────────────────────────
+@router.get("/offers")
+async def offers() -> dict[str, Any]:
+ return {
+ "private_beta": build_private_beta_offer(),
+ "pilot_499": build_499_pilot_offer(),
+ "growth_os_pilot": build_growth_os_pilot_offer(),
+ "case_study_free": build_case_study_free_offer(),
+ }
+
+
+@router.post("/offers/recommend")
+async def offers_recommend(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
+ return recommend_offer_for_segment(payload.get("segment_id", ""))
+
+
+# ── Outreach ─────────────────────────────────────────────────
+@router.get("/outreach/first-20")
+async def outreach_first_20() -> dict[str, Any]:
+ seg = build_first_20_segments_v2()
+ return {
+ **seg,
+ "messages": {
+ s["id"]: build_outreach_message_v2(s["id"])
+ for s in seg["segments"]
+ },
+ "reply_handlers": build_reply_handlers_v2(),
+ }
+
+
+@router.post("/outreach/followup")
+async def outreach_followup(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
+ step = int(payload.get("step", 1))
+ builder = build_followup_2 if step >= 2 else build_followup_1
+ return builder(
+ segment_id=payload.get("segment_id", ""),
+ name=payload.get("name", "[الاسم]"),
+ )
+
+
+# ── Demo ─────────────────────────────────────────────────────
+@router.get("/demo-flow")
+async def demo_flow() -> dict[str, Any]:
+ return {
+ "flow": demo_12_min(),
+ "discovery_questions": demo_discovery(),
+ "objections": demo_objections(),
+ "close": demo_close_script(),
+ }
+
+
+# ── Pipeline ─────────────────────────────────────────────────
+@router.get("/pipeline/schema")
+async def pipeline_schema() -> dict[str, Any]:
+ return build_pipeline_schema()
+
+
+@router.post("/pipeline/summarize")
+async def pipeline_summarize(
+ pipeline: list[dict[str, Any]] = Body(default_factory=list, embed=True),
+) -> dict[str, Any]:
+ return summarize_pipeline(pipeline)
+
+
+# ── Pilot delivery ───────────────────────────────────────────
+@router.get("/pilot-delivery/intake-form")
+async def pilot_intake_form() -> dict[str, Any]:
+ return build_client_intake_form()
+
+
+@router.post("/pilot-delivery/24h-plan")
+async def pilot_24h_plan(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
+ return build_24h_delivery_plan(payload.get("service_id", ""))
+
+
+@router.post("/pilot-delivery/first-10")
+async def pilot_first_10(intake: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]:
+ return build_first_10_opportunities_delivery(intake)
+
+
+@router.post("/pilot-delivery/list-intelligence")
+async def pilot_list_intelligence(intake: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]:
+ return build_list_intelligence_delivery(intake)
+
+
+@router.post("/pilot-delivery/free-diagnostic")
+async def pilot_free_diagnostic(intake: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]:
+ return build_growth_diagnostic_delivery(intake)
+
+
+# ── Payment manual flow ──────────────────────────────────────
+@router.post("/payment/invoice-instructions")
+async def payment_invoice_instructions(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]:
+ return build_moyasar_invoice_instructions(
+ amount_sar=int(payload.get("amount_sar", 499)),
+ customer_name=payload.get("customer_name", ""),
+ invoice_description=payload.get(
+ "invoice_description",
+ "Dealix Private Beta Pilot — 7 days",
+ ),
+ )
+
+
+@router.post("/payment/link-message")
+async def payment_link_message(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
+ return build_payment_link_message(
+ customer_name=payload.get("customer_name", "[الاسم]"),
+ invoice_url=payload.get("invoice_url", "[INVOICE_URL]"),
+ amount_sar=int(payload.get("amount_sar", 499)),
+ )
+
+
+@router.get("/payment/confirmation-checklist")
+async def payment_confirmation_checklist() -> dict[str, Any]:
+ return build_payment_confirmation_checklist()
+
+
+# ── Proof Pack ───────────────────────────────────────────────
+@router.post("/proof-pack/template")
+async def proof_pack_template(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]:
+ return build_private_beta_proof_pack(
+ company_name=payload.get("company_name", ""),
+ metrics=payload.get("metrics", {}),
+ )
+
+
+@router.post("/proof-pack/client-summary")
+async def proof_pack_client_summary(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]:
+ return build_client_summary(
+ company_name=payload.get("company_name", ""),
+ opportunities_count=int(payload.get("opportunities_count", 0)),
+ approved_drafts=int(payload.get("approved_drafts", 0)),
+ meetings=int(payload.get("meetings", 0)),
+ pipeline_sar=float(payload.get("pipeline_sar", 0)),
+ risks_blocked=int(payload.get("risks_blocked", 0)),
+ )
+
+
+@router.post("/proof-pack/next-step")
+async def proof_pack_next_step(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]:
+ return build_next_step_recommendation(pilot_metrics=payload)
diff --git a/dealix/auto_client_acquisition/launch_ops/__init__.py b/dealix/auto_client_acquisition/launch_ops/__init__.py
new file mode 100644
index 00000000..8becfedd
--- /dev/null
+++ b/dealix/auto_client_acquisition/launch_ops/__init__.py
@@ -0,0 +1,61 @@
+"""Launch Ops — Private Beta launch workflow + Go/No-Go gates + scorecards.
+
+Connects everything else into a single launch-day operating layer:
+ - private_beta: today's offer, gates, FAQ
+ - demo_flow: 12-min demo script consolidator
+ - outreach_messages: first-20 plan + per-segment messages
+ - go_no_go: deterministic launch readiness gate
+ - launch_scorecard: daily ops metrics
+"""
+
+from __future__ import annotations
+
+from .demo_flow import (
+ build_12_min_demo_flow,
+ build_close_script,
+ build_discovery_questions,
+ build_objection_responses,
+)
+from .go_no_go import build_launch_readiness, decide_go_no_go
+from .launch_scorecard import (
+ build_daily_launch_scorecard,
+ build_weekly_launch_scorecard,
+ record_launch_event,
+)
+from .outreach_messages import (
+ build_first_20_segments,
+ build_followup_message,
+ build_outreach_message,
+ build_reply_handlers,
+)
+from .private_beta import (
+ PRIVATE_BETA_OFFER,
+ build_private_beta_offer,
+ build_private_beta_safety_notes,
+ private_beta_faq,
+)
+
+__all__ = [
+ # private_beta
+ "PRIVATE_BETA_OFFER",
+ "build_private_beta_offer",
+ "build_private_beta_safety_notes",
+ "private_beta_faq",
+ # demo_flow
+ "build_12_min_demo_flow",
+ "build_close_script",
+ "build_discovery_questions",
+ "build_objection_responses",
+ # outreach_messages
+ "build_first_20_segments",
+ "build_followup_message",
+ "build_outreach_message",
+ "build_reply_handlers",
+ # go_no_go
+ "build_launch_readiness",
+ "decide_go_no_go",
+ # launch_scorecard
+ "build_daily_launch_scorecard",
+ "build_weekly_launch_scorecard",
+ "record_launch_event",
+]
diff --git a/dealix/auto_client_acquisition/launch_ops/demo_flow.py b/dealix/auto_client_acquisition/launch_ops/demo_flow.py
new file mode 100644
index 00000000..e768f18f
--- /dev/null
+++ b/dealix/auto_client_acquisition/launch_ops/demo_flow.py
@@ -0,0 +1,104 @@
+"""Demo flow — 12-min Arabic demo + discovery + objection handling + close."""
+
+from __future__ import annotations
+
+from typing import Any
+
+
+def build_12_min_demo_flow() -> dict[str, Any]:
+ """The canonical 12-minute Arabic demo plan."""
+ return {
+ "duration_minutes": 12,
+ "minute_by_minute_ar": [
+ "0–2: الفكرة الكبرى — Dealix ليس CRM ولا أداة واتساب.",
+ "2–4: Daily Brief / Command Feed — 3 قرارات + 3 فرص + 3 مخاطر.",
+ "4–6: 10 فرص في 10 دقائق — مثال حي.",
+ "6–8: Trust Score + Simulator + Approval Card.",
+ "8–10: الأمان والتكاملات — security_curator + connector_catalog.",
+ "10–12: العرض والـ CTA — Pilot 7 أيام / 499 ريال.",
+ ],
+ "demo_endpoints": [
+ "/api/v1/personal-operator/daily-brief",
+ "/api/v1/intelligence/command-feed/demo",
+ "/api/v1/intelligence/missions",
+ "/api/v1/targeting/free-diagnostic",
+ "/api/v1/services/catalog",
+ "/api/v1/launch/private-beta/offer",
+ ],
+ "do_not_do_in_demo_ar": [
+ "لا تكشف API keys على الشاشة.",
+ "لا تشغّل live WhatsApp أو Gmail send.",
+ "لا تعد بأرقام لم تُحقَّق.",
+ ],
+ }
+
+
+def build_discovery_questions() -> list[dict[str, str]]:
+ """5 discovery questions to ask in the demo's first 4 minutes."""
+ return [
+ {"key": "challenge",
+ "q_ar": "وش أكبر تحدي نمو لديكم اليوم؟"},
+ {"key": "current_targeting",
+ "q_ar": "كيف تستهدفون اليوم؟ ما الذي يعمل؟ ما الذي لا يعمل؟"},
+ {"key": "time_drain",
+ "q_ar": "ما الذي يأخذ وقتاً يومياً ولا يثبت قيمة؟"},
+ {"key": "old_list",
+ "q_ar": "هل عندكم قائمة عملاء قدامى لم تتم متابعتهم؟"},
+ {"key": "approval_owner",
+ "q_ar": "من يوافق على الرسائل قبل الإرسال؟"},
+ ]
+
+
+def build_objection_responses() -> dict[str, str]:
+ """Standard Arabic objection-handling responses."""
+ return {
+ "price": (
+ "نقدم Free Diagnostic أولاً — تشوفون عينة قبل الدفع. "
+ "Pilot 499 ريال أرخص من ساعة عمل في وكالة."
+ ),
+ "timing": (
+ "Pilot 7 أيام لا يحتاج التزام طويل. "
+ "نسلّم خلال أسبوع، تقررون بعدها."
+ ),
+ "trust": (
+ "Approval-first: لا نرسل أي شيء بدون موافقتكم. "
+ "Audit ledger يسجل كل فعل."
+ ),
+ "complexity": (
+ "Pilot لا يحتاج تكاملات. "
+ "نستلم intake في 30 دقيقة ونسلم خلال 24 ساعة."
+ ),
+ "data_privacy": (
+ "PDPL-aware من اليوم الأول. "
+ "DPA draft جاهز للتوقيع. "
+ "بياناتكم تُخزّن في Supabase KSA-region حسب الإمكان."
+ ),
+ "results_uncertainty": (
+ "لا نضمن أرقاماً، نضمن طريقة تشغيل + Proof Pack مفصّل. "
+ "إذا ما اقتنعتم بعد 7 أيام، تأخذون Proof Pack مجاناً وتمشون."
+ ),
+ }
+
+
+def build_close_script() -> dict[str, Any]:
+ """The closing script — used in minute 11-12 of the demo."""
+ return {
+ "close_sequence_ar": [
+ "هل الفكرة منطقية؟",
+ "هل عندك أسئلة محددة قبل ما نبدأ؟",
+ "أحدد لكم Pilot يبدأ يوم الأحد القادم — موافق؟",
+ "أرسل لكم intake form + invoice خلال ساعة من نهاية المكالمة.",
+ ],
+ "close_template_ar": (
+ "تمام، نبدأ Pilot 7 أيام بـ499 ريال. "
+ "أرسل لك خلال ساعة:\n"
+ "1. نموذج intake.\n"
+ "2. Moyasar invoice.\n"
+ "3. تأكيد موعد الكيك-أوف.\n\n"
+ "بعد الدفع، Pilot يبدأ يوم الأحد."
+ ),
+ "if_hesitant_ar": (
+ "إذا تحبون عينة قبل الالتزام، أرسل لكم Free Growth Diagnostic "
+ "خلال 24 ساعة — 3 فرص + رسالة + توصية، بدون التزام."
+ ),
+ }
diff --git a/dealix/auto_client_acquisition/launch_ops/go_no_go.py b/dealix/auto_client_acquisition/launch_ops/go_no_go.py
new file mode 100644
index 00000000..4cf807bd
--- /dev/null
+++ b/dealix/auto_client_acquisition/launch_ops/go_no_go.py
@@ -0,0 +1,130 @@
+"""Go/No-Go launch readiness — 10 deterministic gates."""
+
+from __future__ import annotations
+
+from typing import Any
+
+# All 10 gates Dealix Launch Control Room checks before approving sale.
+LAUNCH_GATES: tuple[dict[str, str], ...] = (
+ {"id": "tests_passed", "label_ar": "اختبارات pytest خضراء"},
+ {"id": "routes_check", "label_ar": "scripts/print_routes.py لا يكشف تكرار"},
+ {"id": "no_secrets", "label_ar": "scan الأسرار نظيف"},
+ {"id": "staging_health", "label_ar": "/health على staging يرجع 200"},
+ {"id": "supabase_staging", "label_ar": "Supabase staging مهيأ"},
+ {"id": "service_catalog", "label_ar": "/services/catalog يعمل ويعرض ≥4 خدمات"},
+ {"id": "private_beta_page", "label_ar": "landing/private-beta.html جاهزة"},
+ {"id": "first_20_ready", "label_ar": "أول 20 prospect معرّفون"},
+ {"id": "live_sends_disabled", "label_ar": "WHATSAPP/GMAIL/CALENDAR/MOYASAR live=false"},
+ {"id": "payment_manual_ready", "label_ar": "Moyasar invoice/payment link جاهز يدوياً"},
+)
+
+
+def build_launch_readiness(
+ *, statuses: dict[str, bool] | None = None,
+) -> dict[str, Any]:
+ """
+ Build the launch-readiness checklist with current statuses.
+
+ Pass `statuses` as a dict of gate_id → bool. Unknown gates default to False.
+ """
+ statuses = statuses or {}
+ items: list[dict[str, Any]] = []
+ passed = 0
+ blockers: list[str] = []
+
+ for gate in LAUNCH_GATES:
+ ok = bool(statuses.get(gate["id"], False))
+ items.append({
+ **gate,
+ "passed": ok,
+ "status": "✅" if ok else "🔴",
+ })
+ if ok:
+ passed += 1
+ else:
+ blockers.append(gate["label_ar"])
+
+ total = len(LAUNCH_GATES)
+ pct = round(100.0 * passed / total, 1) if total else 0.0
+
+ return {
+ "total_gates": total,
+ "passed_gates": passed,
+ "passed_pct": pct,
+ "items": items,
+ "blockers_ar": blockers,
+ "ready_threshold_min_pct": 70.0,
+ }
+
+
+def decide_go_no_go(
+ *, statuses: dict[str, bool] | None = None,
+) -> dict[str, Any]:
+ """
+ Decide whether Dealix can sell today.
+
+ Rules:
+ - All "critical" gates must pass: no_secrets, live_sends_disabled, staging_health.
+ - At least 7 of 10 gates must pass overall.
+ """
+ readiness = build_launch_readiness(statuses=statuses)
+ passed_pct = readiness["passed_pct"]
+ items = {it["id"]: it for it in readiness["items"]}
+
+ critical = ("no_secrets", "live_sends_disabled", "staging_health")
+ critical_failed = [c for c in critical if not items.get(c, {}).get("passed")]
+
+ if critical_failed:
+ verdict = "no_go"
+ reason_ar = (
+ f"بوابات حرجة فشلت: {', '.join(critical_failed)}. "
+ "لا تبيع اليوم."
+ )
+ elif passed_pct >= 70:
+ verdict = "go"
+ reason_ar = (
+ f"الجاهزية {passed_pct}%. "
+ "ابدأ Private Beta — لا Public Launch."
+ )
+ else:
+ verdict = "fix_then_go"
+ reason_ar = (
+ f"الجاهزية {passed_pct}% — أقل من 70%. "
+ "ابدأ بإصلاح: " + ", ".join(readiness["blockers_ar"][:3])
+ )
+
+ return {
+ "verdict": verdict,
+ "reason_ar": reason_ar,
+ "readiness": readiness,
+ "next_actions_ar": _next_actions(readiness),
+ }
+
+
+def _next_actions(readiness: dict[str, Any]) -> list[str]:
+ """Build concrete next-actions for any failing gates."""
+ by_id = {it["id"]: it for it in readiness["items"]}
+ actions: list[str] = []
+ if not by_id["tests_passed"]["passed"]:
+ actions.append("شغّل: pytest -q")
+ if not by_id["routes_check"]["passed"]:
+ actions.append("شغّل: python scripts/print_routes.py")
+ if not by_id["no_secrets"]["passed"]:
+ actions.append("شغّل grep scan + ألغِ أي مفتاح ظهر.")
+ if not by_id["staging_health"]["passed"]:
+ actions.append("انشر على Railway: railway up + curl /health.")
+ if not by_id["supabase_staging"]["passed"]:
+ actions.append("شغّل: supabase db push --dry-run ثم db push.")
+ if not by_id["service_catalog"]["passed"]:
+ actions.append("افحص: curl /api/v1/services/catalog.")
+ if not by_id["private_beta_page"]["passed"]:
+ actions.append("افتح landing/private-beta.html وتحقق من CTA.")
+ if not by_id["first_20_ready"]["passed"]:
+ actions.append("جهز Sheet 'Dealix First 20 Pipeline' بالعمدة.")
+ if not by_id["live_sends_disabled"]["passed"]:
+ actions.append(
+ "تأكد: WHATSAPP_ALLOW_LIVE_SEND=false (وما يماثلها)."
+ )
+ if not by_id["payment_manual_ready"]["passed"]:
+ actions.append("افتح Moyasar dashboard وجهّز invoice template.")
+ return actions
diff --git a/dealix/auto_client_acquisition/launch_ops/launch_scorecard.py b/dealix/auto_client_acquisition/launch_ops/launch_scorecard.py
new file mode 100644
index 00000000..4ef881a7
--- /dev/null
+++ b/dealix/auto_client_acquisition/launch_ops/launch_scorecard.py
@@ -0,0 +1,140 @@
+"""Launch scorecard — daily and weekly metrics for Private Beta ops."""
+
+from __future__ import annotations
+
+from collections import defaultdict
+from typing import Any
+
+# Valid event types the launch scorecard accepts.
+VALID_LAUNCH_EVENTS: tuple[str, ...] = (
+ "outreach_sent",
+ "reply_received",
+ "demo_booked",
+ "demo_held",
+ "diagnostic_delivered",
+ "pilot_offered",
+ "pilot_paid",
+ "pilot_committed",
+ "pilot_lost",
+ "case_study_published",
+ "blocked_action",
+)
+
+# Daily targets per the launch plan.
+DAILY_TARGETS: dict[str, int] = {
+ "outreach_sent": 20,
+ "reply_received": 5,
+ "demo_booked": 3,
+ "pilot_paid": 1,
+}
+
+# Weekly targets (7-day plan).
+WEEKLY_TARGETS: dict[str, int] = {
+ "outreach_sent": 100,
+ "reply_received": 20,
+ "demo_booked": 10,
+ "pilot_paid": 2,
+}
+
+
+def record_launch_event(
+ *,
+ event_type: str,
+ customer_id: str | None = None,
+ notes: str | None = None,
+ event_log: list[dict[str, Any]] | None = None,
+) -> dict[str, Any]:
+ """
+ Record a launch event into an in-memory log.
+
+ Returns the appended entry (validated). Raises ValueError on unknown type.
+ """
+ if event_type not in VALID_LAUNCH_EVENTS:
+ raise ValueError(
+ f"Unknown launch event: {event_type}. "
+ f"Valid: {', '.join(VALID_LAUNCH_EVENTS)}"
+ )
+ entry: dict[str, Any] = {
+ "event_type": event_type,
+ "customer_id": customer_id,
+ "notes": (notes or "")[:300],
+ }
+ if event_log is not None:
+ event_log.append(entry)
+ return entry
+
+
+def _aggregate(events: list[dict[str, Any]]) -> dict[str, int]:
+ counts: dict[str, int] = defaultdict(int)
+ for e in events or []:
+ et = str(e.get("event_type", ""))
+ counts[et] += 1
+ return dict(counts)
+
+
+def build_daily_launch_scorecard(
+ *, events: list[dict[str, Any]] | None = None,
+) -> dict[str, Any]:
+ """Build today's Arabic launch scorecard from event log."""
+ counts = _aggregate(events or [])
+ metrics = {k: counts.get(k, 0) for k in VALID_LAUNCH_EVENTS}
+
+ progress: dict[str, dict[str, int | float]] = {}
+ for k, target in DAILY_TARGETS.items():
+ actual = metrics.get(k, 0)
+ pct = round(100 * actual / target, 1) if target else 0.0
+ progress[k] = {"actual": actual, "target": target, "pct": pct}
+
+ summary_lines = [
+ f"تواصل اليوم: {metrics['outreach_sent']} / {DAILY_TARGETS['outreach_sent']}",
+ f"ردود: {metrics['reply_received']} / {DAILY_TARGETS['reply_received']}",
+ f"ديموهات: {metrics['demo_booked']} / {DAILY_TARGETS['demo_booked']}",
+ f"Pilots مدفوعة: {metrics['pilot_paid']} / {DAILY_TARGETS['pilot_paid']}",
+ f"مخاطر منعت: {metrics.get('blocked_action', 0)}",
+ ]
+
+ return {
+ "metrics": metrics,
+ "targets": DAILY_TARGETS,
+ "progress": progress,
+ "summary_ar": summary_lines,
+ }
+
+
+def build_weekly_launch_scorecard(
+ *, events: list[dict[str, Any]] | None = None,
+) -> dict[str, Any]:
+ """Build the 7-day Arabic launch scorecard."""
+ counts = _aggregate(events or [])
+ metrics = {k: counts.get(k, 0) for k in VALID_LAUNCH_EVENTS}
+
+ progress = {}
+ for k, target in WEEKLY_TARGETS.items():
+ actual = metrics.get(k, 0)
+ pct = round(100 * actual / target, 1) if target else 0.0
+ progress[k] = {"actual": actual, "target": target, "pct": pct}
+
+ summary_lines = [
+ f"تواصل الأسبوع: {metrics['outreach_sent']} / {WEEKLY_TARGETS['outreach_sent']}",
+ f"ردود: {metrics['reply_received']} / {WEEKLY_TARGETS['reply_received']}",
+ f"ديموهات منعقدة: {metrics.get('demo_held', 0)}",
+ f"Pilots مدفوعة: {metrics['pilot_paid']} / {WEEKLY_TARGETS['pilot_paid']}",
+ f"Pilots commitments: {metrics.get('pilot_committed', 0)}",
+ f"Pilots خسرت: {metrics.get('pilot_lost', 0)}",
+ f"مخاطر منعت: {metrics.get('blocked_action', 0)}",
+ ]
+
+ if metrics["pilot_paid"] >= WEEKLY_TARGETS["pilot_paid"]:
+ verdict = "on_track"
+ elif metrics["demo_booked"] >= 5:
+ verdict = "promising"
+ else:
+ verdict = "needs_focus"
+
+ return {
+ "metrics": metrics,
+ "targets": WEEKLY_TARGETS,
+ "progress": progress,
+ "summary_ar": summary_lines,
+ "verdict": verdict,
+ }
diff --git a/dealix/auto_client_acquisition/launch_ops/outreach_messages.py b/dealix/auto_client_acquisition/launch_ops/outreach_messages.py
new file mode 100644
index 00000000..a62f21b4
--- /dev/null
+++ b/dealix/auto_client_acquisition/launch_ops/outreach_messages.py
@@ -0,0 +1,188 @@
+"""First 20 outreach segments + per-segment Arabic messages + reply handlers."""
+
+from __future__ import annotations
+
+from typing import Any
+
+
+def build_first_20_segments() -> dict[str, Any]:
+ """The deterministic first-20 plan — 4 segments × 5 prospects each."""
+ return {
+ "total_targets": 20,
+ "segments": [
+ {
+ "id": "agency_b2b",
+ "label_ar": "وكالات تسويق B2B",
+ "count": 5,
+ "best_offer_id": "agency_partner_program",
+ "fallback_offer_id": "partner_sprint",
+ "primary_channel": "email",
+ },
+ {
+ "id": "training_consulting",
+ "label_ar": "شركات تدريب واستشارات",
+ "count": 5,
+ "best_offer_id": "first_10_opportunities_sprint",
+ "fallback_offer_id": "free_growth_diagnostic",
+ "primary_channel": "email",
+ },
+ {
+ "id": "saas_tech_small",
+ "label_ar": "SaaS / تقنية صغيرة",
+ "count": 5,
+ "best_offer_id": "first_10_opportunities_sprint",
+ "fallback_offer_id": "growth_os_monthly",
+ "primary_channel": "linkedin_lead_form",
+ },
+ {
+ "id": "services_with_whatsapp",
+ "label_ar": "شركات خدمات لديها واتساب نشط",
+ "count": 5,
+ "best_offer_id": "list_intelligence",
+ "fallback_offer_id": "whatsapp_compliance_setup",
+ "primary_channel": "email",
+ },
+ ],
+ "rules_ar": [
+ "لا scraping ولا قوائم مشتراة.",
+ "استخدم علاقاتك المباشرة + جهات تعرفها.",
+ "كل رسالة يدوية، لا automation.",
+ "حد أقصى 3 follow-ups ثم أرشفة.",
+ ],
+ }
+
+
+_BASE_INTRO = "هلا [الاسم]، أطلقنا Beta محدودة لـ Dealix."
+
+
+def build_outreach_message(segment_id: str, *, name: str = "[الاسم]") -> dict[str, Any]:
+ """Build the first-touch Arabic message for a segment."""
+ intro = f"هلا {name}،"
+
+ if segment_id == "agency_b2b":
+ body = (
+ f"{intro} عندي Beta خاص للوكالات.\n\n"
+ "Dealix يساعد الوكالة تطلع فرص لعملائها، تجهز رسائل عربية، تدير "
+ "موافقات، وتطلع Proof Pack باسم الوكالة والعميل.\n\n"
+ "أبحث عن وكالة واحدة نجرب معها Pilot مشترك على عميل حقيقي. "
+ "يناسبك ديمو 15 دقيقة؟"
+ )
+ elif segment_id == "training_consulting":
+ body = (
+ f"{intro} متابع توسع شركتكم في برامج الشركات.\n\n"
+ "Dealix يطلع لكم 10 فرص B2B خلال 7 أيام، يكتب الرسائل بالعربي، "
+ "ويخلي صاحب القرار يوافق قبل أي تواصل، وبعدها يعطي Proof Pack.\n\n"
+ "Pilot بـ499 ريال أو مجاني مقابل case study. يناسبك ديمو 12 دقيقة؟"
+ )
+ elif segment_id == "saas_tech_small":
+ body = (
+ f"{intro} رأيت إصدار النسخة الجديدة من منتجكم — مبروك.\n\n"
+ "نشتغل على مدير نمو عربي يطلع 10 فرص B2B، يستخدم LinkedIn Lead "
+ "Forms (لا scraping)، ويكتب الرسائل بالعربي.\n\n"
+ "أبغى أجربه مع شركة SaaS سعودية واحدة. يناسبك ديمو 12 دقيقة؟"
+ )
+ elif segment_id == "services_with_whatsapp":
+ body = (
+ f"{intro} عندكم قاعدة عملاء واتساب نشطة، صحيح؟\n\n"
+ "Dealix ينظف القائمة، يصنف الـ opt-in، يحظر cold WhatsApp تلقائياً، "
+ "ويكتب رسائل عربية للحملات الآمنة + Proof Pack شهري.\n\n"
+ "List Intelligence بـ499–1,500 ريال. يناسبك أعطيك تشخيص مجاني أولاً؟"
+ )
+ else:
+ body = (
+ f"{intro} {_BASE_INTRO}\n\n"
+ "Dealix يطلع لك 10 فرص B2B + رسائل عربية + Proof Pack — "
+ "وأنت توافق قبل أي تواصل. Pilot 7 أيام بـ499 ريال. "
+ "يناسبك ديمو 12 دقيقة؟"
+ )
+
+ return {
+ "segment_id": segment_id,
+ "channel": "email_or_dm",
+ "body_ar": body,
+ "approval_required": True,
+ "live_send_allowed": False,
+ }
+
+
+def build_followup_message(
+ segment_id: str, *, step: int = 1, name: str = "[الاسم]",
+) -> dict[str, Any]:
+ """Build follow-up #1, #2, or #3 (final archive)."""
+ if step <= 1:
+ body = (
+ f"هلا {name}، أرسل لك مثال سريع بدل شرح طويل؟\n"
+ "أقدر أطلع لك عينة من 3 فرص مناسبة لشركتكم + رسالة واحدة جاهزة + "
+ "ملاحظة عن أفضل قناة. إذا أعجبتك نكمل Pilot كامل."
+ )
+ kind = "followup_1"
+ elif step == 2:
+ body = (
+ f"هلا {name}، أعرف أن وقتك مزدحم.\n"
+ "سؤال أخير: لو طلعت لك 3 فرص B2B بالعربي مجاناً هذا الأسبوع، "
+ "تعطيني 15 دقيقة feedback؟"
+ )
+ kind = "followup_2"
+ else:
+ body = (
+ f"هلا {name}، أعتذر على الإلحاح.\n"
+ "أرشّفها وأكون موجود لو احتجتني لاحقاً. شاكر لك."
+ )
+ kind = "followup_3_final"
+
+ return {
+ "segment_id": segment_id,
+ "step": step,
+ "kind": kind,
+ "body_ar": body,
+ "approval_required": True,
+ "live_send_allowed": False,
+ }
+
+
+def build_reply_handlers() -> dict[str, dict[str, str]]:
+ """Standard reply-classifier → response mapping (Arabic)."""
+ return {
+ "interested": {
+ "label_ar": "مهتم",
+ "response_ar": (
+ "ممتاز. أرسل لك intake form + موعد ديمو 12 دقيقة هذا الأسبوع. "
+ "أي وقت يناسبك بين 10 ص و 5 م؟"
+ ),
+ "next_action": "send_intake_and_demo_link",
+ },
+ "needs_more_info": {
+ "label_ar": "يحتاج معلومات أكثر",
+ "response_ar": (
+ "أرسل لك Free Growth Diagnostic — 3 فرص + رسالة + توصية، "
+ "بدون التزام. أحتاج فقط: قطاعكم، مدينتكم، عرضكم الرئيسي."
+ ),
+ "next_action": "send_free_diagnostic_intake",
+ },
+ "price_objection": {
+ "label_ar": "اعتراض سعر",
+ "response_ar": (
+ "تمام، نبدأ بـ Free Diagnostic مجاناً. "
+ "تشوفون النتائج قبل أي دفع."
+ ),
+ "next_action": "send_free_diagnostic_intake",
+ },
+ "not_now": {
+ "label_ar": "ليس الآن",
+ "response_ar": (
+ "تمام، شاكر لك. أتواصل معك بعد شهرين بدون إلحاح. "
+ "إن احتجتنا قبل، أنا موجود."
+ ),
+ "next_action": "schedule_followup_60_days",
+ },
+ "no_thanks": {
+ "label_ar": "غير مهتم",
+ "response_ar": "تمام، شاكر لك. أرشّفها وأتمنى لكم التوفيق.",
+ "next_action": "archive",
+ },
+ "unsubscribe": {
+ "label_ar": "إلغاء",
+ "response_ar": "تم. لن أتواصل معك مجدداً.",
+ "next_action": "honor_opt_out_immediately",
+ },
+ }
diff --git a/dealix/auto_client_acquisition/launch_ops/private_beta.py b/dealix/auto_client_acquisition/launch_ops/private_beta.py
new file mode 100644
index 00000000..c4c4344a
--- /dev/null
+++ b/dealix/auto_client_acquisition/launch_ops/private_beta.py
@@ -0,0 +1,110 @@
+"""Private Beta offer — today's offer + safety notes + FAQ."""
+
+from __future__ import annotations
+
+from typing import Any
+
+PRIVATE_BETA_OFFER: dict[str, Any] = {
+ "offer_id": "private_beta_pilot_7d",
+ "name_ar": "Private Beta Pilot — 7 أيام",
+ "promise_ar": (
+ "خلال 7 أيام نطلع لك 10 فرص B2B + رسائل عربية + خطة متابعة + Proof Pack، "
+ "وأنت توافق قبل أي تواصل."
+ ),
+ "deliverables_ar": [
+ "10 فرص B2B مع why-now + buying committee.",
+ "10 رسائل عربية بنبرة سعودية طبيعية.",
+ "تصنيف القنوات (safe / needs_review / blocked) لكل contact.",
+ "خطة متابعة 7 أيام.",
+ "Proof Pack مختصر (PDF + JSON).",
+ "جلسة مراجعة 30 دقيقة في نهاية الأسبوع.",
+ ],
+ "price_sar": 499,
+ "free_alternative_ar": "مجاني مقابل case study بعد انتهاء الـ Pilot.",
+ "approval_required": True,
+ "live_send_allowed": False,
+ "duration_days": 7,
+ "seats_available": 5,
+}
+
+
+def build_private_beta_offer(*, seats_remaining: int | None = None) -> dict[str, Any]:
+ """Build today's Private Beta offer card. Seats are configurable."""
+ out = dict(PRIVATE_BETA_OFFER)
+ if seats_remaining is not None:
+ out["seats_available"] = max(0, int(seats_remaining))
+ out["upsell_path"] = [
+ "growth_os_pilot_30d",
+ "growth_os_monthly",
+ ]
+ return out
+
+
+def build_private_beta_safety_notes() -> dict[str, Any]:
+ """Return the explicit 'what we will NOT do today' list."""
+ return {
+ "title_ar": "ضمانات Dealix",
+ "do_not_do_ar": [
+ "لا live WhatsApp send بدون env flag + اعتماد بشري.",
+ "لا live Gmail send.",
+ "لا Calendar insert تلقائي.",
+ "لا charge Moyasar تلقائي — invoice/payment link يدوي فقط.",
+ "لا scraping LinkedIn ولا auto-DM.",
+ "لا cold WhatsApp (PDPL).",
+ "لا وعود بنتائج مضمونة.",
+ "لا تخزين بيانات بطاقات.",
+ ],
+ "do_ar": [
+ "Approval-first في كل قناة.",
+ "Audit ledger لكل فعل.",
+ "Saudi Tone + Safety eval قبل أي رسالة.",
+ "Reputation Guard يوقف القناة عند تدهور السمعة.",
+ "Free Diagnostic قبل أي التزام.",
+ ],
+ }
+
+
+def private_beta_faq() -> list[dict[str, str]]:
+ """Common Arabic FAQ entries for the Private Beta page."""
+ return [
+ {
+ "q_ar": "كيف يعمل Pilot الـ7 أيام؟",
+ "a_ar": (
+ "نأخذ منك intake (قطاع/مدينة/عرض/هدف) خلال 30 دقيقة. "
+ "خلال 24 ساعة عمل نسلّم 10 فرص + رسائل + تصنيف القنوات. "
+ "خلال الأسبوع نتابع الردود ونحدّث Proof Pack."
+ ),
+ },
+ {
+ "q_ar": "هل ترسلون رسائل بدون موافقتي؟",
+ "a_ar": "لا. كل رسالة تظل draft حتى توافق عليها صراحة.",
+ },
+ {
+ "q_ar": "ماذا لو ما رد أحد؟",
+ "a_ar": (
+ "Proof Pack يوضح المخاطر التي منعناها + توصية بقطاع/زاوية مختلفة. "
+ "Pilot يثبت طريقة التشغيل وليس عدداً مضموناً من الصفقات."
+ ),
+ },
+ {
+ "q_ar": "هل تعرفون شروط واتساب ولينكدإن؟",
+ "a_ar": (
+ "نعم. لا cold WhatsApp بدون opt-in. "
+ "لا scraping ولا auto-DM في LinkedIn — نستخدم Lead Gen Forms والمهام اليدوية."
+ ),
+ },
+ {
+ "q_ar": "كيف أدفع 499 ريال؟",
+ "a_ar": (
+ "نرسل لك Moyasar invoice أو payment link من الـ dashboard. "
+ "بعد الدفع نبدأ Pilot يوم الأحد التالي."
+ ),
+ },
+ {
+ "q_ar": "هل يصلح للوكالات؟",
+ "a_ar": (
+ "نعم — Agency Partner Program يعطي الوكالة co-branded Proof Pack + "
+ "revenue share على عملائها. تواصل معنا مباشرة للترتيب."
+ ),
+ },
+ ]
diff --git a/dealix/auto_client_acquisition/revenue_launch/__init__.py b/dealix/auto_client_acquisition/revenue_launch/__init__.py
new file mode 100644
index 00000000..7cf7c062
--- /dev/null
+++ b/dealix/auto_client_acquisition/revenue_launch/__init__.py
@@ -0,0 +1,86 @@
+"""Revenue Launch — turn Dealix into actual paid pilots TODAY.
+
+Scope:
+ - offer_builder: build today's paid offers (499 Pilot, Growth OS Pilot, free case study)
+ - pipeline_tracker: deterministic pipeline schema + add/update/summarize
+ - outreach_sequence: build first-20 with day-by-day cadence
+ - demo_closer: 12-min demo wrapper + close script + objection bank
+ - pilot_delivery: 24-hour delivery template per service
+ - proof_pack_template: client-facing summary
+ - payment_manual_flow: Moyasar invoice/payment-link manual instructions
+"""
+
+from __future__ import annotations
+
+from .demo_closer import (
+ build_12_min_demo_flow as demo_12_min,
+ build_close_script as demo_close_script,
+ build_discovery_questions as demo_discovery,
+ build_objection_responses as demo_objections,
+)
+from .offer_builder import (
+ build_499_pilot_offer,
+ build_case_study_free_offer,
+ build_growth_os_pilot_offer,
+ build_private_beta_offer,
+ recommend_offer_for_segment,
+)
+from .payment_manual_flow import (
+ build_moyasar_invoice_instructions,
+ build_payment_confirmation_checklist,
+ build_payment_link_message,
+)
+from .pilot_delivery import (
+ build_24h_delivery_plan,
+ build_client_intake_form,
+ build_first_10_opportunities_delivery,
+ build_growth_diagnostic_delivery,
+ build_list_intelligence_delivery,
+)
+from .pipeline_tracker import (
+ PIPELINE_STAGES,
+ add_prospect,
+ build_pipeline_schema,
+ summarize_pipeline,
+ update_stage,
+)
+from .proof_pack_template import (
+ build_client_summary,
+ build_next_step_recommendation,
+ build_private_beta_proof_pack,
+)
+from .outreach_sequence import (
+ build_first_20_segments_v2,
+ build_followup_1,
+ build_followup_2,
+ build_outreach_message_v2,
+ build_reply_handlers_v2,
+)
+
+__all__ = [
+ # offer_builder
+ "build_499_pilot_offer", "build_case_study_free_offer",
+ "build_growth_os_pilot_offer", "build_private_beta_offer",
+ "recommend_offer_for_segment",
+ # pipeline_tracker
+ "PIPELINE_STAGES", "add_prospect", "build_pipeline_schema",
+ "summarize_pipeline", "update_stage",
+ # outreach_sequence
+ "build_first_20_segments_v2", "build_followup_1",
+ "build_followup_2", "build_outreach_message_v2",
+ "build_reply_handlers_v2",
+ # demo_closer
+ "demo_12_min", "demo_close_script", "demo_discovery", "demo_objections",
+ # pilot_delivery
+ "build_24h_delivery_plan", "build_client_intake_form",
+ "build_first_10_opportunities_delivery",
+ "build_growth_diagnostic_delivery",
+ "build_list_intelligence_delivery",
+ # proof_pack_template
+ "build_client_summary", "build_next_step_recommendation",
+ "build_private_beta_proof_pack",
+ # payment_manual_flow
+ "build_moyasar_invoice_instructions",
+ "build_payment_confirmation_checklist",
+ "build_payment_link_message",
+]
diff --git a/dealix/auto_client_acquisition/revenue_launch/demo_closer.py b/dealix/auto_client_acquisition/revenue_launch/demo_closer.py
new file mode 100644
index 00000000..78955341
--- /dev/null
+++ b/dealix/auto_client_acquisition/revenue_launch/demo_closer.py
@@ -0,0 +1,17 @@
+"""Demo closer — re-export single source of truth from launch_ops."""
+
+from __future__ import annotations
+
+from auto_client_acquisition.launch_ops.demo_flow import (
+ build_12_min_demo_flow,
+ build_close_script,
+ build_discovery_questions,
+ build_objection_responses,
+)
+
+__all__ = [
+ "build_12_min_demo_flow",
+ "build_close_script",
+ "build_discovery_questions",
+ "build_objection_responses",
+]
diff --git a/dealix/auto_client_acquisition/revenue_launch/offer_builder.py b/dealix/auto_client_acquisition/revenue_launch/offer_builder.py
new file mode 100644
index 00000000..239d9620
--- /dev/null
+++ b/dealix/auto_client_acquisition/revenue_launch/offer_builder.py
@@ -0,0 +1,131 @@
+"""Today's paid offers — 499 Pilot, Growth OS Pilot, free case study."""
+
+from __future__ import annotations
+
+from typing import Any
+
+
+def build_499_pilot_offer() -> dict[str, Any]:
+ """The headline 499 SAR Pilot — Dealix's revenue funnel entry."""
+ return {
+ "offer_id": "pilot_499_7d",
+ "name_ar": "Pilot 7 أيام — 499 ريال",
+ "promise_ar": (
+ "خلال 7 أيام: 10 فرص B2B + رسائل عربية + خطة متابعة + Proof Pack."
+ ),
+ "deliverables_ar": [
+ "10 فرص مرتبة بـ fit_score",
+ "10 رسائل عربية بنبرة سعودية",
+ "تصنيف القنوات (safe / needs_review / blocked)",
+ "خطة متابعة 7 أيام",
+ "Proof Pack مختصر (PDF + JSON)",
+ "جلسة مراجعة 30 دقيقة في نهاية الأسبوع",
+ ],
+ "price_sar": 499,
+ "duration_days": 7,
+ "approval_required": True,
+ "live_send_allowed": False,
+ "no_live_charge": True,
+ "payment_method": "moyasar_invoice_or_payment_link",
+ "delivery_starts": "next_sunday_after_payment",
+ }
+
+
+def build_growth_os_pilot_offer() -> dict[str, Any]:
+ """30-day Growth OS Pilot — for serious customers."""
+ return {
+ "offer_id": "growth_os_pilot_30d",
+ "name_ar": "Growth OS Pilot — 30 يوم",
+ "promise_ar": (
+ "تشغيل يومي للنمو لمدة شهر: command feed + drafts + اجتماعات + Proof Pack."
+ ),
+ "deliverables_ar": [
+ "Daily growth brief عربي",
+ "First 10 Opportunities Sprint",
+ "List Intelligence على قائمة العميل",
+ "Email/WhatsApp drafts (بدون live send)",
+ "Meeting drafts على Calendar",
+ "Weekly Proof Pack",
+ "تحويل لـ Growth OS Monthly بعد الإثبات",
+ ],
+ "price_sar_min": 1500,
+ "price_sar_max": 3000,
+ "duration_days": 30,
+ "approval_required": True,
+ "no_live_charge": True,
+ "payment_method": "moyasar_invoice_or_payment_link",
+ }
+
+
+def build_case_study_free_offer() -> dict[str, Any]:
+ """Free Pilot in exchange for a case study + permission to publish."""
+ return {
+ "offer_id": "case_study_free_7d",
+ "name_ar": "Pilot مجاني مقابل case study",
+ "promise_ar": (
+ "نسلّم Pilot 7 أيام مجاناً، وأنت تعطينا تصريحاً بنشر case study بدون "
+ "بيانات حساسة."
+ ),
+ "eligibility_ar": [
+ "شركة سعودية أو خليجية",
+ "حجم متوسط (≥10 موظفين)",
+ "قرار سريع (مدير مفوّض على الرد)",
+ "موافقة كتابية على نشر النتائج بدون بيانات حساسة",
+ ],
+ "price_sar": 0,
+ "case_study_required": True,
+ "approval_required": True,
+ "no_live_charge": True,
+ }
+
+
+def build_private_beta_offer() -> dict[str, Any]:
+ """Re-export the Private Beta offer (single source of truth)."""
+ from auto_client_acquisition.launch_ops import PRIVATE_BETA_OFFER
+ return dict(PRIVATE_BETA_OFFER)
+
+
+def recommend_offer_for_segment(segment_id: str) -> dict[str, Any]:
+ """Map outreach segment → best-fit paid offer."""
+ s = (segment_id or "").lower().strip()
+
+ if s == "agency_b2b":
+ return {
+ "primary_offer": "growth_os_pilot_30d",
+ "fallback_offer": "case_study_free_7d",
+ "reason_ar": (
+ "وكالة → Growth OS Pilot يعطيها revenue share واضح. "
+ "إذا ترددت، اعرض free case study."
+ ),
+ }
+ if s == "training_consulting":
+ return {
+ "primary_offer": "pilot_499_7d",
+ "fallback_offer": "case_study_free_7d",
+ "reason_ar": (
+ "تدريب/استشارات → Pilot 499 سريع. "
+ "free case study للأسماء البارزة."
+ ),
+ }
+ if s == "saas_tech_small":
+ return {
+ "primary_offer": "pilot_499_7d",
+ "fallback_offer": "growth_os_pilot_30d",
+ "reason_ar": (
+ "SaaS صغيرة → Pilot 499 يكسر الجليد + ترقية لـ Growth OS Pilot."
+ ),
+ }
+ if s == "services_with_whatsapp":
+ return {
+ "primary_offer": "pilot_499_7d",
+ "fallback_offer": "case_study_free_7d",
+ "reason_ar": (
+ "خدمات بقاعدة واتساب → Pilot 499 ثم WhatsApp Compliance Setup."
+ ),
+ }
+
+ return {
+ "primary_offer": "pilot_499_7d",
+ "fallback_offer": "case_study_free_7d",
+ "reason_ar": "افتراضي: Pilot 499.",
+ }
diff --git a/dealix/auto_client_acquisition/revenue_launch/outreach_sequence.py b/dealix/auto_client_acquisition/revenue_launch/outreach_sequence.py
new file mode 100644
index 00000000..ee268270
--- /dev/null
+++ b/dealix/auto_client_acquisition/revenue_launch/outreach_sequence.py
@@ -0,0 +1,36 @@
+"""Outreach sequence — re-uses launch_ops with revenue-tier extensions."""
+
+from __future__ import annotations
+
+from typing import Any
+
+from auto_client_acquisition.launch_ops.outreach_messages import (
+ build_first_20_segments as _base_segments,
+ build_followup_message as _base_followup,
+ build_outreach_message as _base_msg,
+ build_reply_handlers as _base_handlers,
+)
+
+
+def build_first_20_segments_v2() -> dict[str, Any]:
+ """Re-export (single source of truth in launch_ops)."""
+ return _base_segments()
+
+
+def build_outreach_message_v2(
+ segment_id: str, *, name: str = "[الاسم]",
+) -> dict[str, Any]:
+ """Re-export from launch_ops."""
+ return _base_msg(segment_id, name=name)
+
+
+def build_followup_1(segment_id: str, *, name: str = "[الاسم]") -> dict[str, Any]:
+ return _base_followup(segment_id, step=1, name=name)
+
+
+def build_followup_2(segment_id: str, *, name: str = "[الاسم]") -> dict[str, Any]:
+ return _base_followup(segment_id, step=2, name=name)
+
+
+def build_reply_handlers_v2() -> dict[str, dict[str, str]]:
+ return _base_handlers()
diff --git a/dealix/auto_client_acquisition/revenue_launch/payment_manual_flow.py b/dealix/auto_client_acquisition/revenue_launch/payment_manual_flow.py
new file mode 100644
index 00000000..7b145b47
--- /dev/null
+++ b/dealix/auto_client_acquisition/revenue_launch/payment_manual_flow.py
@@ -0,0 +1,97 @@
+"""Manual Moyasar invoice/payment-link flow — never charges live from API."""
+
+from __future__ import annotations
+
+from typing import Any
+
+
+def build_moyasar_invoice_instructions(
+ *,
+ amount_sar: int = 499,
+ customer_name: str = "",
+ invoice_description: str = "Dealix Private Beta Pilot — 7 days",
+) -> dict[str, Any]:
+ """
+ Step-by-step instructions to create a Moyasar invoice from the dashboard.
+
+ Never calls the API. Founder-driven only.
+ """
+ amount_halalas = int(amount_sar) * 100
+ return {
+ "amount_sar": amount_sar,
+ "amount_halalas": amount_halalas,
+ "currency": "SAR",
+ "customer_name": customer_name,
+ "description": invoice_description,
+ "method": "manual_moyasar_dashboard",
+ "no_live_charge": True,
+ "instructions_ar": [
+ "1. افتح Moyasar dashboard.",
+ "2. اختر Invoices → Create Invoice.",
+ f"3. ضع المبلغ {amount_sar} ريال (الـ API يستخدم halalas = {amount_halalas}).",
+ f"4. اكتب الوصف: {invoice_description}.",
+ f"5. أضف اسم العميل: {customer_name or '(اسم العميل)'}.",
+ "6. فعّل خيار إرسال الفاتورة بالإيميل.",
+ "7. اضغط Send.",
+ "8. سجّل invoice ID + رابط الفاتورة في pipeline_tracker.",
+ ],
+ "do_not_do_ar": [
+ "لا تخزّن بيانات بطاقة العميل.",
+ "لا تستخدم API live charge من Dealix.",
+ "لا ترسل دفعة بدون تأكيد العميل صراحة.",
+ ],
+ }
+
+
+def build_payment_link_message(
+ *,
+ customer_name: str = "[الاسم]",
+ invoice_url: str = "[INVOICE_URL]",
+ amount_sar: int = 499,
+) -> dict[str, Any]:
+ """Build the Arabic message to send to the customer with the payment link."""
+ body_ar = (
+ f"هلا {customer_name}،\n\n"
+ f"تمام، نبدأ Pilot 7 أيام بـ{amount_sar} ريال.\n\n"
+ "يشمل:\n"
+ "• 10 فرص مناسبة\n"
+ "• رسائل عربية جاهزة\n"
+ "• فحص مخاطر القنوات\n"
+ "• خطة متابعة 7 أيام\n"
+ "• Proof Pack مختصر\n\n"
+ f"رابط الدفع/الفاتورة: {invoice_url}\n\n"
+ "بعد الدفع أحتاج منك:\n"
+ "1. رابط موقعكم.\n"
+ "2. القطاع المستهدف.\n"
+ "3. المدينة.\n"
+ "4. العرض الرئيسي.\n\n"
+ "خلال 24 ساعة عمل بعد الدفع، أسلّمك أول دفعة من المخرجات.\n\nشاكر لك."
+ )
+ return {
+ "channel": "email_or_whatsapp",
+ "body_ar": body_ar,
+ "amount_sar": amount_sar,
+ "invoice_url": invoice_url,
+ "approval_required": True,
+ "live_send_allowed": False,
+ }
+
+
+def build_payment_confirmation_checklist() -> dict[str, Any]:
+ """Checklist after the customer claims to have paid."""
+ return {
+ "title_ar": "تأكيد دفعة Moyasar",
+ "checks_ar": [
+ "افتح Moyasar dashboard → Invoices.",
+ "تحقق أن invoice في حالة paid (وليس initiated أو failed).",
+ "تطابق amount/currency مع الفاتورة الأصلية.",
+ "سجّل في pipeline_tracker: stage=paid + price_sar.",
+ "ابعث للعميل: تأكيد + intake form + موعد الكيك-أوف.",
+ "ابدأ build_24h_delivery_plan.",
+ ],
+ "do_not_do_ar": [
+ "لا تبدأ التسليم قبل تأكيد paid في Moyasar.",
+ "لا تشارك invoice ID في القنوات العامة.",
+ ],
+ "approval_required": True,
+ }
diff --git a/dealix/auto_client_acquisition/revenue_launch/pilot_delivery.py b/dealix/auto_client_acquisition/revenue_launch/pilot_delivery.py
new file mode 100644
index 00000000..e14bfe87
--- /dev/null
+++ b/dealix/auto_client_acquisition/revenue_launch/pilot_delivery.py
@@ -0,0 +1,140 @@
+"""24-hour pilot delivery templates per service."""
+
+from __future__ import annotations
+
+from typing import Any
+
+
+def build_client_intake_form() -> dict[str, Any]:
+ """The single intake form sent to a customer after they pay."""
+ return {
+ "fields": [
+ {"key": "company_name", "label_ar": "اسم الشركة", "required": True},
+ {"key": "website", "label_ar": "رابط الموقع", "required": True},
+ {"key": "sector", "label_ar": "القطاع", "required": True},
+ {"key": "city", "label_ar": "المدينة", "required": True},
+ {"key": "primary_offer", "label_ar": "العرض الرئيسي", "required": True},
+ {"key": "ideal_customer", "label_ar": "العميل المثالي",
+ "required": True},
+ {"key": "avg_deal_value_sar", "label_ar": "متوسط قيمة الصفقة",
+ "required": False},
+ {"key": "has_contact_list", "label_ar": "هل عندكم قائمة عملاء؟",
+ "required": True, "type": "boolean"},
+ {"key": "channels_available", "label_ar": "القنوات المتاحة",
+ "required": True, "type": "multi"},
+ {"key": "whatsapp_opt_in_status",
+ "label_ar": "حالة opt-in واتساب", "required": False},
+ {"key": "approval_owner",
+ "label_ar": "من يوافق على الرسائل قبل الإرسال؟",
+ "required": True},
+ {"key": "exclusions",
+ "label_ar": "شركات أو أشخاص لا نتواصل معهم",
+ "required": False, "type": "list"},
+ ],
+ "estimated_completion_minutes": 10,
+ "approval_required": True,
+ }
+
+
+def build_24h_delivery_plan(service_id: str) -> dict[str, Any]:
+ """Generic 24-hour delivery plan for any service."""
+ return {
+ "service_id": service_id,
+ "phases": [
+ {"phase": "T+0h", "label_ar": "كيك-أوف",
+ "actions_ar": ["مراجعة intake + تأكيد القناة الأساسية"]},
+ {"phase": "T+1h", "label_ar": "Diagnosis",
+ "actions_ar": [
+ "تشغيل targeting/contactability على القائمة أو القطاع",
+ "تحديد buying committee + why-now",
+ ]},
+ {"phase": "T+6h", "label_ar": "Drafting",
+ "actions_ar": [
+ "صياغة 10 رسائل عربية",
+ "تشغيل safety + Saudi tone evals على كل رسالة",
+ ]},
+ {"phase": "T+18h", "label_ar": "Approval Pack",
+ "actions_ar": [
+ "إرسال drafts للعميل في approval cards (≤3 أزرار لكل بطاقة)",
+ "تحديث Action Ledger",
+ ]},
+ {"phase": "T+24h", "label_ar": "Proof Pack v1",
+ "actions_ar": [
+ "تسليم Proof Pack المختصر",
+ "حجز جلسة مراجعة 30 دقيقة في نهاية الأسبوع",
+ ]},
+ ],
+ "approval_required": True,
+ "live_send_allowed": False,
+ }
+
+
+def build_first_10_opportunities_delivery(intake: dict[str, Any]) -> dict[str, Any]:
+ """Service-specific delivery for First 10 Opportunities Sprint."""
+ return {
+ "service_id": "first_10_opportunities_sprint",
+ "intake_received": bool(intake),
+ "delivery_steps_ar": [
+ "تشغيل account_finder على (sector, city) + offer.",
+ "buyer_role_mapper لكل شركة → 1 DM + 2 influencers.",
+ "explain_why_now لكل شركة (Arabic).",
+ "draft_b2b_email و/أو draft_whatsapp_message حسب القناة.",
+ "safety_eval + saudi_tone_eval على كل رسالة قبل التسليم.",
+ "بناء follow-up sequence لـ7 أيام.",
+ "Proof Pack v1 (PDF + JSON).",
+ ],
+ "deliverables": [
+ "10 opportunity cards",
+ "10 Arabic messages",
+ "follow-up plan",
+ "Proof Pack v1",
+ ],
+ "approval_required": True,
+ }
+
+
+def build_list_intelligence_delivery(intake: dict[str, Any]) -> dict[str, Any]:
+ """Service-specific delivery for List Intelligence."""
+ return {
+ "service_id": "list_intelligence",
+ "intake_received": bool(intake),
+ "delivery_steps_ar": [
+ "تنظيف الـ CSV + dedupe.",
+ "classify_source لكل صف.",
+ "evaluate_contactability + allowed_channels لكل contact.",
+ "تقسيم القائمة: safe / needs_review / blocked.",
+ "اختيار أفضل 50 target.",
+ "كتابة رسائل عربية للقطاع المهيمن.",
+ "Risk report + retention recommendation.",
+ ],
+ "deliverables": [
+ "Cleaned CSV",
+ "Top 50 targets",
+ "Arabic messages per segment",
+ "Risk report",
+ "Channel mix recommendation",
+ ],
+ "approval_required": True,
+ }
+
+
+def build_growth_diagnostic_delivery(intake: dict[str, Any]) -> dict[str, Any]:
+ """Free 24-hour growth diagnostic delivery."""
+ return {
+ "service_id": "free_growth_diagnostic",
+ "intake_received": bool(intake),
+ "delivery_steps_ar": [
+ "تشغيل recommend_accounts(sector, city) → 3 فرص.",
+ "كتابة رسالة عربية واحدة جاهزة.",
+ "تقرير risk سريع (واتساب opt-in / domain reputation / channel mix).",
+ "توصية بالخدمة المدفوعة الأنسب (Pilot 499 / Growth OS Pilot).",
+ ],
+ "deliverables": [
+ "3 opportunities",
+ "1 Arabic message",
+ "Risk note",
+ "Paid pilot recommendation",
+ ],
+ "delivery_time": "خلال 24 ساعة عمل",
+ "approval_required": True,
+ }
diff --git a/dealix/auto_client_acquisition/revenue_launch/pipeline_tracker.py b/dealix/auto_client_acquisition/revenue_launch/pipeline_tracker.py
new file mode 100644
index 00000000..0b4ec07e
--- /dev/null
+++ b/dealix/auto_client_acquisition/revenue_launch/pipeline_tracker.py
@@ -0,0 +1,155 @@
+"""Deterministic pipeline tracker — schema, add, update, summarize."""
+
+from __future__ import annotations
+
+from typing import Any
+
+PIPELINE_STAGES: tuple[str, ...] = (
+ "identified",
+ "contacted",
+ "replied",
+ "demo_booked",
+ "diagnostic_sent",
+ "pilot_offered",
+ "paid",
+ "lost",
+)
+
+# Default Sheet/CSV columns the pipeline tracker emits.
+PIPELINE_COLUMNS: tuple[str, ...] = (
+ "company", "person", "segment", "source", "channel",
+ "message_sent_at", "reply_status", "stage",
+ "demo_booked", "service_offered", "price_sar",
+ "paid", "next_step", "notes",
+)
+
+
+def build_pipeline_schema() -> dict[str, Any]:
+ """Return the canonical pipeline schema (deterministic)."""
+ return {
+ "stages": list(PIPELINE_STAGES),
+ "columns": list(PIPELINE_COLUMNS),
+ "stage_progression": [
+ {"from": "identified", "to": "contacted", "trigger": "outreach_sent"},
+ {"from": "contacted", "to": "replied", "trigger": "reply_received"},
+ {"from": "replied", "to": "demo_booked", "trigger": "demo_scheduled"},
+ {"from": "demo_booked", "to": "diagnostic_sent", "trigger": "diagnostic_delivered"},
+ {"from": "diagnostic_sent", "to": "pilot_offered", "trigger": "offer_sent"},
+ {"from": "pilot_offered", "to": "paid", "trigger": "moyasar_invoice_paid"},
+ ],
+ "loss_reasons_ar": [
+ "السعر",
+ "التوقيت",
+ "بديل قائم",
+ "صانع القرار غير متاح",
+ "PDPL/أمان",
+ "لا حاجة الآن",
+ ],
+ "notes_ar": (
+ "هذا المخطط deterministic. كل صفقة تتقدم بـ trigger صريح فقط، "
+ "ولا يحدث تغيير stage بدون event موثّق."
+ ),
+ }
+
+
+def add_prospect(
+ *,
+ pipeline: list[dict[str, Any]] | None = None,
+ company: str,
+ person: str = "",
+ segment: str = "",
+ source: str = "manual",
+ channel: str = "email",
+ notes: str = "",
+) -> dict[str, Any]:
+ """Add a new prospect to the in-memory pipeline. Stage starts at identified."""
+ entry: dict[str, Any] = {
+ "company": company,
+ "person": person,
+ "segment": segment,
+ "source": source,
+ "channel": channel,
+ "message_sent_at": None,
+ "reply_status": "none",
+ "stage": "identified",
+ "demo_booked": False,
+ "service_offered": "",
+ "price_sar": 0,
+ "paid": False,
+ "next_step": "send_first_outreach",
+ "notes": notes[:300],
+ }
+ if pipeline is not None:
+ pipeline.append(entry)
+ return entry
+
+
+def update_stage(
+ *,
+ prospect: dict[str, Any],
+ new_stage: str,
+ notes: str = "",
+) -> dict[str, Any]:
+ """Move a prospect to a new stage. Validates the new stage is known."""
+ if new_stage not in PIPELINE_STAGES:
+ raise ValueError(
+ f"Unknown stage: {new_stage}. "
+ f"Valid: {', '.join(PIPELINE_STAGES)}"
+ )
+ prospect["stage"] = new_stage
+ if notes:
+ existing = str(prospect.get("notes", ""))
+ sep = " | " if existing else ""
+ prospect["notes"] = (existing + sep + notes)[:300]
+ if new_stage == "paid":
+ prospect["paid"] = True
+ prospect["next_step"] = "deliver_24h"
+ elif new_stage == "lost":
+ prospect["next_step"] = "archive"
+ return prospect
+
+
+def summarize_pipeline(
+ pipeline: list[dict[str, Any]] | None,
+) -> dict[str, Any]:
+ """Aggregate pipeline counts + revenue."""
+ pipeline = pipeline or []
+ by_stage: dict[str, int] = {s: 0 for s in PIPELINE_STAGES}
+ by_segment: dict[str, int] = {}
+ revenue_paid_sar = 0.0
+ revenue_offered_sar = 0.0
+
+ for p in pipeline:
+ stage = str(p.get("stage", "identified"))
+ if stage in by_stage:
+ by_stage[stage] += 1
+ seg = str(p.get("segment", "unknown"))
+ by_segment[seg] = by_segment.get(seg, 0) + 1
+ price = float(p.get("price_sar", 0) or 0)
+ if p.get("paid"):
+ revenue_paid_sar += price
+ if stage in ("pilot_offered", "paid"):
+ revenue_offered_sar += price
+
+ total = len(pipeline)
+ won = by_stage["paid"]
+ lost = by_stage["lost"]
+ closed = won + lost
+ win_rate = round(won / closed, 3) if closed else 0.0
+
+ return {
+ "total_prospects": total,
+ "by_stage": by_stage,
+ "by_segment": by_segment,
+ "revenue_paid_sar": round(revenue_paid_sar, 2),
+ "revenue_offered_sar": round(revenue_offered_sar, 2),
+ "win_rate": win_rate,
+ "summary_ar": [
+ f"إجمالي الـ prospects: {total}",
+ f"اتصالات: {by_stage['contacted']} | ردود: {by_stage['replied']}",
+ f"ديموهات: {by_stage['demo_booked']} | عروض: {by_stage['pilot_offered']}",
+ f"مدفوعة: {by_stage['paid']} | خسرت: {by_stage['lost']}",
+ f"إيراد محصّل: {revenue_paid_sar:.0f} ريال",
+ f"win rate: {win_rate * 100:.1f}%",
+ ],
+ }
diff --git a/dealix/auto_client_acquisition/revenue_launch/proof_pack_template.py b/dealix/auto_client_acquisition/revenue_launch/proof_pack_template.py
new file mode 100644
index 00000000..55c2b02f
--- /dev/null
+++ b/dealix/auto_client_acquisition/revenue_launch/proof_pack_template.py
@@ -0,0 +1,100 @@
+"""Proof Pack template — client-facing summary at end of Pilot."""
+
+from __future__ import annotations
+
+from typing import Any
+
+
+def build_private_beta_proof_pack(
+ *,
+ company_name: str = "",
+ metrics: dict[str, Any] | None = None,
+) -> dict[str, Any]:
+ """Build the private-beta Proof Pack template (Arabic)."""
+ metrics = metrics or {}
+ return {
+ "title_ar": f"Proof Pack — {company_name or 'Pilot 7 أيام'}",
+ "sections_ar": [
+ "ملخص تنفيذي (5 أسطر)",
+ "ما عمله Dealix هذا الأسبوع",
+ "النتائج بالأرقام (vs أهداف الأسبوع)",
+ "أبرز الردود والاعتراضات",
+ "المخاطر التي تم منعها (PDPL/سمعة القناة)",
+ "أفضل 3 رسائل (مع safety+tone scores)",
+ "Action Ledger (كل فعل + مَن اعتمده)",
+ "التوصية بالخطوة التالية",
+ ],
+ "metrics_to_include": [
+ "opportunities_generated",
+ "drafts_approved",
+ "positive_replies",
+ "meetings_drafted",
+ "pipeline_influenced_sar",
+ "risks_blocked",
+ "time_saved_hours",
+ ],
+ "captured_metrics": metrics,
+ "approval_required": True,
+ "delivery_format": ["pdf", "json", "whatsapp_summary"],
+ }
+
+
+def build_client_summary(
+ *,
+ company_name: str = "",
+ opportunities_count: int = 0,
+ approved_drafts: int = 0,
+ meetings: int = 0,
+ pipeline_sar: float = 0.0,
+ risks_blocked: int = 0,
+) -> dict[str, Any]:
+ """5-line Arabic executive summary for the client."""
+ lines = [
+ f"خلال 7 أيام، شغّل Dealix Pilot لشركة {company_name or '(العميل)'}.",
+ f"تم توليد {opportunities_count} فرصة B2B + اعتماد {approved_drafts} رسالة.",
+ f"نتج عن ذلك {meetings} اجتماع و pipeline متأثر بقيمة {pipeline_sar:.0f} ريال.",
+ f"تم منع {risks_blocked} مخاطر تواصل تلقائياً (PDPL/cold WhatsApp/سمعة).",
+ "التوصية: الترقية لـ Growth OS Pilot 30 يوم لتثبيت العائد المتكرر.",
+ ]
+ return {
+ "company_name": company_name,
+ "summary_ar": lines,
+ "approval_required": True,
+ "deliverable_format": "5_line_executive_summary",
+ }
+
+
+def build_next_step_recommendation(
+ *,
+ pilot_metrics: dict[str, Any] | None = None,
+) -> dict[str, Any]:
+ """Recommend next step based on pilot outcome metrics."""
+ m = pilot_metrics or {}
+ pipeline_sar = float(m.get("pipeline_sar", 0))
+ meetings = int(m.get("meetings", 0))
+ csat = int(m.get("csat", 0)) # 0..10
+
+ if csat >= 8 and (pipeline_sar >= 25_000 or meetings >= 2):
+ action = "upsell_growth_os_monthly"
+ msg = (
+ "Pilot قوي — اعرض Growth OS Monthly بـ2,999 ريال شهرياً مع "
+ "خصم 15% على الاشتراك السنوي."
+ )
+ elif pipeline_sar < 5_000 and meetings == 0:
+ action = "iterate_or_archive"
+ msg = (
+ "النتائج ضعيفة هذه الجولة. اقترح زاوية مختلفة (قطاع/عرض) "
+ "أو أرشف العميل بدون ضغط."
+ )
+ else:
+ action = "extend_pilot"
+ msg = (
+ "Pilot واعد. مدّد الأسبوع لأسبوعين بـ500 ريال إضافي، "
+ "أو أضف قناة (Email + LinkedIn Lead Form)."
+ )
+
+ return {
+ "next_action": action,
+ "recommendation_ar": msg,
+ "approval_required": True,
+ }
diff --git a/dealix/auto_client_acquisition/service_tower/__init__.py b/dealix/auto_client_acquisition/service_tower/__init__.py
index 3c2117bc..eb210771 100644
--- a/dealix/auto_client_acquisition/service_tower/__init__.py
+++ b/dealix/auto_client_acquisition/service_tower/__init__.py
@@ -6,12 +6,22 @@
from __future__ import annotations
+from .contract_templates import (
+ draft_sla_outline,
+ list_contract_templates,
+)
from .deliverables import (
build_client_report_outline,
build_deliverables,
build_internal_operator_checklist,
build_proof_pack_template,
)
+from .vertical_service_map import (
+ VERTICALS_AR,
+ list_verticals,
+ map_industry_to_vertical,
+ recommend_services_for_vertical,
+)
from .mission_templates import (
build_service_workflow,
get_default_mission_steps,
@@ -79,4 +89,9 @@ __all__ = [
# upgrade_paths
"build_upsell_message_ar", "map_service_to_subscription",
"recommend_upgrade",
+ # contract_templates
+ "draft_sla_outline", "list_contract_templates",
+ # vertical_service_map
+ "VERTICALS_AR", "list_verticals", "map_industry_to_vertical",
+ "recommend_services_for_vertical",
]
diff --git a/dealix/auto_client_acquisition/service_tower/contract_templates.py b/dealix/auto_client_acquisition/service_tower/contract_templates.py
new file mode 100644
index 00000000..519d9e7a
--- /dev/null
+++ b/dealix/auto_client_acquisition/service_tower/contract_templates.py
@@ -0,0 +1,68 @@
+"""Service-tier contract templates — re-export from targeting_os and add SLA."""
+
+from __future__ import annotations
+
+from typing import Any
+
+from auto_client_acquisition.targeting_os.contract_drafts import (
+ draft_agency_partner_outline,
+ draft_dpa_outline,
+ draft_pilot_agreement_outline,
+ draft_referral_agreement_outline,
+ draft_scope_of_work,
+)
+
+
+def list_contract_templates() -> dict[str, Any]:
+ """List all contract templates available to the Service Tower."""
+ return {
+ "templates": [
+ {"id": "pilot_agreement", **draft_pilot_agreement_outline()},
+ {"id": "dpa", **draft_dpa_outline()},
+ {"id": "referral", **draft_referral_agreement_outline()},
+ {"id": "agency_partner", **draft_agency_partner_outline()},
+ {"id": "sow", **draft_scope_of_work()},
+ {"id": "sla", **draft_sla_outline()},
+ ],
+ "approval_required": True,
+ "legal_review_required": True,
+ "not_legal_advice": True,
+ }
+
+
+def draft_sla_outline() -> dict[str, Any]:
+ """Service Level Agreement outline for paid pilots and Growth OS Monthly."""
+ return {
+ "title_ar": "اتفاقية مستوى الخدمة (SLA)",
+ "sections_ar": [
+ "نطاق الخدمة (الـ Pilot أو Growth OS).",
+ "أوقات الاستجابة (intake خلال 30 دقيقة، diagnostic خلال 24 ساعة).",
+ "أوقات التسليم لكل deliverable.",
+ "حدود التوفر (أيام العمل، Time Zone).",
+ "المسارات في حالة التأخير (escalation).",
+ "حقوق العميل عند عدم الالتزام (refund / extension).",
+ "حدود المسؤولية.",
+ "السرية.",
+ "PDPL والاحتفاظ بالبيانات.",
+ "التغييرات في النطاق.",
+ "إنهاء الاتفاقية.",
+ ],
+ "approval_required": True,
+ "legal_review_required": True,
+ "not_legal_advice": True,
+ "disclaimer_ar": (
+ "هذه مسودة هيكلية فقط، ليست استشارة قانونية. "
+ "لا تُوقَّع قبل مراجعة محامٍ مرخّص في المملكة العربية السعودية."
+ ),
+ }
+
+
+__all__ = [
+ "draft_agency_partner_outline",
+ "draft_dpa_outline",
+ "draft_pilot_agreement_outline",
+ "draft_referral_agreement_outline",
+ "draft_scope_of_work",
+ "draft_sla_outline",
+ "list_contract_templates",
+]
diff --git a/dealix/auto_client_acquisition/service_tower/vertical_service_map.py b/dealix/auto_client_acquisition/service_tower/vertical_service_map.py
new file mode 100644
index 00000000..5bd67048
--- /dev/null
+++ b/dealix/auto_client_acquisition/service_tower/vertical_service_map.py
@@ -0,0 +1,168 @@
+"""Vertical service map — which services to recommend per industry vertical."""
+
+from __future__ import annotations
+
+from typing import Any
+
+# 6 verticals × recommended service stack.
+VERTICALS_AR: dict[str, dict[str, Any]] = {
+ "b2b_saas": {
+ "label_ar": "B2B SaaS",
+ "primary_services": [
+ "first_10_opportunities_sprint",
+ "linkedin_lead_gen_setup",
+ "growth_os_monthly",
+ ],
+ "supporting_services": [
+ "meeting_booking_sprint",
+ "executive_growth_brief",
+ ],
+ "buyer_roles": ["founder_ceo", "head_of_sales", "growth_manager"],
+ "common_pains_ar": [
+ "Pipeline ضعيف عند الإطلاق",
+ "صعوبة الوصول لـ decision makers في المؤسسات",
+ "Cold outreach يضرّ سمعة الـ domain",
+ ],
+ "winning_offer_ar": "Pilot 7 أيام يثبت Saudi Tone + LinkedIn Lead Forms.",
+ },
+ "agencies": {
+ "label_ar": "الوكالات (تسويق/مبيعات/CRM)",
+ "primary_services": [
+ "agency_partner_program",
+ "partner_sprint",
+ ],
+ "supporting_services": [
+ "list_intelligence",
+ "first_10_opportunities_sprint",
+ ],
+ "buyer_roles": ["agency_owner", "head_of_sales", "growth_manager"],
+ "common_pains_ar": [
+ "تسليم نتائج قابلة للقياس للعملاء",
+ "Proof Packs للعملاء بدون فريق نمو داخلي",
+ "خلق revenue stream متكرر",
+ ],
+ "winning_offer_ar": "Agency Partner Program مع co-branded Proof Pack.",
+ },
+ "training_consulting": {
+ "label_ar": "التدريب والاستشارات",
+ "primary_services": [
+ "first_10_opportunities_sprint",
+ "list_intelligence",
+ "growth_os_monthly",
+ ],
+ "supporting_services": [
+ "executive_growth_brief",
+ "meeting_booking_sprint",
+ ],
+ "buyer_roles": ["founder_ceo", "head_of_sales", "hr_manager"],
+ "common_pains_ar": [
+ "اعتماد مفرط على العلاقات الشخصية",
+ "Pipeline متذبذب بين الفصول الدراسية/الـ quarters",
+ "صعوبة الوصول لمدراء HR في الشركات",
+ ],
+ "winning_offer_ar": "First 10 Opportunities Sprint للوصول لـHR managers.",
+ },
+ "real_estate": {
+ "label_ar": "العقار",
+ "primary_services": [
+ "list_intelligence",
+ "whatsapp_compliance_setup",
+ "first_10_opportunities_sprint",
+ ],
+ "supporting_services": [
+ "meeting_booking_sprint",
+ "growth_os_monthly",
+ ],
+ "buyer_roles": ["founder_ceo", "head_of_sales", "branch_manager"],
+ "common_pains_ar": [
+ "قاعدة عملاء واتساب غير منظمة",
+ "خطر حظر رقم واتساب من الإفراط",
+ "leads تأتي بدون مصدر واضح",
+ ],
+ "winning_offer_ar": "List Intelligence + WhatsApp Compliance Setup.",
+ },
+ "healthcare_local": {
+ "label_ar": "العيادات والخدمات المحلية",
+ "primary_services": [
+ "local_growth_os",
+ "whatsapp_compliance_setup",
+ "list_intelligence",
+ ],
+ "supporting_services": [
+ "growth_os_monthly",
+ ],
+ "buyer_roles": ["clinic_manager", "founder_ceo", "operations_manager"],
+ "common_pains_ar": [
+ "Reviews سلبية على Google Business",
+ "no-show عالي بدون متابعة",
+ "Reactivation للعملاء القدامى",
+ ],
+ "winning_offer_ar": "Local Growth OS لإدارة Reviews + WhatsApp inbound.",
+ },
+ "retail_ecommerce": {
+ "label_ar": "التجزئة والـ E-commerce",
+ "primary_services": [
+ "list_intelligence",
+ "whatsapp_compliance_setup",
+ "local_growth_os",
+ ],
+ "supporting_services": [
+ "growth_os_monthly",
+ "executive_growth_brief",
+ ],
+ "buyer_roles": ["founder_ceo", "store_manager", "marketing_manager"],
+ "common_pains_ar": [
+ "Customer reactivation متعب يدوياً",
+ "Reviews + reputation متفرقة",
+ "Payment link sharing غير منظم",
+ ],
+ "winning_offer_ar": "List Intelligence + Local Growth OS + Moyasar invoice flow.",
+ },
+}
+
+
+def list_verticals() -> dict[str, Any]:
+ """Return all verticals with their full service stacks."""
+ return {
+ "total": len(VERTICALS_AR),
+ "verticals": [
+ {"id": vid, **vdata} for vid, vdata in VERTICALS_AR.items()
+ ],
+ }
+
+
+def recommend_services_for_vertical(vertical_id: str) -> dict[str, Any]:
+ """Recommend the service stack for a given vertical."""
+ v = VERTICALS_AR.get(vertical_id)
+ if v is None:
+ return {
+ "error": f"unknown vertical: {vertical_id}",
+ "available_verticals": list(VERTICALS_AR.keys()),
+ }
+ return {
+ "vertical_id": vertical_id,
+ "label_ar": v["label_ar"],
+ "primary_services": list(v["primary_services"]),
+ "supporting_services": list(v["supporting_services"]),
+ "buyer_roles": list(v["buyer_roles"]),
+ "common_pains_ar": list(v["common_pains_ar"]),
+ "winning_offer_ar": v["winning_offer_ar"],
+ }
+
+
+def map_industry_to_vertical(industry: str) -> str:
+ """Best-effort mapping from a free-text industry → known vertical_id."""
+ s = (industry or "").lower().strip()
+ if any(k in s for k in ("saas", "software", "tech", "تقنية", "برمجيات")):
+ return "b2b_saas"
+ if any(k in s for k in ("agency", "وكالة", "marketing", "تسويق")):
+ return "agencies"
+ if any(k in s for k in ("training", "تدريب", "consult", "استشار")):
+ return "training_consulting"
+ if any(k in s for k in ("real estate", "عقار", "property", "broker")):
+ return "real_estate"
+ if any(k in s for k in ("clinic", "عيادة", "doctor", "health", "medical")):
+ return "healthcare_local"
+ if any(k in s for k in ("retail", "store", "متجر", "shop", "ecommerce", "تجزئة")):
+ return "retail_ecommerce"
+ return "b2b_saas" # safe default
diff --git a/dealix/docs/DEALIX_100_PERCENT_LAUNCH_PLAN.md b/dealix/docs/DEALIX_100_PERCENT_LAUNCH_PLAN.md
index 625e46ab..9a3212ea 100644
--- a/dealix/docs/DEALIX_100_PERCENT_LAUNCH_PLAN.md
+++ b/dealix/docs/DEALIX_100_PERCENT_LAUNCH_PLAN.md
@@ -277,6 +277,44 @@ OAuth Gmail/Calendar، حصص، سياسات.
- `landing/first-10-opportunities.html` — Kill Feature.
- `landing/agency-partner.html` — برنامج الوكالة الشريكة.
- `landing/private-beta.html` — Private Beta launch.
+- `landing/list-intelligence.html` — تحليل القوائم.
+- `landing/growth-os.html` — اشتراك Growth OS الشهري.
+
+## 40. Launch Ops — برج إطلاق الـ Private Beta
+
+5 modules + 11 endpoints + 25 اختبار. كل ما يحتاجه إطلاق Private Beta اليوم:
+
+- `private_beta`: عرض اليوم (499 ريال × 7 أيام) + safety notes + FAQ عربي.
+- `demo_flow`: 12-min Arabic demo + discovery Qs + objection bank + close script.
+- `outreach_messages`: 4 segments × 5 prospects + per-segment رسائل + 3 follow-ups + 6 reply handlers.
+- `go_no_go`: 10-gate readiness + critical gates (no_secrets / live_sends_disabled / staging_health) + verdict + concrete next-actions.
+- `launch_scorecard`: daily/weekly metrics بـ11 event types + targets (20 outreach/5 ردود/3 ديمو/1 pilot يومياً).
+
+**Endpoints:** `/api/v1/launch/{private-beta/offer, demo/flow, outreach/first-20, outreach/message, outreach/followup, go-no-go, readiness, scorecard/event, scorecard/daily, scorecard/weekly, scorecard/demo}`.
+
+## 41. Revenue Launch — تحويل Dealix إلى دخل
+
+7 modules + 18 endpoints + 31 اختبار. **التفصيل:** [`REVENUE_TODAY_PLAYBOOK.md`](REVENUE_TODAY_PLAYBOOK.md).
+
+- `offer_builder`: 4 عروض (Private Beta / 499 Pilot / Growth OS Pilot / Free Case Study) + recommend per segment.
+- `pipeline_tracker`: 8 stages (identified→contacted→replied→demo_booked→diagnostic_sent→pilot_offered→paid/lost) + Sheet schema + summarize.
+- `outreach_sequence`: re-export with revenue-tier extensions.
+- `demo_closer`: re-export single source of truth.
+- `pilot_delivery`: 24-hour delivery template + intake form (12 fields) + per-service delivery (First 10 / List Intel / Free Diagnostic).
+- `proof_pack_template`: 5-line client summary + ROI x-multiples + next-step recommendation (upsell / iterate / extend).
+- `payment_manual_flow`: Moyasar invoice instructions (halalas-correct) + payment-link message + confirmation checklist. **No API charge ever**.
+
+**Endpoints:** `/api/v1/revenue-launch/{offers, offers/recommend, outreach/first-20, outreach/followup, demo-flow, pipeline/schema, pipeline/summarize, pilot-delivery/intake-form, pilot-delivery/24h-plan, pilot-delivery/first-10, pilot-delivery/list-intelligence, pilot-delivery/free-diagnostic, payment/invoice-instructions, payment/link-message, payment/confirmation-checklist, proof-pack/template, proof-pack/client-summary, proof-pack/next-step}`.
+
+## 42. Service Tower extensions
+
+- `contract_templates.py` — re-export targeting_os contracts + new SLA outline.
+- `vertical_service_map.py` — 6 verticals (B2B SaaS, agencies, training/consulting, real estate, healthcare/local, retail/ecommerce) → recommended service stack + buyer roles + common pains.
+
+## 43. Scripts
+
+- `scripts/launch_readiness_check.py` — runs 10 gates locally + against optional staging URL; reports JSON or pretty output.
+- `scripts/smoke_staging.py` — already exists (preserved).
---
diff --git a/dealix/docs/REVENUE_TODAY_PLAYBOOK.md b/dealix/docs/REVENUE_TODAY_PLAYBOOK.md
new file mode 100644
index 00000000..646a7fd5
--- /dev/null
+++ b/dealix/docs/REVENUE_TODAY_PLAYBOOK.md
@@ -0,0 +1,202 @@
+# Revenue Today Playbook — تحويل Dealix إلى دخل اليوم
+
+> **القاعدة:** الهدف اليوم ليس إطلاق عام. الهدف **أول 499 ريال أو commitment** عبر Private Beta + Pilot 7 أيام.
+
+---
+
+## 1. العروض المدفوعة المتاحة اليوم
+
+### Pilot 7 أيام — 499 ريال (الأساسي)
+- 10 فرص B2B + رسائل عربية + خطة متابعة + Proof Pack.
+- بدائل: مجاني مقابل case study.
+- مدة التسليم: 7 أيام، تبدأ يوم الأحد بعد الدفع.
+
+### Growth OS Pilot — 1,500–3,000 ريال (30 يوم)
+- التشغيل الكامل لشهر: command feed + drafts + اجتماعات + Proof Pack أسبوعي.
+- الترقية المنطقية لـ Growth OS Monthly (2,999/شهر).
+
+### Free Growth Diagnostic — 0 ريال (24 ساعة)
+- 3 فرص + رسالة + توصية بخدمة مدفوعة.
+- يقود لـ Pilot 499 أو Growth OS Pilot.
+
+---
+
+## 2. من نستهدف اليوم (4 فئات × 5 = 20 prospect)
+
+| Segment | عدد | عرض أساسي | عرض احتياطي | قناة |
+|---------|----:|-----------|-------------|------|
+| وكالات تسويق B2B | 5 | Growth OS Pilot | Free Case Study | Email |
+| تدريب/استشارات | 5 | Pilot 499 | Free Case Study | Email |
+| SaaS/تقنية صغيرة | 5 | Pilot 499 | Growth OS Pilot | LinkedIn Lead Form |
+| خدمات بقاعدة واتساب | 5 | List Intelligence | WhatsApp Compliance | Email |
+
+**القواعد:**
+- لا scraping ولا قوائم مشتراة.
+- استخدم علاقاتك المباشرة + جهات تعرفها.
+- كل رسالة يدوية، لا automation.
+- حد أقصى 3 follow-ups ثم أرشفة.
+
+---
+
+## 3. أول 20 رسالة — جاهزة للنسخ
+
+استخدم endpoint:
+```
+GET /api/v1/launch/outreach/first-20
+```
+
+أو يدوياً:
+
+### رسالة عامة
+هلا [الاسم]، أطلقنا Beta محدودة لـ Dealix.
+Dealix يساعد الشركات تطلع فرص B2B مناسبة، يكتب الرسائل بالعربي، ويخلي صانع القرار يوافق قبل أي تواصل، وبعدها يعطي Proof Pack.
+أفتح 5 مقاعد Pilot هذا الأسبوع. يناسبك أعطيك Free Diagnostic لشركتكم؟
+
+### وكالة
+هلا [الاسم]، عندي Beta خاص للوكالات.
+Dealix يطلع فرص لعملاءكم، يجهز رسائل عربية، يدير موافقات، ويطلع Proof Pack بعلامة الوكالة.
+أبحث عن وكالة واحدة نجرب معها Pilot مشترك على عميل حقيقي. يناسبك ديمو 15 دقيقة؟
+
+### SaaS
+هلا [الاسم]، رأيت إصدار النسخة الجديدة من منتجكم — مبروك.
+نشتغل على مدير نمو عربي يطلع 10 فرص B2B عبر LinkedIn Lead Forms (لا scraping) ويكتب الرسائل بالعربي.
+أبغى أجربه مع شركة SaaS سعودية واحدة. يناسبك ديمو 12 دقيقة؟
+
+---
+
+## 4. الديمو — 12 دقيقة
+
+استخدم:
+```
+GET /api/v1/launch/demo/flow
+```
+
+ملخص: 0–2 الفكرة → 2–4 Daily Brief → 4–6 10 فرص → 6–8 Trust + Approval → 8–10 الأمان → 10–12 العرض والـCTA.
+
+**الإغلاق:**
+> "تمام، نبدأ Pilot 7 أيام بـ499 ريال. أرسل لك خلال ساعة intake form + Moyasar invoice + موعد كيك-أوف."
+
+---
+
+## 5. Pipeline Tracker
+
+8 stages:
+```
+identified → contacted → replied → demo_booked →
+diagnostic_sent → pilot_offered → paid → (or lost)
+```
+
+استخدم:
+```
+GET /api/v1/revenue-launch/pipeline/schema
+POST /api/v1/revenue-launch/pipeline/summarize
+```
+
+أو افتح Sheet باسم `Dealix First 20 Pipeline` بالعمدة المعرّفة في الـ schema.
+
+---
+
+## 6. تسليم أول Pilot — خلال 24 ساعة
+
+بعد الدفع:
+1. **T+0h** — كيك-أوف + استلام intake.
+2. **T+1h** — Diagnosis (targeting + contactability).
+3. **T+6h** — Drafting (10 رسائل عربية + safety/tone evals).
+4. **T+18h** — Approval Pack (cards مع ≤3 أزرار).
+5. **T+24h** — Proof Pack v1 + جدولة جلسة المراجعة.
+
+استخدم:
+```
+GET /api/v1/revenue-launch/pilot-delivery/intake-form
+POST /api/v1/revenue-launch/pilot-delivery/24h-plan
+```
+
+---
+
+## 7. الدفع اليدوي عبر Moyasar
+
+**لا live charge من API.** فقط:
+- Moyasar Dashboard → Invoices → Create Invoice.
+- 499 ريال = 49,900 halalas.
+- وصف: "Dealix Private Beta Pilot — 7 days".
+- إرسال للعميل بالإيميل.
+
+استخدم:
+```
+POST /api/v1/revenue-launch/payment/invoice-instructions
+POST /api/v1/revenue-launch/payment/link-message
+GET /api/v1/revenue-launch/payment/confirmation-checklist
+```
+
+**قبل بدء التسليم:** تأكد invoice في حالة `paid` على Moyasar dashboard.
+
+---
+
+## 8. Proof Pack — في نهاية الأسبوع
+
+5 أسطر executive summary + 8 metrics + توصية بالخطوة التالية.
+
+استخدم:
+```
+POST /api/v1/revenue-launch/proof-pack/template
+POST /api/v1/revenue-launch/proof-pack/client-summary
+POST /api/v1/revenue-launch/proof-pack/next-step
+```
+
+---
+
+## 9. أهداف اليوم
+
+| Metric | Target |
+|--------|-------:|
+| Outreach sent | 20 |
+| Replies | 5 |
+| Demos booked | 3 |
+| Pilots paid | 1 |
+
+أهداف 7 أيام: 100 outreach / 20 ردود / 10 ديمو / 2 pilots مدفوعة.
+
+استخدم:
+```
+GET /api/v1/launch/scorecard/demo
+POST /api/v1/launch/scorecard/event
+POST /api/v1/launch/scorecard/daily
+POST /api/v1/launch/scorecard/weekly
+```
+
+---
+
+## 10. Go / No-Go اليوم
+
+10 بوابات (`POST /api/v1/launch/go-no-go` أو `python scripts/launch_readiness_check.py`):
+
+1. Tests passed.
+2. Routes check OK.
+3. No secrets in repo.
+4. Staging /health → 200.
+5. Supabase staging configured.
+6. Service catalog ≥4 services.
+7. landing/private-beta.html ready.
+8. First-20 prospects identified.
+9. WHATSAPP/GMAIL/CALENDAR/MOYASAR live=false.
+10. Moyasar invoice/payment-link manual flow ready.
+
+**Critical gates** (must pass): `no_secrets`, `live_sends_disabled`, `staging_health`. Otherwise: NO-GO.
+
+---
+
+## 11. ما لا تفعله اليوم
+
+- لا live WhatsApp/Gmail/Calendar/Moyasar من API.
+- لا scraping LinkedIn ولا auto-DM.
+- لا cold WhatsApp.
+- لا Public Launch / إعلان صحفي.
+- لا "نضمن نتائج".
+
+---
+
+## 12. الخطوة بعد أول Pilot
+
+- Proof Pack → Case Study → ترقية لـ Growth OS Monthly.
+- Case Study → استخدمه في الـ outreach التالي.
+- متابعة شهرية مع Service Excellence backlog (ما يحسّن الخدمة).
diff --git a/dealix/landing/growth-os.html b/dealix/landing/growth-os.html
new file mode 100644
index 00000000..f5d05d5d
--- /dev/null
+++ b/dealix/landing/growth-os.html
@@ -0,0 +1,120 @@
+
+
+
+
+
+Dealix Growth OS — اشتراك شهري
+
+
+
+
+
+
+
+ ماذا تستلم شهرياً؟
+
+ - Daily Command Feed عربي — 5 cards/day.
+ - First 10 Opportunities Sprint كل أسبوع.
+ - List Intelligence على قاعدة عملائك.
+ - Email + WhatsApp drafts (بدون live send بدون اعتماد).
+ - Calendar drafts + meeting briefs.
+ - Approval Center: CEO يوافق من واتساب.
+ - Proof Pack شهري + Founder Shadow Board أسبوعي.
+ - Reputation Guard على كل قناة.
+ - Service Excellence Score على كل campaign.
+ - Decision Memory يتعلم من Accept/Skip/Edit.
+
+
+
+
+ التسعير
+
+
+
Pilot 30 يوم
+
1,500–3,000 ريال
+
إعداد + شهر تجربة
+
+
+
Growth OS Monthly
+
2,999 ريال
+
شهري — بعد الإثبات
+
+
+
Annual (–15%)
+
30,589 ريال
+
دفع سنوي — توفير شهرين
+
+
+
+
+
+ التكاملات (Phase 1)
+
+ - Gmail (drafts فقط افتراضياً)
+ - Google Calendar (drafts فقط)
+ - WhatsApp Cloud (مع opt-in + approval)
+ - Moyasar (invoice/payment link manual)
+ - Google Sheets (read/append بموافقة)
+ - Website Forms (ingest)
+
+ المرحلة 2: LinkedIn Lead Forms، Google Business Profile، Google Meet transcripts.
+ المرحلة 3: Instagram, X (ingest only), social drafts.
+
+
+
+ الأمان والامتثال
+
+ - Approval-first في كل قناة — لا live send بدون اعتماد بشري.
+ - PDPL-aware: لا cold WhatsApp، DPA draft جاهز.
+ - Secret redactor + patch firewall + trace redactor.
+ - Saudi Tone + Safety eval قبل كل رسالة.
+ - Reputation Guard يوقف القناة عند تدهور السمعة.
+ - Action Ledger يسجّل كل فعل + من اعتمده.
+
+
+
+
+ كيف تبدأ؟
+
+ - Free Growth Diagnostic — 24 ساعة بدون التزام.
+ - Pilot 7 أيام بـ499 ريال — تثبت طريقة التشغيل.
+ - Growth OS Pilot 30 يوم — تشغيل شهر كامل.
+ - Growth OS Monthly — التزام مستمر مع Proof Pack شهري.
+
+
+
+
+
+
+
diff --git a/dealix/landing/list-intelligence.html b/dealix/landing/list-intelligence.html
new file mode 100644
index 00000000..d20bd35e
--- /dev/null
+++ b/dealix/landing/list-intelligence.html
@@ -0,0 +1,87 @@
+
+
+
+
+
+Dealix — تحليل القوائم (List Intelligence)
+
+
+
+
+
+
+
+ كيف تعمل؟
+
+ - ارفع CSV مع الحقول الأساسية (اسم/شركة/إيميل/هاتف/مصدر).
+ - Dealix ينظّف ويحذف التكرار.
+ - يصنّف كل صف حسب المصدر (CRM / inbound / event / cold list / opt-out).
+ - يحدد الـ contactability: safe / needs_review / blocked.
+ - يعطيك أفضل 50 هدف + القناة الأفضل لكل واحد.
+ - يكتب رسائل عربية للقطاع المهيمن.
+ - يعطي تقرير مخاطر تلقائي (PDPL + سمعة القناة).
+
+
+
+
+ المخرجات
+
+ - Cleaned CSV (مع علامة على كل صف: safe/review/blocked).
+ - Top 50 targets للأسبوع القادم.
+ - رسائل عربية لكل segment.
+ - Channel mix recommendation (إيميل / LinkedIn Lead Form / واتساب opt-in).
+ - Risk report — لماذا 8 صفوف blocked، ولماذا 30 يحتاجون مراجعة.
+
+ 499 – 1,500 ريال
+
+
+
+ ضمان Dealix:
+ نطبّق contactability قبل أي توصية بقناة. لا cold WhatsApp.
+ كل البيانات الحساسة محمية بـ secret_redactor قبل أي trace.
+ Retention: 6 أشهر افتراضياً، تُحذف عند الطلب.
+
+
+
+ للمن؟
+
+ - شركات لديها قاعدة عملاء قدامى لم تُحدَّث منذ 6+ أشهر.
+ - وكالات استلمت قوائم من عميل وتحتاج تنظيفاً.
+ - عيادات/متاجر/عقار — قاعدة واتساب مزدحمة بدون opt-in واضح.
+ - SaaS/تدريب — قائمة مؤتمر أو event يحتاجون تأهيلاً.
+
+
+
+
+
+
+
diff --git a/dealix/scripts/launch_readiness_check.py b/dealix/scripts/launch_readiness_check.py
new file mode 100644
index 00000000..4f79c019
--- /dev/null
+++ b/dealix/scripts/launch_readiness_check.py
@@ -0,0 +1,232 @@
+#!/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())
diff --git a/dealix/tests/unit/test_launch_ops.py b/dealix/tests/unit/test_launch_ops.py
new file mode 100644
index 00000000..3415eb3f
--- /dev/null
+++ b/dealix/tests/unit/test_launch_ops.py
@@ -0,0 +1,204 @@
+"""Unit tests for Launch Ops."""
+
+from __future__ import annotations
+
+import pytest
+
+from auto_client_acquisition.launch_ops import (
+ PRIVATE_BETA_OFFER,
+ build_12_min_demo_flow,
+ build_close_script,
+ build_daily_launch_scorecard,
+ build_discovery_questions,
+ build_first_20_segments,
+ build_followup_message,
+ build_launch_readiness,
+ build_objection_responses,
+ build_outreach_message,
+ build_private_beta_offer,
+ build_private_beta_safety_notes,
+ build_reply_handlers,
+ build_weekly_launch_scorecard,
+ decide_go_no_go,
+ private_beta_faq,
+ record_launch_event,
+)
+
+
+# ── Private Beta ─────────────────────────────────────────────
+def test_private_beta_offer_has_essentials():
+ o = build_private_beta_offer()
+ assert o["price_sar"] == 499
+ assert o["duration_days"] == 7
+ assert o["live_send_allowed"] is False
+ assert o["approval_required"] is True
+ assert len(o["deliverables_ar"]) >= 4
+
+
+def test_private_beta_offer_seats_override():
+ o = build_private_beta_offer(seats_remaining=2)
+ assert o["seats_available"] == 2
+
+
+def test_private_beta_safety_notes_blocks_live():
+ s = build_private_beta_safety_notes()
+ text = " ".join(s["do_not_do_ar"])
+ assert "live" in text.lower() or "عشوائي" in text or "تلقائي" in text
+ assert any("PDPL" in line for line in s["do_not_do_ar"])
+
+
+def test_private_beta_faq_arabic():
+ faq = private_beta_faq()
+ assert len(faq) >= 4
+ for item in faq:
+ assert any("" <= ch <= "ۿ" for ch in item["q_ar"])
+ assert any("" <= ch <= "ۿ" for ch in item["a_ar"])
+
+
+# ── Demo Flow ────────────────────────────────────────────────
+def test_demo_flow_is_12_minutes():
+ f = build_12_min_demo_flow()
+ assert f["duration_minutes"] == 12
+ assert len(f["minute_by_minute_ar"]) == 6
+
+
+def test_demo_discovery_has_5_questions():
+ out = build_discovery_questions()
+ assert len(out) == 5
+
+
+def test_objection_responses_cover_essentials():
+ out = build_objection_responses()
+ for k in ("price", "timing", "trust", "complexity", "data_privacy"):
+ assert k in out
+
+
+def test_close_script_arabic():
+ out = build_close_script()
+ assert len(out["close_sequence_ar"]) >= 3
+ assert any("" <= ch <= "ۿ" for ch in out["close_template_ar"])
+
+
+# ── Outreach ─────────────────────────────────────────────────
+def test_first_20_has_4_segments_total_20():
+ out = build_first_20_segments()
+ assert out["total_targets"] == 20
+ assert len(out["segments"]) == 4
+ assert sum(s["count"] for s in out["segments"]) == 20
+
+
+def test_outreach_message_is_arabic_and_drafts_only():
+ out = build_outreach_message("agency_b2b", name="أحمد")
+ assert any("" <= ch <= "ۿ" for ch in out["body_ar"])
+ assert out["live_send_allowed"] is False
+
+
+def test_outreach_unknown_segment_falls_back():
+ out = build_outreach_message("totally_unknown", name="X")
+ assert out["body_ar"]
+
+
+def test_followup_step_2_different_from_1():
+ s1 = build_followup_message("training_consulting", step=1, name="X")
+ s2 = build_followup_message("training_consulting", step=2, name="X")
+ assert s1["body_ar"] != s2["body_ar"]
+
+
+def test_followup_step_3_archives():
+ s3 = build_followup_message("agency_b2b", step=3, name="X")
+ assert s3["kind"] == "followup_3_final"
+
+
+def test_reply_handlers_include_critical():
+ h = build_reply_handlers()
+ for k in ("interested", "needs_more_info", "price_objection",
+ "not_now", "no_thanks", "unsubscribe"):
+ assert k in h
+
+
+# ── Go / No-Go ───────────────────────────────────────────────
+def test_readiness_all_false_returns_zero_pct():
+ r = build_launch_readiness(statuses={})
+ assert r["passed_pct"] == 0.0
+ assert r["passed_gates"] == 0
+ assert len(r["blockers_ar"]) == r["total_gates"]
+
+
+def test_readiness_all_true_returns_full_pct():
+ statuses = {gate["id"]: True for gate in
+ __import__("auto_client_acquisition.launch_ops",
+ fromlist=["LAUNCH_GATES"]).go_no_go.LAUNCH_GATES}
+ r = build_launch_readiness(statuses=statuses)
+ assert r["passed_pct"] == 100.0
+ assert r["passed_gates"] == r["total_gates"]
+
+
+def test_go_no_go_blocks_when_no_secrets_fails():
+ decision = decide_go_no_go(statuses={"tests_passed": True,
+ "routes_check": True,
+ "no_secrets": False,
+ "staging_health": True,
+ "live_sends_disabled": True})
+ assert decision["verdict"] == "no_go"
+
+
+def test_go_no_go_blocks_when_live_sends_enabled():
+ decision = decide_go_no_go(statuses={"tests_passed": True,
+ "routes_check": True,
+ "no_secrets": True,
+ "staging_health": True,
+ "live_sends_disabled": False})
+ assert decision["verdict"] == "no_go"
+
+
+def test_go_no_go_passes_with_critical_and_70pct():
+ statuses = {
+ "tests_passed": True, "routes_check": True, "no_secrets": True,
+ "staging_health": True, "supabase_staging": True,
+ "service_catalog": True, "private_beta_page": True,
+ "first_20_ready": True, "live_sends_disabled": True,
+ "payment_manual_ready": False, # 9/10 = 90%
+ }
+ decision = decide_go_no_go(statuses=statuses)
+ assert decision["verdict"] == "go"
+
+
+# ── Scorecard ────────────────────────────────────────────────
+def test_record_event_unknown_raises():
+ with pytest.raises(ValueError):
+ record_launch_event(event_type="totally_invalid")
+
+
+def test_record_event_appends_to_log():
+ log: list = []
+ record_launch_event(event_type="outreach_sent", event_log=log)
+ assert len(log) == 1
+ assert log[0]["event_type"] == "outreach_sent"
+
+
+def test_daily_scorecard_aggregates():
+ events = [{"event_type": "outreach_sent"}] * 12 + \
+ [{"event_type": "demo_booked"}] * 2
+ s = build_daily_launch_scorecard(events=events)
+ assert s["metrics"]["outreach_sent"] == 12
+ assert s["metrics"]["demo_booked"] == 2
+ assert s["progress"]["outreach_sent"]["pct"] == 60.0 # 12/20 = 60%
+
+
+def test_weekly_scorecard_returns_verdict():
+ events = [{"event_type": "outreach_sent"}] * 50 + \
+ [{"event_type": "pilot_paid"}] * 2
+ s = build_weekly_launch_scorecard(events=events)
+ assert s["verdict"] == "on_track"
+
+
+def test_weekly_scorecard_needs_focus_for_low_demos():
+ events = [{"event_type": "outreach_sent"}] * 5
+ s = build_weekly_launch_scorecard(events=events)
+ assert s["verdict"] == "needs_focus"
+
+
+# ── Constants exposed ────────────────────────────────────────
+def test_private_beta_offer_constant_exposed():
+ assert PRIVATE_BETA_OFFER["price_sar"] == 499
+ assert PRIVATE_BETA_OFFER["live_send_allowed"] is False
diff --git a/dealix/tests/unit/test_revenue_launch.py b/dealix/tests/unit/test_revenue_launch.py
new file mode 100644
index 00000000..28c6f4e1
--- /dev/null
+++ b/dealix/tests/unit/test_revenue_launch.py
@@ -0,0 +1,245 @@
+"""Unit tests for Revenue Launch."""
+
+from __future__ import annotations
+
+import pytest
+
+from auto_client_acquisition.revenue_launch import (
+ PIPELINE_STAGES,
+ add_prospect,
+ build_24h_delivery_plan,
+ build_499_pilot_offer,
+ build_case_study_free_offer,
+ build_client_intake_form,
+ build_client_summary,
+ build_first_10_opportunities_delivery,
+ build_first_20_segments_v2,
+ build_followup_1,
+ build_followup_2,
+ build_growth_diagnostic_delivery,
+ build_growth_os_pilot_offer,
+ build_list_intelligence_delivery,
+ build_moyasar_invoice_instructions,
+ build_next_step_recommendation,
+ build_outreach_message_v2,
+ build_payment_confirmation_checklist,
+ build_payment_link_message,
+ build_pipeline_schema,
+ build_private_beta_offer,
+ build_private_beta_proof_pack,
+ build_reply_handlers_v2,
+ recommend_offer_for_segment,
+ summarize_pipeline,
+ update_stage,
+)
+
+
+# ── Offers ───────────────────────────────────────────────────
+def test_499_pilot_has_correct_price():
+ o = build_499_pilot_offer()
+ assert o["price_sar"] == 499
+ assert o["live_send_allowed"] is False
+ assert o["no_live_charge"] is True
+
+
+def test_growth_os_pilot_30_days():
+ o = build_growth_os_pilot_offer()
+ assert o["duration_days"] == 30
+ assert o["price_sar_min"] == 1500
+ assert o["price_sar_max"] == 3000
+
+
+def test_case_study_free_requires_consent():
+ o = build_case_study_free_offer()
+ assert o["price_sar"] == 0
+ assert o["case_study_required"] is True
+
+
+def test_recommend_offer_for_agency():
+ out = recommend_offer_for_segment("agency_b2b")
+ assert out["primary_offer"] == "growth_os_pilot_30d"
+
+
+def test_recommend_offer_for_training():
+ out = recommend_offer_for_segment("training_consulting")
+ assert out["primary_offer"] == "pilot_499_7d"
+
+
+def test_recommend_offer_unknown_segment_default():
+ out = recommend_offer_for_segment("totally_unknown")
+ assert out["primary_offer"] == "pilot_499_7d"
+
+
+def test_private_beta_offer_re_export():
+ o = build_private_beta_offer()
+ assert o["price_sar"] == 499
+
+
+# ── Pipeline ─────────────────────────────────────────────────
+def test_pipeline_schema_has_8_stages():
+ s = build_pipeline_schema()
+ assert len(s["stages"]) == 8
+ assert "paid" in s["stages"]
+ assert "lost" in s["stages"]
+
+
+def test_add_prospect_starts_at_identified():
+ p = add_prospect(company="Acme", segment="saas_tech_small")
+ assert p["stage"] == "identified"
+ assert p["paid"] is False
+
+
+def test_update_stage_to_paid_marks_paid_true():
+ p = add_prospect(company="Acme")
+ update_stage(prospect=p, new_stage="paid", notes="Moyasar 499")
+ assert p["stage"] == "paid"
+ assert p["paid"] is True
+ assert "Moyasar" in str(p["notes"])
+
+
+def test_update_stage_invalid_raises():
+ p = add_prospect(company="Acme")
+ with pytest.raises(ValueError):
+ update_stage(prospect=p, new_stage="bogus_stage")
+
+
+def test_summarize_pipeline_counts_revenue():
+ pipeline = []
+ p1 = add_prospect(pipeline=pipeline, company="A", segment="agency_b2b")
+ p2 = add_prospect(pipeline=pipeline, company="B", segment="training")
+ p1["price_sar"] = 499
+ update_stage(prospect=p1, new_stage="paid")
+ update_stage(prospect=p2, new_stage="lost")
+ s = summarize_pipeline(pipeline)
+ assert s["total_prospects"] == 2
+ assert s["revenue_paid_sar"] == 499.0
+ assert s["by_stage"]["paid"] == 1
+ assert s["by_stage"]["lost"] == 1
+ assert s["win_rate"] == 0.5
+
+
+# ── Outreach ─────────────────────────────────────────────────
+def test_first_20_segments_v2():
+ out = build_first_20_segments_v2()
+ assert out["total_targets"] == 20
+
+
+def test_outreach_message_v2_arabic():
+ out = build_outreach_message_v2("agency_b2b")
+ assert any("" <= ch <= "ۿ" for ch in out["body_ar"])
+
+
+def test_followup_1_and_2_differ():
+ s1 = build_followup_1("training_consulting")
+ s2 = build_followup_2("training_consulting")
+ assert s1["body_ar"] != s2["body_ar"]
+
+
+def test_reply_handlers_v2_includes_unsubscribe():
+ h = build_reply_handlers_v2()
+ assert "unsubscribe" in h
+
+
+# ── Pilot delivery ───────────────────────────────────────────
+def test_intake_form_has_required_fields():
+ f = build_client_intake_form()
+ keys = {q["key"] for q in f["fields"]}
+ for required in ("company_name", "sector", "city", "primary_offer",
+ "approval_owner"):
+ assert required in keys
+
+
+def test_24h_delivery_plan_has_5_phases():
+ p = build_24h_delivery_plan("first_10_opportunities_sprint")
+ assert len(p["phases"]) == 5
+ assert p["live_send_allowed"] is False
+
+
+def test_first_10_delivery_has_proof():
+ out = build_first_10_opportunities_delivery({"sector": "training"})
+ assert "Proof Pack v1" in out["deliverables"]
+ assert out["approval_required"] is True
+
+
+def test_list_intelligence_delivery_includes_50_targets():
+ out = build_list_intelligence_delivery({"sector": "real_estate"})
+ assert any("50" in d for d in out["deliverables"])
+
+
+def test_growth_diagnostic_delivery_24h():
+ out = build_growth_diagnostic_delivery({"sector": "saas"})
+ assert "24" in out["delivery_time"] or "ساعة" in out["delivery_time"]
+
+
+# ── Payment manual flow ──────────────────────────────────────
+def test_invoice_instructions_correct_halalas():
+ out = build_moyasar_invoice_instructions(amount_sar=499)
+ assert out["amount_sar"] == 499
+ assert out["amount_halalas"] == 49900
+ assert out["no_live_charge"] is True
+
+
+def test_invoice_instructions_warns_no_card_storage():
+ out = build_moyasar_invoice_instructions(amount_sar=499)
+ text = " ".join(out["do_not_do_ar"])
+ assert "بطاقة" in text or "card" in text.lower()
+
+
+def test_payment_link_message_arabic_and_no_live_send():
+ out = build_payment_link_message(
+ customer_name="أحمد", invoice_url="https://example.com/inv/1",
+ )
+ assert any("" <= ch <= "ۿ" for ch in out["body_ar"])
+ assert out["live_send_allowed"] is False
+
+
+def test_payment_confirmation_checklist_blocks_premature_delivery():
+ out = build_payment_confirmation_checklist()
+ text = " ".join(out["do_not_do_ar"])
+ assert "paid" in text.lower() or "تأكيد" in text
+
+
+# ── Proof Pack ───────────────────────────────────────────────
+def test_proof_pack_template_has_metrics():
+ out = build_private_beta_proof_pack(company_name="Acme")
+ assert "opportunities_generated" in out["metrics_to_include"]
+ assert out["approval_required"] is True
+
+
+def test_client_summary_returns_5_lines():
+ out = build_client_summary(
+ company_name="Acme", opportunities_count=10,
+ approved_drafts=4, meetings=2, pipeline_sar=18000,
+ risks_blocked=3,
+ )
+ assert len(out["summary_ar"]) == 5
+ assert any("18000" in line or "18,000" in line or "18000" in str(line)
+ for line in out["summary_ar"])
+
+
+def test_next_step_upsell_for_strong_outcome():
+ out = build_next_step_recommendation(pilot_metrics={
+ "pipeline_sar": 30000, "meetings": 3, "csat": 9,
+ })
+ assert out["next_action"] == "upsell_growth_os_monthly"
+
+
+def test_next_step_iterate_for_weak_outcome():
+ out = build_next_step_recommendation(pilot_metrics={
+ "pipeline_sar": 1000, "meetings": 0, "csat": 5,
+ })
+ assert out["next_action"] == "iterate_or_archive"
+
+
+def test_next_step_extend_for_promising_outcome():
+ out = build_next_step_recommendation(pilot_metrics={
+ "pipeline_sar": 12000, "meetings": 1, "csat": 7,
+ })
+ assert out["next_action"] == "extend_pilot"
+
+
+# ── Constants ───────────────────────────────────────────────
+def test_pipeline_stages_constant_exposed():
+ assert "identified" in PIPELINE_STAGES
+ assert "paid" in PIPELINE_STAGES
+ assert "lost" in PIPELINE_STAGES