system-prompts-and-models-o.../dealix/api/routers/personal_operator.py
2026-05-01 14:03:52 +03:00

147 lines
5.6 KiB
Python

"""Arabic Personal Strategic Operator endpoints."""
from __future__ import annotations
from typing import Any
from fastapi import APIRouter, Body, HTTPException
from auto_client_acquisition.personal_operator import (
ApprovalDecision,
build_daily_brief,
default_sami_profile,
draft_follow_up,
draft_intro_message,
suggest_opportunities,
)
from auto_client_acquisition.personal_operator.launch_report import build_launch_report
from auto_client_acquisition.personal_operator.operator import apply_decision, launch_readiness_score
from auto_client_acquisition.v3.project_intelligence import answer_operator_question, explain_project_intelligence_stack
router = APIRouter(prefix="/api/v1/personal-operator", tags=["personal-operator"])
def _opportunity_by_id(opportunity_id: str):
for opportunity in suggest_opportunities(default_sami_profile()):
if opportunity.id == opportunity_id:
return opportunity
opportunities = suggest_opportunities(default_sami_profile())
return opportunities[0] if opportunities else None
def _parse_decision(raw: Any) -> ApprovalDecision:
try:
return ApprovalDecision(str(raw).lower().strip())
except ValueError:
raise HTTPException(status_code=400, detail="invalid_decision") from None
@router.get("/daily-brief")
async def daily_brief() -> dict[str, Any]:
"""Arabic executive daily brief for Sami."""
return build_daily_brief(default_sami_profile()).to_dict()
@router.get("/opportunities")
async def opportunities() -> dict[str, Any]:
items = suggest_opportunities(default_sami_profile())
return {"count": len(items), "items": [item.to_card() for item in items]}
@router.post("/opportunities")
async def create_contextual_opportunities(body: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]:
"""Return operator opportunities with optional context."""
items = suggest_opportunities(default_sami_profile())
return {
"context_received": body,
"count": len(items),
"items": [item.to_card() for item in items],
}
@router.post("/opportunities/{opportunity_id}/decision")
async def decide_opportunity(opportunity_id: str, body: dict[str, Any] = Body(...)) -> dict[str, Any]:
opportunity = _opportunity_by_id(opportunity_id)
if not opportunity:
raise HTTPException(status_code=404, detail="opportunity_not_found")
decision = _parse_decision(body.get("decision", "draft"))
result = apply_decision(opportunity, decision)
approval_required = bool(result.get("approval_required", decision != ApprovalDecision.SKIP))
next_action = str(result.get("next_action", "none"))
return {
"opportunity": opportunity.to_card(),
"decision": decision.value,
"result": result,
"approval_required": approval_required,
"next_action": next_action,
}
@router.post("/messages/draft")
async def draft_message(body: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]:
opportunities = suggest_opportunities(default_sami_profile())
selected = opportunities[0]
if body.get("opportunity_id"):
selected = _opportunity_by_id(str(body["opportunity_id"])) or selected
tone = str(body.get("tone", "warm"))
return draft_intro_message(selected, tone=tone)
@router.post("/followups/draft")
async def followup(body: dict[str, Any] = Body(...)) -> dict[str, Any]:
return draft_follow_up(
meeting_title=str(body.get("meeting_title", "اجتماع Dealix")),
outcome=str(body.get("outcome", "اتفقنا على مراجعة الفكرة وإرسال ملخص")),
next_step=str(body.get("next_step", "إرسال ملخص تنفيذي وتجربة قصيرة")),
)
@router.get("/project/intelligence")
async def project_intelligence() -> dict[str, Any]:
return explain_project_intelligence_stack()
@router.post("/project/ask")
async def ask_project(body: dict[str, Any] = Body(...)) -> dict[str, Any]:
question = str(body.get("question", "وش ناقص المشروع؟"))
deep = bool(body.get("deep_scan", False))
root = str(body.get("root", "."))
answered = answer_operator_question(question, root=root, deep_scan=deep)
readiness = launch_readiness_score()
return {
"question": question,
"answer_ar": answered["answer_ar"],
"semantic_status_ar": answered["semantic_status_ar"],
"related_files": answered["related_files"],
"search_hits": answered.get("search_hits", []),
"launch_readiness": readiness,
}
@router.get("/launch-readiness")
async def launch_readiness() -> dict[str, Any]:
return launch_readiness_score()
@router.get("/launch-report")
async def launch_report() -> dict[str, Any]:
return build_launch_report().to_dict()
@router.post("/meetings/schedule-draft")
async def schedule_draft(body: dict[str, Any] = Body(default_factory=dict)) -> dict[str, Any]:
return {
"status": "calendar_draft_ready",
"approval_required": True,
"title": body.get("title", "Dealix Strategic Intro"),
"duration": int(body.get("duration_minutes", 30)),
"duration_minutes": int(body.get("duration_minutes", 30)),
"agenda_ar": [
"تعريف سريع بـ Dealix",
"أخذ رأي الشخص في التموضع والسوق",
"تحديد فرصة تعاون أو intro قادمة",
],
"note_ar": "هذا المسار يجهز payload الاجتماع فقط. إنشاء حدث في Google Calendar يتطلب موافقة صريحة وطبقة تكامل.",
"note": "This endpoint prepares the meeting payload. Actual Google Calendar creation should only happen after approval.",
}