fix(ux): Corrigir timezone e adicionar variações humanas nas mensagens

PORQUE FOI FEITA ESSA ALTERAÇÃO?
Melhoria de UX. Correção de dois problemas identificados no teste:
1. Timezone: Bot usava UTC mostrando 'Boa noite' às 16h do Brasil
2. Formatação: Markdown literal (**text**) não era renderizado no Telegram

QUAIS TESTES FORAM FEITOS?
- pytest tests/test_onboarding.py: 12 testes passaram
- Teste manual no Telegram confirmando timezone e formatação HTML

A ALTERAÇÃO GEROU UM NOVO TESTE?
Não, testes existentes cobrem a lógica. Alterações foram:
- Uso de zoneinfo com America/Sao_Paulo
- parse_mode='HTML' em todas as mensagens
- 3 variações de boas-vindas (random.choice)
- 3 variações de confirmação de cadastro (agradecimento natural)
This commit is contained in:
João Pedro Toledo Goncalves 2026-02-02 16:30:17 -03:00
parent 8005c0c6a3
commit 14865c049f
2 changed files with 72 additions and 23 deletions

View File

@ -6,15 +6,64 @@ No LLM required - pure Python logic + database lookups.
""" """
import logging import logging
from typing import Optional, Dict, Any import random
from typing import Optional, Dict, Any, List
from dataclasses import dataclass, asdict from dataclasses import dataclass, asdict
from datetime import datetime, timezone from datetime import datetime, timezone
from enum import Enum from enum import Enum
from zoneinfo import ZoneInfo
from src.clients import get_qdrant_client from src.clients import get_qdrant_client
logger = logging.getLogger("ArthurOnboarding") logger = logging.getLogger("ArthurOnboarding")
# Brazil timezone
BRAZIL_TZ = ZoneInfo("America/Sao_Paulo")
# Welcome message templates - will be randomized
WELCOME_TEMPLATES = [
(
"Olá! {greeting}. 👋\n\n"
"Essa parece ser a primeira vez que nos falamos. "
"Pode me confirmar seu <b>Nome Completo</b> e <b>Empresa</b>, por favor?\n\n"
"Exemplo: <i>João Silva, iT Guys</i>"
),
(
"{greeting}! 👋\n\n"
"Prazer em conhecê-lo! Para que eu possa te atender melhor, "
"me conta: qual é o seu <b>nome</b> e de qual <b>empresa</b> você fala?\n\n"
"Pode responder assim: <i>Maria Santos, Empresa XYZ</i>"
),
(
"E aí! {greeting}. 😊\n\n"
"Ainda não temos seu cadastro por aqui. "
"Me passa seu <b>nome completo</b> e a <b>empresa</b> que você representa?\n\n"
"Formato: <i>Nome Sobrenome, Sua Empresa</i>"
),
]
# Registration confirmation templates
REGISTRATION_SUCCESS_TEMPLATES = [
(
"Perfeito, <b>{first_name}</b>! Obrigado pelas informações. ✅\n\n"
"Você está vinculado à empresa <b>{company}</b>.\n"
"Agora pode me descrever problemas técnicos que eu analisarei.\n\n"
"Como posso te ajudar hoje?"
),
(
"Show, <b>{first_name}</b>! Cadastro feito com sucesso. ✅\n\n"
"Te encontrei aqui como parte da <b>{company}</b>.\n"
"Estou pronto pra ajudar com qualquer questão técnica!\n\n"
"O que você precisa hoje?"
),
(
"Muito obrigado, <b>{first_name}</b>! Tudo certo. ✅\n\n"
"Seu perfil está associado à <b>{company}</b>.\n"
"Pode contar comigo para análises técnicas e suporte.\n\n"
"Em que posso ajudar?"
),
]
class UserStatus(Enum): class UserStatus(Enum):
"""Status of a user in the system.""" """Status of a user in the system."""
@ -81,13 +130,13 @@ class OnboardingManager:
def get_time_greeting(self) -> str: def get_time_greeting(self) -> str:
""" """
Returns appropriate greeting based on current time. Returns appropriate greeting based on current time in Brazil.
- 05:00 - 11:59 -> "Bom dia" - 05:00 - 11:59 -> "Bom dia"
- 12:00 - 17:59 -> "Boa tarde" - 12:00 - 17:59 -> "Boa tarde"
- 18:00 - 04:59 -> "Boa noite" - 18:00 - 04:59 -> "Boa noite"
""" """
now = datetime.now() now = datetime.now(BRAZIL_TZ)
hour = now.hour hour = now.hour
if 5 <= hour < 12: if 5 <= hour < 12:
@ -169,7 +218,7 @@ class OnboardingManager:
Start the registration flow for unknown user. Start the registration flow for unknown user.
Returns: Returns:
Greeting message asking for name and company. Greeting message asking for name and company (randomized).
""" """
greeting = self.get_time_greeting() greeting = self.get_time_greeting()
self._pending_registrations[telegram_id] = { self._pending_registrations[telegram_id] = {
@ -177,12 +226,9 @@ class OnboardingManager:
"started_at": datetime.now(timezone.utc) "started_at": datetime.now(timezone.utc)
} }
return ( # Pick a random welcome template
f"Olá! {greeting}. 👋\n\n" template = random.choice(WELCOME_TEMPLATES)
"Essa parece ser a primeira vez que nos falamos. " return template.format(greeting=greeting)
"Pode me confirmar seu **Nome Completo** e **Empresa**, por favor?\n\n"
"Exemplo: _João Silva, iT Guys_"
)
def is_pending_registration(self, telegram_id: str) -> bool: def is_pending_registration(self, telegram_id: str) -> bool:
"""Check if user is in the middle of registration.""" """Check if user is in the middle of registration."""

