"""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 2–3 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 Wi‑Fi/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))