system-prompts-and-models-o.../dealix/api/routers/autonomous_service_operator.py
Dealix Builder ef08649efe 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>
2026-05-01 17:50:32 +03:00

305 lines
12 KiB
Python

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