feat(launch+revenue): Private Beta Launch Ops + Revenue Launch — 14 modules + 29 endpoints + 56 tests + scripts/landing/docs

Launch Ops (5 modules) — برج إطلاق الـ Private Beta
- private_beta: 499 SAR × 7-day offer + safety notes + 6-question Arabic FAQ
- demo_flow: 12-minute minute-by-minute Arabic demo + 5 discovery Qs + 6 objection responses + close script
- outreach_messages: 4 segments × 5 prospects = 20 + per-segment Arabic messages + 3-step follow-ups + 6 reply handlers
- go_no_go: 10-gate readiness + 3 critical gates (no_secrets/live_sends_disabled/staging_health) + verdict + next-actions
- launch_scorecard: 11 event types + daily/weekly aggregation + targets (20 outreach/5 replies/3 demos/1 pilot daily)

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

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

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

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

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

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

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

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

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

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

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dealix Builder 2026-05-01 17:28:08 +03:00
parent e106a9a0d2
commit 84f1ad9620
27 changed files with 3195 additions and 0 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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",
]

View File

@ -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": [
"02: الفكرة الكبرى — Dealix ليس CRM ولا أداة واتساب.",
"24: Daily Brief / Command Feed — 3 قرارات + 3 فرص + 3 مخاطر.",
"46: 10 فرص في 10 دقائق — مثال حي.",
"68: Trust Score + Simulator + Approval Card.",
"810: الأمان والتكاملات — security_curator + connector_catalog.",
"1012: العرض والـ 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 فرص + رسالة + توصية، بدون التزام."
),
}

View File

@ -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

View File

@ -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,
}

View File

@ -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 بـ4991,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",
},
}

View File

@ -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 على عملائها. تواصل معنا مباشرة للترتيب."
),
},
]

View File

@ -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",
]

View File

@ -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",
]

View File

@ -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.",
}

View File

@ -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()

View File

@ -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,
}

View File

@ -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,
}

View File

@ -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}%",
],
}

View File

@ -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,
}

View File

@ -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",
]

View File

@ -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",
]

View File

@ -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

View File

@ -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).
---

View File

