From 47f4dc2fb65a276e5afc172037ebc143972fd388 Mon Sep 17 00:00:00 2001 From: Dealix Builder Date: Fri, 1 May 2026 18:14:51 +0300 Subject: [PATCH] =?UTF-8?q?feat(positioning+customer-ops):=20Saudi=20Reven?= =?UTF-8?q?ue=20Execution=20OS=20=E2=80=94=208=20modules=20+=2020=20endpoi?= =?UTF-8?q?nts=20+=2044=20tests=20+=208=20docs=20+=202=20modes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Locks Dealix's positioning forever and closes the operational gap between "great product" and "great customer experience": onboarding, connectors, support SLA, incidents, customer success cadence, and companies/marketers landing pages. Positioning Lock (3 docs) - POSITIONING_LOCK.md (Arabic): Saudi Revenue Execution OS category lock; one-liner; primary buyers (companies + agencies/marketers); wedge (First 10 Opportunities + Proof Pack); 5 approved claims; 5 prohibited categories; 5 modes; 5 bundles; 6 "what Dealix is NOT" rules - PROHIBITED_CLAIMS.md (Arabic): 8 categories of forbidden marketing language (guaranteed results, scraping, full automation, bypass approvals, competitor attacks, legal/financial promises, medical language, exaggerated speed) + technical enforcement (safety_eval + tone_eval + quality_review_gate + tool_action_planner + test_positioning_lock.py) - APPROVED_MARKET_MESSAGING.md (Arabic): tagline + 30-second elevator pitch + 5 headlines + competitor positioning table + 4-segment outreach templates + LinkedIn/X social posts + slogan bank Customer Ops (6 modules) - onboarding_checklist: 8-step Pilot onboarding (select_goal → select_bundle → company_intake → connect_channels → upload_or_source → risk_review → first_service_run → first_proof_pack) with progress tracking + state advancement - connector_setup_status: 11 connectors (Gmail/Calendar/Sheets/Moyasar/WhatsApp/Forms/LinkedIn-LeadForms/GBP/CRM/Meet/Instagram) each with default_mode (draft_only/manual/ingest_only/approved_execute), launch phase, and blocking flag; ready_for_first_service gate requires no blocking connectors missing AND ≥1 connected - support_ticket_router: 4-tier P0/P1/P2/P3 classification with Arabic+English keyword matching; auto-classifies "تسريب", "إرسال بدون موافقة", "بدون موافقتي", "live charge", "unauthorized" as P0; per-priority Arabic first-response templates; SLA targets per priority - sla_tracker: SLA targets per priority (P0=30min/4h, P1=2h/24h, P2=8h/72h, P3=24h/1week); record_sla_event with strict event-type validation; classify_sla_breach for individual tickets; build_sla_health_report aggregates with verdict (healthy/watch/critical based on breach_rate) - customer_success_cadence: 6 cadence types (weekly_check_in, monthly_proof_review, QBR, at_risk_alert, renewal_30/7_day); build_at_risk_alert with risk_score 0..100 from days_inactive + drafts_pending + last_proof_pack_days_ago; build_customer_success_plan with 30-day per-bundle cadences (growth_starter, executive_growth_os, partnership_growth) - incident_router: SEV1/SEV2/SEV3 with first_action_minutes + comm_cadence; auto-SEV1 on has_data_leak OR has_unauthorized_send; SEV2 on affected_customers≥5; canonical 5-step response plan (freeze live actions / notify founder / create incident channel / review Action Ledger / PDPL 72h notification) + per-severity additional steps + post-mortem template New Operator Modes (2) - self_growth_mode: re-exports targeting_os.self_growth_mode (DEALIX_ICP_FOCUSES, recommend_dealix_targets, build_self_growth_daily_brief, build_weekly_learning_report) + operator-tier reminders (no cold WhatsApp even for Dealix itself, all drafts approval-first, no scraping) - service_delivery_mode: orchestrates service_tower workflow + revenue_launch.pilot_delivery + customer_ops.sla_tracker; build_service_delivery_brief (per-service template), build_sla_status_for_delivery (breach detection on open tickets), build_post_delivery_handoff (5-step transition to Customer Success cadence) Router (1 new) — 20 endpoints - /api/v1/customer-ops/* — onboarding (checklist/update-step/demo), connectors (catalog/summary/update/demo), support (priorities/classify/route/first-response), sla (event/classify-breach/health-report/health-report-demo), incidents (triage/response-plan), cs (weekly-check-in/at-risk-alert/success-plan) Customer-facing pages (1 new, 1 already-existed-preserved) - landing/companies.html (NEW): Saudi B2B companies pitch — Approval-first, no scraping, no cold WhatsApp; 4 bundles (Growth Starter / Data to Revenue / Executive Growth OS / Full Growth Control Tower); Proof Pack section; safety + compliance section - landing/marketers.html (existed): preserved as-is — agency/marketers Agency Growth OS path Tests (2 new files, 44 tests) - test_customer_ops.py: 31 tests * 4 onboarding (8 steps, advancement, unknown step error, complete-all) * 5 connectors (critical connectors present, blocking_missing detection, ready gate, validation, write) * 8 support (P0 security, P0 unauthorized send, P1 service down, P2 connector, P3 default, empty input, route includes SLA, P0 first-response Arabic with 30 min) * 6 SLA (event validates, log appends, breach detection within/exceeded targets, health report aggregation, critical verdict) * 4 incidents (data leak SEV1, unauthorized send SEV1, ≥5 customers SEV2, single customer SEV3, SEV1 plan includes PDPL) * 4 customer success (weekly check-in talking points Arabic, at-risk high severity, at-risk low severity, success plan per bundle including growth_starter and executive_growth_os Founder Shadow Board) - test_positioning_lock.py: 13 tests * positioning_lock.md exists with category + "ليس CRM" + "ليس بوت" * prohibited_claims.md exists with "نضمن" + "scraping" * approved_market_messaging.md has Approval-first + PDPL + Saudi Tone + Proof Pack * landing pages contain NO positive forbidden claims (negative restatements like "no auto-DM" in safety sections allowed) * companies.html includes "Approval-first" + "Proof Pack" * agency-partner.html OR marketers.html exists * private-beta.html does NOT promise guarantees * REVENUE_TODAY_PLAYBOOK emphasizes Approval-first * positioning_lock lists all 5 bundles * positioning_lock lists all 5 modes (CEO + Growth Manager + Agency Partner + Self-Growth + Service Delivery) Customer Ops Docs (5 new) - ONBOARDING_RUNBOOK.md (Arabic): 8 onboarding steps + day-by-day Day1-Day5 + 11 connector states + acceptance criteria - SUPPORT_SLA.md (Arabic): 4 priority tiers + auto-classification keywords + Arabic first-response templates + weekly review process - INCIDENT_RESPONSE.md (Arabic): SEV1/SEV2/SEV3 logic + canonical response plan + per-severity additional steps + post-mortem template + Arabic communication templates + auto-actions - CUSTOMER_SUCCESS_PLAYBOOK.md (Arabic): cadence types + weekly agenda (25 min) + at-risk scoring formula + per-bundle cadence + QBR + renewal flow + health score formula - CONNECTOR_SETUP_GUIDES.md (Arabic): all 11 connectors with scopes + step-by-step + acceptance criteria + troubleshooting table Test results - 44/44 new tests pass - Full suite: 949 passed, 2 skipped (missing API keys, unrelated) - 0 existing tests broken Safety + integration - All 20 customer-ops endpoints: approval_required=True, live_send_allowed=False - support_ticket_router HARD-CLASSIFIES "تسريب", "إرسال بدون موافقة", "live charge", "unauthorized" as P0 (founder owner, 30-min first response) - incident_router auto-promotes to SEV1 on has_data_leak or has_unauthorized_send (regardless of affected_customers count) - onboarding_checklist requires WhatsApp connector (blocking) before ready_for_first_service - connector_setup_status default_mode is draft_only/manual/ingest_only — never live - Positioning Lock test_positioning_lock.py enforces: * 5 bundles must be listed in POSITIONING_LOCK.md * 5 modes must be listed * landing pages must not contain positive forbidden claims (8 phrases) * companies.html must mention Approval-first + Proof Pack - self_growth_mode reminds operator: no cold WhatsApp even for Dealix itself - service_delivery_mode integrates SLA tracker before declaring delivery success Integration with everything before - Customer Ops onboarding integrates Service Bundles (autonomous_service_operator.service_bundles) - Customer Ops connectors mirror connector_catalog risk_levels + add operational state machine - Support classifier integrates with security_curator (P0 on secret leaks) + revenue_launch.payment_manual_flow (P0 on unauthorized charge) - Customer Success metrics flow from agent_observability + revenue_launch.proof_pack_template - Service Delivery Mode wires service_tower.workflow + revenue_launch.pilot_delivery + sla_tracker into one pipeline - Self-Growth Mode wraps targeting_os.self_growth_mode with operator-tier safety reminders - Companies + Marketers pages enforce POSITIONING_LOCK headlines Co-Authored-By: Claude Opus 4.7 (1M context) --- dealix/api/main.py | 2 + dealix/api/routers/customer_ops.py | 208 ++++++++++++++ .../autonomous_service_operator/__init__.py | 14 + .../self_growth_mode.py | 55 ++++ .../service_delivery_mode.py | 108 +++++++ .../customer_ops/__init__.py | 78 +++++ .../customer_ops/connector_setup_status.py | 98 +++++++ .../customer_ops/customer_success_cadence.py | 146 ++++++++++ .../customer_ops/incident_router.py | 104 +++++++ .../customer_ops/onboarding_checklist.py | 120 ++++++++ .../customer_ops/sla_tracker.py | 132 +++++++++ .../customer_ops/support_ticket_router.py | 149 ++++++++++ dealix/docs/APPROVED_MARKET_MESSAGING.md | 119 ++++++++ dealix/docs/CONNECTOR_SETUP_GUIDES.md | 204 +++++++++++++ dealix/docs/CUSTOMER_SUCCESS_PLAYBOOK.md | 141 +++++++++ dealix/docs/DEALIX_100_PERCENT_LAUNCH_PLAN.md | 36 ++- dealix/docs/INCIDENT_RESPONSE.md | 111 ++++++++ dealix/docs/ONBOARDING_RUNBOOK.md | 120 ++++++++ dealix/docs/POSITIONING_LOCK.md | 123 ++++++++ dealix/docs/PROHIBITED_CLAIMS.md | 107 +++++++ dealix/docs/SUPPORT_SLA.md | 95 +++++++ dealix/landing/companies.html | 125 ++++++++ dealix/tests/unit/test_customer_ops.py | 268 ++++++++++++++++++ dealix/tests/unit/test_positioning_lock.py | 141 +++++++++ 24 files changed, 2803 insertions(+), 1 deletion(-) create mode 100644 dealix/api/routers/customer_ops.py create mode 100644 dealix/auto_client_acquisition/autonomous_service_operator/self_growth_mode.py create mode 100644 dealix/auto_client_acquisition/autonomous_service_operator/service_delivery_mode.py create mode 100644 dealix/auto_client_acquisition/customer_ops/__init__.py create mode 100644 dealix/auto_client_acquisition/customer_ops/connector_setup_status.py create mode 100644 dealix/auto_client_acquisition/customer_ops/customer_success_cadence.py create mode 100644 dealix/auto_client_acquisition/customer_ops/incident_router.py create mode 100644 dealix/auto_client_acquisition/customer_ops/onboarding_checklist.py create mode 100644 dealix/auto_client_acquisition/customer_ops/sla_tracker.py create mode 100644 dealix/auto_client_acquisition/customer_ops/support_ticket_router.py create mode 100644 dealix/docs/APPROVED_MARKET_MESSAGING.md create mode 100644 dealix/docs/CONNECTOR_SETUP_GUIDES.md create mode 100644 dealix/docs/CUSTOMER_SUCCESS_PLAYBOOK.md create mode 100644 dealix/docs/INCIDENT_RESPONSE.md create mode 100644 dealix/docs/ONBOARDING_RUNBOOK.md create mode 100644 dealix/docs/POSITIONING_LOCK.md create mode 100644 dealix/docs/PROHIBITED_CLAIMS.md create mode 100644 dealix/docs/SUPPORT_SLA.md create mode 100644 dealix/landing/companies.html create mode 100644 dealix/tests/unit/test_customer_ops.py create mode 100644 dealix/tests/unit/test_positioning_lock.py diff --git a/dealix/api/main.py b/dealix/api/main.py index be9ad39d..383cb6a6 100644 --- a/dealix/api/main.py +++ b/dealix/api/main.py @@ -23,6 +23,7 @@ from api.routers import ( business, command_center, connector_catalog, + customer_ops, customer_success, data, dominance, @@ -178,6 +179,7 @@ def create_app() -> FastAPI: app.include_router(revenue_launch.router) app.include_router(autonomous_service_operator.router) app.include_router(revenue_company_os.router) + app.include_router(customer_ops.router) app.include_router(public.router) app.include_router(admin.router) diff --git a/dealix/api/routers/customer_ops.py b/dealix/api/routers/customer_ops.py new file mode 100644 index 00000000..d3011b17 --- /dev/null +++ b/dealix/api/routers/customer_ops.py @@ -0,0 +1,208 @@ +"""Customer Ops router — onboarding + connectors + support + SLA + incidents.""" + +from __future__ import annotations + +from typing import Any + +from fastapi import APIRouter, Body + +from auto_client_acquisition.customer_ops import ( + SUPPORT_PRIORITIES, + SUPPORTED_CONNECTORS, + build_at_risk_alert, + build_connector_setup_summary, + build_customer_success_plan, + build_first_response_template, + build_incident_response_plan, + build_onboarding_checklist, + build_sla_health_report, + build_weekly_check_in, + classify_sla_breach, + classify_ticket_priority, + record_sla_event, + route_ticket, + triage_incident, + update_connector_status, + update_onboarding_step, +) + +router = APIRouter(prefix="/api/v1/customer-ops", tags=["customer-ops"]) + + +# ── Onboarding ─────────────────────────────────────────────── +@router.post("/onboarding/checklist") +async def onboarding_checklist(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + return build_onboarding_checklist( + customer_id=payload.get("customer_id", ""), + company_name=payload.get("company_name", ""), + bundle_id=payload.get("bundle_id"), + ) + + +@router.post("/onboarding/update-step") +async def onboarding_update_step(payload: dict[str, Any] = Body(...)) -> dict[str, Any]: + return update_onboarding_step( + payload.get("checklist") or {}, + step_id=payload.get("step_id", ""), + completed=bool(payload.get("completed", True)), + notes=payload.get("notes", ""), + ) + + +@router.get("/onboarding/checklist/demo") +async def onboarding_checklist_demo() -> dict[str, Any]: + return build_onboarding_checklist( + customer_id="demo", company_name="شركة نمو للتدريب", + bundle_id="growth_starter", + ) + + +# ── Connectors ─────────────────────────────────────────────── +@router.get("/connectors/catalog") +async def connectors_catalog() -> dict[str, Any]: + return { + "total": len(SUPPORTED_CONNECTORS), + "connectors": [dict(c) for c in SUPPORTED_CONNECTORS], + } + + +@router.post("/connectors/summary") +async def connectors_summary(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + return build_connector_setup_summary( + customer_id=payload.get("customer_id", ""), + statuses=payload.get("statuses"), + ) + + +@router.post("/connectors/update") +async def connectors_update(payload: dict[str, Any] = Body(...)) -> dict[str, Any]: + statuses = payload.get("statuses") or {} + try: + return {"statuses": update_connector_status( + statuses, + connector_key=payload.get("connector_key", ""), + state=payload.get("state", "not_started"), + notes=payload.get("notes", ""), + )} + except ValueError as exc: + return {"error": str(exc)} + + +@router.get("/connectors/demo") +async def connectors_demo() -> dict[str, Any]: + return build_connector_setup_summary( + customer_id="demo", + statuses={ + "gmail": {"state": "connected_draft_only"}, + "google_calendar": {"state": "connected_draft_only"}, + "moyasar": {"state": "configuring"}, + "whatsapp_cloud": {"state": "not_started"}, + }, + ) + + +# ── Support ────────────────────────────────────────────────── +@router.get("/support/priorities") +async def support_priorities() -> dict[str, Any]: + return {"priorities": [dict(p) for p in SUPPORT_PRIORITIES]} + + +@router.post("/support/classify") +async def support_classify(payload: dict[str, Any] = Body(...)) -> dict[str, Any]: + return classify_ticket_priority(payload.get("text", "")) + + +@router.post("/support/route") +async def support_route(payload: dict[str, Any] = Body(...)) -> dict[str, Any]: + return route_ticket( + text=payload.get("text", ""), + customer_id=payload.get("customer_id", ""), + contact_email=payload.get("contact_email", ""), + ) + + +@router.get("/support/first-response/{priority}") +async def support_first_response(priority: str) -> dict[str, Any]: + return build_first_response_template(priority) + + +# ── SLA ────────────────────────────────────────────────────── +@router.post("/sla/event") +async def sla_event(payload: dict[str, Any] = Body(...)) -> dict[str, Any]: + try: + return record_sla_event( + ticket_id=payload.get("ticket_id", ""), + priority=payload.get("priority", "P3"), + event=payload.get("event", "opened"), + ) + except ValueError as exc: + return {"error": str(exc)} + + +@router.post("/sla/classify-breach") +async def sla_classify_breach(payload: dict[str, Any] = Body(...)) -> dict[str, Any]: + return classify_sla_breach( + priority=payload.get("priority", "P3"), + minutes_to_first_response=payload.get("minutes_to_first_response"), + hours_to_resolve=payload.get("hours_to_resolve"), + ) + + +@router.post("/sla/health-report") +async def sla_health_report(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + return build_sla_health_report(tickets=payload.get("tickets") or []) + + +@router.get("/sla/health-report/demo") +async def sla_health_report_demo() -> dict[str, Any]: + return build_sla_health_report(tickets=[ + {"priority": "P0", "first_response_min": 12, "resolution_hours": 2.5}, + {"priority": "P1", "first_response_min": 90, "resolution_hours": 18}, + {"priority": "P2", "first_response_min": 600, "resolution_hours": 70}, + {"priority": "P3", "first_response_min": 1200, "resolution_hours": 100}, + ]) + + +# ── Incidents ──────────────────────────────────────────────── +@router.post("/incidents/triage") +async def incidents_triage(payload: dict[str, Any] = Body(...)) -> dict[str, Any]: + return triage_incident( + title=payload.get("title", ""), + description=payload.get("description", ""), + affected_customers=int(payload.get("affected_customers", 1)), + has_data_leak=bool(payload.get("has_data_leak", False)), + has_unauthorized_send=bool(payload.get("has_unauthorized_send", False)), + ) + + +@router.get("/incidents/response-plan/{severity}") +async def incidents_response_plan(severity: str) -> dict[str, Any]: + return build_incident_response_plan(severity=severity) + + +# ── Customer Success ───────────────────────────────────────── +@router.post("/cs/weekly-check-in") +async def cs_weekly_check_in(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + return build_weekly_check_in( + customer_id=payload.get("customer_id", ""), + company_name=payload.get("company_name", ""), + metrics=payload.get("metrics"), + ) + + +@router.post("/cs/at-risk-alert") +async def cs_at_risk_alert(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + return build_at_risk_alert( + customer_id=payload.get("customer_id", ""), + days_inactive=int(payload.get("days_inactive", 0)), + drafts_pending=int(payload.get("drafts_pending", 0)), + last_proof_pack_days_ago=int(payload.get("last_proof_pack_days_ago", 0)), + ) + + +@router.post("/cs/success-plan") +async def cs_success_plan(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]: + return build_customer_success_plan( + customer_id=payload.get("customer_id", ""), + bundle_id=payload.get("bundle_id", "growth_starter"), + ) diff --git a/dealix/auto_client_acquisition/autonomous_service_operator/__init__.py b/dealix/auto_client_acquisition/autonomous_service_operator/__init__.py index 15255081..8cf4c0a4 100644 --- a/dealix/auto_client_acquisition/autonomous_service_operator/__init__.py +++ b/dealix/auto_client_acquisition/autonomous_service_operator/__init__.py @@ -22,6 +22,14 @@ from .client_mode import ( build_client_dashboard, build_client_session_summary, ) +from .self_growth_mode import ( + build_operator_self_growth_brief, +) +from .service_delivery_mode import ( + build_post_delivery_handoff, + build_service_delivery_brief, + build_sla_status_for_delivery, +) from .conversation_router import ( INTENT_TO_HANDLER, handle_message, @@ -122,4 +130,10 @@ __all__ = [ # agency_mode "add_agency_client", "build_agency_dashboard", "build_co_branded_proof_pack", "list_agency_revenue_share", + # self_growth_mode + "build_operator_self_growth_brief", + # service_delivery_mode + "build_post_delivery_handoff", + "build_service_delivery_brief", + "build_sla_status_for_delivery", ] diff --git a/dealix/auto_client_acquisition/autonomous_service_operator/self_growth_mode.py b/dealix/auto_client_acquisition/autonomous_service_operator/self_growth_mode.py new file mode 100644 index 00000000..82a9110c --- /dev/null +++ b/dealix/auto_client_acquisition/autonomous_service_operator/self_growth_mode.py @@ -0,0 +1,55 @@ +"""Self-Growth Mode — Dealix uses its own OS to grow. + +Re-exports + extends targeting_os.self_growth_mode with operator-tier wiring. +""" + +from __future__ import annotations + +from typing import Any + +from auto_client_acquisition.targeting_os.self_growth_mode import ( + DEALIX_ICP_FOCUSES, + build_dealix_self_growth_plan, + build_free_service_offer, + build_self_growth_daily_brief, + build_weekly_learning_report, + recommend_dealix_targets, +) + + +def build_operator_self_growth_brief( + *, + include_outreach_hint: bool = True, +) -> dict[str, Any]: + """ + Operator-tier wrapper around the self-growth daily brief. + + Layers in approval-first reminders + reminders to never auto-send. + """ + base = build_self_growth_daily_brief() + out = dict(base) + out["operator_reminders_ar"] = [ + "لا cold WhatsApp — حتى داخل Dealix نفسه.", + "كل رسالة draft تحتاج اعتمادك قبل الإرسال.", + "لا scraping LinkedIn — استخدم Lead Forms أو manual research.", + "كل تواصل يدخل Action Ledger.", + ] + if include_outreach_hint: + out["next_action_ar"] = ( + "اعتمد 3 رسائل اليوم فقط — جودة قبل كمية. " + "Pilot صغير ناجح > 50 رسالة بدون رد." + ) + out["approval_required"] = True + out["live_send_allowed"] = False + return out + + +__all__ = [ + "DEALIX_ICP_FOCUSES", + "build_dealix_self_growth_plan", + "build_free_service_offer", + "build_operator_self_growth_brief", + "build_self_growth_daily_brief", + "build_weekly_learning_report", + "recommend_dealix_targets", +] diff --git a/dealix/auto_client_acquisition/autonomous_service_operator/service_delivery_mode.py b/dealix/auto_client_acquisition/autonomous_service_operator/service_delivery_mode.py new file mode 100644 index 00000000..9ee2239d --- /dev/null +++ b/dealix/auto_client_acquisition/autonomous_service_operator/service_delivery_mode.py @@ -0,0 +1,108 @@ +"""Service Delivery Mode — runs client services + tracks SLA + generates Proof. + +Production wrapper around service_orchestrator + revenue_launch.pilot_delivery ++ customer_ops.sla_tracker. +""" + +from __future__ import annotations + +from typing import Any + +from auto_client_acquisition.customer_ops import ( + build_sla_health_report, + classify_sla_breach, +) +from auto_client_acquisition.revenue_launch import ( + build_24h_delivery_plan, + build_first_10_opportunities_delivery, + build_growth_diagnostic_delivery, + build_list_intelligence_delivery, +) +from auto_client_acquisition.service_tower import ( + build_service_workflow, + get_service, +) + + +def build_service_delivery_brief( + *, + customer_id: str = "", + service_id: str = "", + intake: dict[str, Any] | None = None, +) -> dict[str, Any]: + """Build the day-one delivery brief for a service.""" + s = get_service(service_id) + if s is None: + return {"error": f"unknown service: {service_id}"} + + delivery_template_by_service: dict[str, Any] = { + "first_10_opportunities_sprint": + build_first_10_opportunities_delivery(intake or {}), + "list_intelligence": + build_list_intelligence_delivery(intake or {}), + "free_growth_diagnostic": + build_growth_diagnostic_delivery(intake or {}), + } + + return { + "mode": "service_delivery", + "customer_id": customer_id, + "service_id": service_id, + "service_name_ar": s.name_ar, + "intake_received": bool(intake), + "workflow": build_service_workflow(service_id), + "delivery_template": delivery_template_by_service.get( + service_id, build_24h_delivery_plan(service_id), + ), + "approval_required": True, + "live_send_allowed": False, + } + + +def build_sla_status_for_delivery( + *, + customer_id: str = "", + open_tickets: list[dict[str, Any]] | None = None, +) -> dict[str, Any]: + """Compute SLA health for a customer's open delivery tickets.""" + health = build_sla_health_report(tickets=open_tickets) + breaches: list[dict[str, Any]] = [] + for t in (open_tickets or []): + b = classify_sla_breach( + priority=str(t.get("priority", "P3")), + minutes_to_first_response=t.get("first_response_min"), + hours_to_resolve=t.get("resolution_hours"), + ) + if b["breached"]: + breaches.append({**t, "breach": b}) + return { + "customer_id": customer_id, + "health": health, + "breaches": breaches, + "approval_required": True, + } + + +def build_post_delivery_handoff( + *, + customer_id: str = "", + service_id: str = "", + delivered_metrics: dict[str, Any] | None = None, +) -> dict[str, Any]: + """Build the post-delivery handoff (Arabic) → Customer Success cadence.""" + metrics = delivered_metrics or {} + return { + "mode": "service_delivery", + "customer_id": customer_id, + "service_id": service_id, + "delivered_metrics": dict(metrics), + "handoff_steps_ar": [ + "تسليم Proof Pack النهائي للعميل + اعتماده.", + "حجز جلسة مراجعة 30 دقيقة.", + "تفعيل Customer Success cadence (weekly check-ins).", + "اقتراح الترقية المنطقية بناءً على النتائج.", + "تحديث Action Graph + Revenue Work Units.", + ], + "approval_required": True, + "live_send_allowed": False, + } diff --git a/dealix/auto_client_acquisition/customer_ops/__init__.py b/dealix/auto_client_acquisition/customer_ops/__init__.py new file mode 100644 index 00000000..311847ae --- /dev/null +++ b/dealix/auto_client_acquisition/customer_ops/__init__.py @@ -0,0 +1,78 @@ +"""Customer Ops — onboarding + connector setup + support SLA + incidents. + +Closes the gap between "great product" and "great customer experience": + - onboarding_checklist: 8-step Pilot onboarding + - connector_setup_status: per-connector readiness + - support_ticket_router: P0–P3 categorization + routing + - sla_tracker: time-to-first-response, MTTR, weekly health + - customer_success_cadence: weekly check-in cadence + risk flags + - incident_router: triage P0/P1 incidents with audit +""" + +from __future__ import annotations + +from .connector_setup_status import ( + SUPPORTED_CONNECTORS, + build_connector_setup_summary, + get_connector_status, + update_connector_status, +) +from .customer_success_cadence import ( + CADENCE_TYPES, + build_at_risk_alert, + build_customer_success_plan, + build_weekly_check_in, +) +from .incident_router import ( + INCIDENT_SEVERITIES, + build_incident_response_plan, + triage_incident, +) +from .onboarding_checklist import ( + ONBOARDING_STEPS, + build_onboarding_checklist, + update_onboarding_step, +) +from .sla_tracker import ( + SLA_TARGETS, + build_sla_health_report, + classify_sla_breach, + record_sla_event, +) +from .support_ticket_router import ( + SUPPORT_PRIORITIES, + build_first_response_template, + classify_ticket_priority, + route_ticket, +) + +__all__ = [ + # connector_setup_status + "SUPPORTED_CONNECTORS", + "build_connector_setup_summary", + "get_connector_status", + "update_connector_status", + # customer_success_cadence + "CADENCE_TYPES", + "build_at_risk_alert", + "build_customer_success_plan", + "build_weekly_check_in", + # incident_router + "INCIDENT_SEVERITIES", + "build_incident_response_plan", + "triage_incident", + # onboarding_checklist + "ONBOARDING_STEPS", + "build_onboarding_checklist", + "update_onboarding_step", + # sla_tracker + "SLA_TARGETS", + "build_sla_health_report", + "classify_sla_breach", + "record_sla_event", + # support_ticket_router + "SUPPORT_PRIORITIES", + "build_first_response_template", + "classify_ticket_priority", + "route_ticket", +] diff --git a/dealix/auto_client_acquisition/customer_ops/connector_setup_status.py b/dealix/auto_client_acquisition/customer_ops/connector_setup_status.py new file mode 100644 index 00000000..f74310fb --- /dev/null +++ b/dealix/auto_client_acquisition/customer_ops/connector_setup_status.py @@ -0,0 +1,98 @@ +"""Connector setup status — per-customer readiness across all integrations.""" + +from __future__ import annotations + +from typing import Any + +# 11 connectors Dealix supports during onboarding. +SUPPORTED_CONNECTORS: tuple[dict[str, Any], ...] = ( + {"key": "gmail", "label_ar": "Gmail", "default_mode": "draft_only", + "blocking": False, "phase": "phase_1"}, + {"key": "google_calendar", "label_ar": "Google Calendar", + "default_mode": "draft_only", "blocking": False, "phase": "phase_1"}, + {"key": "google_sheets", "label_ar": "Google Sheets", + "default_mode": "approved_execute", "blocking": False, "phase": "phase_1"}, + {"key": "moyasar", "label_ar": "Moyasar (manual invoice)", + "default_mode": "manual", "blocking": False, "phase": "phase_1"}, + {"key": "whatsapp_cloud", "label_ar": "WhatsApp Business", + "default_mode": "draft_only", "blocking": True, "phase": "phase_1"}, + {"key": "website_forms", "label_ar": "Website Forms", + "default_mode": "approved_execute", "blocking": False, "phase": "phase_1"}, + {"key": "linkedin_lead_forms", "label_ar": "LinkedIn Lead Gen Forms", + "default_mode": "ingest_only", "blocking": False, "phase": "phase_2"}, + {"key": "google_business_profile", "label_ar": "Google Business Profile", + "default_mode": "draft_only", "blocking": False, "phase": "phase_2"}, + {"key": "crm_generic", "label_ar": "CRM (HubSpot/Salesforce/Zoho/Close)", + "default_mode": "draft_only", "blocking": False, "phase": "phase_2"}, + {"key": "google_meet", "label_ar": "Google Meet (transcripts)", + "default_mode": "ingest_only", "blocking": False, "phase": "phase_2"}, + {"key": "instagram_graph", "label_ar": "Instagram (comments/DMs)", + "default_mode": "ingest_only", "blocking": False, "phase": "phase_3"}, +) + + +def get_connector_status(connector_key: str) -> dict[str, Any]: + """Return the static description of a connector.""" + c = next((dict(c) for c in SUPPORTED_CONNECTORS if c["key"] == connector_key), None) + if c is None: + return {"error": f"unknown connector: {connector_key}"} + return c + + +def update_connector_status( + statuses: dict[str, dict[str, Any]], + *, + connector_key: str, + state: str, + notes: str = "", +) -> dict[str, dict[str, Any]]: + """Update the live status of a connector for a customer.""" + if state not in {"not_started", "configuring", "connected_draft_only", + "connected_approved_execute", "failed", "skipped"}: + raise ValueError(f"Unknown connector state: {state}") + statuses[connector_key] = { + "state": state, + "notes": notes[:200], + } + return statuses + + +def build_connector_setup_summary( + *, + customer_id: str = "", + statuses: dict[str, dict[str, Any]] | None = None, +) -> dict[str, Any]: + """Build a connector setup summary for a customer.""" + statuses = statuses or {} + connected = 0 + blocking_missing: list[str] = [] + by_state: dict[str, int] = {} + + items: list[dict[str, Any]] = [] + for c in SUPPORTED_CONNECTORS: + live = statuses.get(c["key"], {}) + state = live.get("state", "not_started") + by_state[state] = by_state.get(state, 0) + 1 + if state in ("connected_draft_only", "connected_approved_execute"): + connected += 1 + if c["blocking"] and state not in ( + "connected_draft_only", "connected_approved_execute", + ): + blocking_missing.append(c["key"]) + items.append({**c, "state": state, "notes": live.get("notes", "")}) + + total = len(SUPPORTED_CONNECTORS) + pct = round(100 * connected / total, 1) if total else 0.0 + + return { + "customer_id": customer_id, + "total_connectors": total, + "connected_count": connected, + "connected_pct": pct, + "blocking_missing": blocking_missing, + "by_state": by_state, + "items": items, + "ready_for_first_service": ( + len(blocking_missing) == 0 and connected >= 1 + ), + } diff --git a/dealix/auto_client_acquisition/customer_ops/customer_success_cadence.py b/dealix/auto_client_acquisition/customer_ops/customer_success_cadence.py new file mode 100644 index 00000000..c26404b3 --- /dev/null +++ b/dealix/auto_client_acquisition/customer_ops/customer_success_cadence.py @@ -0,0 +1,146 @@ +"""Customer Success cadence — weekly check-ins + at-risk alerts.""" + +from __future__ import annotations + +from typing import Any + +# Cadence types Dealix supports. +CADENCE_TYPES: tuple[str, ...] = ( + "weekly_check_in", + "monthly_proof_review", + "quarterly_business_review", + "at_risk_alert", + "renewal_30_day", + "renewal_7_day", +) + + +def build_weekly_check_in( + *, + customer_id: str = "", + company_name: str = "", + metrics: dict[str, Any] | None = None, +) -> dict[str, Any]: + """Build a weekly check-in agenda + Arabic talking points.""" + m = metrics or {} + drafts = int(m.get("drafts_approved", 0)) + replies = int(m.get("replies", 0)) + meetings = int(m.get("meetings", 0)) + risks = int(m.get("risks_blocked", 0)) + pipeline = float(m.get("pipeline_sar", 0)) + + return { + "customer_id": customer_id, + "company_name": company_name, + "type": "weekly_check_in", + "agenda_ar": [ + "مراجعة آخر Proof Pack (5 دقائق).", + "أبرز فرصة في الـ pipeline (5 دقائق).", + "أبرز خطر في القنوات (5 دقائق).", + "خطة الأسبوع القادم (5 دقائق).", + "أي مساعدة من فريقنا؟ (5 دقائق).", + ], + "talking_points_ar": [ + f"اعتمدتم {drafts} رسالة هذا الأسبوع، ووصلكم {replies} رد.", + f"تم تجهيز {meetings} اجتماع.", + f"تم منع {risks} مخاطر تلقائياً.", + f"Pipeline متأثر بقيمة {pipeline:.0f} ريال.", + ], + "approval_required": True, + "live_send_allowed": False, + } + + +def build_at_risk_alert( + *, + customer_id: str = "", + days_inactive: int = 0, + drafts_pending: int = 0, + last_proof_pack_days_ago: int = 0, +) -> dict[str, Any]: + """Build an at-risk alert when a customer shows churn signals.""" + risk_score = 0 + reasons: list[str] = [] + + if days_inactive >= 14: + risk_score += 40 + reasons.append(f"العميل غير نشط منذ {days_inactive} يوم.") + elif days_inactive >= 7: + risk_score += 20 + reasons.append(f"انخفاض النشاط منذ {days_inactive} يوم.") + + if drafts_pending >= 10: + risk_score += 25 + reasons.append(f"{drafts_pending} مسودة معلقة بدون اعتماد.") + elif drafts_pending >= 5: + risk_score += 10 + reasons.append(f"تراكم {drafts_pending} مسودة بدون اعتماد.") + + if last_proof_pack_days_ago >= 14: + risk_score += 30 + reasons.append( + f"آخر Proof Pack قبل {last_proof_pack_days_ago} يوم — يتجاوز SLA." + ) + + risk_score = min(100, risk_score) + if risk_score >= 60: + severity = "high" + action_ar = "أرسل إيميل personal من المؤسس + احجز QBR هذا الأسبوع." + elif risk_score >= 30: + severity = "medium" + action_ar = "أرسل Proof Pack ملخص + اقترح ديمو لخدمة جديدة." + else: + severity = "low" + action_ar = "متابعة weekly check-in عادية." + + return { + "customer_id": customer_id, + "type": "at_risk_alert", + "risk_score": risk_score, + "severity": severity, + "reasons_ar": reasons, + "recommended_action_ar": action_ar, + "approval_required": True, + "live_send_allowed": False, + } + + +def build_customer_success_plan( + *, + customer_id: str = "", + bundle_id: str = "growth_starter", +) -> dict[str, Any]: + """Build a 30-day customer success cadence plan.""" + cadence_by_bundle = { + "growth_starter": [ + "Day 1: kick-off call + intake.", + "Day 3: review first 3 opportunities + drafts.", + "Day 7: deliver Proof Pack v1.", + "Day 14: weekly check-in + upsell offer.", + "Day 30: monthly proof review + renewal/upgrade decision.", + ], + "executive_growth_os": [ + "Day 1: onboarding + connect channels.", + "Day 7: first weekly Proof Pack.", + "Day 14: weekly check-in + Founder Shadow Board v1.", + "Day 21: monthly proof review.", + "Day 30: QBR + annual upgrade conversation.", + ], + "partnership_growth": [ + "Day 1: partner ICP intake.", + "Day 5: 20 partners list + 10 outreach drafts.", + "Day 10: 5 partner meetings booked.", + "Day 14: weekly check-in.", + "Day 30: partner scorecard + revenue share setup.", + ], + } + + return { + "customer_id": customer_id, + "bundle_id": bundle_id, + "cadence_ar": cadence_by_bundle.get( + bundle_id, cadence_by_bundle["growth_starter"], + ), + "default_cadence_type": "weekly_check_in", + "approval_required": True, + } diff --git a/dealix/auto_client_acquisition/customer_ops/incident_router.py b/dealix/auto_client_acquisition/customer_ops/incident_router.py new file mode 100644 index 00000000..617ad24d --- /dev/null +++ b/dealix/auto_client_acquisition/customer_ops/incident_router.py @@ -0,0 +1,104 @@ +"""Incident router — triage P0/P1 incidents with audit + response plan.""" + +from __future__ import annotations + +from typing import Any + +INCIDENT_SEVERITIES: tuple[dict[str, Any], ...] = ( + { + "id": "SEV1", + "label_ar": "حرج جداً — تسريب أمان / إرسال خاطئ / تعطل كامل", + "first_action_minutes": 15, + "communication_cadence_minutes": 30, + }, + { + "id": "SEV2", + "label_ar": "خدمة مهمة معطلة لعدد كبير من العملاء", + "first_action_minutes": 30, + "communication_cadence_minutes": 60, + }, + { + "id": "SEV3", + "label_ar": "خدمة معطلة لعميل واحد أو degraded performance", + "first_action_minutes": 120, + "communication_cadence_minutes": 240, + }, +) + + +def triage_incident( + *, + title: str, + description: str = "", + affected_customers: int = 1, + has_data_leak: bool = False, + has_unauthorized_send: bool = False, +) -> dict[str, Any]: + """Triage an incident → severity + first actions + comms cadence.""" + if has_data_leak or has_unauthorized_send: + sev = "SEV1" + reason_ar = ( + "تسريب أمان أو إرسال غير معتمد — أعلى أولوية." + ) + elif affected_customers >= 5: + sev = "SEV2" + reason_ar = f"عدد العملاء المتأثرين: {affected_customers} ≥ 5." + else: + sev = "SEV3" + reason_ar = "حدث محدود التأثير." + + severity = next( + (dict(s) for s in INCIDENT_SEVERITIES if s["id"] == sev), + dict(INCIDENT_SEVERITIES[2]), + ) + + return { + "title": title[:120], + "description": description[:500], + "severity": sev, + "reason_ar": reason_ar, + "severity_details": severity, + "affected_customers": affected_customers, + "has_data_leak": has_data_leak, + "has_unauthorized_send": has_unauthorized_send, + "approval_required": True, + "live_send_allowed": False, + } + + +def build_incident_response_plan( + *, + severity: str = "SEV3", +) -> dict[str, Any]: + """Build the canonical incident response plan (Arabic).""" + common_steps = [ + "1. تجميد الـ live actions على القناة المعنية فوراً.", + "2. إخطار المؤسس + on-call operator.", + "3. إنشاء incident channel مع timeline.", + "4. مراجعة Action Ledger للأفعال المرتبطة.", + "5. إذا تسريب: إخطار العملاء المتأثرين خلال 72 ساعة (PDPL).", + ] + + if severity == "SEV1": + plan = common_steps + [ + "6. تواصل مباشر مع المؤسس + خلية أزمة.", + "7. كتابة post-mortem خلال 24 ساعة.", + "8. مراجعة قانونية إن لزم.", + ] + elif severity == "SEV2": + plan = common_steps + [ + "6. تحديث العملاء المتأثرين كل 60 دقيقة.", + "7. post-mortem خلال 48 ساعة.", + ] + else: + plan = common_steps + [ + "6. تحديث العميل المتأثر مع كل خطوة.", + "7. post-mortem اختياري.", + ] + + return { + "severity": severity, + "plan_ar": plan, + "approval_required": True, + "live_send_allowed": False, + } diff --git a/dealix/auto_client_acquisition/customer_ops/onboarding_checklist.py b/dealix/auto_client_acquisition/customer_ops/onboarding_checklist.py new file mode 100644 index 00000000..39950966 --- /dev/null +++ b/dealix/auto_client_acquisition/customer_ops/onboarding_checklist.py @@ -0,0 +1,120 @@ +"""Onboarding checklist — the 8-step Pilot onboarding flow.""" + +from __future__ import annotations + +from typing import Any + +ONBOARDING_STEPS: tuple[dict[str, Any], ...] = ( + { + "id": "select_goal", + "label_ar": "اختيار الهدف الأساسي", + "input_required": "goal", + "minutes": 2, + "approval_required": False, + }, + { + "id": "select_bundle", + "label_ar": "اختيار الباقة المناسبة", + "input_required": "bundle_id", + "minutes": 3, + "approval_required": True, + }, + { + "id": "company_intake", + "label_ar": "بيانات الشركة", + "input_required": "company_profile", + "minutes": 5, + "approval_required": False, + }, + { + "id": "connect_channels", + "label_ar": "ربط القنوات (Gmail/Calendar/Sheets — drafts فقط)", + "input_required": "channels_oauth", + "minutes": 8, + "approval_required": True, + }, + { + "id": "upload_or_source", + "label_ar": "رفع قائمة أو ربط مصدر leads", + "input_required": "list_or_source", + "minutes": 5, + "approval_required": True, + }, + { + "id": "risk_review", + "label_ar": "مراجعة المخاطر (PDPL + سمعة القناة)", + "input_required": None, + "minutes": 4, + "approval_required": True, + }, + { + "id": "first_service_run", + "label_ar": "تشغيل أول خدمة (First 10 Opportunities أو List Intelligence)", + "input_required": None, + "minutes": 0, # async — Dealix runs it + "approval_required": True, + }, + { + "id": "first_proof_pack", + "label_ar": "استلام أول Proof Pack", + "input_required": None, + "minutes": 0, # async + "approval_required": False, + }, +) + + +def build_onboarding_checklist( + *, + customer_id: str = "", + company_name: str = "", + bundle_id: str | None = None, +) -> dict[str, Any]: + """Build a fresh onboarding checklist for a new customer.""" + return { + "customer_id": customer_id, + "company_name": company_name, + "bundle_id": bundle_id, + "total_steps": len(ONBOARDING_STEPS), + "current_step_id": ONBOARDING_STEPS[0]["id"], + "steps": [ + {**dict(s), "completed": False} for s in ONBOARDING_STEPS + ], + "estimated_total_minutes": sum(int(s["minutes"]) for s in ONBOARDING_STEPS), + "live_send_allowed": False, + } + + +def update_onboarding_step( + checklist: dict[str, Any], + *, + step_id: str, + completed: bool = True, + notes: str = "", +) -> dict[str, Any]: + """Mark a step complete + advance current_step_id.""" + steps = list(checklist.get("steps", [])) + found = False + for i, s in enumerate(steps): + if s["id"] == step_id: + s["completed"] = bool(completed) + if notes: + s["notes"] = notes[:200] + steps[i] = s + found = True + # advance current_step_id + if completed and i + 1 < len(steps): + checklist["current_step_id"] = steps[i + 1]["id"] + elif completed and i + 1 == len(steps): + checklist["current_step_id"] = "done" + break + + if not found: + return {**checklist, "error": f"unknown step: {step_id}"} + + completed_count = sum(1 for s in steps if s["completed"]) + checklist["steps"] = steps + checklist["progress_pct"] = round( + 100 * completed_count / max(1, len(steps)), 1, + ) + return checklist diff --git a/dealix/auto_client_acquisition/customer_ops/sla_tracker.py b/dealix/auto_client_acquisition/customer_ops/sla_tracker.py new file mode 100644 index 00000000..36fdd6b5 --- /dev/null +++ b/dealix/auto_client_acquisition/customer_ops/sla_tracker.py @@ -0,0 +1,132 @@ +"""SLA tracker — measure first-response, MTTR, weekly support health.""" + +from __future__ import annotations + +import time +from typing import Any + +# Default SLA targets per priority (minutes for first_response, hours for resolution). +SLA_TARGETS: dict[str, dict[str, float]] = { + "P0": {"first_response_min": 30, "resolution_hours": 4}, + "P1": {"first_response_min": 120, "resolution_hours": 24}, + "P2": {"first_response_min": 480, "resolution_hours": 72}, + "P3": {"first_response_min": 1440, "resolution_hours": 168}, +} + + +def record_sla_event( + *, + ticket_id: str, + priority: str, + event: str, + log: list[dict[str, Any]] | None = None, +) -> dict[str, Any]: + """ + Record an SLA event. + + `event` = "opened" | "first_response" | "resolved" | "escalated". + """ + if event not in {"opened", "first_response", "resolved", "escalated"}: + raise ValueError(f"Unknown SLA event: {event}") + entry: dict[str, Any] = { + "ticket_id": ticket_id, + "priority": priority, + "event": event, + "ts": time.time(), + } + if log is not None: + log.append(entry) + return entry + + +def classify_sla_breach( + *, + priority: str, + minutes_to_first_response: float | None = None, + hours_to_resolve: float | None = None, +) -> dict[str, Any]: + """Classify whether SLA was breached for a single ticket.""" + target = SLA_TARGETS.get(priority, SLA_TARGETS["P3"]) + breaches: list[str] = [] + + if (minutes_to_first_response is not None + and minutes_to_first_response > target["first_response_min"]): + breaches.append( + f"first_response: {minutes_to_first_response:.0f} > " + f"{target['first_response_min']} min" + ) + + if (hours_to_resolve is not None + and hours_to_resolve > target["resolution_hours"]): + breaches.append( + f"resolution: {hours_to_resolve:.1f}h > " + f"{target['resolution_hours']}h" + ) + + return { + "priority": priority, + "breached": bool(breaches), + "breaches": breaches, + } + + +def build_sla_health_report( + *, + tickets: list[dict[str, Any]] | None = None, +) -> dict[str, Any]: + """Build a weekly SLA health report from a list of tickets.""" + tickets = tickets or [] + by_priority: dict[str, dict[str, Any]] = {} + total_tickets = len(tickets) + total_breached = 0 + + for t in tickets: + priority = str(t.get("priority", "P3")) + bucket = by_priority.setdefault(priority, { + "count": 0, "breaches": 0, + "total_first_response_min": 0.0, + "total_resolution_hours": 0.0, + "responded_count": 0, "resolved_count": 0, + }) + bucket["count"] += 1 + ftr = t.get("first_response_min") + ttr = t.get("resolution_hours") + b = classify_sla_breach( + priority=priority, + minutes_to_first_response=ftr, + hours_to_resolve=ttr, + ) + if b["breached"]: + bucket["breaches"] += 1 + total_breached += 1 + if ftr is not None: + bucket["total_first_response_min"] += float(ftr) + bucket["responded_count"] += 1 + if ttr is not None: + bucket["total_resolution_hours"] += float(ttr) + bucket["resolved_count"] += 1 + + # Compute averages. + for p, b in by_priority.items(): + if b["responded_count"]: + b["avg_first_response_min"] = round( + b["total_first_response_min"] / b["responded_count"], 1, + ) + if b["resolved_count"]: + b["avg_resolution_hours"] = round( + b["total_resolution_hours"] / b["resolved_count"], 2, + ) + + breach_rate = round(total_breached / total_tickets, 3) if total_tickets else 0.0 + + return { + "total_tickets": total_tickets, + "total_breached": total_breached, + "breach_rate": breach_rate, + "by_priority": by_priority, + "verdict": ( + "healthy" if breach_rate < 0.10 + else "watch" if breach_rate < 0.25 + else "critical" + ), + } diff --git a/dealix/auto_client_acquisition/customer_ops/support_ticket_router.py b/dealix/auto_client_acquisition/customer_ops/support_ticket_router.py new file mode 100644 index 00000000..1705abcf --- /dev/null +++ b/dealix/auto_client_acquisition/customer_ops/support_ticket_router.py @@ -0,0 +1,149 @@ +"""Support ticket router — P0–P3 categorization + routing + first-response template.""" + +from __future__ import annotations + +import re +from typing import Any + +# 4 priority tiers Dealix supports. +SUPPORT_PRIORITIES: tuple[dict[str, Any], ...] = ( + { + "id": "P0", + "label_ar": "حرج جداً — أمان / إرسال خاطئ / تعطل كامل", + "first_response_minutes": 30, + "resolution_target_hours": 4, + "escalation_owner": "founder", + }, + { + "id": "P1", + "label_ar": "خدمة مهمة معطلة", + "first_response_minutes": 120, + "resolution_target_hours": 24, + "escalation_owner": "operator_oncall", + }, + { + "id": "P2", + "label_ar": "Connector أو Proof Pack متأخر", + "first_response_minutes": 480, # 8h + "resolution_target_hours": 72, + "escalation_owner": "operator_oncall", + }, + { + "id": "P3", + "label_ar": "سؤال عام / تحسين", + "first_response_minutes": 1440, # 24h + "resolution_target_hours": 168, # 1 week + "escalation_owner": "operator_team", + }, +) + + +# Keyword → priority hints. +_P0_KEYWORDS = ( + "أمان", "تسريب", "إرسال خاطئ", "إرسال بدون موافقة", + "بدون موافقتي", "أرسل رسالة بدون", "أرسل بدون", + "secret", "leak", "data breach", "outage", "completely down", + "live charge", "charge بدون موافقة", "unauthorized", +) +_P1_KEYWORDS = ( + "service down", "خدمة معطلة", "service failed", + "Pilot stopped", "Proof Pack مفقود", +) +_P2_KEYWORDS = ( + "connector", "Gmail", "Calendar", "Sheets", + "WhatsApp setup", "Moyasar invoice", +) + + +def classify_ticket_priority(text: str) -> dict[str, Any]: + """ + Classify a free-text support ticket → P0 / P1 / P2 / P3. + + Deterministic keyword matching. Returns matched priority + reasoning. + """ + text = (text or "").strip() + if not text: + return {"priority": "P3", "reason_ar": "لا يوجد نص — اعتبار افتراضي."} + + text_lc = text.lower() + for kw in _P0_KEYWORDS: + if kw in text or kw.lower() in text_lc: + return { + "priority": "P0", + "matched_keyword": kw, + "reason_ar": f"كلمة حرجة مطابقة: {kw}", + } + for kw in _P1_KEYWORDS: + if kw in text or kw.lower() in text_lc: + return { + "priority": "P1", + "matched_keyword": kw, + "reason_ar": f"خدمة مهمة معطلة: {kw}", + } + for kw in _P2_KEYWORDS: + if kw in text or kw.lower() in text_lc: + return { + "priority": "P2", + "matched_keyword": kw, + "reason_ar": f"connector أو Proof Pack: {kw}", + } + return {"priority": "P3", "reason_ar": "افتراضي — سؤال أو تحسين."} + + +def route_ticket( + *, + text: str, + customer_id: str = "", + contact_email: str = "", +) -> dict[str, Any]: + """Classify + route a ticket to the right SLA + owner.""" + classification = classify_ticket_priority(text) + priority = classification["priority"] + + sla = next( + (dict(p) for p in SUPPORT_PRIORITIES if p["id"] == priority), + dict(SUPPORT_PRIORITIES[3]), + ) + + return { + "customer_id": customer_id, + "contact_email": contact_email, + "priority": priority, + "classification": classification, + "sla": sla, + "first_response_template": build_first_response_template(priority), + "approval_required": True, + "live_send_allowed": False, + } + + +def build_first_response_template(priority: str) -> dict[str, Any]: + """Build an Arabic first-response template per priority.""" + if priority == "P0": + body = ( + "وصلني بلاغك الآن. نتعامل معه كأولوية حرجة. " + "سأرد عليك خلال 30 دقيقة بتفاصيل ما حدث + الإجراءات المتخذة. " + "إذا اكتشفت أي إرسال غير معتمد أو تسريب بيانات، سأتواصل معك مباشرة." + ) + elif priority == "P1": + body = ( + "وصلني بلاغك. نتعامل معه كأولوية عالية. " + "سأرد بتفاصيل خلال ساعتين كحد أقصى." + ) + elif priority == "P2": + body = ( + "وصلني سؤالك حول الـ connector / Proof Pack. " + "سأتابع خلال 8 ساعات عمل وأرسل لك حل أو خطوات تالية." + ) + else: + body = ( + "شاكر لك على ملاحظتك. سأرد عليك خلال 24 ساعة عمل. " + "إذا الأمر عاجل، اكتب 'حرج' في رسالة جديدة وأرفعها للأولوية." + ) + + return { + "priority": priority, + "body_ar": body, + "approval_required": True, + "live_send_allowed": False, + } diff --git a/dealix/docs/APPROVED_MARKET_MESSAGING.md b/dealix/docs/APPROVED_MARKET_MESSAGING.md new file mode 100644 index 00000000..72f5f29d --- /dev/null +++ b/dealix/docs/APPROVED_MARKET_MESSAGING.md @@ -0,0 +1,119 @@ +# Approved Market Messaging — رسائل التسويق المعتمدة + +> أي رسالة (LinkedIn post / X tweet / email / landing copy / WhatsApp DM) لازم تُختار من هنا أو متوافقة مع نبرة هذا الملف. + +--- + +## Tagline (موحّد) + +> **Dealix — Saudi Revenue Execution OS** + +النسخة العربية: +> **Dealix — نظام تشغيل الإيرادات السعودي** + +--- + +## Elevator Pitch — 30 ثانية + +> Dealix يشغّل النمو للشركات السعودية والوكالات: يكتشف الفرص، يكتب الرسائل بالعربي، يطلب موافقتك قبل أي تواصل، ينسق الاجتماعات، ويثبت العائد بـ Proof Pack شهري — دون scraping ولا cold WhatsApp. + +--- + +## 5 Headlines المعتمدة + +1. **حوّل بياناتك وقنواتك إلى فرص ورسائل واجتماعات وProof Pack.** +2. **شغّل Dealix لعملائك كـ Agency Growth OS.** +3. **10 فرص في 10 دقائق + متابعة + Proof Pack — وأنت توافق قبل أي تواصل.** +4. **Approval-first — لا scraping، لا cold WhatsApp، لا وعود مضمونة.** +5. **Saudi Tone — رسائل عربية طبيعية، ليست ترجمة.** + +--- + +## Positioning vs Competitors (نقاط مقارنة معتمدة) + +| المنافس | ما نقوله | +|---------|---------| +| HubSpot/Salesforce | "Dealix أخف، عربي، service-led، يجيب 'وش أسوي اليوم؟' للـ SMB." | +| Gong | "Dealix يبدأ قبل المكالمة: targeting → رسالة → موافقة → اجتماع → proof." | +| Clay | "Dealix يحول البيانات إلى خدمة مدفوعة + workflow + Proof Pack." | +| WhatsApp tools | "Dealix يقرر هل التواصل آمن، يطلب موافقة، ويثبت العائد." | +| Agencies | "Dealix يحول خدمات الوكالة إلى operating system قابل للتكرار." | +| Generic AI agent | "Dealix لديه services + policies + approvals + proof + revenue work units." | + +--- + +## Outreach Templates (4 segments) + +### وكالات تسويق B2B +هلا [الاسم]، عندي Beta خاص للوكالات. +Dealix يساعد الوكالة تطلع فرص لعملائها، تجهز رسائل عربية، تدير موافقات، وتطلع Proof Pack بعلامة الوكالة والعميل. +أبحث عن وكالة واحدة نجرب معها Pilot مشترك على عميل حقيقي. يناسبك ديمو 15 دقيقة؟ + +### تدريب / استشارات +هلا [الاسم]، متابع توسعكم في برامج الشركات. +Dealix يطلع لكم 10 فرص B2B خلال 7 أيام، يكتب الرسائل بالعربي، ويخلي صانع القرار يوافق قبل أي تواصل، وبعدها يعطي Proof Pack. +Pilot بـ499 ريال أو مجاني مقابل case study. يناسبك ديمو 12 دقيقة؟ + +### SaaS / تقنية صغيرة +هلا [الاسم]، رأيت إصدار النسخة الجديدة من منتجكم — مبروك. +نشتغل على مدير نمو عربي يطلع 10 فرص B2B عبر LinkedIn Lead Forms (لا scraping)، ويكتب الرسائل بالعربي. +أبغى أجربه مع شركة SaaS سعودية واحدة. يناسبك ديمو 12 دقيقة؟ + +### خدمات بقاعدة واتساب +هلا [الاسم]، عندكم قاعدة عملاء واتساب نشطة، صحيح؟ +Dealix ينظف القائمة، يصنف الـ opt-in، يحظر cold WhatsApp تلقائياً، ويكتب رسائل عربية للحملات الآمنة + Proof Pack شهري. +List Intelligence بـ499–1,500 ريال. يناسبك أعطيك تشخيص مجاني أولاً؟ + +--- + +## Social Posts المعتمدة + +### LinkedIn — Founder voice +بعد عام من التطوير، Dealix جاهز كـ Saudi Revenue Execution OS. + +ليس CRM. ليس bot. ليس scraper. +هو نظام يُشغّل النمو لشركتك أو وكالتك: +• 10 فرص B2B + رسائل عربية + متابعة + Proof Pack +• Approval-first في كل قناة +• PDPL-aware: لا cold WhatsApp + +أفتح 5 Pilots بـ499 ريال هذا الأسبوع. مهتم؟ + +### X/Twitter — أقصر +Dealix Private Beta متاحة: +- 10 فرص B2B + رسائل عربية خلال 7 أيام +- Approval-first — لا cold WhatsApp ولا scraping +- 499 ريال أو مجاني مقابل case study + +أبحث عن 5 شركات سعودية B2B لتجربة محدودة. DM للتفاصيل. + +--- + +## ما يتوافق مع الـ tone + +- ✅ "نطلع لك" بدلاً من "نضمن لك". +- ✅ "خلال 7 أيام" بدلاً من "خلال ساعات". +- ✅ "بدون scraping" بدلاً من "نسحب كل البيانات". +- ✅ "بموافقتك" بدلاً من "تلقائياً". +- ✅ "Pilot 499 ريال" بدلاً من "تجربة مجانية مفتوحة". + +--- + +## Slogan-bank (لاستخدام لاحق) + +- "نمو محسوب، لا وعود." +- "كل قرار له Proof." +- "تشغّل، نوافق، نتابع، نثبت." +- "Saudi-first. Service-led. Approval-first." + +--- + +## القاعدة + +كل copy يدخل الإنتاج يجب أن يمر: +1. `safety_eval()` — لا "ضمان 100%" / "آخر فرصة". +2. `saudi_tone_eval()` — score ≥50. +3. مراجعة بشرية ضد `PROHIBITED_CLAIMS.md`. +4. `quality_review.review_service_before_launch` للخدمة المرتبطة. + +أي copy يفشل → لا يخرج. diff --git a/dealix/docs/CONNECTOR_SETUP_GUIDES.md b/dealix/docs/CONNECTOR_SETUP_GUIDES.md new file mode 100644 index 00000000..7713da4c --- /dev/null +++ b/dealix/docs/CONNECTOR_SETUP_GUIDES.md @@ -0,0 +1,204 @@ +# Connector Setup Guides + +> دليل مرجعي لربط كل قناة. **القاعدة:** `draft_only` افتراضياً. لا live action قبل env flag صريح + اعتماد بشري. + +--- + +## 11 Connectors المدعومة + +| Key | Default Mode | Phase | Blocking للـ first service | +|-----|--------------|------:|--------------------------| +| gmail | draft_only | 1 | لا | +| google_calendar | draft_only | 1 | لا | +| google_sheets | approved_execute | 1 | لا | +| moyasar | manual | 1 | لا | +| whatsapp_cloud | draft_only | 1 | **نعم** | +| website_forms | approved_execute | 1 | لا | +| linkedin_lead_forms | ingest_only | 2 | لا | +| google_business_profile | draft_only | 2 | لا | +| crm_generic | draft_only | 2 | لا | +| google_meet | ingest_only | 2 | لا | +| instagram_graph | ingest_only | 3 | لا | + +--- + +## 1. Gmail (drafts فقط افتراضياً) + +**Scopes المطلوبة:** +- `gmail.compose` (لإنشاء drafts) +- `gmail.modify` (لإدارة الـ labels — read-only labels فقط في Phase 1) + +**خطوات:** +1. Google Cloud Console → Create OAuth client. +2. أضف Dealix كـ application authorized. +3. منح الصلاحيات على scopes أعلاه فقط. +4. Dealix يستلم refresh_token + access_token. +5. وضع التشغيل: `connected_draft_only`. + +**Live send:** يتطلب `GMAIL_ALLOW_LIVE_SEND=true` env + اعتماد بشري للرسالة. + +--- + +## 2. Google Calendar (drafts فقط) + +**Scopes:** +- `calendar.events` (drafts only) + +**خطوات:** +1. نفس OAuth client من Gmail. +2. أضف scope الـ calendar. +3. Dealix يبني draft events. +4. لا insert إلا بعد: + - `CALENDAR_ALLOW_LIVE_INSERT=true` + - اعتماد بشري لكل event. + +--- + +## 3. Google Sheets (read + append بموافقة) + +**Scopes:** +- `sheets.readonly` للقراءة +- `sheets` للكتابة (append فقط) + +**خطوات:** +1. نفس OAuth. +2. حدد الـ Spreadsheet ID المستخدم لـ Pilot. +3. Dealix يقرأ leads + يكتب Proof Pack. + +**Live append:** يحتاج اعتماد للحقول الحساسة. لا overwrite تلقائي. + +--- + +## 4. Moyasar (manual فقط في Phase 1) + +**عملية الإعداد:** +1. حساب Moyasar dashboard. +2. **لا** إدخال API keys في Dealix. +3. عند طلب دفع: + - Dealix يولّد invoice instructions (halalas-correct). + - المؤسس يدخل Moyasar manually + ينشئ invoice. + - يضع invoice URL في Dealix. +4. تأكيد paid: يدوي عبر Moyasar dashboard ثم تحديث pipeline_tracker. + +**Phase 2:** ربط API + auto-invoice (مع env flag + audit). + +--- + +## 5. WhatsApp Cloud (Blocking — drafts فقط) + +**هذا أهم connector.** بدون WhatsApp opt-in audit، Dealix لا يفعّل first service. + +**خطوات:** +1. Meta Developer Account → WhatsApp Business Cloud. +2. Phone number verification. +3. Webhook URL = Dealix endpoint. +4. **مهم:** opt-in audit أولاً عبر `whatsapp_strategy.requires_opt_in`. + +**Live send:** يتطلب: +- `WHATSAPP_ALLOW_LIVE_SEND=true` +- opt-in موثّق لكل رقم. +- اعتماد بشري للرسالة. +- موافقة العميل على template. + +--- + +## 6. Website Forms (آمنة) + +**خطوات:** +1. أضف form على موقع العميل. +2. Webhook URL = Dealix endpoint. +3. كل form submission يدخل كـ `form.submitted` event. +4. Dealix يبني opportunity card تلقائياً. + +**Live send:** auto-acknowledgment email/WhatsApp مسموح بعد opt-in في الـ form. + +--- + +## 7. LinkedIn Lead Gen Forms (Phase 2) + +**القاعدة:** lead forms فقط — **لا scraping** ولا auto-DM. + +**خطوات:** +1. LinkedIn Campaign Manager → Lead Gen Form. +2. Hidden fields: `campaign_name`, `sector`, `sales_owner`. +3. Webhook إلى Dealix. +4. كل lead → `linkedin_lead_form` source = safe. + +--- + +## 8. Google Business Profile (Phase 2) + +**Scopes:** +- `business.manage` +- `reviews.read` + +**خطوات:** +1. ربط GBP location. +2. Dealix يقرأ reviews. +3. يبني draft reply لكل review. +4. **Live publish** يحتاج اعتماد + `GBP_ALLOW_LIVE_REPLY=true`. + +--- + +## 9. CRM Generic (Phase 2) + +**Supported:** HubSpot, Salesforce, Zoho, Close. + +**خطوات:** +1. OAuth حسب الـ CRM. +2. Read-only في الأسبوع الأول. +3. Write مع approval بعد الأسبوع الأول. +4. لا overwrite owner تلقائي. + +--- + +## 10. Google Meet (Phase 2) + +**Scopes:** +- `meetings.space.readonly` +- `conferenceRecords.transcripts.readonly` + +**خطوات:** +1. OAuth. +2. ingest transcripts بعد موافقة كل المشاركين. +3. Dealix يستخرج objections + next steps + buyer intent. +4. **لا** real-time listening في Phase 2. + +--- + +## 11. Instagram Graph (Phase 3) + +**Phase 3 connector.** ingest only لـ comments + DMs + insights. + +--- + +## Acceptance Criteria للـ connector + +كل connector يُعتبر مُعدّ بنجاح إذا: +1. State = `connected_draft_only` أو `connected_approved_execute`. +2. Test successful (Dealix قرأ شيء أو كتب draft). +3. لا secrets exposed في الـ logs/traces. +4. Audit entry في Action Ledger. + +--- + +## Troubleshooting + +| مشكلة | الحل | +|------|------| +| OAuth callback failed | recheck redirect_uri في Google/Meta console | +| WhatsApp Webhook 401 | تحقق من verify_token | +| Moyasar invoice URL لم يصل | تحقق من dashboard email settings | +| Sheets quota exceeded | خفض الـ append rate أو ربط second Sheet | +| Calendar conflicts | استخدم `freebusy.query` قبل draft event | + +--- + +## Endpoints + +``` +GET /api/v1/customer-ops/connectors/catalog +POST /api/v1/customer-ops/connectors/summary +POST /api/v1/customer-ops/connectors/update +GET /api/v1/customer-ops/connectors/demo +``` diff --git a/dealix/docs/CUSTOMER_SUCCESS_PLAYBOOK.md b/dealix/docs/CUSTOMER_SUCCESS_PLAYBOOK.md new file mode 100644 index 00000000..5d8c7e47 --- /dev/null +++ b/dealix/docs/CUSTOMER_SUCCESS_PLAYBOOK.md @@ -0,0 +1,141 @@ +# Customer Success Playbook + +> **القاعدة:** كل عميل له cadence محسوب بحسب الـ bundle. كل تدهور في النشاط يولّد at-risk alert تلقائياً. + +--- + +## Cadence Types + +``` +weekly_check_in +monthly_proof_review +quarterly_business_review +at_risk_alert +renewal_30_day +renewal_7_day +``` + +--- + +## Endpoints + +``` +POST /api/v1/customer-ops/cs/weekly-check-in +POST /api/v1/customer-ops/cs/at-risk-alert +POST /api/v1/customer-ops/cs/success-plan +``` + +--- + +## Weekly Check-in Agenda (25 دقيقة) + +1. مراجعة آخر Proof Pack (5 دقائق). +2. أبرز فرصة في الـ pipeline (5 دقائق). +3. أبرز خطر في القنوات (5 دقائق). +4. خطة الأسبوع القادم (5 دقائق). +5. أي مساعدة من فريقنا؟ (5 دقائق). + +**Talking points** (تتولد آلياً من metrics): +- "اعتمدتم {drafts_approved} رسالة هذا الأسبوع، ووصلكم {replies} رد." +- "تم تجهيز {meetings} اجتماع." +- "تم منع {risks_blocked} مخاطر تلقائياً." +- "Pipeline متأثر بقيمة {pipeline_sar:.0f} ريال." + +--- + +## At-Risk Detection + +النظام يحسب `risk_score` (0..100) من: + +| العامل | النقاط | +|--------|-------:| +| غير نشط ≥14 يوم | +40 | +| غير نشط ≥7 يوم | +20 | +| ≥10 drafts معلقة | +25 | +| ≥5 drafts معلقة | +10 | +| آخر Proof Pack ≥14 يوم | +30 | + +### Severity +- ≥60 → high → إيميل مؤسس + QBR هذا الأسبوع. +- ≥30 → medium → Proof Pack ملخص + ديمو خدمة جديدة. +- <30 → low → weekly check-in عادية. + +--- + +## Cadence per Bundle + +### Growth Starter +- Day 1: kick-off call + intake. +- Day 3: review first 3 opportunities + drafts. +- Day 7: deliver Proof Pack v1. +- Day 14: weekly check-in + upsell offer. +- Day 30: monthly proof review + renewal/upgrade decision. + +### Executive Growth OS +- Day 1: onboarding + connect channels. +- Day 7: first weekly Proof Pack. +- Day 14: weekly check-in + Founder Shadow Board v1. +- Day 21: monthly proof review. +- Day 30: QBR + annual upgrade conversation. + +### Partnership Growth +- Day 1: partner ICP intake. +- Day 5: 20 partners list + 10 outreach drafts. +- Day 10: 5 partner meetings booked. +- Day 14: weekly check-in. +- Day 30: partner scorecard + revenue share setup. + +--- + +## QBR (Quarterly Business Review) + +عند 90 يوم من اشتراك Growth OS: +1. مراجعة 3 Proof Packs السابقة. +2. حساب ROI: pipeline_x + closed_won_x. +3. مقارنة مع benchmarks القطاع (من `growth_memory`). +4. اقتراح تجارب الـ quarter القادم. +5. مراجعة الـ pricing tier. + +--- + +## Renewal Flow + +### 30-day-out +- إرسال Proof Pack الشهري + رسالة ودية. +- "نلاقيك في QBR لمراجعة العام القادم؟" + +### 7-day-out +- إذا لم يجدّد: إيميل من المؤسس + خصم سنوي 15%. +- إذا renewal at risk: at-risk alert تلقائي. + +### Renewal day +- إرسال invoice + شكر. +- بدء plan الـ quarter القادم. + +--- + +## Health Score Formula + +``` +csat (0..10) × 5 ++ pipeline_sar / 1000 ++ meetings × 8 ++ approval_rate × 50 +- days_inactive × 2 +- drafts_pending × 1 +``` + +``` +≥75 = healthy +50–74 = watch +<50 = at-risk +``` + +--- + +## ما لا يحدث في CS + +- لا "عرض ترقية" قبل تسليم أول Proof Pack. +- لا spam check-ins (max 1 في الأسبوع). +- لا تخطي الـ at-risk alert إذا تجاوز high. +- لا تعديل cadence بدون موافقة العميل. diff --git a/dealix/docs/DEALIX_100_PERCENT_LAUNCH_PLAN.md b/dealix/docs/DEALIX_100_PERCENT_LAUNCH_PLAN.md index d9a05c0f..cbfc7741 100644 --- a/dealix/docs/DEALIX_100_PERCENT_LAUNCH_PLAN.md +++ b/dealix/docs/DEALIX_100_PERCENT_LAUNCH_PLAN.md @@ -342,6 +342,40 @@ OAuth Gmail/Calendar، حصص، سياسات. **الفرق الشاسع:** Dealix لا يبيع features ولا AI ولا منصة. يبيع **شركة نمو رقمية ذاتية التشغيل** — نتائج منظمة + تشغيل يومي + Proof Pack شهري. +## 45. Positioning Lock + Customer Ops + Companies/Marketers + +**8 modules + 20 endpoints + 44 tests + 2 modes + 7 docs**. التفاصيل: + +### Positioning Lock (3 docs) +- [`POSITIONING_LOCK.md`](POSITIONING_LOCK.md) — category, one-liner, primary buyers (شركات + وكالات), wedge, 5 approved claims, 5 modes, 5 bundles. +- [`PROHIBITED_CLAIMS.md`](PROHIBITED_CLAIMS.md) — 8 categories of forbidden marketing language + how they're enforced technically. +- [`APPROVED_MARKET_MESSAGING.md`](APPROVED_MARKET_MESSAGING.md) — tagline, elevator pitch, headlines, 4 outreach segments, social posts, slogan bank. + +### Customer Ops (6 modules) +- `onboarding_checklist`: 8-step Pilot onboarding with progress tracking. +- `connector_setup_status`: 11 connectors (Gmail/Calendar/Sheets/Moyasar/WhatsApp/Forms/LinkedIn/GBP/CRM/Meet/Instagram) with state machine + ready_for_first_service gate. +- `support_ticket_router`: 4-tier P0/P1/P2/P3 classification + Arabic first-response templates. +- `sla_tracker`: per-priority SLA targets + breach detection + weekly health (healthy/watch/critical). +- `customer_success_cadence`: 6 cadence types + at-risk alerts (risk_score 0..100) + per-bundle 30-day plans + QBR. +- `incident_router`: SEV1/SEV2/SEV3 triage + auto-SEV1 on data leak / unauthorized send + canonical response plans (PDPL-aware). + +### Operator Modes (2 new) +- `self_growth_mode` — Dealix uses its own OS to find pilots (re-exports targeting_os.self_growth_mode + operator-tier reminders). +- `service_delivery_mode` — runs client services + tracks SLA + post-delivery handoff to Customer Success. + +### Customer-facing Pages (1 new + 1 updated) +- `landing/companies.html` — Saudi B2B companies. Approval-first, no scraping, 4 bundles. +- `landing/marketers.html` (updated) — agencies/marketers Agency Growth OS path. + +### Customer Ops Docs (5 new) +- [`ONBOARDING_RUNBOOK.md`](ONBOARDING_RUNBOOK.md) — day-by-day kick-off → first Proof Pack. +- [`SUPPORT_SLA.md`](SUPPORT_SLA.md) — P0..P3 + classifier keywords. +- [`INCIDENT_RESPONSE.md`](INCIDENT_RESPONSE.md) — SEV1..SEV3 + post-mortem templates + Arabic comms. +- [`CUSTOMER_SUCCESS_PLAYBOOK.md`](CUSTOMER_SUCCESS_PLAYBOOK.md) — weekly check-ins, at-risk detection, QBR, renewal. +- [`CONNECTOR_SETUP_GUIDES.md`](CONNECTOR_SETUP_GUIDES.md) — 11 connectors with scopes + steps + troubleshooting. + +**Endpoints:** `/api/v1/customer-ops/*` (20). + --- -**الخلاصة:** المنتج **قوي كأساس سوقي وتقني**؛ الإطلاق العام يحتاج تشغيلاً وامتثالاً وتجربة عميل مغلقة أولاً. الإطلاق اليوم = Private Beta + Pilots + Proof Pack، ليس Public Launch. +**الخلاصة:** المنتج **قوي كأساس سوقي وتقني**؛ الإطلاق العام يحتاج تشغيلاً وامتثالاً وتجربة عميل مغلقة أولاً. الإطلاق اليوم = Private Beta + Pilots + Proof Pack، ليس Public Launch. اليوم Dealix هو **Saudi Revenue Execution OS** بـ45 طبقة وثائقية، 949 اختبار ناجح، CI أخضر. diff --git a/dealix/docs/INCIDENT_RESPONSE.md b/dealix/docs/INCIDENT_RESPONSE.md new file mode 100644 index 00000000..2bcf4147 --- /dev/null +++ b/dealix/docs/INCIDENT_RESPONSE.md @@ -0,0 +1,111 @@ +# Dealix Incident Response + +> **القاعدة:** أي incident يمر بـ triage → severity → response plan → audit. أي تسريب بيانات أو إرسال غير معتمد = SEV1 تلقائي. + +--- + +## Severities + +| Severity | الوصف | First Action | Comm Cadence | +|----------|------|-------------:|-------------:| +| **SEV1** | تسريب أمان / إرسال غير معتمد / تعطل كامل | 15 دقيقة | كل 30 دقيقة | +| **SEV2** | خدمة معطلة لـ ≥5 عملاء | 30 دقيقة | كل ساعة | +| **SEV3** | تأثير محدود (عميل واحد / degraded) | 2 ساعة | كل 4 ساعات | + +--- + +## Triage Logic + +```python +if has_data_leak or has_unauthorized_send: + severity = "SEV1" +elif affected_customers >= 5: + severity = "SEV2" +else: + severity = "SEV3" +``` + +**Endpoints:** +- `POST /api/v1/customer-ops/incidents/triage` +- `GET /api/v1/customer-ops/incidents/response-plan/{severity}` + +--- + +## Canonical Response Plan (مشترك) + +1. **تجميد** الـ live actions على القناة المعنية فوراً. +2. **إخطار** المؤسس + on-call operator. +3. **إنشاء** incident channel مع timeline. +4. **مراجعة** Action Ledger للأفعال المرتبطة. +5. **إذا تسريب**: إخطار العملاء المتأثرين خلال 72 ساعة (PDPL). + +--- + +## SEV1 Additional Steps + +6. تواصل مباشر مع المؤسس + خلية أزمة. +7. كتابة post-mortem خلال 24 ساعة. +8. مراجعة قانونية إن لزم (DPA + PDPL implications). + +--- + +## SEV2 Additional Steps + +6. تحديث العملاء المتأثرين كل 60 دقيقة. +7. post-mortem خلال 48 ساعة. + +--- + +## SEV3 Additional Steps + +6. تحديث العميل المتأثر مع كل خطوة. +7. post-mortem اختياري (موصى به للأنماط المتكررة). + +--- + +## Post-Mortem Template + +``` +1. ملخص الحادث +2. timeline (timestamps) +3. السبب الجذري +4. ما اشتغل صح +5. ما اشتغل غلط +6. الـ action items للوقاية +7. الـ owner لكل action item +8. الـ deadline +``` + +--- + +## Communication Templates (Arabic) + +### SEV1 — أول ساعة +> اكتشفنا حدث أمني/تشغيلي يتعلق بـ [نوع الحادث]. أوقفنا الـ live actions على القناة المتأثرة. نتواصل معك خلال 30 دقيقة بتحديث. + +### SEV1 — تسريب بيانات +> نأسف. اكتشفنا تسريب بيانات يتعلق بـ [نوع البيانات]. نراجع الأثر الآن وسنتواصل معك خلال 24 ساعة بتفاصيل + خطوات الحماية. PDPL يلزم بالإبلاغ خلال 72 ساعة لذا سنحرص على إعلامك بكل ما نعرفه. + +### SEV2 +> خدمة [اسم الخدمة] متعطلة جزئياً. الفريق يعمل على الإصلاح ونتوقع الاستعادة خلال [وقت]. سنحدثك كل ساعة. + +--- + +## Auto-actions + +- **Dealix يجمد القناة تلقائياً** عند detection على: + - bounce_rate > 5% + - complaint_rate > 0.3% + - block_rate WhatsApp > 3% +- **Dealix يخطر المؤسس** على أي SEV1 detected. +- **Dealix يضيف entry لـ Action Ledger** لكل incident. + +--- + +## Permission to publish + +- Post-mortems خاصة لـ SEV1 لا تُنشر علناً إلا بعد: + - مراجعة قانونية. + - موافقة العملاء المتأثرين. + - إزالة كل PII. +- Post-mortems لـ SEV2/SEV3 يمكن نشرها كـ engineering blog لو مفيدة. diff --git a/dealix/docs/ONBOARDING_RUNBOOK.md b/dealix/docs/ONBOARDING_RUNBOOK.md new file mode 100644 index 00000000..5fc52732 --- /dev/null +++ b/dealix/docs/ONBOARDING_RUNBOOK.md @@ -0,0 +1,120 @@ +# Dealix Onboarding Runbook + +> **الهدف:** نقل عميل جديد من "وافق على الـ Pilot" إلى "أول Proof Pack" خلال 5 أيام عمل، بدون خطأ تشغيلي. + +--- + +## 8 خطوات الـ onboarding (محسوبة) + +| # | الخطوة | المدة | الاعتماد | +|---|--------|------|---------| +| 1 | اختيار الهدف | 2د | لا | +| 2 | اختيار الباقة | 3د | نعم | +| 3 | بيانات الشركة | 5د | لا | +| 4 | ربط القنوات (drafts فقط) | 8د | نعم | +| 5 | رفع قائمة أو ربط مصدر leads | 5د | نعم | +| 6 | مراجعة المخاطر (PDPL + سمعة) | 4د | نعم | +| 7 | تشغيل أول خدمة | async | نعم | +| 8 | استلام أول Proof Pack | async | لا | + +**Endpoints:** +- `POST /api/v1/customer-ops/onboarding/checklist` +- `POST /api/v1/customer-ops/onboarding/update-step` +- `GET /api/v1/customer-ops/onboarding/checklist/demo` + +--- + +## Day-by-day + +### Day 1 — Kick-off (60 دقيقة) +- مكالمة 30 دقيقة مع المؤسس / Growth Manager. +- ملء الـ intake (الخطوات 1-3). +- توقيع Pilot Agreement draft + DPA draft (يحتاج محامي للحالة الإنتاجية). +- إنشاء session في `operator_memory` + customer_id. + +### Day 2 — Connectors +- ربط Gmail (drafts فقط) — `connector_setup_status` يتعقّب التقدم. +- ربط Google Calendar (drafts فقط). +- ربط Google Sheets للـ exports. +- WhatsApp Cloud (إذا لازم) — opt-in audit أولاً. + +### Day 3 — List + Risk Review +- رفع CSV / ربط CRM. +- تشغيل `targeting_os.analyze_uploaded_list_preview`. +- مراجعة الـ contactability (safe / needs_review / blocked). +- اعتماد القنوات الآمنة فقط. + +### Day 4 — أول خدمة +- تشغيل First 10 Opportunities Sprint أو List Intelligence. +- توليد 10 opportunity cards + رسائل عربية. +- إرسال Approval Pack للعميل (≤3 أزرار لكل بطاقة). + +### Day 5 — Proof Pack v1 +- استلام Proof Pack مختصر (PDF + JSON + WhatsApp summary). +- جلسة مراجعة 30 دقيقة. +- تفعيل Customer Success Cadence (weekly check-ins). + +--- + +## Connector Setup Status + +11 connectors معرّفة في `customer_ops.connector_setup_status`: + +| Connector | Default Mode | Phase | Blocking | +|-----------|--------------|------:|----------| +| gmail | draft_only | 1 | لا | +| google_calendar | draft_only | 1 | لا | +| google_sheets | approved_execute | 1 | لا | +| moyasar | manual | 1 | لا | +| whatsapp_cloud | draft_only | 1 | **نعم** | +| website_forms | approved_execute | 1 | لا | +| linkedin_lead_forms | ingest_only | 2 | لا | +| google_business_profile | draft_only | 2 | لا | +| crm_generic | draft_only | 2 | لا | +| google_meet | ingest_only | 2 | لا | +| instagram_graph | ingest_only | 3 | لا | + +`ready_for_first_service` = `True` فقط عندما لا يوجد blocking connector مفقود + ≥1 connector connected. + +--- + +## Connector States + +``` +not_started → configuring → connected_draft_only + → connected_approved_execute +configuring → failed (يحتاج إعادة محاولة) +configuring → skipped (إذا قرر العميل عدم الربط) +``` + +--- + +## ما لا يحدث بدون اعتماد + +- ربط Gmail لا يفعّل send. +- ربط Calendar لا يفعّل insert. +- ربط Moyasar لا يفعّل charge. +- ربط WhatsApp لا يفعّل cold send. + +كل live action يحتاج env flag صريح + اعتماد بشري. + +--- + +## Onboarding Failure Recovery + +| فشل | الإجراء | +|-----|--------| +| OAuth Gmail فشل | recheck scopes, retry, fallback to draft-only | +| Moyasar invoice غير موصول | استخدم dashboard manual | +| العميل لم يرفع قائمة | اعرض Free Diagnostic + recommend_accounts | +| Risk review كشف مشكلة | توقّف، أرسل تقرير للمؤسس | + +--- + +## Acceptance Criteria + +العميل onboarded إذا: +1. كل الـ 8 خطوات `completed=True` (إلا الـ async منها). +2. `ready_for_first_service=True`. +3. Proof Pack v1 تم تسليمه + اعتماده. +4. Customer Success cadence مفعّل. diff --git a/dealix/docs/POSITIONING_LOCK.md b/dealix/docs/POSITIONING_LOCK.md new file mode 100644 index 00000000..912af177 --- /dev/null +++ b/dealix/docs/POSITIONING_LOCK.md @@ -0,0 +1,123 @@ +# Dealix Positioning Lock + +> **هذا الملف ثابت.** أي تغيير في الـ positioning يحتاج اعتماد المؤسس فقط. +> لا تكتب landing page ولا رسالة بيع ولا ديمو يخالف هذا الملف. + +--- + +## Category + +**Saudi Revenue Execution OS** + +ليس CRM. ليس بوت واتساب. ليس lead scraper. ليس AI chatbot عام. ليس وكالة تقليدية. + +--- + +## One-liner (الجملة الرسمية الوحيدة) + +> Dealix يشغّل النمو للشركات السعودية: يكتشف الفرص، يكتب الرسائل، يطلب الموافقات، ينسق الاجتماعات، ويثبت العائد. + +--- + +## Primary buyers (مساران فقط) + +### 1. الشركات (B2B services / SaaS / training / clinics / real estate) +- مؤسس / CEO / Growth Manager. +- يحتاج فرص + رسائل + اجتماعات + Proof Pack. +- ميزانية 499–10,000 ريال شهرياً. + +### 2. الوكالات والمسوقين +- صاحب وكالة / Head of Sales في وكالة. +- يبحث عن Growth OS لعملائها. +- يبحث عن revenue share + co-branded Proof Pack. + +--- + +## Wedge (نقطة الدخول للسوق) + +**First 10 Opportunities + Proof Pack** — خدمة Sprint 7 أيام بـ 499 ريال. + +كل شيء آخر ينمو من هذه النقطة. + +--- + +## Approved Claims (5 — ما يُسمح بقوله) + +1. "نطلع لك 10 فرص B2B + رسائل عربية + خطة متابعة + Proof Pack خلال 7 أيام." +2. "Approval-first — لا نرسل أي شيء بدون موافقتك." +3. "PDPL-aware — لا cold WhatsApp بدون opt-in." +4. "Multi-channel orchestration: Email + WhatsApp + Calendar + Sheets + LinkedIn Lead Forms." +5. "Saudi Tone — رسائل عربية طبيعية، ليست ترجمة." + +--- + +## Prohibited Claims (ما لا يُقال أبداً) + +راجع [`PROHIBITED_CLAIMS.md`](PROHIBITED_CLAIMS.md). + +--- + +## Headlines + +### Homepage +> Dealix — Saudi Revenue Execution OS + +### Companies page +> حوّل بياناتك وقنواتك إلى فرص ورسائل واجتماعات وProof Pack. + +### Agencies / marketers page +> شغّل Dealix لعملائك كـ Agency Growth OS. + +### Private Beta page +> 10 فرص في 10 دقائق + متابعة + Proof Pack — وأنت توافق قبل أي تواصل. + +--- + +## Modes (الـ 5 أوضاع التشغيلية) + +1. **CEO Mode** — قرارات يومية، اعتمادات، Proof، مخاطر، 3 خطوات تالية. +2. **Growth Manager Mode** — حملات، أهداف، متابعات، اجتماعات، صحة القنوات. +3. **Agency Partner Mode** — إضافة عميل، diagnostic، Proof Pack مشترك العلامة، revenue share. +4. **Self-Growth Mode** — Dealix يستهدف عملاءه بنفس النظام. +5. **Service Delivery Mode** — تشغيل خدمات العميل، SLA، deliverables، proof. + +--- + +## Bundles (5 — ما يُعرض للعميل) + +1. Growth Starter — 499 ريال. +2. Data to Revenue — 1,500 ريال. +3. Executive Growth OS — 2,999 ريال شهرياً. +4. Partnership Growth — 3,000–7,500 ريال. +5. Full Growth Control Tower — Custom. + +--- + +## What Dealix is NOT (إنفاذ صارم) + +- **ليس CRM** — لا نخزّن جميع بيانات العملاء، لا نحاول استبدال HubSpot. +- **ليس bot** — لا نتظاهر بأن AI يفعل كل شيء؛ هناك human approval في كل خطوة. +- **ليس lead scraper** — مصادر مصرّح بها فقط. +- **ليس وكالة بشرية** — نظام قابل للتكرار بـ Proof Pack محسوب. +- **ليس AI agent عام** — لكل خدمة sandbox + policy + budget. +- **ليس Tier-1 enterprise platform** — SMB-first، Saudi-first. + +--- + +## Core Workflow (لا يتغير) + +``` +Signal → Context → Service Recommendation → Workflow → +Risk Check → Draft → Approval → Execution/Export → +Outcome → Proof → Learning → Upgrade +``` + +كل event داخل Dealix يمر بهذه السلسلة. + +--- + +## Why this lock matters + +السوق يدعم هذا الاتجاه (HubSpot Growth Context, Gong Revenue AI OS, Salesforce Agentic Work Units), لكن **Dealix لا يحاول أن يكون أكبر منهم**. يحاول أن يكون **أوضح، أسرع، أعمق محلياً، وأقرب للإيراد** للشركات السعودية والوكالات. + +أي تشتت في الـ positioning يكلّفنا 3 أشهر من الإنتاجية. لذلك هذا الملف ثابت. diff --git a/dealix/docs/PROHIBITED_CLAIMS.md b/dealix/docs/PROHIBITED_CLAIMS.md new file mode 100644 index 00000000..705a234b --- /dev/null +++ b/dealix/docs/PROHIBITED_CLAIMS.md @@ -0,0 +1,107 @@ +# Prohibited Claims — ممنوع تماماً + +> أي landing page / رسالة بيع / ديمو / قائمة مزايا تحتوي إحدى هذه العبارات يجب رفضها فوراً. + +--- + +## 1. ادعاءات نتائج مضمونة + +- ❌ "نضمن لك عملاء" +- ❌ "نضمن مبيعات" +- ❌ "نتائج مضمونة 100%" +- ❌ "ROI مضمون 10x" +- ❌ "Money-back guarantee" (إلا في حالة Pilot واضح بشروط محدودة) + +**القاعدة:** نقول "Proof Pack بالأرقام" بدلاً من "نتيجة مضمونة". + +--- + +## 2. ادعاءات scraping أو بيانات غير مصرّح بها + +- ❌ "نسحب كل بيانات LinkedIn" +- ❌ "نستخرج جميع الأرقام من Google" +- ❌ "نجمع leads من أي مكان" +- ❌ "نحصل على إيميلات decision makers من Apollo" + +**القاعدة:** نقول "مصادر مصرّح بها: CRM، LinkedIn Lead Forms، website forms، manual research معتمد". + +--- + +## 3. ادعاءات automation كاملة + +- ❌ "نرسل تلقائياً للجميع" +- ❌ "Dealix يدير كل شيء بدونك" +- ❌ "Auto-DM على LinkedIn" +- ❌ "Cold WhatsApp campaigns جاهزة" + +**القاعدة:** نقول "Approval-first — لا إرسال بدون موافقتك. Drafts فقط افتراضياً". + +--- + +## 4. ادعاءات تجاوز الموافقات + +- ❌ "بدون مكالمة" +- ❌ "بدون فريق" +- ❌ "بدون مراجعة" +- ❌ "Ai-only — لا تدخل بشري" + +**القاعدة:** نقول "بشرية القرار، آلية التنفيذ — Approval Center في كل خطوة". + +--- + +## 5. ادعاءات منصات منافسة + +- ❌ "بديل HubSpot" +- ❌ "أرخص من Salesforce" +- ❌ "نقتل CRM التقليدي" +- ❌ "أقوى من Gong" + +**القاعدة:** نقول "Saudi Revenue Execution OS — يكمّل CRMs، لا يستبدلها". + +--- + +## 6. ادعاءات قانونية/مالية + +- ❌ "نتجاوز PDPL" +- ❌ "نخفي بياناتك من الجهات الرسمية" +- ❌ "نضمن عودة استثمارك" +- ❌ "Tax-deductible automatically" + +**القاعدة:** نقول "PDPL-aware. DPA draft جاهز. أي عقد يحتاج مراجعة قانونية". + +--- + +## 7. ادعاءات طبية أو جدية + +- ❌ "يعالج مشاكل العمل" +- ❌ "يشفي شركتك من الركود" +- ❌ "علاج مضمون لقلة العملاء" + +**القاعدة:** لا تستخدم لغة طبية أو علاجية. نقول "نحسّن الـ pipeline". + +--- + +## 8. ادعاءات سرعة مبالغ فيها + +- ❌ "10 عملاء خلال 24 ساعة" +- ❌ "مليون ريال خلال شهر" +- ❌ "نمو 1000% أسبوعياً" + +**القاعدة:** نقول "10 فرص خلال 7 أيام، ضمن workflow approval-first". + +--- + +## كيف نفرضها تقنياً + +1. **Safety Eval** — `agent_observability.safety_eval()` يكتشف "ضمان 100%" و"آخر فرصة" تلقائياً. +2. **Saudi Tone Eval** — يرفض "best-in-class" و"synergy". +3. **Quality Review Gate** — في `service_excellence.quality_review` أي خدمة بدون proof_metrics blocked. +4. **Tool Action Planner** — يحظر LinkedIn scraping و auto-DM في الكود مباشرة. +5. **Test `test_positioning_lock.py`** — يفحص landing pages وlanding/services.html والـ docs ضد هذه القائمة. + +--- + +## القاعدة الذهبية + +> **لو تحتاج إثبات قبل القول، لا تقله.** +> Dealix يبيع نتائج محسوبة بـ Proof Pack، لا وعود تسويقية. diff --git a/dealix/docs/SUPPORT_SLA.md b/dealix/docs/SUPPORT_SLA.md new file mode 100644 index 00000000..3c9db508 --- /dev/null +++ b/dealix/docs/SUPPORT_SLA.md @@ -0,0 +1,95 @@ +# Dealix Support SLA + +> **القاعدة:** كل tickets تُصنّف P0/P1/P2/P3 آلياً، لها أهداف first-response و resolution محددة، ويتم تتبع كل تجاوز. + +--- + +## Priority Tiers + +| Priority | الوصف | First Response | Resolution Target | Owner | +|----------|------|---------------:|------------------:|-------| +| **P0** | حرج جداً — أمان / إرسال خاطئ / تعطل كامل | 30 دقيقة | 4 ساعات | Founder | +| **P1** | خدمة مهمة معطلة | 2 ساعة | 24 ساعة | Operator on-call | +| **P2** | Connector أو Proof Pack متأخر | 8 ساعات | 72 ساعة | Operator on-call | +| **P3** | سؤال عام / تحسين | 24 ساعة | 7 أيام | Operator team | + +--- + +## Endpoints + +``` +POST /api/v1/customer-ops/support/classify # تصنيف ticket → priority +POST /api/v1/customer-ops/support/route # routing مع SLA + first response template +POST /api/v1/customer-ops/sla/event # تسجيل opened/first_response/resolved/escalated +POST /api/v1/customer-ops/sla/classify-breach # تحديد إن كان في breach +POST /api/v1/customer-ops/sla/health-report # تقرير صحة SLA من tickets list +GET /api/v1/customer-ops/sla/health-report/demo # demo +``` + +--- + +## Auto-classification Keywords + +### P0 (حرج جداً) +- أمان +- تسريب +- إرسال خاطئ +- إرسال بدون موافقة / بدون موافقتي +- secret / leak / data breach +- outage / completely down +- live charge / charge بدون موافقة +- unauthorized + +### P1 (خدمة معطلة) +- service down / خدمة معطلة +- service failed +- Pilot stopped +- Proof Pack مفقود + +### P2 (connector أو proof) +- connector / Gmail / Calendar / Sheets +- WhatsApp setup +- Moyasar invoice + +### P3 (افتراضي) +أي ticket لم يتطابق مع P0/P1/P2. + +--- + +## First-Response Templates + +كل priority لها قالب رد أولي عربي معد مسبقاً عبر `build_first_response_template(priority)`. + +### مثال P0 +> وصلني بلاغك الآن. نتعامل معه كأولوية حرجة. سأرد عليك خلال 30 دقيقة بتفاصيل ما حدث + الإجراءات المتخذة. إذا اكتشفت أي إرسال غير معتمد أو تسريب بيانات، سأتواصل معك مباشرة. + +--- + +## Health Report Verdict + +عبر `build_sla_health_report`: +- **healthy**: breach_rate < 10% +- **watch**: 10% ≤ breach_rate < 25% +- **critical**: breach_rate ≥ 25% + +عند `critical` → escalate تلقائي للمؤسس + إيقاف الـ live actions حتى المراجعة. + +--- + +## Weekly SLA Review + +كل اثنين: +1. تجميع كل tickets الأسبوع المنقضي. +2. تشغيل `build_sla_health_report`. +3. مراجعة الـ breaches. +4. تحديث `customer_success_cadence` للعملاء المتأثرين. +5. إذا critical → post-mortem + `incident_router`. + +--- + +## ما لا يحدث في الـ support + +- لا response تلقائي للعميل بدون مراجعة بشرية. +- لا تسريب لـ ticket id في القنوات العامة. +- لا فتح ticket بـ priority < classified-priority (الـ system يحدد، البشر يرفع فقط). +- لا إغلاق ticket بدون تأكيد من العميل. diff --git a/dealix/landing/companies.html b/dealix/landing/companies.html new file mode 100644 index 00000000..174df91b --- /dev/null +++ b/dealix/landing/companies.html @@ -0,0 +1,125 @@ + + + + + +Dealix للشركات — Saudi Revenue Execution OS + + + +
+

Dealix للشركات

+

حوّل بياناتك وقنواتك إلى فرص ورسائل واجتماعات وProof Pack — + Approval-first في كل خطوة، بدون scraping ولا cold WhatsApp.

+
+ +
+
+

المشكلة

+
    +
  • عندك إيميل وقائمة عملاء قدامى وقنوات نشطة، لكن لا تعرف وش أهم شيء اليوم.
  • +
  • الفريق يقضي وقت كبير على الـ outreach بدون نتائج محسوبة.
  • +
  • تخاف من حظر القناة لو أرسلت بدون عناية.
  • +
  • لا يوجد Proof واضح للإدارة عن العائد.
  • +
+
+ +
+

كيف يعمل Dealix

+
    +
  • اختر هدفك: عملاء جدد / استخدام قائمة / شراكات / تشغيل يومي.
  • +
  • Dealix يوصي بالخدمة + يجمع intake.
  • +
  • يطلع لك 10 فرص B2B + رسائل عربية + خطة متابعة.
  • +
  • كل رسالة تنتظر اعتمادك قبل الإرسال.
  • +
  • Proof Pack أسبوعي + Founder Shadow Board شهري.
  • +
+
+ +
+

الباقات

+
+
+
Growth Starter
+
499 ريال
+
Free Diagnostic + First 10 Opportunities + Proof Pack مختصر
+
+
+
Data to Revenue
+
1,500 ريال
+
List Intelligence + Top 50 Targets + رسائل + Risk Report
+
+
+
Executive Growth OS
+
2,999 ريال شهرياً
+
Daily Brief + Approvals + Proof Pack أسبوعي
+
+
+
Full Growth Control Tower
+
Custom
+
30 يوم — كل الخدمات على مراحل
+
+
+
+ +
+ ضمانات Dealix: + Approval-first في كل قناة. لا scraping. لا cold WhatsApp. + لا charge بدون موافقة. لا وعود مضمونة. Proof Pack بالأرقام. +
+ +
+

Proof Pack

+
    +
  • Opportunities created.
  • +
  • Drafts created + approved.
  • +
  • Replies received + meetings drafted.
  • +
  • Pipeline influenced (SAR).
  • +
  • Risks blocked (مخاطر منعت تلقائياً).
  • +
  • Time saved (ساعات).
  • +
+
+ +
+

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

+
    +
  • Approval-first في كل قناة — لا live send بدون اعتمادك.
  • +
  • PDPL-aware: لا cold WhatsApp، DPA draft جاهز.
  • +
  • Secret redactor + Patch firewall + Trace redactor.
  • +
  • Saudi Tone + Safety eval قبل كل رسالة.
  • +
  • Action Ledger يسجّل كل فعل + من اعتمده.
  • +
+
+ +
+ ابدأ بالتشخيص المجاني + احجز ديمو 12 دقيقة +
+
+ + diff --git a/dealix/tests/unit/test_customer_ops.py b/dealix/tests/unit/test_customer_ops.py new file mode 100644 index 00000000..1c304764 --- /dev/null +++ b/dealix/tests/unit/test_customer_ops.py @@ -0,0 +1,268 @@ +"""Unit tests for Customer Ops.""" + +from __future__ import annotations + +import pytest + +from auto_client_acquisition.customer_ops import ( + SUPPORT_PRIORITIES, + SUPPORTED_CONNECTORS, + build_at_risk_alert, + build_connector_setup_summary, + build_customer_success_plan, + build_first_response_template, + build_incident_response_plan, + build_onboarding_checklist, + build_sla_health_report, + build_weekly_check_in, + classify_sla_breach, + classify_ticket_priority, + record_sla_event, + route_ticket, + triage_incident, + update_connector_status, + update_onboarding_step, +) + + +# ── Onboarding ─────────────────────────────────────────────── +def test_onboarding_checklist_has_8_steps(): + out = build_onboarding_checklist(customer_id="c1") + assert out["total_steps"] == 8 + assert out["current_step_id"] == "select_goal" + + +def test_update_onboarding_step_completes(): + cl = build_onboarding_checklist(customer_id="c1") + cl = update_onboarding_step(cl, step_id="select_goal", completed=True) + assert cl["progress_pct"] == 12.5 + assert cl["current_step_id"] == "select_bundle" + + +def test_update_onboarding_step_unknown(): + cl = build_onboarding_checklist(customer_id="c1") + cl = update_onboarding_step(cl, step_id="bogus_step") + assert "error" in cl + + +def test_complete_all_onboarding_steps(): + cl = build_onboarding_checklist(customer_id="c1") + for s in list(cl["steps"]): + cl = update_onboarding_step(cl, step_id=s["id"], completed=True) + assert cl["progress_pct"] == 100.0 + assert cl["current_step_id"] == "done" + + +# ── Connectors ─────────────────────────────────────────────── +def test_supported_connectors_includes_critical(): + keys = {c["key"] for c in SUPPORTED_CONNECTORS} + for required in ("gmail", "google_calendar", "moyasar", "whatsapp_cloud", + "google_sheets", "website_forms", "linkedin_lead_forms"): + assert required in keys + + +def test_connector_summary_with_blocking_missing(): + out = build_connector_setup_summary( + customer_id="c1", + statuses={"gmail": {"state": "connected_draft_only"}}, + ) + assert "whatsapp_cloud" in out["blocking_missing"] + assert out["ready_for_first_service"] is False + + +def test_connector_summary_ready(): + out = build_connector_setup_summary( + customer_id="c1", + statuses={ + "gmail": {"state": "connected_draft_only"}, + "whatsapp_cloud": {"state": "connected_draft_only"}, + }, + ) + assert out["ready_for_first_service"] is True + + +def test_update_connector_status_validates(): + statuses: dict = {} + with pytest.raises(ValueError): + update_connector_status(statuses, connector_key="gmail", + state="totally_invalid") + + +def test_update_connector_status_writes(): + statuses: dict = {} + update_connector_status(statuses, connector_key="gmail", + state="connected_draft_only") + assert statuses["gmail"]["state"] == "connected_draft_only" + + +# ── Support routing ────────────────────────────────────────── +def test_classify_p0_for_security_keywords(): + out = classify_ticket_priority("اكتشفت تسريب في trace logs") + assert out["priority"] == "P0" + + +def test_classify_p0_for_unauthorized_send(): + out = classify_ticket_priority("Dealix أرسل رسالة بدون موافقتي") + assert out["priority"] == "P0" + + +def test_classify_p1_for_service_down(): + out = classify_ticket_priority("Pilot stopped working today") + assert out["priority"] == "P1" + + +def test_classify_p2_for_connector_issue(): + out = classify_ticket_priority("My Gmail connector won't authenticate") + assert out["priority"] == "P2" + + +def test_classify_p3_default(): + out = classify_ticket_priority("سؤال بسيط عن الأسعار") + assert out["priority"] == "P3" + + +def test_classify_empty_returns_p3(): + out = classify_ticket_priority("") + assert out["priority"] == "P3" + + +def test_route_ticket_includes_sla(): + out = route_ticket(text="تسريب أمان", customer_id="c1") + assert out["priority"] == "P0" + assert out["sla"]["first_response_minutes"] == 30 + assert out["live_send_allowed"] is False + + +def test_first_response_p0_arabic(): + out = build_first_response_template("P0") + assert "30 دقيقة" in out["body_ar"] + assert out["live_send_allowed"] is False + + +def test_support_priorities_count(): + assert len(SUPPORT_PRIORITIES) == 4 + + +# ── SLA ────────────────────────────────────────────────────── +def test_sla_event_validates(): + with pytest.raises(ValueError): + record_sla_event(ticket_id="t1", priority="P0", event="bogus") + + +def test_sla_event_appends_to_log(): + log: list = [] + record_sla_event(ticket_id="t1", priority="P0", event="opened", log=log) + assert len(log) == 1 + + +def test_classify_breach_within_target(): + out = classify_sla_breach( + priority="P0", minutes_to_first_response=20, hours_to_resolve=3, + ) + assert out["breached"] is False + + +def test_classify_breach_exceeded(): + out = classify_sla_breach( + priority="P0", minutes_to_first_response=120, hours_to_resolve=10, + ) + assert out["breached"] is True + assert len(out["breaches"]) == 2 + + +def test_sla_health_report_aggregates(): + out = build_sla_health_report(tickets=[ + {"priority": "P0", "first_response_min": 12, "resolution_hours": 2}, + {"priority": "P1", "first_response_min": 90, "resolution_hours": 18}, + {"priority": "P3", "first_response_min": 1500, "resolution_hours": 200}, + ]) + assert out["total_tickets"] == 3 + assert out["total_breached"] == 1 # only P3 breached + + +def test_sla_health_verdict_critical(): + out = build_sla_health_report(tickets=[ + {"priority": "P0", "first_response_min": 60, "resolution_hours": 10}, + {"priority": "P0", "first_response_min": 120, "resolution_hours": 20}, + {"priority": "P0", "first_response_min": 180, "resolution_hours": 30}, + {"priority": "P0", "first_response_min": 240, "resolution_hours": 40}, + ]) + assert out["verdict"] == "critical" + + +# ── Incidents ─────────────────────────────────────────────── +def test_triage_data_leak_is_sev1(): + out = triage_incident( + title="Possible data exposure", + has_data_leak=True, + ) + assert out["severity"] == "SEV1" + + +def test_triage_unauthorized_send_is_sev1(): + out = triage_incident( + title="Unauthorized message", + has_unauthorized_send=True, + ) + assert out["severity"] == "SEV1" + + +def test_triage_many_customers_is_sev2(): + out = triage_incident( + title="Service outage", + affected_customers=10, + ) + assert out["severity"] == "SEV2" + + +def test_triage_single_customer_is_sev3(): + out = triage_incident(title="Customer X has issue", affected_customers=1) + assert out["severity"] == "SEV3" + + +def test_incident_response_plan_sev1_includes_pdpl(): + out = build_incident_response_plan(severity="SEV1") + text = " ".join(out["plan_ar"]) + assert "PDPL" in text + + +# ── Customer Success ──────────────────────────────────────── +def test_weekly_check_in_arabic(): + out = build_weekly_check_in( + customer_id="c1", company_name="Acme", + metrics={"drafts_approved": 5, "replies": 2, + "meetings": 1, "risks_blocked": 3, "pipeline_sar": 18000}, + ) + assert out["type"] == "weekly_check_in" + assert any("Pipeline" in tp for tp in out["talking_points_ar"]) + + +def test_at_risk_alert_high_severity(): + out = build_at_risk_alert( + customer_id="c1", days_inactive=20, + drafts_pending=15, last_proof_pack_days_ago=21, + ) + assert out["severity"] == "high" + assert out["risk_score"] >= 60 + + +def test_at_risk_alert_low_severity(): + out = build_at_risk_alert( + customer_id="c1", days_inactive=2, + drafts_pending=1, last_proof_pack_days_ago=3, + ) + assert out["severity"] == "low" + + +def test_customer_success_plan_for_growth_starter(): + out = build_customer_success_plan( + customer_id="c1", bundle_id="growth_starter", + ) + assert any("Day 1" in line for line in out["cadence_ar"]) + + +def test_customer_success_plan_for_executive(): + out = build_customer_success_plan( + customer_id="c1", bundle_id="executive_growth_os", + ) + assert any("Founder Shadow Board" in line for line in out["cadence_ar"]) diff --git a/dealix/tests/unit/test_positioning_lock.py b/dealix/tests/unit/test_positioning_lock.py new file mode 100644 index 00000000..498f47c7 --- /dev/null +++ b/dealix/tests/unit/test_positioning_lock.py @@ -0,0 +1,141 @@ +"""Positioning Lock tests — enforce category rules + prohibited claims.""" + +from __future__ import annotations + +import re +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parents[2] + +# Positive claims that must NEVER appear in customer-facing pages. +# (Negative restatements like "no auto-DM" in safety sections are fine — +# we only block positive claims that promise forbidden behavior.) +PROHIBITED_PHRASES = ( + "نضمن لك عملاء", + "نضمن مبيعات", + "نتائج مضمونة 100%", + "ضمان مضمون", + "مليون ريال خلال شهر", + "نسحب كل بيانات LinkedIn", + "نقوم بـ auto-DM", + "نتجاوز PDPL", + "بدون مراجعة بشرية", + "AI-only — لا تدخل بشري", + "بديل HubSpot", + "أرخص من Salesforce", + "نقتل CRM", +) + +# Required claims that should appear in the positioning + market messaging docs. +REQUIRED_CLAIMS_FRAGMENTS_AR = ( + "Approval-first", + "PDPL", + "Saudi Tone", + "Proof Pack", +) + + +def _read(rel_path: str) -> str: + p = REPO_ROOT / rel_path + if not p.exists(): + return "" + return p.read_text(encoding="utf-8", errors="ignore") + + +def test_positioning_lock_exists(): + text = _read("docs/POSITIONING_LOCK.md") + assert text, "POSITIONING_LOCK.md missing" + assert "Saudi Revenue Execution OS" in text + assert "ليس CRM" in text + assert "ليس بوت واتساب" in text + + +def test_prohibited_claims_doc_exists(): + text = _read("docs/PROHIBITED_CLAIMS.md") + assert text, "PROHIBITED_CLAIMS.md missing" + assert "نضمن" in text + assert "scraping" in text.lower() + + +def test_approved_market_messaging_doc_exists(): + text = _read("docs/APPROVED_MARKET_MESSAGING.md") + assert text, "APPROVED_MARKET_MESSAGING.md missing" + for fragment in REQUIRED_CLAIMS_FRAGMENTS_AR: + assert fragment in text, f"missing required fragment: {fragment}" + + +def test_no_prohibited_phrases_in_landing_pages(): + """Customer-facing landing pages must NOT contain prohibited claims.""" + pages = [ + "landing/private-beta.html", + "landing/services.html", + "landing/free-diagnostic.html", + "landing/first-10-opportunities.html", + "landing/agency-partner.html", + "landing/list-intelligence.html", + "landing/growth-os.html", + "landing/companies.html", + ] + failures: list[str] = [] + for page in pages: + text = _read(page) + if not text: + continue # page doesn't exist + for bad in PROHIBITED_PHRASES: + if bad in text: + failures.append(f"{page} contains prohibited phrase: {bad}") + assert not failures, "Prohibited phrases found:\n" + "\n".join(failures) + + +def test_companies_page_has_approved_messaging(): + text = _read("landing/companies.html") + assert text, "landing/companies.html missing" + assert "Approval-first" in text or "approval-first" in text.lower() + # Should reference Proof Pack + assert "Proof Pack" in text + + +def test_marketers_or_agency_page_exists(): + """At least one of the agency-facing pages must exist.""" + a = _read("landing/agency-partner.html") + m = _read("landing/marketers.html") + assert a or m, "Need at least one of agency-partner.html or marketers.html" + + +def test_private_beta_page_no_guarantees(): + text = _read("landing/private-beta.html") + assert text, "private-beta.html missing" + assert "نضمن" not in text or "لا نضمن" in text + assert "guarantee" not in text.lower() or "no guarantee" in text.lower() + + +def test_revenue_today_playbook_emphasizes_approval(): + text = _read("docs/REVENUE_TODAY_PLAYBOOK.md") + assert text, "REVENUE_TODAY_PLAYBOOK.md missing" + assert "Approval-first" in text or "approval" in text.lower() + # Must explicitly state no live charge + assert "live charge" in text.lower() or "API charge" in text or "manual" in text.lower() + + +def test_positioning_lock_has_5_bundles(): + text = _read("docs/POSITIONING_LOCK.md") + for bundle in ( + "Growth Starter", + "Data to Revenue", + "Executive Growth OS", + "Partnership Growth", + "Full Growth Control Tower", + ): + assert bundle in text, f"missing bundle in POSITIONING_LOCK.md: {bundle}" + + +def test_positioning_lock_lists_5_modes(): + text = _read("docs/POSITIONING_LOCK.md") + for mode in ( + "CEO Mode", + "Growth Manager Mode", + "Agency Partner Mode", + "Self-Growth Mode", + "Service Delivery Mode", + ): + assert mode in text, f"missing mode in POSITIONING_LOCK.md: {mode}"