mirror of
https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools.git
synced 2026-06-17 14:59:35 +00:00
feat(autonomous-revenue-os): Dealix becomes a Category — Autonomous Revenue Company OS — 26 modules + 47 endpoints + 81 tests
# Dealix is no longer "a platform". It is a new category: # An Autonomous Revenue Company OS that runs growth FOR Saudi businesses # as if Growth + Sales + Partnerships + Customer Success + Strategy + # Compliance + Data sat in one self-improving system. Autonomous Service Operator (16 modules) — البوت المركزي - intent_classifier: 16 supported intents (Arabic + English keywords; deterministic; no LLM) - conversation_router: route_message + handle_message — single entry point that classifies, routes to handler, recommends a bundle, builds intake + initial pipeline - session_state: 13 valid states + UUID-based sessions + audit history - intake_collector: per-intent intake question sets + parse + validation - approval_manager: Arabic approval cards (capped at 3 buttons) + decision processing (approve/edit/skip/reject including Arabic verbs) - service_orchestrator: 11-step canonical pipeline (intake→data_check→targeting→contactability→strategy→drafting→approval→execution_or_export→tracking→proof→upsell) - workflow_runner: advance + completion check - tool_action_planner: HARD-BLOCKS linkedin.scrape_profile, linkedin.auto_dm, linkedin.auto_connect, social.scrape_followers; high-risk tools require approval; draft-safe tools return draft_only; unknown tools default to approval_required - proof_pack_dispatcher: per-service Proof Pack envelope with required metrics - upsell_engine: 3 deterministic verdicts (upsell_now / iterate_first / gentle_upsell) based on csat + pipeline + meetings - whatsapp_renderer: render any card / approval / daily brief as WhatsApp draft (≤3 buttons, Arabic body, never live) - operator_memory: in-process sessions + customer_facts + preferences + audit log (production = Supabase) - service_bundles: 6 customer-facing bundles instead of 20 raw services (Growth Starter, Data to Revenue, Executive Growth OS, Partnership Growth, Local Growth OS, Full Growth Control Tower) - executive_mode: CEO command center + daily brief + revenue risks (3) + next 3 moves - client_mode: Growth Manager dashboard with 4 panels - agency_mode: multi-client roster + co-branded Proof Pack + revenue share calc Revenue Company OS (10 modules) — الذكاء عبر القنوات - event_to_card: 13 event types → Arabic decision cards (email/whatsapp/form/review/payment/risk/partner/meeting/service.completed/...) each with title_ar/summary_ar/why_now_ar/recommended_action_ar/risk_level/buttons_ar (≤3) - command_feed_engine: aggregate events for a customer + sort by risk (high first) + by_type and by_risk counts - action_graph: 14 typed edges (signal_created_opportunity → message_triggered_reply → reply_led_to_meeting → meeting_led_to_proposal → proposal_led_to_payment → ...) with what_works_for_customer scoring (outcome edges weigh more) - revenue_work_units: 19 RWU types (Salesforce-inspired): opportunity_created, draft_created, approval_collected, meeting_drafted, payment_received, risk_blocked, etc. + aggregate_work_units (counts/revenue/risks) - channel_health: cross-channel reputation snapshot (email/whatsapp/linkedin) + overall_score + channels_at_risk - opportunity_factory: turn (sector, city) into 5 opportunity cards via targeting_os.recommend_accounts + buying committee - service_factory: instantiate any service for a customer (intake + workflow + quote) - proof_ledger (revenue-tier, NOT platform_services.proof_ledger): customer-facing scoreboard with totals + summary_ar + by_type breakdown - growth_memory: anonymized cross-customer aggregates — sector_message_winrate, sector_channel_winrate, common_objections, blocked_action_reasons, successful_playbooks; best_message_for_sector + best_channel_for_sector - self_improvement_loop: weekly Arabic recommendations from real metrics (approval_rate, reply_rate, meeting_rate, blocked_actions, service_revenue) + best_service_id + next_experiment Routers (2 new) — 47 endpoints - /api/v1/operator/* (28): chat (message/decision/classify), sessions (new/transition/context/get), cards (approval/whatsapp/render), intake (questions/validate), service (start), tools (plan), proof-pack (dispatch), upsell (recommend/card), bundles (list/recommend), modes (ceo/ceo-daily-brief/ceo-risks/client/agency/agency-add-client/agency-revenue-share/agency-co-branded-proof), demos (whatsapp-daily-brief/proof-pack) - /api/v1/revenue-os/* (19): command-feed (demo/build/events-ingest), work-units (types/build/aggregate/demo), proof-ledger/demo, action-graph (edge-types/demo), channel-health (snapshot/demo), opportunity-factory (run/demo), service-factory (instantiate/demo), growth-memory/demo, self-improvement (weekly-report/demo) Tests (2 new files, 81 tests) - test_autonomous_service_operator.py: 50 tests * 8 intent classification tests (want_more_customers, has_contact_list, partnerships, whatsapp, pricing, approve, unknown fallback) * 4 conversation router (recommends correct service per intent + bundle, processes approval decisions) * 4 session lifecycle (UUID, transition validation, memory store, context build) * 4 intake (questions per intent, validation detects missing fields, complete intake passes) * 4 approval (≤3 buttons, approve/skip Arabic, unknown decision returns error) * 5 tool planner (linkedin scrape blocked, auto_dm blocked, high-risk → approval, draft-safe → draft_only, unknown → approval_required) * 4 bundles (6 total, agency → partnership_growth, local → local_growth_os, default → growth_starter) * 7 modes (CEO Arabic, daily brief 3 decisions, 3 risks, client panels, agency aggregation, revenue share calc, co-branded includes both names) * 3 WhatsApp renderer (no live send, ≤3 buttons, Arabic morning text) * 4 proof + upsell (Proof Pack draft, upsell_now for strong, iterate_first for weak, ≤3 buttons) - test_revenue_company_os.py: 31 tests * 4 event → card (email Arabic, low review high-risk, risk.blocked high, unknown → action_required) * 3 command feed (demo 8 events, sorts high-risk first, empty handling) * 4 RWUs (≥18 types, build validates, aggregate sums revenue, risks_blocked counted) * 4 action graph (≥12 edge types, validates type, demo 2 customers, what_works scoring) * 2 channel health (returns score, flags risky channel) * 2 opportunity factory (5 opps no live send, blocks unsafe in notes) * 3 service factory (instantiate known + unknown errors, demo 4 services) * 3 proof ledger (appends, rejects unknown, demo has revenue + risks) * 2 growth memory (top objections, best message per sector) * 3 self-improvement (low approval recommends fix, high blocked recommends review, returns best service) Docs (1 new + 1 updated) - AUTONOMOUS_REVENUE_COMPANY_OS.md (Arabic): 12-layer architecture + service bundles + safety + endpoints + competitive positioning - DEALIX_100_PERCENT_LAUNCH_PLAN.md: added §44 Autonomous Revenue Company OS Test results - 81/81 new tests pass - Full suite: 905 passed, 2 skipped (missing API keys, unrelated) - 0 existing tests broken Safety + integration - All 47 new endpoints: live_send_allowed=False, approval_required=True - LinkedIn scrape/auto-DM/auto-connect HARD-BLOCKED in tool_action_planner - High-risk tools (whatsapp.send_message, gmail.send, calendar.insert_event, moyasar.charge, gbp.publish_review_reply, social.publish_dm, social.publish_post) → approval_required forced - Cold WhatsApp blocked via existing contactability_matrix - Operator memory hashes nothing yet — production must wire to security_curator.trace_redactor before any persistence - 6 bundles unify the 12 productized services from Service Tower - Modes integrate platform_services + intelligence_layer + service_excellence - Action Graph + Revenue Work Units + Proof Ledger together form Dealix's Saudi Revenue Graph - Self-improvement loop reads metrics that flow from agent_observability + growth_curator Integration with everything before - Autonomous Service Operator orchestrates Service Tower, Service Excellence OS, Targeting OS, Platform Services, Intelligence Layer - Revenue Company OS reads from platform_services event_bus + intelligence_layer mission_engine + targeting_os reputation_guard - Service factory uses service_tower.get_service + build_intake_questions + quote_service - Opportunity factory uses targeting_os.recommend_accounts + map_buying_committee - Channel health uses targeting_os.calculate_channel_reputation - Tool planner integrates with platform_services.tool_gateway policies - WhatsApp renderer aligns with launch_ops button caps - Bundles map to service_tower upgrade_paths Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
84f1ad9620
commit
ef08649efe
@ -17,6 +17,7 @@ from api.routers import (
|
||||
admin,
|
||||
agent_observability,
|
||||
agents,
|
||||
autonomous_service_operator,
|
||||
automation,
|
||||
autonomous,
|
||||
business,
|
||||
@ -45,6 +46,7 @@ from api.routers import (
|
||||
prospect,
|
||||
public,
|
||||
revenue,
|
||||
revenue_company_os,
|
||||
revenue_launch,
|
||||
revenue_os,
|
||||
sales,
|
||||
@ -174,6 +176,8 @@ def create_app() -> FastAPI:
|
||||
app.include_router(service_excellence.router)
|
||||
app.include_router(launch_ops.router)
|
||||
app.include_router(revenue_launch.router)
|
||||
app.include_router(autonomous_service_operator.router)
|
||||
app.include_router(revenue_company_os.router)
|
||||
app.include_router(public.router)
|
||||
app.include_router(admin.router)
|
||||
|
||||
|
||||
304
dealix/api/routers/autonomous_service_operator.py
Normal file
304
dealix/api/routers/autonomous_service_operator.py
Normal file
@ -0,0 +1,304 @@
|
||||
"""Autonomous Service Operator router — chat + decisions + sessions + bundles."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, Body, HTTPException
|
||||
|
||||
from auto_client_acquisition.autonomous_service_operator import (
|
||||
OperatorMemory,
|
||||
add_agency_client,
|
||||
build_agency_dashboard,
|
||||
build_approval_card,
|
||||
build_ceo_command_center,
|
||||
build_client_dashboard,
|
||||
build_co_branded_proof_pack,
|
||||
build_executive_daily_brief,
|
||||
build_intake_questions_for_intent,
|
||||
build_new_session,
|
||||
build_revenue_risks_summary,
|
||||
build_service_pipeline,
|
||||
build_session_context,
|
||||
build_upsell_card,
|
||||
classify_intent,
|
||||
dispatch_proof_pack,
|
||||
handle_message,
|
||||
list_bundles,
|
||||
list_agency_revenue_share,
|
||||
plan_tool_action,
|
||||
process_approval_decision,
|
||||
recommend_bundle,
|
||||
recommend_upsell_after_service,
|
||||
render_approval_card_for_whatsapp,
|
||||
render_card_for_whatsapp,
|
||||
render_daily_brief_for_whatsapp,
|
||||
transition_session,
|
||||
validate_intake_completeness,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/api/v1/operator", tags=["autonomous-service-operator"])
|
||||
|
||||
# Process-level memory (demo). Production = Redis/Supabase.
|
||||
_MEMORY = OperatorMemory()
|
||||
|
||||
|
||||
# ── Chat ─────────────────────────────────────────────────────
|
||||
@router.post("/chat/message")
|
||||
async def chat_message(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
|
||||
"""Send a message to the operator. Classifies intent + recommends action."""
|
||||
return handle_message(
|
||||
message=payload.get("message", ""),
|
||||
customer_id=payload.get("customer_id"),
|
||||
has_contact_list=bool(payload.get("has_contact_list", False)),
|
||||
is_agency=bool(payload.get("is_agency", False)),
|
||||
is_local_business=bool(payload.get("is_local_business", False)),
|
||||
budget_sar=int(payload.get("budget_sar", 1000)),
|
||||
)
|
||||
|
||||
|
||||
@router.post("/chat/decision")
|
||||
async def chat_decision(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
|
||||
"""Process an approval/edit/skip decision on an action card."""
|
||||
card = payload.get("card") or build_approval_card(
|
||||
action_type="example",
|
||||
title_ar="فعل مثال",
|
||||
summary_ar="مثال",
|
||||
)
|
||||
return process_approval_decision(
|
||||
card,
|
||||
decision=payload.get("decision", "skip"),
|
||||
decided_by=payload.get("decided_by", "user"),
|
||||
note=payload.get("note", ""),
|
||||
)
|
||||
|
||||
|
||||
@router.post("/chat/classify")
|
||||
async def chat_classify(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
|
||||
return classify_intent(payload.get("message", ""))
|
||||
|
||||
|
||||
# ── Sessions ─────────────────────────────────────────────────
|
||||
@router.post("/sessions/new")
|
||||
async def sessions_new(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]:
|
||||
session = build_new_session(customer_id=payload.get("customer_id"))
|
||||
_MEMORY.upsert_session(session)
|
||||
return session.to_dict()
|
||||
|
||||
|
||||
@router.get("/sessions/{session_id}")
|
||||
async def sessions_get(session_id: str) -> dict[str, Any]:
|
||||
session = _MEMORY.get_session(session_id)
|
||||
if session is None:
|
||||
raise HTTPException(status_code=404, detail="session not found")
|
||||
return session.to_dict()
|
||||
|
||||
|
||||
@router.post("/sessions/{session_id}/transition")
|
||||
async def sessions_transition(
|
||||
session_id: str,
|
||||
payload: dict[str, Any] = Body(...),
|
||||
) -> dict[str, Any]:
|
||||
session = _MEMORY.get_session(session_id)
|
||||
if session is None:
|
||||
raise HTTPException(status_code=404, detail="session not found")
|
||||
transition_session(
|
||||
session,
|
||||
new_state=payload.get("new_state", "new"),
|
||||
note=payload.get("note", ""),
|
||||
)
|
||||
return session.to_dict()
|
||||
|
||||
|
||||
@router.get("/sessions/{session_id}/context")
|
||||
async def sessions_context(session_id: str) -> dict[str, Any]:
|
||||
return build_session_context(memory=_MEMORY, session_id=session_id)
|
||||
|
||||
|
||||
# ── Cards / Approvals ────────────────────────────────────────
|
||||
@router.post("/cards/approval")
|
||||
async def cards_approval(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
|
||||
return build_approval_card(
|
||||
action_type=payload.get("action_type", "unknown"),
|
||||
title_ar=payload.get("title_ar", ""),
|
||||
summary_ar=payload.get("summary_ar", ""),
|
||||
risk_level=payload.get("risk_level", "low"),
|
||||
why_now_ar=payload.get("why_now_ar", ""),
|
||||
recommended_action_ar=payload.get("recommended_action_ar", ""),
|
||||
expected_impact_sar=float(payload.get("expected_impact_sar", 0)),
|
||||
service_id=payload.get("service_id"),
|
||||
customer_id=payload.get("customer_id"),
|
||||
action_id=payload.get("action_id"),
|
||||
)
|
||||
|
||||
|
||||
@router.post("/cards/whatsapp/render")
|
||||
async def cards_whatsapp_render(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
|
||||
kind = payload.get("kind", "card")
|
||||
if kind == "approval":
|
||||
return render_approval_card_for_whatsapp(payload.get("card") or {})
|
||||
if kind == "daily_brief":
|
||||
return render_daily_brief_for_whatsapp(payload.get("brief") or {})
|
||||
return render_card_for_whatsapp(payload.get("card") or {})
|
||||
|
||||
|
||||
# ── Intake ───────────────────────────────────────────────────
|
||||
@router.get("/intake/questions/{intent}")
|
||||
async def intake_questions(intent: str) -> dict[str, Any]:
|
||||
return build_intake_questions_for_intent(intent)
|
||||
|
||||
|
||||
@router.post("/intake/validate")
|
||||
async def intake_validate(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
|
||||
return validate_intake_completeness(
|
||||
payload.get("intent", "ask_services"),
|
||||
payload.get("payload") or {},
|
||||
)
|
||||
|
||||
|
||||
# ── Service workflow ─────────────────────────────────────────
|
||||
@router.post("/service/start")
|
||||
async def service_start(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
|
||||
return build_service_pipeline(
|
||||
service_id=payload.get("service_id", ""),
|
||||
customer_id=payload.get("customer_id", ""),
|
||||
)
|
||||
|
||||
|
||||
@router.post("/tools/plan")
|
||||
async def tools_plan(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
|
||||
return plan_tool_action(
|
||||
tool=payload.get("tool", ""),
|
||||
payload=payload.get("payload"),
|
||||
customer_id=payload.get("customer_id"),
|
||||
context=payload.get("context"),
|
||||
)
|
||||
|
||||
|
||||
# ── Proof + Upsell ───────────────────────────────────────────
|
||||
@router.post("/proof-pack/dispatch")
|
||||
async def proof_pack_dispatch(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
|
||||
return dispatch_proof_pack(
|
||||
service_id=payload.get("service_id", ""),
|
||||
customer_id=payload.get("customer_id"),
|
||||
channel=payload.get("channel", "email"),
|
||||
metrics=payload.get("metrics"),
|
||||
)
|
||||
|
||||
|
||||
@router.post("/upsell/recommend")
|
||||
async def upsell_recommend(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
|
||||
return recommend_upsell_after_service(
|
||||
completed_service_id=payload.get("completed_service_id", ""),
|
||||
pilot_metrics=payload.get("pilot_metrics"),
|
||||
)
|
||||
|
||||
|
||||
@router.post("/upsell/card")
|
||||
async def upsell_card(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
|
||||
return build_upsell_card(
|
||||
completed_service_id=payload.get("completed_service_id", ""),
|
||||
pilot_metrics=payload.get("pilot_metrics"),
|
||||
)
|
||||
|
||||
|
||||
# ── Bundles ──────────────────────────────────────────────────
|
||||
@router.get("/bundles")
|
||||
async def bundles() -> dict[str, Any]:
|
||||
return list_bundles()
|
||||
|
||||
|
||||
@router.post("/bundles/recommend")
|
||||
async def bundles_recommend(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
|
||||
return recommend_bundle(
|
||||
intent=payload.get("intent"),
|
||||
has_contact_list=bool(payload.get("has_contact_list", False)),
|
||||
is_agency=bool(payload.get("is_agency", False)),
|
||||
is_local_business=bool(payload.get("is_local_business", False)),
|
||||
budget_sar=int(payload.get("budget_sar", 1000)),
|
||||
)
|
||||
|
||||
|
||||
# ── Modes ────────────────────────────────────────────────────
|
||||
@router.post("/mode/ceo")
|
||||
async def mode_ceo(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]:
|
||||
return build_ceo_command_center(
|
||||
company_name=payload.get("company_name", ""),
|
||||
sector=payload.get("sector", "saas"),
|
||||
)
|
||||
|
||||
|
||||
@router.post("/mode/ceo/daily-brief")
|
||||
async def mode_ceo_daily(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]:
|
||||
return build_executive_daily_brief(
|
||||
company_name=payload.get("company_name", ""),
|
||||
sector=payload.get("sector", "saas"),
|
||||
)
|
||||
|
||||
|
||||
@router.post("/mode/ceo/risks")
|
||||
async def mode_ceo_risks() -> dict[str, Any]:
|
||||
return build_revenue_risks_summary()
|
||||
|
||||
|
||||
@router.post("/mode/client")
|
||||
async def mode_client(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]:
|
||||
return build_client_dashboard(
|
||||
customer_id=payload.get("customer_id", ""),
|
||||
company_name=payload.get("company_name", ""),
|
||||
active_services=payload.get("active_services") or [],
|
||||
open_actions=int(payload.get("open_actions", 0)),
|
||||
proof_pack_due=bool(payload.get("proof_pack_due", False)),
|
||||
)
|
||||
|
||||
|
||||
@router.post("/mode/agency")
|
||||
async def mode_agency(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]:
|
||||
return build_agency_dashboard(
|
||||
agency_id=payload.get("agency_id", "agency_demo"),
|
||||
agency_name=payload.get("agency_name", ""),
|
||||
clients=payload.get("clients") or [],
|
||||
)
|
||||
|
||||
|
||||
@router.post("/mode/agency/add-client")
|
||||
async def mode_agency_add_client(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
|
||||
return add_agency_client(
|
||||
agency_id=payload.get("agency_id", "agency_demo"),
|
||||
client_company_name=payload.get("client_company_name", ""),
|
||||
sector=payload.get("sector", ""),
|
||||
monthly_subscription_sar=int(payload.get("monthly_subscription_sar", 0)),
|
||||
revenue_share_pct=int(payload.get("revenue_share_pct", 20)),
|
||||
)
|
||||
|
||||
|
||||
@router.post("/mode/agency/revenue-share")
|
||||
async def mode_agency_revenue_share(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]:
|
||||
return list_agency_revenue_share(clients=payload.get("clients") or [])
|
||||
|
||||
|
||||
@router.post("/mode/agency/co-branded-proof")
|
||||
async def mode_agency_co_branded_proof(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
|
||||
return build_co_branded_proof_pack(
|
||||
agency_name=payload.get("agency_name", ""),
|
||||
client_company_name=payload.get("client_company_name", ""),
|
||||
metrics=payload.get("metrics"),
|
||||
)
|
||||
|
||||
|
||||
# ── Demos ────────────────────────────────────────────────────
|
||||
@router.get("/whatsapp/daily-brief/demo")
|
||||
async def whatsapp_daily_brief_demo() -> dict[str, Any]:
|
||||
brief = build_executive_daily_brief(company_name="Acme")
|
||||
return render_daily_brief_for_whatsapp(brief)
|
||||
|
||||
|
||||
@router.get("/proof-pack/demo")
|
||||
async def proof_pack_demo() -> dict[str, Any]:
|
||||
return dispatch_proof_pack(
|
||||
service_id="first_10_opportunities_sprint",
|
||||
customer_id="demo",
|
||||
metrics={"opportunities_generated": 10, "drafts_approved": 6,
|
||||
"meetings_drafted": 2, "pipeline_influenced_sar": 30000,
|
||||
"risks_blocked": 3},
|
||||
)
|
||||
172
dealix/api/routers/revenue_company_os.py
Normal file
172
dealix/api/routers/revenue_company_os.py
Normal file
@ -0,0 +1,172 @@
|
||||
"""Revenue Company OS router — command feed + work units + proof + memory."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, Body
|
||||
|
||||
from auto_client_acquisition.revenue_company_os import (
|
||||
REVENUE_EDGE_TYPES,
|
||||
REVENUE_WORK_UNIT_TYPES,
|
||||
aggregate_work_units,
|
||||
build_card_from_event,
|
||||
build_channel_health_snapshot,
|
||||
build_command_feed_for_customer,
|
||||
build_growth_memory_demo,
|
||||
build_opportunity_factory_demo,
|
||||
build_revenue_action_graph_demo,
|
||||
build_revenue_proof_ledger_demo,
|
||||
build_revenue_work_unit,
|
||||
build_service_factory_demo,
|
||||
build_weekly_self_improvement_report,
|
||||
instantiate_service,
|
||||
revenue_os_command_feed_demo,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/api/v1/revenue-os", tags=["revenue-company-os"])
|
||||
|
||||
|
||||
# ── Command Feed ─────────────────────────────────────────────
|
||||
@router.get("/command-feed/demo")
|
||||
async def command_feed_demo() -> dict[str, Any]:
|
||||
return revenue_os_command_feed_demo()
|
||||
|
||||
|
||||
@router.post("/events/ingest")
|
||||
async def events_ingest(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
|
||||
"""Convert one event → Arabic decision card. Never executes anything."""
|
||||
return build_card_from_event(payload)
|
||||
|
||||
|
||||
@router.post("/command-feed/build")
|
||||
async def command_feed_build(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
|
||||
return build_command_feed_for_customer(
|
||||
customer_id=payload.get("customer_id", "demo"),
|
||||
events=payload.get("events", []),
|
||||
)
|
||||
|
||||
|
||||
# ── Work Units ───────────────────────────────────────────────
|
||||
@router.get("/work-units/types")
|
||||
async def work_unit_types() -> dict[str, Any]:
|
||||
return {"types": list(REVENUE_WORK_UNIT_TYPES)}
|
||||
|
||||
|
||||
@router.post("/work-units/build")
|
||||
async def work_units_build(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
|
||||
try:
|
||||
return build_revenue_work_unit(
|
||||
unit_type=payload.get("unit_type", ""),
|
||||
service_id=payload.get("service_id", ""),
|
||||
customer_id=payload.get("customer_id", ""),
|
||||
risk_level=payload.get("risk_level", "low"),
|
||||
revenue_influenced_sar=float(payload.get("revenue_influenced_sar", 0)),
|
||||
proof_event=payload.get("proof_event", ""),
|
||||
notes=payload.get("notes", ""),
|
||||
)
|
||||
except ValueError as exc:
|
||||
return {"error": str(exc)}
|
||||
|
||||
|
||||
@router.post("/work-units/aggregate")
|
||||
async def work_units_aggregate(
|
||||
units: list[dict[str, Any]] = Body(default_factory=list, embed=True),
|
||||
) -> dict[str, Any]:
|
||||
return aggregate_work_units(units)
|
||||
|
||||
|
||||
@router.get("/work-units/demo")
|
||||
async def work_units_demo() -> dict[str, Any]:
|
||||
"""Demo aggregation across 12 sample units."""
|
||||
return build_revenue_proof_ledger_demo()
|
||||
|
||||
|
||||
# ── Proof Ledger ─────────────────────────────────────────────
|
||||
@router.get("/proof-ledger/demo")
|
||||
async def proof_ledger_demo() -> dict[str, Any]:
|
||||
return build_revenue_proof_ledger_demo()
|
||||
|
||||
|
||||
# ── Action Graph ─────────────────────────────────────────────
|
||||
@router.get("/action-graph/edge-types")
|
||||
async def action_graph_edge_types() -> dict[str, Any]:
|
||||
return {"edge_types": list(REVENUE_EDGE_TYPES)}
|
||||
|
||||
|
||||
@router.get("/action-graph/demo")
|
||||
async def action_graph_demo() -> dict[str, Any]:
|
||||
return build_revenue_action_graph_demo()
|
||||
|
||||
|
||||
# ── Channel Health ───────────────────────────────────────────
|
||||
@router.post("/channel-health/snapshot")
|
||||
async def channel_health_snapshot(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]:
|
||||
return build_channel_health_snapshot(
|
||||
metrics_per_channel=payload.get("metrics_per_channel"),
|
||||
)
|
||||
|
||||
|
||||
@router.get("/channel-health/demo")
|
||||
async def channel_health_demo() -> dict[str, Any]:
|
||||
return build_channel_health_snapshot()
|
||||
|
||||
|
||||
# ── Opportunity Factory ──────────────────────────────────────
|
||||
@router.post("/opportunity-factory")
|
||||
async def opportunity_factory(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]:
|
||||
return build_opportunity_factory_demo(
|
||||
sector=payload.get("sector", "training"),
|
||||
city=payload.get("city", "Riyadh"),
|
||||
limit=int(payload.get("limit", 5)),
|
||||
)
|
||||
|
||||
|
||||
@router.get("/opportunity-factory/demo")
|
||||
async def opportunity_factory_demo() -> dict[str, Any]:
|
||||
return build_opportunity_factory_demo()
|
||||
|
||||
|
||||
# ── Service Factory ──────────────────────────────────────────
|
||||
@router.post("/service-factory")
|
||||
async def service_factory(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
|
||||
return instantiate_service(
|
||||
service_id=payload.get("service_id", ""),
|
||||
customer_id=payload.get("customer_id", ""),
|
||||
company_size=payload.get("company_size", "small"),
|
||||
urgency=payload.get("urgency", "normal"),
|
||||
)
|
||||
|
||||
|
||||
@router.get("/service-factory/demo")
|
||||
async def service_factory_demo() -> dict[str, Any]:
|
||||
return build_service_factory_demo()
|
||||
|
||||
|
||||
# ── Growth Memory ────────────────────────────────────────────
|
||||
@router.get("/growth-memory/demo")
|
||||
async def growth_memory_demo() -> dict[str, Any]:
|
||||
return build_growth_memory_demo()
|
||||
|
||||
|
||||
# ── Self-Improvement Loop ────────────────────────────────────
|
||||
@router.post("/self-improvement/weekly-report")
|
||||
async def self_improvement_weekly(payload: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]:
|
||||
return build_weekly_self_improvement_report(weekly_metrics=payload)
|
||||
|
||||
|
||||
@router.get("/self-improvement/demo")
|
||||
async def self_improvement_demo() -> dict[str, Any]:
|
||||
return build_weekly_self_improvement_report(weekly_metrics={
|
||||
"approval_rate": 0.42,
|
||||
"reply_rate": 0.05,
|
||||
"meeting_rate": 0.018,
|
||||
"blocked_actions": 12,
|
||||
"service_revenue_sar": {
|
||||
"first_10_opportunities_sprint": 1500,
|
||||
"list_intelligence": 999,
|
||||
"growth_os_monthly": 2999,
|
||||
},
|
||||
"top_objections": ["price", "timing"],
|
||||
"channel_outcomes": {"email": "healthy", "whatsapp": "watch"},
|
||||
})
|
||||
@ -0,0 +1,125 @@
|
||||
"""Autonomous Service Operator — البوت المركزي الذي يدير الخدمات.
|
||||
|
||||
Not a chatbot — a **service operator**: understands the customer's goal,
|
||||
recommends a service, collects intake, runs workflow, requests approval,
|
||||
delivers Proof Pack, suggests upgrade.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from .agency_mode import (
|
||||
add_agency_client,
|
||||
build_agency_dashboard,
|
||||
build_co_branded_proof_pack,
|
||||
list_agency_revenue_share,
|
||||
)
|
||||
from .approval_manager import (
|
||||
APPROVAL_STATES,
|
||||
build_approval_card,
|
||||
process_approval_decision,
|
||||
)
|
||||
from .client_mode import (
|
||||
build_client_dashboard,
|
||||
build_client_session_summary,
|
||||
)
|
||||
from .conversation_router import (
|
||||
INTENT_TO_HANDLER,
|
||||
handle_message,
|
||||
route_message,
|
||||
)
|
||||
from .executive_mode import (
|
||||
build_ceo_command_center,
|
||||
build_executive_daily_brief,
|
||||
build_revenue_risks_summary,
|
||||
)
|
||||
from .intake_collector import (
|
||||
build_intake_questions_for_intent,
|
||||
parse_intake_payload,
|
||||
validate_intake_completeness,
|
||||
)
|
||||
from .intent_classifier import (
|
||||
SUPPORTED_INTENTS,
|
||||
classify_intent,
|
||||
intent_to_service,
|
||||
)
|
||||
from .operator_memory import (
|
||||
OperatorMemory,
|
||||
build_session_context,
|
||||
)
|
||||
from .proof_pack_dispatcher import (
|
||||
dispatch_proof_pack,
|
||||
proof_pack_for_service,
|
||||
)
|
||||
from .service_bundles import (
|
||||
BUNDLES,
|
||||
get_bundle,
|
||||
list_bundles,
|
||||
recommend_bundle,
|
||||
)
|
||||
from .service_orchestrator import (
|
||||
SERVICE_PIPELINE_STEPS,
|
||||
build_service_pipeline,
|
||||
run_service_step,
|
||||
)
|
||||
from .session_state import (
|
||||
SessionState,
|
||||
build_new_session,
|
||||
transition_session,
|
||||
)
|
||||
from .tool_action_planner import (
|
||||
plan_tool_action,
|
||||
review_planned_action,
|
||||
)
|
||||
from .upsell_engine import (
|
||||
build_upsell_card,
|
||||
recommend_upsell_after_service,
|
||||
)
|
||||
from .whatsapp_renderer import (
|
||||
render_approval_card_for_whatsapp,
|
||||
render_card_for_whatsapp,
|
||||
render_daily_brief_for_whatsapp,
|
||||
)
|
||||
from .workflow_runner import (
|
||||
advance_workflow,
|
||||
build_workflow_state,
|
||||
is_workflow_complete,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
# conversation_router
|
||||
"INTENT_TO_HANDLER", "handle_message", "route_message",
|
||||
# intent_classifier
|
||||
"SUPPORTED_INTENTS", "classify_intent", "intent_to_service",
|
||||
# service_orchestrator
|
||||
"SERVICE_PIPELINE_STEPS", "build_service_pipeline", "run_service_step",
|
||||
# session_state
|
||||
"SessionState", "build_new_session", "transition_session",
|
||||
# intake_collector
|
||||
"build_intake_questions_for_intent", "parse_intake_payload",
|
||||
"validate_intake_completeness",
|
||||
# approval_manager
|
||||
"APPROVAL_STATES", "build_approval_card", "process_approval_decision",
|
||||
# workflow_runner
|
||||
"advance_workflow", "build_workflow_state", "is_workflow_complete",
|
||||
# tool_action_planner
|
||||
"plan_tool_action", "review_planned_action",
|
||||
# proof_pack_dispatcher
|
||||
"dispatch_proof_pack", "proof_pack_for_service",
|
||||
# upsell_engine
|
||||
"build_upsell_card", "recommend_upsell_after_service",
|
||||
# whatsapp_renderer
|
||||
"render_approval_card_for_whatsapp", "render_card_for_whatsapp",
|
||||
"render_daily_brief_for_whatsapp",
|
||||
# operator_memory
|
||||
"OperatorMemory", "build_session_context",
|
||||
# service_bundles
|
||||
"BUNDLES", "get_bundle", "list_bundles", "recommend_bundle",
|
||||
# executive_mode
|
||||
"build_ceo_command_center", "build_executive_daily_brief",
|
||||
"build_revenue_risks_summary",
|
||||
# client_mode
|
||||
"build_client_dashboard", "build_client_session_summary",
|
||||
# agency_mode
|
||||
"add_agency_client", "build_agency_dashboard",
|
||||
"build_co_branded_proof_pack", "list_agency_revenue_share",
|
||||
]
|
||||
@ -0,0 +1,133 @@
|
||||
"""Agency Mode — manage multiple clients + co-branded Proof Pack + revenue share."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
def add_agency_client(
|
||||
*,
|
||||
agency_id: str,
|
||||
client_company_name: str,
|
||||
sector: str = "",
|
||||
monthly_subscription_sar: int = 0,
|
||||
revenue_share_pct: int = 20,
|
||||
clients: list[dict[str, Any]] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Add a new client to an agency's roster + return the entry."""
|
||||
entry: dict[str, Any] = {
|
||||
"agency_id": agency_id,
|
||||
"client_company_name": client_company_name,
|
||||
"sector": sector,
|
||||
"monthly_subscription_sar": int(monthly_subscription_sar),
|
||||
"revenue_share_pct": int(revenue_share_pct),
|
||||
"status": "onboarding",
|
||||
"co_branded_proof_pack": True,
|
||||
"approval_required": True,
|
||||
}
|
||||
if clients is not None:
|
||||
clients.append(entry)
|
||||
return entry
|
||||
|
||||
|
||||
def build_agency_dashboard(
|
||||
*,
|
||||
agency_id: str,
|
||||
agency_name: str = "",
|
||||
clients: list[dict[str, Any]] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Build the agency's dashboard summary."""
|
||||
clients = clients or []
|
||||
total_clients = len(clients)
|
||||
active = sum(1 for c in clients if c.get("status") in ("active", "onboarding"))
|
||||
monthly_revenue_total = sum(
|
||||
float(c.get("monthly_subscription_sar", 0) or 0) for c in clients
|
||||
)
|
||||
avg_share_pct = (
|
||||
round(
|
||||
sum(int(c.get("revenue_share_pct", 0) or 0) for c in clients)
|
||||
/ max(1, total_clients),
|
||||
1,
|
||||
)
|
||||
if total_clients else 0.0
|
||||
)
|
||||
|
||||
return {
|
||||
"mode": "agency",
|
||||
"agency_id": agency_id,
|
||||
"agency_name": agency_name,
|
||||
"metrics": {
|
||||
"total_clients": total_clients,
|
||||
"active_clients": active,
|
||||
"monthly_revenue_sar": round(monthly_revenue_total, 2),
|
||||
"avg_revenue_share_pct": avg_share_pct,
|
||||
},
|
||||
"summary_ar": [
|
||||
f"عملاء الوكالة: {total_clients} (نشط: {active}).",
|
||||
f"الإيراد الشهري الكلي: {monthly_revenue_total:.0f} ريال.",
|
||||
f"متوسط revenue share: {avg_share_pct}%.",
|
||||
],
|
||||
"panels_ar": [
|
||||
"Add Client — إضافة عميل جديد",
|
||||
"Run Diagnostic — تشخيص لعميل",
|
||||
"Co-Branded Proof Pack — Proof بعلامة الوكالة",
|
||||
"Referral Tracking — متابعة الإحالات",
|
||||
"Partner Scorecard — تقييم الأداء",
|
||||
],
|
||||
"approval_required": True,
|
||||
"live_send_allowed": False,
|
||||
}
|
||||
|
||||
|
||||
def list_agency_revenue_share(
|
||||
*, clients: list[dict[str, Any]] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Compute revenue share owed to an agency for the current month."""
|
||||
clients = clients or []
|
||||
line_items: list[dict[str, Any]] = []
|
||||
total_share_sar = 0.0
|
||||
for c in clients:
|
||||
sub = float(c.get("monthly_subscription_sar", 0) or 0)
|
||||
pct = int(c.get("revenue_share_pct", 0) or 0)
|
||||
share = round(sub * pct / 100.0, 2)
|
||||
total_share_sar += share
|
||||
line_items.append({
|
||||
"client_company_name": c.get("client_company_name"),
|
||||
"monthly_subscription_sar": sub,
|
||||
"revenue_share_pct": pct,
|
||||
"agency_share_sar": share,
|
||||
})
|
||||
return {
|
||||
"line_items": line_items,
|
||||
"total_share_sar": round(total_share_sar, 2),
|
||||
"currency": "SAR",
|
||||
}
|
||||
|
||||
|
||||
def build_co_branded_proof_pack(
|
||||
*,
|
||||
agency_name: str,
|
||||
client_company_name: str,
|
||||
metrics: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Build a co-branded Proof Pack envelope for an agency client."""
|
||||
metrics = metrics or {}
|
||||
return {
|
||||
"title_ar": (
|
||||
f"Proof Pack — {client_company_name} (تنفيذ: {agency_name})"
|
||||
),
|
||||
"co_branded": True,
|
||||
"agency_name": agency_name,
|
||||
"client_company_name": client_company_name,
|
||||
"sections_ar": [
|
||||
"ملخص تنفيذي للعميل",
|
||||
"ما عملته الوكالة + Dealix",
|
||||
"النتائج بالأرقام",
|
||||
"Action Ledger",
|
||||
"المخاطر التي منعتها الوكالة",
|
||||
"التوصية بالخطوة التالية",
|
||||
],
|
||||
"metrics": dict(metrics),
|
||||
"approval_required": True,
|
||||
"live_send_allowed": False,
|
||||
}
|
||||
@ -0,0 +1,87 @@
|
||||
"""Approval manager — Arabic approval cards (≤3 buttons) + decision processing."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
APPROVAL_STATES: tuple[str, ...] = (
|
||||
"pending",
|
||||
"approved",
|
||||
"edited",
|
||||
"rejected",
|
||||
"expired",
|
||||
)
|
||||
|
||||
|
||||
def build_approval_card(
|
||||
*,
|
||||
action_type: str,
|
||||
title_ar: str,
|
||||
summary_ar: str,
|
||||
risk_level: str = "low",
|
||||
why_now_ar: str = "",
|
||||
recommended_action_ar: str = "",
|
||||
expected_impact_sar: float = 0.0,
|
||||
service_id: str | None = None,
|
||||
customer_id: str | None = None,
|
||||
action_id: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Build a structured Arabic approval card."""
|
||||
return {
|
||||
"type": "approval",
|
||||
"action_id": action_id,
|
||||
"action_type": action_type,
|
||||
"service_id": service_id,
|
||||
"customer_id": customer_id,
|
||||
"title_ar": title_ar[:140],
|
||||
"summary_ar": summary_ar[:280],
|
||||
"why_now_ar": why_now_ar[:200],
|
||||
"recommended_action_ar": recommended_action_ar[:200],
|
||||
"risk_level": risk_level if risk_level in (
|
||||
"low", "medium", "high",
|
||||
) else "medium",
|
||||
"expected_impact_sar": float(expected_impact_sar),
|
||||
"buttons_ar": ["اعتمد", "عدّل", "تخطي"],
|
||||
"state": "pending",
|
||||
"approval_required": True,
|
||||
"live_send_allowed": False,
|
||||
}
|
||||
|
||||
|
||||
def process_approval_decision(
|
||||
card: dict[str, Any],
|
||||
*,
|
||||
decision: str,
|
||||
decided_by: str = "user",
|
||||
note: str = "",
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Process an approval decision (`approve` / `edit` / `skip` / `reject`).
|
||||
|
||||
Returns the updated card with new state + audit info.
|
||||
"""
|
||||
decision_lc = (decision or "").strip().lower()
|
||||
if decision_lc in ("approve", "approved", "موافق", "اعتمد", "نعم"):
|
||||
new_state = "approved"
|
||||
next_action = "execute_with_audit"
|
||||
elif decision_lc in ("edit", "عدّل", "تعديل"):
|
||||
new_state = "edited"
|
||||
next_action = "rewrite_then_resend_for_approval"
|
||||
elif decision_lc in ("skip", "تخطي", "تجاوز"):
|
||||
new_state = "rejected"
|
||||
next_action = "archive"
|
||||
elif decision_lc in ("reject", "ارفض", "لا"):
|
||||
new_state = "rejected"
|
||||
next_action = "archive_with_reason"
|
||||
else:
|
||||
return {
|
||||
"error": f"unknown decision: {decision}",
|
||||
"valid_decisions": ["approve", "edit", "skip", "reject"],
|
||||
}
|
||||
|
||||
out = dict(card)
|
||||
out["state"] = new_state
|
||||
out["decided_by"] = decided_by
|
||||
out["decision_note"] = note[:200]
|
||||
out["next_action"] = next_action
|
||||
return out
|
||||
@ -0,0 +1,55 @@
|
||||
"""Client Mode — dashboard for the customer (Growth Manager) view."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
def build_client_dashboard(
|
||||
*,
|
||||
customer_id: str = "",
|
||||
company_name: str = "",
|
||||
active_services: list[str] | None = None,
|
||||
open_actions: int = 0,
|
||||
proof_pack_due: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
"""Build the client-facing dashboard."""
|
||||
active_services = active_services or []
|
||||
return {
|
||||
"mode": "client",
|
||||
"customer_id": customer_id,
|
||||
"company_name": company_name,
|
||||
"active_services": list(active_services),
|
||||
"open_actions": open_actions,
|
||||
"proof_pack_due": proof_pack_due,
|
||||
"today_panels_ar": [
|
||||
"Command Feed — قرارات اليوم",
|
||||
"Approvals Center — رسائل تنتظر اعتمادك",
|
||||
"Pipeline Tracker — مرحلة كل عميل",
|
||||
"Proof Pack — آخر تقرير + الـ ROI",
|
||||
],
|
||||
"buttons_ar": ["اعرض القرارات", "اعتمد جماعي", "افتح Proof Pack"],
|
||||
"approval_required": True,
|
||||
"live_send_allowed": False,
|
||||
}
|
||||
|
||||
|
||||
def build_client_session_summary(
|
||||
*,
|
||||
session_id: str,
|
||||
customer_id: str = "",
|
||||
last_intent: str = "",
|
||||
last_recommended_service: str = "",
|
||||
) -> dict[str, Any]:
|
||||
"""Build a session summary for the client view."""
|
||||
return {
|
||||
"mode": "client",
|
||||
"session_id": session_id,
|
||||
"customer_id": customer_id,
|
||||
"last_intent": last_intent,
|
||||
"last_recommended_service": last_recommended_service,
|
||||
"next_step_ar": (
|
||||
"أكمل الـ intake للحصول على workflow الخدمة + أول Proof Pack."
|
||||
),
|
||||
"approval_required": True,
|
||||
}
|
||||
@ -0,0 +1,114 @@
|
||||
"""Conversation router — single entry point for any operator message."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from .approval_manager import (
|
||||
build_approval_card,
|
||||
process_approval_decision,
|
||||
)
|
||||
from .intake_collector import build_intake_questions_for_intent
|
||||
from .intent_classifier import classify_intent, intent_to_service
|
||||
from .service_bundles import recommend_bundle
|
||||
from .service_orchestrator import build_service_pipeline
|
||||
|
||||
|
||||
# Map: intent → handler name
|
||||
INTENT_TO_HANDLER: dict[str, str] = {
|
||||
"want_more_customers": "start_first_10_opportunities",
|
||||
"has_contact_list": "start_list_intelligence",
|
||||
"want_partnerships": "start_partner_sprint",
|
||||
"want_daily_growth": "start_growth_os",
|
||||
"want_meetings": "start_meeting_sprint",
|
||||
"want_email_rescue": "start_email_rescue",
|
||||
"want_whatsapp_setup": "start_whatsapp_compliance",
|
||||
"ask_pricing": "show_pricing",
|
||||
"approve_action": "process_approval",
|
||||
"edit_action": "process_edit",
|
||||
"skip_action": "process_skip",
|
||||
"ask_demo": "send_demo",
|
||||
"ask_proof": "send_proof_pack",
|
||||
"ask_services": "show_bundles",
|
||||
"ask_partnership": "show_agency_partner",
|
||||
"ask_revenue_today": "show_revenue_today_plan",
|
||||
}
|
||||
|
||||
|
||||
def route_message(message: str) -> dict[str, Any]:
|
||||
"""Classify a message + return the routed handler + recommended service."""
|
||||
classification = classify_intent(message)
|
||||
intent = classification["intent"]
|
||||
handler = INTENT_TO_HANDLER.get(intent, "show_bundles")
|
||||
service_id = intent_to_service(intent)
|
||||
|
||||
return {
|
||||
"message": (message or "")[:300],
|
||||
"classification": classification,
|
||||
"intent": intent,
|
||||
"handler": handler,
|
||||
"recommended_service_id": service_id,
|
||||
}
|
||||
|
||||
|
||||
def handle_message(
|
||||
message: str,
|
||||
*,
|
||||
customer_id: str | None = None,
|
||||
has_contact_list: bool = False,
|
||||
is_agency: bool = False,
|
||||
is_local_business: bool = False,
|
||||
budget_sar: int = 1000,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Full single-shot handler — classifies + plans + returns operator response.
|
||||
|
||||
Never executes any external action. Just plans + drafts.
|
||||
"""
|
||||
routed = route_message(message)
|
||||
intent = routed["intent"]
|
||||
handler = routed["handler"]
|
||||
|
||||
# Recommend a bundle (high-level package).
|
||||
bundle_rec = recommend_bundle(
|
||||
intent=intent,
|
||||
has_contact_list=has_contact_list,
|
||||
is_agency=is_agency,
|
||||
is_local_business=is_local_business,
|
||||
budget_sar=budget_sar,
|
||||
)
|
||||
|
||||
# If a service is recommended, build its initial pipeline + intake form.
|
||||
response: dict[str, Any] = {
|
||||
"intent": intent,
|
||||
"handler": handler,
|
||||
"bundle_recommendation": bundle_rec,
|
||||
"service_id": routed["recommended_service_id"],
|
||||
"approval_required": True,
|
||||
"live_send_allowed": False,
|
||||
}
|
||||
|
||||
if intent in ("approve_action", "edit_action", "skip_action"):
|
||||
# Approvals are handled separately — surface a placeholder card.
|
||||
decision = (
|
||||
"approve" if intent == "approve_action"
|
||||
else "edit" if intent == "edit_action"
|
||||
else "skip"
|
||||
)
|
||||
sample_card = build_approval_card(
|
||||
action_type="example_action",
|
||||
title_ar="فعل مثال",
|
||||
summary_ar="هذا مثال على approval card",
|
||||
)
|
||||
response["decision_processed"] = process_approval_decision(
|
||||
sample_card, decision=decision, decided_by=customer_id or "user",
|
||||
)
|
||||
return response
|
||||
|
||||
if routed["recommended_service_id"]:
|
||||
response["intake_questions"] = build_intake_questions_for_intent(intent)
|
||||
response["initial_pipeline"] = build_service_pipeline(
|
||||
routed["recommended_service_id"], customer_id=customer_id or "",
|
||||
)
|
||||
|
||||
return response
|
||||
@ -0,0 +1,92 @@
|
||||
"""Executive Mode — CEO command center + daily brief + revenue risks."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
def build_executive_daily_brief(
|
||||
*,
|
||||
company_name: str = "",
|
||||
sector: str = "saas",
|
||||
) -> dict[str, Any]:
|
||||
"""Build the CEO's daily brief (Arabic)."""
|
||||
return {
|
||||
"title_ar": f"موجز اليوم التنفيذي — {company_name or '(الشركة)'}",
|
||||
"summary_ar": [
|
||||
f"3 قرارات تنتظر اعتمادك في قطاع {sector}.",
|
||||
"5 رسائل drafts معدّة بـ Saudi tone.",
|
||||
"2 leads متأخرة في المتابعة (>72 ساعة).",
|
||||
"1 شريك وكالة جاهز لاجتماع.",
|
||||
"1 خطر سمعة على قناة (يحتاج مراجعة).",
|
||||
],
|
||||
"priority_decisions_ar": [
|
||||
"اعتمد 5 رسائل إيميل (10 دقائق).",
|
||||
"راجع 12 رقم بدون مصدر واضح قبل أي واتساب.",
|
||||
"احجز ديمو شريك الوكالة.",
|
||||
],
|
||||
"metric_to_watch_ar": (
|
||||
"نسبة approval_rate الأسبوعية — هي المؤشر الأقوى لجودة "
|
||||
"الـ targeting + الـ Saudi Tone."
|
||||
),
|
||||
"buttons_ar": ["اعرض القرارات", "Proof Pack", "لاحقاً"],
|
||||
"approval_required": True,
|
||||
}
|
||||
|
||||
|
||||
def build_revenue_risks_summary(
|
||||
*,
|
||||
open_risks: list[dict[str, Any]] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Build a 3-risk summary (Arabic)."""
|
||||
open_risks = open_risks or [
|
||||
{
|
||||
"id": "wa_quality",
|
||||
"title_ar": "جودة واتساب",
|
||||
"summary_ar": "نسبة الحظر على رقم واتساب الرئيسي تقترب من حد التحذير.",
|
||||
"severity": "high",
|
||||
"action_ar": "خفّض الحجم 50% + راجع الرسائل.",
|
||||
},
|
||||
{
|
||||
"id": "list_freshness",
|
||||
"title_ar": "قائمة قديمة",
|
||||
"summary_ar": "60% من القائمة لم يتم تحديثها منذ 9 أشهر.",
|
||||
"severity": "medium",
|
||||
"action_ar": "شغّل List Intelligence لتنظيفها.",
|
||||
},
|
||||
{
|
||||
"id": "single_threading",
|
||||
"title_ar": "صفقة بشخص واحد",
|
||||
"summary_ar": "صفقة كبيرة (250K) معتمدة على شخص واحد بدون buying committee.",
|
||||
"severity": "high",
|
||||
"action_ar": "ادعُ صانع قرار ثانٍ من نفس الشركة.",
|
||||
},
|
||||
]
|
||||
return {
|
||||
"title_ar": "أعلى 3 مخاطر إيراد اليوم",
|
||||
"risks": open_risks[:3],
|
||||
"approval_required": True,
|
||||
}
|
||||
|
||||
|
||||
def build_ceo_command_center(
|
||||
*,
|
||||
company_name: str = "",
|
||||
sector: str = "saas",
|
||||
) -> dict[str, Any]:
|
||||
"""Build the full CEO command-center page."""
|
||||
return {
|
||||
"mode": "ceo",
|
||||
"company_name": company_name,
|
||||
"daily_brief": build_executive_daily_brief(
|
||||
company_name=company_name, sector=sector,
|
||||
),
|
||||
"revenue_risks": build_revenue_risks_summary(),
|
||||
"next_three_moves_ar": [
|
||||
"اعتمد رسائل اليوم (5).",
|
||||
"ابدأ Pilot 7 أيام لقطاع جديد (testing).",
|
||||
"حدد منسّق Approvals بديل خلال 24 ساعة.",
|
||||
],
|
||||
"approval_required": True,
|
||||
"live_send_allowed": False,
|
||||
}
|
||||
@ -0,0 +1,129 @@
|
||||
"""Intake collector — builds intake questions per intent + validates payloads."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
# Intake questions per intent (Arabic).
|
||||
_INTAKE_QUESTIONS_BY_INTENT: dict[str, list[dict[str, Any]]] = {
|
||||
"want_more_customers": [
|
||||
{"key": "company_name", "label_ar": "اسم الشركة", "required": True},
|
||||
{"key": "sector", "label_ar": "القطاع", "required": True},
|
||||
{"key": "city", "label_ar": "المدينة", "required": True},
|
||||
{"key": "offer", "label_ar": "العرض الرئيسي", "required": True},
|
||||
{"key": "ideal_customer", "label_ar": "العميل المثالي",
|
||||
"required": True},
|
||||
],
|
||||
"has_contact_list": [
|
||||
{"key": "company_name", "label_ar": "اسم الشركة", "required": True},
|
||||
{"key": "sector", "label_ar": "القطاع", "required": True},
|
||||
{"key": "list_size", "label_ar": "حجم القائمة (تقريباً)",
|
||||
"required": True},
|
||||
{"key": "list_source", "label_ar": "مصدر القائمة (CRM/event/upload)",
|
||||
"required": True},
|
||||
{"key": "channels_available", "label_ar": "القنوات المتاحة",
|
||||
"required": True},
|
||||
],
|
||||
"want_partnerships": [
|
||||
{"key": "company_name", "label_ar": "اسم الشركة", "required": True},
|
||||
{"key": "sector", "label_ar": "القطاع", "required": True},
|
||||
{"key": "partner_goal",
|
||||
"label_ar": "هدف الشراكة (وكالات/موزعين/co-marketing)",
|
||||
"required": True},
|
||||
{"key": "current_partners", "label_ar": "شركاء حاليين (إن وجد)",
|
||||
"required": False},
|
||||
],
|
||||
"want_daily_growth": [
|
||||
{"key": "company_name", "label_ar": "اسم الشركة", "required": True},
|
||||
{"key": "sector", "label_ar": "القطاع", "required": True},
|
||||
{"key": "team_size", "label_ar": "حجم فريق المبيعات/النمو",
|
||||
"required": True},
|
||||
{"key": "channels", "label_ar": "القنوات الحالية", "required": True},
|
||||
{"key": "approval_owner", "label_ar": "من يوافق على الرسائل؟",
|
||||
"required": True},
|
||||
],
|
||||
"want_meetings": [
|
||||
{"key": "company_name", "label_ar": "اسم الشركة", "required": True},
|
||||
{"key": "prospect_count", "label_ar": "عدد الـ prospects",
|
||||
"required": True},
|
||||
{"key": "calendar_link", "label_ar": "رابط Calendar (لو وُجد)",
|
||||
"required": False},
|
||||
],
|
||||
"want_email_rescue": [
|
||||
{"key": "company_name", "label_ar": "اسم الشركة", "required": True},
|
||||
{"key": "gmail_label",
|
||||
"label_ar": "اسم الـ label/الـ folder المستهدف",
|
||||
"required": True},
|
||||
{"key": "ICP", "label_ar": "العميل المثالي", "required": True},
|
||||
],
|
||||
"want_whatsapp_setup": [
|
||||
{"key": "company_name", "label_ar": "اسم الشركة", "required": True},
|
||||
{"key": "list_size",
|
||||
"label_ar": "حجم قاعدة الواتساب الحالية", "required": True},
|
||||
{"key": "current_practice",
|
||||
"label_ar": "الطريقة الحالية في إرسال الرسائل", "required": True},
|
||||
],
|
||||
"ask_revenue_today": [
|
||||
{"key": "company_name", "label_ar": "اسم الشركة", "required": True},
|
||||
{"key": "sector", "label_ar": "القطاع", "required": True},
|
||||
{"key": "city", "label_ar": "المدينة", "required": True},
|
||||
{"key": "offer", "label_ar": "العرض الرئيسي", "required": True},
|
||||
],
|
||||
# Default minimal intake for any "ask_*" intent.
|
||||
"ask_services": [
|
||||
{"key": "goal", "label_ar": "ما هدفك الأساسي؟", "required": True},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def build_intake_questions_for_intent(intent: str) -> dict[str, Any]:
|
||||
"""Return intake questions for an intent. Falls back to ask_services."""
|
||||
questions = _INTAKE_QUESTIONS_BY_INTENT.get(intent)
|
||||
if questions is None:
|
||||
questions = _INTAKE_QUESTIONS_BY_INTENT["ask_services"]
|
||||
return {
|
||||
"intent": intent,
|
||||
"questions": [dict(q) for q in questions],
|
||||
"estimated_minutes": max(2, len(questions) * 1),
|
||||
"approval_required": True,
|
||||
}
|
||||
|
||||
|
||||
def parse_intake_payload(
|
||||
intent: str, raw_payload: dict[str, Any] | None,
|
||||
) -> dict[str, Any]:
|
||||
"""Parse + sanitize an intake payload against the intent's question set."""
|
||||
raw_payload = raw_payload or {}
|
||||
questions = _INTAKE_QUESTIONS_BY_INTENT.get(
|
||||
intent, _INTAKE_QUESTIONS_BY_INTENT["ask_services"],
|
||||
)
|
||||
parsed: dict[str, Any] = {}
|
||||
for q in questions:
|
||||
key = q["key"]
|
||||
val = raw_payload.get(key)
|
||||
if val is None:
|
||||
continue
|
||||
# Strings get truncated to 500 chars.
|
||||
if isinstance(val, str):
|
||||
val = val.strip()[:500]
|
||||
parsed[key] = val
|
||||
return parsed
|
||||
|
||||
|
||||
def validate_intake_completeness(
|
||||
intent: str, payload: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
"""Check that all required intake fields are present."""
|
||||
questions = _INTAKE_QUESTIONS_BY_INTENT.get(
|
||||
intent, _INTAKE_QUESTIONS_BY_INTENT["ask_services"],
|
||||
)
|
||||
missing: list[str] = []
|
||||
for q in questions:
|
||||
if q.get("required") and not payload.get(q["key"]):
|
||||
missing.append(str(q["key"]))
|
||||
return {
|
||||
"intent": intent,
|
||||
"complete": not missing,
|
||||
"missing_fields": missing,
|
||||
"missing_count": len(missing),
|
||||
}
|
||||
@ -0,0 +1,180 @@
|
||||
"""Deterministic intent classifier — Arabic + English keywords → 16 intents."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
# 16 supported intents that drive the operator.
|
||||
SUPPORTED_INTENTS: tuple[str, ...] = (
|
||||
"want_more_customers",
|
||||
"has_contact_list",
|
||||
"want_partnerships",
|
||||
"want_daily_growth",
|
||||
"want_meetings",
|
||||
"want_email_rescue",
|
||||
"want_whatsapp_setup",
|
||||
"ask_pricing",
|
||||
"approve_action",
|
||||
"edit_action",
|
||||
"skip_action",
|
||||
"ask_demo",
|
||||
"ask_proof",
|
||||
"ask_services",
|
||||
"ask_partnership",
|
||||
"ask_revenue_today",
|
||||
)
|
||||
|
||||
# Each intent → (Arabic keywords, English keywords).
|
||||
_KEYWORDS: dict[str, tuple[list[str], list[str]]] = {
|
||||
"want_more_customers": (
|
||||
["عملاء", "فرص", "leads", "ليدز", "عميل جديد", "مبيعات",
|
||||
"أبغى عملاء", "زيادة عملاء"],
|
||||
["customers", "leads", "more sales", "new clients", "pipeline"],
|
||||
),
|
||||
"has_contact_list": (
|
||||
["قائمة", "أرقام", "إيميلات", "CSV", "قائمتي", "عملاء قدامى",
|
||||
"اللستة", "ملف"],
|
||||
["list", "csv", "old customers", "spreadsheet", "contacts"],
|
||||
),
|
||||
"want_partnerships": (
|
||||
["شراكات", "شريك", "وكالة", "تعاون", "موزع", "شركاء"],
|
||||
["partnership", "partner", "agency deal", "referral"],
|
||||
),
|
||||
"want_daily_growth": (
|
||||
["تشغيل يومي", "نمو شهري", "Growth OS", "اشتراك", "يومياً",
|
||||
"مدير نمو"],
|
||||
["daily growth", "growth os", "subscription", "monthly"],
|
||||
),
|
||||
"want_meetings": (
|
||||
["اجتماعات", "ديمو", "meeting", "موعد", "احجز", "مكالمة",
|
||||
"demo"],
|
||||
["meeting", "demo", "book", "schedule call"],
|
||||
),
|
||||
"want_email_rescue": (
|
||||
["إيميل", "Gmail", "Outlook", "إنباكس", "بريد", "ضائعة"],
|
||||
["email rescue", "inbox", "gmail", "missed emails"],
|
||||
),
|
||||
"want_whatsapp_setup": (
|
||||
["واتساب", "WhatsApp", "opt-in", "حملة واتساب", "أرقامي"],
|
||||
["whatsapp", "compliance", "opt-in"],
|
||||
),
|
||||
"ask_pricing": (
|
||||
["السعر", "كم", "بكم", "تكلفة", "اشتراك"],
|
||||
["price", "cost", "how much", "pricing"],
|
||||
),
|
||||
"approve_action": (
|
||||
["اعتمد", "موافق", "وافق", "تمام", "نعم"],
|
||||
["approve", "ok", "yes", "go ahead", "confirm"],
|
||||
),
|
||||
"edit_action": (
|
||||
["عدّل", "تعديل", "غير", "بدّل"],
|
||||
["edit", "change", "modify", "tweak"],
|
||||
),
|
||||
"skip_action": (
|
||||
["تخطي", "تخطى", "تجاوز", "خطّي", "لا"],
|
||||
["skip", "no", "pass", "later"],
|
||||
),
|
||||
"ask_demo": (
|
||||
["ديمو", "عرض", "أشوف", "جرب", "تجربة"],
|
||||
["demo", "try", "show me", "trial"],
|
||||
),
|
||||
"ask_proof": (
|
||||
["proof", "نتائج", "case study", "إثبات", "تقرير"],
|
||||
["proof", "results", "case study", "report"],
|
||||
),
|
||||
"ask_services": (
|
||||
["الخدمات", "وش عندكم", "ماذا تقدمون", "العروض", "bundles"],
|
||||
["services", "what do you offer", "bundles", "packages"],
|
||||
),
|
||||
"ask_partnership": (
|
||||
["وكالة شريكة", "Agency Partner", "revenue share", "شراكة وكالة"],
|
||||
["agency partner", "revenue share", "white label"],
|
||||
),
|
||||
"ask_revenue_today": (
|
||||
["دخل اليوم", "أبيع اليوم", "اول pilot", "ابدأ اليوم"],
|
||||
["revenue today", "sell today", "first pilot", "private beta"],
|
||||
),
|
||||
}
|
||||
|
||||
# Map intent → recommended service ID (in service_tower.service_catalog).
|
||||
INTENT_TO_SERVICE: dict[str, str] = {
|
||||
"want_more_customers": "first_10_opportunities_sprint",
|
||||
"has_contact_list": "list_intelligence",
|
||||
"want_partnerships": "partner_sprint",
|
||||
"want_daily_growth": "growth_os_monthly",
|
||||
"want_meetings": "meeting_booking_sprint",
|
||||
"want_email_rescue": "email_revenue_rescue",
|
||||
"want_whatsapp_setup": "whatsapp_compliance_setup",
|
||||
"ask_pricing": "free_growth_diagnostic",
|
||||
"ask_demo": "free_growth_diagnostic",
|
||||
"ask_proof": "free_growth_diagnostic",
|
||||
"ask_services": "free_growth_diagnostic",
|
||||
"ask_partnership": "agency_partner_program",
|
||||
"ask_revenue_today": "first_10_opportunities_sprint",
|
||||
}
|
||||
|
||||
|
||||
def classify_intent(message: str) -> dict[str, Any]:
|
||||
"""
|
||||
Classify a free-text message → intent + confidence.
|
||||
|
||||
Deterministic, keyword-based. No LLM. Returns:
|
||||
{
|
||||
"intent": str,
|
||||
"confidence": float (0..1),
|
||||
"matched_keywords": list[str],
|
||||
"all_scores": dict[intent, score],
|
||||
}
|
||||
"""
|
||||
text = (message or "").strip()
|
||||
if not text:
|
||||
return {
|
||||
"intent": "ask_services",
|
||||
"confidence": 0.1,
|
||||
"matched_keywords": [],
|
||||
"all_scores": {},
|
||||
}
|
||||
|
||||
text_lc = text.lower()
|
||||
scores: dict[str, int] = {}
|
||||
matched_by_intent: dict[str, list[str]] = {}
|
||||
|
||||
for intent, (ar_kw, en_kw) in _KEYWORDS.items():
|
||||
matches: list[str] = []
|
||||
for kw in ar_kw:
|
||||
if kw in text:
|
||||
matches.append(kw)
|
||||
for kw in en_kw:
|
||||
if kw.lower() in text_lc:
|
||||
matches.append(kw)
|
||||
scores[intent] = len(matches)
|
||||
if matches:
|
||||
matched_by_intent[intent] = matches
|
||||
|
||||
if not any(scores.values()):
|
||||
return {
|
||||
"intent": "ask_services",
|
||||
"confidence": 0.2,
|
||||
"matched_keywords": [],
|
||||
"all_scores": scores,
|
||||
}
|
||||
|
||||
best_intent = max(scores, key=lambda k: scores[k])
|
||||
total_matches = sum(scores.values())
|
||||
confidence = (
|
||||
round(scores[best_intent] / max(1, total_matches), 3)
|
||||
if total_matches else 0.0
|
||||
)
|
||||
|
||||
return {
|
||||
"intent": best_intent,
|
||||
"confidence": confidence,
|
||||
"matched_keywords": matched_by_intent.get(best_intent, []),
|
||||
"all_scores": scores,
|
||||
}
|
||||
|
||||
|
||||
def intent_to_service(intent: str) -> str | None:
|
||||
"""Return the service-tower service ID linked to an intent (or None)."""
|
||||
return INTENT_TO_SERVICE.get(intent)
|
||||
@ -0,0 +1,104 @@
|
||||
"""Operator memory — minimal in-process store for sessions + facts."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any
|
||||
|
||||
from .session_state import SessionState
|
||||
|
||||
|
||||
@dataclass
|
||||
class OperatorMemory:
|
||||
"""In-process memory for the operator. Production = Supabase/Redis."""
|
||||
sessions: dict[str, SessionState] = field(default_factory=dict)
|
||||
customer_facts: dict[str, dict[str, Any]] = field(default_factory=dict)
|
||||
customer_preferences: dict[str, dict[str, Any]] = field(default_factory=dict)
|
||||
blocked_actions_log: list[dict[str, Any]] = field(default_factory=list)
|
||||
approved_actions_log: list[dict[str, Any]] = field(default_factory=list)
|
||||
pivots_log: list[dict[str, Any]] = field(default_factory=list)
|
||||
|
||||
# ── sessions ────────────────────────────────────────────
|
||||
def upsert_session(self, session: SessionState) -> SessionState:
|
||||
self.sessions[session.session_id] = session
|
||||
return session
|
||||
|
||||
def get_session(self, session_id: str) -> SessionState | None:
|
||||
return self.sessions.get(session_id)
|
||||
|
||||
def list_sessions_for_customer(self, customer_id: str) -> list[SessionState]:
|
||||
return [s for s in self.sessions.values()
|
||||
if s.customer_id == customer_id]
|
||||
|
||||
# ── customer facts ──────────────────────────────────────
|
||||
def remember_fact(self, customer_id: str, key: str, value: Any) -> None:
|
||||
bucket = self.customer_facts.setdefault(customer_id, {})
|
||||
bucket[key] = value
|
||||
|
||||
def get_fact(self, customer_id: str, key: str) -> Any:
|
||||
return self.customer_facts.get(customer_id, {}).get(key)
|
||||
|
||||
def all_facts(self, customer_id: str) -> dict[str, Any]:
|
||||
return dict(self.customer_facts.get(customer_id, {}))
|
||||
|
||||
# ── preferences ─────────────────────────────────────────
|
||||
def update_preference(
|
||||
self, customer_id: str, *, key: str, value: Any,
|
||||
) -> None:
|
||||
bucket = self.customer_preferences.setdefault(customer_id, {})
|
||||
bucket[key] = value
|
||||
|
||||
def get_preferences(self, customer_id: str) -> dict[str, Any]:
|
||||
return dict(self.customer_preferences.get(customer_id, {}))
|
||||
|
||||
# ── action audit ────────────────────────────────────────
|
||||
def log_blocked_action(
|
||||
self, *, action_type: str, reason_ar: str,
|
||||
customer_id: str | None = None,
|
||||
) -> None:
|
||||
self.blocked_actions_log.append({
|
||||
"ts": time.time(),
|
||||
"action_type": action_type,
|
||||
"reason_ar": reason_ar[:200],
|
||||
"customer_id": customer_id,
|
||||
})
|
||||
|
||||
def log_approved_action(
|
||||
self, *, action_type: str,
|
||||
customer_id: str | None = None,
|
||||
notes: str = "",
|
||||
) -> None:
|
||||
self.approved_actions_log.append({
|
||||
"ts": time.time(),
|
||||
"action_type": action_type,
|
||||
"customer_id": customer_id,
|
||||
"notes": notes[:200],
|
||||
})
|
||||
|
||||
def summarize_audit(self) -> dict[str, Any]:
|
||||
return {
|
||||
"blocked_count": len(self.blocked_actions_log),
|
||||
"approved_count": len(self.approved_actions_log),
|
||||
"blocked_recent": self.blocked_actions_log[-5:],
|
||||
"approved_recent": self.approved_actions_log[-5:],
|
||||
}
|
||||
|
||||
|
||||
def build_session_context(
|
||||
*,
|
||||
memory: OperatorMemory,
|
||||
session_id: str,
|
||||
) -> dict[str, Any]:
|
||||
"""Build a context blob for a session — facts + recent audit + state."""
|
||||
session = memory.get_session(session_id)
|
||||
if session is None:
|
||||
return {"error": "unknown session"}
|
||||
|
||||
customer_id = session.customer_id or ""
|
||||
return {
|
||||
"session": session.to_dict(),
|
||||
"customer_facts": memory.all_facts(customer_id),
|
||||
"preferences": memory.get_preferences(customer_id),
|
||||
"audit": memory.summarize_audit(),
|
||||
}
|
||||
@ -0,0 +1,72 @@
|
||||
"""Proof Pack dispatcher — generates + delivers Proof Packs per service."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
def proof_pack_for_service(
|
||||
service_id: str, *, metrics: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Build a Proof Pack template for any service."""
|
||||
metrics = metrics or {}
|
||||
return {
|
||||
"service_id": service_id,
|
||||
"title_ar": f"Proof Pack — {service_id}",
|
||||
"sections_ar": [
|
||||
"ملخص تنفيذي (5 أسطر)",
|
||||
"ما عمله Dealix",
|
||||
"النتائج (الأرقام)",
|
||||
"أبرز الردود/الاعتراضات",
|
||||
"المخاطر التي تم منعها",
|
||||
"Action Ledger مختصر",
|
||||
"التوصية بالخطوة التالية",
|
||||
],
|
||||
"metrics_captured": dict(metrics),
|
||||
"metrics_required": [
|
||||
"opportunities_generated",
|
||||
"drafts_approved",
|
||||
"positive_replies",
|
||||
"meetings_drafted",
|
||||
"pipeline_influenced_sar",
|
||||
"risks_blocked",
|
||||
"time_saved_hours",
|
||||
],
|
||||
"delivery_format": ["pdf", "json", "whatsapp_summary"],
|
||||
"approval_required": True,
|
||||
"live_send_allowed": False,
|
||||
}
|
||||
|
||||
|
||||
def dispatch_proof_pack(
|
||||
*,
|
||||
service_id: str,
|
||||
customer_id: str | None = None,
|
||||
channel: str = "email",
|
||||
metrics: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Dispatch a Proof Pack to a customer.
|
||||
|
||||
Returns a draft envelope — never sends. The actual delivery requires
|
||||
customer/admin approval through the Approval Center.
|
||||
"""
|
||||
template = proof_pack_for_service(service_id, metrics=metrics)
|
||||
return {
|
||||
"service_id": service_id,
|
||||
"customer_id": customer_id,
|
||||
"channel": channel,
|
||||
"envelope": {
|
||||
"subject_ar": template["title_ar"],
|
||||
"body_ar": (
|
||||
"مرفق Proof Pack الخاص بـ Pilot. "
|
||||
"يحتوي على ملخص تنفيذي + النتائج + المخاطر التي تم منعها + "
|
||||
"التوصية بالخطوة التالية."
|
||||
),
|
||||
"attachments": ["proof_pack.pdf", "proof_pack.json"],
|
||||
},
|
||||
"template": template,
|
||||
"status": "draft",
|
||||
"approval_required": True,
|
||||
"live_send_allowed": False,
|
||||
}
|
||||
@ -0,0 +1,215 @@
|
||||
"""Service bundles — 6 packaged offerings instead of 20 raw services."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
# 6 bundles that simplify the customer's choice.
|
||||
BUNDLES: tuple[dict[str, Any], ...] = (
|
||||
{
|
||||
"id": "growth_starter",
|
||||
"name_ar": "Growth Starter",
|
||||
"best_for_ar": "أي شركة تجرب Dealix لأول مرة",
|
||||
"services": [
|
||||
"free_growth_diagnostic",
|
||||
"first_10_opportunities_sprint",
|
||||
],
|
||||
"deliverables_ar": [
|
||||
"تشخيص نمو مجاني خلال 24 ساعة",
|
||||
"10 فرص + رسائل عربية",
|
||||
"Proof Pack مختصر",
|
||||
],
|
||||
"timeline_ar": "8 أيام (1 ديمو + 7 Pilot)",
|
||||
"price_min_sar": 499,
|
||||
"price_max_sar": 1500,
|
||||
"proof_metrics": [
|
||||
"opportunities_count", "drafts_approved",
|
||||
"positive_replies", "diagnostic_to_paid_conversion",
|
||||
],
|
||||
"upgrade_path": ["executive_growth_os"],
|
||||
},
|
||||
{
|
||||
"id": "data_to_revenue",
|
||||
"name_ar": "Data to Revenue",
|
||||
"best_for_ar": "شركات لديها قائمة عملاء/أرقام لم تُستثمر",
|
||||
"services": [
|
||||
"list_intelligence",
|
||||
"first_10_opportunities_sprint",
|
||||
],
|
||||
"deliverables_ar": [
|
||||
"قائمة منظفة + تصنيف مصادر",
|
||||
"أفضل 50 target بالقنوات الآمنة",
|
||||
"رسائل عربية لكل segment",
|
||||
"Risk report + retention",
|
||||
],
|
||||
"timeline_ar": "10 أيام",
|
||||
"price_min_sar": 1500,
|
||||
"price_max_sar": 3000,
|
||||
"proof_metrics": [
|
||||
"contacts_classified", "safe_targets_found",
|
||||
"risks_blocked", "pipeline_influenced_sar",
|
||||
],
|
||||
"upgrade_path": ["executive_growth_os"],
|
||||
},
|
||||
{
|
||||
"id": "executive_growth_os",
|
||||
"name_ar": "Executive Growth OS",
|
||||
"best_for_ar": "CEO / Growth Manager — تشغيل شهري",
|
||||
"services": [
|
||||
"growth_os_monthly",
|
||||
"executive_growth_brief",
|
||||
],
|
||||
"deliverables_ar": [
|
||||
"Daily Command Feed عربي",
|
||||
"Approval Center عبر واتساب",
|
||||
"First 10 Opportunities أسبوعياً",
|
||||
"Proof Pack شهري",
|
||||
"Founder Shadow Board أسبوعي",
|
||||
"Revenue Leak Detector",
|
||||
],
|
||||
"timeline_ar": "شهري متجدد (ابدأ بـPilot 30 يوم)",
|
||||
"price_min_sar": 2999,
|
||||
"price_max_sar": 2999,
|
||||
"proof_metrics": [
|
||||
"monthly_pipeline_sar", "monthly_meetings",
|
||||
"monthly_revenue_influenced", "monthly_risks_blocked",
|
||||
],
|
||||
"upgrade_path": ["partnership_growth", "full_growth_control_tower"],
|
||||
},
|
||||
{
|
||||
"id": "partnership_growth",
|
||||
"name_ar": "Partnership Growth",
|
||||
"best_for_ar": "شركات تنمو عبر الشركاء/الوكالات/الموزعين",
|
||||
"services": [
|
||||
"partner_sprint",
|
||||
"meeting_booking_sprint",
|
||||
],
|
||||
"deliverables_ar": [
|
||||
"20 شريك محتمل + scorecard",
|
||||
"10 رسائل + drafts اجتماعات",
|
||||
"Referral Agreement Draft",
|
||||
"Partner-Proof Pack",
|
||||
],
|
||||
"timeline_ar": "14 يوم",
|
||||
"price_min_sar": 3000,
|
||||
"price_max_sar": 7500,
|
||||
"proof_metrics": [
|
||||
"partners_identified", "partner_meetings",
|
||||
"referral_revenue_sar",
|
||||
],
|
||||
"upgrade_path": ["full_growth_control_tower"],
|
||||
},
|
||||
{
|
||||
"id": "local_growth_os",
|
||||
"name_ar": "Local Growth OS",
|
||||
"best_for_ar": "عيادات / متاجر / فروع / خدمات محلية",
|
||||
"services": [
|
||||
"local_growth_os",
|
||||
"whatsapp_compliance_setup",
|
||||
"list_intelligence",
|
||||
],
|
||||
"deliverables_ar": [
|
||||
"Google Business reviews ledger + draft replies",
|
||||
"WhatsApp opt-in audit + templates",
|
||||
"Customer reactivation campaign drafts",
|
||||
"Branch-level Proof Pack",
|
||||
],
|
||||
"timeline_ar": "3 أسابيع",
|
||||
"price_min_sar": 999,
|
||||
"price_max_sar": 2999,
|
||||
"proof_metrics": [
|
||||
"reviews_handled", "opt_ins_collected",
|
||||
"customers_reactivated", "risks_blocked",
|
||||
],
|
||||
"upgrade_path": ["executive_growth_os"],
|
||||
},
|
||||
{
|
||||
"id": "full_growth_control_tower",
|
||||
"name_ar": "Full Growth Control Tower",
|
||||
"best_for_ar": "مؤسسات تريد تشغيل كامل على 30+ يوم",
|
||||
"services": [
|
||||
"growth_os_monthly",
|
||||
"list_intelligence",
|
||||
"first_10_opportunities_sprint",
|
||||
"partner_sprint",
|
||||
"executive_growth_brief",
|
||||
"linkedin_lead_gen_setup",
|
||||
],
|
||||
"deliverables_ar": [
|
||||
"كل خدمات Growth OS",
|
||||
"Partnership Sprint موازٍ",
|
||||
"LinkedIn Lead Gen campaign",
|
||||
"Founder Shadow Board",
|
||||
"Service Excellence weekly review",
|
||||
],
|
||||
"timeline_ar": "30 يوم — قابل للتجديد",
|
||||
"price_min_sar": 12000,
|
||||
"price_max_sar": 25000,
|
||||
"proof_metrics": [
|
||||
"monthly_pipeline_sar", "monthly_revenue_influenced",
|
||||
"partners_signed", "monthly_meetings",
|
||||
],
|
||||
"upgrade_path": [],
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def list_bundles() -> dict[str, Any]:
|
||||
return {
|
||||
"total": len(BUNDLES),
|
||||
"bundles": [dict(b) for b in BUNDLES],
|
||||
}
|
||||
|
||||
|
||||
def get_bundle(bundle_id: str) -> dict[str, Any] | None:
|
||||
return next((dict(b) for b in BUNDLES if b["id"] == bundle_id), None)
|
||||
|
||||
|
||||
def recommend_bundle(
|
||||
*,
|
||||
intent: str | None = None,
|
||||
has_contact_list: bool = False,
|
||||
is_agency: bool = False,
|
||||
is_local_business: bool = False,
|
||||
budget_sar: int = 1000,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Recommend the best-fit bundle deterministically.
|
||||
|
||||
Order of priority:
|
||||
agency → partnership_growth
|
||||
local business → local_growth_os
|
||||
has list → data_to_revenue
|
||||
monthly budget → executive_growth_os
|
||||
partnerships intent → partnership_growth
|
||||
default → growth_starter
|
||||
"""
|
||||
if is_agency:
|
||||
chosen = "partnership_growth"
|
||||
reason = "وكالة → Partnership Growth + ترقية لـ Agency Partner Program."
|
||||
elif is_local_business:
|
||||
chosen = "local_growth_os"
|
||||
reason = "نشاط محلي → Local Growth OS."
|
||||
elif has_contact_list:
|
||||
chosen = "data_to_revenue"
|
||||
reason = "العميل لديه قائمة → Data to Revenue."
|
||||
elif intent == "want_partnerships":
|
||||
chosen = "partnership_growth"
|
||||
reason = "هدف الشراكات → Partnership Growth."
|
||||
elif intent == "want_daily_growth" or budget_sar >= 2999:
|
||||
chosen = "executive_growth_os"
|
||||
reason = "تشغيل يومي/ميزانية شهرية → Executive Growth OS."
|
||||
elif budget_sar >= 12000:
|
||||
chosen = "full_growth_control_tower"
|
||||
reason = "ميزانية كبيرة → Full Growth Control Tower."
|
||||
else:
|
||||
chosen = "growth_starter"
|
||||
reason = "ابدأ بـ Growth Starter."
|
||||
|
||||
bundle = get_bundle(chosen)
|
||||
return {
|
||||
"recommended_bundle_id": chosen,
|
||||
"bundle": bundle,
|
||||
"reason_ar": reason,
|
||||
"approval_required": True,
|
||||
}
|
||||
@ -0,0 +1,94 @@
|
||||
"""Service orchestrator — runs the canonical service pipeline."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
# Canonical pipeline every service goes through.
|
||||
SERVICE_PIPELINE_STEPS: tuple[str, ...] = (
|
||||
"intake",
|
||||
"data_check",
|
||||
"targeting",
|
||||
"contactability",
|
||||
"strategy",
|
||||
"drafting",
|
||||
"approval",
|
||||
"execution_or_export",
|
||||
"tracking",
|
||||
"proof",
|
||||
"upsell",
|
||||
)
|
||||
|
||||
_STEP_LABELS_AR: dict[str, str] = {
|
||||
"intake": "جمع المدخلات",
|
||||
"data_check": "فحص جودة البيانات",
|
||||
"targeting": "تحديد الأهداف",
|
||||
"contactability": "تقييم إمكانية التواصل",
|
||||
"strategy": "صياغة الاستراتيجية",
|
||||
"drafting": "كتابة المسودات",
|
||||
"approval": "اعتماد بشري",
|
||||
"execution_or_export": "تنفيذ أو تصدير",
|
||||
"tracking": "متابعة النتائج",
|
||||
"proof": "Proof Pack",
|
||||
"upsell": "ترقية الخدمة",
|
||||
}
|
||||
|
||||
|
||||
def build_service_pipeline(
|
||||
service_id: str, *, customer_id: str = "",
|
||||
) -> dict[str, Any]:
|
||||
"""Build the canonical pipeline state for a service."""
|
||||
return {
|
||||
"service_id": service_id,
|
||||
"customer_id": customer_id,
|
||||
"current_step": "intake",
|
||||
"completed_steps": [],
|
||||
"steps": [
|
||||
{
|
||||
"step_id": s,
|
||||
"label_ar": _STEP_LABELS_AR.get(s, s),
|
||||
"completed": False,
|
||||
"approval_required": s in {
|
||||
"drafting", "approval", "execution_or_export",
|
||||
},
|
||||
}
|
||||
for s in SERVICE_PIPELINE_STEPS
|
||||
],
|
||||
"approval_required": True,
|
||||
"live_send_allowed": False,
|
||||
}
|
||||
|
||||
|
||||
def run_service_step(
|
||||
pipeline: dict[str, Any], *, step_id: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Mark the current (or supplied) step as run + advance the pipeline.
|
||||
|
||||
Does NOT execute any external action — only updates state.
|
||||
"""
|
||||
target = step_id or pipeline.get("current_step")
|
||||
steps = list(pipeline.get("steps", []))
|
||||
found = False
|
||||
for i, s in enumerate(steps):
|
||||
if s.get("step_id") == target:
|
||||
s["completed"] = True
|
||||
steps[i] = s
|
||||
found = True
|
||||
# Move to next step.
|
||||
if i + 1 < len(steps):
|
||||
pipeline["current_step"] = steps[i + 1]["step_id"]
|
||||
else:
|
||||
pipeline["current_step"] = "done"
|
||||
break
|
||||
|
||||
if not found:
|
||||
return {**pipeline, "error": f"unknown step: {target}"}
|
||||
|
||||
completed = [s["step_id"] for s in steps if s["completed"]]
|
||||
pipeline["steps"] = steps
|
||||
pipeline["completed_steps"] = completed
|
||||
pipeline["progress_pct"] = round(
|
||||
100 * len(completed) / max(1, len(steps)), 1,
|
||||
)
|
||||
return pipeline
|
||||
@ -0,0 +1,95 @@
|
||||
"""Session state — minimal in-memory state for an operator conversation."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
import uuid
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any
|
||||
|
||||
# Valid state transitions for the operator session.
|
||||
_VALID_STATES: tuple[str, ...] = (
|
||||
"new",
|
||||
"intent_classified",
|
||||
"intake_collecting",
|
||||
"intake_complete",
|
||||
"service_recommended",
|
||||
"workflow_running",
|
||||
"approval_pending",
|
||||
"approval_received",
|
||||
"executing",
|
||||
"proof_pending",
|
||||
"proof_delivered",
|
||||
"upsell_offered",
|
||||
"closed",
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class SessionState:
|
||||
"""A single operator conversation session."""
|
||||
session_id: str
|
||||
customer_id: str | None = None
|
||||
state: str = "new"
|
||||
intent: str | None = None
|
||||
recommended_service_id: str | None = None
|
||||
bundle_id: str | None = None
|
||||
intake_payload: dict[str, Any] = field(default_factory=dict)
|
||||
actions_pending_approval: list[dict[str, Any]] = field(default_factory=list)
|
||||
actions_approved: list[dict[str, Any]] = field(default_factory=list)
|
||||
actions_blocked: list[dict[str, Any]] = field(default_factory=list)
|
||||
proof_pack: dict[str, Any] | None = None
|
||||
upsell_offer: dict[str, Any] | None = None
|
||||
history: list[dict[str, Any]] = field(default_factory=list)
|
||||
created_at: float = field(default_factory=time.time)
|
||||
updated_at: float = field(default_factory=time.time)
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
return {
|
||||
"session_id": self.session_id,
|
||||
"customer_id": self.customer_id,
|
||||
"state": self.state,
|
||||
"intent": self.intent,
|
||||
"recommended_service_id": self.recommended_service_id,
|
||||
"bundle_id": self.bundle_id,
|
||||
"intake_payload": dict(self.intake_payload),
|
||||
"actions_pending_approval": list(self.actions_pending_approval),
|
||||
"actions_approved": list(self.actions_approved),
|
||||
"actions_blocked": list(self.actions_blocked),
|
||||
"proof_pack": self.proof_pack,
|
||||
"upsell_offer": self.upsell_offer,
|
||||
"history_len": len(self.history),
|
||||
"created_at": self.created_at,
|
||||
"updated_at": self.updated_at,
|
||||
}
|
||||
|
||||
|
||||
def build_new_session(customer_id: str | None = None) -> SessionState:
|
||||
"""Build a fresh session with a generated UUID."""
|
||||
return SessionState(
|
||||
session_id=str(uuid.uuid4()),
|
||||
customer_id=customer_id,
|
||||
)
|
||||
|
||||
|
||||
def transition_session(
|
||||
session: SessionState,
|
||||
*,
|
||||
new_state: str,
|
||||
note: str = "",
|
||||
) -> SessionState:
|
||||
"""Move the session to a new state with audit trail."""
|
||||
if new_state not in _VALID_STATES:
|
||||
raise ValueError(
|
||||
f"Unknown session state: {new_state}. "
|
||||
f"Valid: {', '.join(_VALID_STATES)}"
|
||||
)
|
||||
session.history.append({
|
||||
"from": session.state,
|
||||
"to": new_state,
|
||||
"note": note[:200],
|
||||
"ts": time.time(),
|
||||
})
|
||||
session.state = new_state
|
||||
session.updated_at = time.time()
|
||||
return session
|
||||
@ -0,0 +1,102 @@
|
||||
"""Tool action planner — plan + review actions before they hit Tool Gateway."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
# Tools that REQUIRE explicit human approval, no exceptions.
|
||||
_HIGH_RISK_TOOLS: frozenset[str] = frozenset({
|
||||
"whatsapp.send_message",
|
||||
"gmail.send",
|
||||
"calendar.insert_event",
|
||||
"moyasar.charge",
|
||||
"google_business.publish_review_reply",
|
||||
"social.publish_dm",
|
||||
"social.publish_post",
|
||||
})
|
||||
|
||||
# Tools that are safe in draft mode (still approval-required, never live-by-default).
|
||||
_DRAFT_SAFE_TOOLS: frozenset[str] = frozenset({
|
||||
"whatsapp.draft_message",
|
||||
"gmail.create_draft",
|
||||
"calendar.draft_event",
|
||||
"moyasar.create_invoice_draft",
|
||||
"moyasar.create_payment_link_draft",
|
||||
"google_business.draft_review_reply",
|
||||
"social.draft_post",
|
||||
})
|
||||
|
||||
# Tools never to plan, period.
|
||||
_FORBIDDEN_TOOLS: frozenset[str] = frozenset({
|
||||
"linkedin.scrape_profile",
|
||||
"linkedin.auto_dm",
|
||||
"linkedin.auto_connect",
|
||||
"social.scrape_followers",
|
||||
"phone.cold_call_unscripted",
|
||||
})
|
||||
|
||||
|
||||
def plan_tool_action(
|
||||
*,
|
||||
tool: str,
|
||||
payload: dict[str, Any] | None = None,
|
||||
customer_id: str | None = None,
|
||||
context: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Plan a tool action — does NOT execute. Returns the plan + safety verdict.
|
||||
|
||||
Verdicts:
|
||||
- "blocked" (tool is forbidden or unsafe)
|
||||
- "draft_only" (tool may run as draft, requires approval)
|
||||
- "approval_required"(tool requires human approval before execution)
|
||||
- "ready_for_gateway"(tool is safe internal — pass to Tool Gateway)
|
||||
"""
|
||||
payload = payload or {}
|
||||
context = context or {}
|
||||
tool_lc = (tool or "").strip().lower()
|
||||
|
||||
if tool_lc in _FORBIDDEN_TOOLS:
|
||||
return {
|
||||
"tool": tool, "verdict": "blocked",
|
||||
"reason_ar": "أداة محظورة (LinkedIn scraping/auto-DM/scraping social).",
|
||||
"live_send_allowed": False,
|
||||
}
|
||||
|
||||
if tool_lc in _HIGH_RISK_TOOLS:
|
||||
return {
|
||||
"tool": tool, "verdict": "approval_required",
|
||||
"reason_ar": (
|
||||
"أداة عالية المخاطرة — تحتاج اعتماد بشري + env flag مفعّل."
|
||||
),
|
||||
"live_send_allowed": False,
|
||||
}
|
||||
|
||||
if tool_lc in _DRAFT_SAFE_TOOLS:
|
||||
return {
|
||||
"tool": tool, "verdict": "draft_only",
|
||||
"reason_ar": "draft فقط — أرسل للمراجعة قبل الاعتماد.",
|
||||
"live_send_allowed": False,
|
||||
}
|
||||
|
||||
# Unknown tool — default to safest verdict.
|
||||
return {
|
||||
"tool": tool, "verdict": "approval_required",
|
||||
"reason_ar": "أداة غير مصنّفة — تحتاج مراجعة قبل التنفيذ.",
|
||||
"live_send_allowed": False,
|
||||
}
|
||||
|
||||
|
||||
def review_planned_action(plan: dict[str, Any]) -> dict[str, Any]:
|
||||
"""
|
||||
Quick safety review on an already-planned action. Returns updated plan.
|
||||
|
||||
Strips any 'live_send_allowed=True' and forces it back to False.
|
||||
"""
|
||||
out = dict(plan)
|
||||
out["live_send_allowed"] = False
|
||||
out["safety_reviewed"] = True
|
||||
if out.get("verdict") == "ready_for_gateway":
|
||||
# Even safe tools must be audited — promote to approval_required.
|
||||
out["verdict"] = "approval_required"
|
||||
return out
|
||||
@ -0,0 +1,94 @@
|
||||
"""Upsell engine — recommend the next service after current one delivers."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
# Mapping: completed_service → next_recommended_service.
|
||||
_UPSELL_MAP: dict[str, str] = {
|
||||
"free_growth_diagnostic": "first_10_opportunities_sprint",
|
||||
"list_intelligence": "growth_os_monthly",
|
||||
"first_10_opportunities_sprint": "growth_os_monthly",
|
||||
"self_growth_operator": "growth_os_monthly",
|
||||
"email_revenue_rescue": "growth_os_monthly",
|
||||
"meeting_booking_sprint": "growth_os_monthly",
|
||||
"partner_sprint": "agency_partner_program",
|
||||
"agency_partner_program": "growth_os_monthly",
|
||||
"whatsapp_compliance_setup": "growth_os_monthly",
|
||||
"linkedin_lead_gen_setup": "growth_os_monthly",
|
||||
"executive_growth_brief": "growth_os_monthly",
|
||||
"growth_os_monthly": "growth_os_monthly", # already at top — annual upgrade
|
||||
}
|
||||
|
||||
_UPSELL_PRICING_AR: dict[str, str] = {
|
||||
"first_10_opportunities_sprint": "499–1,500 ريال (Sprint)",
|
||||
"growth_os_monthly": "2,999 ريال شهرياً (أو سنوي بخصم 15%)",
|
||||
"agency_partner_program": "10,000–50,000 ريال (Setup) + Revenue Share",
|
||||
}
|
||||
|
||||
|
||||
def recommend_upsell_after_service(
|
||||
*,
|
||||
completed_service_id: str,
|
||||
pilot_metrics: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Recommend an upsell based on the completed service + metrics.
|
||||
|
||||
Strong outcomes (csat ≥ 8 + pipeline ≥ 25K OR meetings ≥ 2) → upsell now.
|
||||
Weak outcomes (pipeline < 5K + meetings = 0) → iterate, don't upsell.
|
||||
Otherwise: gentle upsell.
|
||||
"""
|
||||
next_id = _UPSELL_MAP.get(completed_service_id, "growth_os_monthly")
|
||||
metrics = pilot_metrics or {}
|
||||
pipeline_sar = float(metrics.get("pipeline_sar", 0))
|
||||
meetings = int(metrics.get("meetings", 0))
|
||||
csat = int(metrics.get("csat", 0))
|
||||
|
||||
if csat >= 8 and (pipeline_sar >= 25_000 or meetings >= 2):
|
||||
verdict = "upsell_now"
|
||||
urgency_ar = (
|
||||
"النتائج قوية — اعرض الترقية اليوم مع خصم سنوي 15%."
|
||||
)
|
||||
elif pipeline_sar < 5_000 and meetings == 0:
|
||||
verdict = "iterate_first"
|
||||
urgency_ar = (
|
||||
"النتائج ضعيفة هذه الجولة. اقترح زاوية مختلفة قبل الترقية."
|
||||
)
|
||||
else:
|
||||
verdict = "gentle_upsell"
|
||||
urgency_ar = (
|
||||
"النتائج واعدة. اعرض Pilot موسّع 30 يوم قبل الاشتراك الشهري."
|
||||
)
|
||||
|
||||
return {
|
||||
"completed_service_id": completed_service_id,
|
||||
"recommended_next_service_id": next_id,
|
||||
"verdict": verdict,
|
||||
"pricing_ar": _UPSELL_PRICING_AR.get(next_id, "حسب الحاجة"),
|
||||
"urgency_ar": urgency_ar,
|
||||
"approval_required": True,
|
||||
}
|
||||
|
||||
|
||||
def build_upsell_card(
|
||||
*,
|
||||
completed_service_id: str,
|
||||
pilot_metrics: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Build an Arabic upsell card to deliver after Proof Pack."""
|
||||
rec = recommend_upsell_after_service(
|
||||
completed_service_id=completed_service_id,
|
||||
pilot_metrics=pilot_metrics,
|
||||
)
|
||||
return {
|
||||
"type": "upsell",
|
||||
"title_ar": f"الترقية المقترحة بعد {completed_service_id}",
|
||||
"summary_ar": rec["urgency_ar"],
|
||||
"next_service_id": rec["recommended_next_service_id"],
|
||||
"pricing_ar": rec["pricing_ar"],
|
||||
"verdict": rec["verdict"],
|
||||
"buttons_ar": ["ابدأ الترقية", "اشرح أكثر", "لاحقاً"],
|
||||
"approval_required": True,
|
||||
"live_send_allowed": False,
|
||||
}
|
||||
@ -0,0 +1,75 @@
|
||||
"""WhatsApp renderer — convert cards/briefs to WhatsApp-ready format.
|
||||
|
||||
Drafts only. Never sends. Always emits buttons_ar capped at 3 (WhatsApp Reply
|
||||
Buttons limit) and Arabic body text.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
def render_card_for_whatsapp(card: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Render any decision card as a WhatsApp-style draft message."""
|
||||
title = str(card.get("title_ar", "")).strip()[:60]
|
||||
summary = str(card.get("summary_ar", "")).strip()[:300]
|
||||
why_now = str(card.get("why_now_ar", "")).strip()[:200]
|
||||
action = str(card.get("recommended_action_ar", "")).strip()[:200]
|
||||
risk = str(card.get("risk_level", "")).strip()
|
||||
buttons = list(card.get("buttons_ar", []))[:3]
|
||||
|
||||
body_lines: list[str] = [title]
|
||||
if summary:
|
||||
body_lines.append("")
|
||||
body_lines.append(summary)
|
||||
if why_now:
|
||||
body_lines.append("")
|
||||
body_lines.append(f"لماذا الآن: {why_now}")
|
||||
if action:
|
||||
body_lines.append(f"الإجراء المقترح: {action}")
|
||||
if risk:
|
||||
body_lines.append(f"المخاطرة: {risk}")
|
||||
if buttons:
|
||||
body_lines.append("")
|
||||
body_lines.append("أزرار: " + " | ".join(buttons))
|
||||
|
||||
return {
|
||||
"channel": "whatsapp",
|
||||
"kind": "card_draft",
|
||||
"body_ar": "\n".join(body_lines),
|
||||
"buttons_ar": buttons,
|
||||
"approval_required": True,
|
||||
"live_send_allowed": False,
|
||||
}
|
||||
|
||||
|
||||
def render_approval_card_for_whatsapp(
|
||||
card: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
"""Render an approval card specifically — guarantees the 3 standard buttons."""
|
||||
out = render_card_for_whatsapp(card)
|
||||
out["buttons_ar"] = card.get("buttons_ar") or ["اعتمد", "عدّل", "تخطي"]
|
||||
out["kind"] = "approval_card"
|
||||
return out
|
||||
|
||||
|
||||
def render_daily_brief_for_whatsapp(brief: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Render a CEO/Growth Manager daily brief as WhatsApp draft."""
|
||||
summary_lines = list(brief.get("summary_ar", []))[:8]
|
||||
decisions = list(brief.get("priority_decisions_ar", []))[:3]
|
||||
|
||||
body_lines = ["صباح الخير 👋", "", "أهم اليوم:"]
|
||||
body_lines.extend(f"• {line}" for line in summary_lines)
|
||||
if decisions:
|
||||
body_lines.append("")
|
||||
body_lines.append("3 قرارات تنتظر:")
|
||||
body_lines.extend(f"{i + 1}. {d}" for i, d in enumerate(decisions))
|
||||
|
||||
return {
|
||||
"channel": "whatsapp",
|
||||
"kind": "daily_brief_draft",
|
||||
"body_ar": "\n".join(body_lines),
|
||||
"buttons_ar": ["اعرض القرارات", "Proof Pack", "لاحقاً"],
|
||||
"approval_required": True,
|
||||
"live_send_allowed": False,
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
"""Workflow runner — advances service pipelines + checks completion."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from .service_orchestrator import (
|
||||
SERVICE_PIPELINE_STEPS,
|
||||
build_service_pipeline,
|
||||
run_service_step,
|
||||
)
|
||||
|
||||
|
||||
def build_workflow_state(service_id: str, *, customer_id: str = "") -> dict[str, Any]:
|
||||
"""Initialize a new workflow state for a service."""
|
||||
pipeline = build_service_pipeline(service_id, customer_id=customer_id)
|
||||
return {
|
||||
"service_id": service_id,
|
||||
"customer_id": customer_id,
|
||||
"pipeline": pipeline,
|
||||
"human_approvals_received": 0,
|
||||
"human_approvals_pending": 0,
|
||||
"blocked_actions": 0,
|
||||
}
|
||||
|
||||
|
||||
def advance_workflow(
|
||||
workflow_state: dict[str, Any], *, step_id: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Advance the underlying pipeline by one step."""
|
||||
pipeline = workflow_state.get("pipeline") or build_service_pipeline(
|
||||
str(workflow_state.get("service_id", "")),
|
||||
)
|
||||
pipeline = run_service_step(pipeline, step_id=step_id)
|
||||
workflow_state["pipeline"] = pipeline
|
||||
return workflow_state
|
||||
|
||||
|
||||
def is_workflow_complete(workflow_state: dict[str, Any]) -> bool:
|
||||
"""True iff all canonical steps have run."""
|
||||
pipeline = workflow_state.get("pipeline", {})
|
||||
completed = pipeline.get("completed_steps", [])
|
||||
return len(completed) >= len(SERVICE_PIPELINE_STEPS)
|
||||
@ -0,0 +1,67 @@
|
||||
"""Revenue Company OS — multi-channel command feed + Revenue Work Units + self-improvement.
|
||||
|
||||
Sits above platform_services + intelligence_layer + service_tower:
|
||||
- event_to_card: any event → Arabic decision card
|
||||
- command_feed_engine: aggregate cards across channels for the day
|
||||
- action_graph: signal → action → outcome → proof
|
||||
- revenue_work_units: Dealix's unit of measurement (Salesforce-inspired)
|
||||
- channel_health: cross-channel reputation snapshot
|
||||
- opportunity_factory: turn signals into opportunity cards
|
||||
- service_factory: instantiate a service from a customer + intent
|
||||
- proof_ledger: revenue-tier proof aggregator (NOT platform_services.proof_ledger)
|
||||
- growth_memory: long-term cross-customer learning store
|
||||
- self_improvement_loop: weekly review + recommendations
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from .action_graph import (
|
||||
REVENUE_EDGE_TYPES,
|
||||
RevenueActionGraph,
|
||||
build_revenue_action_graph_demo,
|
||||
)
|
||||
from .channel_health import build_channel_health_snapshot
|
||||
from .command_feed_engine import (
|
||||
build_command_feed_demo as revenue_os_command_feed_demo,
|
||||
build_command_feed_for_customer,
|
||||
)
|
||||
from .event_to_card import EVENT_TO_CARD_TYPES, build_card_from_event
|
||||
from .growth_memory import GrowthMemory, build_growth_memory_demo
|
||||
from .opportunity_factory import build_opportunity_factory_demo
|
||||
from .proof_ledger import (
|
||||
RevenueProofLedger,
|
||||
build_revenue_proof_ledger_demo,
|
||||
)
|
||||
from .revenue_work_units import (
|
||||
REVENUE_WORK_UNIT_TYPES,
|
||||
aggregate_work_units,
|
||||
build_revenue_work_unit,
|
||||
)
|
||||
from .self_improvement_loop import build_weekly_self_improvement_report
|
||||
from .service_factory import build_service_factory_demo, instantiate_service
|
||||
|
||||
__all__ = [
|
||||
# action_graph
|
||||
"REVENUE_EDGE_TYPES", "RevenueActionGraph",
|
||||
"build_revenue_action_graph_demo",
|
||||
# channel_health
|
||||
"build_channel_health_snapshot",
|
||||
# command_feed_engine
|
||||
"build_command_feed_for_customer",
|
||||
"revenue_os_command_feed_demo",
|
||||
# event_to_card
|
||||
"EVENT_TO_CARD_TYPES", "build_card_from_event",
|
||||
# growth_memory
|
||||
"GrowthMemory", "build_growth_memory_demo",
|
||||
# opportunity_factory
|
||||
"build_opportunity_factory_demo",
|
||||
# proof_ledger
|
||||
"RevenueProofLedger", "build_revenue_proof_ledger_demo",
|
||||
# revenue_work_units
|
||||
"REVENUE_WORK_UNIT_TYPES", "aggregate_work_units",
|
||||
"build_revenue_work_unit",
|
||||
# self_improvement_loop
|
||||
"build_weekly_self_improvement_report",
|
||||
# service_factory
|
||||
"build_service_factory_demo", "instantiate_service",
|
||||
]
|
||||
@ -0,0 +1,123 @@
|
||||
"""Revenue Action Graph — signal → action → outcome → proof relationships."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
import uuid
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any
|
||||
|
||||
# 14 typed edges Dealix records to learn what works.
|
||||
REVENUE_EDGE_TYPES: tuple[str, ...] = (
|
||||
"signal_created_opportunity",
|
||||
"opportunity_drafted_message",
|
||||
"message_triggered_reply",
|
||||
"reply_led_to_meeting",
|
||||
"meeting_led_to_proposal",
|
||||
"proposal_led_to_payment",
|
||||
"partner_introduced_customer",
|
||||
"review_created_recovery_task",
|
||||
"approval_allowed_send",
|
||||
"blocked_action_prevented_risk",
|
||||
"list_intel_top50_targets",
|
||||
"service_completed_generated_proof",
|
||||
"proof_triggered_upsell",
|
||||
"upsell_converted_to_subscription",
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class RevenueActionGraph:
|
||||
"""In-memory revenue action graph. Production = Supabase + pgvector."""
|
||||
edges: list[dict[str, Any]] = field(default_factory=list)
|
||||
|
||||
def add_edge(
|
||||
self,
|
||||
*,
|
||||
edge_type: str,
|
||||
src_id: str,
|
||||
dst_id: str,
|
||||
customer_id: str = "",
|
||||
weight: float = 1.0,
|
||||
metadata: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Add a typed edge. Validates edge_type."""
|
||||
if edge_type not in REVENUE_EDGE_TYPES:
|
||||
raise ValueError(
|
||||
f"Unknown edge_type: {edge_type}. "
|
||||
f"Valid: {', '.join(REVENUE_EDGE_TYPES)}"
|
||||
)
|
||||
edge: dict[str, Any] = {
|
||||
"edge_id": str(uuid.uuid4()),
|
||||
"edge_type": edge_type,
|
||||
"src_id": src_id,
|
||||
"dst_id": dst_id,
|
||||
"customer_id": customer_id,
|
||||
"weight": float(weight),
|
||||
"metadata": dict(metadata or {}),
|
||||
"ts": time.time(),
|
||||
}
|
||||
self.edges.append(edge)
|
||||
return edge
|
||||
|
||||
def what_works_for_customer(self, customer_id: str) -> dict[str, Any]:
|
||||
"""Aggregate edges for a customer → what's working."""
|
||||
edges = [e for e in self.edges if e["customer_id"] == customer_id]
|
||||
by_type: dict[str, int] = {}
|
||||
for e in edges:
|
||||
by_type[e["edge_type"]] = by_type.get(e["edge_type"], 0) + 1
|
||||
|
||||
# Score: weighted edge counts. Outcome edges weigh more.
|
||||
outcome_edges = {
|
||||
"proposal_led_to_payment": 5,
|
||||
"upsell_converted_to_subscription": 5,
|
||||
"reply_led_to_meeting": 3,
|
||||
"meeting_led_to_proposal": 3,
|
||||
"blocked_action_prevented_risk": 2,
|
||||
}
|
||||
score = sum(by_type.get(e, 0) * w for e, w in outcome_edges.items())
|
||||
|
||||
return {
|
||||
"customer_id": customer_id,
|
||||
"total_edges": len(edges),
|
||||
"by_type": by_type,
|
||||
"outcome_score": score,
|
||||
}
|
||||
|
||||
|
||||
def build_revenue_action_graph_demo() -> dict[str, Any]:
|
||||
"""Demo graph with realistic edges across 2 customers."""
|
||||
g = RevenueActionGraph()
|
||||
# Customer A — full funnel
|
||||
g.add_edge(edge_type="signal_created_opportunity",
|
||||
src_id="signal_1", dst_id="opp_1", customer_id="cust_A")
|
||||
g.add_edge(edge_type="opportunity_drafted_message",
|
||||
src_id="opp_1", dst_id="msg_1", customer_id="cust_A")
|
||||
g.add_edge(edge_type="approval_allowed_send",
|
||||
src_id="msg_1", dst_id="msg_1_approved", customer_id="cust_A")
|
||||
g.add_edge(edge_type="message_triggered_reply",
|
||||
src_id="msg_1_approved", dst_id="reply_1", customer_id="cust_A")
|
||||
g.add_edge(edge_type="reply_led_to_meeting",
|
||||
src_id="reply_1", dst_id="meeting_1", customer_id="cust_A")
|
||||
g.add_edge(edge_type="meeting_led_to_proposal",
|
||||
src_id="meeting_1", dst_id="proposal_1", customer_id="cust_A")
|
||||
g.add_edge(edge_type="proposal_led_to_payment",
|
||||
src_id="proposal_1", dst_id="payment_499",
|
||||
customer_id="cust_A", weight=499)
|
||||
g.add_edge(edge_type="service_completed_generated_proof",
|
||||
src_id="payment_499", dst_id="proof_1", customer_id="cust_A")
|
||||
g.add_edge(edge_type="proof_triggered_upsell",
|
||||
src_id="proof_1", dst_id="upsell_1", customer_id="cust_A")
|
||||
# Customer B — risk path
|
||||
g.add_edge(edge_type="blocked_action_prevented_risk",
|
||||
src_id="msg_2", dst_id="cold_wa_blocked", customer_id="cust_B")
|
||||
g.add_edge(edge_type="review_created_recovery_task",
|
||||
src_id="review_2", dst_id="recovery_1", customer_id="cust_B")
|
||||
g.add_edge(edge_type="partner_introduced_customer",
|
||||
src_id="partner_1", dst_id="customer_B_intro",
|
||||
customer_id="cust_B")
|
||||
return {
|
||||
"edges": list(g.edges),
|
||||
"summary_a": g.what_works_for_customer("cust_A"),
|
||||
"summary_b": g.what_works_for_customer("cust_B"),
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
"""Channel health — cross-channel reputation snapshot for the customer."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from auto_client_acquisition.targeting_os.reputation_guard import (
|
||||
calculate_channel_reputation,
|
||||
)
|
||||
|
||||
|
||||
def build_channel_health_snapshot(
|
||||
*,
|
||||
metrics_per_channel: dict[str, dict[str, float]] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Build a single snapshot of channel health across channels.
|
||||
|
||||
Input:
|
||||
metrics_per_channel = {
|
||||
"email": {"bounce_rate": 0.005, "complaint_rate": 0.0001, ...},
|
||||
"whatsapp": {"block_rate": 0.01, "report_rate": 0.001, ...},
|
||||
...
|
||||
}
|
||||
"""
|
||||
metrics_per_channel = metrics_per_channel or {
|
||||
"email": {"bounce_rate": 0.005, "complaint_rate": 0.0001,
|
||||
"opt_out_rate": 0.01, "reply_rate": 0.04},
|
||||
"whatsapp": {"block_rate": 0.005, "report_rate": 0.001,
|
||||
"opt_out_rate": 0.02, "reply_rate": 0.10},
|
||||
"linkedin": {"connection_decline": 0.25},
|
||||
}
|
||||
|
||||
snapshot: dict[str, Any] = {}
|
||||
for channel, metrics in metrics_per_channel.items():
|
||||
snapshot[channel] = calculate_channel_reputation(
|
||||
metrics, channel=channel,
|
||||
)
|
||||
|
||||
overall_score = (
|
||||
sum(int(s.get("score", 0) or 0) for s in snapshot.values())
|
||||
/ max(1, len(snapshot))
|
||||
)
|
||||
risky = [c for c, s in snapshot.items() if s.get("verdict") == "pause"]
|
||||
|
||||
return {
|
||||
"channels": snapshot,
|
||||
"overall_score": round(overall_score, 1),
|
||||
"channels_at_risk": risky,
|
||||
"summary_ar": [
|
||||
f"الدرجة الكلية: {round(overall_score, 1)} / 100",
|
||||
(
|
||||
f"قنوات في حالة pause: {', '.join(risky)}."
|
||||
if risky else
|
||||
"جميع القنوات صحية الآن."
|
||||
),
|
||||
],
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
"""Command Feed engine — aggregates events across channels into a daily feed."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from .event_to_card import build_card_from_event
|
||||
|
||||
|
||||
def build_command_feed_for_customer(
|
||||
*,
|
||||
customer_id: str,
|
||||
events: list[dict[str, Any]] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Build today's Arabic command feed for a customer."""
|
||||
events = events or []
|
||||
cards = [build_card_from_event(e) for e in events]
|
||||
by_type: dict[str, int] = {}
|
||||
by_risk: dict[str, int] = {"low": 0, "medium": 0, "high": 0}
|
||||
for c in cards:
|
||||
by_type[c["type"]] = by_type.get(c["type"], 0) + 1
|
||||
by_risk[c["risk_level"]] = by_risk.get(c["risk_level"], 0) + 1
|
||||
|
||||
# Sort: high risk first, then medium, then low. Stable.
|
||||
risk_order = {"high": 0, "medium": 1, "low": 2}
|
||||
cards_sorted = sorted(cards, key=lambda c: risk_order.get(c["risk_level"], 9))
|
||||
|
||||
return {
|
||||
"customer_id": customer_id,
|
||||
"feed_size": len(cards),
|
||||
"by_type": by_type,
|
||||
"by_risk": by_risk,
|
||||
"cards": cards_sorted,
|
||||
"approval_required": True,
|
||||
}
|
||||
|
||||
|
||||
def build_command_feed_demo() -> dict[str, Any]:
|
||||
"""Demo feed with 8 synthetic events across all channels."""
|
||||
demo_events = [
|
||||
{"event_type": "email.received", "customer_id": "demo",
|
||||
"payload": {"from": "ali@example.sa", "subject": "نطلب عرض"}},
|
||||
{"event_type": "whatsapp.reply_received", "customer_id": "demo",
|
||||
"payload": {"text": "شكرًا، أبغى أعرف باقات الشركات"}},
|
||||
{"event_type": "form.submitted", "customer_id": "demo",
|
||||
"payload": {"company": "شركة نمو", "role": "Head of Sales"}},
|
||||
{"event_type": "review.created", "customer_id": "demo",
|
||||
"payload": {"rating": 2, "text": "تأخير في الرد"}},
|
||||
{"event_type": "payment.link_created", "customer_id": "demo",
|
||||
"payload": {"amount_sar": 499, "description": "Pilot 7d"}},
|
||||
{"event_type": "risk.blocked", "customer_id": "demo",
|
||||
"payload": {"reason_ar": "محاولة cold WhatsApp بدون opt-in"}},
|
||||
{"event_type": "partner.suggested", "customer_id": "demo",
|
||||
"payload": {"partner_type": "agency",
|
||||
"reason_ar": "وكالة B2B لديها 20 عميل في قطاع التدريب"}},
|
||||
{"event_type": "service.completed", "customer_id": "demo",
|
||||
"payload": {"service_id": "first_10_opportunities_sprint"}},
|
||||
]
|
||||
return build_command_feed_for_customer(
|
||||
customer_id="demo", events=demo_events,
|
||||
)
|
||||
@ -0,0 +1,172 @@
|
||||
"""Event → Card converter — every channel event becomes an Arabic decision card."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
# Each event_type → card_type Dealix renders.
|
||||
EVENT_TO_CARD_TYPES: dict[str, str] = {
|
||||
"email.received": "email_lead",
|
||||
"whatsapp.reply_received": "whatsapp_reply",
|
||||
"form.submitted": "opportunity",
|
||||
"lead.uploaded": "list_intake",
|
||||
"meeting.drafted": "meeting_prep",
|
||||
"meeting.completed": "meeting_outcome",
|
||||
"payment.link_created": "payment",
|
||||
"partner.suggested": "partner_suggestion",
|
||||
"review.created": "review_response",
|
||||
"social.comment_received": "social_signal",
|
||||
"proof.generated": "proof_pack",
|
||||
"risk.blocked": "risk_alert",
|
||||
"service.completed": "service_outcome",
|
||||
}
|
||||
|
||||
|
||||
def build_card_from_event(event: dict[str, Any]) -> dict[str, Any]:
|
||||
"""
|
||||
Convert a typed event into an Arabic decision card.
|
||||
|
||||
Returns a dict with title_ar/summary_ar/why_now/recommended_action_ar/
|
||||
risk_level/buttons_ar (≤3)/approval_required/live_send_allowed=False.
|
||||
"""
|
||||
event_type = str(event.get("event_type", ""))
|
||||
payload = dict(event.get("payload", {}) or {})
|
||||
customer_id = event.get("customer_id")
|
||||
|
||||
card_type = EVENT_TO_CARD_TYPES.get(event_type, "action_required")
|
||||
|
||||
base = {
|
||||
"type": card_type,
|
||||
"event_type": event_type,
|
||||
"customer_id": customer_id,
|
||||
"approval_required": True,
|
||||
"live_send_allowed": False,
|
||||
"buttons_ar": ["اعتمد", "عدّل", "تخطي"],
|
||||
}
|
||||
|
||||
if event_type == "email.received":
|
||||
return {
|
||||
**base,
|
||||
"title_ar": "إيميل جديد يحتوي إشارة شراء",
|
||||
"summary_ar": (
|
||||
f"من: {payload.get('from', '?')}. "
|
||||
f"الموضوع: {payload.get('subject', '?')}."
|
||||
),
|
||||
"why_now_ar": "ينتظر رداً منذ آخر تفاعل.",
|
||||
"recommended_action_ar": "جهّز رد عربي + احجز اجتماع",
|
||||
"risk_level": "low",
|
||||
}
|
||||
|
||||
if event_type == "whatsapp.reply_received":
|
||||
return {
|
||||
**base,
|
||||
"title_ar": "رد واتساب من Lead",
|
||||
"summary_ar": (
|
||||
f"المحتوى: {str(payload.get('text', ''))[:120]}."
|
||||
),
|
||||
"why_now_ar": "اهتمام نشط — احفظ الزخم.",
|
||||
"recommended_action_ar": "اعتمد رد قصير + لا ترسل عرض PDF كامل",
|
||||
"risk_level": "low",
|
||||
}
|
||||
|
||||
if event_type == "form.submitted":
|
||||
return {
|
||||
**base,
|
||||
"title_ar": "Lead جديد من نموذج الموقع",
|
||||
"summary_ar": (
|
||||
f"الشركة: {payload.get('company', '?')}. "
|
||||
f"الدور: {payload.get('role', '?')}."
|
||||
),
|
||||
"why_now_ar": "Inbound lead — أعلى أولوية اليوم.",
|
||||
"recommended_action_ar": "اعتمد رسالة شكر + احجز ديمو 12 دقيقة",
|
||||
"risk_level": "low",
|
||||
}
|
||||
|
||||
if event_type == "review.created":
|
||||
rating = int(payload.get("rating", 5) or 5)
|
||||
return {
|
||||
**base,
|
||||
"title_ar": f"تقييم جديد — {rating} نجوم",
|
||||
"summary_ar": str(payload.get("text", ""))[:200],
|
||||
"why_now_ar": "السمعة المحلية حساسة — لا تتأخر.",
|
||||
"recommended_action_ar": (
|
||||
"رد علني قصير + تواصل خاص لتفاصيل."
|
||||
if rating < 3 else
|
||||
"شكر علني + سؤال ما الذي أعجبهم تحديداً."
|
||||
),
|
||||
"risk_level": "high" if rating < 3 else "low",
|
||||
}
|
||||
|
||||
if event_type == "payment.link_created":
|
||||
return {
|
||||
**base,
|
||||
"title_ar": "رابط دفع جاهز",
|
||||
"summary_ar": (
|
||||
f"المبلغ: {payload.get('amount_sar', '?')} ريال — "
|
||||
f"{payload.get('description', '')}."
|
||||
),
|
||||
"why_now_ar": "العميل وافق — أرسل الرابط بعد المراجعة.",
|
||||
"recommended_action_ar": "راجع المبلغ ثم أرسل من Moyasar dashboard",
|
||||
"risk_level": "medium",
|
||||
}
|
||||
|
||||
if event_type == "risk.blocked":
|
||||
return {
|
||||
**base,
|
||||
"title_ar": "تنبيه: تم منع فعل خطر تلقائياً",
|
||||
"summary_ar": str(payload.get("reason_ar", ""))[:200],
|
||||
"why_now_ar": "حماية القناة من الحظر/المخالفة.",
|
||||
"recommended_action_ar": "راجع السياسة + جهّز بديل آمن",
|
||||
"risk_level": "high",
|
||||
"buttons_ar": ["فهم", "اعرض البديل", "أرشف"],
|
||||
}
|
||||
|
||||
if event_type == "partner.suggested":
|
||||
return {
|
||||
**base,
|
||||
"title_ar": "اقتراح شريك جديد",
|
||||
"summary_ar": (
|
||||
f"النوع: {payload.get('partner_type', '?')}. "
|
||||
f"السبب: {payload.get('reason_ar', '')[:120]}."
|
||||
),
|
||||
"why_now_ar": "نقطة تكامل واضحة + قاعدة عملاء مشتركة.",
|
||||
"recommended_action_ar": "اكتب رسالة warm + احجز مكالمة 20 دقيقة",
|
||||
"risk_level": "low",
|
||||
}
|
||||
|
||||
if event_type == "meeting.drafted":
|
||||
return {
|
||||
**base,
|
||||
"title_ar": "مسودة اجتماع جاهزة",
|
||||
"summary_ar": (
|
||||
f"مع: {payload.get('with_company', '?')} — "
|
||||
f"{payload.get('proposed_time', 'الوقت المقترح')}"
|
||||
),
|
||||
"why_now_ar": "اعتمد المسودة لإرسال الدعوة.",
|
||||
"recommended_action_ar": "راجع الـ agenda + اعتمد",
|
||||
"risk_level": "low",
|
||||
}
|
||||
|
||||
if event_type == "service.completed":
|
||||
return {
|
||||
**base,
|
||||
"title_ar": "خدمة اكتملت — Proof Pack جاهز",
|
||||
"summary_ar": (
|
||||
f"الخدمة: {payload.get('service_id', '?')}. "
|
||||
"Proof Pack + توصية بالخطوة التالية معدّة."
|
||||
),
|
||||
"why_now_ar": "وقت الترقية بينما النتائج طازجة.",
|
||||
"recommended_action_ar": "اعتمد Proof Pack + ابدأ Upsell",
|
||||
"risk_level": "low",
|
||||
"buttons_ar": ["اعتمد Proof", "ابدأ Upsell", "لاحقاً"],
|
||||
}
|
||||
|
||||
# Default fallback.
|
||||
return {
|
||||
**base,
|
||||
"title_ar": f"حدث: {event_type}",
|
||||
"summary_ar": str(payload)[:200],
|
||||
"why_now_ar": "حدث جديد يحتاج مراجعة.",
|
||||
"recommended_action_ar": "افتح للمراجعة",
|
||||
"risk_level": "low",
|
||||
}
|
||||
@ -0,0 +1,108 @@
|
||||
"""Growth memory — long-term cross-customer learning store (anonymized aggregates)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any
|
||||
|
||||
|
||||
@dataclass
|
||||
class GrowthMemory:
|
||||
"""Cross-customer aggregates Dealix learns from (anonymized + bucketed)."""
|
||||
sector_message_winrate: dict[str, dict[str, float]] = field(default_factory=dict)
|
||||
sector_channel_winrate: dict[str, dict[str, float]] = field(default_factory=dict)
|
||||
common_objections: dict[str, int] = field(default_factory=dict)
|
||||
blocked_action_reasons: dict[str, int] = field(default_factory=dict)
|
||||
successful_playbooks: list[dict[str, Any]] = field(default_factory=list)
|
||||
|
||||
def record_message_outcome(
|
||||
self, *, sector: str, message_id: str, won: bool,
|
||||
) -> None:
|
||||
bucket = self.sector_message_winrate.setdefault(sector, {})
|
||||
# rolling success/fail count stored as floats in [0..1]
|
||||
prev = bucket.get(message_id, 0.5)
|
||||
bucket[message_id] = round((prev + (1.0 if won else 0.0)) / 2.0, 3)
|
||||
|
||||
def record_channel_outcome(
|
||||
self, *, sector: str, channel: str, won: bool,
|
||||
) -> None:
|
||||
bucket = self.sector_channel_winrate.setdefault(sector, {})
|
||||
prev = bucket.get(channel, 0.5)
|
||||
bucket[channel] = round((prev + (1.0 if won else 0.0)) / 2.0, 3)
|
||||
|
||||
def record_objection(self, label: str) -> None:
|
||||
self.common_objections[label] = self.common_objections.get(label, 0) + 1
|
||||
|
||||
def record_blocked_reason(self, reason: str) -> None:
|
||||
self.blocked_action_reasons[reason] = (
|
||||
self.blocked_action_reasons.get(reason, 0) + 1
|
||||
)
|
||||
|
||||
def append_successful_playbook(
|
||||
self, *, sector: str, name: str, win_rate: float,
|
||||
) -> None:
|
||||
self.successful_playbooks.append({
|
||||
"ts": time.time(),
|
||||
"sector": sector,
|
||||
"name": name,
|
||||
"win_rate": float(win_rate),
|
||||
})
|
||||
|
||||
def best_message_for_sector(self, sector: str) -> dict[str, Any]:
|
||||
bucket = self.sector_message_winrate.get(sector, {})
|
||||
if not bucket:
|
||||
return {"sector": sector, "best_message_id": None, "win_rate": 0.0}
|
||||
best = max(bucket.items(), key=lambda x: x[1])
|
||||
return {"sector": sector, "best_message_id": best[0], "win_rate": best[1]}
|
||||
|
||||
def best_channel_for_sector(self, sector: str) -> dict[str, Any]:
|
||||
bucket = self.sector_channel_winrate.get(sector, {})
|
||||
if not bucket:
|
||||
return {"sector": sector, "best_channel": None, "win_rate": 0.0}
|
||||
best = max(bucket.items(), key=lambda x: x[1])
|
||||
return {"sector": sector, "best_channel": best[0], "win_rate": best[1]}
|
||||
|
||||
def summary(self) -> dict[str, Any]:
|
||||
return {
|
||||
"sector_message_winrate": {
|
||||
k: dict(v) for k, v in self.sector_message_winrate.items()
|
||||
},
|
||||
"sector_channel_winrate": {
|
||||
k: dict(v) for k, v in self.sector_channel_winrate.items()
|
||||
},
|
||||
"top_objections": sorted(
|
||||
self.common_objections.items(),
|
||||
key=lambda x: -x[1],
|
||||
)[:5],
|
||||
"top_blocked_reasons": sorted(
|
||||
self.blocked_action_reasons.items(),
|
||||
key=lambda x: -x[1],
|
||||
)[:5],
|
||||
"successful_playbooks": self.successful_playbooks[-5:],
|
||||
}
|
||||
|
||||
|
||||
def build_growth_memory_demo() -> dict[str, Any]:
|
||||
"""Build a demo memory with sample aggregates."""
|
||||
g = GrowthMemory()
|
||||
g.record_message_outcome(sector="training", message_id="msg_warm_intro", won=True)
|
||||
g.record_message_outcome(sector="training", message_id="msg_warm_intro", won=True)
|
||||
g.record_message_outcome(sector="training", message_id="msg_cold_pitch", won=False)
|
||||
g.record_channel_outcome(sector="training", channel="email", won=True)
|
||||
g.record_channel_outcome(sector="training", channel="email", won=True)
|
||||
g.record_channel_outcome(sector="training", channel="linkedin_lead_form", won=True)
|
||||
g.record_objection("price")
|
||||
g.record_objection("timing")
|
||||
g.record_objection("price")
|
||||
g.record_blocked_reason("cold_whatsapp")
|
||||
g.record_blocked_reason("cold_whatsapp")
|
||||
g.record_blocked_reason("payload_contains_secret")
|
||||
g.append_successful_playbook(
|
||||
sector="training", name="warm_intro_with_proof", win_rate=0.42,
|
||||
)
|
||||
return {
|
||||
"summary": g.summary(),
|
||||
"best_message_training": g.best_message_for_sector("training"),
|
||||
"best_channel_training": g.best_channel_for_sector("training"),
|
||||
}
|
||||
@ -0,0 +1,54 @@
|
||||
"""Opportunity factory — turn signals into opportunity cards using Targeting OS."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from auto_client_acquisition.targeting_os import (
|
||||
map_buying_committee,
|
||||
recommend_accounts,
|
||||
)
|
||||
|
||||
|
||||
def build_opportunity_factory_demo(
|
||||
*,
|
||||
sector: str = "training",
|
||||
city: str = "Riyadh",
|
||||
limit: int = 5,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Build demo opportunities for a (sector, city).
|
||||
|
||||
Each opportunity includes account fit + buying committee + recommended channel.
|
||||
"""
|
||||
accounts_data = recommend_accounts(
|
||||
sector=sector, city=city, limit=limit,
|
||||
)
|
||||
committee = map_buying_committee(sector=sector, company_size="small")
|
||||
|
||||
enriched = []
|
||||
for acct in accounts_data["accounts"]:
|
||||
enriched.append({
|
||||
"company": acct.get("name"),
|
||||
"fit_score": acct.get("fit_score"),
|
||||
"tier": acct.get("tier"),
|
||||
"why_now_ar": acct.get("why_now_ar"),
|
||||
"best_angle_ar": acct.get("best_angle_ar"),
|
||||
"recommended_channel": acct.get("recommended_channel"),
|
||||
"primary_decision_maker": committee["primary_decision_maker"],
|
||||
"approval_required": True,
|
||||
"live_send_allowed": False,
|
||||
})
|
||||
|
||||
return {
|
||||
"sector": sector,
|
||||
"city": city,
|
||||
"count": len(enriched),
|
||||
"opportunities": enriched,
|
||||
"buying_committee_template": committee,
|
||||
"do_not_do_ar": [
|
||||
"لا scraping LinkedIn ولا auto-DM.",
|
||||
"لا cold WhatsApp.",
|
||||
"لا تواصل بدون موافقة المالك.",
|
||||
],
|
||||
}
|
||||
@ -0,0 +1,130 @@
|
||||
"""Revenue Proof Ledger — revenue-tier proof aggregator across all services.
|
||||
|
||||
Distinct from `platform_services.proof_ledger`: this aggregates Revenue Work
|
||||
Units + Action Graph edges into a customer-facing scoreboard.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any
|
||||
|
||||
from .revenue_work_units import REVENUE_WORK_UNIT_TYPES, aggregate_work_units
|
||||
|
||||
|
||||
@dataclass
|
||||
class RevenueProofLedger:
|
||||
"""In-memory revenue proof ledger. Production = Supabase append-only."""
|
||||
work_units: list[dict[str, Any]] = field(default_factory=list)
|
||||
notable_events: list[dict[str, Any]] = field(default_factory=list)
|
||||
|
||||
def append_work_unit(self, unit: dict[str, Any]) -> None:
|
||||
"""Append an RWU after validating its type."""
|
||||
ut = str(unit.get("unit_type", ""))
|
||||
if ut not in REVENUE_WORK_UNIT_TYPES:
|
||||
raise ValueError(f"Unknown RWU type: {ut}")
|
||||
self.work_units.append(dict(unit))
|
||||
|
||||
def append_notable_event(
|
||||
self, *, event_type: str, summary_ar: str, customer_id: str = "",
|
||||
) -> None:
|
||||
self.notable_events.append({
|
||||
"ts": time.time(),
|
||||
"event_type": event_type,
|
||||
"summary_ar": summary_ar[:200],
|
||||
"customer_id": customer_id,
|
||||
})
|
||||
|
||||
def summary_for_customer(self, customer_id: str) -> dict[str, Any]:
|
||||
"""Build the customer-facing Arabic Proof scoreboard."""
|
||||
units = [u for u in self.work_units
|
||||
if u.get("customer_id") == customer_id]
|
||||
agg = aggregate_work_units(units)
|
||||
|
||||
opps = agg["by_type"].get("opportunity_created", 0)
|
||||
approvals = agg["by_type"].get("approval_collected", 0)
|
||||
meetings = agg["by_type"].get("meeting_drafted", 0)
|
||||
meetings_held = agg["by_type"].get("meeting_held", 0)
|
||||
risks_blocked = agg["risks_blocked"]
|
||||
revenue = agg["total_revenue_influenced_sar"]
|
||||
|
||||
events_for_customer = [
|
||||
e for e in self.notable_events
|
||||
if e.get("customer_id") == customer_id
|
||||
]
|
||||
|
||||
return {
|
||||
"customer_id": customer_id,
|
||||
"totals": {
|
||||
"opportunities_created": opps,
|
||||
"approvals_collected": approvals,
|
||||
"meetings_drafted": meetings,
|
||||
"meetings_held": meetings_held,
|
||||
"risks_blocked": risks_blocked,
|
||||
"revenue_influenced_sar": revenue,
|
||||
},
|
||||
"summary_ar": [
|
||||
f"الفرص: {opps} | الاعتمادات: {approvals}.",
|
||||
f"الاجتماعات: {meetings} drafted, {meetings_held} held.",
|
||||
f"مخاطر منعت: {risks_blocked}.",
|
||||
f"إيراد متأثر: {revenue:.0f} ريال.",
|
||||
],
|
||||
"notable_events": events_for_customer[-5:],
|
||||
"by_type": agg["by_type"],
|
||||
}
|
||||
|
||||
|
||||
def build_revenue_proof_ledger_demo() -> dict[str, Any]:
|
||||
"""Demo ledger with 12 sample RWUs for a single customer."""
|
||||
from .revenue_work_units import build_revenue_work_unit
|
||||
led = RevenueProofLedger()
|
||||
cust = "demo"
|
||||
sample_units = [
|
||||
build_revenue_work_unit(unit_type="opportunity_created",
|
||||
service_id="first_10_opportunities_sprint",
|
||||
customer_id=cust, revenue_influenced_sar=18000),
|
||||
build_revenue_work_unit(unit_type="opportunity_created",
|
||||
service_id="first_10_opportunities_sprint",
|
||||
customer_id=cust, revenue_influenced_sar=12000),
|
||||
build_revenue_work_unit(unit_type="draft_created",
|
||||
service_id="first_10_opportunities_sprint",
|
||||
customer_id=cust),
|
||||
build_revenue_work_unit(unit_type="draft_created",
|
||||
service_id="first_10_opportunities_sprint",
|
||||
customer_id=cust),
|
||||
build_revenue_work_unit(unit_type="approval_collected",
|
||||
service_id="first_10_opportunities_sprint",
|
||||
customer_id=cust),
|
||||
build_revenue_work_unit(unit_type="approval_collected",
|
||||
service_id="first_10_opportunities_sprint",
|
||||
customer_id=cust),
|
||||
build_revenue_work_unit(unit_type="meeting_drafted",
|
||||
service_id="meeting_booking_sprint",
|
||||
customer_id=cust, revenue_influenced_sar=20000),
|
||||
build_revenue_work_unit(unit_type="risk_blocked",
|
||||
service_id="whatsapp_compliance_setup",
|
||||
customer_id=cust, risk_level="high"),
|
||||
build_revenue_work_unit(unit_type="risk_blocked",
|
||||
service_id="whatsapp_compliance_setup",
|
||||
customer_id=cust, risk_level="high"),
|
||||
build_revenue_work_unit(unit_type="proof_generated",
|
||||
service_id="growth_os_monthly",
|
||||
customer_id=cust),
|
||||
build_revenue_work_unit(unit_type="upsell_offered",
|
||||
service_id="growth_os_monthly",
|
||||
customer_id=cust),
|
||||
build_revenue_work_unit(unit_type="payment_received",
|
||||
customer_id=cust, revenue_influenced_sar=499),
|
||||
]
|
||||
for u in sample_units:
|
||||
led.append_work_unit(u)
|
||||
led.append_notable_event(
|
||||
event_type="risk.blocked", customer_id=cust,
|
||||
summary_ar="منع cold WhatsApp بدون opt-in (PDPL).",
|
||||
)
|
||||
led.append_notable_event(
|
||||
event_type="service.completed", customer_id=cust,
|
||||
summary_ar="اكتمل First 10 Opportunities Sprint بنجاح.",
|
||||
)
|
||||
return led.summary_for_customer(cust)
|
||||
@ -0,0 +1,95 @@
|
||||
"""Revenue Work Units — Dealix's unit of measurement (Salesforce-inspired).
|
||||
|
||||
Each completed, measurable task by Dealix counts as 1 RWU. The platform
|
||||
proves its value by RWUs delivered + risks blocked, not by abstract "AI usage".
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
import uuid
|
||||
from typing import Any
|
||||
|
||||
# Categories of Revenue Work Units.
|
||||
REVENUE_WORK_UNIT_TYPES: tuple[str, ...] = (
|
||||
"opportunity_created",
|
||||
"target_ranked",
|
||||
"contact_blocked",
|
||||
"draft_created",
|
||||
"approval_collected",
|
||||
"message_sent_after_approval",
|
||||
"meeting_drafted",
|
||||
"meeting_held",
|
||||
"followup_created",
|
||||
"proof_generated",
|
||||
"partner_suggested",
|
||||
"payment_link_drafted",
|
||||
"payment_received",
|
||||
"review_reply_drafted",
|
||||
"list_classified",
|
||||
"risk_blocked",
|
||||
"service_completed",
|
||||
"upsell_offered",
|
||||
"subscription_started",
|
||||
)
|
||||
|
||||
|
||||
def build_revenue_work_unit(
|
||||
*,
|
||||
unit_type: str,
|
||||
service_id: str = "",
|
||||
customer_id: str = "",
|
||||
risk_level: str = "low",
|
||||
revenue_influenced_sar: float = 0.0,
|
||||
proof_event: str = "",
|
||||
notes: str = "",
|
||||
) -> dict[str, Any]:
|
||||
"""Build a single RWU. Validates `unit_type` strictly."""
|
||||
if unit_type not in REVENUE_WORK_UNIT_TYPES:
|
||||
raise ValueError(
|
||||
f"Unknown RWU type: {unit_type}. "
|
||||
f"Valid: {', '.join(REVENUE_WORK_UNIT_TYPES)}"
|
||||
)
|
||||
return {
|
||||
"unit_id": str(uuid.uuid4()),
|
||||
"unit_type": unit_type,
|
||||
"service_id": service_id,
|
||||
"customer_id": customer_id,
|
||||
"risk_level": risk_level if risk_level in ("low", "medium", "high") else "low",
|
||||
"revenue_influenced_sar": float(revenue_influenced_sar),
|
||||
"proof_event": proof_event,
|
||||
"notes": notes[:200],
|
||||
"ts": time.time(),
|
||||
}
|
||||
|
||||
|
||||
def aggregate_work_units(
|
||||
units: list[dict[str, Any]] | None,
|
||||
) -> dict[str, Any]:
|
||||
"""Aggregate RWUs → counts + total revenue + risks blocked."""
|
||||
units = units or []
|
||||
by_type: dict[str, int] = {}
|
||||
by_customer: dict[str, int] = {}
|
||||
total_revenue = 0.0
|
||||
risks_blocked = 0
|
||||
high_risk_count = 0
|
||||
|
||||
for u in units:
|
||||
ut = str(u.get("unit_type", ""))
|
||||
by_type[ut] = by_type.get(ut, 0) + 1
|
||||
cid = str(u.get("customer_id", "unknown"))
|
||||
by_customer[cid] = by_customer.get(cid, 0) + 1
|
||||
total_revenue += float(u.get("revenue_influenced_sar", 0) or 0)
|
||||
if ut == "risk_blocked":
|
||||
risks_blocked += 1
|
||||
if u.get("risk_level") == "high":
|
||||
high_risk_count += 1
|
||||
|
||||
return {
|
||||
"total_units": len(units),
|
||||
"by_type": by_type,
|
||||
"by_customer": by_customer,
|
||||
"total_revenue_influenced_sar": round(total_revenue, 2),
|
||||
"risks_blocked": risks_blocked,
|
||||
"high_risk_count": high_risk_count,
|
||||
}
|
||||
@ -0,0 +1,97 @@
|
||||
"""Self-improvement loop — weekly review across services + recommendations."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
def build_weekly_self_improvement_report(
|
||||
*,
|
||||
weekly_metrics: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Build the weekly Arabic self-improvement report.
|
||||
|
||||
Inputs:
|
||||
weekly_metrics = {
|
||||
"approval_rate": 0.42,
|
||||
"reply_rate": 0.05,
|
||||
"meeting_rate": 0.02,
|
||||
"blocked_actions": 8,
|
||||
"service_revenue_sar": {"first_10_opportunities_sprint": 1500, ...},
|
||||
"top_objections": ["price", "timing"],
|
||||
"channel_outcomes": {"email": "healthy", "whatsapp": "watch", ...},
|
||||
}
|
||||
"""
|
||||
m = weekly_metrics or {}
|
||||
approval_rate = float(m.get("approval_rate", 0))
|
||||
reply_rate = float(m.get("reply_rate", 0))
|
||||
meeting_rate = float(m.get("meeting_rate", 0))
|
||||
blocked_actions = int(m.get("blocked_actions", 0))
|
||||
service_revenue = m.get("service_revenue_sar", {}) or {}
|
||||
top_objections = m.get("top_objections", []) or []
|
||||
channel_outcomes = m.get("channel_outcomes", {}) or {}
|
||||
|
||||
recommendations: list[str] = []
|
||||
|
||||
if approval_rate < 0.30:
|
||||
recommendations.append(
|
||||
"approval_rate منخفضة — راجع Saudi Tone + قلل الـ length في الـ drafts."
|
||||
)
|
||||
elif approval_rate < 0.50:
|
||||
recommendations.append(
|
||||
"approval_rate متوسطة — جرّب 3 صياغات مختلفة لكل رسالة."
|
||||
)
|
||||
|
||||
if reply_rate < 0.03:
|
||||
recommendations.append(
|
||||
"reply_rate منخفضة — جرّب why-now أوضح + نقاط شراء أحدث."
|
||||
)
|
||||
|
||||
if meeting_rate < 0.01:
|
||||
recommendations.append(
|
||||
"meeting_rate منخفضة — ضع CTA حجز اجتماع أسهل في الرسالة."
|
||||
)
|
||||
|
||||
if blocked_actions >= 10:
|
||||
recommendations.append(
|
||||
f"تم منع {blocked_actions} فعل — راجع contactability + opt-in policies."
|
||||
)
|
||||
|
||||
# Best-performing service
|
||||
best_service = None
|
||||
if service_revenue:
|
||||
best_service = max(service_revenue, key=lambda k: service_revenue[k])
|
||||
recommendations.append(
|
||||
f"الخدمة الأكثر إيراداً: {best_service} — ضاعف الإعلان عنها هذا الأسبوع."
|
||||
)
|
||||
|
||||
# Channel risks
|
||||
risky_channels = [
|
||||
ch for ch, v in channel_outcomes.items() if v == "pause"
|
||||
]
|
||||
if risky_channels:
|
||||
recommendations.append(
|
||||
f"قنوات في حالة pause: {', '.join(risky_channels)} — أوقف الحملات حتى تستعيد السمعة."
|
||||
)
|
||||
|
||||
next_experiment = (
|
||||
f"اختبر زاوية رسالة جديدة لقطاع 'training' لمدة 7 أيام."
|
||||
if not recommendations else
|
||||
"ابدأ بأعلى توصية في القائمة قبل أي تجربة جديدة."
|
||||
)
|
||||
|
||||
return {
|
||||
"captured_metrics": dict(m),
|
||||
"summary_ar": [
|
||||
f"approval_rate: {approval_rate * 100:.1f}%",
|
||||
f"reply_rate: {reply_rate * 100:.1f}%",
|
||||
f"meeting_rate: {meeting_rate * 100:.1f}%",
|
||||
f"actions blocked: {blocked_actions}",
|
||||
f"top objections: {', '.join(top_objections) or 'لا شيء بارز'}",
|
||||
],
|
||||
"recommendations_ar": recommendations,
|
||||
"next_experiment_ar": next_experiment,
|
||||
"best_service_id": best_service,
|
||||
"approval_required": True,
|
||||
}
|
||||
@ -0,0 +1,54 @@
|
||||
"""Service factory — instantiate a service for a customer."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from auto_client_acquisition.service_tower import (
|
||||
build_intake_questions,
|
||||
build_service_workflow,
|
||||
get_service,
|
||||
quote_service,
|
||||
)
|
||||
|
||||
|
||||
def instantiate_service(
|
||||
*,
|
||||
service_id: str,
|
||||
customer_id: str = "",
|
||||
company_size: str = "small",
|
||||
urgency: str = "normal",
|
||||
) -> dict[str, Any]:
|
||||
"""Instantiate a service for a customer + return ready-to-run state."""
|
||||
s = get_service(service_id)
|
||||
if s is None:
|
||||
return {"error": f"unknown service: {service_id}"}
|
||||
|
||||
return {
|
||||
"service_id": service_id,
|
||||
"service_name_ar": s.name_ar,
|
||||
"customer_id": customer_id,
|
||||
"intake": build_intake_questions(service_id),
|
||||
"workflow": build_service_workflow(service_id),
|
||||
"quote": quote_service(
|
||||
service_id, company_size=company_size, urgency=urgency,
|
||||
),
|
||||
"approval_required": True,
|
||||
"live_send_allowed": False,
|
||||
}
|
||||
|
||||
|
||||
def build_service_factory_demo() -> dict[str, Any]:
|
||||
"""Demo: instantiate the 4 launch-day services for a sample customer."""
|
||||
services = [
|
||||
"free_growth_diagnostic",
|
||||
"list_intelligence",
|
||||
"first_10_opportunities_sprint",
|
||||
"growth_os_monthly",
|
||||
]
|
||||
return {
|
||||
"instantiations": [
|
||||
instantiate_service(service_id=sid, customer_id="demo")
|
||||
for sid in services
|
||||
],
|
||||
}
|
||||
200
dealix/docs/AUTONOMOUS_REVENUE_COMPANY_OS.md
Normal file
200
dealix/docs/AUTONOMOUS_REVENUE_COMPANY_OS.md
Normal file
@ -0,0 +1,200 @@
|
||||
# Dealix Autonomous Revenue Company OS
|
||||
|
||||
> **الفئة الجديدة:** Dealix ليس CRM ولا أداة واتساب ولا AI agent ولا lead scraper.
|
||||
> هو **شركة نمو رقمية ذاتية التشغيل** تدخل أي بزنس، تفهمه، تبني خطة نمو، تشغّل الخدمات المناسبة، تطلب موافقات، تنسق القنوات، تفتح شراكات، ترتب اجتماعات، تجهز مدفوعات، وتثبت العائد.
|
||||
|
||||
---
|
||||
|
||||
## 1. القيم الأساسية للنظام
|
||||
|
||||
```
|
||||
Signal → Context → Service Recommendation → Workflow →
|
||||
Risk Check → Draft → Approval → Execution/Export →
|
||||
Outcome → Proof → Learning → Upgrade
|
||||
```
|
||||
|
||||
كل event داخل Dealix يمر بهذه السلسلة. لا توجد فجوة بين "إشارة" و"إيراد".
|
||||
|
||||
---
|
||||
|
||||
## 2. الطبقات الـ12
|
||||
|
||||
| الطبقة | الموقع |
|
||||
|--------|--------|
|
||||
| Autonomous Service Operator | `auto_client_acquisition/autonomous_service_operator/` |
|
||||
| Service Tower | `auto_client_acquisition/service_tower/` |
|
||||
| Service Excellence OS | `auto_client_acquisition/service_excellence/` |
|
||||
| Targeting OS | `auto_client_acquisition/targeting_os/` |
|
||||
| Safe Tool Gateway | `auto_client_acquisition/platform_services/tool_gateway.py` |
|
||||
| Agent Runtime | كل layer يحدد الـ agents فيه |
|
||||
| Workflow Engine | `service_orchestrator + workflow_runner` |
|
||||
| Revenue Graph | `revenue_company_os/action_graph.py` |
|
||||
| Proof Ledger | `revenue_company_os/proof_ledger.py` + `platform_services/proof_ledger.py` |
|
||||
| Self-Improving Layer | `revenue_company_os/self_improvement_loop.py` + `growth_curator/` |
|
||||
| Revenue Launch System | `revenue_launch/` + `launch_ops/` |
|
||||
| Growth Memory | `revenue_company_os/growth_memory.py` |
|
||||
|
||||
---
|
||||
|
||||
## 3. Autonomous Service Operator
|
||||
|
||||
**16 module + 28 endpoint.** البوت المركزي:
|
||||
|
||||
- **`intent_classifier`** — 16 intent عبر Arabic + English keywords (deterministic).
|
||||
- **`conversation_router`** — كل intent → handler + خدمة موصى بها.
|
||||
- **`session_state`** — 13 حالة جلسة + audit history.
|
||||
- **`intake_collector`** — أسئلة intake لكل intent + validation.
|
||||
- **`approval_manager`** — كروت ≤3 أزرار + decisions (approve/edit/skip/reject).
|
||||
- **`service_orchestrator`** — pipeline 11-step canonical.
|
||||
- **`workflow_runner`** — advance + completion check.
|
||||
- **`tool_action_planner`** — يحظر LinkedIn scraping/auto-DM، يطلب approval لـ high-risk، draft فقط للآمنة.
|
||||
- **`proof_pack_dispatcher`** — Proof Pack envelope per service.
|
||||
- **`upsell_engine`** — 3 verdicts (upsell_now / iterate_first / gentle_upsell).
|
||||
- **`whatsapp_renderer`** — ≤3 buttons، Arabic body.
|
||||
- **`operator_memory`** — sessions + facts + preferences + audit.
|
||||
- **`service_bundles`** — 6 bundles (Growth Starter, Data to Revenue, Executive Growth OS, Partnership Growth, Local Growth OS, Full Growth Control Tower).
|
||||
- **`executive_mode`** — CEO command center.
|
||||
- **`client_mode`** — Growth Manager dashboard.
|
||||
- **`agency_mode`** — multi-client + co-branded Proof Pack + revenue share.
|
||||
|
||||
---
|
||||
|
||||
## 4. Revenue Company OS
|
||||
|
||||
**10 module + 19 endpoint.** الذكاء عبر القنوات:
|
||||
|
||||
- **`event_to_card`** — 13 event types → Arabic decision cards (≤3 buttons).
|
||||
- **`command_feed_engine`** — daily aggregation + sort by risk.
|
||||
- **`action_graph`** — 14 typed edges signal → action → outcome → proof.
|
||||
- **`revenue_work_units`** — 19 RWU types (Salesforce-inspired) + aggregation.
|
||||
- **`channel_health`** — cross-channel reputation snapshot.
|
||||
- **`opportunity_factory`** — turn signals into opportunity cards.
|
||||
- **`service_factory`** — instantiate any service for a customer.
|
||||
- **`proof_ledger`** — Revenue Proof scoreboard per customer.
|
||||
- **`growth_memory`** — cross-customer aggregates (anonymized): best message/channel/objections.
|
||||
- **`self_improvement_loop`** — weekly Arabic recommendations from real metrics.
|
||||
|
||||
---
|
||||
|
||||
## 5. Service Bundles (6 customer-facing offerings)
|
||||
|
||||
| Bundle | Best for | Price (SAR) |
|
||||
|--------|----------|-------------|
|
||||
| Growth Starter | أي شركة تجرب لأول مرة | 499–1,500 |
|
||||
| Data to Revenue | شركات لديها قائمة | 1,500–3,000 |
|
||||
| Executive Growth OS | CEO / Growth Manager شهرياً | 2,999 |
|
||||
| Partnership Growth | شركات تنمو عبر الشركاء | 3,000–7,500 |
|
||||
| Local Growth OS | عيادات/متاجر/فروع | 999–2,999 |
|
||||
| Full Growth Control Tower | مؤسسات 30+ يوم | 12,000–25,000 |
|
||||
|
||||
---
|
||||
|
||||
## 6. الأمان (Critical Gates)
|
||||
|
||||
كل tool action يمر:
|
||||
```
|
||||
Intent → Policy → Approval → Execution → Audit
|
||||
```
|
||||
|
||||
أوضاع التنفيذ:
|
||||
- `suggest_only`
|
||||
- `draft_only`
|
||||
- `approval_required`
|
||||
- `approved_execute` (env flag مفعّل + اعتماد)
|
||||
- `blocked`
|
||||
|
||||
**الممنوع تماماً (حتى مع env flag):**
|
||||
- LinkedIn scraping / auto-DM / auto-connect.
|
||||
- cold WhatsApp بدون opt-in.
|
||||
- Moyasar live charge من API.
|
||||
- إرسال Gmail بدون اعتماد بشري.
|
||||
|
||||
---
|
||||
|
||||
## 7. Endpoints الجديدة
|
||||
|
||||
### Autonomous Service Operator (28)
|
||||
```
|
||||
POST /api/v1/operator/chat/{message, decision, classify}
|
||||
POST /api/v1/operator/sessions/{new, {id}/transition, {id}/context}
|
||||
GET /api/v1/operator/sessions/{id}
|
||||
POST /api/v1/operator/cards/{approval, whatsapp/render}
|
||||
GET /api/v1/operator/intake/questions/{intent}
|
||||
POST /api/v1/operator/intake/validate
|
||||
POST /api/v1/operator/service/start
|
||||
POST /api/v1/operator/tools/plan
|
||||
POST /api/v1/operator/proof-pack/dispatch
|
||||
POST /api/v1/operator/upsell/{recommend, card}
|
||||
GET /api/v1/operator/bundles
|
||||
POST /api/v1/operator/bundles/recommend
|
||||
POST /api/v1/operator/mode/{ceo, ceo/daily-brief, ceo/risks, client, agency, agency/add-client, agency/revenue-share, agency/co-branded-proof}
|
||||
GET /api/v1/operator/whatsapp/daily-brief/demo
|
||||
GET /api/v1/operator/proof-pack/demo
|
||||
```
|
||||
|
||||
### Revenue Company OS (19)
|
||||
```
|
||||
GET /api/v1/revenue-os/command-feed/demo
|
||||
POST /api/v1/revenue-os/{events/ingest, command-feed/build}
|
||||
GET /api/v1/revenue-os/work-units/{types, demo}
|
||||
POST /api/v1/revenue-os/work-units/{build, aggregate}
|
||||
GET /api/v1/revenue-os/proof-ledger/demo
|
||||
GET /api/v1/revenue-os/action-graph/{edge-types, demo}
|
||||
POST /api/v1/revenue-os/channel-health/snapshot
|
||||
GET /api/v1/revenue-os/channel-health/demo
|
||||
POST /api/v1/revenue-os/opportunity-factory
|
||||
GET /api/v1/revenue-os/opportunity-factory/demo
|
||||
POST /api/v1/revenue-os/service-factory
|
||||
GET /api/v1/revenue-os/service-factory/demo
|
||||
GET /api/v1/revenue-os/growth-memory/demo
|
||||
POST /api/v1/revenue-os/self-improvement/weekly-report
|
||||
GET /api/v1/revenue-os/self-improvement/demo
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. اختبارات
|
||||
|
||||
`tests/unit/test_autonomous_service_operator.py` — 50 tests.
|
||||
`tests/unit/test_revenue_company_os.py` — 31 tests.
|
||||
|
||||
تغطية:
|
||||
- Intent classification (8 intents).
|
||||
- Bundle recommendation per persona.
|
||||
- Tool planner blocks LinkedIn scrape/auto-DM.
|
||||
- Approval cards ≤3 buttons.
|
||||
- Sessions transition + audit.
|
||||
- Modes (CEO / Client / Agency) with revenue share calc.
|
||||
- Event → card with risk levels.
|
||||
- Action Graph what-works.
|
||||
- RWU aggregation + revenue total.
|
||||
- Self-improvement recommendations.
|
||||
|
||||
---
|
||||
|
||||
## 9. الفرق الشاسع عن المنافسين
|
||||
|
||||
| المنافس | ماذا يملك | أين Dealix يتفوق |
|
||||
|---------|-----------|-----------------|
|
||||
| CRM | بيانات وفرص | يقول ماذا تفعل اليوم |
|
||||
| WhatsApp tool | إرسال | يقرر هل ترسل، لمن، ولماذا، وبأي موافقة |
|
||||
| Email assistant | يكتب رد | يحول الإيميل إلى pipeline + meeting + Proof |
|
||||
| Agency | تنفيذ يدوي | نظام قابل للتكرار + Proof Pack |
|
||||
| Generic AI agent | ينفذ prompts | عنده خدمات + سياسات + Proof + موافقات + تحسين ذاتي |
|
||||
| HubSpot/Gong/Salesforce | منصات قوية | سعودي/عربي/SMB/Service-first/WhatsApp-aware |
|
||||
|
||||
---
|
||||
|
||||
## 10. الخلاصة
|
||||
|
||||
Dealix الآن **فئة جديدة**:
|
||||
- 12 طبقة معمارية متكاملة.
|
||||
- 905 اختبار ناجح.
|
||||
- 47 endpoint جديد في هذه الجولة.
|
||||
- Approval-first في كل قناة.
|
||||
- Self-improving أسبوعياً.
|
||||
- Revenue Work Units قابلة للقياس.
|
||||
- Proof Ledger يُثبت العائد.
|
||||
- 6 bundles + Service Excellence Score يحكم كل خدمة.
|
||||
|
||||
**لا يبيع features. يبيع نتائج منظمة.**
|
||||
@ -316,6 +316,32 @@ OAuth Gmail/Calendar، حصص، سياسات.
|
||||
- `scripts/launch_readiness_check.py` — runs 10 gates locally + against optional staging URL; reports JSON or pretty output.
|
||||
- `scripts/smoke_staging.py` — already exists (preserved).
|
||||
|
||||
## 44. Autonomous Revenue Company OS
|
||||
|
||||
> Dealix الآن **فئة جديدة** — ليس منصة، بل شركة نمو رقمية ذاتية التشغيل.
|
||||
|
||||
**26 module جديد + 47 endpoint جديد + 81 اختبار**. **التفصيل:** [`AUTONOMOUS_REVENUE_COMPANY_OS.md`](AUTONOMOUS_REVENUE_COMPANY_OS.md).
|
||||
|
||||
### Autonomous Service Operator (16 modules)
|
||||
البوت المركزي يدير كل المحادثات وتشغيل الخدمات:
|
||||
- `intent_classifier` (16 intents) → `conversation_router` → `service_orchestrator`.
|
||||
- `intake_collector` + `approval_manager` (≤3 buttons) + `workflow_runner` + `tool_action_planner` (LinkedIn scrape/auto-DM blocked).
|
||||
- `proof_pack_dispatcher` + `upsell_engine` + `whatsapp_renderer` + `operator_memory`.
|
||||
- `service_bundles` (6 bundles: Growth Starter / Data to Revenue / Executive Growth OS / Partnership Growth / Local Growth OS / Full Growth Control Tower).
|
||||
- `executive_mode` (CEO) + `client_mode` (Growth Manager) + `agency_mode` (multi-client + co-branded + revenue share).
|
||||
|
||||
### Revenue Company OS (10 modules)
|
||||
الذكاء عبر القنوات:
|
||||
- `event_to_card` (13 event types → Arabic decision cards).
|
||||
- `command_feed_engine` (sort by risk) + `action_graph` (14 typed edges: signal→action→outcome→proof).
|
||||
- `revenue_work_units` (19 RWU types, Salesforce-inspired) + `channel_health`.
|
||||
- `opportunity_factory` + `service_factory` + `proof_ledger` (revenue-tier scoreboard).
|
||||
- `growth_memory` (cross-customer aggregates) + `self_improvement_loop` (weekly Arabic recommendations).
|
||||
|
||||
**Endpoints:** `/api/v1/operator/*` (28) + `/api/v1/revenue-os/*` (19).
|
||||
|
||||
**الفرق الشاسع:** Dealix لا يبيع features ولا AI ولا منصة. يبيع **شركة نمو رقمية ذاتية التشغيل** — نتائج منظمة + تشغيل يومي + Proof Pack شهري.
|
||||
|
||||
---
|
||||
|
||||
**الخلاصة:** المنتج **قوي كأساس سوقي وتقني**؛ الإطلاق العام يحتاج تشغيلاً وامتثالاً وتجربة عميل مغلقة أولاً. الإطلاق اليوم = Private Beta + Pilots + Proof Pack، ليس Public Launch.
|
||||
|
||||
379
dealix/tests/unit/test_autonomous_service_operator.py
Normal file
379
dealix/tests/unit/test_autonomous_service_operator.py
Normal file
@ -0,0 +1,379 @@
|
||||
"""Unit tests for the Autonomous Service Operator."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from auto_client_acquisition.autonomous_service_operator import (
|
||||
OperatorMemory,
|
||||
SUPPORTED_INTENTS,
|
||||
add_agency_client,
|
||||
build_agency_dashboard,
|
||||
build_approval_card,
|
||||
build_ceo_command_center,
|
||||
build_client_dashboard,
|
||||
build_co_branded_proof_pack,
|
||||
build_executive_daily_brief,
|
||||
build_intake_questions_for_intent,
|
||||
build_new_session,
|
||||
build_revenue_risks_summary,
|
||||
build_service_pipeline,
|
||||
build_session_context,
|
||||
build_upsell_card,
|
||||
classify_intent,
|
||||
dispatch_proof_pack,
|
||||
handle_message,
|
||||
intent_to_service,
|
||||
list_agency_revenue_share,
|
||||
list_bundles,
|
||||
plan_tool_action,
|
||||
process_approval_decision,
|
||||
recommend_bundle,
|
||||
recommend_upsell_after_service,
|
||||
render_approval_card_for_whatsapp,
|
||||
render_card_for_whatsapp,
|
||||
render_daily_brief_for_whatsapp,
|
||||
transition_session,
|
||||
validate_intake_completeness,
|
||||
)
|
||||
|
||||
|
||||
# ── Intent classification ────────────────────────────────────
|
||||
def test_intent_want_more_customers():
|
||||
out = classify_intent("أبغى عملاء أكثر لشركتي")
|
||||
assert out["intent"] == "want_more_customers"
|
||||
|
||||
|
||||
def test_intent_has_contact_list():
|
||||
out = classify_intent("عندي قائمة أرقام كبيرة")
|
||||
assert out["intent"] == "has_contact_list"
|
||||
|
||||
|
||||
def test_intent_partnerships():
|
||||
out = classify_intent("أبغى شراكات مع وكالات")
|
||||
assert out["intent"] == "want_partnerships"
|
||||
|
||||
|
||||
def test_intent_whatsapp_setup():
|
||||
out = classify_intent("نستخدم واتساب بدون opt-in")
|
||||
assert out["intent"] == "want_whatsapp_setup"
|
||||
|
||||
|
||||
def test_intent_pricing():
|
||||
out = classify_intent("بكم السعر؟")
|
||||
assert out["intent"] == "ask_pricing"
|
||||
|
||||
|
||||
def test_intent_approve():
|
||||
out = classify_intent("اعتمد")
|
||||
assert out["intent"] == "approve_action"
|
||||
|
||||
|
||||
def test_intent_unknown_falls_back_to_services():
|
||||
out = classify_intent("xyz random text")
|
||||
assert out["intent"] == "ask_services"
|
||||
|
||||
|
||||
def test_intent_to_service_mapping():
|
||||
assert intent_to_service("want_more_customers") == "first_10_opportunities_sprint"
|
||||
assert intent_to_service("has_contact_list") == "list_intelligence"
|
||||
assert intent_to_service("want_partnerships") == "partner_sprint"
|
||||
|
||||
|
||||
def test_supported_intents_count():
|
||||
assert len(SUPPORTED_INTENTS) == 16
|
||||
|
||||
|
||||
# ── Conversation router ──────────────────────────────────────
|
||||
def test_handle_message_recommends_first_10_for_want_more_customers():
|
||||
out = handle_message("أبغى عملاء أكثر")
|
||||
assert out["service_id"] == "first_10_opportunities_sprint"
|
||||
assert out["live_send_allowed"] is False
|
||||
|
||||
|
||||
def test_handle_message_uses_agency_bundle_for_agency():
|
||||
out = handle_message("أبغى شراكات", is_agency=True)
|
||||
assert out["bundle_recommendation"]["recommended_bundle_id"] == "partnership_growth"
|
||||
|
||||
|
||||
def test_handle_message_uses_data_to_revenue_when_list_provided():
|
||||
out = handle_message("أبغى أستخدم قائمتي", has_contact_list=True)
|
||||
assert out["bundle_recommendation"]["recommended_bundle_id"] == "data_to_revenue"
|
||||
|
||||
|
||||
def test_handle_message_approval_processes_decision():
|
||||
out = handle_message("اعتمد")
|
||||
assert "decision_processed" in out
|
||||
assert out["decision_processed"]["state"] == "approved"
|
||||
|
||||
|
||||
# ── Sessions ────────────────────────────────────────────────
|
||||
def test_new_session_has_uuid():
|
||||
s = build_new_session(customer_id="cust_1")
|
||||
assert s.session_id
|
||||
assert s.state == "new"
|
||||
assert s.customer_id == "cust_1"
|
||||
|
||||
|
||||
def test_session_transition_audit_trail():
|
||||
s = build_new_session()
|
||||
transition_session(s, new_state="intent_classified", note="initial")
|
||||
assert s.state == "intent_classified"
|
||||
assert len(s.history) == 1
|
||||
assert s.history[0]["from"] == "new"
|
||||
|
||||
|
||||
def test_session_transition_unknown_raises():
|
||||
s = build_new_session()
|
||||
with pytest.raises(ValueError):
|
||||
transition_session(s, new_state="bogus_state")
|
||||
|
||||
|
||||
def test_operator_memory_stores_session():
|
||||
mem = OperatorMemory()
|
||||
s = build_new_session(customer_id="cust_1")
|
||||
mem.upsert_session(s)
|
||||
assert mem.get_session(s.session_id) is s
|
||||
ctx = build_session_context(memory=mem, session_id=s.session_id)
|
||||
assert ctx["session"]["session_id"] == s.session_id
|
||||
|
||||
|
||||
# ── Intake ──────────────────────────────────────────────────
|
||||
def test_intake_questions_for_known_intent():
|
||||
out = build_intake_questions_for_intent("want_more_customers")
|
||||
assert len(out["questions"]) >= 4
|
||||
|
||||
|
||||
def test_intake_questions_unknown_intent_falls_back():
|
||||
out = build_intake_questions_for_intent("totally_unknown_intent")
|
||||
assert out["questions"]
|
||||
|
||||
|
||||
def test_intake_validation_detects_missing():
|
||||
out = validate_intake_completeness(
|
||||
"want_more_customers",
|
||||
{"sector": "training"}, # only one field
|
||||
)
|
||||
assert out["complete"] is False
|
||||
assert "company_name" in out["missing_fields"]
|
||||
|
||||
|
||||
def test_intake_validation_complete():
|
||||
out = validate_intake_completeness(
|
||||
"want_more_customers",
|
||||
{"company_name": "X", "sector": "training", "city": "Riyadh",
|
||||
"offer": "Pilot 7 أيام", "ideal_customer": "B2B"},
|
||||
)
|
||||
assert out["complete"] is True
|
||||
|
||||
|
||||
# ── Approval manager ────────────────────────────────────────
|
||||
def test_approval_card_has_three_buttons():
|
||||
card = build_approval_card(
|
||||
action_type="send_email", title_ar="إرسال إيميل",
|
||||
summary_ar="إيميل لـ Acme",
|
||||
)
|
||||
assert len(card["buttons_ar"]) <= 3
|
||||
assert card["live_send_allowed"] is False
|
||||
|
||||
|
||||
def test_approval_decision_approve():
|
||||
card = build_approval_card(action_type="x", title_ar="x", summary_ar="x")
|
||||
out = process_approval_decision(card, decision="approve")
|
||||
assert out["state"] == "approved"
|
||||
assert out["next_action"] == "execute_with_audit"
|
||||
|
||||
|
||||
def test_approval_decision_arabic_skip():
|
||||
card = build_approval_card(action_type="x", title_ar="x", summary_ar="x")
|
||||
out = process_approval_decision(card, decision="تخطي")
|
||||
assert out["state"] == "rejected"
|
||||
|
||||
|
||||
def test_approval_decision_unknown_returns_error():
|
||||
card = build_approval_card(action_type="x", title_ar="x", summary_ar="x")
|
||||
out = process_approval_decision(card, decision="bogus")
|
||||
assert "error" in out
|
||||
|
||||
|
||||
# ── Service pipeline ────────────────────────────────────────
|
||||
def test_service_pipeline_starts_at_intake():
|
||||
p = build_service_pipeline("first_10_opportunities_sprint")
|
||||
assert p["current_step"] == "intake"
|
||||
assert any(s["step_id"] == "approval" for s in p["steps"])
|
||||
|
||||
|
||||
# ── Tool action planner ─────────────────────────────────────
|
||||
def test_plan_blocks_linkedin_scrape():
|
||||
out = plan_tool_action(tool="linkedin.scrape_profile")
|
||||
assert out["verdict"] == "blocked"
|
||||
|
||||
|
||||
def test_plan_blocks_linkedin_auto_dm():
|
||||
out = plan_tool_action(tool="linkedin.auto_dm")
|
||||
assert out["verdict"] == "blocked"
|
||||
|
||||
|
||||
def test_plan_high_risk_requires_approval():
|
||||
out = plan_tool_action(tool="whatsapp.send_message")
|
||||
assert out["verdict"] == "approval_required"
|
||||
assert out["live_send_allowed"] is False
|
||||
|
||||
|
||||
def test_plan_draft_safe_returns_draft_only():
|
||||
out = plan_tool_action(tool="gmail.create_draft")
|
||||
assert out["verdict"] == "draft_only"
|
||||
|
||||
|
||||
def test_plan_unknown_defaults_to_approval_required():
|
||||
out = plan_tool_action(tool="bogus.tool")
|
||||
assert out["verdict"] == "approval_required"
|
||||
|
||||
|
||||
# ── Bundles ─────────────────────────────────────────────────
|
||||
def test_list_bundles_returns_six():
|
||||
out = list_bundles()
|
||||
assert out["total"] == 6
|
||||
|
||||
|
||||
def test_recommend_bundle_for_agency():
|
||||
out = recommend_bundle(is_agency=True)
|
||||
assert out["recommended_bundle_id"] == "partnership_growth"
|
||||
|
||||
|
||||
def test_recommend_bundle_for_local_business():
|
||||
out = recommend_bundle(is_local_business=True)
|
||||
assert out["recommended_bundle_id"] == "local_growth_os"
|
||||
|
||||
|
||||
def test_recommend_bundle_with_list():
|
||||
out = recommend_bundle(has_contact_list=True)
|
||||
assert out["recommended_bundle_id"] == "data_to_revenue"
|
||||
|
||||
|
||||
def test_recommend_bundle_default():
|
||||
out = recommend_bundle(budget_sar=500)
|
||||
assert out["recommended_bundle_id"] == "growth_starter"
|
||||
|
||||
|
||||
# ── Modes ───────────────────────────────────────────────────
|
||||
def test_ceo_command_center_arabic():
|
||||
out = build_ceo_command_center(company_name="Acme")
|
||||
assert out["mode"] == "ceo"
|
||||
assert any("" <= ch <= "ۿ" for ch in out["daily_brief"]["title_ar"])
|
||||
|
||||
|
||||
def test_executive_daily_brief_three_decisions():
|
||||
out = build_executive_daily_brief(company_name="Acme")
|
||||
assert len(out["priority_decisions_ar"]) == 3
|
||||
assert len(out["buttons_ar"]) <= 3
|
||||
|
||||
|
||||
def test_revenue_risks_summary_three_risks():
|
||||
out = build_revenue_risks_summary()
|
||||
assert len(out["risks"]) == 3
|
||||
|
||||
|
||||
def test_client_dashboard_has_panels():
|
||||
out = build_client_dashboard(customer_id="c1", company_name="Acme")
|
||||
assert out["mode"] == "client"
|
||||
assert len(out["today_panels_ar"]) >= 3
|
||||
|
||||
|
||||
def test_agency_dashboard_aggregates():
|
||||
clients = [
|
||||
{"client_company_name": "A", "monthly_subscription_sar": 2999,
|
||||
"revenue_share_pct": 20, "status": "active"},
|
||||
{"client_company_name": "B", "monthly_subscription_sar": 1500,
|
||||
"revenue_share_pct": 25, "status": "onboarding"},
|
||||
]
|
||||
out = build_agency_dashboard(agency_id="ag1", clients=clients)
|
||||
assert out["metrics"]["total_clients"] == 2
|
||||
assert out["metrics"]["monthly_revenue_sar"] == 4499.0
|
||||
|
||||
|
||||
def test_agency_revenue_share_calculation():
|
||||
clients = [
|
||||
{"client_company_name": "A", "monthly_subscription_sar": 2999,
|
||||
"revenue_share_pct": 20},
|
||||
]
|
||||
out = list_agency_revenue_share(clients=clients)
|
||||
assert out["total_share_sar"] == 599.8
|
||||
|
||||
|
||||
def test_agency_add_client_appends():
|
||||
clients: list = []
|
||||
add_agency_client(
|
||||
agency_id="ag1", client_company_name="Acme",
|
||||
monthly_subscription_sar=2999, revenue_share_pct=20,
|
||||
clients=clients,
|
||||
)
|
||||
assert len(clients) == 1
|
||||
|
||||
|
||||
def test_co_branded_proof_pack_includes_both_names():
|
||||
out = build_co_branded_proof_pack(
|
||||
agency_name="Vortex", client_company_name="Acme",
|
||||
)
|
||||
assert out["co_branded"] is True
|
||||
assert out["agency_name"] == "Vortex"
|
||||
|
||||
|
||||
# ── WhatsApp renderer ────────────────────────────────────────
|
||||
def test_render_card_for_whatsapp_no_live_send():
|
||||
card = build_approval_card(
|
||||
action_type="x", title_ar="فرصة", summary_ar="ملخص",
|
||||
)
|
||||
out = render_card_for_whatsapp(card)
|
||||
assert out["live_send_allowed"] is False
|
||||
assert any("" <= ch <= "ۿ" for ch in out["body_ar"])
|
||||
|
||||
|
||||
def test_render_approval_card_has_3_buttons():
|
||||
card = build_approval_card(
|
||||
action_type="x", title_ar="فرصة", summary_ar="ملخص",
|
||||
)
|
||||
out = render_approval_card_for_whatsapp(card)
|
||||
assert len(out["buttons_ar"]) == 3
|
||||
|
||||
|
||||
def test_render_daily_brief_arabic():
|
||||
brief = build_executive_daily_brief(company_name="Acme")
|
||||
out = render_daily_brief_for_whatsapp(brief)
|
||||
assert "صباح" in out["body_ar"]
|
||||
assert out["live_send_allowed"] is False
|
||||
|
||||
|
||||
# ── Proof + Upsell ──────────────────────────────────────────
|
||||
def test_proof_pack_dispatch_returns_draft():
|
||||
out = dispatch_proof_pack(
|
||||
service_id="first_10_opportunities_sprint",
|
||||
customer_id="c1",
|
||||
)
|
||||
assert out["status"] == "draft"
|
||||
assert out["live_send_allowed"] is False
|
||||
|
||||
|
||||
def test_upsell_recommends_growth_os_after_first_10():
|
||||
out = recommend_upsell_after_service(
|
||||
completed_service_id="first_10_opportunities_sprint",
|
||||
pilot_metrics={"pipeline_sar": 30000, "meetings": 3, "csat": 9},
|
||||
)
|
||||
assert out["recommended_next_service_id"] == "growth_os_monthly"
|
||||
assert out["verdict"] == "upsell_now"
|
||||
|
||||
|
||||
def test_upsell_iterate_for_weak_outcome():
|
||||
out = recommend_upsell_after_service(
|
||||
completed_service_id="first_10_opportunities_sprint",
|
||||
pilot_metrics={"pipeline_sar": 1000, "meetings": 0, "csat": 5},
|
||||
)
|
||||
assert out["verdict"] == "iterate_first"
|
||||
|
||||
|
||||
def test_upsell_card_has_three_buttons():
|
||||
out = build_upsell_card(
|
||||
completed_service_id="first_10_opportunities_sprint",
|
||||
)
|
||||
assert len(out["buttons_ar"]) == 3
|
||||
assert out["live_send_allowed"] is False
|
||||
253
dealix/tests/unit/test_revenue_company_os.py
Normal file
253
dealix/tests/unit/test_revenue_company_os.py
Normal file
@ -0,0 +1,253 @@
|
||||
"""Unit tests for the Revenue Company OS layer."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from auto_client_acquisition.revenue_company_os import (
|
||||
REVENUE_EDGE_TYPES,
|
||||
REVENUE_WORK_UNIT_TYPES,
|
||||
RevenueActionGraph,
|
||||
RevenueProofLedger,
|
||||
aggregate_work_units,
|
||||
build_card_from_event,
|
||||
build_channel_health_snapshot,
|
||||
build_command_feed_for_customer,
|
||||
build_growth_memory_demo,
|
||||
build_opportunity_factory_demo,
|
||||
build_revenue_action_graph_demo,
|
||||
build_revenue_proof_ledger_demo,
|
||||
build_revenue_work_unit,
|
||||
build_service_factory_demo,
|
||||
build_weekly_self_improvement_report,
|
||||
instantiate_service,
|
||||
revenue_os_command_feed_demo,
|
||||
)
|
||||
|
||||
|
||||
# ── Event → card ────────────────────────────────────────────
|
||||
def test_email_event_returns_arabic_card():
|
||||
card = build_card_from_event({
|
||||
"event_type": "email.received",
|
||||
"customer_id": "c1",
|
||||
"payload": {"from": "ali@example.sa", "subject": "نطلب عرض"},
|
||||
})
|
||||
assert card["type"] == "email_lead"
|
||||
assert any("" <= ch <= "ۿ" for ch in card["title_ar"])
|
||||
assert card["live_send_allowed"] is False
|
||||
|
||||
|
||||
def test_low_review_returns_high_risk():
|
||||
card = build_card_from_event({
|
||||
"event_type": "review.created",
|
||||
"payload": {"rating": 1, "text": "تأخير في الرد"},
|
||||
})
|
||||
assert card["risk_level"] == "high"
|
||||
|
||||
|
||||
def test_risk_blocked_event_high_risk():
|
||||
card = build_card_from_event({
|
||||
"event_type": "risk.blocked",
|
||||
"payload": {"reason_ar": "محاولة cold WhatsApp"},
|
||||
})
|
||||
assert card["risk_level"] == "high"
|
||||
assert "فهم" in card["buttons_ar"]
|
||||
|
||||
|
||||
def test_unknown_event_returns_action_required():
|
||||
card = build_card_from_event({"event_type": "totally.unknown"})
|
||||
assert card["type"] == "action_required"
|
||||
assert card["live_send_allowed"] is False
|
||||
|
||||
|
||||
# ── Command feed ────────────────────────────────────────────
|
||||
def test_command_feed_demo_has_8_events():
|
||||
feed = revenue_os_command_feed_demo()
|
||||
assert feed["feed_size"] == 8
|
||||
|
||||
|
||||
def test_command_feed_sorts_high_risk_first():
|
||||
feed = revenue_os_command_feed_demo()
|
||||
cards = feed["cards"]
|
||||
assert cards[0]["risk_level"] == "high"
|
||||
|
||||
|
||||
def test_command_feed_for_customer_empty():
|
||||
feed = build_command_feed_for_customer(customer_id="c1", events=[])
|
||||
assert feed["feed_size"] == 0
|
||||
assert feed["cards"] == []
|
||||
|
||||
|
||||
# ── Revenue Work Units ──────────────────────────────────────
|
||||
def test_rwu_types_count():
|
||||
assert len(REVENUE_WORK_UNIT_TYPES) >= 18
|
||||
|
||||
|
||||
def test_build_rwu_validates_type():
|
||||
with pytest.raises(ValueError):
|
||||
build_revenue_work_unit(unit_type="bogus")
|
||||
|
||||
|
||||
def test_build_rwu_returns_valid_unit():
|
||||
u = build_revenue_work_unit(
|
||||
unit_type="opportunity_created",
|
||||
customer_id="c1",
|
||||
revenue_influenced_sar=18000,
|
||||
)
|
||||
assert u["unit_type"] == "opportunity_created"
|
||||
assert u["revenue_influenced_sar"] == 18000.0
|
||||
|
||||
|
||||
def test_aggregate_work_units_sums_revenue():
|
||||
units = [
|
||||
build_revenue_work_unit(unit_type="opportunity_created",
|
||||
customer_id="c1", revenue_influenced_sar=10000),
|
||||
build_revenue_work_unit(unit_type="opportunity_created",
|
||||
customer_id="c1", revenue_influenced_sar=20000),
|
||||
build_revenue_work_unit(unit_type="risk_blocked",
|
||||
customer_id="c1", risk_level="high"),
|
||||
]
|
||||
agg = aggregate_work_units(units)
|
||||
assert agg["total_units"] == 3
|
||||
assert agg["total_revenue_influenced_sar"] == 30000.0
|
||||
assert agg["risks_blocked"] == 1
|
||||
|
||||
|
||||
# ── Revenue Action Graph ────────────────────────────────────
|
||||
def test_action_graph_edge_types_count():
|
||||
assert len(REVENUE_EDGE_TYPES) >= 12
|
||||
|
||||
|
||||
def test_action_graph_add_edge_validates():
|
||||
g = RevenueActionGraph()
|
||||
with pytest.raises(ValueError):
|
||||
g.add_edge(edge_type="bogus", src_id="a", dst_id="b")
|
||||
|
||||
|
||||
def test_action_graph_demo_has_two_customers():
|
||||
out = build_revenue_action_graph_demo()
|
||||
assert "summary_a" in out
|
||||
assert "summary_b" in out
|
||||
assert out["summary_a"]["outcome_score"] > 0
|
||||
|
||||
|
||||
def test_action_graph_what_works():
|
||||
g = RevenueActionGraph()
|
||||
g.add_edge(edge_type="proposal_led_to_payment", src_id="p1", dst_id="pay1",
|
||||
customer_id="c1")
|
||||
g.add_edge(edge_type="reply_led_to_meeting", src_id="r1", dst_id="m1",
|
||||
customer_id="c1")
|
||||
summary = g.what_works_for_customer("c1")
|
||||
assert summary["total_edges"] == 2
|
||||
assert summary["outcome_score"] > 0
|
||||
|
||||
|
||||
# ── Channel Health ──────────────────────────────────────────
|
||||
def test_channel_health_snapshot_returns_score():
|
||||
out = build_channel_health_snapshot()
|
||||
assert "channels" in out
|
||||
assert "overall_score" in out
|
||||
|
||||
|
||||
def test_channel_health_flags_risky_channel():
|
||||
out = build_channel_health_snapshot(metrics_per_channel={
|
||||
"email": {"bounce_rate": 0.20, "complaint_rate": 0.01,
|
||||
"opt_out_rate": 0.30, "reply_rate": 0.001},
|
||||
})
|
||||
assert "email" in out["channels_at_risk"]
|
||||
|
||||
|
||||
# ── Opportunity factory ─────────────────────────────────────
|
||||
def test_opportunity_factory_returns_5_opps():
|
||||
out = build_opportunity_factory_demo(limit=5)
|
||||
assert out["count"] == 5
|
||||
for opp in out["opportunities"]:
|
||||
assert opp["live_send_allowed"] is False
|
||||
|
||||
|
||||
def test_opportunity_factory_blocks_unsafe_actions():
|
||||
out = build_opportunity_factory_demo()
|
||||
notes = " ".join(out["do_not_do_ar"])
|
||||
assert "scraping" in notes.lower() or "scraping" in notes
|
||||
|
||||
|
||||
# ── Service factory ────────────────────────────────────────
|
||||
def test_instantiate_service_known():
|
||||
out = instantiate_service(
|
||||
service_id="first_10_opportunities_sprint",
|
||||
customer_id="c1",
|
||||
)
|
||||
assert "intake" in out
|
||||
assert "workflow" in out
|
||||
assert "quote" in out
|
||||
assert out["live_send_allowed"] is False
|
||||
|
||||
|
||||
def test_instantiate_service_unknown():
|
||||
out = instantiate_service(service_id="totally_unknown")
|
||||
assert "error" in out
|
||||
|
||||
|
||||
def test_service_factory_demo_returns_4_services():
|
||||
out = build_service_factory_demo()
|
||||
assert len(out["instantiations"]) == 4
|
||||
|
||||
|
||||
# ── Proof Ledger ────────────────────────────────────────────
|
||||
def test_proof_ledger_appends_units():
|
||||
led = RevenueProofLedger()
|
||||
led.append_work_unit(build_revenue_work_unit(
|
||||
unit_type="opportunity_created", customer_id="c1",
|
||||
revenue_influenced_sar=10000,
|
||||
))
|
||||
summary = led.summary_for_customer("c1")
|
||||
assert summary["totals"]["opportunities_created"] == 1
|
||||
|
||||
|
||||
def test_proof_ledger_rejects_unknown_type():
|
||||
led = RevenueProofLedger()
|
||||
with pytest.raises(ValueError):
|
||||
led.append_work_unit({"unit_type": "totally_bogus"})
|
||||
|
||||
|
||||
def test_proof_ledger_demo_has_revenue():
|
||||
out = build_revenue_proof_ledger_demo()
|
||||
assert out["totals"]["revenue_influenced_sar"] > 0
|
||||
assert out["totals"]["risks_blocked"] >= 2
|
||||
|
||||
|
||||
# ── Growth Memory ───────────────────────────────────────────
|
||||
def test_growth_memory_demo_has_top_objections():
|
||||
out = build_growth_memory_demo()
|
||||
assert out["summary"]["top_objections"]
|
||||
|
||||
|
||||
def test_growth_memory_best_message():
|
||||
out = build_growth_memory_demo()
|
||||
assert out["best_message_training"]["sector"] == "training"
|
||||
|
||||
|
||||
# ── Self-improvement loop ───────────────────────────────────
|
||||
def test_self_improvement_low_approval_recommends_fix():
|
||||
out = build_weekly_self_improvement_report(weekly_metrics={
|
||||
"approval_rate": 0.10,
|
||||
})
|
||||
assert out["recommendations_ar"]
|
||||
assert any("approval_rate" in r for r in out["recommendations_ar"])
|
||||
|
||||
|
||||
def test_self_improvement_blocked_actions_high_recommends_review():
|
||||
out = build_weekly_self_improvement_report(weekly_metrics={
|
||||
"approval_rate": 0.5, "blocked_actions": 25,
|
||||
})
|
||||
assert any("منع" in r for r in out["recommendations_ar"])
|
||||
|
||||
|
||||
def test_self_improvement_returns_best_service():
|
||||
out = build_weekly_self_improvement_report(weekly_metrics={
|
||||
"service_revenue_sar": {
|
||||
"first_10_opportunities_sprint": 1500,
|
||||
"growth_os_monthly": 5000,
|
||||
},
|
||||
})
|
||||
assert out["best_service_id"] == "growth_os_monthly"
|
||||
Loading…
Reference in New Issue
Block a user