Voltar ao Portfolio
Optimus PlatformBusiness Intelligence

Rules Engine

Motor de regras Python-native que elimina o overhead interpretado do JSONLogic. Avaliação em sub-millisegundo, regras criadas em runtime por tenant, type safety completo.

Python LambdaSub-millisecondMulti-tenantEvent-drivenType SafeCache Coherence

O Problema: Por que JSONLogic não escala

❌ JSONLogic - A Armadilha

// Regra "simples" em JSONLogic
{
  "and": [
    {"<": [{"var": "last_visit_days"}, 180]},
    {"==": [{"var": "has_insurance"}, true]},
    {"or": [
      {">": [{"var": "pending_treatments"}, 0]},
      {"in": [{"var": "vertical"}, ["dental", "medical"]]}
    ]}
  ]
}
  • ⚠️Interpretado recursivamente - cada operador é uma chamada de função aninhada
  • ⚠️Operadores limitados - só suporta <, >, ==, in, and, or
  • ⚠️Sem type checking - erros só aparecem em runtime
  • ⚠️Debug impossível - stack traces incompreensíveis
  • ⚠️~50-200ms para avaliar 100 regras complexas

✅ Python-Native - A Solução

# Mesma regra em Python-native
lambda facts: (
    facts.get('last_visit_days', 999) < 180 and
    facts.get('has_insurance') is True and
    (facts.get('pending_treatments', 0) > 0 or
     facts.get('vertical') in ('dental', 'medical'))
)
  • Compilado uma vez - bytecode Python nativo
  • Full Python power - regex, datetime, math, tudo
  • Type hints + mypy - erros antes do deploy
  • Debug normal - pdb, breakpoints, stack traces
  • <0.5ms para avaliar 100 regras - 1000x mais rápido

📊 Benchmark Real: JSONLogic vs Python-Native

~150ms
JSONLogic (100 regras)
<0.5ms
Python-Native (100 regras)
300x
Mais rápido (cold)
1000x
Mais rápido (cached)

Arquitetura: Rules Engine + Coordinator

┌─────────────────────────────────────────────────────────────────────────┐
│                        Request Flow (sub-ms target)                      │
└─────────────────────────────────────────────────────────────────────────┘

   Client Request
         │
         ▼
┌─────────────────┐     ┌──────────────────────┐     ┌─────────────────┐
│    Backend      │────▶│  Rules Coordinator   │────▶│  Rules Engine   │
│  Orchestrator   │     │  (Cache + Fallback)  │     │   (Port 8040)   │
└─────────────────┘     └──────────────────────┘     └─────────────────┘
                               │                              │
                               │                              │
                        ┌──────▼──────┐                ┌──────▼──────┐
                        │ Redis Cache │                │ PostgreSQL  │
                        │  (15 min)   │                │  (Rules DB) │
                        └─────────────┘                └─────────────┘

┌─────────────────────────────────────────────────────────────────────────┐
│                    Fallback Hierarchy (never fails)                      │
├─────────────────────────────────────────────────────────────────────────┤
│  1. Rules Engine API → Primary (target <50ms)                           │
│  2. Redis Cache      → Secondary (target <5ms)                          │
│  3. Basic Fallback   → Always available (keyword-based)                  │
└─────────────────────────────────────────────────────────────────────────┘
🎯

Rules Engine

Microsserviço dedicado que compila e executa regras Python-native. Cada tenant tem suas próprias regras isoladas.

  • • Compilação de lambdas Python
  • • Sandboxing de execução
  • • Métricas por regra
  • • Multi-vertical support
🔄

Rules Coordinator

Proxy inteligente no Backend Orchestrator com cache, circuit breaker e fallback multi-tier.

  • • Cache de avaliações (15min TTL)
  • • Memory context enrichment
  • • Circuit breaker protection
  • • Graceful degradation
🧠

Context Enrichment

Integração com Memory Engine para enriquecer fatos com contexto histórico do cliente.

  • • Frequência de interação
  • • Nível de urgência detectado
  • • Indicadores de dor/emergência
  • • Padrões inteligentes

Runtime Rule Creation: Cada Negócio é Único

O grande diferencial do Rules Engine é permitir que cada tenant crie suas próprias regras em tempo real, sem deploy, sem downtime, sem código.

API de Criação de Regras

POST /api/rules/
{
  "name": "Emergency Dental Protocol",
  "vertical": "dental",
  "event_types": ["message_received"],
  "condition_code": """
    lambda facts: (
      any(word in facts.get('message_content', '').lower()
          for word in ['pain', 'blood', 'swollen']) and
      facts.get('customer_urgency_level') == 'high'
    )
  """,
  "actions": [{
    "type": "emergency_protocol",
    "params": {
      "escalate_to_human": true,
      "priority": "urgent",
      "notification_channel": "whatsapp"
    }
  }],
  "priority": 1,
  "timeout_seconds": 30
}

🦷 Regra Dental

Lembrete de limpeza baseado em última visita + status do seguro. Se passou 6 meses e tem cobertura → agenda preventiva.

🛒 Regra E-commerce

Carrinho abandonado há 2h + valor > R$200 + cliente recorrente → oferta de 10% desconto + frete grátis.

⚖️ Regra Legal

Prazo processual em 48h + cliente não respondeu última mensagem → alerta urgente + escalação para advogado responsável.

🔒 Isolamento Multi-tenant Completo

🏥
Clínica ABC
47 regras ativas
Vertical: dental
Foco: agendamento
🛍️
Loja XYZ
89 regras ativas
Vertical: e-commerce
Foco: conversão
⚖️
Advocacia 123
23 regras ativas
Vertical: legal
Foco: prazos

Cada tenant tem regras completamente isoladas. Nenhuma regra da Clínica ABC afeta a Loja XYZ. Zero vazamento de lógica de negócio entre clientes.

