mirror of
https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools.git
synced 2026-06-17 23:09:35 +00:00
feat(positioning+customer-ops): Saudi Revenue Execution OS — 8 modules + 20 endpoints + 44 tests + 8 docs + 2 modes
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) <noreply@anthropic.com>
This commit is contained in:
parent
ef08649efe
commit
47f4dc2fb6
@ -23,6 +23,7 @@ from api.routers import (
|
|||||||
business,
|
business,
|
||||||
command_center,
|
command_center,
|
||||||
connector_catalog,
|
connector_catalog,
|
||||||
|
customer_ops,
|
||||||
customer_success,
|
customer_success,
|
||||||
data,
|
data,
|
||||||
dominance,
|
dominance,
|
||||||
@ -178,6 +179,7 @@ def create_app() -> FastAPI:
|
|||||||
app.include_router(revenue_launch.router)
|
app.include_router(revenue_launch.router)
|
||||||
app.include_router(autonomous_service_operator.router)
|
app.include_router(autonomous_service_operator.router)
|
||||||
app.include_router(revenue_company_os.router)
|
app.include_router(revenue_company_os.router)
|
||||||
|
app.include_router(customer_ops.router)
|
||||||
app.include_router(public.router)
|
app.include_router(public.router)
|
||||||
app.include_router(admin.router)
|
app.include_router(admin.router)
|
||||||
|
|
||||||
|
|||||||
208
dealix/api/routers/customer_ops.py
Normal file
208
dealix/api/routers/customer_ops.py
Normal file
@ -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"),
|
||||||
|
)
|
||||||
@ -22,6 +22,14 @@ from .client_mode import (
|
|||||||
build_client_dashboard,
|
build_client_dashboard,
|
||||||
build_client_session_summary,
|
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 (
|
from .conversation_router import (
|
||||||
INTENT_TO_HANDLER,
|
INTENT_TO_HANDLER,
|
||||||
handle_message,
|
handle_message,
|
||||||
@ -122,4 +130,10 @@ __all__ = [
|
|||||||
# agency_mode
|
# agency_mode
|
||||||
"add_agency_client", "build_agency_dashboard",
|
"add_agency_client", "build_agency_dashboard",
|
||||||
"build_co_branded_proof_pack", "list_agency_revenue_share",
|
"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",
|
||||||
]
|
]
|
||||||
|
|||||||
@ -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",
|
||||||
|
]
|
||||||
@ -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,
|
||||||
|
}
|
||||||
78
dealix/auto_client_acquisition/customer_ops/__init__.py
Normal file
78
dealix/auto_client_acquisition/customer_ops/__init__.py
Normal file
@ -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",
|
||||||
|
]
|
||||||
@ -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
|
||||||
|
),
|
||||||
|
}
|
||||||
@ -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,
|
||||||
|
}
|
||||||
104
dealix/auto_client_acquisition/customer_ops/incident_router.py
Normal file
104
dealix/auto_client_acquisition/customer_ops/incident_router.py
Normal file
@ -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,
|
||||||
|
}
|
||||||
@ -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
|
||||||
132
dealix/auto_client_acquisition/customer_ops/sla_tracker.py
Normal file
132
dealix/auto_client_acquisition/customer_ops/sla_tracker.py
Normal file
@ -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"
|
||||||
|
),
|
||||||
|
}
|
||||||
@ -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,
|
||||||
|
}
|
||||||
119
dealix/docs/APPROVED_MARKET_MESSAGING.md
Normal file
119
dealix/docs/APPROVED_MARKET_MESSAGING.md
Normal file
@ -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 يفشل → لا يخرج.
|
||||||
204
dealix/docs/CONNECTOR_SETUP_GUIDES.md
Normal file
204
dealix/docs/CONNECTOR_SETUP_GUIDES.md
Normal file
@ -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
|
||||||
|
```
|
||||||
141
dealix/docs/CUSTOMER_SUCCESS_PLAYBOOK.md
Normal file
141
dealix/docs/CUSTOMER_SUCCESS_PLAYBOOK.md
Normal file
@ -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 بدون موافقة العميل.
|
||||||
@ -342,6 +342,40 @@ OAuth Gmail/Calendar، حصص، سياسات.
|
|||||||
|
|
||||||
**الفرق الشاسع:** Dealix لا يبيع features ولا AI ولا منصة. يبيع **شركة نمو رقمية ذاتية التشغيل** — نتائج منظمة + تشغيل يومي + Proof Pack شهري.
|
**الفرق الشاسع:** 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 أخضر.
|
||||||
|
|||||||
111
dealix/docs/INCIDENT_RESPONSE.md
Normal file
111
dealix/docs/INCIDENT_RESPONSE.md
Normal file
@ -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 لو مفيدة.
|
||||||
120
dealix/docs/ONBOARDING_RUNBOOK.md
Normal file
120
dealix/docs/ONBOARDING_RUNBOOK.md
Normal file
@ -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 مفعّل.
|
||||||
123
dealix/docs/POSITIONING_LOCK.md
Normal file
123
dealix/docs/POSITIONING_LOCK.md
Normal file
@ -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 أشهر من الإنتاجية. لذلك هذا الملف ثابت.
|
||||||
107
dealix/docs/PROHIBITED_CLAIMS.md
Normal file
107
dealix/docs/PROHIBITED_CLAIMS.md
Normal file
@ -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، لا وعود تسويقية.
|
||||||
95
dealix/docs/SUPPORT_SLA.md
Normal file
95
dealix/docs/SUPPORT_SLA.md
Normal file
@ -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 بدون تأكيد من العميل.
|
||||||
125
dealix/landing/companies.html
Normal file
125
dealix/landing/companies.html
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ar" dir="rtl">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Dealix للشركات — Saudi Revenue Execution OS</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: 'Tajawal', sans-serif; background: #0f1115; color: #f5f5f7;
|
||||||
|
line-height: 1.7; margin: 0; }
|
||||||
|
header { padding: 56px 24px; text-align: center;
|
||||||
|
background: linear-gradient(135deg, #1a1d27 0%, #0f1115 100%); }
|
||||||
|
header h1 { font-size: 44px; line-height: 1.2; margin-bottom: 14px; }
|
||||||
|
header h1 span { color: #ffd166; }
|
||||||
|
header p { color: #a8aab2; max-width: 720px; margin: 0 auto; font-size: 19px; }
|
||||||
|
main { max-width: 920px; margin: 0 auto; padding: 24px; }
|
||||||
|
section { background: #181b22; padding: 28px; border-radius: 14px;
|
||||||
|
margin: 18px 0; border: 1px solid #232631; }
|
||||||
|
section h2 { color: #ffd166; margin-bottom: 14px; font-size: 22px; }
|
||||||
|
ul li { padding: 6px 0; }
|
||||||
|
ul li:before { content: "▸"; color: #06d6a0; margin-left: 8px; }
|
||||||
|
.cta { display: inline-block; padding: 14px 28px; background: #ffd166;
|
||||||
|
color: #0f1115; border-radius: 10px; font-weight: 700; text-decoration: none;
|
||||||
|
font-size: 17px; margin: 8px; }
|
||||||
|
.pricing { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||||
|
gap: 14px; }
|
||||||
|
.price-card { padding: 18px; background: #1f232c; border-radius: 10px;
|
||||||
|
border: 1px solid #2a2e39; }
|
||||||
|
.price-card .label { color: #a8aab2; font-size: 14px; }
|
||||||
|
.price-card .price { font-size: 24px; font-weight: 700; color: #ffd166; margin: 6px 0; }
|
||||||
|
.note { background: rgba(6, 214, 160, 0.1); padding: 14px; border-radius: 10px;
|
||||||
|
border: 1px solid rgba(6, 214, 160, 0.3); color: #06d6a0; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<h1>Dealix <span>للشركات</span></h1>
|
||||||
|
<p>حوّل بياناتك وقنواتك إلى فرص ورسائل واجتماعات وProof Pack —
|
||||||
|
Approval-first في كل خطوة، بدون scraping ولا cold WhatsApp.</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<section>
|
||||||
|
<h2>المشكلة</h2>
|
||||||
|
<ul>
|
||||||
|
<li>عندك إيميل وقائمة عملاء قدامى وقنوات نشطة، لكن لا تعرف وش أهم شيء اليوم.</li>
|
||||||
|
<li>الفريق يقضي وقت كبير على الـ outreach بدون نتائج محسوبة.</li>
|
||||||
|
<li>تخاف من حظر القناة لو أرسلت بدون عناية.</li>
|
||||||
|
<li>لا يوجد Proof واضح للإدارة عن العائد.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>كيف يعمل Dealix</h2>
|
||||||
|
<ul>
|
||||||
|
<li>اختر هدفك: عملاء جدد / استخدام قائمة / شراكات / تشغيل يومي.</li>
|
||||||
|
<li>Dealix يوصي بالخدمة + يجمع intake.</li>
|
||||||
|
<li>يطلع لك 10 فرص B2B + رسائل عربية + خطة متابعة.</li>
|
||||||
|
<li>كل رسالة تنتظر اعتمادك قبل الإرسال.</li>
|
||||||
|
<li>Proof Pack أسبوعي + Founder Shadow Board شهري.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>الباقات</h2>
|
||||||
|
<div class="pricing">
|
||||||
|
<div class="price-card">
|
||||||
|
<div class="label">Growth Starter</div>
|
||||||
|
<div class="price">499 ريال</div>
|
||||||
|
<div>Free Diagnostic + First 10 Opportunities + Proof Pack مختصر</div>
|
||||||
|
</div>
|
||||||
|
<div class="price-card">
|
||||||
|
<div class="label">Data to Revenue</div>
|
||||||
|
<div class="price">1,500 ريال</div>
|
||||||
|
<div>List Intelligence + Top 50 Targets + رسائل + Risk Report</div>
|
||||||
|
</div>
|
||||||
|
<div class="price-card">
|
||||||
|
<div class="label">Executive Growth OS</div>
|
||||||
|
<div class="price">2,999 ريال شهرياً</div>
|
||||||
|
<div>Daily Brief + Approvals + Proof Pack أسبوعي</div>
|
||||||
|
</div>
|
||||||
|
<div class="price-card">
|
||||||
|
<div class="label">Full Growth Control Tower</div>
|
||||||
|
<div class="price">Custom</div>
|
||||||
|
<div>30 يوم — كل الخدمات على مراحل</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="note">
|
||||||
|
<strong>ضمانات Dealix:</strong>
|
||||||
|
Approval-first في كل قناة. لا scraping. لا cold WhatsApp.
|
||||||
|
لا charge بدون موافقة. لا وعود مضمونة. Proof Pack بالأرقام.
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Proof Pack</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Opportunities created.</li>
|
||||||
|
<li>Drafts created + approved.</li>
|
||||||
|
<li>Replies received + meetings drafted.</li>
|
||||||
|
<li>Pipeline influenced (SAR).</li>
|
||||||
|
<li>Risks blocked (مخاطر منعت تلقائياً).</li>
|
||||||
|
<li>Time saved (ساعات).</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>الأمان والامتثال</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Approval-first في كل قناة — لا live send بدون اعتمادك.</li>
|
||||||
|
<li>PDPL-aware: لا cold WhatsApp، DPA draft جاهز.</li>
|
||||||
|
<li>Secret redactor + Patch firewall + Trace redactor.</li>
|
||||||
|
<li>Saudi Tone + Safety eval قبل كل رسالة.</li>
|
||||||
|
<li>Action Ledger يسجّل كل فعل + من اعتمده.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div style="text-align: center; padding: 24px;">
|
||||||
|
<a href="free-diagnostic.html" class="cta">ابدأ بالتشخيص المجاني</a>
|
||||||
|
<a href="mailto:bassam.m.assiri@gmail.com?subject=Dealix%20Companies%20Pilot"
|
||||||
|
class="cta" style="background: transparent; color: #f5f5f7; border: 1px solid #383b44;">احجز ديمو 12 دقيقة</a>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
268
dealix/tests/unit/test_customer_ops.py
Normal file
268
dealix/tests/unit/test_customer_ops.py
Normal file
@ -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"])
|
||||||
141
dealix/tests/unit/test_positioning_lock.py
Normal file
141
dealix/tests/unit/test_positioning_lock.py
Normal file
@ -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}"
|
||||||
Loading…
Reference in New Issue
Block a user