from __future__ import annotations import base64 import logging from typing import Any from agente_contas_tim.constants.ic_tags_enum import SADTag, TMDTag from agente_contas_tim.integrations.rct_policy import RCTOperation, rct_tags_for_attempt from agente_contas_tim.workflows.actions.registry import ( WorkflowRuntimeContext, workflow_action, ) from agente_contas_tim.workflows.runtime_types import ActionResult from agente_contas_tim.workflows.actions.common.helpers import ( _emit_ic, _normalize_bool, _result_failed_or_missing_data, _runtime_llm_callbacks, _runtime_llm_metadata, _to_dict, ) logger = logging.getLogger("agente_contas_tim.workflows.actions.tim_actions") @workflow_action("finalizar_atendimento_action") def finalizar_atendimento_action( state: dict[str, Any], params: dict[str, Any], runtime: WorkflowRuntimeContext, ) -> ActionResult: input_state = state.get("input", {}) if isinstance(state, dict) else {} if not isinstance(input_state, dict): input_state = {} _emit_ic(SADTag.FINALIZACAO, input_state) status = str(params.get("status", "")).strip() summary = str(params.get("summary", "")).strip() return ActionResult.ok( { "success": True, "status": status, "summary": summary, } ) @workflow_action("formatar_capability_resposta") def formatar_capability_resposta( state: dict[str, Any], params: dict[str, Any], runtime: WorkflowRuntimeContext, ) -> ActionResult: vars_state = state.get("vars", {}) if isinstance(state, dict) else {} source_node = str(params.get("source_node", "resolve")) payload = vars_state.get(source_node, {}) if not isinstance(payload, dict): payload = {} input_state = state.get("input", {}) if isinstance(state, dict) else {} msisdn = str(params.get("msisdn", input_state.get("msisdn", ""))) servico = str(params.get("servico", input_state.get("servico", ""))) nome_plano = str(params.get("nome_plano", input_state.get("nome_plano", ""))) tipo = str(params.get("tipo", "")).strip().lower() instrucoes = str(payload.get("content", "")).strip() mensagem = "" if tipo == "vas_estrategico": mensagem = ( f"O serviço {servico} já está incluso no seu plano " f"na linha de final {msisdn[-2:]} e não gera " "cobrança adicional na fatura." ) elif tipo == "valor_divergente": mensagem = ( f"Identificamos uma alteração no valor do plano " f"na linha de final {msisdn[-2:]}. Vou verificar " "os detalhes para você." ) elif tipo == "pro_rata": mensagem = ( f"A fatura da linha de final {msisdn[-2:]} possui uma " "cobrança proporcional (pro rata) porque houve mudança " "de plano durante o ciclo de faturamento. Na próxima " "fatura o valor volta ao normal do novo plano." ) elif tipo == "termino_desconto": _emit_ic(TMDTag.CHEGADA_FLUXO, input_state) plan_info = f" do plano {nome_plano}" if nome_plano else "" mensagem = ( f"O desconto de fidelidade{plan_info} vinculado à " f"linha de final {msisdn[-2:]} chegou ao fim. " "O período promocional contratado expirou e o valor " "do plano volta ao preço original." ) _emit_ic(TMDTag.FIM_FLUXO, input_state) response: dict[str, Any] = { "success": True, "instrucoes": instrucoes, "mensagem": mensagem, "msisdn": msisdn, } if servico: response["servico"] = servico if nome_plano: response["nome_plano"] = nome_plano return ActionResult.ok(response) @workflow_action("combinar_divergencia") def combinar_divergencia( state: dict[str, Any], params: dict[str, Any], runtime: WorkflowRuntimeContext, ) -> ActionResult: vars_state = state.get("vars", {}) if isinstance(state, dict) else {} divergence = vars_state.get("consultar_divergencia", {}) explanation = vars_state.get("invoice_explanation", {}) if not isinstance(divergence, dict): divergence = {} if not isinstance(explanation, dict): explanation = {} input_state = state.get("input", {}) if isinstance(state, dict) else {} msisdn = str(input_state.get("msisdn", "")) resumo = str( divergence.get("resumo") or divergence.get("mensagem") or "" ).strip() detalhes = divergence.get("detalhes", {}) explicacao = str(explanation.get("mensagem", "")).strip() success = bool(resumo or explicacao) mensagem = resumo or explicacao return ActionResult.ok( { "success": success, "resumo": resumo, "detalhes": detalhes if isinstance(detalhes, dict) else {}, "mensagem": mensagem, "explicacao": explicacao, "msisdn": msisdn, } ) @workflow_action("consultar_divergencia") def query_divergence( state: dict[str, Any], params: dict[str, Any], runtime: WorkflowRuntimeContext, ) -> ActionResult: """Consulta resumo de divergência da fatura.""" result = runtime.factory.create_divergence( msisdn=str(params["msisdn"]), ).execute() if _result_failed_or_missing_data(result, state=state): return ActionResult.fail( result.error or "Falha na consulta de divergencia", **result.metadata, ) return ActionResult.ok(_to_dict(result.data), **result.metadata) @workflow_action("resolve_capability") def resolve_capability_action( state: dict[str, Any], params: dict[str, Any], runtime: WorkflowRuntimeContext, ) -> ActionResult: """Resolve uma capability via LLM Gateway (sem invocar LLM).""" if runtime.llm_gateway is None: return ActionResult.ok({"content": "", "source": "unavailable"}) capability_id = str(params.get("capability_id", "")).strip() if not capability_id: return ActionResult.fail( "capability_id obrigatorio para resolve_capability", ) try: resolved = runtime.llm_gateway.resolve_capability( capability_id=capability_id, ) return ActionResult.ok({ "capability_id": resolved.capability_id, "prompt_id": resolved.prompt_id, "content": resolved.content, "source": resolved.source, "version": resolved.version, }) except Exception as exc: return ActionResult.ok( {"content": "", "source": "fallback", "error": str(exc)}, ) @workflow_action("no_op") def no_op( state: dict[str, Any], params: dict[str, Any], runtime: WorkflowRuntimeContext, ) -> ActionResult: return ActionResult.ok({}) @workflow_action("llm_capability") def llm_capability( state: dict[str, Any], params: dict[str, Any], runtime: WorkflowRuntimeContext, ) -> ActionResult: if runtime.llm_gateway is None: return ActionResult.fail("LLM gateway nao configurado") capability_id = str(params.get("capability_id", "")).strip() if not capability_id: return ActionResult.fail("capability_id obrigatorio para llm_capability") variables = params.get("variables", {}) if not isinstance(variables, dict): return ActionResult.fail("variables deve ser um objeto") user_text_raw = params.get("user_text") user_text = None if user_text_raw is None else str(user_text_raw) result = runtime.llm_gateway.execute( capability_id=capability_id, variables=variables, user_text=user_text, callbacks=_runtime_llm_callbacks(runtime), tags=["workflow_action"], metadata=_runtime_llm_metadata(runtime), ) return ActionResult.ok( { "capability_id": result.capability_id, "prompt_id": result.prompt_id, "content": result.content, "source": result.source, "version": result.version, "metadata": dict(result.metadata), } ) @workflow_action("buscar_fatura") def buscar_fatura( state: dict[str, Any], params: dict[str, Any], runtime: WorkflowRuntimeContext, ) -> ActionResult: """Busca fatura (PDF) do cliente ou dados interpretados.""" input_state = state.get("input", {}) if isinstance(state, dict) else {} if not isinstance(input_state, dict): input_state = {} tentativa = int(params.get("tentativa_anterior") or 0) + 1 result = runtime.factory.create_bill_pdf( invoice_id=str(params["invoice_id"]), msisdn=str(params["msisdn"]), customer_id=str(params["customer_id"]), output=str(params.get("output", "")), include_danfe=_normalize_bool(params.get("include_danfe", False)), ).execute() if _result_failed_or_missing_data(result, state=state): for tag in rct_tags_for_attempt(RCTOperation.PDF_FATURA, tentativa, success=False): _emit_ic(tag, input_state) return ActionResult.fail( result.error or "Falha ao buscar fatura", **result.metadata, ) for tag in rct_tags_for_attempt(RCTOperation.PDF_FATURA, tentativa, success=True): _emit_ic(tag, input_state) payload = _to_dict(result.data) if isinstance(payload, dict): file_content = payload.get("file_content") if isinstance(file_content, (bytes, bytearray)): payload["file_content_b64"] = base64.b64encode( bytes(file_content) ).decode("ascii") payload["file_content"] = None return ActionResult.ok(payload, **result.metadata) __all__ = [ 'finalizar_atendimento_action', 'formatar_capability_resposta', 'combinar_divergencia', 'query_divergence', 'resolve_capability_action', 'no_op', 'llm_capability', 'buscar_fatura', ]