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

Dealix Growth OS

+

منصة نمو شهرية تدير قنواتك الخارجية، تجمع كل الإشارات في Command Feed، + تكتب الرسائل، تطلب موافقات، ترتب اجتماعات، وتطلع Proof Pack شهري.

+
+ +
+
+

ماذا تستلم شهرياً؟

+
    +
  • Daily Command Feed عربي — 5 cards/day.
  • +
  • First 10 Opportunities Sprint كل أسبوع.
  • +
  • List Intelligence على قاعدة عملائك.
  • +
  • Email + WhatsApp drafts (بدون live send بدون اعتماد).
  • +
  • Calendar drafts + meeting briefs.
  • +
  • Approval Center: CEO يوافق من واتساب.
  • +
  • Proof Pack شهري + Founder Shadow Board أسبوعي.
  • +
  • Reputation Guard على كل قناة.
  • +
  • Service Excellence Score على كل campaign.
  • +
  • Decision Memory يتعلم من Accept/Skip/Edit.
  • +
+
+ +
+

التسعير

+
+
+
Pilot 30 يوم
+
1,500–3,000 ريال
+
إعداد + شهر تجربة
+
+
+
Growth OS Monthly
+
2,999 ريال
+
شهري — بعد الإثبات
+
+
+
Annual (–15%)
+
30,589 ريال
+
دفع سنوي — توفير شهرين
+
+
+
+ +
+

التكاملات (Phase 1)

+
    +
  • Gmail (drafts فقط افتراضياً)
  • +
  • Google Calendar (drafts فقط)
  • +
  • WhatsApp Cloud (مع opt-in + approval)
  • +
  • Moyasar (invoice/payment link manual)
  • +
  • Google Sheets (read/append بموافقة)
  • +
  • Website Forms (ingest)
  • +
+

المرحلة 2: LinkedIn Lead Forms، Google Business Profile، Google Meet transcripts.

+

المرحلة 3: Instagram, X (ingest only), social drafts.

+
+ +
+

الأمان والامتثال

+
    +
  • Approval-first في كل قناة — لا live send بدون اعتماد بشري.
  • +
  • PDPL-aware: لا cold WhatsApp، DPA draft جاهز.
  • +
  • Secret redactor + patch firewall + trace redactor.
  • +
  • Saudi Tone + Safety eval قبل كل رسالة.
  • +
  • Reputation Guard يوقف القناة عند تدهور السمعة.
  • +
  • Action Ledger يسجّل كل فعل + من اعتمده.
  • +
+
+ +
+

كيف تبدأ؟

+
    +
  1. Free Growth Diagnostic — 24 ساعة بدون التزام.
  2. +
  3. Pilot 7 أيام بـ499 ريال — تثبت طريقة التشغيل.
  4. +
  5. Growth OS Pilot 30 يوم — تشغيل شهر كامل.
  6. +
  7. Growth OS Monthly — التزام مستمر مع Proof Pack شهري.
  8. +
+
+ +
+ ابدأ بالتشخيص المجاني + احجز ديمو +
+
+ + diff --git a/dealix/landing/list-intelligence.html b/dealix/landing/list-intelligence.html new file mode 100644 index 00000000..d20bd35e --- /dev/null +++ b/dealix/landing/list-intelligence.html @@ -0,0 +1,87 @@ + + + + + +Dealix — تحليل القوائم (List Intelligence) + + + +
+

تحليل قوائمكم — List Intelligence

+

ارفع قائمتك (CSV من العملاء، إيميلات، أرقام واتساب). نظف، صنف، وحدد أفضل + 50 هدف + رسائل عربية + خطة 7 أيام — بدون أي إرسال.

+
+ +
+
+

كيف تعمل؟

+
    +
  • ارفع CSV مع الحقول الأساسية (اسم/شركة/إيميل/هاتف/مصدر).
  • +
  • Dealix ينظّف ويحذف التكرار.
  • +
  • يصنّف كل صف حسب المصدر (CRM / inbound / event / cold list / opt-out).
  • +
  • يحدد الـ contactability: safe / needs_review / blocked.
  • +
  • يعطيك أفضل 50 هدف + القناة الأفضل لكل واحد.
  • +
  • يكتب رسائل عربية للقطاع المهيمن.
  • +
  • يعطي تقرير مخاطر تلقائي (PDPL + سمعة القناة).
  • +
+
+ +
+

المخرجات

