Lab 71 — Quando ML resolve, e quando uma regra resolve melhor
O problema, e a empresa que o tem
A Cadência é um marketplace de material de construção com 40 lojistas parceiros. Desde o L70, o time de dados garante que prata_produtos chega sem violar o contrato de preço — o que o modelo de reposição treina é confiável. O problema deste laboratório mora em outra decisão, tomada no time de Operações: quando o pedido é elegível para frete grátis.
Até três meses atrás, essa decisão era três condições no controller de checkout, escritas por alguém que já saiu da empresa: valor do pedido igual ou acima de R$ 150, distância até a loja igual ou abaixo de 30 km, e categoria diferente de "pesados" (cimento e argamassa, que pesam demais para o frete compensar). Funcionava, ninguém reclamava, e era auditável em três linhas.
O time de dados, animado com o sucesso do contrato do L70, propôs treinar um classificador para a mesma decisão — valor, distância, categoria, lojista, dia da semana, histórico do cliente — e prometeu que um modelo "aprenderia os padrões que a regra não captura". O modelo foi treinado, avaliado com métricas técnicas boas, e publicado direto num endpoint SageMaker em produção. Ninguém, em nenhum momento, mediu se a regra de três `if`s decidiria igual — ou melhor.
O que este laboratório NÃO é
Não é contra machine learning — é sobre viabilidade. O L70 garante que o dado que chega ao modelo está correto; este módulo garante que existe um modelo NESTE problema, e não em outro que uma regra já resolvia igual ou melhor. Não é sobre servir o modelo em produção com baixa latência (isso é o L74) nem sobre garantir a mesma feature no treino e na inferência (isso é o L72, que depende deste). Este módulo resolve uma coisa: nenhuma decisão de negócio troca de regra determinística por modelo estatístico sem um número mostrando o ganho — e o custo — dessa troca.
O que você vai conseguir fazer
Objetivos verificáveis: cada um se prova com um comando ou uma consulta na seção de implantação.
- Explicar por que "o modelo tem métrica boa" não responde à pergunta "o modelo vale a complexidade que acrescenta".
- Implementar a baseline de regra como função Lambda determinística, versionada e testável.
- Rodar o modelo candidato em modo sombra, sem que a resposta ao cliente dependa dele.
- Registrar as duas decisões — regra e modelo — lado a lado, por pedido, num log consultável.
- Calcular acurácia E custo ponderado por tipo de erro, não só acerto/erro simples.
- Justificar por que a decisão APLICADA continua sendo a da regra até o modelo provar, com número, que vence por margem suficiente para cobrir o próprio custo de operação.
- Nomear os dois tipos de erro deste problema — frete grátis indevido e frete negado indevido — e explicar por que eles não custam o mesmo.
- Provar com número: quantos pedidos foram avaliados, qual a diferença de custo entre regra e modelo, e se essa diferença cobre o custo do endpoint que a mede.
O que a certificação cobra disto
| Conceito | Certificação | Como aparece aqui | O que dominar |
|---|---|---|---|
| Baseline antes de modelo | AIF-C01, MLA-C01 | regra de três condições medida ANTES de aceitar o modelo como decisão aplicada | por que um projeto de ML sem baseline não tem como provar que agrega valor — é a primeira pergunta que a prova cobra em cenário de "a empresa quer usar IA para X" |
| Custo do erro assimétrico | AIF-C01, MLA-C01 | falso positivo (frete grátis indevido) e falso negativo (frete negado indevido) custam valores diferentes, e a decisão pondera os dois, não só acurácia agregada | diferença entre métrica técnica (acurácia, F1) e métrica de negócio (custo ponderado) — e por que otimizar a primeira pode piorar a segunda |
| Modo sombra (shadow deployment) | MLA-C01 | o endpoint SageMaker é chamado, mas a resposta ao usuário nunca depende dele — só a regra decide, o modelo só é medido | diferença entre shadow deployment (mede sem aplicar), canário (aplica a uma fração) e blue/green (troca tudo de uma vez) — e quando cada um é apropriado |
| Viabilidade de ML como decisão de produto | AIF-C01 | a seção de decisões trata "usar ML aqui" como escolha de arquitetura com alternativas e trade-off, não como conclusão óbvia | os sinais de que um problema É candidato a ML (padrão não-linear, dado histórico farto) e os sinais de que NÃO é (regra simples já funciona, decisão precisa ser auditável em uma frase) |
| Endpoint em tempo real como recurso que cobra parado | MLA-C01, MLS-C01 | o custo do endpoint SageMaker em modo sombra é medido e comparado ao ganho que ele mede — não é assumido como "o preço de fazer ML direito" | por que um endpoint ml.* ligado 24 horas cobra independente de tráfego, e como isso muda a conta de viabilidade de um modelo com ganho marginal |
Requisitos, e como cada um muda o desenho
Requisito que não muda uma linha do desenho é intenção, não requisito. A terceira coluna é onde cada um deixou marca.
| Requisito | Valor declarado | O que ele decide no desenho |
|---|---|---|
| Nenhuma decisão de negócio troca de regra para modelo sem baseline medida | comparação de acurácia e custo, com número, antes de qualquer promoção | obriga um caminho de modo sombra: o modelo roda em paralelo à regra, e a regra continua sendo a decisão aplicada até o comparativo provar vitória |
| Falha ou lentidão do endpoint SageMaker não pode travar o checkout | resposta ao cliente nunca espera pela chamada ao modelo | a chamada ao endpoint é assíncrona/best-effort a partir do orquestrador; timeout ou erro do modelo grava "sem decisão do challenger" e segue com a regra normalmente |
| Custo de operar o endpoint tem de ser justificado pelo ganho medido | economia líquida (ganho de acurácia ponderado por custo de erro, menos custo de infraestrutura) maior que zero, com o número exato | obriga registrar o custo do endpoint (Cost Explorer) e comparar contra a economia calculada, não só a diferença de acurácia isolada |
| Os dois tipos de erro deste problema não custam o mesmo | custo de falso positivo e de falso negativo declarados separadamente, em reais | a métrica de comparação é custo ponderado por erro, não acurácia simples — muda a consulta que a seção de prova roda |
| A decisão aplicada tem de ser auditável por qualquer pessoa do time de Operações | quem revisa um pedido específico entende a decisão sem abrir um notebook | a regra continua sendo a decisão aplicada por padrão; o modelo só assume quando o comparativo autoriza, e mesmo assim a decisão de cada pedido fica registrada com as features que a geraram |
| Custo de medir contido, sem serviço novo além do que a comparação exige | sem cluster dedicado a avaliação, sem pipeline paralelo de streaming | Lambda, DynamoDB, S3 e Athena — peças que a Cadência já opera desde a banda 6 e 7 — em vez de uma plataforma de experimentação dedicada |
Arquitetura mínima: o modelo publicado que ninguém comparou com nada
Este é o desenho que está em produção na Cadência hoje. Ele funciona — o modelo responde, o checkout aplica a decisão, ninguém abriu um incidente por causa dele. O defeito não está em nenhuma peça quebrada: está no que nunca foi perguntado antes de publicar.
- → pedido fechado, aguardando decisão de frete
- → features do pedido, chamada síncrona bloqueante
- → probabilidade de elegibilidade
- → decisão final aplicada ao pedido
- Rede e entrega
- Compute
- IA e machine learning
- Banco de dados
Este é o desenho que a Cadência tem hoje: legítimo como primeira versão funcional, e exatamente o ponto cego que o módulo existe para fechar. O pedido nunca passa perto de uma regra — só existe o modelo, e se ele falha, o pedido falha com ele. Percorra os passos e repare onde a pergunta "uma regra resolveria isso mais barato?" nunca chega a ser feita.
- O checkout fecha e a decisão de frete tem de sair antes da tela seguinte. API Gateway recebe a chamada síncrona; o cliente vê o valor do frete na mesma tela em que confirma a compra, então o caminho inteiro precisa responder dentro do orçamento de latência do checkout.
- A Lambda não decide nada sozinha — só empacota e repassa. Valor, distância, categoria e lojista viram um payload de features e seguem direto para o endpoint. Não existe nenhum código, nenhuma condição, que pudesse decidir sem o modelo.
- O endpoint devolve uma probabilidade, e o corte é aplicado sem contexto. 0,5 é o limiar padrão de um classificador binário — ninguém calibrou esse número contra o custo real de errar para um lado ou para o outro.
- A decisão vai para o pedido, e nada mais é registrado. DynamoDB grava só o resultado final. Não há campo para "o que a regra teria decidido", porque a regra nunca chegou a ser calculada.
- Se o endpoint demora ou falha, o checkout inteiro trava com ele. Não existe caminho alternativo. A chamada de lambda_min para sm_min é síncrona e bloqueante — um cold start ou uma instância sobrecarregada vira erro visível para o cliente no momento de pagar.
- Ninguém nunca mediu se três `if`s decidiriam igual, ou melhor. É o vazio central deste desenho: o modelo foi publicado porque "ML resolve melhor" soou razoável, não porque um número comparou as duas opções.
# Simula o pedido chegando e mostra que a decisao do modelo nunca e comparada
# com o que a regra de tres `if`s decidiria para o MESMO pedido.
curl -s -X POST https://api.cadencia.com/checkout/frete \
-H 'content-type: application/json' \
-d '{"pedido_id":"PED-88213","valor":162.40,"distancia_km":18.2,
"categoria":"ferragens","lojista_id":"L07"}'
# Resposta: {"frete_gratis": true, "origem": "modelo"}
# A regra manual, aplicada ao MESMO pedido, teria decidido:
# valor 162.40 >= 150.00 -> verdadeiro
# distancia 18.2 <= 30.0 -> verdadeiro
# categoria "ferragens" != pesados -> verdadeiro
# => frete_gratis = true (mesma decisao, por acaso -- ninguem verificou)
# Nada no pedido PED-88213 registra que essa comparacao existe. Repita para
# 18.400 pedidos e voce tem o problema real: a diferenca so aparece se alguem
# calcular -- e ninguem calcula, porque nao ha onde essa comparacao morar.
O modelo pode estar certo e o problema continuar existindo
Não é sobre o modelo errar. Neste exemplo específico ele decidiu igual à regra — e é exatamente por isso que o defeito passa despercebido: quando os dois concordam, ninguém sente falta da comparação. O risco aparece nos pedidos em que discordam, e sem registro nenhum de decisão dupla, não há como saber quantos são, nem para que lado o erro pesa mais.
Arquitetura para produção
Cada peça nova abaixo rastreia a uma linha da tabela de requisitos. A troca não é "acrescentar uma checagem" ao desenho anterior — é a regra deixar de ser ausente e virar o motor de primeira classe, com o modelo competindo ao lado dela, não substituindo-a por hábito.
- → pedido fechado, chega primeiro à regra
- → decisão da regra + os dados do pedido
- → chamada ao challenger, best-effort, não bloqueante
- → decisão do challenger, ou timeout/erro
- → decisão APLICADA ao pedido — hoje, sempre a da regra
- → as duas decisões lado a lado, uma linha por pedido
- → consulta agregando 90 dias de comparação
- → métrica de divergência e de falha do challenger
- Rede e entrega
- Compute
- IA e machine learning
- Banco de dados
- Armazenamento
- Analytics
- Gestão e governança
A diferença não é uma caixa a mais: a regra vira um motor de primeira classe (não um fallback), a chamada ao modelo deixa de bloquear a resposta, e existe um caminho inteiro — log comparativo, consulta agregada, alarme — que nunca existiu. Percorra os passos: cada peça nova resolve exatamente um requisito da seção anterior.
- A regra decide primeiro — antes de qualquer chamada ao modelo. lambda_baseline roda as três condições sobre o pedido em milissegundos, sem nenhuma dependência de infraestrutura de ML. Essa é a decisão que o cliente recebe, sempre que o comparativo ainda não autorizou o modelo a assumir.
- O orquestrador recebe a decisão da regra e chama o challenger sem bloquear nada. lambda_decisao dispara a chamada ao endpoint SageMaker com um timeout curto e best-effort — se ela não voltar a tempo, o orquestrador segue com a decisão da regra, que já estava pronta.
- O modelo devolve sua decisão — ou não devolve nada, e isso é aceitável. sagemaker>lambda_decisao é a resposta do challenger quando ela chega dentro do timeout. Quando não chega, o campo fica vazio no log — é dado, não incidente, porque o pedido nunca dependeu dela.
- A decisão aplicada ao pedido continua sendo a da regra. DynamoDB grava a decisão que o cliente de fato recebeu. Enquanto o comparativo não mostrar, com número, que o modelo vence por margem suficiente, esse campo vem sempre de lambda_baseline.
- As duas decisões viram uma linha no log comparativo — é o material que decide. Cada pedido grava a decisão da regra, a decisão do modelo (quando chegou) e as features usadas. Sem essa linha, nenhuma comparação futura é possível — é o mesmo raciocínio de quarentena rastreável do L70, aplicado a decisão, não a dado.
- Athena agrega 90 dias de log num número só: acurácia e custo comparados. A consulta soma falsos positivos e falsos negativos de cada motor, pondera pelo custo real de cada tipo de erro, e devolve a diferença — é essa consulta que a seção de prova roda.
- CloudWatch mede a saúde do challenger, não a do checkout. Divergência alta entre regra e modelo, ou taxa de timeout do endpoint acima do normal, são sinais que pertencem a este painel — o checkout continua saudável mesmo quando o challenger está com problema.
A diferença estrutural em relação à arquitetura mínima não é a presença do endpoint — ele já existia. É que o destino da chamada ao modelo deixou de ser "a decisão" e passou a ser "um dado a mais para medir". Quem decide o pedido, hoje, é a regra; o modelo só assume quando o comparativo autorizar.
O ganho que o cliente nunca vê, e é o mais importante
Antes, uma falha do endpoint travava o checkout. Depois desta mudança, o cliente nunca sabe se o challenger respondeu, demorou ou caiu — a regra decide de qualquer forma, em milissegundos, sem depender de infraestrutura de ML nenhuma. A melhor resiliência para uma decisão de negócio é ter um motor determinístico que nunca falha por hardware.
O caminho da decisão, ponta a ponta
O campo que mais importa no evento gravado não é a decisão em si — é decisao_aplicada vs. decisao_challenger lado a lado. É a diferença entre os dois, somada sobre milhares de pedidos, que a seção de prova transforma em número.
Uma linha do log comparativo em s3://cadencia-ml-comparacao/frete-gratis/dt=/ Escrita pelo lambda_decisao a cada pedido, ganhe ou perca o challenger.
{
"pedido_id": "PED-88213",
"dt": "2026-07-28",
"valor": 162.40,
"distancia_km": 18.2,
"categoria": "ferragens",
"lojista_id": "L07",
"decisao_regra": true,
"decisao_challenger": true,
"challenger_respondeu": true,
"decisao_aplicada": "regra",
"resultado_real_observado": true
}
O campo que falta hoje, e por que ele é decisivo
resultado_real_observado não vem do modelo nem da regra — vem de uma auditoria manual periódica do time de Operações sobre uma amostra de pedidos, registrando se o frete grátis realmente deveria ter sido dado. Sem esse rótulo independente, comparar regra e modelo seria comparar dois palpites entre si, e nunca com a realidade que ambos tentam prever.
As decisões, e o que se perde em cada uma
📋 A Cadência tem uma regra de três condições já em produção, decidindo frete grátis para 40 lojas, e um modelo treinado pronto — sem orçamento para uma plataforma de experimentação dedicada, e com o requisito de que o checkout nunca trave por causa do modelo.
Resolve a pergunta de viabilidade sem inventar peça de infraestrutura nova: a regra reaproveita o mesmo Lambda que a Cadência já opera desde a banda 3, o log comparativo reaproveita o mesmo S3 do lake das bandas 6 e 7, e a consulta reaproveita o mesmo Athena do L66 — só que medindo decisão de negócio em vez de qualidade de dado. O modelo continua rodando, mas cada chamada vira dado de comparação, não decisão cega.
Alt: Promover o modelo direto para decisão aplicada, mantendo a regra como fallback de erro — é o desenho que a Cadência já tinha antes deste módulo — o modelo decide por hábito, e a regra só aparece quando algo quebra, nunca como comparação contínua. O ganho nunca é medido, só assumido.
Alt: Desistir do modelo e manter só a regra, permanentemente — descarta um sinal real: se o padrão de pedidos mudar — nova categoria, sazonalidade, lojista com comportamento atípico — a regra fixa não se adapta, e o modelo poderia captar isso. Fechar a porta cedo demais custa tanto quanto abri-la sem medir.
Alt: Revisão manual de uma amostra de pedidos por um analista, todo dia — não escala com 40 lojas e milhares de pedidos por mês, e é exatamente o tipo de disciplina humana que o L65 já mostrou não sobreviver a rotatividade de time.
Alt: Confiar só na acurácia técnica do modelo (medida em validação, 91,7%) sem medir o custo ponderado por tipo de erro — é o erro mais comum desta decisão: acurácia agregada esconde QUE tipo de erro cada motor comete mais, e os dois tipos de erro deste problema não custam o mesmo — a seção de prova mostra a diferença que isso faz.
| Decisão | Escolha | Alternativas | Motivo | O que se perde |
|---|---|---|---|---|
| Quem decide o pedido, hoje | a regra (lambda_baseline), sempre | o modelo, direto | decisão determinística, auditável em uma frase, sem custo de inferência por pedido | nenhum ganho do modelo chega ao cliente até o comparativo autorizar — mesmo que o modelo já esteja "melhor" hoje |
| Como o modelo é avaliado | modo sombra: chamado, medido, nunca aplicado | canário (aplica a uma fração dos pedidos) | zero risco de decisão ruim do modelo chegar ao cliente enquanto ele ainda não foi validado com dado real de produção | leva mais tempo para acumular confiança suficiente do que um canário aplicado de verdade — decisão consciente de trocar velocidade por segurança |
| Métrica de comparação | custo ponderado por tipo de erro, em reais | acurácia simples | os dois tipos de erro deste problema custam valores diferentes — otimizar acurácia isolada pode escolher o motor que erra mais caro com mais frequência | métrica mais trabalhosa de calcular e de explicar do que um único número de acurácia |
| Onde o log comparativo mora | S3 + Athena, reaproveitando o lake existente | plataforma de experimentação dedicada (feature flags de ML, A/B testing especializado) | sem custo de serviço novo, e o time de dados já sabe operar as duas peças desde a banda 7 | sem interface de experimentação pronta — cada nova comparação exige escrever a consulta Athena à mão |
A dívida que este módulo cria, e que ele não paga
O comparativo atual usa um único corte de decisão para o modelo (probabilidade > 0,5) e uma janela fixa de 90 dias. Ele não recalibra o corte para minimizar custo ponderado — um corte diferente de 0,5 poderia mudar o resultado da comparação sem mudar o modelo em si. Calibrar o limiar de decisão contra a métrica de negócio, não contra 0,5 por convenção, é uma melhoria real que este módulo deixa em aberto.
Construir: a baseline de regra em Lambda
A escolha mais consequente deste bloco não é a lógica — três comparações não têm segredo. É que os limiares vivem em variável de ambiente, não em número escrito dentro do `if`: revisar "30 km" para "35 km" vira deploy de configuração, revisável em PR, não uma mudança de código que exige reler a função inteira para confirmar que nada mais mudou junto.
# baseline_frete.py -- a decisao inteira cabe em uma funcao pura, sem estado, sem
# chamada de rede. E o motor que decide o pedido enquanto o modelo nao provar que
# vence por margem suficiente.
import json
import os
from dataclasses import dataclass
# Limiares como configuracao, NUNCA como numero magico no meio do if -- revisar um
# limiar vira deploy de variavel de ambiente, nao deploy de codigo.
VALOR_MINIMO = float(os.environ.get("VALOR_MINIMO_FRETE", "150.00"))
DISTANCIA_MAXIMA_KM = float(os.environ.get("DISTANCIA_MAXIMA_KM", "30.0"))
CATEGORIAS_EXCLUIDAS = set(
os.environ.get("CATEGORIAS_EXCLUIDAS_FRETE", "pesados").split(",")
)
@dataclass
class Pedido:
pedido_id: str
valor: float
distancia_km: float
categoria: str
lojista_id: str
def decidir_frete_gratis(pedido: Pedido) -> bool:
"""As tres condicoes que a Cadencia usava antes de qualquer ML -- e que
continuam decidindo o pedido hoje, em producao, enquanto o modelo compete ao
lado sem aplicar nada."""
return (
pedido.valor >= VALOR_MINIMO
and pedido.distancia_km <= DISTANCIA_MAXIMA_KM
and pedido.categoria not in CATEGORIAS_EXCLUIDAS
)
def handler(event, context):
corpo = json.loads(event["body"])
pedido = Pedido(
pedido_id=corpo["pedido_id"],
valor=float(corpo["valor"]),
distancia_km=float(corpo["distancia_km"]),
categoria=corpo["categoria"],
lojista_id=corpo["lojista_id"],
)
decisao = decidir_frete_gratis(pedido)
return {
"statusCode": 200,
"body": json.dumps({
"pedido_id": pedido.pedido_id,
"decisao_regra": decisao,
"limiares_usados": {
"valor_minimo": VALOR_MINIMO,
"distancia_maxima_km": DISTANCIA_MAXIMA_KM,
"categorias_excluidas": sorted(CATEGORIAS_EXCLUIDAS),
},
}),
}
Por que a baseline não é "só um placeholder até o modelo ficar pronto"
O modelo JÁ está pronto — foi publicado antes deste módulo existir. A baseline não é um degrau temporário; ela é a decisão aplicada por padrão, permanentemente, até o comparativo autorizar a troca. Tratá-la como código descartável é o primeiro passo para ela ficar desatualizada sem ninguém perceber.
Construir: o orquestrador que compara sem nunca bloquear
A parte que mais gente erra ao construir modo sombra não é chamar o modelo — é deixar de tratar a chamada como opcional. `read_timeout=0.3` e `retries={"max_attempts": 0}` não são detalhes de performance: são o que garante que o challenger nunca vira o gargalo da decisão que o cliente está esperando.
# decisao_frete.py -- orquestra baseline + challenger, decide o que aplicar, o
# que grava, e nunca deixa o challenger atrasar a resposta ao cliente.
import json
import time
import boto3
from botocore.config import Config
TABELA_PEDIDOS = "cadencia-pedidos"
BUCKET_COMPARACAO = "cadencia-ml-comparacao"
ENDPOINT_CHALLENGER = "cadencia-frete-gratis-challenger"
# Timeout curto e config sem retry automatico -- uma tentativa a mais so
# atrasaria uma resposta que ja teria voltado da regra ha muito tempo.
sagemaker_runtime = boto3.client(
"sagemaker-runtime",
config=Config(connect_timeout=0.1, read_timeout=0.3, retries={"max_attempts": 0}),
)
dynamodb = boto3.resource("dynamodb").Table(TABELA_PEDIDOS)
s3 = boto3.client("s3")
def chamar_challenger_sem_bloquear(features: dict) -> tuple[bool | None, bool]:
"""Tenta o challenger; NUNCA propaga excecao para quem chamou. Retorna
(decisao_ou_None, respondeu_a_tempo)."""
try:
resposta = sagemaker_runtime.invoke_endpoint(
EndpointName=ENDPOINT_CHALLENGER,
ContentType="application/json",
Body=json.dumps(features),
)
corpo = json.loads(resposta["Body"].read())
return bool(corpo["probabilidade"] > 0.5), True
except Exception:
# timeout, throttling, instancia fria -- qualquer falha aqui vira
# "challenger nao respondeu", nunca um erro que o cliente ve.
return None, False
def handler(event, context):
corpo = json.loads(event["body"])
pedido_id = corpo["pedido_id"]
# 1. A regra ja decidiu -- veio de baseline_frete.py, chamado antes deste
# orquestrador na cadeia (API Gateway -> lambda_baseline -> lambda_decisao).
decisao_regra = corpo["decisao_regra"]
features = {
"valor": corpo["valor"],
"distancia_km": corpo["distancia_km"],
"categoria": corpo["categoria"],
"lojista_id": corpo["lojista_id"],
}
# 2. O challenger e chamado, mas o resultado NUNCA decide o que o cliente ve.
inicio = time.monotonic()
decisao_challenger, respondeu = chamar_challenger_sem_bloquear(features)
duracao_challenger_ms = (time.monotonic() - inicio) * 1000
# 3. A decisao APLICADA e sempre a da regra -- este e o requisito central
# do modulo, e esta linha e onde ele vira codigo.
decisao_aplicada = "regra"
dynamodb.put_item(Item={
"pedido_id": pedido_id,
"decisao_frete": decisao_regra,
"decisao_aplicada": decisao_aplicada,
})
# 4. As DUAS decisoes viram uma linha no log comparativo -- ganhe ou perca
# o challenger, e mesmo quando ele nao responde a tempo.
linha = {
"pedido_id": pedido_id,
**features,
"decisao_regra": decisao_regra,
"decisao_challenger": decisao_challenger,
"challenger_respondeu": respondeu,
"challenger_duracao_ms": round(duracao_challenger_ms, 1),
"decisao_aplicada": decisao_aplicada,
}
s3.put_object(
Bucket=BUCKET_COMPARACAO,
Key=f"frete-gratis/decisoes/dt={corpo['dt']}/{pedido_id}.json",
Body=json.dumps(linha),
)
return {
"statusCode": 200,
"body": json.dumps({"pedido_id": pedido_id, "frete_gratis": decisao_regra}),
}
Retry automático no challenger transformaria "medir sem risco" em "risco escondido"
Deixar o cliente boto3 com a política de retry padrão pareceria mais seguro — "tenta de novo antes de desistir" — mas cada tentativa a mais soma latência ao caminho que promete nunca bloquear o checkout. Um endpoint degradado com retry automático pode transformar um timeout de 300 ms num atraso de segundos, exatamente no motor que este módulo existe para manter fora do caminho crítico.
Construir: o endpoint do challenger e o log comparativo
Todo recurso aqui já é familiar de bandas anteriores da série — DynamoDB, S3, Lambda. O único elemento novo de verdade é `aws_sagemaker_endpoint`, e ele é também o único que cobra por hora ligada independente de tráfego — a diferença central entre operar uma regra e operar um modelo.
# infra-comparativo.tf -- endpoint do challenger, tabela de decisao aplicada,
# bucket do log comparativo, e as permissoes que cada Lambda precisa.
resource "aws_dynamodb_table" "pedidos" {
name = "${var.projeto}-pedidos"
billing_mode = "PAY_PER_REQUEST"
hash_key = "pedido_id"
attribute {
name = "pedido_id"
type = "S"
}
tags = {
Projeto = var.projeto
Time = "operacoes"
}
}
resource "aws_s3_bucket" "ml_comparacao" {
bucket = "${var.projeto}-ml-comparacao"
tags = {
Projeto = var.projeto
Time = "dados"
}
}
# O modelo challenger, servido como endpoint em tempo real. E O RECURSO que
# cobra por hora ligada, tenha ou nao trafego -- ver a secao de custo.
resource "aws_sagemaker_model" "challenger" {
name = "${var.projeto}-frete-gratis-challenger"
execution_role_arn = aws_iam_role.sagemaker_execucao.arn
primary_container {
image = var.imagem_container_xgboost
model_data_url = "s3://${var.bucket_modelos}/frete-gratis/model.tar.gz"
}
}
resource "aws_sagemaker_endpoint_configuration" "challenger" {
name = "${var.projeto}-frete-gratis-challenger-cfg"
production_variants {
variant_name = "challenger"
model_name = aws_sagemaker_model.challenger.name
instance_type = "ml.m5.large"
initial_instance_count = 1
}
}
resource "aws_sagemaker_endpoint" "challenger" {
name = "${var.projeto}-frete-gratis-challenger"
endpoint_config_name = aws_sagemaker_endpoint_configuration.challenger.name
}
# Permissao do orquestrador: invocar o endpoint, escrever no log comparativo,
# gravar a decisao aplicada. Resource especifico em cada acao -- nunca "*".
data "aws_iam_policy_document" "lambda_decisao" {
statement {
effect = "Allow"
actions = ["sagemaker:InvokeEndpoint"]
resources = [aws_sagemaker_endpoint.challenger.arn]
}
statement {
effect = "Allow"
actions = ["s3:PutObject"]
resources = ["${aws_s3_bucket.ml_comparacao.arn}/frete-gratis/decisoes/*"]
}
statement {
effect = "Allow"
actions = ["dynamodb:PutItem"]
resources = [aws_dynamodb_table.pedidos.arn]
}
}
resource "aws_iam_role_policy" "lambda_decisao" {
role = aws_iam_role.lambda_decisao.id
policy = data.aws_iam_policy_document.lambda_decisao.json
}
instance_type errado aqui é o erro mais caro deste módulo
`ml.m5.large` é um ponto de partida razoável para um modelo pequeno em modo sombra com baixo volume — mas é uma escolha, não um padrão universal. Subdimensionar gera throttling que infla a taxa de "challenger não respondeu" e distorce a comparação; superdimensionar paga por capacidade que o modo sombra, de baixo tráfego relativo, não usa. Meça latência e taxa de erro reais antes de mudar o tipo de instância.
Implantar, e provar com o número se o modelo vale a complexidade
Quatro números, na ordem em que a decisão de negócio precisa deles: quantos pedidos foram avaliados, a acurácia de cada motor, o custo ponderado de cada um, e o que sobra depois de descontar o que o endpoint cobra para ser medido.
# comparar_regra_e_modelo.py -- roda contra o log de 90 dias e devolve o numero
# que decide: o modelo vale a complexidade que acrescenta, ou nao?
import boto3
athena = boto3.client("athena")
# Custo em reais de cada tipo de erro, DECLARADO pelo time de Operacoes -- nao e
# um numero tecnico, e uma decisao de negocio que precisa ser revisitada se a
# margem media ou o custo de aquisicao mudarem.
CUSTO_FALSO_POSITIVO = 8.90 # frete gratis dado quando nao devia -- margem perdida
CUSTO_FALSO_NEGATIVO = 14.20 # frete negado quando devia -- carrinho abandonado, estimado
CONSULTA = """
SELECT
motor,
SUM(CASE WHEN decisao = TRUE AND resultado_real = FALSE THEN 1 ELSE 0 END) AS falsos_positivos,
SUM(CASE WHEN decisao = FALSE AND resultado_real = TRUE THEN 1 ELSE 0 END) AS falsos_negativos,
COUNT(*) AS total_avaliado
FROM cadencia_lake.comparacao_frete_gratis
WHERE dt BETWEEN DATE '2026-04-30' AND DATE '2026-07-28'
AND resultado_real IS NOT NULL
GROUP BY motor
"""
def custo_ponderado(falsos_positivos: int, falsos_negativos: int) -> float:
return falsos_positivos * CUSTO_FALSO_POSITIVO + falsos_negativos * CUSTO_FALSO_NEGATIVO
# Resultado observado sobre 18.400 pedidos com resultado_real auditado (ver texto):
#
# motor fp fn total acuracia custo_ponderado
# regra 612 1008 18400 91,20% R$ 19.760,40
# modelo 705 826 18400 91,68% R$ 18.003,70
#
# Economia bruta do modelo: R$ 19.760,40 - R$ 18.003,70 = R$ 1.756,70 em 90 dias.
# Custo do endpoint ml.m5.large, ligado 24h, no mesmo periodo (Cost Explorer da
# conta de exemplo): R$ 1.300,14.
# Ganho LIQUIDO do modelo: R$ 1.756,70 - R$ 1.300,14 = R$ 456,56 em 90 dias,
# ou aproximadamente R$ 5,07/dia sobre 18.400 pedidos -- positivo, mas fino.
| Prova | O que mede | Resultado observado | O que ele significa |
|---|---|---|---|
| 1 · Volume com rótulo real auditado | quantos dos 18.400 pedidos têm resultado_real_observado preenchido | 18.400 de 18.400 no período — auditoria periódica cobriu 100% da janela de teste | sem esse número, a comparação seguinte compararia dois motores contra si mesmos, não contra a realidade |
| 2 · Acurácia de cada motor | percentual de decisões que bateram com resultado_real_observado | regra: 91,20% (16.780/18.400) · modelo: 91,68% (16.869/18.400) | diferença de 0,48 ponto percentual — pequena o bastante para não decidir nada sozinha |
| 3 · Custo ponderado por tipo de erro | falsos positivos × R$ 8,90 + falsos negativos × R$ 14,20, por motor | regra: R$ 19.760,40 · modelo: R$ 18.003,70 — economia bruta de R$ 1.756,70 em 90 dias | o modelo erra MENOS no lado caro (falso negativo) mesmo errando mais no lado barato (falso positivo) — é essa distribuição, não a acurácia, que muda a conta |
| 4 · Ganho líquido após custo de infraestrutura | economia bruta menos o custo do endpoint no mesmo período (Cost Explorer) | R$ 1.756,70 − R$ 1.300,14 = R$ 456,56 em 90 dias, ≈ R$ 5,07/dia | positivo, mas fino — não é "não", mas também não é a vitória clara que justificaria promover o modelo à decisão aplicada hoje; a seção seguinte discute por que |
Por que um ganho líquido positivo não promoveu o modelo automaticamente
R$ 456,56 em 90 dias, sobre um único endpoint, é um sinal — não é margem de segurança. Ele pode inverter com uma leve mudança de tráfego, uma alteração de preço de instância, ou simplesmente ruído estatístico numa amostra de 18.400 pedidos. A decisão de promover exige um intervalo de confiança em cima desse número, não só o sinal do resultado — e calcular isso corretamente é o próximo passo, fora do escopo deste módulo.
Quebrar de propósito: três falhas e o diagnóstico
As três se parecem no sintoma superficial — "o comparativo está estranho". O que separa uma da outra é ONDE o defeito mora: no rótulo real, na chamada ao challenger, ou na suposição sobre o que a acurácia agregada esconde.
| Falha | Como provocar | Sintoma | Onde olhar | Correção |
|---|---|---|---|---|
| Auditoria manual atrasa e resultado_real_observado fica nulo em boa parte do período | pare de rodar a auditoria periódica do time de Operações por duas semanas | a consulta do Athena passa a comparar contra uma fração pequena da janela, e o número de acurácia fica instável de uma execução para outra | COUNT(resultado_real_observado) dividido pelo total do período, na consulta de prova | retomar a cadência de auditoria; nunca decidir promoção de modelo sobre uma janela com cobertura baixa de rótulo real |
| Timeout do challenger configurado longo demais | aumente read_timeout de 0.3 para 3 segundos no cliente do sagemaker-runtime | p95 de latência do checkout sobe, mesmo a decisão aplicada continuando a ser da regra — o tempo gasto esperando o challenger vaza para o caminho crítico | challenger_duracao_ms no log comparativo, e o painel de latência do checkout | reduzir o timeout de volta para a faixa de dezenas a poucas centenas de milissegundos; a resposta ao cliente não deveria notar a existência do challenger |
| Comparar só acurácia agregada, ignorando o custo ponderado | peça a alguém que decida promover o modelo olhando só "91,68% > 91,20%" | a decisão de promover é tomada sobre uma diferença de 0,48 ponto percentual sem saber se ela vem de menos erro caro ou de mais erro barato | a tabela de falsos positivos e falsos negativos por motor, não só o total de acertos | recalcular com CUSTO_FALSO_POSITIVO e CUSTO_FALSO_NEGATIVO antes de qualquer decisão de promoção — é a diferença entre as seções 2 e 3 da prova |
A pergunta que resolve metade destes casos
Antes de confiar em qualquer número do comparativo, pergunte: essa comparação está olhando para o custo real do erro, ou só para "quem acertou mais"? A segunda pergunta é mais fácil de responder e engana com mais frequência — é o motivo pelo qual este módulo inteiro existe.
A Cadência tem uma regra de três condições decidindo frete grátis há meses. O time de dados publica um modelo SageMaker com acurácia de validação de 92% e o coloca direto como decisão aplicada, sem comparar com a regra. Qual é o defeito arquitetural central deste desenho, segundo o que este módulo ensina?
Segurança: o preço de comparar duas decisões por pedido
O log comparativo carrega dado comercial sensível — valor de pedido, lojista, categoria — por cliente, por decisão. É útil justamente porque é granular, e é granular justamente o que faz dele um alvo se o acesso não for restrito.
| Risco | Probabilidade | Impacto | Controle preventivo | Detecção | Resposta |
|---|---|---|---|---|---|
| Log comparativo em S3 legível por qualquer papel da conta de dados | média | médio | policy do bucket restrita ao papel do orquestrador (escrita) e ao papel de análise (leitura via Athena), nunca acesso público nem `*` | CloudTrail em GetObject sobre o prefixo frete-gratis/decisoes/ fora dos dois papéis esperados | revogar o acesso indevido e revisar quem mais tinha a permissão |
| Endpoint SageMaker do challenger sem escopo de invocação restrito | baixa | médio | IAM Resource específico no ARN do endpoint (como em infra-comparativo.tf), nunca `sagemaker:*` sobre todos os endpoints da conta | CloudTrail em InvokeEndpoint de papéis diferentes do orquestrador | restringir a policy ao endpoint específico; investigar chamadas de origem inesperada |
| Limiares da regra (valor, distância, categoria) alterados sem revisão de Operações | média | alto | exigir aprovação do time de Operações como reviewer obrigatório do PR que toca as variáveis de ambiente de baseline_frete.py | diff de PR sem o reviewer certo, via regra de proteção de branch | reverter e reabrir com o reviewer correto antes de reaplicar |
| Custo do falso positivo e do falso negativo desatualizados em relação à margem real | média | alto | revisão trimestral dos dois valores com Financeiro, junto com a revisão de faixa de preço do L70 | diferença entre margem declarada em CUSTO_FALSO_POSITIVO e margem real do relatório financeiro do trimestre | atualizar os valores e reprocessar o comparativo do trimestre com os números corretos |
Um modelo promovido sobre número desatualizado decide errado, sistematicamente, sem alarme nenhum
Se CUSTO_FALSO_POSITIVO e CUSTO_FALSO_NEGATIVO ficarem desatualizados, o comparativo pode declarar vitória do motor errado — e nenhum dos dois motores "quebra" para acusar isso: os dois continuam decidindo, só que a decisão aplicada passa a otimizar para um custo que não é mais o real. É o mesmo risco silencioso do cast bem-sucedido do L70, agora sobre uma decisão de negócio em vez de um valor de coluna.
Observabilidade: as perguntas que o painel tem de responder
Um painel deste comparativo tem uma função estreita: dizer se o modelo está pronto para assumir, e se o motor que decide hoje continua saudável. Métrica que não ajuda essa pergunta pertence a outro painel.
| Pergunta | Métrica ou consulta | O que significa mudar | Limiar inicial |
|---|---|---|---|
| O challenger está respondendo dentro do timeout? | taxa de challenger_respondeu = true sobre o total de pedidos | queda indica throttling, instância subdimensionada ou cold start frequente | abaixo de 95% de respostas a tempo |
| Regra e modelo discordam com que frequência? | percentual de pedidos com decisao_regra != decisao_challenger | divergência alta é esperada no início; queda ao longo do tempo indica que os dois motores estão convergindo (ou que um deles parou de aprender algo novo) | acima de 15% sustentado merece investigação da causa da discordância |
| O ganho líquido do modelo está subindo, caindo ou estável? | custo_ponderado(regra) - custo_ponderado(modelo) - custo_endpoint, por janela móvel de 30 dias | tendência de queda pode indicar drift no padrão de pedidos, tema do L77 | qualquer trimestre com ganho líquido negativo dispara revisão da decisão de manter o challenger no ar |
| A cobertura de rótulo real está completa? | percentual de pedidos com resultado_real_observado preenchido, por semana | queda invalida a comparação daquela janela — é o mesmo problema da checagem 1 da seção de troubleshooting | abaixo de 90% de cobertura na semana invalida decisão de promoção sobre ela |
| O custo do endpoint está subindo mais rápido que o ganho que ele mede? | Cost Explorer do endpoint vs. economia bruta calculada no mesmo período | endpoint parado (auto scaling mal configurado, instância maior que o necessário) come o ganho líquido sem gerar nenhum sinal técnico de erro | custo do endpoint acima de 70% da economia bruta é sinal de dimensionamento errado |
A métrica que engana neste comparativo
Acurácia do modelo subindo, isolada, não significa "o modelo está mais pronto para assumir". Um modelo pode ficar mais acurado no agregado e ainda errar mais caro — acertando mais falsos positivos baratos enquanto erra mais falsos negativos caros. O painel que só mostra acurácia dá falsa confiança exatamente no ponto que mais importa.
Escala: 40 lojas, 400 lojas, e o que passa a doer sem AZ nenhuma
| Volume | O que acontece | O que passa a doer | O que fazer |
|---|---|---|---|
| 40 lojas, ~200 pedidos/dia (Cadência hoje) | um endpoint ml.m5.large em modo sombra atende com folga | nada | nada |
| 400 lojas (10×) | mais chamadas concorrentes ao challenger, mesma lógica de regra | auto scaling do endpoint precisa entrar em cena, ou throttling derruba a taxa de resposta a tempo | configurar auto scaling do endpoint por invocations-per-instance, medido antes de assumir que a instância única aguenta |
| Categoria nova todo mês (novo tipo de produto na Cadência) | a regra precisa de uma linha a mais em CATEGORIAS_EXCLUIDAS; o modelo precisa de retrain para aprender o padrão da categoria nova | a regra se ajusta em minutos (PR + deploy de config); o modelo fica desatualizado até o próximo ciclo de retrain, que é manual até o L76 | documentar a defasagem esperada do modelo para categoria nova como risco aceito, não como bug |
| Divergência entre regra e modelo acima de 30% sustentada | os dois motores discordam num terço dos pedidos | decidir qual está certo exige mais auditoria manual do que o time de Operações consegue sustentar | amostrar a divergência por segmento (categoria, faixa de valor) em vez de tentar auditar tudo |
| Falha de AZ | Lambda, DynamoDB, S3 e Athena são regionais; o endpoint SageMaker roda numa AZ por instância, salvo múltiplas instâncias configuradas | com uma instância só, falha de AZ derruba o challenger — mas a decisão aplicada, vindo da regra, continua no ar sem interrupção | múltiplas instâncias em AZs diferentes se o modo sombra precisar de alta disponibilidade própria — hoje, o requisito de disponibilidade do módulo é só o da regra |
O gargalo que só aparece com muito volume
O comparativo em si escala bem — Athena consulta anos de log sem esforço adicional de infraestrutura. O que não escala é a auditoria manual que produz resultado_real_observado: sem ela, todo o resto deste módulo mede dois motores contra si mesmos, não contra a realidade.
Custo: o que este laboratório acrescenta à fatura
A regra em si é quase gratuita — Lambda cobra por invocação, e três comparações não pesam no tempo de execução. O que pesa é o endpoint SageMaker, ligado o dia inteiro para medir um ganho que, no caso da Cadência hoje, ainda é fino.
| Cenário | Volume | O que acrescenta | Tendência | Otimização |
|---|---|---|---|---|
| Piloto | endpoint ligado, poucas centenas de pedidos/dia | custo do endpoint quase todo ocioso — poucas invocações reais por hora | desprezível em valor absoluto, mas já mostra a forma da conta | usar instância menor no piloto; medir latência antes de aumentar |
| Produção pequena | Cadência hoje: 18.400 pedidos/90 dias, ≈ 204/dia | endpoint ml.m5.large ligado 24h é a linha dominante; Lambda, DynamoDB, S3 e Athena somam pouco em comparação | custo do endpoint fixo, independente de tráfego — não cai em dia de menos pedidos | avaliar SageMaker Serverless Inference para o challenger, que cobra por invocação em vez de por hora ligada, se o tráfego continuar baixo |
| Alta escala | 400 lojas, milhares de pedidos/dia | auto scaling do endpoint entra na conta; o custo por invocação cai, o custo total sobe com o volume | a economia por decisão tende a ficar mais clara em volume alto — mais dado, intervalo de confiança mais apertado no comparativo | reavaliar o tipo de instância com o volume real, não com a estimativa do piloto |
| Dimensão | Cobra por | Cuidado |
|---|---|---|
| Endpoint SageMaker (challenger) | hora ligada por instância, independente de tráfego | é o item que decide se o ganho medido compensa — meça o seu, não assuma o R$ 1.300,14 deste módulo, que é específico do tipo de instância e da região do exemplo |
| Lambda (baseline + orquestrador) | por invocação e por GB-segundo de execução | desprezível no volume da Cadência; a regra em si roda em milissegundos |
| S3 (log comparativo) | requisições PUT e GB armazenado | cresce linear com pedidos avaliados; sem ciclo de vida configurado, acumula indefinidamente |
| Athena (consulta de comparação) | por byte varrido na consulta | particionar o log por dt= (como já é o padrão do lake desde o L64) mantém a consulta de 90 dias barata mesmo em alta escala |
O custo que este módulo evita, e que não aparece em nenhuma fatura da AWS
Sem o comparativo, a Cadência estaria pagando o endpoint do modelo por hábito, sem saber se o ganho de R$ 456,56 líquidos em 90 dias justifica mantê-lo. Esse número pequeno não é uma falha do módulo — é exatamente o tipo de decisão que só um comparativo medido revela, e que "o modelo parece funcionar" nunca teria mostrado.
Well-Architected nos seis pilares
| Pilar | Situação ao fim deste laboratório | Risco que fica | Melhoria | Prioridade |
|---|---|---|---|---|
| Excelência operacional | decisão auditável em uma frase (a regra); comparativo automático, sem intervenção manual além da auditoria de rótulo | auditoria manual de resultado_real_observado não escala indefinidamente | amostragem estatística da auditoria em vez de cobertura tentando ser total | média |
| Segurança | IAM com Resource específico por ação; log comparativo restrito a dois papéis | custo de erro (CUSTO_FALSO_POSITIVO/NEGATIVO) hardcoded no script de comparação, não versionado como configuração revisável | mover os dois valores para Parameter Store ou Terraform, com o mesmo processo de revisão trimestral do ruleset do L70 | média |
| Confiabilidade | checkout nunca depende do challenger; timeout curto e sem retry protege o caminho crítico | instância única do endpoint sem redundância entre AZs | avaliar redundância só se o modo sombra precisar de disponibilidade própria — hoje não é requisito declarado | baixa |
| Eficiência de performance | regra decide em milissegundos; challenger não compete pelo mesmo orçamento de latência | instance_type escolhido por convenção (`ml.m5.large`), não por medição de carga real | medir p50/p95 de invocação real antes de assumir que o tipo de instância está certo | média |
| Otimização de custos | sem serviço novo além do endpoint — reaproveita Lambda, S3, DynamoDB e Athena já em uso | ganho líquido medido (R$ 456,56/90 dias) é fino e pode inverter com pequena mudança de custo de instância | reavaliar SageMaker Serverless Inference, que cobra por invocação, dado o tráfego baixo relativo da Cadência | alta |
| Sustentabilidade | endpoint dimensionado para um único modelo, sem infraestrutura de experimentação paralela ociosa | instância ligada 24h mesmo fora do horário de pico de pedidos | avaliar escalonamento por horário, já que o tráfego de checkout tem padrão diário conhecido | baixa |
Evolução em níveis: de regra escondida a modelo com rede de segurança
A terceira arquitetura não é um desenho: é a resposta a QUANDO trocar de motor de decisão. Cada nível resolve um risco deste módulo e compra outro — até o ponto em que o próprio MODELO assume a decisão, e a regra que hoje decide tudo vira a rede de segurança por baixo dele.
Threshold hardcoded direto no controller de checkout, sem versão, sem teste, sem ninguém lembrando por que "30 km" foi o número escolhido — é onde a Cadência estava antes até de considerar ML.Regra como Lambda de primeira classe, decisão aplicada sempre da regra, modelo medido em paralelo sem nunca decidir, comparativo com custo ponderado por tipo de erro.Feature Store garante que "distância até a loja" e o histórico do lojista são calculados do mesmo jeito no treino do modelo e no momento em que o challenger decide em produção.Retrain do modelo dispara automaticamente quando dado novo valida no contrato do L70, com linhagem rastreável do dado até o modelo publicado — não mais um notebook rodado à mão.A métrica que decide não é mais só acurácia nem só o custo ponderado fixo deste módulo — é o impacto medido em receita e margem real, reconciliado com o financeiro a cada trimestre.Quando o comparativo mostra vitória sustentada com intervalo de confiança apertado, e o monitoramento de drift (L77) confirma que o dado de entrada continua estável, o MODELO se torna a decisão padrão — e a regra de três `if`s nunca é apagada: vira o fallback automático quando o endpoint falha ou o drift dispara.A ordem não é negociável, e o motivo é concreto
Promover o modelo do nível 2 direto para o nível 6, pulando o Feature Store do nível 3, mediria uma vantagem que pode ser artefato de skew entre treino e produção — não ganho real. O comparativo deste módulo só é confiável se a feature que o modelo vê aqui for a mesma que ele viu no treino, e é exatamente isso que o L72 garante antes de qualquer promoção séria.
Onde IA agrega neste laboratório, e onde ela seria só hype
Este módulo inteiro já é sobre um modelo de ML clássico — não confunda isso com "IA agrega automaticamente aqui". A pergunta que a certificação AIF cobra não é "dá para usar ML?", é "esse ML específico, com esse custo específico, ganha de uma regra que já funciona?". A resposta medida na seção de prova é "sim, por uma margem fina" — e é essa margem fina, não a existência do modelo, que decide se ele continua no ar.
Há um uso de IA que seria hype puro aqui, e vale nomear porque é tentador: pedir a um modelo de linguagem para "decidir" frete grátis a partir de uma descrição em texto do pedido, em vez de usar as features estruturadas que já existem. Isso trocaria uma decisão determinística e barata (a regra) e um classificador supervisionado auditável (o challenger) por uma chamada de LLM mais lenta, mais cara por decisão, e não determinística — para um problema que já tem dado estruturado e rótulo. É o mesmo erro de fundo que este módulo existe para prevenir, um nível acima.
| Pergunta | Resposta honesta para este módulo |
|---|---|
| O SageMaker challenger agrega valor aqui? | sim, por uma margem fina e medida — R$ 456,56 líquidos em 90 dias — e é essa margem, não uma opinião técnica, que autoriza (ou não) mantê-lo |
| Por que não usar um LLM para decidir frete grátis por pedido? | porque a decisão já tem dado estruturado e rótulo histórico — um LLM adicionaria latência, custo por token e resultado não determinístico a um problema que um classificador supervisionado clássico resolve com mais previsibilidade e menos custo |
| Onde IA generativa entraria de fato num tema como este? | em explicar, para um analista humano, POR QUE um pedido específico caiu em divergência entre regra e modelo — resumir o padrão de 50 casos divergentes em linguagem natural é tarefa de linguagem, não de classificação, e aí um LLM ajuda de verdade |
| Qual o risco de aceitar qualquer modelo só porque ele é "mais moderno" que a regra? | herdar custo de infraestrutura permanente por um ganho que pode ser menor que o próprio custo — é exatamente o resultado que apareceria se a Cadência tivesse promovido o modelo sem medir o ganho líquido |
O uso de IA que parece atraente e é armadilha aqui
Adicionar um segundo modelo — maior, mais caro, "para melhorar ainda mais a acurácia" — sem primeiro medir se o ganho do primeiro challenger justifica o custo dele repetiria o erro original deste módulo em escala maior. Mais complexidade só se justifica com mais número, nunca com mais confiança não verificada.
Anti-padrões deste laboratório
| Anti-padrão | Por que alguém faz | Por que é problema | Sintoma em produção | Forma correta | Quando é aceitável |
|---|---|---|---|---|---|
| Publicar o modelo direto como decisão aplicada, sem baseline | é o caminho mais curto entre "treinamos um modelo" e "o modelo está em produção" — pular a comparação parece só economizar um passo | sem baseline medida, ninguém sabe se o modelo ganha de uma regra simples pelo custo que acrescenta — a complexidade fica sem justificativa numérica | é a arquitetura mínima deste módulo: modelo publicado, ninguém nunca perguntou se três `if`s decidiriam igual | regra como decisão aplicada, modelo em modo sombra até vencer por margem medida | nunca — mesmo um protótipo de um dia se beneficia de saber contra o que está competindo |
| Otimizar acurácia agregada em vez de custo ponderado por tipo de erro | acurácia é a métrica que todo framework de ML mostra por padrão, e comparar um número só parece mais simples | os dois tipos de erro deste problema custam valores diferentes — um motor pode parecer melhor no agregado e ser pior no custo real | modelo promovido por ter acurácia mais alta, e o custo real de operação sobe depois | custo ponderado por tipo de erro, com os valores revisados junto ao Financeiro | apenas quando os dois tipos de erro deste problema específico custam de fato o mesmo — raro em decisão de negócio real |
| Deixar o checkout esperar pela resposta do challenger | parece mais simples chamar o modelo de forma síncrona e usar a resposta dele direto, sem construir o caminho "aplicar sempre a regra, medir o modelo à parte" | acopla a disponibilidade do checkout à disponibilidade de um endpoint de ML que ainda está sendo avaliado — exatamente o motor menos testado do sistema | timeout do endpoint vira erro visível para o cliente no momento de pagar | timeout curto, sem retry, e a decisão aplicada nunca depende da resposta do challenger chegar a tempo | só depois que o modelo já é a decisão aplicada com confiança medida — mesmo assim, com fallback declarado (tema do L79) |
| Considerar a comparação "feita" depois de uma única medição | um número parece definitivo, e recalcular parece esforço redundante | R$ 456,56 líquidos em 90 dias é um sinal sobre uma amostra, não uma garantia permanente — tráfego, custo de instância e padrão de pedidos mudam | modelo promovido baseado numa medição de um trimestre, sem revisão, dois anos depois | comparativo contínuo, revisado no mínimo a cada trimestre, junto com a revisão de custo de erro do Financeiro | nunca — mesmo depois de promovido, o motor perdedor continua sendo medido, para detectar se a vantagem se inverte |
Quando algo não funciona
Modo sombra tem uma característica incômoda: quando ele quebra, o cliente não percebe — a regra continua decidindo — e por isso o defeito vive semanas. Os cinco sintomas abaixo são os que aparecem, na ordem em que costumam aparecer.
| Sintoma | Causa provável | Como investigar | Log ou métrica | Correção |
|---|---|---|---|---|
| O checkout ficou mais lento depois que o modo sombra entrou | A chamada ao challenger deixou de ser opcional: timeout alto demais, ou retry habilitado no cliente boto3/SDK | Comparar a duração da Lambda com e sem a chamada ao endpoint, no mesmo período | Duração p99 da Lambda de decisão contra latência do endpoint SageMaker | Fixar timeout de leitura curto e `max_attempts=0`. O challenger nunca pode atrasar a decisão que o cliente espera — se ele não respondeu a tempo, a comparação daquele pedido se perde, e isso é aceitável |
| O log comparativo tem buracos em horário de pico | Escrita no DynamoDB estrangulada, ou a gravação foi disparada sem ser aguardada e a Lambda encerrou antes | Cruzar a contagem de decisões servidas com a contagem de registros gravados na mesma janela | `ThrottledRequests` na tabela e a diferença entre pedidos atendidos e linhas gravadas | Capacidade sob demanda na tabela e gravação aguardada antes do retorno; se a latência não permitir, enfileirar em vez de descartar. Amostragem declarada é aceitável — buraco silencioso não |
| A regra e o modelo divergem em quase 100% dos pedidos | Desalinhamento de feature entre treino e inferência: ordem das colunas, unidade, ou codificação de categoria diferente | Pegar um pedido único, montar a entrada à mão e chamar o endpoint diretamente, comparando com o que o orquestrador enviou | O payload íntegro enviado ao endpoint, registrado para uma amostra | É o mesmo defeito de contrato do L70, num ponto novo. Alinhar pela definição do Feature Store do L72 em vez de remontar a entrada no orquestrador |
| A acurácia do challenger caiu de um dia para o outro | O endpoint passou a servir outro artefato, ou a distribuição de entrada mudou | Conferir qual artefato o endpoint está servindo hoje contra o do dia anterior, e comparar a distribuição das features entre os dois dias | Nome da variante e do artefato no endpoint; histograma das features por dia | Se o artefato mudou, o problema é de processo de publicação. Se a entrada mudou, o modelo não caiu — o mundo mudou, e é exatamente essa a informação que justifica manter a regra ao lado |
| A fatura subiu num mês em que o tráfego caiu | O endpoint do challenger cobra por hora ligada, independentemente de receber requisição | Isolar o custo do endpoint no Cost Explorer e dividir pelo número de decisões comparadas no mês | Custo do endpoint por decisão comparada | É o número que decide a continuidade do experimento, não um defeito a corrigir. Se o ganho de acurácia não paga a hora ligada, a resposta correta do laboratório é desligar o challenger — e essa também é uma conclusão de sucesso |
Limpeza: o que o destroy não leva
Este laboratório cria um recurso que cobra por hora ligada mesmo sem tráfego — o endpoint do challenger — e dois que sobrevivem por conta própria: o log comparativo e os resultados de consulta do Athena.
#!/usr/bin/env bash
# limpar.sh -- o endpoint SageMaker e o que mais continua cobrando depois do
# laboratorio, mesmo com o Terraform destruido.
set -euo pipefail
PROJETO="${PROJETO:?defina PROJETO}"
# 1. Derruba o que o Terraform administra -- endpoint, config, modelo, tabela,
# bucket, policies.
terraform destroy -auto-approve
# 2. O ENDPOINT e o que mais cobra e o mais facil de esquecer -- confirme que
# ele realmente sumiu, nao so que o comando de destroy "terminou".
aws sagemaker describe-endpoint --endpoint-name "${PROJETO}-frete-gratis-challenger" \
2>&1 | grep -q "Could not find endpoint" && echo "endpoint removido, ok"
# 3. O LOG COMPARATIVO no S3 nao e removido pelo destroy -- e dado gravado pelo
# orquestrador, nao recurso do Terraform.
aws s3 ls "s3://cadencia-ml-comparacao/frete-gratis/" --recursive --summarize | tail -2
# Decida: manter para auditoria historica, ou:
aws s3 rm "s3://cadencia-ml-comparacao/frete-gratis/" --recursive
# 4. RESULTADOS DE CONSULTA DO ATHENA usados na prova, sem ciclo de vida.
aws s3 rm "s3://cadencia-lake/atenas-resultados/" --recursive
# 5. Prova final: nada com o nome do projeto de pe fora do esperado.
aws resourcegroupstaggingapi get-resources \
--tag-filters Key=Projeto,Values="${PROJETO}" \
--query "ResourceTagMappingList[].ResourceARN" --output table
| Recurso | Sai no destroy? | Cobra parado? | Por que fica |
|---|---|---|---|
| Endpoint SageMaker do challenger | sim, se em Terraform | sim, por hora, enquanto esteve no ar | é o recurso central deste módulo, e o mais fácil de deixar ligado por engano — confirme com describe-endpoint, não só com o retorno do destroy |
| Tabela DynamoDB de pedidos | sim | não parado — PAY_PER_REQUEST só cobra por uso | sem custo residual relevante |
| Log comparativo em S3 | não — é dado gravado pelo orquestrador, não pelo Terraform | sim, GB-mês | decisão editorial: manter para auditoria histórica de decisão de frete, ou apagar — este módulo não decide isso por você |
| Resultados de consulta do Athena | não | sim, GB-mês | cada execução grava um arquivo; sem ciclo de vida configurado, acumula para sempre |
Apagar o endpoint não apaga a decisão que ele documentava
Remover aws_sagemaker_endpoint do Terraform tira o challenger do ar, mas o comparativo — a diferença de R$ 456,56 líquidos medida em 90 dias — continua sendo uma decisão de negócio real, tomada com base num modelo específico e num instance_type específico. Perder o log sem registrar a conclusão em outro lugar é perder o motivo da decisão, não só o código dela.
Resumo: problema, peça e motivo
| Problema | Peça | Por que ela, e não outra |
|---|---|---|
| Modelo publicado direto, sem ninguém medir se uma regra decidiria igual | baseline de regra como Lambda de primeira classe, sempre calculada | decisão determinística, auditável em uma frase, sem custo de inferência |
| Chamada ao modelo poderia travar o checkout | modo sombra: challenger chamado com timeout curto, sem retry, resposta nunca esperada pelo cliente | a disponibilidade da decisão de negócio não pode depender da disponibilidade de infraestrutura de ML |
| Acurácia agregada esconde qual erro cada motor comete mais | custo ponderado por falso positivo e falso negativo, com valores declarados pelo negócio | os dois tipos de erro deste problema custam valores diferentes na Cadência real |
| Ninguém saberia se o modelo vale o custo do endpoint | ganho líquido = economia bruta menos custo de infraestrutura, medido no Cost Explorer | R$ 456,56 líquidos em 90 dias é positivo, mas fino — a decisão de manter o challenger precisa desse número, não de uma impressão |
| Decisão de frete precisa ser auditável por Operações, não só por quem lê Python | decisão aplicada sempre registrada como "regra" enquanto o modelo não é promovido | quem revisa um pedido específico entende a decisão sem abrir um notebook |
| Feature de treino pode divergir da feature vista em produção | não resolvido neste módulo | risco residual aceito e documentado; é o assunto do L72 (Feature Store) |
| Falha | O que a protege | O que ela NÃO protege |
|---|---|---|
| Modelo promovido sem ganho real medido | comparativo com custo ponderado e ganho líquido, revisado antes de qualquer promoção | ruído estatístico numa amostra pequena, sem intervalo de confiança calculado |
| Checkout travando por falha do endpoint | modo sombra com timeout curto e sem retry | custo do endpoint continuar subindo mesmo saudável, se o dimensionamento estiver errado |
| Custo de erro desatualizado distorcendo a decisão | revisão trimestral dos valores com o Financeiro | valor revisado errado por falta de dado de margem confiável |
| Feature calculada diferente no treino e na inferência | nada neste módulo | é o L72 inteiro, com Feature Store |
- Checkout fecha o carrinho e chama /checkout/frete via API Gateway.
- lambda_baseline avalia as três condições e decide em milissegundos.
- lambda_decisao recebe a decisão da regra e dispara o challenger, com timeout curto e sem retry.
- O challenger responde (ou não, e isso é aceitável) — a decisão aplicada nunca espera por ele.
- A decisão aplicada, sempre da regra hoje, é gravada em DynamoDB junto ao pedido.
- As duas decisões, mais as features, viram uma linha no log comparativo em S3.
- Athena agrega o log em acurácia e custo ponderado por tipo de erro, por trimestre.
- O ganho líquido — economia bruta menos custo do endpoint — decide se o challenger continua no ar, e se algum dia é promovido.
Perguntas frequentes
❓ Por que a decisão aplicada continua sendo da regra, e não do modelo?
❓ O que acontece se o endpoint SageMaker do challenger ficar fora do ar por um dia inteiro?
❓ Por que usar custo ponderado por tipo de erro em vez de só comparar acurácia?
❓ Esse comparativo substitui a avaliação técnica do modelo feita antes do treino (AUC, F1)?
❓ Por que a regra fica em variável de ambiente em vez de hardcoded direto no `if`?
❓ Um ganho de R$ 456,56 em 90 dias é pequeno demais para valer o módulo?
❓ O que impede subir o corte de decisão do modelo até ele vencer a regra?
Fixando
Na arquitetura de produção, lambda_decisao chama o endpoint SageMaker do challenger com read_timeout=0.3 segundos e retries={"max_attempts": 0}. Por que a política de retry padrão do boto3 — tentar de novo antes de desistir — seria um problema aqui, e não uma proteção extra?
O comparativo mostra: regra com 612 falsos positivos e 1.008 falsos negativos; modelo com 705 falsos positivos e 826 falsos negativos, ambos sobre 18.400 pedidos. A acurácia do modelo (91,68%) é só 0,48 ponto percentual maior que a da regra (91,20%). Por que a seção de prova ainda assim calcula o modelo como vencedor em custo?
Conhecimentos, próximo módulo e documentação
| Item | Conteúdo |
|---|---|
| Conhecimentos anteriores necessários | L70 (contrato de dado e quarentena) — prata_produtos confiável não é o mesmo problema que este módulo resolve, mas garante que qualquer feature derivada do lake, incluindo as usadas pelo challenger, parte de dado correto |
| Conhecimentos adquiridos | baseline como requisito antes de aceitar complexidade de modelo; modo sombra (shadow deployment) e por que ele nunca bloqueia o caminho crítico; custo ponderado por tipo de erro vs. acurácia agregada; ganho líquido como economia bruta menos custo de infraestrutura |
| Limitação que fica | o comparativo não garante que a feature vista pelo modelo em produção é idêntica à vista no treino — training/serving skew pode inflar ou destruir a vantagem medida aqui sem ninguém perceber a causa; é o assunto do L72 |
| Próximo exemplo recomendado | L72 — Feature Store: o mesmo cálculo no treino e na inferência. Garante que a vantagem medida neste comparativo é real, não artefato de feature calculada diferente em cada caminho |
| Também habilitado por este módulo | L78 (avaliação ligada a métrica de negócio) reaproveita o raciocínio de custo ponderado por tipo de erro deste módulo, generalizado para métrica financeira real reconciliada com o Financeiro |
| Data da última validação técnica | 8 de agosto de 2026 |
Documentação oficial consultada: Amazon SageMaker AI — invocação de endpoint em tempo real via SageMaker Runtime (InvokeEndpoint) e tipos de instância ml.*; AWS Lambda — configuração de timeout e política de retry de clientes boto3 dentro de uma função; AWS Well-Architected Machine Learning Lens — baseline e avaliação de viabilidade antes de promover um modelo. Nenhum preço de instância aparece como valor fixo por decisão: use o AWS Pricing Calculator, porque preço varia por região e envelhece mais rápido que o conteúdo.
O que não foi verificado, e você deve conferir na sua conta
Os valores R$ 1.300,14 (custo do endpoint em 90 dias) e R$ 8,90/R$ 14,20 (custo de falso positivo e falso negativo) são do cenário de exemplo da Cadência — meça os seus antes de decidir qualquer coisa: preço de instância muda por região e ao longo do tempo, e o custo real de margem perdida ou carrinho abandonado é específico do seu negócio, não um número que se copia de um módulo de estudo.
Terminou de ler?
Marcar como concluído registra o XP, mantém sua sequência e coloca 3 cartas deste módulo na fila de revisão espaçada.
Próximos passos sugeridos
Temas deste módulo
Discussão
Carregando comentários…