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:
Dealix Builder 2026-05-01 18:14:51 +03:00
parent ef08649efe
commit 47f4dc2fb6
24 changed files with 2803 additions and 1 deletions

View File

@ -23,6 +23,7 @@ from api.routers import (
business,
command_center,
connector_catalog,
customer_ops,
customer_success,
data,
dominance,
@ -178,6 +179,7 @@ def create_app() -> FastAPI:
app.include_router(revenue_launch.router)
app.include_router(autonomous_service_operator.router)
app.include_router(revenue_company_os.router)
app.include_router(customer_ops.router)
app.include_router(public.router)
app.include_router(admin.router)

View 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"),
)

View File

@ -22,6 +22,14 @@ from .client_mode import (
build_client_dashboard,
build_client_session_summary,
)
from .self_growth_mode import (
build_operator_self_growth_brief,
)
from .service_delivery_mode import (
build_post_delivery_handoff,
build_service_delivery_brief,
build_sla_status_for_delivery,
)
from .conversation_router import (
INTENT_TO_HANDLER,
handle_message,
@ -122,4 +130,10 @@ __all__ = [
# agency_mode
"add_agency_client", "build_agency_dashboard",
"build_co_branded_proof_pack", "list_agency_revenue_share",
# self_growth_mode
"build_operator_self_growth_brief",
# service_delivery_mode
"build_post_delivery_handoff",
"build_service_delivery_brief",
"build_sla_status_for_delivery",
]

View File

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

View File

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

View 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: P0P3 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",
]

View File

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

View File

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

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

View File

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

View 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"
),
}

View File

@ -0,0 +1,149 @@
"""Support ticket router — P0P3 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,
}

View 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 بـ4991,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 يفشل → لا يخرج.

View 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
```

View 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
5074 = watch
<50 = at-risk
```
---
## ما لا يحدث في CS
- لا "عرض ترقية" قبل تسليم أول Proof Pack.
- لا spam check-ins (max 1 في الأسبوع).
- لا تخطي الـ at-risk alert إذا تجاوز high.
- لا تعديل cadence بدون موافقة العميل.

View File

@ -342,6 +342,40 @@ OAuth Gmail/Calendar، حصص، سياسات.
**الفرق الشاسع:** Dealix لا يبيع features ولا AI ولا منصة. يبيع **شركة نمو رقمية ذاتية التشغيل** — نتائج منظمة + تشغيل يومي + Proof Pack شهري.
## 45. Positioning Lock + Customer Ops + Companies/Marketers
**8 modules + 20 endpoints + 44 tests + 2 modes + 7 docs**. التفاصيل:
### Positioning Lock (3 docs)
- [`POSITIONING_LOCK.md`](POSITIONING_LOCK.md) — category, one-liner, primary buyers (شركات + وكالات), wedge, 5 approved claims, 5 modes, 5 bundles.
- [`PROHIBITED_CLAIMS.md`](PROHIBITED_CLAIMS.md) — 8 categories of forbidden marketing language + how they're enforced technically.
- [`APPROVED_MARKET_MESSAGING.md`](APPROVED_MARKET_MESSAGING.md) — tagline, elevator pitch, headlines, 4 outreach segments, social posts, slogan bank.
### Customer Ops (6 modules)
- `onboarding_checklist`: 8-step Pilot onboarding with progress tracking.
- `connector_setup_status`: 11 connectors (Gmail/Calendar/Sheets/Moyasar/WhatsApp/Forms/LinkedIn/GBP/CRM/Meet/Instagram) with state machine + ready_for_first_service gate.
- `support_ticket_router`: 4-tier P0/P1/P2/P3 classification + Arabic first-response templates.
- `sla_tracker`: per-priority SLA targets + breach detection + weekly health (healthy/watch/critical).
- `customer_success_cadence`: 6 cadence types + at-risk alerts (risk_score 0..100) + per-bundle 30-day plans + QBR.
- `incident_router`: SEV1/SEV2/SEV3 triage + auto-SEV1 on data leak / unauthorized send + canonical response plans (PDPL-aware).
### Operator Modes (2 new)
- `self_growth_mode` — Dealix uses its own OS to find pilots (re-exports targeting_os.self_growth_mode + operator-tier reminders).
- `service_delivery_mode` — runs client services + tracks SLA + post-delivery handoff to Customer Success.
### Customer-facing Pages (1 new + 1 updated)
- `landing/companies.html` — Saudi B2B companies. Approval-first, no scraping, 4 bundles.
- `landing/marketers.html` (updated) — agencies/marketers Agency Growth OS path.
### Customer Ops Docs (5 new)
- [`ONBOARDING_RUNBOOK.md`](ONBOARDING_RUNBOOK.md) — day-by-day kick-off → first Proof Pack.
- [`SUPPORT_SLA.md`](SUPPORT_SLA.md) — P0..P3 + classifier keywords.
- [`INCIDENT_RESPONSE.md`](INCIDENT_RESPONSE.md) — SEV1..SEV3 + post-mortem templates + Arabic comms.
- [`CUSTOMER_SUCCESS_PLAYBOOK.md`](CUSTOMER_SUCCESS_PLAYBOOK.md) — weekly check-ins, at-risk detection, QBR, renewal.
- [`CONNECTOR_SETUP_GUIDES.md`](CONNECTOR_SETUP_GUIDES.md) — 11 connectors with scopes + steps + troubleshooting.
**Endpoints:** `/api/v1/customer-ops/*` (20).
---
**الخلاصة:** المنتج **قوي كأساس سوقي وتقني**؛ الإطلاق العام يحتاج تشغيلاً وامتثالاً وتجربة عميل مغلقة أولاً. الإطلاق اليوم = Private Beta + Pilots + Proof Pack، ليس Public Launch.
**الخلاصة:** المنتج **قوي كأساس سوقي وتقني**؛ الإطلاق العام يحتاج تشغيلاً وامتثالاً وتجربة عميل مغلقة أولاً. الإطلاق اليوم = Private Beta + Pilots + Proof Pack، ليس Public Launch. اليوم Dealix هو **Saudi Revenue Execution OS** بـ45 طبقة وثائقية، 949 اختبار ناجح، CI أخضر.

View 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 لو مفيدة.

View 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 مفعّل.

View 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.
- ميزانية 49910,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,0007,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 أشهر من الإنتاجية. لذلك هذا الملف ثابت.

View 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، لا وعود تسويقية.

View 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 بدون تأكيد من العميل.

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

View 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"])

View 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}"