mirror of
https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools.git
synced 2026-06-17 23:09:35 +00:00
feat(launch+revenue): Private Beta Launch Ops + Revenue Launch — 14 modules + 29 endpoints + 56 tests + scripts/landing/docs
Launch Ops (5 modules) — برج إطلاق الـ Private Beta
- private_beta: 499 SAR × 7-day offer + safety notes + 6-question Arabic FAQ
- demo_flow: 12-minute minute-by-minute Arabic demo + 5 discovery Qs + 6 objection responses + close script
- outreach_messages: 4 segments × 5 prospects = 20 + per-segment Arabic messages + 3-step follow-ups + 6 reply handlers
- go_no_go: 10-gate readiness + 3 critical gates (no_secrets/live_sends_disabled/staging_health) + verdict + next-actions
- launch_scorecard: 11 event types + daily/weekly aggregation + targets (20 outreach/5 replies/3 demos/1 pilot daily)
Revenue Launch (7 modules) — تحويل Dealix إلى دخل
- offer_builder: 4 offers (Private Beta, 499 Pilot, Growth OS Pilot 1.5-3K, Free Case Study) + segment-aware recommend
- pipeline_tracker: 8-stage deterministic pipeline + add/update/summarize + revenue tracking + win rate
- outreach_sequence: re-export single source of truth from launch_ops with revenue-tier wrappers
- demo_closer: re-export from launch_ops
- pilot_delivery: 12-field intake form + 5-phase 24h delivery plan + per-service templates (First 10 / List Intel / Free Diagnostic)
- proof_pack_template: 5-line Arabic client summary + ROI estimate (pipeline_x + closed_won_x) + next-step recommendation (upsell/iterate/extend)
- payment_manual_flow: Moyasar invoice step-by-step (halalas-correct) + Arabic payment-link message + confirmation checklist; NEVER charges via API
Service Tower extensions (2 modules)
- contract_templates: re-export targeting_os contracts + new SLA outline (legal_review_required, PDPL-aware)
- vertical_service_map: 6 verticals (B2B SaaS / agencies / training-consulting / real estate / healthcare-local / retail-ecommerce) with primary+supporting services + buyer roles + common pains + winning offer
Routers (2 new) — 29 endpoints
- /api/v1/launch/* — 11 endpoints (private-beta/offer, demo/flow, outreach/{first-20, message, followup}, go-no-go, readiness, scorecard/{event, daily, weekly, demo})
- /api/v1/revenue-launch/* — 18 endpoints (offers + offers/recommend, outreach/{first-20, followup}, demo-flow, pipeline/{schema, summarize}, pilot-delivery/{intake-form, 24h-plan, first-10, list-intelligence, free-diagnostic}, payment/{invoice-instructions, link-message, confirmation-checklist}, proof-pack/{template, client-summary, next-step})
Tests (2 new files, 56 tests)
- test_launch_ops.py: 25 tests (Private Beta offer essentials + Arabic FAQ; demo flow 12-min structure; first-20 segments × 5; outreach Arabic + drafts only; followup steps differ; reply handlers include unsubscribe; go/no-go critical gates block; scorecard aggregation + verdict)
- test_revenue_launch.py: 31 tests (offers correct prices, no_live_charge=True; segment-aware recommends; pipeline 8 stages + add/update/summarize + win rate; outreach v2 Arabic; intake fields; 24h plan 5 phases; invoice halalas correct; payment confirmation blocks premature delivery; proof pack 5 lines + 3 next-step paths)
Scripts (1 new)
- scripts/launch_readiness_check.py: runs 10 gates locally + optional --staging-url; pretty/JSON output; critical gates determine GO/NO-GO/FIX-THEN-GO verdict
Landing pages (2 new, RTL Arabic)
- list-intelligence.html — List Intelligence service detail (499–1,500 SAR)
- growth-os.html — Growth OS Monthly subscription page (2,999 SAR/month)
Docs (1 new + 1 updated)
- REVENUE_TODAY_PLAYBOOK.md (Arabic) — 12-section playbook: offers, segments, messages, demo, pipeline, 24h delivery, Moyasar manual flow, proof pack, daily targets, go/no-go, what-not-to-do, next-step
- DEALIX_100_PERCENT_LAUNCH_PLAN.md — added §40 Launch Ops + §41 Revenue Launch + §42 Service Tower extensions + §43 Scripts
Test results
- 56/56 new tests pass
- Full suite: 824 passed, 2 skipped (missing API keys, unrelated)
- 0 existing tests broken
Safety integration
- All offers: live_send_allowed=False, no_live_charge=True, approval_required=True
- 10-gate go/no-go BLOCKS launch if no_secrets/live_sends_disabled/staging_health fail
- Moyasar: invoice/payment-link manual only; NEVER calls live charge API
- Payment confirmation checklist blocks delivery before invoice paid status
- All outreach messages: drafts only, follow-ups capped at 3, opt-out honored immediately
- 6 verticals mapped to safe service stacks; LinkedIn always Lead Forms (never scraping)
Integration with previous layers
- Launch Ops uses platform_services tool_gateway, intelligence_layer command_feed, security_curator redaction
- Revenue Launch uses targeting_os contractability + service_tower offers + intelligence_layer simulator
- Pipeline tracker integrates with action_ledger for stage transitions
- Proof Pack template references intelligence_layer proof metrics + service_excellence ROI
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e106a9a0d2
commit
84f1ad9620
@ -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)
|
||||
|
||||
|
||||
135
dealix/api/routers/launch_ops.py
Normal file
135
dealix/api/routers/launch_ops.py
Normal file
@ -0,0 +1,135 @@
|
||||
"""Launch Ops router — Private Beta + Demo + Outreach + Go/No-Go + Scorecard."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, Body
|
||||
|
||||
from auto_client_acquisition.launch_ops import (
|
||||
build_12_min_demo_flow,
|
||||
build_close_script,
|
||||
build_daily_launch_scorecard,
|
||||
build_discovery_questions,
|
||||
build_first_20_segments,
|
||||
build_followup_message,
|
||||
build_launch_readiness,
|
||||
build_objection_responses,
|
||||
build_outreach_message,
|
||||
build_private_beta_offer,
|
||||
build_private_beta_safety_notes,
|
||||
build_reply_handlers,
|
||||
build_weekly_launch_scorecard,
|
||||
decide_go_no_go,
|
||||
private_beta_faq,
|
||||
record_launch_event,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/api/v1/launch", tags=["launch-ops"])
|
||||
|
||||
|
||||
# ── Private Beta ─────────────────────────────────────────────
|
||||
@router.get("/private-beta/offer")
|
||||
async def private_beta_offer() -> dict[str, Any]:
|
||||
return {
|
||||
"offer": build_private_beta_offer(),
|
||||
"safety": build_private_beta_safety_notes(),
|
||||
"faq": private_beta_faq(),
|
||||
}
|
||||
|
||||
|
||||
# ── Demo flow ────────────────────────────────────────────────
|
||||
@router.get("/demo/flow")
|
||||
async def demo_flow() -> dict[str, Any]:
|
||||
return {
|
||||
"flow": build_12_min_demo_flow(),
|
||||
"discovery_questions": build_discovery_questions(),
|
||||
"objections": build_objection_responses(),
|
||||
"close": build_close_script(),
|
||||
}
|
||||
|
||||
|
||||
# ── Outreach ─────────────────────────────────────────────────
|
||||
@router.get("/outreach/first-20")
|
||||
async def outreach_first_20() -> dict[str, Any]:
|
||||
segments = build_first_20_segments()
|
||||
sample_messages = {
|
||||
s["id"]: build_outreach_message(s["id"])
|
||||
for s in segments["segments"]
|
||||
}
|
||||
return {
|
||||
**segments,
|
||||
"sample_messages": sample_messages,
|
||||
"reply_handlers": build_reply_handlers(),
|
||||
}
|
||||
|
||||
|
||||
@router.post("/outreach/message")
|
||||
async def outreach_message(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
|
||||
return build_outreach_message(
|
||||
segment_id=payload.get("segment_id", ""),
|
||||
name=payload.get("name", "[الاسم]"),
|
||||
)
|
||||
|
||||
|
||||
@router.post("/outreach/followup")
|
||||
async def outreach_followup(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
|
||||
return build_followup_message(
|
||||
segment_id=payload.get("segment_id", ""),
|
||||
step=int(payload.get("step", 1)),
|
||||
name=payload.get("name", "[الاسم]"),
|
||||
)
|
||||
|
||||
|
||||
# ── Go / No-Go ───────────────────────────────────────────────
|
||||
@router.post("/go-no-go")
|
||||
async def go_no_go(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]:
|
||||
return decide_go_no_go(statuses=payload.get("statuses"))
|
||||
|
||||
|
||||
@router.get("/readiness")
|
||||
async def readiness() -> dict[str, Any]:
|
||||
"""Readiness with all gates assumed False (use POST /go-no-go for real status)."""
|
||||
return build_launch_readiness(statuses={})
|
||||
|
||||
|
||||
# ── Scorecard ────────────────────────────────────────────────
|
||||
@router.post("/scorecard/event")
|
||||
async def scorecard_event(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
|
||||
try:
|
||||
return record_launch_event(
|
||||
event_type=payload.get("event_type", ""),
|
||||
customer_id=payload.get("customer_id"),
|
||||
notes=payload.get("notes"),
|
||||
)
|
||||
except ValueError as exc:
|
||||
return {"error": str(exc)}
|
||||
|
||||
|
||||
@router.post("/scorecard/daily")
|
||||
async def scorecard_daily(
|
||||
events: list[dict[str, Any]] = Body(default_factory=list, embed=True),
|
||||
) -> dict[str, Any]:
|
||||
return build_daily_launch_scorecard(events=events)
|
||||
|
||||
|
||||
@router.post("/scorecard/weekly")
|
||||
async def scorecard_weekly(
|
||||
events: list[dict[str, Any]] = Body(default_factory=list, embed=True),
|
||||
) -> dict[str, Any]:
|
||||
return build_weekly_launch_scorecard(events=events)
|
||||
|
||||
|
||||
@router.get("/scorecard/demo")
|
||||
async def scorecard_demo() -> dict[str, Any]:
|
||||
"""Demo scorecard with synthetic events."""
|
||||
demo_events = [
|
||||
{"event_type": "outreach_sent"} for _ in range(15)
|
||||
] + [
|
||||
{"event_type": "reply_received"} for _ in range(4)
|
||||
] + [
|
||||
{"event_type": "demo_booked"} for _ in range(2)
|
||||
] + [
|
||||
{"event_type": "blocked_action"} for _ in range(6)
|
||||
]
|
||||
return build_daily_launch_scorecard(events=demo_events)
|
||||
182
dealix/api/routers/revenue_launch.py
Normal file
182
dealix/api/routers/revenue_launch.py
Normal file
@ -0,0 +1,182 @@
|
||||
"""Revenue Launch router — paid offer + pipeline + delivery + payment + proof."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, Body
|
||||
|
||||
from auto_client_acquisition.revenue_launch import (
|
||||
build_24h_delivery_plan,
|
||||
build_499_pilot_offer,
|
||||
build_case_study_free_offer,
|
||||
build_client_intake_form,
|
||||
build_client_summary,
|
||||
build_first_10_opportunities_delivery,
|
||||
build_first_20_segments_v2,
|
||||
build_followup_1,
|
||||
build_followup_2,
|
||||
build_growth_diagnostic_delivery,
|
||||
build_growth_os_pilot_offer,
|
||||
build_list_intelligence_delivery,
|
||||
build_moyasar_invoice_instructions,
|
||||
build_next_step_recommendation,
|
||||
build_outreach_message_v2,
|
||||
build_payment_confirmation_checklist,
|
||||
build_payment_link_message,
|
||||
build_pipeline_schema,
|
||||
build_private_beta_offer,
|
||||
build_private_beta_proof_pack,
|
||||
build_reply_handlers_v2,
|
||||
demo_12_min,
|
||||
demo_close_script,
|
||||
demo_discovery,
|
||||
demo_objections,
|
||||
recommend_offer_for_segment,
|
||||
summarize_pipeline,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/api/v1/revenue-launch", tags=["revenue-launch"])
|
||||
|
||||
|
||||
# ── Offers ───────────────────────────────────────────────────
|
||||
@router.get("/offers")
|
||||
async def offers() -> dict[str, Any]:
|
||||
return {
|
||||
"private_beta": build_private_beta_offer(),
|
||||
"pilot_499": build_499_pilot_offer(),
|
||||
"growth_os_pilot": build_growth_os_pilot_offer(),
|
||||
"case_study_free": build_case_study_free_offer(),
|
||||
}
|
||||
|
||||
|
||||
@router.post("/offers/recommend")
|
||||
async def offers_recommend(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
|
||||
return recommend_offer_for_segment(payload.get("segment_id", ""))
|
||||
|
||||
|
||||
# ── Outreach ─────────────────────────────────────────────────
|
||||
@router.get("/outreach/first-20")
|
||||
async def outreach_first_20() -> dict[str, Any]:
|
||||
seg = build_first_20_segments_v2()
|
||||
return {
|
||||
**seg,
|
||||
"messages": {
|
||||
s["id"]: build_outreach_message_v2(s["id"])
|
||||
for s in seg["segments"]
|
||||
},
|
||||
"reply_handlers": build_reply_handlers_v2(),
|
||||
}
|
||||
|
||||
|
||||
@router.post("/outreach/followup")
|
||||
async def outreach_followup(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
|
||||
step = int(payload.get("step", 1))
|
||||
builder = build_followup_2 if step >= 2 else build_followup_1
|
||||
return builder(
|
||||
segment_id=payload.get("segment_id", ""),
|
||||
name=payload.get("name", "[الاسم]"),
|
||||
)
|
||||
|
||||
|
||||
# ── Demo ─────────────────────────────────────────────────────
|
||||
@router.get("/demo-flow")
|
||||
async def demo_flow() -> dict[str, Any]:
|
||||
return {
|
||||
"flow": demo_12_min(),
|
||||
"discovery_questions": demo_discovery(),
|
||||
"objections": demo_objections(),
|
||||
"close": demo_close_script(),
|
||||
}
|
||||
|
||||
|
||||
# ── Pipeline ─────────────────────────────────────────────────
|
||||
@router.get("/pipeline/schema")
|
||||
async def pipeline_schema() -> dict[str, Any]:
|
||||
return build_pipeline_schema()
|
||||
|
||||
|
||||
@router.post("/pipeline/summarize")
|
||||
async def pipeline_summarize(
|
||||
pipeline: list[dict[str, Any]] = Body(default_factory=list, embed=True),
|
||||
) -> dict[str, Any]:
|
||||
return summarize_pipeline(pipeline)
|
||||
|
||||
|
||||
# ── Pilot delivery ───────────────────────────────────────────
|
||||
@router.get("/pilot-delivery/intake-form")
|
||||
async def pilot_intake_form() -> dict[str, Any]:
|
||||
return build_client_intake_form()
|
||||
|
||||
|
||||
@router.post("/pilot-delivery/24h-plan")
|
||||
async def pilot_24h_plan(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
|
||||
return build_24h_delivery_plan(payload.get("service_id", ""))
|
||||
|
||||
|
||||
@router.post("/pilot-delivery/first-10")
|
||||
async def pilot_first_10(intake: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]:
|
||||
return build_first_10_opportunities_delivery(intake)
|
||||
|
||||
|
||||
@router.post("/pilot-delivery/list-intelligence")
|
||||
async def pilot_list_intelligence(intake: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]:
|
||||
return build_list_intelligence_delivery(intake)
|
||||
|
||||
|
||||
@router.post("/pilot-delivery/free-diagnostic")
|
||||
async def pilot_free_diagnostic(intake: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]:
|
||||
return build_growth_diagnostic_delivery(intake)
|
||||
|
||||
|
||||
# ── Payment manual flow ──────────────────────────────────────
|
||||
@router.post("/payment/invoice-instructions")
|
||||
async def payment_invoice_instructions(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]:
|
||||
return build_moyasar_invoice_instructions(
|
||||
amount_sar=int(payload.get("amount_sar", 499)),
|
||||
customer_name=payload.get("customer_name", ""),
|
||||
invoice_description=payload.get(
|
||||
"invoice_description",
|
||||
"Dealix Private Beta Pilot — 7 days",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@router.post("/payment/link-message")
|
||||
async def payment_link_message(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
|
||||
return build_payment_link_message(
|
||||
customer_name=payload.get("customer_name", "[الاسم]"),
|
||||
invoice_url=payload.get("invoice_url", "[INVOICE_URL]"),
|
||||
amount_sar=int(payload.get("amount_sar", 499)),
|
||||
)
|
||||
|
||||
|
||||
@router.get("/payment/confirmation-checklist")
|
||||
async def payment_confirmation_checklist() -> dict[str, Any]:
|
||||
return build_payment_confirmation_checklist()
|
||||
|
||||
|
||||
# ── Proof Pack ───────────────────────────────────────────────
|
||||
@router.post("/proof-pack/template")
|
||||
async def proof_pack_template(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]:
|
||||
return build_private_beta_proof_pack(
|
||||
company_name=payload.get("company_name", ""),
|
||||
metrics=payload.get("metrics", {}),
|
||||
)
|
||||
|
||||
|
||||
@router.post("/proof-pack/client-summary")
|
||||
async def proof_pack_client_summary(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]:
|
||||
return build_client_summary(
|
||||
company_name=payload.get("company_name", ""),
|
||||
opportunities_count=int(payload.get("opportunities_count", 0)),
|
||||
approved_drafts=int(payload.get("approved_drafts", 0)),
|
||||
meetings=int(payload.get("meetings", 0)),
|
||||
pipeline_sar=float(payload.get("pipeline_sar", 0)),
|
||||
risks_blocked=int(payload.get("risks_blocked", 0)),
|
||||
)
|
||||
|
||||
|
||||
@router.post("/proof-pack/next-step")
|
||||
async def proof_pack_next_step(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]:
|
||||
return build_next_step_recommendation(pilot_metrics=payload)
|
||||
61
dealix/auto_client_acquisition/launch_ops/__init__.py
Normal file
61
dealix/auto_client_acquisition/launch_ops/__init__.py
Normal file
@ -0,0 +1,61 @@
|
||||
"""Launch Ops — Private Beta launch workflow + Go/No-Go gates + scorecards.
|
||||
|
||||
Connects everything else into a single launch-day operating layer:
|
||||
- private_beta: today's offer, gates, FAQ
|
||||
- demo_flow: 12-min demo script consolidator
|
||||
- outreach_messages: first-20 plan + per-segment messages
|
||||
- go_no_go: deterministic launch readiness gate
|
||||
- launch_scorecard: daily ops metrics
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from .demo_flow import (
|
||||
build_12_min_demo_flow,
|
||||
build_close_script,
|
||||
build_discovery_questions,
|
||||
build_objection_responses,
|
||||
)
|
||||
from .go_no_go import build_launch_readiness, decide_go_no_go
|
||||
from .launch_scorecard import (
|
||||
build_daily_launch_scorecard,
|
||||
build_weekly_launch_scorecard,
|
||||
record_launch_event,
|
||||
)
|
||||
from .outreach_messages import (
|
||||
build_first_20_segments,
|
||||
build_followup_message,
|
||||
build_outreach_message,
|
||||
build_reply_handlers,
|
||||
)
|
||||
from .private_beta import (
|
||||
PRIVATE_BETA_OFFER,
|
||||
build_private_beta_offer,
|
||||
build_private_beta_safety_notes,
|
||||
private_beta_faq,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
# private_beta
|
||||
"PRIVATE_BETA_OFFER",
|
||||
"build_private_beta_offer",
|
||||
"build_private_beta_safety_notes",
|
||||
"private_beta_faq",
|
||||
# demo_flow
|
||||
"build_12_min_demo_flow",
|
||||
"build_close_script",
|
||||
"build_discovery_questions",
|
||||
"build_objection_responses",
|
||||
# outreach_messages
|
||||
"build_first_20_segments",
|
||||
"build_followup_message",
|
||||
"build_outreach_message",
|
||||
"build_reply_handlers",
|
||||
# go_no_go
|
||||
"build_launch_readiness",
|
||||
"decide_go_no_go",
|
||||
# launch_scorecard
|
||||
"build_daily_launch_scorecard",
|
||||
"build_weekly_launch_scorecard",
|
||||
"record_launch_event",
|
||||
]
|
||||
104
dealix/auto_client_acquisition/launch_ops/demo_flow.py
Normal file
104
dealix/auto_client_acquisition/launch_ops/demo_flow.py
Normal file
@ -0,0 +1,104 @@
|
||||
"""Demo flow — 12-min Arabic demo + discovery + objection handling + close."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
def build_12_min_demo_flow() -> dict[str, Any]:
|
||||
"""The canonical 12-minute Arabic demo plan."""
|
||||
return {
|
||||
"duration_minutes": 12,
|
||||
"minute_by_minute_ar": [
|
||||
"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 فرص + رسالة + توصية، بدون التزام."
|
||||
),
|
||||
}
|
||||
130
dealix/auto_client_acquisition/launch_ops/go_no_go.py
Normal file
130
dealix/auto_client_acquisition/launch_ops/go_no_go.py
Normal file
@ -0,0 +1,130 @@
|
||||
"""Go/No-Go launch readiness — 10 deterministic gates."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
# All 10 gates Dealix Launch Control Room checks before approving sale.
|
||||
LAUNCH_GATES: tuple[dict[str, str], ...] = (
|
||||
{"id": "tests_passed", "label_ar": "اختبارات pytest خضراء"},
|
||||
{"id": "routes_check", "label_ar": "scripts/print_routes.py لا يكشف تكرار"},
|
||||
{"id": "no_secrets", "label_ar": "scan الأسرار نظيف"},
|
||||
{"id": "staging_health", "label_ar": "/health على staging يرجع 200"},
|
||||
{"id": "supabase_staging", "label_ar": "Supabase staging مهيأ"},
|
||||
{"id": "service_catalog", "label_ar": "/services/catalog يعمل ويعرض ≥4 خدمات"},
|
||||
{"id": "private_beta_page", "label_ar": "landing/private-beta.html جاهزة"},
|
||||
{"id": "first_20_ready", "label_ar": "أول 20 prospect معرّفون"},
|
||||
{"id": "live_sends_disabled", "label_ar": "WHATSAPP/GMAIL/CALENDAR/MOYASAR live=false"},
|
||||
{"id": "payment_manual_ready", "label_ar": "Moyasar invoice/payment link جاهز يدوياً"},
|
||||
)
|
||||
|
||||
|
||||
def build_launch_readiness(
|
||||
*, statuses: dict[str, bool] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Build the launch-readiness checklist with current statuses.
|
||||
|
||||
Pass `statuses` as a dict of gate_id → bool. Unknown gates default to False.
|
||||
"""
|
||||
statuses = statuses or {}
|
||||
items: list[dict[str, Any]] = []
|
||||
passed = 0
|
||||
blockers: list[str] = []
|
||||
|
||||
for gate in LAUNCH_GATES:
|
||||
ok = bool(statuses.get(gate["id"], False))
|
||||
items.append({
|
||||
**gate,
|
||||
"passed": ok,
|
||||
"status": "✅" if ok else "🔴",
|
||||
})
|
||||
if ok:
|
||||
passed += 1
|
||||
else:
|
||||
blockers.append(gate["label_ar"])
|
||||
|
||||
total = len(LAUNCH_GATES)
|
||||
pct = round(100.0 * passed / total, 1) if total else 0.0
|
||||
|
||||
return {
|
||||
"total_gates": total,
|
||||
"passed_gates": passed,
|
||||
"passed_pct": pct,
|
||||
"items": items,
|
||||
"blockers_ar": blockers,
|
||||
"ready_threshold_min_pct": 70.0,
|
||||
}
|
||||
|
||||
|
||||
def decide_go_no_go(
|
||||
*, statuses: dict[str, bool] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Decide whether Dealix can sell today.
|
||||
|
||||
Rules:
|
||||
- All "critical" gates must pass: no_secrets, live_sends_disabled, staging_health.
|
||||
- At least 7 of 10 gates must pass overall.
|
||||
"""
|
||||
readiness = build_launch_readiness(statuses=statuses)
|
||||
passed_pct = readiness["passed_pct"]
|
||||
items = {it["id"]: it for it in readiness["items"]}
|
||||
|
||||
critical = ("no_secrets", "live_sends_disabled", "staging_health")
|
||||
critical_failed = [c for c in critical if not items.get(c, {}).get("passed")]
|
||||
|
||||
if critical_failed:
|
||||
verdict = "no_go"
|
||||
reason_ar = (
|
||||
f"بوابات حرجة فشلت: {', '.join(critical_failed)}. "
|
||||
"لا تبيع اليوم."
|
||||
)
|
||||
elif passed_pct >= 70:
|
||||
verdict = "go"
|
||||
reason_ar = (
|
||||
f"الجاهزية {passed_pct}%. "
|
||||
"ابدأ Private Beta — لا Public Launch."
|
||||
)
|
||||
else:
|
||||
verdict = "fix_then_go"
|
||||
reason_ar = (
|
||||
f"الجاهزية {passed_pct}% — أقل من 70%. "
|
||||
"ابدأ بإصلاح: " + ", ".join(readiness["blockers_ar"][:3])
|
||||
)
|
||||
|
||||
return {
|
||||
"verdict": verdict,
|
||||
"reason_ar": reason_ar,
|
||||
"readiness": readiness,
|
||||
"next_actions_ar": _next_actions(readiness),
|
||||
}
|
||||
|
||||
|
||||
def _next_actions(readiness: dict[str, Any]) -> list[str]:
|
||||
"""Build concrete next-actions for any failing gates."""
|
||||
by_id = {it["id"]: it for it in readiness["items"]}
|
||||
actions: list[str] = []
|
||||
if not by_id["tests_passed"]["passed"]:
|
||||
actions.append("شغّل: pytest -q")
|
||||
if not by_id["routes_check"]["passed"]:
|
||||
actions.append("شغّل: python scripts/print_routes.py")
|
||||
if not by_id["no_secrets"]["passed"]:
|
||||
actions.append("شغّل grep scan + ألغِ أي مفتاح ظهر.")
|
||||
if not by_id["staging_health"]["passed"]:
|
||||
actions.append("انشر على Railway: railway up + curl /health.")
|
||||
if not by_id["supabase_staging"]["passed"]:
|
||||
actions.append("شغّل: supabase db push --dry-run ثم db push.")
|
||||
if not by_id["service_catalog"]["passed"]:
|
||||
actions.append("افحص: curl /api/v1/services/catalog.")
|
||||
if not by_id["private_beta_page"]["passed"]:
|
||||
actions.append("افتح landing/private-beta.html وتحقق من CTA.")
|
||||
if not by_id["first_20_ready"]["passed"]:
|
||||
actions.append("جهز Sheet 'Dealix First 20 Pipeline' بالعمدة.")
|
||||
if not by_id["live_sends_disabled"]["passed"]:
|
||||
actions.append(
|
||||
"تأكد: WHATSAPP_ALLOW_LIVE_SEND=false (وما يماثلها)."
|
||||
)
|
||||
if not by_id["payment_manual_ready"]["passed"]:
|
||||
actions.append("افتح Moyasar dashboard وجهّز invoice template.")
|
||||
return actions
|
||||
140
dealix/auto_client_acquisition/launch_ops/launch_scorecard.py
Normal file
140
dealix/auto_client_acquisition/launch_ops/launch_scorecard.py
Normal file
@ -0,0 +1,140 @@
|
||||
"""Launch scorecard — daily and weekly metrics for Private Beta ops."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import defaultdict
|
||||
from typing import Any
|
||||
|
||||
# Valid event types the launch scorecard accepts.
|
||||
VALID_LAUNCH_EVENTS: tuple[str, ...] = (
|
||||
"outreach_sent",
|
||||
"reply_received",
|
||||
"demo_booked",
|
||||
"demo_held",
|
||||
"diagnostic_delivered",
|
||||
"pilot_offered",
|
||||
"pilot_paid",
|
||||
"pilot_committed",
|
||||
"pilot_lost",
|
||||
"case_study_published",
|
||||
"blocked_action",
|
||||
)
|
||||
|
||||
# Daily targets per the launch plan.
|
||||
DAILY_TARGETS: dict[str, int] = {
|
||||
"outreach_sent": 20,
|
||||
"reply_received": 5,
|
||||
"demo_booked": 3,
|
||||
"pilot_paid": 1,
|
||||
}
|
||||
|
||||
# Weekly targets (7-day plan).
|
||||
WEEKLY_TARGETS: dict[str, int] = {
|
||||
"outreach_sent": 100,
|
||||
"reply_received": 20,
|
||||
"demo_booked": 10,
|
||||
"pilot_paid": 2,
|
||||
}
|
||||
|
||||
|
||||
def record_launch_event(
|
||||
*,
|
||||
event_type: str,
|
||||
customer_id: str | None = None,
|
||||
notes: str | None = None,
|
||||
event_log: list[dict[str, Any]] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Record a launch event into an in-memory log.
|
||||
|
||||
Returns the appended entry (validated). Raises ValueError on unknown type.
|
||||
"""
|
||||
if event_type not in VALID_LAUNCH_EVENTS:
|
||||
raise ValueError(
|
||||
f"Unknown launch event: {event_type}. "
|
||||
f"Valid: {', '.join(VALID_LAUNCH_EVENTS)}"
|
||||
)
|
||||
entry: dict[str, Any] = {
|
||||
"event_type": event_type,
|
||||
"customer_id": customer_id,
|
||||
"notes": (notes or "")[:300],
|
||||
}
|
||||
if event_log is not None:
|
||||
event_log.append(entry)
|
||||
return entry
|
||||
|
||||
|
||||
def _aggregate(events: list[dict[str, Any]]) -> dict[str, int]:
|
||||
counts: dict[str, int] = defaultdict(int)
|
||||
for e in events or []:
|
||||
et = str(e.get("event_type", ""))
|
||||
counts[et] += 1
|
||||
return dict(counts)
|
||||
|
||||
|
||||
def build_daily_launch_scorecard(
|
||||
*, events: list[dict[str, Any]] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Build today's Arabic launch scorecard from event log."""
|
||||
counts = _aggregate(events or [])
|
||||
metrics = {k: counts.get(k, 0) for k in VALID_LAUNCH_EVENTS}
|
||||
|
||||
progress: dict[str, dict[str, int | float]] = {}
|
||||
for k, target in DAILY_TARGETS.items():
|
||||
actual = metrics.get(k, 0)
|
||||
pct = round(100 * actual / target, 1) if target else 0.0
|
||||
progress[k] = {"actual": actual, "target": target, "pct": pct}
|
||||
|
||||
summary_lines = [
|
||||
f"تواصل اليوم: {metrics['outreach_sent']} / {DAILY_TARGETS['outreach_sent']}",
|
||||
f"ردود: {metrics['reply_received']} / {DAILY_TARGETS['reply_received']}",
|
||||
f"ديموهات: {metrics['demo_booked']} / {DAILY_TARGETS['demo_booked']}",
|
||||
f"Pilots مدفوعة: {metrics['pilot_paid']} / {DAILY_TARGETS['pilot_paid']}",
|
||||
f"مخاطر منعت: {metrics.get('blocked_action', 0)}",
|
||||
]
|
||||
|
||||
return {
|
||||
"metrics": metrics,
|
||||
"targets": DAILY_TARGETS,
|
||||
"progress": progress,
|
||||
"summary_ar": summary_lines,
|
||||
}
|
||||
|
||||
|
||||
def build_weekly_launch_scorecard(
|
||||
*, events: list[dict[str, Any]] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Build the 7-day Arabic launch scorecard."""
|
||||
counts = _aggregate(events or [])
|
||||
metrics = {k: counts.get(k, 0) for k in VALID_LAUNCH_EVENTS}
|
||||
|
||||
progress = {}
|
||||
for k, target in WEEKLY_TARGETS.items():
|
||||
actual = metrics.get(k, 0)
|
||||
pct = round(100 * actual / target, 1) if target else 0.0
|
||||
progress[k] = {"actual": actual, "target": target, "pct": pct}
|
||||
|
||||
summary_lines = [
|
||||
f"تواصل الأسبوع: {metrics['outreach_sent']} / {WEEKLY_TARGETS['outreach_sent']}",
|
||||
f"ردود: {metrics['reply_received']} / {WEEKLY_TARGETS['reply_received']}",
|
||||
f"ديموهات منعقدة: {metrics.get('demo_held', 0)}",
|
||||
f"Pilots مدفوعة: {metrics['pilot_paid']} / {WEEKLY_TARGETS['pilot_paid']}",
|
||||
f"Pilots commitments: {metrics.get('pilot_committed', 0)}",
|
||||
f"Pilots خسرت: {metrics.get('pilot_lost', 0)}",
|
||||
f"مخاطر منعت: {metrics.get('blocked_action', 0)}",
|
||||
]
|
||||
|
||||
if metrics["pilot_paid"] >= WEEKLY_TARGETS["pilot_paid"]:
|
||||
verdict = "on_track"
|
||||
elif metrics["demo_booked"] >= 5:
|
||||
verdict = "promising"
|
||||
else:
|
||||
verdict = "needs_focus"
|
||||
|
||||
return {
|
||||
"metrics": metrics,
|
||||
"targets": WEEKLY_TARGETS,
|
||||
"progress": progress,
|
||||
"summary_ar": summary_lines,
|
||||
"verdict": verdict,
|
||||
}
|
||||
188
dealix/auto_client_acquisition/launch_ops/outreach_messages.py
Normal file
188
dealix/auto_client_acquisition/launch_ops/outreach_messages.py
Normal file
@ -0,0 +1,188 @@
|
||||
"""First 20 outreach segments + per-segment Arabic messages + reply handlers."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
def build_first_20_segments() -> dict[str, Any]:
|
||||
"""The deterministic first-20 plan — 4 segments × 5 prospects each."""
|
||||
return {
|
||||
"total_targets": 20,
|
||||
"segments": [
|
||||
{
|
||||
"id": "agency_b2b",
|
||||
"label_ar": "وكالات تسويق B2B",
|
||||
"count": 5,
|
||||
"best_offer_id": "agency_partner_program",
|
||||
"fallback_offer_id": "partner_sprint",
|
||||
"primary_channel": "email",
|
||||
},
|
||||
{
|
||||
"id": "training_consulting",
|
||||
"label_ar": "شركات تدريب واستشارات",
|
||||
"count": 5,
|
||||
"best_offer_id": "first_10_opportunities_sprint",
|
||||
"fallback_offer_id": "free_growth_diagnostic",
|
||||
"primary_channel": "email",
|
||||
},
|
||||
{
|
||||
"id": "saas_tech_small",
|
||||
"label_ar": "SaaS / تقنية صغيرة",
|
||||
"count": 5,
|
||||
"best_offer_id": "first_10_opportunities_sprint",
|
||||
"fallback_offer_id": "growth_os_monthly",
|
||||
"primary_channel": "linkedin_lead_form",
|
||||
},
|
||||
{
|
||||
"id": "services_with_whatsapp",
|
||||
"label_ar": "شركات خدمات لديها واتساب نشط",
|
||||
"count": 5,
|
||||
"best_offer_id": "list_intelligence",
|
||||
"fallback_offer_id": "whatsapp_compliance_setup",
|
||||
"primary_channel": "email",
|
||||
},
|
||||
],
|
||||
"rules_ar": [
|
||||
"لا scraping ولا قوائم مشتراة.",
|
||||
"استخدم علاقاتك المباشرة + جهات تعرفها.",
|
||||
"كل رسالة يدوية، لا automation.",
|
||||
"حد أقصى 3 follow-ups ثم أرشفة.",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
_BASE_INTRO = "هلا [الاسم]، أطلقنا Beta محدودة لـ Dealix."
|
||||
|
||||
|
||||
def build_outreach_message(segment_id: str, *, name: str = "[الاسم]") -> dict[str, Any]:
|
||||
"""Build the first-touch Arabic message for a segment."""
|
||||
intro = f"هلا {name}،"
|
||||
|
||||
if segment_id == "agency_b2b":
|
||||
body = (
|
||||
f"{intro} عندي Beta خاص للوكالات.\n\n"
|
||||
"Dealix يساعد الوكالة تطلع فرص لعملائها، تجهز رسائل عربية، تدير "
|
||||
"موافقات، وتطلع Proof Pack باسم الوكالة والعميل.\n\n"
|
||||
"أبحث عن وكالة واحدة نجرب معها Pilot مشترك على عميل حقيقي. "
|
||||
"يناسبك ديمو 15 دقيقة؟"
|
||||
)
|
||||
elif segment_id == "training_consulting":
|
||||
body = (
|
||||
f"{intro} متابع توسع شركتكم في برامج الشركات.\n\n"
|
||||
"Dealix يطلع لكم 10 فرص B2B خلال 7 أيام، يكتب الرسائل بالعربي، "
|
||||
"ويخلي صاحب القرار يوافق قبل أي تواصل، وبعدها يعطي Proof Pack.\n\n"
|
||||
"Pilot بـ499 ريال أو مجاني مقابل case study. يناسبك ديمو 12 دقيقة؟"
|
||||
)
|
||||
elif segment_id == "saas_tech_small":
|
||||
body = (
|
||||
f"{intro} رأيت إصدار النسخة الجديدة من منتجكم — مبروك.\n\n"
|
||||
"نشتغل على مدير نمو عربي يطلع 10 فرص B2B، يستخدم LinkedIn Lead "
|
||||
"Forms (لا scraping)، ويكتب الرسائل بالعربي.\n\n"
|
||||
"أبغى أجربه مع شركة SaaS سعودية واحدة. يناسبك ديمو 12 دقيقة؟"
|
||||
)
|
||||
elif segment_id == "services_with_whatsapp":
|
||||
body = (
|
||||
f"{intro} عندكم قاعدة عملاء واتساب نشطة، صحيح؟\n\n"
|
||||
"Dealix ينظف القائمة، يصنف الـ opt-in، يحظر cold WhatsApp تلقائياً، "
|
||||
"ويكتب رسائل عربية للحملات الآمنة + Proof Pack شهري.\n\n"
|
||||
"List Intelligence بـ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",
|
||||
},
|
||||
}
|
||||
110
dealix/auto_client_acquisition/launch_ops/private_beta.py
Normal file
110
dealix/auto_client_acquisition/launch_ops/private_beta.py
Normal file
@ -0,0 +1,110 @@
|
||||
"""Private Beta offer — today's offer + safety notes + FAQ."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
PRIVATE_BETA_OFFER: dict[str, Any] = {
|
||||
"offer_id": "private_beta_pilot_7d",
|
||||
"name_ar": "Private Beta Pilot — 7 أيام",
|
||||
"promise_ar": (
|
||||
"خلال 7 أيام نطلع لك 10 فرص B2B + رسائل عربية + خطة متابعة + Proof Pack، "
|
||||
"وأنت توافق قبل أي تواصل."
|
||||
),
|
||||
"deliverables_ar": [
|
||||
"10 فرص B2B مع why-now + buying committee.",
|
||||
"10 رسائل عربية بنبرة سعودية طبيعية.",
|
||||
"تصنيف القنوات (safe / needs_review / blocked) لكل contact.",
|
||||
"خطة متابعة 7 أيام.",
|
||||
"Proof Pack مختصر (PDF + JSON).",
|
||||
"جلسة مراجعة 30 دقيقة في نهاية الأسبوع.",
|
||||
],
|
||||
"price_sar": 499,
|
||||
"free_alternative_ar": "مجاني مقابل case study بعد انتهاء الـ Pilot.",
|
||||
"approval_required": True,
|
||||
"live_send_allowed": False,
|
||||
"duration_days": 7,
|
||||
"seats_available": 5,
|
||||
}
|
||||
|
||||
|
||||
def build_private_beta_offer(*, seats_remaining: int | None = None) -> dict[str, Any]:
|
||||
"""Build today's Private Beta offer card. Seats are configurable."""
|
||||
out = dict(PRIVATE_BETA_OFFER)
|
||||
if seats_remaining is not None:
|
||||
out["seats_available"] = max(0, int(seats_remaining))
|
||||
out["upsell_path"] = [
|
||||
"growth_os_pilot_30d",
|
||||
"growth_os_monthly",
|
||||
]
|
||||
return out
|
||||
|
||||
|
||||
def build_private_beta_safety_notes() -> dict[str, Any]:
|
||||
"""Return the explicit 'what we will NOT do today' list."""
|
||||
return {
|
||||
"title_ar": "ضمانات Dealix",
|
||||
"do_not_do_ar": [
|
||||
"لا live WhatsApp send بدون env flag + اعتماد بشري.",
|
||||
"لا live Gmail send.",
|
||||
"لا Calendar insert تلقائي.",
|
||||
"لا charge Moyasar تلقائي — invoice/payment link يدوي فقط.",
|
||||
"لا scraping LinkedIn ولا auto-DM.",
|
||||
"لا cold WhatsApp (PDPL).",
|
||||
"لا وعود بنتائج مضمونة.",
|
||||
"لا تخزين بيانات بطاقات.",
|
||||
],
|
||||
"do_ar": [
|
||||
"Approval-first في كل قناة.",
|
||||
"Audit ledger لكل فعل.",
|
||||
"Saudi Tone + Safety eval قبل أي رسالة.",
|
||||
"Reputation Guard يوقف القناة عند تدهور السمعة.",
|
||||
"Free Diagnostic قبل أي التزام.",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def private_beta_faq() -> list[dict[str, str]]:
|
||||
"""Common Arabic FAQ entries for the Private Beta page."""
|
||||
return [
|
||||
{
|
||||
"q_ar": "كيف يعمل Pilot الـ7 أيام؟",
|
||||
"a_ar": (
|
||||
"نأخذ منك intake (قطاع/مدينة/عرض/هدف) خلال 30 دقيقة. "
|
||||
"خلال 24 ساعة عمل نسلّم 10 فرص + رسائل + تصنيف القنوات. "
|
||||
"خلال الأسبوع نتابع الردود ونحدّث Proof Pack."
|
||||
),
|
||||
},
|
||||
{
|
||||
"q_ar": "هل ترسلون رسائل بدون موافقتي؟",
|
||||
"a_ar": "لا. كل رسالة تظل draft حتى توافق عليها صراحة.",
|
||||
},
|
||||
{
|
||||
"q_ar": "ماذا لو ما رد أحد؟",
|
||||
"a_ar": (
|
||||
"Proof Pack يوضح المخاطر التي منعناها + توصية بقطاع/زاوية مختلفة. "
|
||||
"Pilot يثبت طريقة التشغيل وليس عدداً مضموناً من الصفقات."
|
||||
),
|
||||
},
|
||||
{
|
||||
"q_ar": "هل تعرفون شروط واتساب ولينكدإن؟",
|
||||
"a_ar": (
|
||||
"نعم. لا cold WhatsApp بدون opt-in. "
|
||||
"لا scraping ولا auto-DM في LinkedIn — نستخدم Lead Gen Forms والمهام اليدوية."
|
||||
),
|
||||
},
|
||||
{
|
||||
"q_ar": "كيف أدفع 499 ريال؟",
|
||||
"a_ar": (
|
||||
"نرسل لك Moyasar invoice أو payment link من الـ dashboard. "
|
||||
"بعد الدفع نبدأ Pilot يوم الأحد التالي."
|
||||
),
|
||||
},
|
||||
{
|
||||
"q_ar": "هل يصلح للوكالات؟",
|
||||
"a_ar": (
|
||||
"نعم — Agency Partner Program يعطي الوكالة co-branded Proof Pack + "
|
||||
"revenue share على عملائها. تواصل معنا مباشرة للترتيب."
|
||||
),
|
||||
},
|
||||
]
|
||||
86
dealix/auto_client_acquisition/revenue_launch/__init__.py
Normal file
86
dealix/auto_client_acquisition/revenue_launch/__init__.py
Normal file
@ -0,0 +1,86 @@
|
||||
"""Revenue Launch — turn Dealix into actual paid pilots TODAY.
|
||||
|
||||
Scope:
|
||||
- offer_builder: build today's paid offers (499 Pilot, Growth OS Pilot, free case study)
|
||||
- pipeline_tracker: deterministic pipeline schema + add/update/summarize
|
||||
- outreach_sequence: build first-20 with day-by-day cadence
|
||||
- demo_closer: 12-min demo wrapper + close script + objection bank
|
||||
- pilot_delivery: 24-hour delivery template per service
|
||||
- proof_pack_template: client-facing summary
|
||||
- payment_manual_flow: Moyasar invoice/payment-link manual instructions
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from .demo_closer import (
|
||||
build_12_min_demo_flow as demo_12_min,
|
||||
build_close_script as demo_close_script,
|
||||
build_discovery_questions as demo_discovery,
|
||||
build_objection_responses as demo_objections,
|
||||
)
|
||||
from .offer_builder import (
|
||||
build_499_pilot_offer,
|
||||
build_case_study_free_offer,
|
||||
build_growth_os_pilot_offer,
|
||||
build_private_beta_offer,
|
||||
recommend_offer_for_segment,
|
||||
)
|
||||
from .payment_manual_flow import (
|
||||
build_moyasar_invoice_instructions,
|
||||
build_payment_confirmation_checklist,
|
||||
build_payment_link_message,
|
||||
)
|
||||
from .pilot_delivery import (
|
||||
build_24h_delivery_plan,
|
||||
build_client_intake_form,
|
||||
build_first_10_opportunities_delivery,
|
||||
build_growth_diagnostic_delivery,
|
||||
build_list_intelligence_delivery,
|
||||
)
|
||||
from .pipeline_tracker import (
|
||||
PIPELINE_STAGES,
|
||||
add_prospect,
|
||||
build_pipeline_schema,
|
||||
summarize_pipeline,
|
||||
update_stage,
|
||||
)
|
||||
from .proof_pack_template import (
|
||||
build_client_summary,
|
||||
build_next_step_recommendation,
|
||||
build_private_beta_proof_pack,
|
||||
)
|
||||
from .outreach_sequence import (
|
||||
build_first_20_segments_v2,
|
||||
build_followup_1,
|
||||
build_followup_2,
|
||||
build_outreach_message_v2,
|
||||
build_reply_handlers_v2,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
# offer_builder
|
||||
"build_499_pilot_offer", "build_case_study_free_offer",
|
||||
"build_growth_os_pilot_offer", "build_private_beta_offer",
|
||||
"recommend_offer_for_segment",
|
||||
# pipeline_tracker
|
||||
"PIPELINE_STAGES", "add_prospect", "build_pipeline_schema",
|
||||
"summarize_pipeline", "update_stage",
|
||||
# outreach_sequence
|
||||
"build_first_20_segments_v2", "build_followup_1",
|
||||
"build_followup_2", "build_outreach_message_v2",
|
||||
"build_reply_handlers_v2",
|
||||
# demo_closer
|
||||
"demo_12_min", "demo_close_script", "demo_discovery", "demo_objections",
|
||||
# pilot_delivery
|
||||
"build_24h_delivery_plan", "build_client_intake_form",
|
||||
"build_first_10_opportunities_delivery",
|
||||
"build_growth_diagnostic_delivery",
|
||||
"build_list_intelligence_delivery",
|
||||
# proof_pack_template
|
||||
"build_client_summary", "build_next_step_recommendation",
|
||||
"build_private_beta_proof_pack",
|
||||
# payment_manual_flow
|
||||
"build_moyasar_invoice_instructions",
|
||||
"build_payment_confirmation_checklist",
|
||||
"build_payment_link_message",
|
||||
]
|
||||
17
dealix/auto_client_acquisition/revenue_launch/demo_closer.py
Normal file
17
dealix/auto_client_acquisition/revenue_launch/demo_closer.py
Normal file
@ -0,0 +1,17 @@
|
||||
"""Demo closer — re-export single source of truth from launch_ops."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from auto_client_acquisition.launch_ops.demo_flow import (
|
||||
build_12_min_demo_flow,
|
||||
build_close_script,
|
||||
build_discovery_questions,
|
||||
build_objection_responses,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"build_12_min_demo_flow",
|
||||
"build_close_script",
|
||||
"build_discovery_questions",
|
||||
"build_objection_responses",
|
||||
]
|
||||
131
dealix/auto_client_acquisition/revenue_launch/offer_builder.py
Normal file
131
dealix/auto_client_acquisition/revenue_launch/offer_builder.py
Normal file
@ -0,0 +1,131 @@
|
||||
"""Today's paid offers — 499 Pilot, Growth OS Pilot, free case study."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
def build_499_pilot_offer() -> dict[str, Any]:
|
||||
"""The headline 499 SAR Pilot — Dealix's revenue funnel entry."""
|
||||
return {
|
||||
"offer_id": "pilot_499_7d",
|
||||
"name_ar": "Pilot 7 أيام — 499 ريال",
|
||||
"promise_ar": (
|
||||
"خلال 7 أيام: 10 فرص B2B + رسائل عربية + خطة متابعة + Proof Pack."
|
||||
),
|
||||
"deliverables_ar": [
|
||||
"10 فرص مرتبة بـ fit_score",
|
||||
"10 رسائل عربية بنبرة سعودية",
|
||||
"تصنيف القنوات (safe / needs_review / blocked)",
|
||||
"خطة متابعة 7 أيام",
|
||||
"Proof Pack مختصر (PDF + JSON)",
|
||||
"جلسة مراجعة 30 دقيقة في نهاية الأسبوع",
|
||||
],
|
||||
"price_sar": 499,
|
||||
"duration_days": 7,
|
||||
"approval_required": True,
|
||||
"live_send_allowed": False,
|
||||
"no_live_charge": True,
|
||||
"payment_method": "moyasar_invoice_or_payment_link",
|
||||
"delivery_starts": "next_sunday_after_payment",
|
||||
}
|
||||
|
||||
|
||||
def build_growth_os_pilot_offer() -> dict[str, Any]:
|
||||
"""30-day Growth OS Pilot — for serious customers."""
|
||||
return {
|
||||
"offer_id": "growth_os_pilot_30d",
|
||||
"name_ar": "Growth OS Pilot — 30 يوم",
|
||||
"promise_ar": (
|
||||
"تشغيل يومي للنمو لمدة شهر: command feed + drafts + اجتماعات + Proof Pack."
|
||||
),
|
||||
"deliverables_ar": [
|
||||
"Daily growth brief عربي",
|
||||
"First 10 Opportunities Sprint",
|
||||
"List Intelligence على قائمة العميل",
|
||||
"Email/WhatsApp drafts (بدون live send)",
|
||||
"Meeting drafts على Calendar",
|
||||
"Weekly Proof Pack",
|
||||
"تحويل لـ Growth OS Monthly بعد الإثبات",
|
||||
],
|
||||
"price_sar_min": 1500,
|
||||
"price_sar_max": 3000,
|
||||
"duration_days": 30,
|
||||
"approval_required": True,
|
||||
"no_live_charge": True,
|
||||
"payment_method": "moyasar_invoice_or_payment_link",
|
||||
}
|
||||
|
||||
|
||||
def build_case_study_free_offer() -> dict[str, Any]:
|
||||
"""Free Pilot in exchange for a case study + permission to publish."""
|
||||
return {
|
||||
"offer_id": "case_study_free_7d",
|
||||
"name_ar": "Pilot مجاني مقابل case study",
|
||||
"promise_ar": (
|
||||
"نسلّم Pilot 7 أيام مجاناً، وأنت تعطينا تصريحاً بنشر case study بدون "
|
||||
"بيانات حساسة."
|
||||
),
|
||||
"eligibility_ar": [
|
||||
"شركة سعودية أو خليجية",
|
||||
"حجم متوسط (≥10 موظفين)",
|
||||
"قرار سريع (مدير مفوّض على الرد)",
|
||||
"موافقة كتابية على نشر النتائج بدون بيانات حساسة",
|
||||
],
|
||||
"price_sar": 0,
|
||||
"case_study_required": True,
|
||||
"approval_required": True,
|
||||
"no_live_charge": True,
|
||||
}
|
||||
|
||||
|
||||
def build_private_beta_offer() -> dict[str, Any]:
|
||||
"""Re-export the Private Beta offer (single source of truth)."""
|
||||
from auto_client_acquisition.launch_ops import PRIVATE_BETA_OFFER
|
||||
return dict(PRIVATE_BETA_OFFER)
|
||||
|
||||
|
||||
def recommend_offer_for_segment(segment_id: str) -> dict[str, Any]:
|
||||
"""Map outreach segment → best-fit paid offer."""
|
||||
s = (segment_id or "").lower().strip()
|
||||
|
||||
if s == "agency_b2b":
|
||||
return {
|
||||
"primary_offer": "growth_os_pilot_30d",
|
||||
"fallback_offer": "case_study_free_7d",
|
||||
"reason_ar": (
|
||||
"وكالة → Growth OS Pilot يعطيها revenue share واضح. "
|
||||
"إذا ترددت، اعرض free case study."
|
||||
),
|
||||
}
|
||||
if s == "training_consulting":
|
||||
return {
|
||||
"primary_offer": "pilot_499_7d",
|
||||
"fallback_offer": "case_study_free_7d",
|
||||
"reason_ar": (
|
||||
"تدريب/استشارات → Pilot 499 سريع. "
|
||||
"free case study للأسماء البارزة."
|
||||
),
|
||||
}
|
||||
if s == "saas_tech_small":
|
||||
return {
|
||||
"primary_offer": "pilot_499_7d",
|
||||
"fallback_offer": "growth_os_pilot_30d",
|
||||
"reason_ar": (
|
||||
"SaaS صغيرة → Pilot 499 يكسر الجليد + ترقية لـ Growth OS Pilot."
|
||||
),
|
||||
}
|
||||
if s == "services_with_whatsapp":
|
||||
return {
|
||||
"primary_offer": "pilot_499_7d",
|
||||
"fallback_offer": "case_study_free_7d",
|
||||
"reason_ar": (
|
||||
"خدمات بقاعدة واتساب → Pilot 499 ثم WhatsApp Compliance Setup."
|
||||
),
|
||||
}
|
||||
|
||||
return {
|
||||
"primary_offer": "pilot_499_7d",
|
||||
"fallback_offer": "case_study_free_7d",
|
||||
"reason_ar": "افتراضي: Pilot 499.",
|
||||
}
|
||||
@ -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()
|
||||
@ -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,
|
||||
}
|
||||
140
dealix/auto_client_acquisition/revenue_launch/pilot_delivery.py
Normal file
140
dealix/auto_client_acquisition/revenue_launch/pilot_delivery.py
Normal file
@ -0,0 +1,140 @@
|
||||
"""24-hour pilot delivery templates per service."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
def build_client_intake_form() -> dict[str, Any]:
|
||||
"""The single intake form sent to a customer after they pay."""
|
||||
return {
|
||||
"fields": [
|
||||
{"key": "company_name", "label_ar": "اسم الشركة", "required": True},
|
||||
{"key": "website", "label_ar": "رابط الموقع", "required": True},
|
||||
{"key": "sector", "label_ar": "القطاع", "required": True},
|
||||
{"key": "city", "label_ar": "المدينة", "required": True},
|
||||
{"key": "primary_offer", "label_ar": "العرض الرئيسي", "required": True},
|
||||
{"key": "ideal_customer", "label_ar": "العميل المثالي",
|
||||
"required": True},
|
||||
{"key": "avg_deal_value_sar", "label_ar": "متوسط قيمة الصفقة",
|
||||
"required": False},
|
||||
{"key": "has_contact_list", "label_ar": "هل عندكم قائمة عملاء؟",
|
||||
"required": True, "type": "boolean"},
|
||||
{"key": "channels_available", "label_ar": "القنوات المتاحة",
|
||||
"required": True, "type": "multi"},
|
||||
{"key": "whatsapp_opt_in_status",
|
||||
"label_ar": "حالة opt-in واتساب", "required": False},
|
||||
{"key": "approval_owner",
|
||||
"label_ar": "من يوافق على الرسائل قبل الإرسال؟",
|
||||
"required": True},
|
||||
{"key": "exclusions",
|
||||
"label_ar": "شركات أو أشخاص لا نتواصل معهم",
|
||||
"required": False, "type": "list"},
|
||||
],
|
||||
"estimated_completion_minutes": 10,
|
||||
"approval_required": True,
|
||||
}
|
||||
|
||||
|
||||
def build_24h_delivery_plan(service_id: str) -> dict[str, Any]:
|
||||
"""Generic 24-hour delivery plan for any service."""
|
||||
return {
|
||||
"service_id": service_id,
|
||||
"phases": [
|
||||
{"phase": "T+0h", "label_ar": "كيك-أوف",
|
||||
"actions_ar": ["مراجعة intake + تأكيد القناة الأساسية"]},
|
||||
{"phase": "T+1h", "label_ar": "Diagnosis",
|
||||
"actions_ar": [
|
||||
"تشغيل targeting/contactability على القائمة أو القطاع",
|
||||
"تحديد buying committee + why-now",
|
||||
]},
|
||||
{"phase": "T+6h", "label_ar": "Drafting",
|
||||
"actions_ar": [
|
||||
"صياغة 10 رسائل عربية",
|
||||
"تشغيل safety + Saudi tone evals على كل رسالة",
|
||||
]},
|
||||
{"phase": "T+18h", "label_ar": "Approval Pack",
|
||||
"actions_ar": [
|
||||
"إرسال drafts للعميل في approval cards (≤3 أزرار لكل بطاقة)",
|
||||
"تحديث Action Ledger",
|
||||
]},
|
||||
{"phase": "T+24h", "label_ar": "Proof Pack v1",
|
||||
"actions_ar": [
|
||||
"تسليم Proof Pack المختصر",
|
||||
"حجز جلسة مراجعة 30 دقيقة في نهاية الأسبوع",
|
||||
]},
|
||||
],
|
||||
"approval_required": True,
|
||||
"live_send_allowed": False,
|
||||
}
|
||||
|
||||
|
||||
def build_first_10_opportunities_delivery(intake: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Service-specific delivery for First 10 Opportunities Sprint."""
|
||||
return {
|
||||
"service_id": "first_10_opportunities_sprint",
|
||||
"intake_received": bool(intake),
|
||||
"delivery_steps_ar": [
|
||||
"تشغيل account_finder على (sector, city) + offer.",
|
||||
"buyer_role_mapper لكل شركة → 1 DM + 2 influencers.",
|
||||
"explain_why_now لكل شركة (Arabic).",
|
||||
"draft_b2b_email و/أو draft_whatsapp_message حسب القناة.",
|
||||
"safety_eval + saudi_tone_eval على كل رسالة قبل التسليم.",
|
||||
"بناء follow-up sequence لـ7 أيام.",
|
||||
"Proof Pack v1 (PDF + JSON).",
|
||||
],
|
||||
"deliverables": [
|
||||
"10 opportunity cards",
|
||||
"10 Arabic messages",
|
||||
"follow-up plan",
|
||||
"Proof Pack v1",
|
||||
],
|
||||
"approval_required": True,
|
||||
}
|
||||
|
||||
|
||||
def build_list_intelligence_delivery(intake: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Service-specific delivery for List Intelligence."""
|
||||
return {
|
||||
"service_id": "list_intelligence",
|
||||
"intake_received": bool(intake),
|
||||
"delivery_steps_ar": [
|
||||
"تنظيف الـ CSV + dedupe.",
|
||||
"classify_source لكل صف.",
|
||||
"evaluate_contactability + allowed_channels لكل contact.",
|
||||
"تقسيم القائمة: safe / needs_review / blocked.",
|
||||
"اختيار أفضل 50 target.",
|
||||
"كتابة رسائل عربية للقطاع المهيمن.",
|
||||
"Risk report + retention recommendation.",
|
||||
],
|
||||
"deliverables": [
|
||||
"Cleaned CSV",
|
||||
"Top 50 targets",
|
||||
"Arabic messages per segment",
|
||||
"Risk report",
|
||||
"Channel mix recommendation",
|
||||
],
|
||||
"approval_required": True,
|
||||
}
|
||||
|
||||
|
||||
def build_growth_diagnostic_delivery(intake: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Free 24-hour growth diagnostic delivery."""
|
||||
return {
|
||||
"service_id": "free_growth_diagnostic",
|
||||
"intake_received": bool(intake),
|
||||
"delivery_steps_ar": [
|
||||
"تشغيل recommend_accounts(sector, city) → 3 فرص.",
|
||||
"كتابة رسالة عربية واحدة جاهزة.",
|
||||
"تقرير risk سريع (واتساب opt-in / domain reputation / channel mix).",
|
||||
"توصية بالخدمة المدفوعة الأنسب (Pilot 499 / Growth OS Pilot).",
|
||||
],
|
||||
"deliverables": [
|
||||
"3 opportunities",
|
||||
"1 Arabic message",
|
||||
"Risk note",
|
||||
"Paid pilot recommendation",
|
||||
],
|
||||
"delivery_time": "خلال 24 ساعة عمل",
|
||||
"approval_required": True,
|
||||
}
|
||||
@ -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}%",
|
||||
],
|
||||
}
|
||||
@ -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,
|
||||
}
|
||||
@ -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",
|
||||
]
|
||||
|
||||
@ -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",
|
||||
]
|
||||
@ -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
|
||||
@ -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).
|
||||
|
||||
---
|
||||
|
||||
|
||||
202
dealix/docs/REVENUE_TODAY_PLAYBOOK.md
Normal file
202
dealix/docs/REVENUE_TODAY_PLAYBOOK.md
Normal file
@ -0,0 +1,202 @@
|
||||
# Revenue Today Playbook — تحويل Dealix إلى دخل اليوم
|
||||
|
||||
> **القاعدة:** الهدف اليوم ليس إطلاق عام. الهدف **أول 499 ريال أو commitment** عبر Private Beta + Pilot 7 أيام.
|
||||
|
||||
---
|
||||
|
||||
## 1. العروض المدفوعة المتاحة اليوم
|
||||
|
||||
### Pilot 7 أيام — 499 ريال (الأساسي)
|
||||
- 10 فرص B2B + رسائل عربية + خطة متابعة + Proof Pack.
|
||||
- بدائل: مجاني مقابل case study.
|
||||
- مدة التسليم: 7 أيام، تبدأ يوم الأحد بعد الدفع.
|
||||
|
||||
### Growth OS Pilot — 1,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 (ما يحسّن الخدمة).
|
||||
120
dealix/landing/growth-os.html
Normal file
120
dealix/landing/growth-os.html
Normal file
@ -0,0 +1,120 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ar" dir="rtl">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Dealix Growth OS — اشتراك شهري</title>
|
||||
<style>
|
||||
body { font-family: 'Tajawal', sans-serif; background: #0f1115; color: #f5f5f7;
|
||||
line-height: 1.7; margin: 0; }
|
||||
header { padding: 56px 24px; text-align: center;
|
||||
background: linear-gradient(135deg, #1a1d27 0%, #0f1115 100%); }
|
||||
header h1 { font-size: 44px; line-height: 1.2; margin-bottom: 14px; }
|
||||
header h1 span { color: #ffd166; }
|
||||
header p { color: #a8aab2; max-width: 720px; margin: 0 auto; font-size: 19px; }
|
||||
main { max-width: 900px; margin: 0 auto; padding: 24px; }
|
||||
section { background: #181b22; padding: 28px; border-radius: 14px;
|
||||
margin: 18px 0; border: 1px solid #232631; }
|
||||
section h2 { color: #ffd166; margin-bottom: 14px; font-size: 22px; }
|
||||
ul li { padding: 6px 0; }
|
||||
ul li:before { content: "▸"; color: #06d6a0; margin-left: 8px; }
|
||||
.pricing { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
gap: 14px; margin-top: 14px; }
|
||||
.price-card { padding: 18px; background: #1f232c; border-radius: 10px;
|
||||
border: 1px solid #2a2e39; }
|
||||
.price-card .label { color: #a8aab2; font-size: 14px; }
|
||||
.price-card .price { font-size: 26px; font-weight: 700; color: #ffd166; margin: 6px 0; }
|
||||
.cta { display: inline-block; padding: 14px 28px; background: #ffd166;
|
||||
color: #0f1115; border-radius: 10px; font-weight: 700; text-decoration: none;
|
||||
font-size: 17px; margin: 8px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Dealix <span>Growth OS</span></h1>
|
||||
<p>منصة نمو شهرية تدير قنواتك الخارجية، تجمع كل الإشارات في Command Feed،
|
||||
تكتب الرسائل، تطلب موافقات، ترتب اجتماعات، وتطلع Proof Pack شهري.</p>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section>
|
||||
<h2>ماذا تستلم شهرياً؟</h2>
|
||||
<ul>
|
||||
<li>Daily Command Feed عربي — 5 cards/day.</li>
|
||||
<li>First 10 Opportunities Sprint كل أسبوع.</li>
|
||||
<li>List Intelligence على قاعدة عملائك.</li>
|
||||
<li>Email + WhatsApp drafts (بدون live send بدون اعتماد).</li>
|
||||
<li>Calendar drafts + meeting briefs.</li>
|
||||
<li>Approval Center: CEO يوافق من واتساب.</li>
|
||||
<li>Proof Pack شهري + Founder Shadow Board أسبوعي.</li>
|
||||
<li>Reputation Guard على كل قناة.</li>
|
||||
<li>Service Excellence Score على كل campaign.</li>
|
||||
<li>Decision Memory يتعلم من Accept/Skip/Edit.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>التسعير</h2>
|
||||
<div class="pricing">
|
||||
<div class="price-card">
|
||||
<div class="label">Pilot 30 يوم</div>
|
||||
<div class="price">1,500–3,000 ريال</div>
|
||||
<div>إعداد + شهر تجربة</div>
|
||||
</div>
|
||||
<div class="price-card">
|
||||
<div class="label">Growth OS Monthly</div>
|
||||
<div class="price">2,999 ريال</div>
|
||||
<div>شهري — بعد الإثبات</div>
|
||||
</div>
|
||||
<div class="price-card">
|
||||
<div class="label">Annual (–15%)</div>
|
||||
<div class="price">30,589 ريال</div>
|
||||
<div>دفع سنوي — توفير شهرين</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>التكاملات (Phase 1)</h2>
|
||||
<ul>
|
||||
<li>Gmail (drafts فقط افتراضياً)</li>
|
||||
<li>Google Calendar (drafts فقط)</li>
|
||||
<li>WhatsApp Cloud (مع opt-in + approval)</li>
|
||||
<li>Moyasar (invoice/payment link manual)</li>
|
||||
<li>Google Sheets (read/append بموافقة)</li>
|
||||
<li>Website Forms (ingest)</li>
|
||||
</ul>
|
||||
<p>المرحلة 2: LinkedIn Lead Forms، Google Business Profile، Google Meet transcripts.</p>
|
||||
<p>المرحلة 3: Instagram, X (ingest only), social drafts.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>الأمان والامتثال</h2>
|
||||
<ul>
|
||||
<li>Approval-first في كل قناة — لا live send بدون اعتماد بشري.</li>
|
||||
<li>PDPL-aware: لا cold WhatsApp، DPA draft جاهز.</li>
|
||||
<li>Secret redactor + patch firewall + trace redactor.</li>
|
||||
<li>Saudi Tone + Safety eval قبل كل رسالة.</li>
|
||||
<li>Reputation Guard يوقف القناة عند تدهور السمعة.</li>
|
||||
<li>Action Ledger يسجّل كل فعل + من اعتمده.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>كيف تبدأ؟</h2>
|
||||
<ol style="padding-right: 18px;">
|
||||
<li>Free Growth Diagnostic — 24 ساعة بدون التزام.</li>
|
||||
<li>Pilot 7 أيام بـ499 ريال — تثبت طريقة التشغيل.</li>
|
||||
<li>Growth OS Pilot 30 يوم — تشغيل شهر كامل.</li>
|
||||
<li>Growth OS Monthly — التزام مستمر مع Proof Pack شهري.</li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<div style="text-align: center; padding: 24px;">
|
||||
<a href="free-diagnostic.html" class="cta">ابدأ بالتشخيص المجاني</a>
|
||||
<a href="mailto:bassam.m.assiri@gmail.com?subject=Dealix%20Growth%20OS"
|
||||
class="cta" style="background: transparent; color: #f5f5f7; border: 1px solid #383b44;">احجز ديمو</a>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
87
dealix/landing/list-intelligence.html
Normal file
87
dealix/landing/list-intelligence.html
Normal file
@ -0,0 +1,87 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ar" dir="rtl">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Dealix — تحليل القوائم (List Intelligence)</title>
|
||||
<style>
|
||||
body { font-family: 'Tajawal', sans-serif; background: #0f1115; color: #f5f5f7;
|
||||
line-height: 1.7; margin: 0; }
|
||||
header { padding: 56px 24px; text-align: center;
|
||||
background: linear-gradient(135deg, #1a1d27 0%, #0f1115 100%); }
|
||||
header h1 { font-size: 42px; line-height: 1.2; margin-bottom: 14px; }
|
||||
header h1 span { color: #ffd166; }
|
||||
header p { color: #a8aab2; max-width: 700px; margin: 0 auto; font-size: 18px; }
|
||||
main { max-width: 800px; margin: 0 auto; padding: 24px; }
|
||||
section { background: #181b22; padding: 28px; border-radius: 14px;
|
||||
margin: 18px 0; border: 1px solid #232631; }
|
||||
section h2 { color: #ffd166; margin-bottom: 14px; font-size: 22px; }
|
||||
ul li { padding: 6px 0; }
|
||||
ul li:before { content: "✓"; color: #06d6a0; margin-left: 8px; }
|
||||
.cta { display: inline-block; padding: 14px 28px; background: #ffd166;
|
||||
color: #0f1115; border-radius: 10px; font-weight: 700; text-decoration: none;
|
||||
font-size: 17px; margin: 8px; }
|
||||
.price { font-size: 28px; font-weight: 700; color: #ffd166; margin: 14px 0; }
|
||||
.note { background: rgba(6, 214, 160, 0.1); padding: 14px; border-radius: 10px;
|
||||
border: 1px solid rgba(6, 214, 160, 0.3); color: #06d6a0; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>تحليل قوائمكم — <span>List Intelligence</span></h1>
|
||||
<p>ارفع قائمتك (CSV من العملاء، إيميلات، أرقام واتساب). نظف، صنف، وحدد أفضل
|
||||
50 هدف + رسائل عربية + خطة 7 أيام — بدون أي إرسال.</p>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section>
|
||||
<h2>كيف تعمل؟</h2>
|
||||
<ul>
|
||||
<li>ارفع CSV مع الحقول الأساسية (اسم/شركة/إيميل/هاتف/مصدر).</li>
|
||||
<li>Dealix ينظّف ويحذف التكرار.</li>
|
||||
<li>يصنّف كل صف حسب المصدر (CRM / inbound / event / cold list / opt-out).</li>
|
||||
<li>يحدد الـ contactability: safe / needs_review / blocked.</li>
|
||||
<li>يعطيك أفضل 50 هدف + القناة الأفضل لكل واحد.</li>
|
||||
<li>يكتب رسائل عربية للقطاع المهيمن.</li>
|
||||
<li>يعطي تقرير مخاطر تلقائي (PDPL + سمعة القناة).</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>المخرجات</h2>
|
||||
<ul>
|
||||
<li>Cleaned CSV (مع علامة على كل صف: safe/review/blocked).</li>
|
||||
<li>Top 50 targets للأسبوع القادم.</li>
|
||||
<li>رسائل عربية لكل segment.</li>
|
||||
<li>Channel mix recommendation (إيميل / LinkedIn Lead Form / واتساب opt-in).</li>
|
||||
<li>Risk report — لماذا 8 صفوف blocked، ولماذا 30 يحتاجون مراجعة.</li>
|
||||
</ul>
|
||||
<div class="price">499 – 1,500 ريال</div>
|
||||
</section>
|
||||
|
||||
<section class="note">
|
||||
<strong>ضمان Dealix:</strong>
|
||||
نطبّق contactability قبل أي توصية بقناة. لا cold WhatsApp.
|
||||
كل البيانات الحساسة محمية بـ secret_redactor قبل أي trace.
|
||||
Retention: 6 أشهر افتراضياً، تُحذف عند الطلب.
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>للمن؟</h2>
|
||||
<ul>
|
||||
<li>شركات لديها قاعدة عملاء قدامى لم تُحدَّث منذ 6+ أشهر.</li>
|
||||
<li>وكالات استلمت قوائم من عميل وتحتاج تنظيفاً.</li>
|
||||
<li>عيادات/متاجر/عقار — قاعدة واتساب مزدحمة بدون opt-in واضح.</li>
|
||||
<li>SaaS/تدريب — قائمة مؤتمر أو event يحتاجون تأهيلاً.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<div style="text-align: center; padding: 24px;">
|
||||
<a href="mailto:bassam.m.assiri@gmail.com?subject=Dealix%20List%20Intelligence"
|
||||
class="cta">ابدأ تحليل قائمتك</a>
|
||||
<a href="services.html" class="cta"
|
||||
style="background: transparent; color: #f5f5f7; border: 1px solid #383b44;">شاهد كل الخدمات</a>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
232
dealix/scripts/launch_readiness_check.py
Normal file
232
dealix/scripts/launch_readiness_check.py
Normal file
@ -0,0 +1,232 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Dealix Launch Readiness — 10-gate Go/No-Go check.
|
||||
|
||||
Runs locally + against an optional staging URL. Reports which gates pass/fail
|
||||
and what the next concrete actions are.
|
||||
|
||||
Usage:
|
||||
python scripts/launch_readiness_check.py
|
||||
python scripts/launch_readiness_check.py --staging-url https://staging.example
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
|
||||
SECRET_PATTERNS = (
|
||||
r"ghp_[A-Za-z0-9]{20,}",
|
||||
r"github_pat_[A-Za-z0-9_]{20,}",
|
||||
r"sk-[A-Za-z0-9]{30,}",
|
||||
r"sk-ant-[A-Za-z0-9_\-]{20,}",
|
||||
r"AKIA[A-Z0-9]{16}",
|
||||
r"AIza[A-Za-z0-9_\-]{30,}",
|
||||
r"EAA[A-Za-z0-9]{30,}",
|
||||
r"-----BEGIN (?:RSA |EC |OPENSSH |)PRIVATE KEY-----",
|
||||
)
|
||||
|
||||
EXCLUDE_DIRS = (".git", ".venv", "node_modules", "__pycache__", ".pytest_cache")
|
||||
|
||||
|
||||
def gate_tests_passed() -> tuple[bool, str]:
|
||||
"""Run pytest with --noconftest on the new layer tests as a quick proxy."""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[sys.executable, "-m", "pytest",
|
||||
"tests/unit/test_launch_ops.py",
|
||||
"tests/unit/test_revenue_launch.py",
|
||||
"tests/unit/test_security_curator.py",
|
||||
"--noconftest", "--no-cov", "-q", "-p", "no:cacheprovider"],
|
||||
cwd=REPO_ROOT, capture_output=True, text=True, timeout=60,
|
||||
)
|
||||
ok = result.returncode == 0
|
||||
last_line = (result.stdout or "").strip().splitlines()[-1:]
|
||||
msg = last_line[0] if last_line else "no output"
|
||||
return ok, msg
|
||||
except Exception as exc: # noqa: BLE001
|
||||
return False, f"pytest error: {exc}"
|
||||
|
||||
|
||||
def gate_routes_check() -> tuple[bool, str]:
|
||||
"""Run scripts/print_routes.py — should not raise."""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[sys.executable, "scripts/print_routes.py"],
|
||||
cwd=REPO_ROOT, capture_output=True, text=True, timeout=30,
|
||||
)
|
||||
if result.returncode == 0:
|
||||
n_routes = result.stdout.count("/api/v1")
|
||||
return True, f"{n_routes} v1 routes"
|
||||
return False, f"exit={result.returncode}"
|
||||
except Exception as exc: # noqa: BLE001
|
||||
return False, f"err: {exc}"
|
||||
|
||||
|
||||
def gate_no_secrets() -> tuple[bool, str]:
|
||||
"""Scan repo for secret patterns. Skips known-safe directories."""
|
||||
findings: list[str] = []
|
||||
pat = re.compile("|".join(SECRET_PATTERNS))
|
||||
for path in REPO_ROOT.rglob("*"):
|
||||
if not path.is_file():
|
||||
continue
|
||||
if any(part in EXCLUDE_DIRS for part in path.parts):
|
||||
continue
|
||||
# Skip docs that intentionally mention patterns as examples.
|
||||
if path.suffix in {".md", ".lock", ".pyc", ".png", ".jpg", ".jpeg",
|
||||
".gif", ".woff", ".woff2", ".ttf"}:
|
||||
continue
|
||||
try:
|
||||
text = path.read_text(encoding="utf-8", errors="ignore")
|
||||
except Exception: # noqa: BLE001
|
||||
continue
|
||||
if pat.search(text):
|
||||
findings.append(str(path.relative_to(REPO_ROOT)))
|
||||
if len(findings) >= 3:
|
||||
break
|
||||
return (not findings), (
|
||||
"clean" if not findings else f"FOUND in: {', '.join(findings)}"
|
||||
)
|
||||
|
||||
|
||||
def gate_staging_health(staging_url: str | None) -> tuple[bool, str]:
|
||||
"""Hit /health on staging if a URL is provided."""
|
||||
if not staging_url:
|
||||
return False, "no --staging-url provided"
|
||||
url = staging_url.rstrip("/") + "/health"
|
||||
try:
|
||||
req = urllib.request.Request(url, headers={"User-Agent": "Dealix/Readiness"})
|
||||
with urllib.request.urlopen(req, timeout=10) as resp: # nosec
|
||||
return resp.status == 200, f"status={resp.status}"
|
||||
except Exception as exc: # noqa: BLE001
|
||||
return False, f"err: {exc}"
|
||||
|
||||
|
||||
def gate_supabase_staging() -> tuple[bool, str]:
|
||||
"""We can only check whether SUPABASE_URL is configured, not connectivity."""
|
||||
if os.getenv("SUPABASE_URL") and os.getenv("SUPABASE_SERVICE_ROLE_KEY"):
|
||||
return True, "env vars configured"
|
||||
return False, "SUPABASE_URL or SERVICE_ROLE_KEY not set in env"
|
||||
|
||||
|
||||
def gate_service_catalog(staging_url: str | None) -> tuple[bool, str]:
|
||||
if not staging_url:
|
||||
return False, "no --staging-url provided"
|
||||
url = staging_url.rstrip("/") + "/api/v1/services/catalog"
|
||||
try:
|
||||
req = urllib.request.Request(url, headers={"User-Agent": "Dealix/Readiness"})
|
||||
with urllib.request.urlopen(req, timeout=10) as resp: # nosec
|
||||
data = json.loads(resp.read().decode("utf-8", errors="ignore"))
|
||||
total = int(data.get("total", 0))
|
||||
return total >= 4, f"{total} services"
|
||||
except Exception as exc: # noqa: BLE001
|
||||
return False, f"err: {exc}"
|
||||
|
||||
|
||||
def gate_private_beta_page() -> tuple[bool, str]:
|
||||
p = REPO_ROOT / "landing" / "private-beta.html"
|
||||
if not p.exists():
|
||||
return False, "missing"
|
||||
text = p.read_text(encoding="utf-8", errors="ignore")
|
||||
has_cta = ("احجز" in text) or ("ابدأ" in text) or ("احصل" in text)
|
||||
has_pilot = ("Pilot" in text) or ("بايلوت" in text)
|
||||
return (has_cta and has_pilot), (
|
||||
"ok" if has_cta and has_pilot else "missing CTA or Pilot mention"
|
||||
)
|
||||
|
||||
|
||||
def gate_first_20_ready() -> tuple[bool, str]:
|
||||
"""Soft check: a tracker doc/sheet may exist as evidence."""
|
||||
candidates = [
|
||||
REPO_ROOT / "docs" / "FIRST_20_OUTREACH_MESSAGES.md",
|
||||
REPO_ROOT / "docs" / "REVENUE_TODAY_PLAYBOOK.md",
|
||||
]
|
||||
found = [str(c.relative_to(REPO_ROOT)) for c in candidates if c.exists()]
|
||||
return bool(found), ", ".join(found) or "no first-20 doc/sheet found"
|
||||
|
||||
|
||||
def gate_live_sends_disabled() -> tuple[bool, str]:
|
||||
"""Verify env flags for live sends are NOT set to true."""
|
||||
flags = [
|
||||
"WHATSAPP_ALLOW_LIVE_SEND",
|
||||
"GMAIL_ALLOW_LIVE_SEND",
|
||||
"CALENDAR_ALLOW_LIVE_INSERT",
|
||||
"MOYASAR_ALLOW_LIVE_CHARGE",
|
||||
"GBP_ALLOW_LIVE_REPLY",
|
||||
]
|
||||
enabled = [f for f in flags if os.getenv(f, "false").lower() == "true"]
|
||||
return (not enabled), (
|
||||
"all disabled" if not enabled else f"ENABLED: {', '.join(enabled)}"
|
||||
)
|
||||
|
||||
|
||||
def gate_payment_manual_ready() -> tuple[bool, str]:
|
||||
"""Soft check: payment-manual flow module is present + accessible."""
|
||||
p = REPO_ROOT / "auto_client_acquisition" / "revenue_launch" / "payment_manual_flow.py"
|
||||
return p.exists(), ("module present" if p.exists() else "missing")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--staging-url", default=None,
|
||||
help="Optional staging URL for live checks")
|
||||
parser.add_argument("--json", action="store_true",
|
||||
help="Emit JSON instead of pretty output")
|
||||
args = parser.parse_args()
|
||||
|
||||
print("Dealix Launch Readiness — 10 Gates")
|
||||
print("─" * 60)
|
||||
|
||||
gates = [
|
||||
("tests_passed", gate_tests_passed()),
|
||||
("routes_check", gate_routes_check()),
|
||||
("no_secrets", gate_no_secrets()),
|
||||
("staging_health", gate_staging_health(args.staging_url)),
|
||||
("supabase_staging", gate_supabase_staging()),
|
||||
("service_catalog", gate_service_catalog(args.staging_url)),
|
||||
("private_beta_page", gate_private_beta_page()),
|
||||
("first_20_ready", gate_first_20_ready()),
|
||||
("live_sends_disabled", gate_live_sends_disabled()),
|
||||
("payment_manual_ready", gate_payment_manual_ready()),
|
||||
]
|
||||
|
||||
passed = sum(1 for _, (ok, _) in gates if ok)
|
||||
total = len(gates)
|
||||
pct = round(100 * passed / total, 1)
|
||||
|
||||
if args.json:
|
||||
out = {
|
||||
"passed": passed, "total": total, "pct": pct,
|
||||
"gates": [{"id": gid, "passed": ok, "info": info}
|
||||
for gid, (ok, info) in gates],
|
||||
}
|
||||
print(json.dumps(out, ensure_ascii=False, indent=2))
|
||||
else:
|
||||
for gid, (ok, info) in gates:
|
||||
mark = "✅" if ok else "🔴"
|
||||
print(f"{mark} {gid:<24} {info}")
|
||||
print("─" * 60)
|
||||
critical = ("no_secrets", "live_sends_disabled", "staging_health")
|
||||
critical_failed = [gid for gid, (ok, _) in gates
|
||||
if gid in critical and not ok]
|
||||
if critical_failed:
|
||||
verdict = f"🔴 NO-GO — critical gates failed: {', '.join(critical_failed)}"
|
||||
elif pct >= 70:
|
||||
verdict = f"✅ GO (Private Beta) — {passed}/{total} = {pct}%"
|
||||
else:
|
||||
verdict = f"🟡 FIX-THEN-GO — only {passed}/{total} = {pct}%"
|
||||
print(verdict)
|
||||
|
||||
return 0 if passed == total else (1 if passed < 7 else 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
204
dealix/tests/unit/test_launch_ops.py
Normal file
204
dealix/tests/unit/test_launch_ops.py
Normal file
@ -0,0 +1,204 @@
|
||||
"""Unit tests for Launch Ops."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from auto_client_acquisition.launch_ops import (
|
||||
PRIVATE_BETA_OFFER,
|
||||
build_12_min_demo_flow,
|
||||
build_close_script,
|
||||
build_daily_launch_scorecard,
|
||||
build_discovery_questions,
|
||||
build_first_20_segments,
|
||||
build_followup_message,
|
||||
build_launch_readiness,
|
||||
build_objection_responses,
|
||||
build_outreach_message,
|
||||
build_private_beta_offer,
|
||||
build_private_beta_safety_notes,
|
||||
build_reply_handlers,
|
||||
build_weekly_launch_scorecard,
|
||||
decide_go_no_go,
|
||||
private_beta_faq,
|
||||
record_launch_event,
|
||||
)
|
||||
|
||||
|
||||
# ── Private Beta ─────────────────────────────────────────────
|
||||
def test_private_beta_offer_has_essentials():
|
||||
o = build_private_beta_offer()
|
||||
assert o["price_sar"] == 499
|
||||
assert o["duration_days"] == 7
|
||||
assert o["live_send_allowed"] is False
|
||||
assert o["approval_required"] is True
|
||||
assert len(o["deliverables_ar"]) >= 4
|
||||
|
||||
|
||||
def test_private_beta_offer_seats_override():
|
||||
o = build_private_beta_offer(seats_remaining=2)
|
||||
assert o["seats_available"] == 2
|
||||
|
||||
|
||||
def test_private_beta_safety_notes_blocks_live():
|
||||
s = build_private_beta_safety_notes()
|
||||
text = " ".join(s["do_not_do_ar"])
|
||||
assert "live" in text.lower() or "عشوائي" in text or "تلقائي" in text
|
||||
assert any("PDPL" in line for line in s["do_not_do_ar"])
|
||||
|
||||
|
||||
def test_private_beta_faq_arabic():
|
||||
faq = private_beta_faq()
|
||||
assert len(faq) >= 4
|
||||
for item in faq:
|
||||
assert any("" <= ch <= "ۿ" for ch in item["q_ar"])
|
||||
assert any("" <= ch <= "ۿ" for ch in item["a_ar"])
|
||||
|
||||
|
||||
# ── Demo Flow ────────────────────────────────────────────────
|
||||
def test_demo_flow_is_12_minutes():
|
||||
f = build_12_min_demo_flow()
|
||||
assert f["duration_minutes"] == 12
|
||||
assert len(f["minute_by_minute_ar"]) == 6
|
||||
|
||||
|
||||
def test_demo_discovery_has_5_questions():
|
||||
out = build_discovery_questions()
|
||||
assert len(out) == 5
|
||||
|
||||
|
||||
def test_objection_responses_cover_essentials():
|
||||
out = build_objection_responses()
|
||||
for k in ("price", "timing", "trust", "complexity", "data_privacy"):
|
||||
assert k in out
|
||||
|
||||
|
||||
def test_close_script_arabic():
|
||||
out = build_close_script()
|
||||
assert len(out["close_sequence_ar"]) >= 3
|
||||
assert any("" <= ch <= "ۿ" for ch in out["close_template_ar"])
|
||||
|
||||
|
||||
# ── Outreach ─────────────────────────────────────────────────
|
||||
def test_first_20_has_4_segments_total_20():
|
||||
out = build_first_20_segments()
|
||||
assert out["total_targets"] == 20
|
||||
assert len(out["segments"]) == 4
|
||||
assert sum(s["count"] for s in out["segments"]) == 20
|
||||
|
||||
|
||||
def test_outreach_message_is_arabic_and_drafts_only():
|
||||
out = build_outreach_message("agency_b2b", name="أحمد")
|
||||
assert any("" <= ch <= "ۿ" for ch in out["body_ar"])
|
||||
assert out["live_send_allowed"] is False
|
||||
|
||||
|
||||
def test_outreach_unknown_segment_falls_back():
|
||||
out = build_outreach_message("totally_unknown", name="X")
|
||||
assert out["body_ar"]
|
||||
|
||||
|
||||
def test_followup_step_2_different_from_1():
|
||||
s1 = build_followup_message("training_consulting", step=1, name="X")
|
||||
s2 = build_followup_message("training_consulting", step=2, name="X")
|
||||
assert s1["body_ar"] != s2["body_ar"]
|
||||
|
||||
|
||||
def test_followup_step_3_archives():
|
||||
s3 = build_followup_message("agency_b2b", step=3, name="X")
|
||||
assert s3["kind"] == "followup_3_final"
|
||||
|
||||
|
||||
def test_reply_handlers_include_critical():
|
||||
h = build_reply_handlers()
|
||||
for k in ("interested", "needs_more_info", "price_objection",
|
||||
"not_now", "no_thanks", "unsubscribe"):
|
||||
assert k in h
|
||||
|
||||
|
||||
# ── Go / No-Go ───────────────────────────────────────────────
|
||||
def test_readiness_all_false_returns_zero_pct():
|
||||
r = build_launch_readiness(statuses={})
|
||||
assert r["passed_pct"] == 0.0
|
||||
assert r["passed_gates"] == 0
|
||||
assert len(r["blockers_ar"]) == r["total_gates"]
|
||||
|
||||
|
||||
def test_readiness_all_true_returns_full_pct():
|
||||
statuses = {gate["id"]: True for gate in
|
||||
__import__("auto_client_acquisition.launch_ops",
|
||||
fromlist=["LAUNCH_GATES"]).go_no_go.LAUNCH_GATES}
|
||||
r = build_launch_readiness(statuses=statuses)
|
||||
assert r["passed_pct"] == 100.0
|
||||
assert r["passed_gates"] == r["total_gates"]
|
||||
|
||||
|
||||
def test_go_no_go_blocks_when_no_secrets_fails():
|
||||
decision = decide_go_no_go(statuses={"tests_passed": True,
|
||||
"routes_check": True,
|
||||
"no_secrets": False,
|
||||
"staging_health": True,
|
||||
"live_sends_disabled": True})
|
||||
assert decision["verdict"] == "no_go"
|
||||
|
||||
|
||||
def test_go_no_go_blocks_when_live_sends_enabled():
|
||||
decision = decide_go_no_go(statuses={"tests_passed": True,
|
||||
"routes_check": True,
|
||||
"no_secrets": True,
|
||||
"staging_health": True,
|
||||
"live_sends_disabled": False})
|
||||
assert decision["verdict"] == "no_go"
|
||||
|
||||
|
||||
def test_go_no_go_passes_with_critical_and_70pct():
|
||||
statuses = {
|
||||
"tests_passed": True, "routes_check": True, "no_secrets": True,
|
||||
"staging_health": True, "supabase_staging": True,
|
||||
"service_catalog": True, "private_beta_page": True,
|
||||
"first_20_ready": True, "live_sends_disabled": True,
|
||||
"payment_manual_ready": False, # 9/10 = 90%
|
||||
}
|
||||
decision = decide_go_no_go(statuses=statuses)
|
||||
assert decision["verdict"] == "go"
|
||||
|
||||
|
||||
# ── Scorecard ────────────────────────────────────────────────
|
||||
def test_record_event_unknown_raises():
|
||||
with pytest.raises(ValueError):
|
||||
record_launch_event(event_type="totally_invalid")
|
||||
|
||||
|
||||
def test_record_event_appends_to_log():
|
||||
log: list = []
|
||||
record_launch_event(event_type="outreach_sent", event_log=log)
|
||||
assert len(log) == 1
|
||||
assert log[0]["event_type"] == "outreach_sent"
|
||||
|
||||
|
||||
def test_daily_scorecard_aggregates():
|
||||
events = [{"event_type": "outreach_sent"}] * 12 + \
|
||||
[{"event_type": "demo_booked"}] * 2
|
||||
s = build_daily_launch_scorecard(events=events)
|
||||
assert s["metrics"]["outreach_sent"] == 12
|
||||
assert s["metrics"]["demo_booked"] == 2
|
||||
assert s["progress"]["outreach_sent"]["pct"] == 60.0 # 12/20 = 60%
|
||||
|
||||
|
||||
def test_weekly_scorecard_returns_verdict():
|
||||
events = [{"event_type": "outreach_sent"}] * 50 + \
|
||||
[{"event_type": "pilot_paid"}] * 2
|
||||
s = build_weekly_launch_scorecard(events=events)
|
||||
assert s["verdict"] == "on_track"
|
||||
|
||||
|
||||
def test_weekly_scorecard_needs_focus_for_low_demos():
|
||||
events = [{"event_type": "outreach_sent"}] * 5
|
||||
s = build_weekly_launch_scorecard(events=events)
|
||||
assert s["verdict"] == "needs_focus"
|
||||
|
||||
|
||||
# ── Constants exposed ────────────────────────────────────────
|
||||
def test_private_beta_offer_constant_exposed():
|
||||
assert PRIVATE_BETA_OFFER["price_sar"] == 499
|
||||
assert PRIVATE_BETA_OFFER["live_send_allowed"] is False
|
||||
245
dealix/tests/unit/test_revenue_launch.py
Normal file
245
dealix/tests/unit/test_revenue_launch.py
Normal file
@ -0,0 +1,245 @@
|
||||
"""Unit tests for Revenue Launch."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from auto_client_acquisition.revenue_launch import (
|
||||
PIPELINE_STAGES,
|
||||
add_prospect,
|
||||
build_24h_delivery_plan,
|
||||
build_499_pilot_offer,
|
||||
build_case_study_free_offer,
|
||||
build_client_intake_form,
|
||||
build_client_summary,
|
||||
build_first_10_opportunities_delivery,
|
||||
build_first_20_segments_v2,
|
||||
build_followup_1,
|
||||
build_followup_2,
|
||||
build_growth_diagnostic_delivery,
|
||||
build_growth_os_pilot_offer,
|
||||
build_list_intelligence_delivery,
|
||||
build_moyasar_invoice_instructions,
|
||||
build_next_step_recommendation,
|
||||
build_outreach_message_v2,
|
||||
build_payment_confirmation_checklist,
|
||||
build_payment_link_message,
|
||||
build_pipeline_schema,
|
||||
build_private_beta_offer,
|
||||
build_private_beta_proof_pack,
|
||||
build_reply_handlers_v2,
|
||||
recommend_offer_for_segment,
|
||||
summarize_pipeline,
|
||||
update_stage,
|
||||
)
|
||||
|
||||
|
||||
# ── Offers ───────────────────────────────────────────────────
|
||||
def test_499_pilot_has_correct_price():
|
||||
o = build_499_pilot_offer()
|
||||
assert o["price_sar"] == 499
|
||||
assert o["live_send_allowed"] is False
|
||||
assert o["no_live_charge"] is True
|
||||
|
||||
|
||||
def test_growth_os_pilot_30_days():
|
||||
o = build_growth_os_pilot_offer()
|
||||
assert o["duration_days"] == 30
|
||||
assert o["price_sar_min"] == 1500
|
||||
assert o["price_sar_max"] == 3000
|
||||
|
||||
|
||||
def test_case_study_free_requires_consent():
|
||||
o = build_case_study_free_offer()
|
||||
assert o["price_sar"] == 0
|
||||
assert o["case_study_required"] is True
|
||||
|
||||
|
||||
def test_recommend_offer_for_agency():
|
||||
out = recommend_offer_for_segment("agency_b2b")
|
||||
assert out["primary_offer"] == "growth_os_pilot_30d"
|
||||
|
||||
|
||||
def test_recommend_offer_for_training():
|
||||
out = recommend_offer_for_segment("training_consulting")
|
||||
assert out["primary_offer"] == "pilot_499_7d"
|
||||
|
||||
|
||||
def test_recommend_offer_unknown_segment_default():
|
||||
out = recommend_offer_for_segment("totally_unknown")
|
||||
assert out["primary_offer"] == "pilot_499_7d"
|
||||
|
||||
|
||||
def test_private_beta_offer_re_export():
|
||||
o = build_private_beta_offer()
|
||||
assert o["price_sar"] == 499
|
||||
|
||||
|
||||
# ── Pipeline ─────────────────────────────────────────────────
|
||||
def test_pipeline_schema_has_8_stages():
|
||||
s = build_pipeline_schema()
|
||||
assert len(s["stages"]) == 8
|
||||
assert "paid" in s["stages"]
|
||||
assert "lost" in s["stages"]
|
||||
|
||||
|
||||
def test_add_prospect_starts_at_identified():
|
||||
p = add_prospect(company="Acme", segment="saas_tech_small")
|
||||
assert p["stage"] == "identified"
|
||||
assert p["paid"] is False
|
||||
|
||||
|
||||
def test_update_stage_to_paid_marks_paid_true():
|
||||
p = add_prospect(company="Acme")
|
||||
update_stage(prospect=p, new_stage="paid", notes="Moyasar 499")
|
||||
assert p["stage"] == "paid"
|
||||
assert p["paid"] is True
|
||||
assert "Moyasar" in str(p["notes"])
|
||||
|
||||
|
||||
def test_update_stage_invalid_raises():
|
||||
p = add_prospect(company="Acme")
|
||||
with pytest.raises(ValueError):
|
||||
update_stage(prospect=p, new_stage="bogus_stage")
|
||||
|
||||
|
||||
def test_summarize_pipeline_counts_revenue():
|
||||
pipeline = []
|
||||
p1 = add_prospect(pipeline=pipeline, company="A", segment="agency_b2b")
|
||||
p2 = add_prospect(pipeline=pipeline, company="B", segment="training")
|
||||
p1["price_sar"] = 499
|
||||
update_stage(prospect=p1, new_stage="paid")
|
||||
update_stage(prospect=p2, new_stage="lost")
|
||||
s = summarize_pipeline(pipeline)
|
||||
assert s["total_prospects"] == 2
|
||||
assert s["revenue_paid_sar"] == 499.0
|
||||
assert s["by_stage"]["paid"] == 1
|
||||
assert s["by_stage"]["lost"] == 1
|
||||
assert s["win_rate"] == 0.5
|
||||
|
||||
|
||||
# ── Outreach ─────────────────────────────────────────────────
|
||||
def test_first_20_segments_v2():
|
||||
out = build_first_20_segments_v2()
|
||||
assert out["total_targets"] == 20
|
||||
|
||||
|
||||
def test_outreach_message_v2_arabic():
|
||||
out = build_outreach_message_v2("agency_b2b")
|
||||
assert any("" <= ch <= "ۿ" for ch in out["body_ar"])
|
||||
|
||||
|
||||
def test_followup_1_and_2_differ():
|
||||
s1 = build_followup_1("training_consulting")
|
||||
s2 = build_followup_2("training_consulting")
|
||||
assert s1["body_ar"] != s2["body_ar"]
|
||||
|
||||
|
||||
def test_reply_handlers_v2_includes_unsubscribe():
|
||||
h = build_reply_handlers_v2()
|
||||
assert "unsubscribe" in h
|
||||
|
||||
|
||||
# ── Pilot delivery ───────────────────────────────────────────
|
||||
def test_intake_form_has_required_fields():
|
||||
f = build_client_intake_form()
|
||||
keys = {q["key"] for q in f["fields"]}
|
||||
for required in ("company_name", "sector", "city", "primary_offer",
|
||||
"approval_owner"):
|
||||
assert required in keys
|
||||
|
||||
|
||||
def test_24h_delivery_plan_has_5_phases():
|
||||
p = build_24h_delivery_plan("first_10_opportunities_sprint")
|
||||
assert len(p["phases"]) == 5
|
||||
assert p["live_send_allowed"] is False
|
||||
|
||||
|
||||
def test_first_10_delivery_has_proof():
|
||||
out = build_first_10_opportunities_delivery({"sector": "training"})
|
||||
assert "Proof Pack v1" in out["deliverables"]
|
||||
assert out["approval_required"] is True
|
||||
|
||||
|
||||
def test_list_intelligence_delivery_includes_50_targets():
|
||||
out = build_list_intelligence_delivery({"sector": "real_estate"})
|
||||
assert any("50" in d for d in out["deliverables"])
|
||||
|
||||
|
||||
def test_growth_diagnostic_delivery_24h():
|
||||
out = build_growth_diagnostic_delivery({"sector": "saas"})
|
||||
assert "24" in out["delivery_time"] or "ساعة" in out["delivery_time"]
|
||||
|
||||
|
||||
# ── Payment manual flow ──────────────────────────────────────
|
||||
def test_invoice_instructions_correct_halalas():
|
||||
out = build_moyasar_invoice_instructions(amount_sar=499)
|
||||
assert out["amount_sar"] == 499
|
||||
assert out["amount_halalas"] == 49900
|
||||
assert out["no_live_charge"] is True
|
||||
|
||||
|
||||
def test_invoice_instructions_warns_no_card_storage():
|
||||
out = build_moyasar_invoice_instructions(amount_sar=499)
|
||||
text = " ".join(out["do_not_do_ar"])
|
||||
assert "بطاقة" in text or "card" in text.lower()
|
||||
|
||||
|
||||
def test_payment_link_message_arabic_and_no_live_send():
|
||||
out = build_payment_link_message(
|
||||
customer_name="أحمد", invoice_url="https://example.com/inv/1",
|
||||
)
|
||||
assert any("" <= ch <= "ۿ" for ch in out["body_ar"])
|
||||
assert out["live_send_allowed"] is False
|
||||
|
||||
|
||||
def test_payment_confirmation_checklist_blocks_premature_delivery():
|
||||
out = build_payment_confirmation_checklist()
|
||||
text = " ".join(out["do_not_do_ar"])
|
||||
assert "paid" in text.lower() or "تأكيد" in text
|
||||
|
||||
|
||||
# ── Proof Pack ───────────────────────────────────────────────
|
||||
def test_proof_pack_template_has_metrics():
|
||||
out = build_private_beta_proof_pack(company_name="Acme")
|
||||
assert "opportunities_generated" in out["metrics_to_include"]
|
||||
assert out["approval_required"] is True
|
||||
|
||||
|
||||
def test_client_summary_returns_5_lines():
|
||||
out = build_client_summary(
|
||||
company_name="Acme", opportunities_count=10,
|
||||
approved_drafts=4, meetings=2, pipeline_sar=18000,
|
||||
risks_blocked=3,
|
||||
)
|
||||
assert len(out["summary_ar"]) == 5
|
||||
assert any("18000" in line or "18,000" in line or "18000" in str(line)
|
||||
for line in out["summary_ar"])
|
||||
|
||||
|
||||
def test_next_step_upsell_for_strong_outcome():
|
||||
out = build_next_step_recommendation(pilot_metrics={
|
||||
"pipeline_sar": 30000, "meetings": 3, "csat": 9,
|
||||
})
|
||||
assert out["next_action"] == "upsell_growth_os_monthly"
|
||||
|
||||
|
||||
def test_next_step_iterate_for_weak_outcome():
|
||||
out = build_next_step_recommendation(pilot_metrics={
|
||||
"pipeline_sar": 1000, "meetings": 0, "csat": 5,
|
||||
})
|
||||
assert out["next_action"] == "iterate_or_archive"
|
||||
|
||||
|
||||
def test_next_step_extend_for_promising_outcome():
|
||||
out = build_next_step_recommendation(pilot_metrics={
|
||||
"pipeline_sar": 12000, "meetings": 1, "csat": 7,
|
||||
})
|
||||
assert out["next_action"] == "extend_pilot"
|
||||
|
||||
|
||||
# ── Constants ───────────────────────────────────────────────
|
||||
def test_pipeline_stages_constant_exposed():
|
||||
assert "identified" in PIPELINE_STAGES
|
||||
assert "paid" in PIPELINE_STAGES
|
||||
assert "lost" in PIPELINE_STAGES
|
||||
Loading…
Reference in New Issue
Block a user