mirror of
https://github.com/hoshikawa2/first_contas.git
synced 2026-07-09 10:14:20 +00:00
305 lines
9.8 KiB
Python
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',
|
|
]
|