Files
first_contas/legacy_reference/workflows/actions/common/actions.py
2026-06-16 20:54:49 -03:00

305 lines
9.8 KiB
Python

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',
]