+
    +
  • Cleaned CSV (مع علامة على كل صف: safe/review/blocked).
  • +
  • Top 50 targets للأسبوع القادم.
  • +
  • رسائل عربية لكل segment.
  • +
  • Channel mix recommendation (إيميل / LinkedIn Lead Form / واتساب opt-in).
  • +
  • Risk report — لماذا 8 صفوف blocked، ولماذا 30 يحتاجون مراجعة.
  • +
+
499 – 1,500 ريال
+
+ +
+ ضمان Dealix: + نطبّق contactability قبل أي توصية بقناة. لا cold WhatsApp. + كل البيانات الحساسة محمية بـ secret_redactor قبل أي trace. + Retention: 6 أشهر افتراضياً، تُحذف عند الطلب. +
+ +
+

للمن؟

+
    +
  • شركات لديها قاعدة عملاء قدامى لم تُحدَّث منذ 6+ أشهر.
  • +
  • وكالات استلمت قوائم من عميل وتحتاج تنظيفاً.
  • +
  • عيادات/متاجر/عقار — قاعدة واتساب مزدحمة بدون opt-in واضح.
  • +
  • SaaS/تدريب — قائمة مؤتمر أو event يحتاجون تأهيلاً.
  • +
+
+ +
+ ابدأ تحليل قائمتك + شاهد كل الخدمات +
+
+ + diff --git a/dealix/scripts/launch_readiness_check.py b/dealix/scripts/launch_readiness_check.py new file mode 100644 index 00000000..4f79c019 --- /dev/null +++ b/dealix/scripts/launch_readiness_check.py @@ -0,0 +1,232 @@ +#!/usr/bin/env python3 +"""Dealix Launch Readiness — 10-gate Go/No-Go check. + +Runs locally + against an optional staging URL. Reports which gates pass/fail +and what the next concrete actions are. + +Usage: + python scripts/launch_readiness_check.py + python scripts/launch_readiness_check.py --staging-url https://staging.example +""" + +from __future__ import annotations + +import argparse +import json +import os +import re +import subprocess +import sys +import urllib.error +import urllib.request +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parent.parent + +SECRET_PATTERNS = ( + r"ghp_[A-Za-z0-9]{20,}", + r"github_pat_[A-Za-z0-9_]{20,}", + r"sk-[A-Za-z0-9]{30,}", + r"sk-ant-[A-Za-z0-9_\-]{20,}", + r"AKIA[A-Z0-9]{16}", + r"AIza[A-Za-z0-9_\-]{30,}", + r"EAA[A-Za-z0-9]{30,}", + r"-----BEGIN (?:RSA |EC |OPENSSH |)PRIVATE KEY-----", +) + +EXCLUDE_DIRS = (".git", ".venv", "node_modules", "__pycache__", ".pytest_cache") + + +def gate_tests_passed() -> tuple[bool, str]: + """Run pytest with --noconftest on the new layer tests as a quick proxy.""" + try: + result = subprocess.run( + [sys.executable, "-m", "pytest", + "tests/unit/test_launch_ops.py", + "tests/unit/test_revenue_launch.py", + "tests/unit/test_security_curator.py", + "--noconftest", "--no-cov", "-q", "-p", "no:cacheprovider"], + cwd=REPO_ROOT, capture_output=True, text=True, timeout=60, + ) + ok = result.returncode == 0 + last_line = (result.stdout or "").strip().splitlines()[-1:] + msg = last_line[0] if last_line else "no output" + return ok, msg + except Exception as exc: # noqa: BLE001 + return False, f"pytest error: {exc}" + + +def gate_routes_check() -> tuple[bool, str]: + """Run scripts/print_routes.py — should not raise.""" + try: + result = subprocess.run( + [sys.executable, "scripts/print_routes.py"], + cwd=REPO_ROOT, capture_output=True, text=True, timeout=30, + ) + if result.returncode == 0: + n_routes = result.stdout.count("/api/v1") + return True, f"{n_routes} v1 routes" + return False, f"exit={result.returncode}" + except Exception as exc: # noqa: BLE001 + return False, f"err: {exc}" + + +def gate_no_secrets() -> tuple[bool, str]: + """Scan repo for secret patterns. Skips known-safe directories.""" + findings: list[str] = [] + pat = re.compile("|".join(SECRET_PATTERNS)) + for path in REPO_ROOT.rglob("*"): + if not path.is_file(): + continue + if any(part in EXCLUDE_DIRS for part in path.parts): + continue + # Skip docs that intentionally mention patterns as examples. + if path.suffix in {".md", ".lock", ".pyc", ".png", ".jpg", ".jpeg", + ".gif", ".woff", ".woff2", ".ttf"}: + continue + try: + text = path.read_text(encoding="utf-8", errors="ignore") + except Exception: # noqa: BLE001 + continue + if pat.search(text): + findings.append(str(path.relative_to(REPO_ROOT))) + if len(findings) >= 3: + break + return (not findings), ( + "clean" if not findings else f"FOUND in: {', '.join(findings)}" + ) + + +def gate_staging_health(staging_url: str | None) -> tuple[bool, str]: + """Hit /health on staging if a URL is provided.""" + if not staging_url: + return False, "no --staging-url provided" + url = staging_url.rstrip("/") + "/health" + try: + req = urllib.request.Request(url, headers={"User-Agent": "Dealix/Readiness"}) + with urllib.request.urlopen(req, timeout=10) as resp: # nosec + return resp.status == 200, f"status={resp.status}" + except Exception as exc: # noqa: BLE001 + return False, f"err: {exc}" + + +def gate_supabase_staging() -> tuple[bool, str]: + """We can only check whether SUPABASE_URL is configured, not connectivity.""" + if os.getenv("SUPABASE_URL") and os.getenv("SUPABASE_SERVICE_ROLE_KEY"): + return True, "env vars configured" + return False, "SUPABASE_URL or SERVICE_ROLE_KEY not set in env" + + +def gate_service_catalog(staging_url: str | None) -> tuple[bool, str]: + if not staging_url: + return False, "no --staging-url provided" + url = staging_url.rstrip("/") + "/api/v1/services/catalog" + try: + req = urllib.request.Request(url, headers={"User-Agent": "Dealix/Readiness"}) + with urllib.request.urlopen(req, timeout=10) as resp: # nosec + data = json.loads(resp.read().decode("utf-8", errors="ignore")) + total = int(data.get("total", 0)) + return total >= 4, f"{total} services" + except Exception as exc: # noqa: BLE001 + return False, f"err: {exc}" + + +def gate_private_beta_page() -> tuple[bool, str]: + p = REPO_ROOT / "landing" / "private-beta.html" + if not p.exists(): + return False, "missing" + text = p.read_text(encoding="utf-8", errors="ignore") + has_cta = ("احجز" in text) or ("ابدأ" in text) or ("احصل" in text) + has_pilot = ("Pilot" in text) or ("بايلوت" in text) + return (has_cta and has_pilot), ( + "ok" if has_cta and has_pilot else "missing CTA or Pilot mention" + ) + + +def gate_first_20_ready() -> tuple[bool, str]: + """Soft check: a tracker doc/sheet may exist as evidence.""" + candidates = [ + REPO_ROOT / "docs" / "FIRST_20_OUTREACH_MESSAGES.md", + REPO_ROOT / "docs" / "REVENUE_TODAY_PLAYBOOK.md", + ] + found = [str(c.relative_to(REPO_ROOT)) for c in candidates if c.exists()] + return bool(found), ", ".join(found) or "no first-20 doc/sheet found" + + +def gate_live_sends_disabled() -> tuple[bool, str]: + """Verify env flags for live sends are NOT set to true.""" + flags = [ + "WHATSAPP_ALLOW_LIVE_SEND", + "GMAIL_ALLOW_LIVE_SEND", + "CALENDAR_ALLOW_LIVE_INSERT", + "MOYASAR_ALLOW_LIVE_CHARGE", + "GBP_ALLOW_LIVE_REPLY", + ] + enabled = [f for f in flags if os.getenv(f, "false").lower() == "true"] + return (not enabled), ( + "all disabled" if not enabled else f"ENABLED: {', '.join(enabled)}" + ) + + +def gate_payment_manual_ready() -> tuple[bool, str]: + """Soft check: payment-manual flow module is present + accessible.""" + p = REPO_ROOT / "auto_client_acquisition" / "revenue_launch" / "payment_manual_flow.py" + return p.exists(), ("module present" if p.exists() else "missing") + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--staging-url", default=None, + help="Optional staging URL for live checks") + parser.add_argument("--json", action="store_true", + help="Emit JSON instead of pretty output") + args = parser.parse_args() + + print("Dealix Launch Readiness — 10 Gates") + print("─" * 60) + + gates = [ + ("tests_passed", gate_tests_passed()), + ("routes_check", gate_routes_check()), + ("no_secrets", gate_no_secrets()), + ("staging_health", gate_staging_health(args.staging_url)), + ("supabase_staging", gate_supabase_staging()), + ("service_catalog", gate_service_catalog(args.staging_url)), + ("private_beta_page", gate_private_beta_page()), + ("first_20_ready", gate_first_20_ready()), + ("live_sends_disabled", gate_live_sends_disabled()), + ("payment_manual_ready", gate_payment_manual_ready()), + ] + + passed = sum(1 for _, (ok, _) in gates if ok) + total = len(gates) + pct = round(100 * passed / total, 1) + + if args.json: + out = { + "passed": passed, "total": total, "pct": pct, + "gates": [{"id": gid, "passed": ok, "info": info} + for gid, (ok, info) in gates], + } + print(json.dumps(out, ensure_ascii=False, indent=2)) + else: + for gid, (ok, info) in gates: + mark = "✅" if ok else "🔴" + print(f"{mark} {gid:<24} {info}") + print("─" * 60) + critical = ("no_secrets", "live_sends_disabled", "staging_health") + critical_failed = [gid for gid, (ok, _) in gates + if gid in critical and not ok] + if critical_failed: + verdict = f"🔴 NO-GO — critical gates failed: {', '.join(critical_failed)}" + elif pct >= 70: + verdict = f"✅ GO (Private Beta) — {passed}/{total} = {pct}%" + else: + verdict = f"🟡 FIX-THEN-GO — only {passed}/{total} = {pct}%" + print(verdict) + + return 0 if passed == total else (1 if passed < 7 else 0) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/dealix/tests/unit/test_launch_ops.py b/dealix/tests/unit/test_launch_ops.py new file mode 100644 index 00000000..3415eb3f --- /dev/null +++ b/dealix/tests/unit/test_launch_ops.py @@ -0,0 +1,204 @@ +"""Unit tests for Launch Ops.""" + +from __future__ import annotations + +import pytest + +from auto_client_acquisition.launch_ops import ( + PRIVATE_BETA_OFFER, + build_12_min_demo_flow, + build_close_script, + build_daily_launch_scorecard, + build_discovery_questions, + build_first_20_segments, + build_followup_message, + build_launch_readiness, + build_objection_responses, + build_outreach_message, + build_private_beta_offer, + build_private_beta_safety_notes, + build_reply_handlers, + build_weekly_launch_scorecard, + decide_go_no_go, + private_beta_faq, + record_launch_event, +) + + +# ── Private Beta ───────────────────────────────────────────── +def test_private_beta_offer_has_essentials(): + o = build_private_beta_offer() + assert o["price_sar"] == 499 + assert o["duration_days"] == 7 + assert o["live_send_allowed"] is False + assert o["approval_required"] is True + assert len(o["deliverables_ar"]) >= 4 + + +def test_private_beta_offer_seats_override(): + o = build_private_beta_offer(seats_remaining=2) + assert o["seats_available"] == 2 + + +def test_private_beta_safety_notes_blocks_live(): + s = build_private_beta_safety_notes() + text = " ".join(s["do_not_do_ar"]) + assert "live" in text.lower() or "عشوائي" in text or "تلقائي" in text + assert any("PDPL" in line for line in s["do_not_do_ar"]) + + +def test_private_beta_faq_arabic(): + faq = private_beta_faq() + assert len(faq) >= 4 + for item in faq: + assert any("؀" <= ch <= "ۿ" for ch in item["q_ar"]) + assert any("؀" <= ch <= "ۿ" for ch in item["a_ar"]) + + +# ── Demo Flow ──────────────────────────────────────────────── +def test_demo_flow_is_12_minutes(): + f = build_12_min_demo_flow() + assert f["duration_minutes"] == 12 + assert len(f["minute_by_minute_ar"]) == 6 + + +def test_demo_discovery_has_5_questions(): + out = build_discovery_questions() + assert len(out) == 5 + + +def test_objection_responses_cover_essentials(): + out = build_objection_responses() + for k in ("price", "timing", "trust", "complexity", "data_privacy"): + assert k in out + + +def test_close_script_arabic(): + out = build_close_script() + assert len(out["close_sequence_ar"]) >= 3 + assert any("؀" <= ch <= "ۿ" for ch in out["close_template_ar"]) + + +# ── Outreach ───────────────────────────────────────────────── +def test_first_20_has_4_segments_total_20(): + out = build_first_20_segments() + assert out["total_targets"] == 20 + assert len(out["segments"]) == 4 + assert sum(s["count"] for s in out["segments"]) == 20 + + +def test_outreach_message_is_arabic_and_drafts_only(): + out = build_outreach_message("agency_b2b", name="أحمد") + assert any("؀" <= ch <= "ۿ" for ch in out["body_ar"]) + assert out["live_send_allowed"] is False + + +def test_outreach_unknown_segment_falls_back(): + out = build_outreach_message("totally_unknown", name="X") + assert out["body_ar"] + + +def test_followup_step_2_different_from_1(): + s1 = build_followup_message("training_consulting", step=1, name="X") + s2 = build_followup_message("training_consulting", step=2, name="X") + assert s1["body_ar"] != s2["body_ar"] + + +def test_followup_step_3_archives(): + s3 = build_followup_message("agency_b2b", step=3, name="X") + assert s3["kind"] == "followup_3_final" + + +def test_reply_handlers_include_critical(): + h = build_reply_handlers() + for k in ("interested", "needs_more_info", "price_objection", + "not_now", "no_thanks", "unsubscribe"): + assert k in h + + +# ── Go / No-Go ─────────────────────────────────────────────── +def test_readiness_all_false_returns_zero_pct(): + r = build_launch_readiness(statuses={}) + assert r["passed_pct"] == 0.0 + assert r["passed_gates"] == 0 + assert len(r["blockers_ar"]) == r["total_gates"] + + +def test_readiness_all_true_returns_full_pct(): + statuses = {gate["id"]: True for gate in + __import__("auto_client_acquisition.launch_ops", + fromlist=["LAUNCH_GATES"]).go_no_go.LAUNCH_GATES} + r = build_launch_readiness(statuses=statuses) + assert r["passed_pct"] == 100.0 + assert r["passed_gates"] == r["total_gates"] + + +def test_go_no_go_blocks_when_no_secrets_fails(): + decision = decide_go_no_go(statuses={"tests_passed": True, + "routes_check": True, + "no_secrets": False, + "staging_health": True, + "live_sends_disabled": True}) + assert decision["verdict"] == "no_go" + + +def test_go_no_go_blocks_when_live_sends_enabled(): + decision = decide_go_no_go(statuses={"tests_passed": True, + "routes_check": True, + "no_secrets": True, + "staging_health": True, + "live_sends_disabled": False}) + assert decision["verdict"] == "no_go" + + +def test_go_no_go_passes_with_critical_and_70pct(): + statuses = { + "tests_passed": True, "routes_check": True, "no_secrets": True, + "staging_health": True, "supabase_staging": True, + "service_catalog": True, "private_beta_page": True, + "first_20_ready": True, "live_sends_disabled": True, + "payment_manual_ready": False, # 9/10 = 90% + } + decision = decide_go_no_go(statuses=statuses) + assert decision["verdict"] == "go" + + +# ── Scorecard ──────────────────────────────────────────────── +def test_record_event_unknown_raises(): + with pytest.raises(ValueError): + record_launch_event(event_type="totally_invalid") + + +def test_record_event_appends_to_log(): + log: list = [] + record_launch_event(event_type="outreach_sent", event_log=log) + assert len(log) == 1 + assert log[0]["event_type"] == "outreach_sent" + + +def test_daily_scorecard_aggregates(): + events = [{"event_type": "outreach_sent"}] * 12 + \ + [{"event_type": "demo_booked"}] * 2 + s = build_daily_launch_scorecard(events=events) + assert s["metrics"]["outreach_sent"] == 12 + assert s["metrics"]["demo_booked"] == 2 + assert s["progress"]["outreach_sent"]["pct"] == 60.0 # 12/20 = 60% + + +def test_weekly_scorecard_returns_verdict(): + events = [{"event_type": "outreach_sent"}] * 50 + \ + [{"event_type": "pilot_paid"}] * 2 + s = build_weekly_launch_scorecard(events=events) + assert s["verdict"] == "on_track" + + +def test_weekly_scorecard_needs_focus_for_low_demos(): + events = [{"event_type": "outreach_sent"}] * 5 + s = build_weekly_launch_scorecard(events=events) + assert s["verdict"] == "needs_focus" + + +# ── Constants exposed ──────────────────────────────────────── +def test_private_beta_offer_constant_exposed(): + assert PRIVATE_BETA_OFFER["price_sar"] == 499 + assert PRIVATE_BETA_OFFER["live_send_allowed"] is False diff --git a/dealix/tests/unit/test_revenue_launch.py b/dealix/tests/unit/test_revenue_launch.py new file mode 100644 index 00000000..28c6f4e1 --- /dev/null +++ b/dealix/tests/unit/test_revenue_launch.py @@ -0,0 +1,245 @@ +"""Unit tests for Revenue Launch.""" + +from __future__ import annotations + +import pytest + +from auto_client_acquisition.revenue_launch import ( + PIPELINE_STAGES, + add_prospect, + build_24h_delivery_plan, + build_499_pilot_offer, + build_case_study_free_offer, + build_client_intake_form, + build_client_summary, + build_first_10_opportunities_delivery, + build_first_20_segments_v2, + build_followup_1, + build_followup_2, + build_growth_diagnostic_delivery, + build_growth_os_pilot_offer, + build_list_intelligence_delivery, + build_moyasar_invoice_instructions, + build_next_step_recommendation, + build_outreach_message_v2, + build_payment_confirmation_checklist, + build_payment_link_message, + build_pipeline_schema, + build_private_beta_offer, + build_private_beta_proof_pack, + build_reply_handlers_v2, + recommend_offer_for_segment, + summarize_pipeline, + update_stage, +) + + +# ── Offers ─────────────────────────────────────────────────── +def test_499_pilot_has_correct_price(): + o = build_499_pilot_offer() + assert o["price_sar"] == 499 + assert o["live_send_allowed"] is False + assert o["no_live_charge"] is True + + +def test_growth_os_pilot_30_days(): + o = build_growth_os_pilot_offer() + assert o["duration_days"] == 30 + assert o["price_sar_min"] == 1500 + assert o["price_sar_max"] == 3000 + + +def test_case_study_free_requires_consent(): + o = build_case_study_free_offer() + assert o["price_sar"] == 0 + assert o["case_study_required"] is True + + +def test_recommend_offer_for_agency(): + out = recommend_offer_for_segment("agency_b2b") + assert out["primary_offer"] == "growth_os_pilot_30d" + + +def test_recommend_offer_for_training(): + out = recommend_offer_for_segment("training_consulting") + assert out["primary_offer"] == "pilot_499_7d" + + +def test_recommend_offer_unknown_segment_default(): + out = recommend_offer_for_segment("totally_unknown") + assert out["primary_offer"] == "pilot_499_7d" + + +def test_private_beta_offer_re_export(): + o = build_private_beta_offer() + assert o["price_sar"] == 499 + + +# ── Pipeline ───────────────────────────────────────────────── +def test_pipeline_schema_has_8_stages(): + s = build_pipeline_schema() + assert len(s["stages"]) == 8 + assert "paid" in s["stages"] + assert "lost" in s["stages"] + + +def test_add_prospect_starts_at_identified(): + p = add_prospect(company="Acme", segment="saas_tech_small") + assert p["stage"] == "identified" + assert p["paid"] is False + + +def test_update_stage_to_paid_marks_paid_true(): + p = add_prospect(company="Acme") + update_stage(prospect=p, new_stage="paid", notes="Moyasar 499") + assert p["stage"] == "paid" + assert p["paid"] is True + assert "Moyasar" in str(p["notes"]) + + +def test_update_stage_invalid_raises(): + p = add_prospect(company="Acme") + with pytest.raises(ValueError): + update_stage(prospect=p, new_stage="bogus_stage") + + +def test_summarize_pipeline_counts_revenue(): + pipeline = [] + p1 = add_prospect(pipeline=pipeline, company="A", segment="agency_b2b") + p2 = add_prospect(pipeline=pipeline, company="B", segment="training") + p1["price_sar"] = 499 + update_stage(prospect=p1, new_stage="paid") + update_stage(prospect=p2, new_stage="lost") + s = summarize_pipeline(pipeline) + assert s["total_prospects"] == 2 + assert s["revenue_paid_sar"] == 499.0 + assert s["by_stage"]["paid"] == 1 + assert s["by_stage"]["lost"] == 1 + assert s["win_rate"] == 0.5 + + +# ── Outreach ───────────────────────────────────────────────── +def test_first_20_segments_v2(): + out = build_first_20_segments_v2() + assert out["total_targets"] == 20 + + +def test_outreach_message_v2_arabic(): + out = build_outreach_message_v2("agency_b2b") + assert any("؀" <= ch <= "ۿ" for ch in out["body_ar"]) + + +def test_followup_1_and_2_differ(): + s1 = build_followup_1("training_consulting") + s2 = build_followup_2("training_consulting") + assert s1["body_ar"] != s2["body_ar"] + + +def test_reply_handlers_v2_includes_unsubscribe(): + h = build_reply_handlers_v2() + assert "unsubscribe" in h + + +# ── Pilot delivery ─────────────────────────────────────────── +def test_intake_form_has_required_fields(): + f = build_client_intake_form() + keys = {q["key"] for q in f["fields"]} + for required in ("company_name", "sector", "city", "primary_offer", + "approval_owner"): + assert required in keys + + +def test_24h_delivery_plan_has_5_phases(): + p = build_24h_delivery_plan("first_10_opportunities_sprint") + assert len(p["phases"]) == 5 + assert p["live_send_allowed"] is False + + +def test_first_10_delivery_has_proof(): + out = build_first_10_opportunities_delivery({"sector": "training"}) + assert "Proof Pack v1" in out["deliverables"] + assert out["approval_required"] is True + + +def test_list_intelligence_delivery_includes_50_targets(): + out = build_list_intelligence_delivery({"sector": "real_estate"}) + assert any("50" in d for d in out["deliverables"]) + + +def test_growth_diagnostic_delivery_24h(): + out = build_growth_diagnostic_delivery({"sector": "saas"}) + assert "24" in out["delivery_time"] or "ساعة" in out["delivery_time"] + + +# ── Payment manual flow ────────────────────────────────────── +def test_invoice_instructions_correct_halalas(): + out = build_moyasar_invoice_instructions(amount_sar=499) + assert out["amount_sar"] == 499 + assert out["amount_halalas"] == 49900 + assert out["no_live_charge"] is True + + +def test_invoice_instructions_warns_no_card_storage(): + out = build_moyasar_invoice_instructions(amount_sar=499) + text = " ".join(out["do_not_do_ar"]) + assert "بطاقة" in text or "card" in text.lower() + + +def test_payment_link_message_arabic_and_no_live_send(): + out = build_payment_link_message( + customer_name="أحمد", invoice_url="https://example.com/inv/1", + ) + assert any("؀" <= ch <= "ۿ" for ch in out["body_ar"]) + assert out["live_send_allowed"] is False + + +def test_payment_confirmation_checklist_blocks_premature_delivery(): + out = build_payment_confirmation_checklist() + text = " ".join(out["do_not_do_ar"]) + assert "paid" in text.lower() or "تأكيد" in text + + +# ── Proof Pack ─────────────────────────────────────────────── +def test_proof_pack_template_has_metrics(): + out = build_private_beta_proof_pack(company_name="Acme") + assert "opportunities_generated" in out["metrics_to_include"] + assert out["approval_required"] is True + + +def test_client_summary_returns_5_lines(): + out = build_client_summary( + company_name="Acme", opportunities_count=10, + approved_drafts=4, meetings=2, pipeline_sar=18000, + risks_blocked=3, + ) + assert len(out["summary_ar"]) == 5 + assert any("18000" in line or "18,000" in line or "18000" in str(line) + for line in out["summary_ar"]) + + +def test_next_step_upsell_for_strong_outcome(): + out = build_next_step_recommendation(pilot_metrics={ + "pipeline_sar": 30000, "meetings": 3, "csat": 9, + }) + assert out["next_action"] == "upsell_growth_os_monthly" + + +def test_next_step_iterate_for_weak_outcome(): + out = build_next_step_recommendation(pilot_metrics={ + "pipeline_sar": 1000, "meetings": 0, "csat": 5, + }) + assert out["next_action"] == "iterate_or_archive" + + +def test_next_step_extend_for_promising_outcome(): + out = build_next_step_recommendation(pilot_metrics={ + "pipeline_sar": 12000, "meetings": 1, "csat": 7, + }) + assert out["next_action"] == "extend_pilot" + + +# ── Constants ─────────────────────────────────────────────── +def test_pipeline_stages_constant_exposed(): + assert "identified" in PIPELINE_STAGES + assert "paid" in PIPELINE_STAGES + assert "lost" in PIPELINE_STAGES