@ -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,5003,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
```
ملخص: 02 الفكرة → 24 Daily Brief → 46 10 فرص → 68 Trust + Approval → 810 الأمان → 1012 العرض والـ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 (ما يحسّن الخدمة).

View File

@ -0,0 +1,120 @@
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Dealix Growth OS — اشتراك شهري</title>
<style>
body { font-family: 'Tajawal', sans-serif; background: #0f1115; color: #f5f5f7;
line-height: 1.7; margin: 0; }
header { padding: 56px 24px; text-align: center;
background: linear-gradient(135deg, #1a1d27 0%, #0f1115 100%); }
header h1 { font-size: 44px; line-height: 1.2; margin-bottom: 14px; }
header h1 span { color: #ffd166; }
header p { color: #a8aab2; max-width: 720px; margin: 0 auto; font-size: 19px; }
main { max-width: 900px; margin: 0 auto; padding: 24px; }
section { background: #181b22; padding: 28px; border-radius: 14px;
margin: 18px 0; border: 1px solid #232631; }
section h2 { color: #ffd166; margin-bottom: 14px; font-size: 22px; }
ul li { padding: 6px 0; }
ul li:before { content: "▸"; color: #06d6a0; margin-left: 8px; }
.pricing { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 14px; margin-top: 14px; }
.price-card { padding: 18px; background: #1f232c; border-radius: 10px;
border: 1px solid #2a2e39; }
.price-card .label { color: #a8aab2; font-size: 14px; }
.price-card .price { font-size: 26px; font-weight: 700; color: #ffd166; margin: 6px 0; }
.cta { display: inline-block; padding: 14px 28px; background: #ffd166;
color: #0f1115; border-radius: 10px; font-weight: 700; text-decoration: none;
font-size: 17px; margin: 8px; }
</style>
</head>
<body>
<header>
<h1>Dealix <span>Growth OS</span></h1>
<p>منصة نمو شهرية تدير قنواتك الخارجية، تجمع كل الإشارات في Command Feed،
تكتب الرسائل، تطلب موافقات، ترتب اجتماعات، وتطلع Proof Pack شهري.</p>
</header>
<main>
<section>
<h2>ماذا تستلم شهرياً؟</h2>
<ul>
<li>Daily Command Feed عربي — 5 cards/day.</li>
<li>First 10 Opportunities Sprint كل أسبوع.</li>
<li>List Intelligence على قاعدة عملائك.</li>
<li>Email + WhatsApp drafts (بدون live send بدون اعتماد).</li>
<li>Calendar drafts + meeting briefs.</li>
<li>Approval Center: CEO يوافق من واتساب.</li>
<li>Proof Pack شهري + Founder Shadow Board أسبوعي.</li>
<li>Reputation Guard على كل قناة.</li>
<li>Service Excellence Score على كل campaign.</li>
<li>Decision Memory يتعلم من Accept/Skip/Edit.</li>
</ul>
</section>
<section>
<h2>التسعير</h2>
<div class="pricing">
<div class="price-card">
<div class="label">Pilot 30 يوم</div>
<div class="price">1,5003,000 ريال</div>
<div>إعداد + شهر تجربة</div>
</div>
<div class="price-card">
<div class="label">Growth OS Monthly</div>
<div class="price">2,999 ريال</div>
<div>شهري — بعد الإثبات</div>
</div>
<div class="price-card">
<div class="label">Annual (15%)</div>
<div class="price">30,589 ريال</div>
<div>دفع سنوي — توفير شهرين</div>
</div>
</div>
</section>
<section>
<h2>التكاملات (Phase 1)</h2>
<ul>
<li>Gmail (drafts فقط افتراضياً)</li>
<li>Google Calendar (drafts فقط)</li>
<li>WhatsApp Cloud (مع opt-in + approval)</li>
<li>Moyasar (invoice/payment link manual)</li>
<li>Google Sheets (read/append بموافقة)</li>
<li>Website Forms (ingest)</li>
</ul>
<p>المرحلة 2: LinkedIn Lead Forms، Google Business Profile، Google Meet transcripts.</p>
<p>المرحلة 3: Instagram, X (ingest only), social drafts.</p>
</section>
<section>
<h2>الأمان والامتثال</h2>
<ul>
<li>Approval-first في كل قناة — لا live send بدون اعتماد بشري.</li>
<li>PDPL-aware: لا cold WhatsApp، DPA draft جاهز.</li>
<li>Secret redactor + patch firewall + trace redactor.</li>
<li>Saudi Tone + Safety eval قبل كل رسالة.</li>
<li>Reputation Guard يوقف القناة عند تدهور السمعة.</li>
<li>Action Ledger يسجّل كل فعل + من اعتمده.</li>
</ul>
</section>
<section>
<h2>كيف تبدأ؟</h2>
<ol style="padding-right: 18px;">
<li>Free Growth Diagnostic — 24 ساعة بدون التزام.</li>
<li>Pilot 7 أيام بـ499 ريال — تثبت طريقة التشغيل.</li>
<li>Growth OS Pilot 30 يوم — تشغيل شهر كامل.</li>
<li>Growth OS Monthly — التزام مستمر مع Proof Pack شهري.</li>
</ol>
</section>
<div style="text-align: center; padding: 24px;">
<a href="free-diagnostic.html" class="cta">ابدأ بالتشخيص المجاني</a>
<a href="mailto:bassam.m.assiri@gmail.com?subject=Dealix%20Growth%20OS"
class="cta" style="background: transparent; color: #f5f5f7; border: 1px solid #383b44;">احجز ديمو</a>
</div>
</main>
</body>
</html>

View File

@ -0,0 +1,87 @@
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Dealix — تحليل القوائم (List Intelligence)</title>
<style>
body { font-family: 'Tajawal', sans-serif; background: #0f1115; color: #f5f5f7;
line-height: 1.7; margin: 0; }
header { padding: 56px 24px; text-align: center;
background: linear-gradient(135deg, #1a1d27 0%, #0f1115 100%); }
header h1 { font-size: 42px; line-height: 1.2; margin-bottom: 14px; }
header h1 span { color: #ffd166; }
header p { color: #a8aab2; max-width: 700px; margin: 0 auto; font-size: 18px; }
main { max-width: 800px; margin: 0 auto; padding: 24px; }
section { background: #181b22; padding: 28px; border-radius: 14px;
margin: 18px 0; border: 1px solid #232631; }
section h2 { color: #ffd166; margin-bottom: 14px; font-size: 22px; }
ul li { padding: 6px 0; }
ul li:before { content: "✓"; color: #06d6a0; margin-left: 8px; }
.cta { display: inline-block; padding: 14px 28px; background: #ffd166;
color: #0f1115; border-radius: 10px; font-weight: 700; text-decoration: none;
font-size: 17px; margin: 8px; }
.price { font-size: 28px; font-weight: 700; color: #ffd166; margin: 14px 0; }
.note { background: rgba(6, 214, 160, 0.1); padding: 14px; border-radius: 10px;
border: 1px solid rgba(6, 214, 160, 0.3); color: #06d6a0; }
</style>
</head>
<body>
<header>
<h1>تحليل قوائمكم — <span>List Intelligence</span></h1>
<p>ارفع قائمتك (CSV من العملاء، إيميلات، أرقام واتساب). نظف، صنف، وحدد أفضل
50 هدف + رسائل عربية + خطة 7 أيام — بدون أي إرسال.</p>
</header>
<main>
<section>
<h2>كيف تعمل؟</h2>
<ul>
<li>ارفع CSV مع الحقول الأساسية (اسم/شركة/إيميل/هاتف/مصدر).</li>
<li>Dealix ينظّف ويحذف التكرار.</li>
<li>يصنّف كل صف حسب المصدر (CRM / inbound / event / cold list / opt-out).</li>
<li>يحدد الـ contactability: safe / needs_review / blocked.</li>
<li>يعطيك أفضل 50 هدف + القناة الأفضل لكل واحد.</li>
<li>يكتب رسائل عربية للقطاع المهيمن.</li>
<li>يعطي تقرير مخاطر تلقائي (PDPL + سمعة القناة).</li>
</ul>
</section>
<section>
<h2>المخرجات</h2>
<ul>
<li>Cleaned CSV (مع علامة على كل صف: safe/review/blocked).</li>
<li>Top 50 targets للأسبوع القادم.</li>
<li>رسائل عربية لكل segment.</li>
<li>Channel mix recommendation (إيميل / LinkedIn Lead Form / واتساب opt-in).</li>
<li>Risk report — لماذا 8 صفوف blocked، ولماذا 30 يحتاجون مراجعة.</li>
</ul>
<div class="price">499 1,500 ريال</div>
</section>
<section class="note">
<strong>ضمان Dealix:</strong>
نطبّق contactability قبل أي توصية بقناة. لا cold WhatsApp.
كل البيانات الحساسة محمية بـ secret_redactor قبل أي trace.
Retention: 6 أشهر افتراضياً، تُحذف عند الطلب.
</section>
<section>
<h2>للمن؟</h2>
<ul>
<li>شركات لديها قاعدة عملاء قدامى لم تُحدَّث منذ 6+ أشهر.</li>
<li>وكالات استلمت قوائم من عميل وتحتاج تنظيفاً.</li>
<li>عيادات/متاجر/عقار — قاعدة واتساب مزدحمة بدون opt-in واضح.</li>
<li>SaaS/تدريب — قائمة مؤتمر أو event يحتاجون تأهيلاً.</li>
</ul>
</section>
<div style="text-align: center; padding: 24px;">
<a href="mailto:bassam.m.assiri@gmail.com?subject=Dealix%20List%20Intelligence"
class="cta">ابدأ تحليل قائمتك</a>
<a href="services.html" class="cta"
style="background: transparent; color: #f5f5f7; border: 1px solid #383b44;">شاهد كل الخدمات</a>
</div>
</main>
</body>
</html>

View File

@ -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())

View File

@ -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

View File

@ -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