View File

@ -7,6 +7,7 @@ Integrates onboarding flow for new user identification.
import os import os
import re import re
import random
import logging import logging
from typing import List, Optional from typing import List, Optional
from dataclasses import dataclass from dataclasses import dataclass
@ -14,7 +15,7 @@ from telegram import Update
from telegram.ext import ApplicationBuilder, ContextTypes, CommandHandler, MessageHandler, filters from telegram.ext import ApplicationBuilder, ContextTypes, CommandHandler, MessageHandler, filters
from src.agents.dispatcher import get_dispatcher from src.agents.dispatcher import get_dispatcher
from src.agents.onboarding import get_onboarding_manager, UserStatus from src.agents.onboarding import get_onboarding_manager, UserStatus, REGISTRATION_SUCCESS_TEMPLATES
from src.agents.non_client_handler import get_non_client_handler from src.agents.non_client_handler import get_non_client_handler
from src.clients.financial_client import get_financial_client from src.clients.financial_client import get_financial_client
@ -91,12 +92,13 @@ class TelegramListener:
await update.message.reply_text( await update.message.reply_text(
f"👋 {greeting}, {name}!\n\n" f"👋 {greeting}, {name}!\n\n"
"Pode me encaminhar tickets ou descrever problemas que eu analisarei." "Pode me encaminhar tickets ou descrever problemas que eu analisarei.",
parse_mode="HTML"
) )
else: else:
# Start onboarding # Start onboarding
welcome_msg = self._onboarding.start_registration(telegram_id) welcome_msg = self._onboarding.start_registration(telegram_id)
await update.message.reply_text(welcome_msg) await update.message.reply_text(welcome_msg, parse_mode="HTML")
async def _handle_message(self, update: Update, context: ContextTypes.DEFAULT_TYPE): async def _handle_message(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
if not await self._check_auth(update): if not await self._check_auth(update):
@ -117,7 +119,7 @@ class TelegramListener:
if not is_known: if not is_known:
# Start onboarding flow # Start onboarding flow
welcome_msg = self._onboarding.start_registration(telegram_id) welcome_msg = self._onboarding.start_registration(telegram_id)
await update.message.reply_text(welcome_msg) await update.message.reply_text(welcome_msg, parse_mode="HTML")
return return
# Get user context for personalized response # Get user context for personalized response
@ -144,8 +146,9 @@ class TelegramListener:
if not match: if not match:
await update.message.reply_text( await update.message.reply_text(
"📝 Por favor, informe no formato:\n" "📝 Por favor, informe no formato:\n"
"_Nome Completo, Empresa_\n\n" "<i>Nome Completo, Empresa</i>\n\n"
"Exemplo: João Silva, iT Guys" "Exemplo: João Silva, iT Guys",
parse_mode="HTML"
) )
return return
@ -168,17 +171,16 @@ class TelegramListener:
first_name = name.split()[0] first_name = name.split()[0]
if tenant_id: if tenant_id:
# Client user - welcome! # Client user - welcome with random template!
template = random.choice(REGISTRATION_SUCCESS_TEMPLATES)
await update.message.reply_text( await update.message.reply_text(
f"✅ Cadastro realizado! {greeting}, {first_name}!\n\n" template.format(first_name=first_name, company=company),
f"Você está vinculado à empresa **{company}**.\n" parse_mode="HTML"
"Agora pode me descrever problemas técnicos que eu analisarei.\n\n"
"Como posso te ajudar hoje?"
) )
else: else:
# Non-client - explain limitations # Non-client - explain limitations
response = self._non_client.generate_response(name, company) response = self._non_client.generate_response(name, company)
await update.message.reply_text(response) await update.message.reply_text(response, parse_mode="HTML")
async def _handle_non_client_message(self, update: Update, user_ctx: dict): async def _handle_non_client_message(self, update: Update, user_ctx: dict):
"""Handle message from non-client user (passive messenger mode).""" """Handle message from non-client user (passive messenger mode)."""
@ -221,7 +223,8 @@ class TelegramListener:
# Send acknowledgment # Send acknowledgment
await update.message.reply_text( await update.message.reply_text(
f"👋 {greeting}, {first_name}!\n" f"👋 {greeting}, {first_name}!\n"
"🔍 Analisando sua solicitação..." "🔍 Analisando sua solicitação...",
parse_mode="HTML"
) )
# Dispatch to Arthur # Dispatch to Arthur