system-prompts-and-models-o.../salesflow-saas/backend/app/api/v1/full_os.py
Claude 3e11da4a5a
feat(dealix): multi-provider WhatsApp with auto-fallback chain
4 WhatsApp providers with automatic fallback:
1. Green API (green-api.com) — free dev tier, simplest setup
2. Ultramsg (ultramsg.com) — existing integration, cleaned
3. Fonnte (fonnte.com) — ultra-cheap alternative
4. Meta Cloud API (official) — most reliable, needs verification

send_whatsapp_smart() tries each configured provider in order
until one succeeds. No hardcoded credentials (removed leaked
Ultramsg token from outreach_engine.py).

New endpoints:
- GET /os/whatsapp-providers — check which are configured
- POST /os/test-send — test send via smart chain

Full OS /os/process-and-act now uses smart multi-provider
instead of Ultramsg-only.

Env vars per provider:
- GREEN_API_INSTANCE_ID + GREEN_API_TOKEN
- ULTRAMSG_INSTANCE_ID + ULTRAMSG_TOKEN
- FONNTE_TOKEN
- WHATSAPP_API_TOKEN + WHATSAPP_PHONE_NUMBER_ID

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

184 lines
6.5 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.services.whatsapp_multi_provider import send_whatsapp_smart
send_result = await send_whatsapp_smart(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("/whatsapp-providers")
async def whatsapp_provider_status() -> Dict[str, Any]:
"""Check which WhatsApp providers are configured."""
from app.services.whatsapp_multi_provider import check_providers
return await check_providers()
@router.post("/test-send")
async def test_whatsapp_send(phone: str, message: str = "اختبار Dealix — النظام شغّال 🚀") -> Dict[str, Any]:
"""Test WhatsApp send via all configured providers."""
from app.services.whatsapp_multi_provider import send_whatsapp_smart
return await send_whatsapp_smart(phone, message)
@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)}