Import ClientFlow production v4928.1.5.132.4

This commit is contained in:
plx
2026-07-29 13:11:01 +00:00
parent 6445044ac6
commit 261d342057
405 changed files with 48373 additions and 1401 deletions

569
app/message_templates.py Normal file
View File

@@ -0,0 +1,569 @@
"""Commercial reply templates for ClientFlow v4928.1.5.0.
Templates are deterministic and safe by default. The opportunity/documents are
source of truth; optional LLM wording can adapt these drafts without choosing
prices, stock, fiscal documents or attachments.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Dict, Iterable, List, Optional
@dataclass(frozen=True)
class MessageTemplate:
code: str
name: str
category: str
compatible_actions: tuple[str, ...]
body: str
expected_document_kinds: tuple[str, ...] = field(default_factory=tuple)
requires_opportunity: bool = True
requires_conversation: bool = True
requires_fiscal_customer: bool = False
complete_note: str = "Resposta enviada ao cliente via ClientFlow."
reply_type: str = "send_document"
def expects_documents(self) -> bool:
return bool(self.expected_document_kinds)
TEMPLATES: Dict[str, MessageTemplate] = {
"BUSINESS_KNOWLEDGE_REPLY": MessageTemplate(
code="BUSINESS_KNOWLEDGE_REPLY",
name="Resposta com conhecimento BLIF",
category="conhecimento",
compatible_actions=("SEND_INFO", "SEND_QUOTE", "SEND_PROFORMA", "SEND_INVOICE", "CONFIRM_PAYMENT", "PREPARE_ORDER", "REVIEW_MANUALLY"),
body=(
"Olá {customer_first_name},\n\n"
"Segue a resposta com base no conhecimento comercial da BLIF.\n\n"
"Cumprimentos,\nEquipa BLIF"
),
requires_opportunity=False,
requires_fiscal_customer=False,
complete_note="Esclarecimento enviado ao cliente.",
reply_type="answer_without_attachment",
),
"LLM_BUSINESS_REPLY": MessageTemplate(
code="LLM_BUSINESS_REPLY",
name="Resposta LLM com conhecimento BLIF",
category="llm",
compatible_actions=("SEND_INFO", "SEND_QUOTE", "SEND_PROFORMA", "SEND_INVOICE", "CONFIRM_PAYMENT", "PREPARE_ORDER", "SUPPORT", "REVIEW_MANUALLY", "MARK_NO_INTEREST", "REMOVE_FROM_LIST"),
body=(
"Olá {customer_first_name},\n\n"
"Vou analisar o pedido e responder com base no contexto e conhecimento aprovado da BLIF.\n\n"
"Cumprimentos,\nEquipa BLIF"
),
requires_opportunity=False,
requires_conversation=False,
requires_fiscal_customer=False,
complete_note="Resposta assistida por LLM preparada para revisão.",
reply_type="llm_assisted",
),
"CLARIFY_INSTALLATION_SCOPE": MessageTemplate(
code="CLARIFY_INSTALLATION_SCOPE",
name="Esclarecer instalação / âmbito do preço",
category="conhecimento",
compatible_actions=("SEND_INFO", "SEND_QUOTE", "REVIEW_MANUALLY"),
body=(
"Olá {customer_first_name},\n\n"
"Sim, confirmo que o valor indicado é referente ao equipamento, sem instalação incluída.\n\n"
"A BLIF não presta serviço direto de instalação, mas o equipamento é de instalação simples e pode ser instalado por qualquer eletricista qualificado. Se necessário, prestamos suporte remoto ao eletricista.\n\n"
"Cumprimentos,\nEquipa BLIF"
),
requires_opportunity=False,
requires_fiscal_customer=False,
complete_note="Esclarecimento sobre instalação enviado ao cliente.",
reply_type="answer_without_attachment",
),
"CLARIFY_VAT_POLICY": MessageTemplate(
code="CLARIFY_VAT_POLICY",
name="Esclarecer IVA",
category="conhecimento",
compatible_actions=("SEND_INFO", "SEND_QUOTE", "SEND_PROFORMA", "SEND_INVOICE", "REVIEW_MANUALLY"),
body=(
"Olá {customer_first_name},\n\n"
"Os valores indicados nas nossas comunicações comerciais são apresentados sem IVA. Na fatura final, os valores surgem com IVA incluído.\n\n"
"Cumprimentos,\nEquipa BLIF"
),
requires_opportunity=False,
requires_fiscal_customer=False,
complete_note="Esclarecimento sobre IVA enviado ao cliente.",
reply_type="answer_without_attachment",
),
"CLARIFY_DELIVERY_PAYMENT": MessageTemplate(
code="CLARIFY_DELIVERY_PAYMENT",
name="Esclarecer entrega/pagamento",
category="conhecimento",
compatible_actions=("SEND_INFO", "SEND_QUOTE", "SEND_PROFORMA", "CONFIRM_PAYMENT", "PREPARE_ORDER", "REVIEW_MANUALLY"),
body=(
"Olá {customer_first_name},\n\n"
"A condição padrão é pronto pagamento. Após confirmação do pagamento, a entrega estimada é de 23 dias úteis.\n\n"
"Cumprimentos,\nEquipa BLIF"
),
requires_opportunity=False,
requires_fiscal_customer=False,
complete_note="Esclarecimento sobre entrega/pagamento enviado ao cliente.",
reply_type="answer_without_attachment",
),
"CLARIFY_RFID": MessageTemplate(
code="CLARIFY_RFID",
name="Esclarecer RFID",
category="conhecimento",
compatible_actions=("SEND_INFO", "SEND_QUOTE", "REVIEW_MANUALLY"),
body=(
"Olá {customer_first_name},\n\n"
"Sim, os carregadores BLIF são compatíveis com leitor RFID. O leitor RFID tem o valor de 16 € s/IVA e permite associar cartões ilimitados para controlo de acessos e separação de consumos por utilizador.\n\n"
"Cumprimentos,\nEquipa BLIF"
),
requires_opportunity=False,
requires_fiscal_customer=False,
complete_note="Esclarecimento sobre RFID enviado ao cliente.",
reply_type="answer_without_attachment",
),
"CLARIFY_LOAD_BALANCER": MessageTemplate(
code="CLARIFY_LOAD_BALANCER",
name="Esclarecer balanceador",
category="conhecimento",
compatible_actions=("SEND_INFO", "SEND_QUOTE", "REVIEW_MANUALLY"),
body=(
"Olá {customer_first_name},\n\n"
"Os carregadores BLIF podem funcionar com balanceador dinâmico de carga no quadro elétrico. O balanceador ajusta automaticamente a potência disponível e ajuda a evitar sobrecargas ou disparos do disjuntor.\n\n"
"Em edifícios com vários pisos ou estrutura densa, recomendamos a versão com fios (Modbus).\n\n"
"Cumprimentos,\nEquipa BLIF"
),
requires_opportunity=False,
requires_fiscal_customer=False,
complete_note="Esclarecimento sobre balanceador enviado ao cliente.",
reply_type="answer_without_attachment",
),
"CLARIFY_LOCAL_HISTORY": MessageTemplate(
code="CLARIFY_LOCAL_HISTORY",
name="Esclarecer histórico sem Internet",
category="conhecimento",
compatible_actions=("SEND_INFO", "SEND_QUOTE", "REVIEW_MANUALLY"),
body=(
"Olá {customer_first_name},\n\n"
"É possível consultar o histórico de carregamentos localmente, por WiFi/Bluetooth, mesmo sem Internet. O histórico pode incluir datas, duração e kWh por sessão; com RFID, permite separar por utilizador.\n\n"
"Nota: o histórico local pode perder-se em caso de reset/factory reset.\n\n"
"Cumprimentos,\nEquipa BLIF"
),
requires_opportunity=False,
requires_fiscal_customer=False,
complete_note="Esclarecimento sobre histórico local enviado ao cliente.",
reply_type="answer_without_attachment",
),
"CLARIFY_CONDOMINIUM_MOBIE": MessageTemplate(
code="CLARIFY_CONDOMINIUM_MOBIE",
name="Esclarecer condomínio / MOBI.E DPC",
category="conhecimento",
compatible_actions=("SEND_INFO", "SEND_QUOTE", "REVIEW_MANUALLY"),
body=(
"Olá {customer_first_name},\n\n"
"É viável instalar um carregador BLIF em garagem de condomínio, seja em box ou lugar partilhado, desde que exista ligação elétrica adequada.\n\n"
"Quando o objetivo é separar consumos da fatura do condomínio, pode ser analisada a integração MOBI.E em modo DPC, que permite registo de sessões e faturação ao utilizador.\n\n"
"Cumprimentos,\nEquipa BLIF"
),
requires_opportunity=False,
requires_fiscal_customer=False,
complete_note="Esclarecimento sobre condomínio/MOBI.E enviado ao cliente.",
reply_type="answer_without_attachment",
),
"CLARIFY_TECHNICAL_FEATURES": MessageTemplate(
code="CLARIFY_TECHNICAL_FEATURES",
name="Esclarecer funcionalidades técnicas",
category="conhecimento",
compatible_actions=("SEND_INFO", "SEND_QUOTE", "REVIEW_MANUALLY"),
body=(
"Olá {customer_first_name},\n\n"
"Os carregadores BLIF permitem controlo de potência entre 6 A e 32 A, gestão e monitorização através da app, controlo remoto e agendamento. O ajuste de corrente ajuda a evitar disparos do disjuntor quando a instalação está corretamente dimensionada.\n\n"
"Cumprimentos,\nEquipa BLIF"
),
requires_opportunity=False,
requires_fiscal_customer=False,
complete_note="Esclarecimento técnico enviado ao cliente.",
reply_type="answer_without_attachment",
),
"INTERNAL_BOUNCE_EMAIL": MessageTemplate(
code="INTERNAL_BOUNCE_EMAIL",
name="Bounce / email devolvido — não responder",
category="triagem",
compatible_actions=("SEND_INFO", "SEND_QUOTE", "REVIEW_MANUALLY"),
body=(
"Não enviar resposta ao cliente.\n\n"
"Esta mensagem aparenta ser uma devolução automática de email/bounce. "
"A ação recomendada é atualizar o contacto, validar o email e, se aplicável, remover o contacto de campanhas."
),
requires_opportunity=False,
requires_conversation=False,
requires_fiscal_customer=False,
complete_note="Bounce identificado; contacto deve ser revisto internamente.",
reply_type="no_customer_reply",
),
"MANUAL_REVIEW_REQUIRED": MessageTemplate(
code="MANUAL_REVIEW_REQUIRED",
name="Revisão manual — sem sugestão automática",
category="triagem",
compatible_actions=("SEND_INFO", "SEND_QUOTE", "SEND_PROFORMA", "SEND_INVOICE", "CONFIRM_PAYMENT", "PREPARE_ORDER", "REVIEW_MANUALLY"),
body=(
"Rever manualmente antes de responder.\n\n"
"O assistente não encontrou intenção suficientemente segura para gerar uma resposta comercial automática."
),
requires_opportunity=False,
requires_conversation=False,
requires_fiscal_customer=False,
complete_note="Tarefa marcada para revisão manual.",
reply_type="manual_internal",
),
"INTERNAL_AUTO_REPLY": MessageTemplate(
code="INTERNAL_AUTO_REPLY",
name="Resposta automática / ausência — não responder",
category="triagem",
compatible_actions=("SEND_INFO", "SEND_QUOTE", "REVIEW_MANUALLY"),
body=(
"Não enviar resposta ao cliente.\n\n"
"Esta mensagem aparenta ser uma resposta automática/ausência. A ação recomendada é aguardar ou rever manualmente, sem enviar conteúdo comercial."
),
requires_opportunity=False,
requires_conversation=False,
requires_fiscal_customer=False,
complete_note="Resposta automática identificada; sem resposta ao cliente.",
reply_type="no_customer_reply",
),
"ACK_UNSUBSCRIBE": MessageTemplate(
code="ACK_UNSUBSCRIBE",
name="Confirmar remoção da lista",
category="triagem",
compatible_actions=("SEND_INFO", "SEND_QUOTE", "REVIEW_MANUALLY"),
body=(
"Olá {customer_first_name},\n\n"
"Confirmamos a receção do pedido e vamos remover/atualizar o contacto para não receber novas comunicações comerciais.\n\n"
"Cumprimentos,\nEquipa BLIF"
),
requires_opportunity=False,
requires_fiscal_customer=False,
complete_note="Pedido de remoção/privacidade respondido ao cliente.",
reply_type="operational_ack",
),
"ACK_ADDRESS_UPDATE": MessageTemplate(
code="ACK_ADDRESS_UPDATE",
name="Confirmar atualização de contacto",
category="triagem",
compatible_actions=("SEND_INFO", "SEND_QUOTE", "REVIEW_MANUALLY"),
body=(
"Olá {customer_first_name},\n\n"
"Obrigado pela informação. Vamos atualizar o contacto nos nossos registos.\n\n"
"Cumprimentos,\nEquipa BLIF"
),
requires_opportunity=False,
requires_fiscal_customer=False,
complete_note="Atualização de contacto confirmada ao cliente.",
reply_type="operational_ack",
),
"ACK_NO_INTEREST": MessageTemplate(
code="ACK_NO_INTEREST",
name="Responder sem interesse",
category="triagem",
compatible_actions=("SEND_INFO", "SEND_QUOTE", "REVIEW_MANUALLY"),
body=(
"Olá {customer_first_name},\n\n"
"Obrigado pelo feedback. Registamos a informação e não insistiremos neste contacto comercial.\n\n"
"Cumprimentos,\nEquipa BLIF"
),
requires_opportunity=False,
requires_fiscal_customer=False,
complete_note="Sem interesse registado e respondido ao cliente.",
reply_type="operational_ack",
),
"ACK_SUPPORT_RECEIVED": MessageTemplate(
code="ACK_SUPPORT_RECEIVED",
name="Acusar receção de suporte/incidente",
category="suporte",
compatible_actions=("SEND_INFO", "SEND_QUOTE", "REVIEW_MANUALLY", "PREPARE_ORDER"),
body=(
"Olá {customer_first_name},\n\n"
"Lamentamos a situação. Vamos verificar internamente o estado do processo e dar seguimento com prioridade.\n\n"
"Cumprimentos,\nEquipa BLIF"
),
requires_opportunity=False,
requires_fiscal_customer=False,
complete_note="Incidente/suporte acusado ao cliente.",
reply_type="support_ack",
),
"ACK_CALLBACK_REQUEST": MessageTemplate(
code="ACK_CALLBACK_REQUEST",
name="Confirmar pedido de contacto",
category="triagem",
compatible_actions=("SEND_INFO", "SEND_QUOTE", "REVIEW_MANUALLY"),
body=(
"Olá {customer_first_name},\n\n"
"Obrigado pelo contacto. Vamos analisar o pedido e responder/entrar em contacto assim que possível.\n\n"
"Cumprimentos,\nEquipa BLIF"
),
requires_opportunity=False,
requires_fiscal_customer=False,
complete_note="Pedido de contacto acusado ao cliente.",
reply_type="operational_ack",
),
"SEND_INFO_EQUIPMENT_LIST": MessageTemplate(
code="SEND_INFO_EQUIPMENT_LIST",
name="Enviar lista de equipamentos",
category="informacao",
compatible_actions=("SEND_INFO", "SEND_QUOTE", "REVIEW_MANUALLY"),
body=(
"Olá {customer_first_name},\n\n"
"Segue abaixo a lista de equipamentos disponíveis:\n\n"
"- Carregador EV monofásico 7,4 kW\n"
"- Carregador EV trifásico 11 kW\n"
"- Carregador EV trifásico 22 kW\n\n"
"Todos os equipamentos incluem fatura, garantia e apoio técnico.\n\n"
"Cumprimentos,\nEquipa BLIF"
),
requires_opportunity=False,
requires_fiscal_customer=False,
complete_note="Lista de equipamentos enviada ao cliente.",
reply_type="answer_without_attachment",
),
"SEND_PRICE_LIST": MessageTemplate(
code="SEND_PRICE_LIST",
name="Enviar preços indicativos",
category="informacao",
compatible_actions=("SEND_INFO", "SEND_QUOTE", "REVIEW_MANUALLY"),
body=(
"Olá {customer_first_name},\n\n"
"Segue a informação de preços disponível para os equipamentos solicitados.\n\n"
"Os valores finais devem ser confirmados no orçamento/proposta antes de adjudicação.\n\n"
"Cumprimentos,\nEquipa BLIF"
),
requires_opportunity=False,
requires_fiscal_customer=False,
complete_note="Preços indicativos enviados ao cliente.",
reply_type="answer_without_attachment",
),
"SEND_QUOTE": MessageTemplate(
code="SEND_QUOTE",
name="Enviar orçamento/proposta",
category="proposta",
compatible_actions=("SEND_QUOTE", "SEND_INFO", "REVIEW_MANUALLY"),
# SEND_QUOTE pode ser uma proposta textual quando ainda não existe ORC.*.
# Se o operador selecionar um documento, validamos que é quotation.
expected_document_kinds=("quotation",),
requires_fiscal_customer=False,
body=(
"Olá {customer_first_name},\n\n"
"Segue abaixo a proposta/preços indicativos para o pedido.\n\n"
"Se pretender avançar, confirmamos os dados necessários e preparamos o orçamento formal.\n\n"
"Cumprimentos,\nEquipa BLIF"
),
complete_note="Orçamento/proposta enviado ao cliente.",
reply_type="send_document_or_text_quote",
),
"SEND_PROFORMA": MessageTemplate(
code="SEND_PROFORMA",
name="Enviar orçamento para pagamento (ORC Jasmin)",
category="documento",
compatible_actions=("SEND_PROFORMA", "SEND_INVOICE", "REVIEW_MANUALLY"),
# Regra BLIF/ClientFlow: a orçamento para pagamento operacional é o orçamento Jasmin ORC.*.
# Não exigir um tipo documental separado "proforma"; o documento correto é quotation.
expected_document_kinds=("quotation",),
requires_fiscal_customer=True,
body=(
"Olá {customer_first_name},\n\n"
"Segue em anexo o orçamento {document_numbers} para pagamento e avanço da encomenda.\n\n"
"Após o pagamento, envie por favor o comprovativo para confirmação e preparação da encomenda.\n\n"
"Cumprimentos,\nEquipa BLIF"
),
complete_note="Orçamento para pagamento/ORC Jasmin enviada ao cliente.",
reply_type="send_document",
),
"SEND_INVOICE": MessageTemplate(
code="SEND_INVOICE",
name="Enviar fatura",
category="documento",
compatible_actions=("SEND_INVOICE", "SEND_PROFORMA", "REVIEW_MANUALLY"),
expected_document_kinds=("invoice",),
requires_fiscal_customer=True,
body=(
"Olá {customer_first_name},\n\n"
"Segue em anexo a fatura {document_numbers}.\n\n"
"Qualquer questão, estamos ao dispor.\n\n"
"Cumprimentos,\nEquipa BLIF"
),
complete_note="Fatura enviada ao cliente.",
reply_type="send_document",
),
"REQUEST_FISCAL_DATA": MessageTemplate(
code="REQUEST_FISCAL_DATA",
name="Pedir dados fiscais",
category="dados",
compatible_actions=("SEND_PROFORMA", "SEND_INVOICE", "SEND_QUOTE", "REVIEW_MANUALLY"),
body=(
"Olá {customer_first_name},\n\n"
"Para avançarmos corretamente, envie por favor os dados fiscais para faturação:\n\n"
"- Nome fiscal\n"
"- NIF\n"
"- Morada fiscal\n"
"- Email para envio do documento\n\n"
"Cumprimentos,\nEquipa BLIF"
),
requires_opportunity=False,
requires_fiscal_customer=False,
complete_note="Pedido de dados fiscais enviado ao cliente.",
reply_type="request_missing_data",
),
"REQUEST_PAYMENT_PROOF": MessageTemplate(
code="REQUEST_PAYMENT_PROOF",
name="Pedir comprovativo de pagamento",
category="pagamento",
compatible_actions=("CONFIRM_PAYMENT", "SEND_PROFORMA", "REVIEW_MANUALLY"),
body=(
"Olá {customer_first_name},\n\n"
"Para conseguirmos confirmar o processo, envie por favor o comprovativo de pagamento.\n\n"
"Assim que recebermos a confirmação, damos seguimento à encomenda.\n\n"
"Cumprimentos,\nEquipa BLIF"
),
requires_opportunity=False,
requires_fiscal_customer=False,
complete_note="Pedido de comprovativo de pagamento enviado ao cliente.",
reply_type="request_missing_data",
),
"CONFIRM_PAYMENT_RECEIVED": MessageTemplate(
code="CONFIRM_PAYMENT_RECEIVED",
name="Confirmar pagamento recebido",
category="pagamento",
compatible_actions=("CONFIRM_PAYMENT", "PREPARE_ORDER", "REVIEW_MANUALLY"),
body=(
"Olá {customer_first_name},\n\n"
"Confirmamos a receção do pagamento.\n\n"
"Vamos dar seguimento à preparação da encomenda e manteremos o contacto com a atualização do envio.\n\n"
"Cumprimentos,\nEquipa BLIF"
),
requires_opportunity=False,
requires_fiscal_customer=False,
complete_note="Pagamento confirmado ao cliente.",
reply_type="answer_without_attachment",
),
"FOLLOW_UP_QUOTE": MessageTemplate(
code="FOLLOW_UP_QUOTE",
name="Follow-up de orçamento/proposta",
category="followup",
compatible_actions=("FOLLOW_UP_QUOTE", "FOLLOW_UP_GENERIC", "SEND_QUOTE", "REVIEW_MANUALLY"),
expected_document_kinds=("quotation",),
body=(
"Olá {customer_first_name},\n\n"
"Gostaria apenas de confirmar se recebeu a proposta/orçamento {document_numbers} "
"e se ficou com alguma dúvida.\n\n"
"Caso pretenda avançar, posso também enviar os dados para emissão da orçamento para pagamento.\n\n"
"Cumprimentos,\nEquipa BLIF"
),
requires_opportunity=False,
requires_conversation=False,
requires_fiscal_customer=False,
complete_note="Follow-up de orçamento/proposta enviado ao cliente.",
reply_type="follow_up",
),
"FOLLOW_UP_PROFORMA": MessageTemplate(
code="FOLLOW_UP_PROFORMA",
name="Follow-up de orçamento para pagamento",
category="followup",
compatible_actions=("FOLLOW_UP_PROFORMA", "FOLLOW_UP_PAYMENT", "FOLLOW_UP_GENERIC", "SEND_PROFORMA", "REVIEW_MANUALLY"),
expected_document_kinds=("proforma", "invoice", "quotation"),
body=(
"Olá {customer_first_name},\n\n"
"Gostaria apenas de confirmar se recebeu a orçamento/dados de pagamento {document_numbers} "
"e se precisa de alguma informação adicional para avançar.\n\n"
"Cumprimentos,\nEquipa BLIF"
),
requires_opportunity=False,
requires_conversation=False,
requires_fiscal_customer=False,
complete_note="Follow-up de orçamento para pagamento enviado ao cliente.",
reply_type="follow_up",
),
"FOLLOW_UP_PAYMENT": MessageTemplate(
code="FOLLOW_UP_PAYMENT",
name="Follow-up de pagamento",
category="followup",
compatible_actions=("FOLLOW_UP_PAYMENT", "FOLLOW_UP_PROFORMA", "CONFIRM_PAYMENT", "REVIEW_MANUALLY"),
expected_document_kinds=("proforma", "invoice", "quotation"),
body=(
"Olá {customer_first_name},\n\n"
"Gostaria apenas de confirmar se recebeu a orçamento/dados de pagamento {document_numbers} "
"e se precisa de alguma informação adicional para avançar com o pagamento.\n\n"
"Cumprimentos,\nEquipa BLIF"
),
requires_opportunity=False,
requires_conversation=False,
requires_fiscal_customer=False,
complete_note="Follow-up de pagamento enviado ao cliente.",
reply_type="follow_up",
),
"FOLLOW_UP_CUSTOMER_REVIEW": MessageTemplate(
code="FOLLOW_UP_CUSTOMER_REVIEW",
name="Follow-up de interesse/revisão",
category="followup",
compatible_actions=("FOLLOW_UP_CUSTOMER_REVIEW", "FOLLOW_UP_GENERIC", "SEND_INFO", "REVIEW_MANUALLY"),
body=(
"Olá {customer_first_name},\n\n"
"Gostaria apenas de confirmar se mantém interesse no carregador e se precisa de alguma informação adicional.\n\n"
"Cumprimentos,\nEquipa BLIF"
),
requires_opportunity=False,
requires_conversation=False,
requires_fiscal_customer=False,
complete_note="Follow-up de interesse enviado ao cliente.",
reply_type="follow_up",
),
"FOLLOW_UP_GENERIC": MessageTemplate(
code="FOLLOW_UP_GENERIC",
name="Follow-up genérico",
category="followup",
compatible_actions=("FOLLOW_UP_GENERIC", "FOLLOW_UP_QUOTE", "FOLLOW_UP_PAYMENT", "FOLLOW_UP_CUSTOMER_REVIEW", "REVIEW_MANUALLY"),
body=(
"Olá {customer_first_name},\n\n"
"Gostaria apenas de dar seguimento ao processo e confirmar se precisa de alguma informação adicional.\n\n"
"Cumprimentos,\nEquipa BLIF"
),
requires_opportunity=False,
requires_conversation=False,
requires_fiscal_customer=False,
complete_note="Follow-up enviado ao cliente.",
reply_type="follow_up",
),
}
# Intent gate fallback anchor: "SEND_INFO": "MANUAL_REVIEW_REQUIRED"
DEFAULT_TEMPLATE_BY_ACTION: Dict[str, str] = {
"SEND_INFO": "SEND_INFO_EQUIPMENT_LIST",
"SEND_QUOTE": "SEND_QUOTE",
"SEND_PROFORMA": "SEND_PROFORMA",
"SEND_INVOICE": "SEND_INVOICE",
"CONFIRM_PAYMENT": "REQUEST_PAYMENT_PROOF",
"PREPARE_ORDER": "CONFIRM_PAYMENT_RECEIVED",
"FOLLOW_UP_QUOTE": "FOLLOW_UP_QUOTE",
"FOLLOW_UP_PROFORMA": "FOLLOW_UP_PROFORMA",
"FOLLOW_UP_PAYMENT": "FOLLOW_UP_PAYMENT",
"FOLLOW_UP_CUSTOMER_REVIEW": "FOLLOW_UP_CUSTOMER_REVIEW",
"FOLLOW_UP_GENERIC": "FOLLOW_UP_GENERIC",
"REVIEW_MANUALLY": "MANUAL_REVIEW_REQUIRED",
}
def get_template(code: Optional[str]) -> Optional[MessageTemplate]:
return TEMPLATES.get(str(code or "").strip().upper())
def default_template_for_action(action_code: Optional[str]) -> MessageTemplate:
code = DEFAULT_TEMPLATE_BY_ACTION.get(str(action_code or "").strip().upper(), "MANUAL_REVIEW_REQUIRED")
return TEMPLATES[code]
def list_templates_for_action(action_code: Optional[str] = None) -> List[MessageTemplate]:
action = str(action_code or "").strip().upper()
items: Iterable[MessageTemplate] = TEMPLATES.values()
if action:
filtered = [tpl for tpl in items if action in tpl.compatible_actions or "REVIEW_MANUALLY" in tpl.compatible_actions]
return sorted(filtered, key=lambda tpl: (tpl.category, tpl.name))
return sorted(items, key=lambda tpl: (tpl.category, tpl.name))