Lab 78 — Avaliação honesta: métrica de modelo ≠ de negócio
O problema, e a empresa que o tem
O time de reposição da Cadência decide, todo dia, quais SKUs em falta entram na fila de reposição — o centro de distribuição e os fornecedores só conseguem atender 400 SKUs por dia, e sobra muito mais pedido em ruptura do que capacidade de repor. Desde o ano passado, um modelo v1 ordena essa fila pela probabilidade de a reposição recuperar uma venda que seria perdida — AUC de 0,85 no teste offline, medido quando o modelo entrou em produção.
O time de dados retreinou um candidato v2 com features novas — velocidade histórica de venda por SKU, sazonalidade por categoria e disponibilidade de produto substituto — e a AUC no mesmo conjunto de teste subiu para 0,89. Não é ruído nem overfitting: a melhora se repete em validação cruzada. O time quer promover v2 e substituir v1 hoje.
O que ninguém tinha perguntado, porque o processo de promoção nunca exigiu a pergunta: uma AUC melhor no teste offline significa que, quando o modelo decide de verdade qual SKU repor, mais receita é recuperada? E significa isso para TODA loja igualmente, ou só para quem já estava bem atendido? Este laboratório é a primeira vez que a Cadência liga a resposta a um número — e o número não confirma o que a AUC prometia.
O que este laboratório NÃO é
Não é sobre re-treinar ou escolher feature/algoritmo do modelo — isso já aconteceu antes deste laboratório começar. Não é sobre COMO servir as duas versões — isso é o L74, e aqui reaproveitamos a variante multi-endpoint dele, não a reinventamos. Não é sobre detectar quando um modelo já em produção degrada com o tempo — isso é o tema do L77. Este laboratório resolve uma coisa: dado um candidato com métrica de ML melhor, PROVAR — ou refutar — que ele é melhor em receita e não pior para nenhum segmento, antes de promover.
O que você vai conseguir fazer
Objetivos verificáveis: cada um se prova com um número na seção de implantação.
- Explicar por que uma AUC melhor no teste offline não garante mais receita recuperada em produção — e o que precisa ser medido para saber.
- Configurar um endpoint SageMaker com duas variantes de produção recebendo tráfego real simultâneo, para um teste A/B controlado.
- Capturar cada inferência com o identificador da variante, e catalogar essa captura no Glue para virar tabela consultável.
- Escrever a consulta Athena que junta a inferência prevista ao pedido real, agregando receita recuperada por variante E por segmento de loja.
- Rodar um job de SageMaker Clarify que mede viés pós-treino por porte de loja, e comparar o resultado entre as duas variantes.
- Calcular intervalo de confiança de 95% sobre a diferença de receita entre v1 e v2, em vez de comparar dois números soltos.
- Orquestrar a decisão de promoção com Step Functions, de forma que a ausência de qualquer um dos dois resultados NEGUE a promoção por padrão.
- Reconhecer o padrão específico deste laboratório: métrica de ML melhor, receita global igual, segmento específico pior — mascarado pela média.
O que a certificação cobra disto
| Conceito | Certificação | Como aparece aqui | O que dominar |
|---|---|---|---|
| Métrica de negócio vs. métrica de ML | MLS-C01, AIF-C01 | AUC de 0,85 para 0,89 no offline; receita recuperada praticamente igual no agregado real | reconhecer que uma métrica de avaliação de modelo (AUC, F1, RMSE) é um PROXY — e proxy que melhora não prova que o objetivo de negócio melhorou |
| SageMaker Clarify — viés pós-treino | MLS-C01 | job de Clarify medindo diferença de proporção de rótulo previsto positivo (DPPL) entre porte de loja pequena e grande | a diferença entre viés de PRÉ-treino (no dado) e de PÓS-treino (nas previsões do modelo) — e por que os dois merecem medição separada |
| Teste A/B com significância estatística | MLS-C01, AIF-C01 (conceito geral) | tráfego dividido 50/50 entre v1 e v2, com intervalo de confiança sobre a diferença de receita, não só o ponto médio | por que "o número de v2 é maior" não é o mesmo que "v2 é melhor" sem o intervalo de confiança em volta do número |
| Athena sobre dado capturado de inferência | MLS-C01, DEA-C01 (conceitual) | consulta que junta a captura da inferência (Data Capture do endpoint) ao pedido real concluído, por SKU, loja e janela de tempo | ligar previsão a outcome real é sempre um JOIN — reconhecer que esse join é o que falta em toda avaliação que para na métrica de ML |
| Métrica que otimiza tudo, menos o que interessa | AIF-C01, MLS-C01 | v2 melhora a média por puxar as lojas grandes para cima, escondendo a piora nas lojas pequenas dentro do agregado | dado um cenário de prova com "métrica agregada melhorou", perguntar SEMPRE por segmento antes de aceitar a conclusão |
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 |
|---|---|---|
| Promoção não pode depender só de AUC | decisão exige lift de receita medido contra dado real, não estimado | obriga um pipeline de Athena juntando previsão capturada e pedido concluído ANTES de qualquer promoção ser possível |
| Nenhum segmento pode piorar mascarado pela média | viés pós-treino medido por porte de loja, com limiar combinado de 0,05 de DPPL | obriga um job de SageMaker Clarify rodando sobre a amostra capturada, separado — e não substituído — pela métrica global |
| Diferença de receita entre v1 e v2 precisa ser significativa | intervalo de confiança de 95%, não "o número maior venceu" | obriga split A/B controlado com as duas variantes recebendo tráfego real e simultâneo — não teste sequencial, que confunde efeito com sazonalidade |
| Dado de receita por segmento é sensível | acesso restrito a quem decide a promoção do modelo | obriga papel de IAM dedicado e chave KMS própria na tabela do Glue Catalog que expõe receita por porte de loja |
| A decisão de promover não pode ser feita "de cabeça" num notebook | resultado registrado, reproduzível, auditável depois do fato | obriga orquestração via Step Functions, com os dois resultados — negócio e viés — persistidos no S3 antes de qualquer gate abrir |
| Falha de qualquer avaliação não pode virar promoção por omissão | o padrão do gate é NEGAR, nunca aprovar por ausência de dado | obriga o Step Functions tratar exceção de cada etapa como "reprovado", não como "pular e seguir" |
Arquitetura mínima: o notebook que só sabe calcular AUC
Este é o processo de promoção que a Cadência tem hoje, e é um ponto de partida honesto: comparar AUC num holdout é rápido e é o que qualquer notebook de treino já calcula de graça. O defeito não é medir AUC — é parar nela.
- → mesmo conjunto de teste para as duas versões
- → pontuação do holdout pela versão em produção
- → pontuação do holdout pela versão candidata
- → AUC(v1)=0,85 e AUC(v2)=0,89, e nada mais
- → v2 substitui v1 por completo, imediatamente
- IA e machine learning
- Armazenamento
- Fora da AWS
As duas caixas do meio são o defeito inteiro: um número (AUC) entra, um número maior sai, e a promoção acontece. Percorra os passos — em nenhum momento a receita real ou o efeito por loja é consultado. Não é que ninguém se importe: é que o pipeline nunca pediu esse dado.
- O holdout alimenta as duas versões igualmente. O mesmo conjunto de teste histórico pontua v1 e v2 — controla pelo menos essa variável.
- O notebook calcula AUC de cada versão sobre o holdout. 0,85 para v1, 0,89 para v2 — a única comparação que existe até este laboratório.
- A decisão é binária: AUC maior vence. Nenhuma outra pergunta é feita — nem receita, nem segmento, nem significância.
- v2 substitui v1 por completo no endpoint de produção. Sem período de teste em paralelo: a fila de reposição inteira passa a usar v2 no dia seguinte.
- Ninguém volta para medir o que aconteceu com a receita. O ciclo termina na promoção — não existe elo de volta que confirme se a AUC melhor virou receita melhor.
# medir_auc_holdout.py -- exatamente o que o notebook de promocao roda hoje
# antes deste laboratorio: calcula AUC e para por ai.
import boto3
from sklearn.metrics import roc_auc_score
import pandas as pd
s3 = boto3.client("s3")
holdout = pd.read_parquet("s3://cadencia-lake/reposicao/holdout-2026-08.parquet")
# 'rotulo' = a reposicao recuperou a venda de fato (medido retroativamente
# no proprio holdout historico -- por isso da para calcular AUC offline).
auc_v1 = roc_auc_score(holdout["rotulo"], holdout["score_v1"])
auc_v2 = roc_auc_score(holdout["rotulo"], holdout["score_v2"])
print(f"AUC v1: {auc_v1:.2f}") # 0.85
print(f"AUC v2: {auc_v2:.2f}") # 0.89
# A linha que promove hoje. So isso. Nenhuma pergunta sobre receita
# real, nenhuma sobre segmento -- e e exatamente essa linha que este
# laboratorio substitui por um pipeline de tres perguntas.
if auc_v2 > auc_v1:
print("promover v2")
AUC melhor, pergunta errada
AUC melhor não é receita melhor — e ninguém tinha perguntado a segunda parte. O holdout mede se o modelo ORDENA melhor um conjunto histórico fixo. A fila de reposição de amanhã não é um conjunto fixo: é uma decisão com capacidade limitada a 400 SKUs, tomada sobre lojas com portes diferentes, cujo efeito só se confirma dias depois, quando o pedido é concluído ou perdido de verdade. Nenhuma dessas três coisas aparece numa AUC.
Arquitetura para produção
Cada peça nova abaixo rastreia a uma linha da tabela de requisitos. A troca não é "adicionar Athena e Clarify no mesmo lugar" — é substituir "um número decide" por "dois resultados independentes e auditáveis decidem juntos", com um gate que nega por padrão.
- → chamada da fila diária de reposição, sem saber qual variante responde
- → grava payload de entrada, score de saída e variant_id de cada chamada
- → prefixo S3 da captura vira tabela consultável
- → prefixo S3 dos pedidos concluídos vira a segunda tabela
- → esquema das duas tabelas, prontas para JOIN por sku_id, loja_id e janela de tempo
- → amostra das previsões, com variant_id e porte_loja, alimenta a medição de viés
- → lift de receita por variante e por porte, com intervalo de confiança
- → DPPL por variante e por porte de loja
- → decisão: promover v2 a 100%, reverter a 100% v1, ou manter o teste
- → receita por variante publicada no painel
- → DPPL por variante publicado no mesmo painel
- Fora da AWS
- IA e machine learning
- Armazenamento
- Analytics
- Integração de apps
- Segurança e identidade
- Gestão e governança
A diferença não é uma caixa a mais: o destino da inferência deixou de ser "a versão com AUC maior" e passou a ser "as duas versões, medidas contra o que realmente aconteceu". Percorra os passos — a promoção só existe do outro lado de um gate que precisa dos dois resultados, negócio e viés, para abrir.
- O tráfego real é dividido pelo peso configurado no endpoint. 50% das chamadas da fila diária vão para v1, 50% para v2 — simultâneas, não sequenciais, para não confundir efeito com sazonalidade.
- Cada inferência é capturada com o identificador da variante. Data Capture do SageMaker grava entrada, saída e variant_id automaticamente — o consumidor nem sabe que isso acontece.
- Glue dá esquema aos dois prefixos S3 crus. Captura de inferência e pedidos concluídos viram duas tabelas — sem isso, o Athena não tem o que consultar.
- Athena junta previsão e outcome real, agregando por variante e por porte. É a pergunta que a arquitetura mínima nunca fez: quanta receita cada versão recuperou, e para qual tipo de loja.
- Clarify mede viés pós-treino sobre a mesma amostra capturada. Compara a proporção de priorização positiva entre lojas pequenas e grandes, separadamente para v1 e v2.
- Step Functions só libera promoção com os dois resultados dentro do limite. Lift de receita significativo E viés (DPPL) abaixo de 0,05 — falta de qualquer um vira reprovação, não omissão.
- O painel do CloudWatch mostra as três métricas lado a lado. AUC, receita e viés juntos — para que a próxima decisão de promoção não volte a olhar só a primeira coluna.
A diferença estrutural em relação à arquitetura mínima não é a contagem de peças: é que a promoção deixou de ser uma linha de código num notebook e passou a depender de um resultado que só existe depois que dinheiro de verdade passou pelas duas versões.
O endpoint com duas variantes não decide sozinho
Variante de produção do SageMaker não é "canary automático" — é um roteador de peso fixo. Ele resolve dividir o tráfego; não resolve decidir quando parar de dividir. Essa decisão continua sendo do Step Functions, olhando os dois resultados — o endpoint multi-variante é a peça de infraestrutura, não a peça de decisão.
O ganho que a fatura não mostra
O ganho que aparece no painel é a decisão que deixou de ser feita de cabeça: promoção com prova de receita e de ausência de viés, em vez de "a AUC subiu". O que não aparece em nenhum dashboard é o que esse pipeline evitou — promover, sem saber, um modelo que melhora a média às custas de um segmento inteiro de lojas.
O caminho do dado, ponta a ponta: da inferência capturada à decisão de promover
O caminho mais fácil de entender errado é achar que "Athena mostra a receita" e "Clarify mostra o viés" são dois relatórios paralelos. Não são: os dois leem a MESMA amostra capturada, e é isso que garante que a comparação de receita e a comparação de viés falam do mesmo período e do mesmo tráfego.
registro que o Data Capture grava no S3 para CADA chamada ao endpoint -- e' este registro que o Glue cataloga e o Athena consulta depois.
{
"inference_id": "inf-88213",
"variant_id": "v2",
"timestamp": "2026-08-03T09:14:02Z",
"sku_id": "SKU-40217",
"loja_id": "L07",
"porte_loja": "pequena",
"features": {
"velocidade_venda_30d": 4.2,
"sazonalidade_categoria": 0.83,
"tem_substituto_disponivel": false
},
"score_previsto": 0.78
}
Por que o teste dura 14 dias, e não menos
A janela entre a inferência e o desfecho real é o motivo de este laboratório rodar 14 dias, não 14 minutos. O score sai na hora; o pedido concluído (ou perdido) só se confirma dias depois, quando a reposição chega ou não chega a tempo. Encurtar a janela para "ver o resultado logo" mede um recorte de pedidos ainda em andamento, não o desfecho real — é o erro que a seção de falhas propositais adiante provoca de propósito.
As decisões, e o que se perde em cada uma
📋 Um modelo candidato (v2) com AUC 0,89 contra o v1 em produção com AUC 0,85, decidindo qual SKU repor por dia numa fila com capacidade fixa de 400 SKUs, para lojas de portes diferentes.
Nenhuma das três medições substitui a AUC — elas respondem a perguntas que a AUC não faz: receita real recuperada, significância estatística da diferença, e se a melhora está concentrada num segmento às custas de outro. A decisão de promover passa a depender de três respostas independentes, não de uma.
Alt: Promover v2 direto pela AUC, como o processo fazia antes — é exatamente o defeito que este laboratório mede: teria promovido um modelo que piora a receita das lojas pequenas em quase 20%, escondido atrás de uma média global estável
Alt: Rodar só a comparação de receita, sem Clarify — a média global de receita (+0,4%, não significativa) já não seria motivo para promover — mas sem segmentar por porte, ninguém saberia POR QUE a média não se moveu, nem que um segmento piorou de verdade
Alt: Comparar receita sequencialmente — v1 por uma semana, depois v2 — confunde efeito do modelo com sazonalidade da semana; a reposição de SKU tem padrão de demanda que muda dia a dia, e teste sequencial atribuiria essa variação ao modelo errado
Alt: Promover v2 só para as lojas grandes, onde ele já vence — resolve o sintoma medido nesta rodada, mas sem investigar a CAUSA do viés — a próxima versão treinada sobre esse cenário herdaria o mesmo desbalanceamento de dado que produziu o problema
| Decisão | Escolha | Alternativa considerada | Motivo | O que se perde |
|---|---|---|---|---|
| Como dividir o tráfego entre v1 e v2 | 50/50 simultâneo por 14 dias | canary de 5% crescendo aos poucos | 50/50 dá poder estatístico suficiente para detectar a diferença de -19,7% no segmento pequeno dentro de 14 dias; canary lento levaria semanas para o mesmo poder | expõe metade do tráfego real a um modelo ainda não aprovado — aceitável aqui porque o pior caso (viés) já é o que se está medindo, não um risco de disponibilidade |
| Onde a comparação de receita roda | Athena sobre tabela do Glue Catalog | exportar tudo para um data warehouse dedicado | os dois conjuntos de dado (captura e pedidos) já vivem no S3; Athena consulta direto, sem pipeline de ETL adicional para manter | consulta federada é mais lenta que tabela nativa de warehouse — aceitável para uma decisão que roda a cada 14 dias, não em tempo real |
| Onde o viés é medido | SageMaker Clarify, pós-treino, facet = porte_loja | calcular a diferença de receita por porte manualmente no Athena | Clarify calcula a métrica padrão (DPPL) de forma comparável entre execuções e entre modelos — reimplementar a fórmula à mão é a armadilha do L41 aplicada a estatística, não a IAM | Clarify exige uma amostra rotulada com o atributo protegido (porte_loja) presente na captura — se esse campo faltar, o job falha, e o gate nega por padrão |
| O que acontece se um dos dois jobs falhar | Step Functions trata como reprovação, nunca como aprovação | seguir com o resultado disponível e assumir o outro como "ok" | é o requisito nº 6 da tabela de requisitos: falha silenciosa vira promoção por omissão, exatamente o padrão que este laboratório existe para impedir | atrasa a decisão até o job ser corrigido e rodado de novo — aceitável, porque a alternativa é promover sem prova |
O que este laboratório não audita
A dívida que este módulo cria, e que ele não paga: o segmento é só "porte de loja". Região, categoria de produto e sazonalidade também podem esconder o mesmo padrão — uma média boa que mascara um subgrupo pior — e este laboratório não os audita. Adicionar mais um facet ao Clarify é replicar o mesmo padrão, não construir algo novo; a decisão de quais segmentos auditar é do time de negócio, não deste módulo.
Construir: o endpoint com duas variantes e captura ligada
A peça nova em relação ao endpoint único do L74 não é o tipo de instância — é `production_variants` com DUAS entradas e `data_capture_config` ligado, para que cada chamada saia com a variante marcada sem precisar tocar o código do consumidor.
# endpoint-ab.tf -- duas variantes no mesmo endpoint, peso igual
resource "aws_sagemaker_endpoint_configuration" "reposicao_ab" {
name = "${var.projeto}-reposicao-ab"
production_variants {
variant_name = "v1"
model_name = aws_sagemaker_model.reposicao_v1.name
instance_type = "ml.m5.large"
initial_instance_count = 1
initial_variant_weight = 1 # peso RELATIVO -- 1 e 1 = 50/50
}
production_variants {
variant_name = "v2"
model_name = aws_sagemaker_model.reposicao_v2.name
instance_type = "ml.m5.large"
initial_instance_count = 1
initial_variant_weight = 1
}
# Data Capture -- grava entrada, saida e variant_id de CADA chamada,
# sem exigir nenhuma mudanca no Sistema de Reposicao que consome o
# endpoint.
data_capture_config {
enable_capture = true
initial_sampling_percentage = 100
destination_s3_uri = "s3://${var.bucket_lake}/reposicao/captura/"
capture_options {
capture_mode = "Input"
}
capture_options {
capture_mode = "Output"
}
}
kms_key_arn = aws_kms_key.receita_reposicao.arn
tags = { Projeto = var.projeto, Time = "dados", Laboratorio = "l78" }
}
resource "aws_sagemaker_endpoint" "reposicao_ab" {
name = "${var.projeto}-reposicao-ab"
endpoint_config_name = aws_sagemaker_endpoint_configuration.reposicao_ab.name
}
# kms-receita.tf -- chave dedicada para o que a captura e o resultado
# de negocio guardam: receita por loja, dado sensivel por franqueado.
resource "aws_kms_key" "receita_reposicao" {
description = "Captura de inferencia e receita por loja -- L78"
deletion_window_in_days = 30
enable_key_rotation = true
}
resource "aws_kms_alias" "receita_reposicao" {
name = "alias/${var.projeto}-receita-reposicao"
target_key_id = aws_kms_key.receita_reposicao.key_id
}
Peso relativo, não percentual
`initial_variant_weight` é peso RELATIVO entre variantes, não percentual absoluto — 1 e 1 dividem igual, 3 e 1 dariam 75/25. É esse número, e só ele, que decide a divisão do tráfego; o SageMaker cuida do roteamento, e nenhuma lógica de split precisa existir do lado do Sistema de Reposição.
Construir: ligar inferência a receita real, no Athena
A tabela que falta no processo antigo não é complicada de escrever — é que ninguém tinha catalogado os dois prefixos S3 para o Athena enxergar os dois como tabela. É esse o trabalho desta seção.
# catalogo-reposicao.tf -- Glue da' esquema aos dois prefixos S3
resource "aws_glue_catalog_database" "reposicao" {
name = "${var.projeto}_reposicao"
}
resource "aws_glue_catalog_table" "inferencias" {
name = "inferencias_capturadas"
database_name = aws_glue_catalog_database.reposicao.name
table_type = "EXTERNAL_TABLE"
parameters = { classification = "json" }
storage_descriptor {
location = "s3://${var.bucket_lake}/reposicao/captura/"
input_format = "org.apache.hadoop.mapred.TextInputFormat"
output_format = "org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat"
ser_de_info {
serialization_library = "org.openx.data.jsonserde.JsonSerDe"
}
columns { name = "inference_id" type = "string" }
columns { name = "variant_id" type = "string" }
columns { name = "timestamp" type = "string" }
columns { name = "sku_id" type = "string" }
columns { name = "loja_id" type = "string" }
columns { name = "porte_loja" type = "string" }
columns { name = "score_previsto" type = "double" }
}
}
resource "aws_athena_workgroup" "avaliacao_reposicao" {
name = "${var.projeto}-avaliacao-reposicao"
configuration {
enforce_workgroup_configuration = true
bytes_scanned_cutoff_per_query = 5368709120 # 5 GB -- consulta que passa disso e' bug, nao pesquisa
result_configuration {
output_location = "s3://${var.bucket_lake}/reposicao/resultados-athena/"
encryption_configuration {
encryption_option = "SSE_KMS"
kms_key_arn = aws_kms_key.receita_reposicao.arn
}
}
}
}
-- receita-por-variante-e-porte.sql
-- Junta a inferencia capturada ao pedido concluido de verdade,
-- agregando receita recuperada por variante e por porte de loja.
SELECT
inf.variant_id,
inf.porte_loja,
COUNT(DISTINCT inf.sku_id) AS skus_repostos,
SUM(ped.valor_total) AS receita_recuperada,
SUM(ped.valor_total) / COUNT(DISTINCT inf.sku_id) AS receita_media_por_sku
FROM reposicao.inferencias_capturadas inf
JOIN reposicao.pedidos_concluidos ped
ON ped.sku_id = inf.sku_id
AND ped.loja_id = inf.loja_id
-- so conta o pedido se ele foi concluido ATE 5 dias depois da
-- reposicao -- fora dessa janela, a venda nao e' efeito da fila
AND ped.data_conclusao BETWEEN
date(inf.timestamp) AND date_add('day', 5, date(inf.timestamp))
AND ped.status = 'concluido'
WHERE date(inf.timestamp) BETWEEN date('2026-07-20') AND date('2026-08-03')
GROUP BY inf.variant_id, inf.porte_loja
ORDER BY inf.variant_id, inf.porte_loja;
A janela do JOIN não é um detalhe
A janela de 5 dias entre reposição e conclusão do pedido é o parâmetro mais fácil de errar nesta consulta: curta demais, e pedidos legítimos ficam de fora, subestimando as duas variantes igualmente — mas se o efeito de v2 se manifesta numa janela diferente da de v1, a distorção deixa de ser igual, e a comparação para de ser justa.
Construir: viés pós-treino com SageMaker Clarify
Clarify não substitui a consulta de receita — ele responde uma pergunta que a média de receita não responde: será que o ganho de uma variante está concentrado num segmento, e o outro paga a conta?
# rodar_clarify_bias.py -- job de Clarify sobre a amostra capturada,
# separado por variante.
import boto3
sagemaker = boto3.client("sagemaker")
def rodar_bias_job(variant_id: str, sufixo_job: str) -> str:
resposta = sagemaker.create_processing_job(
ProcessingJobName=f"cadencia-clarify-{sufixo_job}",
AppSpecification={
# imagem gerenciada de Clarify, publicada pela AWS por regiao
"ImageUri": "205585389593.dkr.ecr.us-east-1.amazonaws.com/sagemaker-clarify-processing:1.0",
},
ProcessingInputs=[{
"InputName": "dataset",
"S3Input": {
"S3Uri": f"s3://cadencia-lake/reposicao/amostra-clarify/{variant_id}/",
"LocalPath": "/opt/ml/processing/input",
"S3DataType": "S3Prefix",
"S3InputMode": "File",
},
}],
ProcessingOutputConfig={"Outputs": [{
"OutputName": "analysis-result",
"S3Output": {
"S3Uri": f"s3://cadencia-lake/reposicao/clarify/{variant_id}/",
"LocalPath": "/opt/ml/processing/output",
"S3UploadMode": "EndOfJob",
},
}]},
ProcessingResources={"ClusterConfig": {
"InstanceType": "ml.m5.xlarge", "InstanceCount": 1, "VolumeSizeInGB": 20,
}},
RoleArn="arn:aws:iam::111122223333:role/ClarifyReposicaoRole",
)
return resposta["ProcessingJobArn"]
# facet = porte_loja ("pequena" vs "grande"); label = reposicao
# recuperou a venda (0/1), lido do resultado real via a mesma consulta
# Athena da secao anterior. DPPL = diferenca de proporcao de rotulo
# PREVISTO positivo entre os dois grupos -- e' a metrica que sinaliza
# se o MODELO favorece um porte, e nao so o dado de entrada.
for variante in ("v1", "v2"):
arn = rodar_bias_job(variante, f"reposicao-{variante}")
print(f"job de vies para {variante}: {arn}")
bias_config.json -- parte do analysis_config.json que o job de Clarify le do S3 antes de rodar
{
"dataset_type": "application/json",
"label": "reposicao_recuperou_venda",
"label_values_or_threshold": [1],
"facet": [
{ "name_or_index": "porte_loja", "value_or_threshold": ["pequena"] }
],
"group_variable": "variant_id",
"methods": {
"post_training_bias": { "methods": ["DPPL", "DI"] }
}
}
Amostra pequena demais mente por omissão
Clarify exige o atributo protegido (`porte_loja`, aqui) presente na amostra, com representação suficiente dos dois grupos — amostra com poucas lojas pequenas produz intervalo de confiança largo demais para a métrica significar algo, e o job não avisa isso sozinho. Confira a contagem por grupo ANTES de confiar no DPPL calculado; é o defeito nº 2 da seção de falhas propositais mais adiante.
Construir: o gate que só libera com significância estatística
A última peça é a que impede "o número de v2 é maior" de virar promoção sozinho. Um teste de duas proporções sobre a receita recuperada decide se a diferença é real ou é o ruído natural de 14 dias de reposição.
# calcular_significancia.py -- teste de duas amostras (Welch) sobre a
# receita recuperada por variante, global e por porte de loja.
import math
from scipy import stats
def intervalo_confianca_diferenca(media_a, dp_a, n_a, media_b, dp_b, n_b, confianca=0.95):
diferenca = media_b - media_a
erro_padrao = math.sqrt((dp_a ** 2) / n_a + (dp_b ** 2) / n_b)
z = stats.norm.ppf(1 - (1 - confianca) / 2)
margem = z * erro_padrao
return diferenca, (diferenca - margem, diferenca + margem)
# Numeros medidos nos 14 dias de teste A/B (media diaria, desvio padrao
# diario, n = 14 dias por variante). Ver a tabela da secao de implantacao.
casos = {
"global": {"v1": (42300, 3100, 14), "v2": (42480, 3400, 14)},
"loja pequena": {"v1": (8900, 640, 14), "v2": (7150, 590, 14)},
"loja grande": {"v1": (33400, 2200, 14), "v2": (35330, 2450, 14)},
}
for segmento, dados in casos.items():
media_v1, dp_v1, n_v1 = dados["v1"]
media_v2, dp_v2, n_v2 = dados["v2"]
diff, (baixo, alto) = intervalo_confianca_diferenca(
media_v1, dp_v1, n_v1, media_v2, dp_v2, n_v2)
significativo = baixo > 0 or alto < 0 # IC nao cruza zero
print(f"{segmento:14s} diff={diff:+8.0f} IC95%=({baixo:+.0f}, {alto:+.0f}) "
f"significativo={significativo}")
gate-promocao.asl.json -- Step Functions: nega por padrao, so promove com os dois resultados dentro do limiar
{
"StartAt": "AvaliarNegocio",
"States": {
"AvaliarNegocio": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-east-1:111122223333:function:cadencia-avaliar-receita",
"Catch": [{ "ErrorEquals": ["States.ALL"], "Next": "Reprovado" }],
"Next": "AvaliarVies"
},
"AvaliarVies": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-east-1:111122223333:function:cadencia-avaliar-vies",
"Catch": [{ "ErrorEquals": ["States.ALL"], "Next": "Reprovado" }],
"Next": "Decidir"
},
"Decidir": {
"Type": "Choice",
"Choices": [{
"And": [
{ "Variable": "$.negocio.significativo_positivo", "BooleanEquals": true },
{ "Variable": "$.vies.dppl_max", "NumericLessThan": 0.05 }
],
"Next": "PromoverV2"
}],
"Default": "Reprovado"
},
"PromoverV2": { "Type": "Task", "Resource": "arn:aws:states:::sagemaker:updateEndpointWeightsAndCapacities", "End": true },
"Reprovado": { "Type": "Task", "Resource": "arn:aws:sns:us-east-1:111122223333:cadencia-decisao-modelo", "End": true }
}
}
Catch em cada etapa, não só no fim
`Catch: States.ALL` em CADA task é o requisito nº 6 virando código: se a Lambda de negócio ou a de viés falhar por qualquer motivo — timeout, exceção, permissão — o fluxo vai direto para `Reprovado`, nunca para `Decidir` com dado incompleto. Sem esse `Catch`, uma falha de rede na Lambda derrubaria a execução inteira sem notificar ninguém — pior que reprovar.
Implantar, e medir a AUC contra a receita, lado a lado
Depois de 14 dias de tráfego real dividido 50/50, os quatro números abaixo são a razão de existir deste laboratório. A AUC prometeu uma coisa; a receita e o viés contam uma história diferente.
# medir_14_dias.sh -- roda a consulta Athena da secao 9 e o teste de
# significancia da secao 11 sobre a janela de 14 dias do teste A/B.
aws athena start-query-execution \
--query-string file://receita-por-variante-e-porte.sql \
--work-group cadencia-avaliacao-reposicao \
--query-execution-context Database=cadencia_reposicao
python3 calcular_significancia.py
# global diff= +180 IC95%=(-508, +868) significativo=False
# loja pequena diff= -1750 IC95%=(-2196, -1304) significativo=True
# loja grande diff= +1930 IC95%=(+1053, +2807) significativo=True
aws sagemaker describe-processing-job --processing-job-name cadencia-clarify-reposicao-v1 \
--query 'ProcessingOutputConfig.Outputs[0].S3Output.S3Uri'
aws sagemaker describe-processing-job --processing-job-name cadencia-clarify-reposicao-v2 \
--query 'ProcessingOutputConfig.Outputs[0].S3Output.S3Uri'
# DPPL v1 (porte pequena vs grande): 0.04
# DPPL v2 (porte pequena vs grande): 0.11
| Métrica | v1 (produção) | v2 (candidato) | Diferença e IC 95% | Significativo? |
|---|---|---|---|---|
| AUC offline (holdout) | 0,85 | 0,89 | +0,04 | não se aplica — é métrica de ML, não de negócio |
| Receita recuperada/dia — global | R$ 42.300 | R$ 42.480 | +R$ 180 · IC (-508; +868) | NÃO — o intervalo cruza zero |
| Receita recuperada/dia — lojas pequenas | R$ 8.900 | R$ 7.150 | -R$ 1.750 (-19,7%) · IC (-2.196; -1.304) | SIM — pior, e o intervalo inteiro é negativo |
| Receita recuperada/dia — lojas grandes | R$ 33.400 | R$ 35.330 | +R$ 1.930 (+5,8%) · IC (+1.053; +2.807) | SIM — melhor, e é isso que puxa a média |
| Viés pós-treino (DPPL, pequena vs grande) | 0,04 | 0,11 | acima do limiar combinado de 0,05 | reprova por si só, mesmo se a receita global tivesse subido |
A leitura correta: v2 não é "pior que v1" nem "melhor que v1" — é melhor para lojas grandes e pior para lojas pequenas, e a média global fica perto de zero porque os dois efeitos quase se cancelam. A AUC de 0,89 nunca poderia ter mostrado isso: ela mede acerto agregado sobre o holdout inteiro, sem separar por porte de loja — a mesma métrica que convenceria o processo antigo a promover é a que esconde o motivo de não promover.
O achado central deste laboratório
AUC melhor, receita global estatisticamente igual, um segmento inteiro pior com significância — e o gate reprova por dois motivos independentes, não um: o lift de receita global não passa no teste de significância, E o DPPL de 0,11 está acima do limiar de 0,05 mesmo olhando só as lojas grandes isoladamente. Promover v2 como está teria melhorado o número que o time media (AUC) e piorado o número que o negócio sente (receita das lojas pequenas).
Quebrar de propósito: três falhas e o diagnóstico
As três falhas abaixo foram provocadas de propósito nesta mesma captura de 14 dias, para mostrar como cada uma passaria despercebida se o pipeline não tivesse os controles certos.
| Falha | Como provocar | Sintoma | Onde olhar | Correção |
|---|---|---|---|---|
| Pedido cancelado por fraude entra na receita recuperada | remover o filtro de `status = 'concluido'` da consulta Athena | receita recuperada global sobe 6–8% para as duas variantes igualmente, sem mudar a diferença entre elas — mas infla o número absoluto que vai para o relatório executivo | contagem de pedidos por status na tabela `pedidos_concluidos` antes e depois do filtro | manter `status = 'concluido'` explícito na consulta, e alertar se a proporção de pedidos cancelados por fraude mudar de mês para mês |
| Amostra do Clarify com poucas lojas pequenas | rodar o job de viés sobre 1 dia de captura em vez de 14 | DPPL sai baixo (parece "sem viés") com intervalo de confiança tão largo que qualquer valor caberia dentro dele — falta de poder estatístico, não ausência de viés | contagem de exemplos por grupo (`porte_loja`) no relatório do job de Clarify, não só o valor final do DPPL | exigir um mínimo de exemplos por grupo antes de aceitar o resultado — abaixo disso, o job deve reprovar por dado insuficiente, não retornar um número que parece limpo |
| Janela do A/B test encurtada para 2 dias | medir a receita recuperada 2 dias após a promoção em vez de 14 | o intervalo de confiança da diferença global cruza zero de forma ainda mais óbvia, mas por falta de amostra — não porque a diferença real seja menor | tamanho de amostra (`n`) usado no cálculo do erro padrão, na saída de `calcular_significancia.py` | definir a duração mínima do teste ANTES de rodar, a partir do poder estatístico necessário — não parar cedo porque o resultado parcial já "parece bom" |
Nenhuma das três derruba o job
As três falhas têm uma coisa em comum: nenhuma quebra o pipeline. Os jobs terminam, os números saem, o dashboard atualiza. O defeito é estatístico, não estrutural — e é exatamente o tipo de erro que passa despercebido quando alguém olha só o valor final, sem checar o tamanho da amostra que produziu esse valor.
O modelo v2 da Cadência tem AUC 0,89 contra 0,85 do v1 no teste offline. Depois de 14 dias de A/B real, a receita recuperada global tem diferença de +R$ 180/dia com IC 95% de (-508, +868). O que essa combinação de números significa?
Segurança: dado de receita por segmento é dado sensível
Receita recuperada por porte de loja não é métrica técnica — é desempenho comercial por franqueado, e vazar isso fora do time certo tem consequência de negócio, não só de conformidade.
| Risco | Probabilidade | Impacto | Controle preventivo | Detecção | Resposta |
|---|---|---|---|---|---|
| Papel de IAM amplo demais lê a tabela de receita por loja | média | alto — performance por franqueado vaza para fora do time de avaliação, afetando relação comercial | política de IAM com `Resource` específico na tabela do Glue Catalog, sem `athena:*` amplo | CloudTrail em consultas Athena por identidade, alarme em acesso fora do papel `AvaliacaoModeloRole` | revogar a política, revisar quem mais tinha acesso ao mesmo papel, notificar o time de dados |
| Bucket de captura de inferência sem KMS | baixa (Terraform já exige a chave) | alto — dado de decisão de negócio em texto claro no S3 | `kms_key_arn` obrigatório no `data_capture_config` e no bucket de resultado do Athena, como na seção 8 | AWS Config regra `s3-bucket-server-side-encryption-enabled` | aplicar criptografia retroativa e girar a chave |
| Alguém promove modelo direto no console, pulando o Step Functions | média — acontece sob pressão de prazo | alto — é o mesmo padrão do L82 (prompt alterado sem rastro), aqui aplicado a modelo em vez de prompt | IAM nega `sagemaker:UpdateEndpointWeightsAndCapacities` diretamente a humano; só a role do Step Functions tem essa permissão | CloudTrail alarma em `UpdateEndpointWeightsAndCapacities` fora da role esperada | reverter o peso ao estado anterior, abrir revisão de acesso IAM |
// iam-avaliacao-role.json -- policy minima do papel que roda Athena e
// le o resultado; NAO inclui permissao de promover o endpoint.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["athena:StartQueryExecution", "athena:GetQueryResults"],
"Resource": "arn:aws:athena:us-east-1:111122223333:workgroup/cadencia-avaliacao-reposicao"
},
{
"Effect": "Allow",
"Action": ["glue:GetTable", "glue:GetDatabase"],
"Resource": [
"arn:aws:glue:us-east-1:111122223333:catalog",
"arn:aws:glue:us-east-1:111122223333:database/cadencia_reposicao",
"arn:aws:glue:us-east-1:111122223333:table/cadencia_reposicao/*"
]
},
{
"Effect": "Allow",
"Action": ["kms:Decrypt", "kms:GenerateDataKey"],
"Resource": "arn:aws:kms:us-east-1:111122223333:key/CHAVE-RECEITA-REPOSICAO"
}
// Nenhuma acao 'sagemaker:UpdateEndpoint*' aqui de proposito --
// essa permissao existe SO na role do Step Functions.
]
}
A permissão que falta é intencional
A ausência de `sagemaker:UpdateEndpointWeightsAndCapacities` nesta policy não é omissão — é a linha que garante que promoção só acontece pelo Step Functions, com os dois resultados verificados. Adicionar essa ação "para agilizar em produção" reabre exatamente o atalho que o requisito nº 5 da tabela de requisitos proíbe.
Observabilidade: as perguntas que o painel tem de responder
O painel único (seção 6 da arquitetura de produção) existe para que ninguém volte a decidir olhando uma métrica de cada vez, em telas separadas.
| Pergunta | Métrica ou consulta | O que significa mudar | Limiar inicial |
|---|---|---|---|
| A diferença de receita entre variantes já é significativa? | IC 95% da diferença, recalculado a cada execução do job Athena | intervalo deixou de cruzar zero — há sinal suficiente para decidir, positivo ou negativo | recalcular diariamente a partir do dia 7; antes disso, amostra insuficiente para confiar no IC |
| Algum segmento está sendo penalizado? | DPPL por porte de loja, por variante, do job de Clarify | DPPL cruzou 0,05 — o modelo passou a tratar os grupos de forma desproporcional | 0,05, combinado com o time de negócio antes do teste começar — não depois de ver o resultado |
| A amostra tem tamanho suficiente para confiar nos dois números acima? | contagem de exemplos por variante e por porte na tabela capturada | algum grupo caiu abaixo do mínimo — os dois cálculos acima deixam de ser confiáveis, mesmo que o valor pareça normal | mínimo de 200 exemplos por combinação variante × porte antes de aceitar qualquer conclusão |
| O endpoint com duas variantes está custando o esperado? | horas-instância do CloudWatch, somadas pelas duas variantes | custo dobrou em relação ao endpoint único — é o preço esperado do teste, não uma anomalia | alarme só se ultrapassar o dobro do custo do endpoint único por mais de 1 dia após o fim programado do teste |
O teste tem data para terminar
O quarto item do painel — custo do endpoint duplo — existe para pegar o erro mais bobo e mais caro: esquecer de encerrar a variante v1 depois que o gate decide. Um teste A/B "esquecido" em produção paga hora dobrada de instância indefinidamente, sem que a decisão de negócio precise de mais nenhum dado novo.
Escala: 400 SKUs/dia, 4.000/dia, e o que passa a doer
| Volume | O que acontece | O que passa a doer | O que fazer |
|---|---|---|---|
| 400 SKUs/dia (hoje) | Athena varre 14 dias de captura em segundos; Clarify roda em menos de 10 minutos por variante | nada — é o volume que o desenho deste laboratório foi medido com | manter como está |
| 4.000 SKUs/dia (10x) | captura cresce 10x em volume, mas continua cabendo no particionamento diário do Glue Catalog sem mudança de esquema | custo de Athena por byte escaneado cresce proporcional — sentido em `bytes_scanned_cutoff_per_query`, que pode começar a barrar consultas legítimas | particionar a tabela do Glue por dia (`PARTITIONED BY (dt)`), reduzindo o quanto cada consulta varre |
| 40.000 SKUs/dia (100x, cenário Black Friday) | Clarify sobre a amostra inteira fica lento demais para rodar dentro da janela diária | amostragem do job de Clarify precisa ser explícita — rodar sobre 10% do tráfego, não sobre tudo, mantendo tamanho mínimo por grupo | configurar `initial_sampling_percentage` menor no Data Capture SÓ para o job de Clarify, mantendo 100% para a consulta de receita, que precisa do dado completo |
| Falha de uma AZ durante o teste A/B | o endpoint SageMaker multi-AZ por padrão absorve a falha sem intervenção — não é diferente de um endpoint de variante única | nada específico deste laboratório — a resiliência do endpoint em si é tema do L74, não deste | confirmar que `initial_instance_count` de cada variante permite pelo menos uma instância saudável por AZ |
O ponto de atenção real não é o endpoint — é o Athena e o Clarify, porque os dois escalam por BYTE e por AMOSTRA, não por instância provisionada. Dobrar o tráfego sem particionar a tabela dobra o custo de cada consulta, silenciosamente, até o `bytes_scanned_cutoff_per_query` começar a recusar consultas legítimas.
Custo: o que este laboratório muda na fatura
Três cenários, cada um dominado por uma dimensão de custo diferente.
| Cenário | Volume | O que acontece com a fatura | Tendência | Otimização |
|---|---|---|---|---|
| Protótipo | 1 teste A/B, 14 dias, 400 SKUs/dia | dominado pela segunda instância do endpoint rodando em paralelo — é literalmente o dobro do custo de instância do L74 durante o teste | termina quando o gate decide; não é custo permanente | encerrar a variante perdedora no mesmo dia da decisão, não na próxima sprint |
| Produção pequena | 1 teste A/B por mês, mais consultas Athena de auditoria avulsas | Athena passa a dominar: cada consulta de auditoria varre a partição do mês inteiro se a tabela não estiver particionada por dia | cresce com a frequência de auditoria, não com o volume de SKUs | particionar por dia (seção de escala) reduz o byte varrido por consulta de auditoria em proporção direta ao particionamento |
| Alta escala (vários modelos em teste A/B ao mesmo tempo) | reposição, cancelamento (L73/L74) e outros modelos, cada um com seu próprio ciclo de avaliação | Clarify passa a dominar: cada job é uma instância dedicada por execução, e o número de execuções cresce com o número de modelos em teste | cresce linear com modelos em avaliação simultânea, não com volume de tráfego de nenhum deles | compartilhar o workgroup do Athena entre modelos, mas manter job de Clarify isolado por modelo — misturar amostras de modelos diferentes invalida o DPPL |
| Dimensão | Cobra por | Cuidado |
|---|---|---|
| Endpoint com duas variantes | hora de instância, dobrado enquanto o teste dura | tem data para acabar — ver o alarme da seção de observabilidade |
| Athena | byte escaneado por consulta | particionamento errado faz uma consulta de auditoria varrer meses de dado por engano |
| SageMaker Clarify | instância de processamento por job, não por amostra | rodar um job por variante por dia, em vez de um job consolidado ao fim do teste, multiplica o custo sem necessidade |
Modelo de cobrança, não valor
Nenhum valor em dólar aparece nesta seção por decisão: preço de instância e de byte escaneado mudam por região e por acordo de conta — confira o AWS Pricing Calculator para o valor atual. O que a certificação cobra, e o que fica verdadeiro independente do preço, é o MODELO de cobrança: instância paga por hora ligada, Athena paga por byte varrido, Clarify paga por job — não por resultado.
Well-Architected nos seis pilares
| Pilar | Situação ao fim deste laboratório | Risco que fica | Melhoria | Prioridade |
|---|---|---|---|---|
| Excelência operacional | gate de promoção auditável e reproduzível via Step Functions, substituindo decisão manual em notebook | ainda depende de alguém disparar o teste A/B; não é gatilho automático a cada novo candidato | acoplar ao pipeline do L76, disparando A/B automaticamente quando um candidato passa no teste offline | média |
| Segurança | receita por segmento restrita por IAM e KMS; promoção impossível fora do Step Functions | o mesmo controle não existe ainda para outros modelos da Cadência além deste | replicar a policy da seção 14 como módulo Terraform reutilizável | alta |
| Confiabilidade | falha em qualquer avaliação nega promoção por padrão, nunca aprova por omissão | falha na PRÓPRIA orquestração do Step Functions (não nas tasks) não tem alarme dedicado ainda | CloudWatch alarme em execução do Step Functions travada por mais de X horas | média |
| Eficiência de performance | Athena e Clarify escalam de forma independente do endpoint de inferência | sem particionamento, consulta de auditoria cresce com o volume acumulado, não com o volume do dia | particionamento diário do Glue Catalog, como descrito na seção de escala | média |
| Otimização de custo | teste A/B tem início e fim definidos, não é custo permanente | nada impede alguém de deixar duas variantes ligadas depois da decisão, por esquecimento | alarme de custo dobrado além da data esperada de término (seção de observabilidade) | alta |
| Sustentabilidade | instância extra da segunda variante só existe durante o teste, não indefinidamente | job de Clarify roda sobre a amostra inteira por padrão, sem amostragem, mesmo quando isso não é necessário | amostragem configurável no Clarify para volumes altos, como na seção de escala | baixa |
Evolução em níveis: o que muda, e o que passa a doer
Cada nível resolve o risco do anterior e cria um risco novo — inclusive este laboratório, que resolve "promover sem prova" e cria "complexidade de manter três avaliações sincronizadas".
Um notebook calcula AUC de v1 e v2 sobre um holdout fixo; se a candidata vence, ela substitui a produção no dia seguinte, sem período de teste em paralelo.Endpoint com duas variantes recebendo tráfego real, Athena ligando previsão a receita real, Clarify medindo viés por segmento, Step Functions negando promoção por padrão.A avaliação do L78 responde "promover ou não", num momento único. O tema do L77 — drift — responde a pergunta seguinte: o modelo promovido continua bom daqui a três meses, ou o padrão de reposição mudou sob os pés dele?Em vez de alguém disparar o teste A/B manualmente, o pipeline de treino (tema do L76) aciona a avaliação de negócio e de viés automaticamente sempre que um candidato passa no teste offline.O facet único (porte de loja) deste laboratório vira uma lista: região, categoria de produto, sazonalidade — cada um auditado separadamente pelo Clarify antes de qualquer promoção.Se um dia a priorização de reposição incorporar um componente generativo — por exemplo, um agente que explica em texto por que priorizou cada SKU — AUC e DPPL deixam de bastar como instrumento de julgamento: dados e modelo continuam existindo, mas a IA que precisa ser avaliada passa a exigir golden set e LLM-as-judge, que é exatamente o tema do L88.Para onde este laboratório aponta
O nível 6 não é deste laboratório — é para onde ele aponta. Enquanto o que a Cadência avalia é um classificador clássico (este laboratório, e os quatro modos do L74), AUC, receita e DPPL são o instrumento certo. No dia em que o sistema avaliado tiver componente generativo, o instrumento muda — não porque os dados ou o modelo desapareçam, mas porque a SAÍDA deixa de ter um rótulo binário para comparar contra um limiar. O L88 resolve essa mudança de instrumento; este laboratório resolve a anterior.
Onde IA generativa entra nesta arquitetura, e onde não entra
O modelo que este laboratório avalia já É um sistema de IA — um classificador que prioriza reposição. A pergunta desta seção é diferente: um LLM agregaria algo à AVALIAÇÃO em si, não ao modelo avaliado?
Uma tentação real: pedir a um LLM que leia os números da tabela da seção de implantação e escreva o relatório executivo em prosa. Isso é conveniência de redação, não decisão — o gate de promoção continua sendo o teste de significância e o limiar de DPPL, calculados por estatística determinística. Um LLM errando a leitura de um IC que cruza zero e escrevendo "resultado positivo" na prosa seria pior que não ter relatório nenhum, porque pareceria autoritativo.
| O que IA generativa poderia fazer aqui | Por que não entra agora | Quando entraria |
|---|---|---|
| Redigir o resumo executivo a partir dos números do Athena/Clarify | é texto sobre um resultado já decidido por estatística — risco de errar a leitura do IC supera o ganho de conveniência, e o gate não pode depender disso | como passo OPCIONAL depois da decisão do Step Functions, nunca antes — e sempre com o número bruto ao lado do texto gerado |
| Julgar a qualidade de uma explicação gerada por um agente de reposição | não existe ainda — o sistema de hoje é um classificador, sem componente de texto gerado para julgar | no dia em que a priorização ganhar um componente generativo (nível 6 da evolução), com golden set e LLM-as-judge — é exatamente o tema do L88 |
Por que a decisão continua sendo estatística, não geração
O `CLAUDE.md` desta escola diz que o ensino de IA aqui é "sem hype". Colocar um LLM no meio de um gate estatístico que já funciona, só porque IA generativa está disponível, seria exatamente o hype que a plataforma existe para não vender — a estatística determinística deste laboratório é a ferramenta certa para a pergunta que ele responde.
Anti-padrões deste laboratório
| Anti-padrão | Por que alguém faz | Sintoma em produção | Forma correta |
|---|---|---|---|
| Promover só porque a AUC subiu | é a métrica que o notebook de treino já mostra de graça — ligar a receita real dá trabalho de Athena e Glue que ninguém orçou | receita não muda, ou piora num segmento, meses depois — sem nenhum sinal no momento da promoção | pipeline de negócio (Athena) obrigatório antes de qualquer gate de promoção poder abrir |
| Olhar só a média de receita, sem segmentar | segmentar exige saber qual atributo de negócio importa (porte de loja, aqui), e a média já "parece boa" sozinha | um segmento inteiro perde participação de mercado silenciosamente, escondido atrás de um número agregado estável | Clarify e Athena sempre agrupados pelo mesmo atributo de segmento, nunca só no agregado |
| Comparar dois números sem intervalo de confiança | parece redundante quando um número já é visivelmente maior que o outro | a decisão é revertida semanas depois, quando a diferença se prova ter sido ruído de amostra pequena | teste de significância (IC 95%) antes de qualquer decisão, mesmo quando a diferença parece óbvia |
| Encurtar a janela do teste A/B porque "o resultado já parece bom" | esperar 14 dias inteiros para uma decisão que parece clara no dia 3 soa como burocracia desnecessária | decisão tomada com poder estatístico insuficiente, revertida quando mais dado chega e muda a conclusão | duração mínima do teste definida ANTES de rodar, a partir do poder estatístico necessário — não da impressão do dia 3 |
Quando algo não funciona
| Sintoma | Causa provável | Como investigar | Onde olhar | Correção |
|---|---|---|---|---|
| Consulta Athena retorna zero linhas para uma das variantes | Data Capture não estava habilitado quando essa variante começou a receber tráfego | conferir `enable_capture` e a data de criação do endpoint contra a data de início do teste | prefixo S3 da captura, particionado por data | reiniciar a contagem dos 14 dias a partir de quando as duas variantes têm captura completa |
| Job de Clarify falha com erro de coluna ausente | o campo `porte_loja` não chegou na captura para alguma SKU (cadastro de loja incompleto) | contar linhas com `porte_loja IS NULL` na tabela capturada | qualidade do cadastro de loja, upstream da captura | excluir da amostra de Clarify, mas alertar — cadastro incompleto é problema de dado, não do job |
| IC da diferença de receita é enorme, maior que a própria média | poucos dias de dado ainda (teste rodando há menos de 7 dias), ou outlier de pedido de valor muito alto distorcendo o desvio padrão | número de dias decorridos do teste, e distribuição de `valor_total` por pedido | resultado do teste de significância, campo `n` usado no cálculo | esperar completar os 14 dias antes de decidir; se for outlier, investigar se é pedido legítimo antes de excluir |
| Step Functions fica preso em `AvaliarNegocio` por horas | consulta Athena travada por `bytes_scanned_cutoff_per_query` sendo atingido, sem retry configurado na Lambda | status da execução no console do Step Functions, e status da query no Athena | logs da Lambda `cadencia-avaliar-receita` | adicionar timeout e retry com backoff na task, e revisar se o particionamento da seção de escala já foi aplicado |
O sintoma mais perigoso desta lista não é o que quebra visivelmente — é o IC enorme do terceiro caso, porque ele PARECE um resultado válido ("não significativo") quando na verdade é falta de dado. Confira sempre o `n` antes de aceitar "não significativo" como resposta final.
Limpeza: o que fica ligado depois do "terminei"
O recurso mais caro que sobrevive ao "terminei" aqui não é exótico — é a segunda variante do endpoint, que continua cobrando hora de instância indefinidamente se ninguém a desligar depois do gate decidir.
#!/usr/bin/env bash
# limpar.sh -- reverte para variante unica e remove o que o A/B test
# deixou para tras.
set -euo pipefail
PROJETO="${PROJETO:?defina PROJETO}"
# 1. A variante perdedora -- e' o item mais caro desta lista. Depois
# que o Step Functions decide, o endpoint deveria voltar a ter uma
# variante so, com 100% do peso.
aws sagemaker update-endpoint-weights-and-capacities \
--endpoint-name "${PROJETO}-reposicao-ab" \
--desired-weights-and-capacities VariantName=v2,DesiredWeight=1 VariantName=v1,DesiredWeight=0
# 2. Terraform derruba endpoint, endpoint config, Glue, Athena workgroup,
# Step Functions e a chave KMS (com janela de 30 dias, nao imediato).
terraform destroy -auto-approve
# 3. O DADO capturado NAO sai no destroy -- e' historico de decisao,
# mantenha por auditoria, mas confirme que o ciclo de vida do bucket
# tem uma politica (nao fica acumulando para sempre).
aws s3api get-bucket-lifecycle-configuration --bucket cadencia-lake 2>/dev/null \
|| echo "AVISO: sem lifecycle configurado no bucket de captura"
# 4. Resultado dos jobs de Clarify tambem fica no S3 -- nao apaga
# sozinho, e cada execucao gera um prefixo novo.
aws s3 ls s3://cadencia-lake/reposicao/clarify/ --recursive | tail -5
# 5. Prova final: nada com o nome do projeto rodando fora do esperado.
aws resourcegroupstaggingapi get-resources \
--tag-filters Key=Laboratorio,Values=l78 \
--query "ResourceTagMappingList[].ResourceARN" --output table
| Recurso | Sai no destroy? | Cobra parado? | Por que fica |
|---|---|---|---|
| Segunda variante do endpoint (v1 ou v2, a perdedora) | só se o destroy remover o endpoint inteiro | sim — hora de instância dobrada, o item mais caro desta lista | `terraform destroy` remove o endpoint inteiro, mas se o objetivo é só voltar a uma variante, é preciso o comando de peso do passo 1 antes |
| Dado capturado no S3 (inferências + resultado do Clarify) | não | não em si, mas acumula custo de armazenamento indefinidamente sem lifecycle policy | é o histórico de decisão do modelo — apagar destrói a auditoria, mas precisa de política de ciclo de vida |
| Chave KMS dedicada | não imediatamente — janela de exclusão de 30 dias | não cobra por estar "pendente de exclusão", mas continua existindo e sendo faturável até o fim da janela | AWS exige a janela para evitar perda acidental de dado criptografado; 30 dias é o padrão deste laboratório |
| Athena workgroup e tabelas do Glue Catalog | sim, no destroy | não — Glue Catalog e workgroup não cobram por existirem vazios | — |
O destroy não sabe qual variante venceu
`terraform destroy` não sabe que existe um passo humano antes dele: se a decisão foi promover v2, alguém precisa atualizar o peso ANTES de destruir, senão o destroy remove os dois endpoints — inclusive o que deveria continuar servindo a fila de reposição de amanhã.
Resumo: problema, peça e motivo
| Problema | Peça | Por que ela, e não outra |
|---|---|---|
| AUC melhor não prova receita melhor | Athena juntando captura de inferência a pedido concluído real | é o único jeito de ligar previsão a outcome sem reimplementar um data warehouse dedicado só para isto |
| Média boa pode esconder segmento pior | SageMaker Clarify, viés pós-treino, facet = porte de loja | calcula a métrica padrão (DPPL) de forma comparável, em vez de reimplementar a fórmula à mão por segmento |
| "O número é maior" não é o mesmo que "é melhor" | teste de duas amostras com intervalo de confiança de 95% | distingue diferença real de ruído de amostra — sem isso, toda decisão de promoção é uma aposta disfarçada de dado |
| Falha silenciosa não pode virar promoção | Step Functions com `Catch` em cada task, negando por padrão | garante que ausência de resultado é tratada como reprovação, nunca como aprovação por omissão |
| Receita por segmento é dado sensível | IAM com `Resource` específico + KMS dedicado | restringe quem vê desempenho comercial por franqueado, sem impedir quem precisa decidir a promoção |
| Falha | O que a protege | O que ela NÃO protege |
|---|---|---|
| Promover por AUC sozinha | gate do Step Functions exigindo resultado de Athena e Clarify | não protege contra um atributo de segmento que ninguém pensou em auditar (região, categoria — ver a dívida declarada na seção de decisões) |
| Viés escondido atrás da média | Clarify segmentado por porte de loja, comparado entre variantes | não protege se o atributo de segmento em si estiver mal cadastrado (a falha nº 2 da seção de troubleshooting) |
| Decisão tomada com dado insuficiente | duração mínima de 14 dias definida antes do teste começar | não protege contra sazonalidade que dura mais que 14 dias (uma campanha trimestral, por exemplo) — o teste mediria só um recorte dela |
- Endpoint com duas variantes, tráfego real dividido 50/50
- Data Capture ligado, cada inferência marcada com variant_id
- Glue Data Catalog sobre captura de inferência e pedidos concluídos
- Consulta Athena de receita recuperada por variante e por porte de loja
- Job de SageMaker Clarify medindo DPPL por porte, por variante
- Cálculo de intervalo de confiança de 95% sobre a diferença de receita
- Step Functions negando promoção por padrão, exigindo os dois resultados dentro do limiar
- IAM e KMS restringindo quem vê receita por segmento e quem pode promover
Perguntas frequentes
❓ Por que uma AUC de 0,89 não é motivo suficiente para promover um modelo?
❓ O que é DPPL, e por que ele é diferente da AUC?
❓ Por que o teste A/B precisa durar 14 dias, e não pode ser decidido no primeiro dia?
❓ Por que dividir o tráfego 50/50 em vez de um canary pequeno crescendo aos poucos?
❓ Por que o Step Functions nega a promoção quando um job de avaliação falha?
❓ Por que a receita por segmento de loja é tratada como dado sensível?
❓ O que este laboratório faria diferente se v2 tivesse vencido nos dois critérios?
Fixando
O DPPL medido pelo SageMaker Clarify é 0,04 para v1 e 0,11 para v2, comparando lojas pequenas e grandes, com limiar combinado de 0,05. Ao mesmo tempo, a receita das lojas grandes sobe 5,8% com v2. Por que o time de avaliação NÃO deveria promover v2 mesmo considerando esse ganho?
A arquitetura mínima deste laboratório (seção 4) promove v2 porque AUC(v2) > AUC(v1), sem nenhuma outra verificação. A arquitetura de produção (seção 5) exige Athena, Clarify E significância estatística antes de promover. Qual é a diferença estrutural entre as duas, além do número de peças?
Conhecimentos, próximo módulo e documentação
| Item | Conteúdo |
|---|---|
| Documentação consultada | SageMaker endpoints com múltiplas production variants e peso relativo; SageMaker Data Capture; SageMaker Clarify — bias pós-treino e as métricas DPPL/DI; Athena sobre tabelas do Glue Data Catalog; Step Functions com tratamento de erro por `Catch` em cada estado. Nenhum valor em dólar aparece neste módulo por decisão: os números de receita, AUC e DPPL são medições do cenário da Cadência — confira o AWS Pricing Calculator para custo atual de instância, byte escaneado no Athena e job de processamento do Clarify |
| Verificação técnica | os nomes de parâmetro (`initial_variant_weight`, `bytes_scanned_cutoff_per_query`, `group_variable` do Clarify) dependem da versão do provider Terraform e do SDK usados — confirme na documentação atual antes de aplicar em produção. Os volumes da Cadência (400 SKUs/dia, R$ 42.300/dia de receita recuperada) são do cenário de exemplo; meça os seus antes de copiar um limiar |
Este laboratório fecha o ciclo que o L74 abriu: servir o modelo não é o fim do trabalho, é o começo da pergunta "esse modelo, servido assim, está fazendo o que a Cadência precisa que ele faça?" — e a resposta, medida com número, quase nunca é a mesma coisa que a métrica de ML sozinha diria.
Próximo passo
O tema do L77 continua de onde este módulo para: mesmo um modelo aprovado pelo gate deste laboratório pode degradar meses depois, silenciosamente, se o padrão de reposição mudar sob os pés dele — é isso que monitoramento de drift audita, de forma contínua, em vez de pontual. E quando o que se avalia deixar de ser um classificador clássico e passar a ter componente generativo, o L88 troca o instrumento de julgamento — golden set e LLM-as-judge — pela mesma disciplina que este módulo aplicou aqui: nunca aceitar uma métrica sem perguntar o que ela está escondendo.
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…