Deep Dive: Como Funciona

Rules Coordinator: Cache + Fallback Inteligente

class RulesCoordinator:
    """
    🎯 Coordenador Central de Regras
    
    Responsibilities:
    1. Proxy inteligente para Rules Engine com <50ms target
    2. Cache Redis para performance otimizada
    3. Integração com Memory Coordinator para contexto enriquecido
    4. Fallback quando Rules Engine falha
    5. Circuit breaker para proteção contra falhas
    """

    async def evaluate_rules_for_conversation(
        self,
        tenant_id: str,
        conversation_data: dict,
        message_content: str,
        memory_context: dict | None = None,
    ) -> dict:
        
        # 1. 🧠 Enriquecer dados com Memory Coordinator
        enriched_facts = await self._enrich_with_memory_context(
            tenant_id, conversation_data, message_content, memory_context
        )

        # 2. 🎯 Tentar Rules Engine (primary)
        try:
            async with circuit_breaker_context("rules_engine_evaluate"):
                response = await self.rules_client.post(
                    "/api/evaluation/evaluate",
                    json={
                        "tenant_id": tenant_id,
                        "event_type": "message_received",
                        "facts": enriched_facts,
                    }
                )
                
                if response.status_code == 200:
                    result = response.json()
                    # Cache para reuso futuro
                    await self._cache_evaluation_result(tenant_id, enriched_facts, result)
                    return result

        except Exception as e:
            logger.warning(f"Rules Engine failed: {e}")

        # 3. 🔄 Fallback para Redis cache
        cache_result = await self._evaluate_via_redis_cache(tenant_id, enriched_facts)
        if cache_result:
            return cache_result

        # 4. 🚨 Basic fallback (keyword-based, nunca falha)
        return await self._evaluate_basic_fallback(tenant_id, enriched_facts)

Context Enrichment: Regras com Contexto Histórico

def _extract_intelligent_patterns(self, memory_context: dict) -> dict:
    """
    Extrai padrões inteligentes do contexto de memória para regras mais sofisticadas
    """
    patterns = {
        "interaction_frequency": "new",      # new | regular | frequent
        "customer_urgency_level": "normal",  # normal | high
        "preferred_communication_style": "formal",
        "likely_appointment_need": False,
        "pain_indicators": False,
        "satisfaction_level": "neutral",
    }

    context_content = memory_context.get("context", "").lower()
    memory_items = memory_context.get("memory_items", [])

    # Detecta urgência (dor, emergência, sangramento)
    if any(word in context_content for word in ["pain", "urgent", "emergency"]):
        patterns["customer_urgency_level"] = "high"
        patterns["pain_indicators"] = True

    # Detecta necessidade de agendamento
    if any(word in context_content for word in ["schedule", "appointment", "book"]):
        patterns["likely_appointment_need"] = True

    # Analisa frequência de interação
    if len(memory_items) > 5:
        patterns["interaction_frequency"] = "frequent"
    elif len(memory_items) > 2:
        patterns["interaction_frequency"] = "regular"

    return patterns

# Resultado: regras podem usar facts enriquecidos
# facts = {
#     "message_content": "I have tooth pain",
#     "customer_urgency_level": "high",
#     "pain_indicators": True,
#     "interaction_frequency": "regular",
#     "has_previous_appointments": True
# }

Event-Driven: Triggers Automáticos

message_received

Nova mensagem do cliente

conversation_started

Início de conversa

handover_completed

Atendente finalizou

appointment_scheduled

Agendamento confirmado

cart_abandoned

Carrinho abandonado

deadline_approaching

Prazo se aproximando

sentiment_negative

Cliente insatisfeito

time_based

Trigger por horário

Resultados: Regras que Escalam

<50ms
Latência P95
Target de performance
1000x
vs JSONLogic
Com cache aquecido
3000+
Regras em Produção
Dental + E-commerce + Medical
100%
Uptime Avaliação
Fallback nunca falha

💡 Decisões Técnicas Chave

1

Python Lambda vs DSL Customizada

Consideramos criar uma DSL (Domain-Specific Language) para regras, mas decidimos usar Python lambda diretamente. Razão: desenvolvedores já conhecem Python, debugging normal, type hints funcionam, ecosystem inteiro disponível. O sandboxing é feito via AST parsing + restricted builtins.

2

Cache de 15 minutos (não infinito)

Regras são cacheadas por 15 minutos, não infinitamente. Isso permite que alterações em regras (via API) sejam refletidas em tempo razoável sem necessidade de invalidação manual. O tradeoff entre performance e freshness foi calibrado em produção.

3

Fallback Keyword-Based (Sempre Funciona)

O último nível de fallback usa análise simples de keywords. Não é sofisticado, mas garante que o sistema NUNCA falha em avaliar uma mensagem. "Dor" → urgência, "agendar" → appointment. Simples, mas funcional como último recurso.

4

Microsserviço Separado (não library)

Rules Engine é um microsserviço independente, não uma library importada. Isso permite escalar horizontalmente, deploy independente, e isolamento de falhas. Se o Rules Engine crashar, o Coordinator usa cache/fallback.

Stack Técnico

Runtime

Python 3.11+ (bytecode optimized)

Framework

FastAPI + Pydantic

Cache

Redis (cache + pub/sub)

Storage

PostgreSQL (rules metadata)

Isolation

Per-tenant rule namespaces

Circuit Breaker

5 failures → 60s recovery

HTTP Client

HTTPX async pooling

Metrics

Prometheus + per-rule tracking

Quer discutir mais sobre Rules Engines?

JSONLogic vs Python-native, DSLs customizadas, ou como fazer regras escalarem - adoro falar sobre esses temas.