system-prompts-and-models-o.../salesflow-saas/backend/app/api/v1/full_os.py
Claude c398bd31ce
feat(dealix): Full OS orchestrator — unified deal lifecycle automation
The missing brain that connects ALL existing services into one system:

1. full_os_orchestrator.py — Deal lifecycle state machine:
   12 stages: new_lead → qualifying → qualified → nurturing →
   meeting_booked → meeting_done → proposal_sent → negotiating →
   payment_requested → pilot_active → closed_won/lost/opted_out.

   Each stage has: auto-transitions based on intent, auto-actions
   (send_whatsapp, book_meeting, sync_crm, etc.), Arabic response
   templates, qualification questions.

2. full_os.py API — 4 endpoints:
   - POST /os/process — classify event + determine next stage + actions
   - POST /os/process-and-act — same + auto-execute (WhatsApp send
     via Ultramsg if safe, or create draft if human approval needed)
   - POST /os/bulk-process — batch event processing
   - GET /os/stages — list all stages with transitions

3. How it works:
   Inbound WhatsApp → /os/process-and-act →
   classify intent → transition stage →
   if auto_send_allowed: send WhatsApp response immediately
   if human_approval_required: create draft for Sami to review
   Always: log activity + suggest next actions

4. Safety:
   - Negotiation/payment/pilot stages = human_approval_required
   - Opt-out = immediate stop, no further contact
   - All sends via existing Ultramsg (rate limited, logged)
   - Draft queue for anything needing review

Connects to existing infrastructure:
- outreach_engine._send_via_ultramsg() for WhatsApp
- OutreachDraft model for draft queue
- Reply classifier for intent detection

https://claude.ai/code/session_01W1rJthWDkasijTdXCfxVHs
2026-04-25 18:04:12 +00:00

170 lines
5.9 KiB
Python

"""Full OS API — unified deal lifecycle orchestration.
Single endpoint processes any event (inbound message, reply, booking,
payment) and returns: next stage, actions to take, response message,
and whether human approval is needed.
"""
from __future__ import annotations
from typing import Any, Dict, List, Optional
from fastapi import APIRouter
from pydantic import BaseModel
router = APIRouter(prefix="/os", tags=["Full OS"])
class ProcessEventRequest(BaseModel):
lead_id: str = ""
phone: str = ""
email: str = ""
company: str = ""
sector: str = ""
source: str = "whatsapp_inbound"
message: str = ""
current_stage: str = "new_lead"
event_type: str = "inbound_message"
class BulkProcessRequest(BaseModel):
events: List[ProcessEventRequest]
@router.post("/process")
async def process_event(req: ProcessEventRequest) -> Dict[str, Any]:
"""Process a single event through the deal lifecycle state machine.
Returns: new_stage, actions, response_message_ar, human_approval_required.
If auto_send_allowed=True, the response can be sent automatically.
If human_approval_required=True, create a draft for Sami to review.
"""
from app.services.full_os_orchestrator import orchestrator, OrchestratorEvent
event = OrchestratorEvent(
lead_id=req.lead_id,
phone=req.phone,
email=req.email,
company=req.company,
sector=req.sector,
source=req.source,
message=req.message,
current_stage=req.current_stage,
event_type=req.event_type,
)
return orchestrator.process_event(event)
@router.post("/process-and-act")
async def process_and_act(req: ProcessEventRequest) -> Dict[str, Any]:
"""Process event AND execute the first safe action.
If auto_send_allowed: sends WhatsApp response via Ultramsg.
If human_approval_required: creates a draft for review.
Always logs the activity.
"""
from app.services.full_os_orchestrator import orchestrator, OrchestratorEvent
event = OrchestratorEvent(
lead_id=req.lead_id,
phone=req.phone,
email=req.email,
company=req.company,
sector=req.sector,
source=req.source,
message=req.message,
current_stage=req.current_stage,
event_type=req.event_type,
)
result = orchestrator.process_event(event)
execution = {"action_taken": "none", "send_result": None, "draft_created": False}
if result.get("auto_send_allowed") and result.get("response_message_ar") and req.phone:
if "send_whatsapp" in result.get("actions", []):
try:
from app.api.v1.outreach_engine import _send_via_ultramsg
send_result = await _send_via_ultramsg(req.phone, result["response_message_ar"])
execution = {
"action_taken": "whatsapp_sent",
"send_result": send_result,
"draft_created": False,
}
except Exception as exc:
execution = {
"action_taken": "whatsapp_failed",
"error": str(exc)[:200],
"draft_created": False,
}
elif result.get("human_approval_required") and result.get("response_message_ar"):
try:
from app.models.outreach_draft import OutreachDraft
from app.database import async_session
async with async_session() as session:
draft = OutreachDraft(
batch_id=f"os_{result['lead_id']}",
company=req.company,
contact_phone=req.phone,
contact_email=req.email,
channel="whatsapp" if req.phone else "email",
subject=f"[{result['new_stage']}] {req.company}",
body=result["response_message_ar"],
sector=req.sector,
status="draft",
approval_required=True,
source="full_os_orchestrator",
)
session.add(draft)
await session.commit()
execution = {
"action_taken": "draft_created",
"draft_id": str(draft.id),
"draft_created": True,
}
except Exception:
execution = {"action_taken": "draft_failed", "draft_created": False}
return {**result, "execution": execution}
@router.post("/bulk-process")
async def bulk_process(req: BulkProcessRequest) -> Dict[str, Any]:
"""Process multiple events at once (for batch imports)."""
from app.services.full_os_orchestrator import orchestrator, OrchestratorEvent
results = []
for event_req in req.events:
event = OrchestratorEvent(
lead_id=event_req.lead_id,
phone=event_req.phone,
email=event_req.email,
company=event_req.company,
sector=event_req.sector,
source=event_req.source,
message=event_req.message,
current_stage=event_req.current_stage,
event_type=event_req.event_type,
)
results.append(orchestrator.process_event(event))
return {
"processed": len(results),
"results": results,
}
@router.get("/stages")
async def list_stages() -> Dict[str, Any]:
"""List all deal lifecycle stages with their possible transitions."""
from app.services.full_os_orchestrator import STAGE_TRANSITIONS, STAGE_AUTO_ACTIONS, STAGE_MESSAGES_AR
stages = {}
for stage, transitions in STAGE_TRANSITIONS.items():
stages[stage.value] = {
"transitions": {k: v.value for k, v in transitions.items()},
"auto_actions": [a.value for a in STAGE_AUTO_ACTIONS.get(stage, [])],
"message_template": STAGE_MESSAGES_AR.get(stage, ""),
}
return {"stages": stages, "total": len(stages)}