import hashlib import re from app.action_decider import decide_action from app.action_mapper import map_action_decision from app.config import settings from app.persistence import save_action_run from app.persistence import save_inbound_message from app.schemas import ActionDecision, AnalyzeRequest, AnalyzeResponse, UsageInfo from app.task_service import create_task_from_action_result async def analyze( request: AnalyzeRequest, raw_event_id: str | None = None, source_event_id: str | None = None, ) -> AnalyzeResponse: needs_review = False # The inbound fact is durable before any LLM/policy decision. This keeps # ignored classifications and decision failures visible and replay-safe. message_id, source_event_id = save_inbound_message( request=request, raw_event_id=raw_event_id, source_event_id=source_event_id, raw_body=request.last_customer_message, clean_body=request.last_customer_message, ) if (request.source or "").lower() == "chatwoot" and source_event_id: from app.communication_service import upsert_chatwoot_inbound_communication upsert_chatwoot_inbound_communication( source_message_id=source_event_id, conversation_id=request.conversation_id, contact_id=request.contact_id, body=request.last_customer_message, metadata={"raw_event_id": raw_event_id, "message_id": message_id}, ) try: decision, action_result, usage, decision_source = await decide_action(request) except Exception as exc: needs_review = True decision = ActionDecision( action_code="REVIEW_MANUALLY", note=f"Falha na decisão Action Core: {exc}", confidence=0.0, ) action_result = map_action_decision(decision) usage = UsageInfo( id=None, model=settings.openrouter_model, provider="error_fallback", prompt_tokens=0, completion_tokens=0, total_tokens=0, cost=0.0, ) decision_source = "fallback" if not action_result.safe_to_post: needs_review = True is_ignored_bounce = str(action_result.action_code or "").upper() == "IGNORE_BOUNCE" action_run_id, message_id = save_action_run( request=request, action_decision=decision, action_result=action_result, usage=usage, needs_review=needs_review, model=settings.openrouter_model, decision_source=decision_source, raw_body=request.last_customer_message, clean_body=request.last_customer_message, raw_event_id=raw_event_id, source_event_id=source_event_id, message_id=message_id, ) normalized_message_for_idem = re.sub(r"\s+", " ", str(request.last_customer_message or "").strip().casefold()) message_fingerprint = hashlib.sha256(normalized_message_for_idem.encode("utf-8")).hexdigest() if normalized_message_for_idem else "" task_id = create_task_from_action_result( action_result=action_result, action_run_id=action_run_id, message_id=message_id, raw_event_id=raw_event_id, conversation_id=request.conversation_id, contact_id=request.contact_id, source_system=request.source or "manual", source_event_id=source_event_id or message_id, metadata={ "created_from_analyzer": True, "content_fingerprint": message_fingerprint, "normalized_message_preview": normalized_message_for_idem[:240], "needs_review": needs_review, "decision_source": decision_source, "llm_customer_intent": getattr(decision, "customer_intent", ""), "llm_evidence": getattr(decision, "evidence", ""), "llm_history_used": getattr(decision, "history_used", False), "payment_intent": getattr(decision, "payment_intent", None), "llm_confidence": getattr(decision, "confidence", 0.0), }, ) if (request.source or "").lower() == "chatwoot" and source_event_id: from app.communication_service import enrich_chatwoot_communication_from_task action_code = str(action_result.action_code or "").upper() enrich_chatwoot_communication_from_task( source_message_id=source_event_id, classification=action_code, confidence=float(getattr(decision, "confidence", 0.0) or 0.0), ignored=is_ignored_bounce or action_code in {"IGNORE_SPAM", "SPAM"}, conversation_id=request.conversation_id, contact_id=request.contact_id, body=request.last_customer_message, task_id=task_id, metadata={"raw_event_id": raw_event_id, "message_id": message_id, "action_run_id": action_run_id, "decision_source": decision_source}, ) return AnalyzeResponse( app="ClientFlow", model=settings.openrouter_model, action_decision=decision, action_result=action_result, usage=usage, needs_review=needs_review, action_run_id=action_run_id, message_id=message_id, task_id=task_id, )