CSuite Pricing Agent - Documentação Completa
📋 Índice
- Visão Geral
- Arquitetura
- Integração com CSuite Pricing
- Cálculo Interno do Preço
- Fatores Calculados Internamente - Explicação para Leigos
- API e Payloads
- Como Usar a Resposta do Agent
- Módulos e Responsabilidades
- Fluxo de Decisão
- Políticas e Validações
- Testes
- Exemplos de Uso
- Troubleshooting
Visão Geral
O CSuite Pricing Agent é um agente autônomo de precificação que integra com o schema csuite_pricing para tomar decisões de preço baseadas em políticas governamentais, respeitando:
- Preço de Tela (PT): Preço máximo de referência
- Piso Técnico: Preço mínimo inviolável
- Corredores de Decisão: Espaços seguros entre PT e Piso
- Elasticidade: Níveis de flexibilidade (NONE, LOW, MEDIUM, HIGH, MAX)
- Cliente Âncora: Preços estáveis derivados de política
Características Principais
- ✅ Integração completa com
csuite_pricingschema - ✅ Suporte a Cliente Âncora (preço estável)
- ✅ Preços Fixos por Cliente (prioridade máxima por SKU/cliente)
- ✅ Promoções Automáticas por Segmento (ondas a cada 2 dias baseadas em estoque)
- ✅ Produtos em Lançamento (preço promocional temporário com bypass de LPP) 🆕
- ✅ Motor SQL (
sp_price_compute_v8) para cálculos - ✅ Promoções esporádicas (preço de promoção substitui cálculo normal)
- ✅ Descontos por quantidade (preços diferentes baseados na quantidade comprada)
- ✅ Bônus por valor do pedido (order_value_factor - descontos extras para pedidos maiores)
- ✅ Desconto por forma de pagamento (descontos para MACHINES com pagamento em menos de 5 parcelas) 🆕
- ✅ Último preço pago (last_price - limite de aumento baseado no histórico do cliente)
- ✅ Validação de políticas em tempo real
- ✅ Auditoria completa de decisões
- ✅ Instrumentação com Policy Engine
- ✅ Compatibilidade com formato legado
Arquitetura
Estrutura de Módulos
agents/pricing/
├── __init__.py # Configuração do agente
├── main.py # FastAPI entrypoint
├── decision.py # Lógica principal de decisão
├── context.py # Construção de contexto enriquecido
├── engine.py # Integração com motor SQL
├── anchor.py # Lógica de Cliente Âncora
├── repository.py # Acesso a dados do schema
├── database.py # Gerenciamento de sessões DB
├── policies.py # Validação de políticas
├── audit.py # Registro de auditoria
└── execution.py # Execução de ações
Fluxo de Dados
Payload → Context Builder → Decision Engine → Policy Validation → Audit → Execution
Implementação (observações do código)
- Context Builder assíncrono: existe
build_context_asyncque paraleliza consultas independentes (PT, Piso, brand role, machine_curve, stock_level, customer profile) para reduzir latência. - Cache de resultado (Redis):
cache_redisé usado para cachear respostas por cliente/sku/brand (TTL configurável viaCACHE_TTL_RESULT). O cache é opcional e não quebra o fluxo quando indisponível. - Repository com cache local: consultas em
repository.pyusam um decorator@cachedcontrolado por variáveis de ambiente (CACHE_ENABLED,CACHE_TTL_PRODUCT,CACHE_TTL_BRAND). Há fallback se o cache não estiver disponível. - Motor SQL (sp_price_compute_v8):
engine.compute_price_v8chama a stored procedurecsuite_pricing.sp_price_compute_v8, aceitainstallments,ptepisopassados pelo contexto para evitar queries duplicadas e normalizastock_level(aceitavery_low, low, normal, high, very_high). Retorna metadados adicionais (tier_code, market_context, volume_12m, brand_role, curve_factor, stock_level_factor, order_value_factor, payment_term_discount, etc.). - Precedência de regras (implementação): o código aplica na ordem — Cliente Âncora (maior prioridade) → Preço Fixo por Cliente → Promoção Ativa → Cálculo via motor SQL → Combo/Bundle (se aplicável) → Desconto por Quantidade (SKU ou família) → Ajustes por Último Preço Pago (LPP) → Teto de Lançamento → Proteção Regional → Validações finais (PT/Piso).
- Promoções:
repository.get_active_promotioncheca primeiropricing_promotions(manuais) e depoispricing_active_promotions(automáticas por estoque). Promoções manuais têm prioridade sobre automáticas. - Descontos por família/cart: quando não há desconto por SKU, o agente tenta detectar família pelo
product_modelou consultandoget_product_familye soma quantidades doorder_itemsou docart/querypara aplicar desconto por família. A extração de família aceita modelos comoA6000P-G→A6000. - Último preço pago (LPP):
get_customer_last_valid_priceretorna referência emax_allowed_price; o agente limita aumentos acima desse máximo. Regras detectam se o último preço foi promoção (quandolast_price < piso * 0.90) e usam preço médio como referência nesses casos. - Produtos em lançamento:
check_launch_productpode marcarignore_lpp— quando ativo, a verificação de LPP é ignorada; além disso há lógica que limita o preço aolaunch_price(teto) enquanto o lançamento estáACTIVE. - Fallbacks e tolerância a falhas: se o engine falhar (ou PT/Piso não encontrados), o agente tenta
_run_decision_legacycomo fallback; muitas operações (cache, instrumentação) são opcionais e não quebram o fluxo se ausentes. - Instrumentação opcional: integração com
Policy Engineé tentada, mas o módulo é importado condicionalmente e não impede execução se estiver ausente (instrument_decisionpode ser None). - Validações e auditoria: decisões e incidentes são registradas via
log_price_calculationelog_price_incidentpara rastreabilidade.
Integração com CSuite Pricing
O Pricing Agent está totalmente integrado com o schema csuite_pricing, utilizando:
Tabelas Principais
price_screens: Preço de Tela (PT) por SKUprice_floors: Piso Técnico por SKUbrands: Informações de marca (principal/secundária)customer_brand_profiles: Perfil do cliente por marcapricing_anchor_prices: Preços âncora (read-only, derivados)pricing_params: Parâmetros de precificaçãopricing_promotions: Promoções esporádicas de produtospricing_quantity_discounts: Descontos por quantidadepricing_order_value_discounts: Bônus por valor do pedidopricing_payment_term_discounts: Descontos por forma de pagamento (MACHINES) 🆕pricing_last_price_rules: Regras de limite de aumento por último preçopricing_launch_products: Produtos em lançamento com preço promocional 🆕price_calculations: Auditoria de cálculosprice_incidents: Registro de incidentes (PT ≤ Piso)
Stored Procedures
sp_price_compute_v8: Motor principal de cálculo (fallback parav7)
Cálculo Interno do Preço
O cálculo do preço final é realizado pela stored procedure sp_price_compute_v8, que considera múltiplos fatores para determinar o desconto e o preço final. Esta seção detalha todos os fatores considerados.
Parâmetros de Entrada
A stored procedure recebe os seguintes parâmetros:
| Parâmetro | Tipo | Descrição | Origem |
|---|---|---|---|
p_brand_id |
BIGINT | ID da marca | Payload |
p_customer_id |
BIGINT | ID do cliente (opcional) | Payload |
p_sku_id |
BIGINT | ID do SKU | Payload |
p_PT |
DECIMAL | Preço de Tela (Price Screen) | csuite_pricing.price_screen |
p_PISO |
DECIMAL | Preço Piso (Price Floor) | csuite_pricing.price_floor |
p_machine_curve |
CHAR(1) | Curva ABC do produto (A-E) | Payload |
p_sku_qty |
INT | Quantidade do SKU no pedido | Payload |
p_order_value |
DECIMAL | Valor total do pedido | Payload |
Fatores Calculados Internamente
A stored procedure calcula os seguintes fatores em sequência:
📚 Fatores Calculados Internamente - Explicação para Leigos
Esta seção explica, de forma simples e sem jargões técnicos, como o sistema calcula o preço final de um produto. Imagine que você está negociando um preço com um cliente - o sistema faz isso automaticamente, considerando vários fatores importantes.
🎯 Como Funciona o Cálculo de Preço
O sistema funciona como um vendedor experiente que analisa várias informações antes de oferecer um desconto. O cálculo segue uma ordem de prioridade:
Ordem de Prioridade:
1. Cliente Âncora? → Se sim, usa preço âncora (EXIT)
2. Produto em Promoção? → Se sim, usa preço de promoção (EXIT)
3. Cálculo Normal → Analisa vários fatores para calcular desconto
Fatores do Cálculo Normal:
1. Quem é o cliente? (Contexto de Mercado)
2. Quanto ele compra? (Volume de Compras)
3. Qual o nível dele? (Tier - Nível de Cliente)
4. Qual a importância da marca? (Papel da Marca)
5. Qual a importância do produto? (Curva ABC)
6. Qual o nível de estoque? (Stock Level)
7. Qual o valor do pedido? (Order Value Factor)
8. Quanto ele pagou antes? (Last Paid Price) 🆕
9. Qual o desconto permitido? (Cálculo Final)
Vamos entender cada um desses fatores:
1. 📍 Contexto de Mercado (Market Context)
O que é?
O sistema verifica se o cliente é do mercado "street" (varejo direto) ou "non_street" (atacado/distribuição).
Por que importa?
- Mercado Street (Varejo): Clientes que vendem diretamente ao consumidor final. Para proteger margens, o desconto máximo é limitado a 12%, mesmo que o cliente seja muito importante.
- Mercado Non-Street (Atacado): Clientes que compram em grandes volumes para revender. Podem receber descontos maiores conforme seu volume de compras.
Exemplo prático:
- Cliente A: Loja de rua que vende diretamente ao consumidor → Street → Desconto máximo: 12%
- Cliente B: Distribuidor que compra grandes volumes → Non-Street → Pode receber descontos maiores
Como funciona no sistema:
O sistema consulta uma tabela que classifica cada cliente. Se não encontrar informação, assume "non_street" por padrão.
2. 💰 Volume de Compras (Volume 12 Meses)
O que é?
A soma de tudo que o cliente comprou nos últimos 12 meses.
Por que importa?
Clientes que compram mais merecem melhores condições. É como um programa de fidelidade: quanto mais você compra, mais benefícios recebe.
Exemplo prático:
- Cliente A: Comprou R$ 50.000 nos últimos 12 meses
- Cliente B: Comprou R$ 500.000 nos últimos 12 meses
- Cliente C: Comprou R$ 2.000.000 nos últimos 12 meses
O Cliente C, que compra muito mais, terá acesso a descontos melhores.
Como funciona no sistema:
O sistema soma todas as compras do cliente dos últimos 12 meses. Se o cliente é novo ou não tem histórico, assume R$ 0,00.
3. 🏆 Tier (Nível do Cliente)
O que é?
Uma classificação do cliente baseada no volume de compras. É como categorias de um programa de fidelidade: Bronze, Prata, Ouro, Platina.
Por que importa?
Cada nível (Tier) tem regras diferentes de desconto. Quanto maior o tier, melhores as condições.
Exemplo prático:
- Tier V1: Clientes que compram até R$ 100.000/ano → Desconto base: 5%
- Tier V2: Clientes que compram entre R$ 100.000 e R$ 500.000/ano → Desconto base: 10%
- Tier V3: Clientes que compram entre R$ 500.000 e R$ 1.000.000/ano → Desconto base: 15%
- Tier V4: Clientes que compram mais de R$ 1.000.000/ano → Desconto base: 20%
Como funciona no sistema:
O sistema compara o volume de compras do cliente com faixas pré-definidas e determina qual tier ele pertence. Se não encontrar, assume Tier V1 (menor desconto).
4. 🎯 Papel da Marca (Brand Role)
O que é?
A importância estratégica da marca para a empresa. Algumas marcas são mais importantes que outras.
Por que importa?
Marcas mais importantes podem ter políticas de desconto diferentes. É como dar mais atenção a um produto "carro-chefe" da empresa.
Exemplo prático:
- Marca Principal (Primary Target): Marca estratégica, queremos vender mais → Descontos maiores
- Marca Secundária (Secondary Target): Marca importante, mas não prioritária → Descontos menores
Como funciona no sistema:
O sistema consulta uma tabela que define o papel de cada marca. Se não encontrar, assume "secondary_target" (marca secundária).
5. 📊 Desconto por Tier + Papel da Marca
O que é?
O desconto base que o cliente pode receber, combinando seu Tier com o Papel da Marca.
Por que importa?
Um cliente Tier V3 comprando uma marca principal pode receber um desconto maior do que um cliente Tier V3 comprando uma marca secundária.
Exemplo prático:
- Cliente Tier V2 + Marca Principal → Desconto: 12%
- Cliente Tier V2 + Marca Secundária → Desconto: 8%
Como funciona no sistema:
O sistema consulta uma tabela que combina Tier e Papel da Marca para determinar o desconto base. Se não encontrar, assume 0% (sem desconto).
6. 🛡️ Limite para Mercado Street (Cap Street)
O que é?
Uma proteção para o mercado varejo: mesmo que o cliente seja muito importante, o desconto máximo é limitado a 12%.
Por que importa?
Protege as margens no mercado varejo, onde os preços precisam ser mais estáveis.
Exemplo prático:
- Cliente Street, Tier V4 (que normalmente teria 20% de desconto) → Desconto limitado a 12%
- Cliente Non-Street, Tier V4 → Desconto completo de 20%
Como funciona no sistema:
Se o cliente é "street", o sistema compara o desconto calculado com 12% e usa o menor valor. Se não é "street", usa o desconto completo.
7. 🏷️ Preços Fixos por Cliente (Prioridade Máxima) 🆕
O que é?
Permite que clientes específicos tenham preços fixos acordados para determinados produtos (SKUs). Estes preços têm validade mensal e auto-renovação.
Por que importa?
- Contratos: Garante preços acordados em contratos de longo prazo.
- Fidelização: Oferece condições especiais estáveis para clientes estratégicos.
- Governança: Flag de revisão automática quando o dólar ou custos sobem.
Como funciona:
1. O sistema busca primeiro se existe um preço fixo ativo para o cliente e produto solicitado.
2. Se existir, este preço é usado imediatamente, ignorando todas as outras regras (Tier, Curva, Estoque, etc).
3. O preço fixo ainda é validado para garantir que não está abaixo do piso técnico (Price Floor).
8. 🎁 Promoções (Nova Camada)
O que é?
Alguns produtos entram em promoção esporadicamente. Quando um produto está em promoção, o sistema usa o preço de promoção como Preço Candidato, ignorando todo o cálculo normal.
Por que importa?
Promoções são usadas para escoar estoque, atrair clientes ou promover produtos específicos. Quando uma promoção está ativa, ela tem prioridade sobre o cálculo normal de preço.
Exemplo prático:
- Produto A: Preço normal calculado: R$ 3.000,00
- Produto A em promoção: Preço de promoção: R$ 2.500,00
- Resultado: Preço Candidato = R$ 2.500,00 (preço de promoção)
Como funciona no sistema:
1. O sistema verifica se o produto tem uma promoção ativa (dentro do período de validade)
2. Se houver promoção, usa o preço de promoção como Preço Candidato
3. O preço de promoção é validado para respeitar limites (PT e Piso)
4. Se o preço de promoção estiver abaixo do piso, é ajustado para o piso
5. Se o preço de promoção estiver acima do PT, é ajustado para o PT
Tabela de Promoções:
As promoções são armazenadas na tabela pricing_promotions (esporádicas) e pricing_active_promotions (automáticas por segmento).
Promoções Automáticas por Segmento:
- Segmentos: Máquinas, Rolamentos, Peças, Autopeças, Motopeças.
- Lógica: Identifica produtos com estoque alto/muito alto e gera ondas promocionais a cada 2 dias.
- Duração: Geralmente 48 horas para incentivar giro rápido.
Ordem de Prioridade no Sistema:
1. Cliente Âncora (maior prioridade)
2. Promoção Ativa (segunda prioridade)
3. Cálculo Normal (terceira prioridade)
4. Desconto por Quantidade (aplicado após cálculo normal)
8. 📊 Descontos por Quantidade (Nova Camada)
O que é?
Alguns produtos (especialmente da Curva A) têm preços diferentes baseados na quantidade comprada. Quanto mais unidades o cliente compra, menor o preço unitário.
Por que importa?
Descontos por quantidade incentivam compras em maior volume, melhorando o giro de estoque e aumentando o ticket médio das vendas.
Exemplo prático:
- Produto SKU 1980206 (Curva A):
- QTY 1-2: R$ 2.610,00 (preço base)
- QTY 3-4: R$ 2.500,00 (desconto por volume)
- QTY 5-9: R$ 2.450,00 (desconto maior)
- QTY 10+: R$ 2.400,00 (desconto máximo)
Como funciona no sistema:
1. O sistema executa o cálculo base via motor SQL (sp_price_compute_v8) para obter PT, Piso, contexto e o payment_term_discount.
2. Em seguida, verifica se existe uma regra especial (Combo/Bundle, desconto por quantidade por SKU ou por família).
3. Se existir uma dessas regras, ela sobrepõe o desconto normal do motor:
- A base de cálculo passa a ser o PT.
- Aplica-se o desconto do combo/quantidade (ou preço fixo).
- Aplica-se apenas o desconto de prazo de pagamento (payment_term_discount).
- Todos os outros descontos (Tier, Brand Role, Curve, Stock, Order Value) são ignorados.
4. O preço final é validado para respeitar limites (PT e Piso), e em seguida pode ser ajustado por LPP/lançamento/proteção regional.
Tabela de Descontos por Quantidade:
As regras são armazenadas na tabela pricing_quantity_discounts com:
- SKU do produto
- Faixa de quantidade (min_quantity, max_quantity)
- Preço fixo OU desconto percentual
- Descrição da regra
- Prioridade (se houver múltiplas regras)
Campos importantes (coluna product_family):
- product_family: identifica uma família de produtos (ex.: códigos de família como B9000). Quando preenchido, a regra aplica-se à família inteira — o agente tentará agregar quantidades do pedido/cart para calcular se a faixa de família é atendida. Se product_family for NULL a regra aplica-se apenas ao sku_id.
- min_quantity / max_quantity: faixas inclusivas usadas para determinar aplicação. max_quantity pode ser NULL para representar 'infinito' (ex.: 10+).
- price: preço fixo por unidade (se fornecido) — substitui cálculo normal.
- discount_pct: desconto percentual sobre o PT (se fornecido) — aplicado quando não há price.
- is_active: flag que habilita/desabilita a regra em tempo de execução.
- priority: ordem de preferência quando múltiplas regras se sobrepõem (maior valor = maior prioridade).
Observação de comportamento:
- Regras por sku_id têm precedência sobre regras por product_family quando ambas aplicam; se não houver regra por SKU, o agente tenta a regra por família agregando quantidades do order_items ou do cart/query.
- Exemplo visto no sistema: algumas linhas têm product_family = 'B9000' (aplicam por família) enquanto outras possuem product_family = NULL (aplicam por SKU). Veja a lista de faixas (1-2, 3-4, 5-9, 10+) usada no front-end.
Exemplos de consultas úteis
-- Listar regras ativas de desconto por SKU/família
SELECT
id,
sku_id,
product_family,
min_quantity,
max_quantity,
price,
discount_pct,
description,
is_active,
priority,
created_by,
created_at,
updated_at
FROM csuite_pricing.pricing_quantity_discounts
WHERE is_active = 1
ORDER BY
COALESCE(sku_id, 999999999),
COALESCE(product_family, ''),
priority DESC,
min_quantity;
-- Buscar regras aplicáveis para um SKU específico (ex.: sku_id = 1980206)
SELECT *
FROM csuite_pricing.pricing_quantity_discounts qd
WHERE qd.is_active = 1
AND (
qd.sku_id = 1980206
OR (qd.product_family IS NOT NULL AND qd.product_family = (
SELECT product_family FROM csuite_pricing.product_classification pc WHERE pc.sku_id = 1980206 LIMIT 1
))
)
ORDER BY qd.priority DESC, qd.min_quantity;
-- Agregar quantidade total por família a partir de um payload/order (exemplo usando uma tabela temporária 'order_items')
SELECT
qd.product_family,
SUM(oi.quantity) AS total_family_qty
FROM order_items oi
JOIN csuite_pricing.product_classification pc ON pc.sku_id = oi.sku_id
JOIN csuite_pricing.pricing_quantity_discounts qd ON qd.product_family = pc.product_family
WHERE oi.order_id = 12345
GROUP BY qd.product_family;
Exemplo em Python (usando funções do repositório)
from agents.pricing import repository
def find_applicable_quantity_discount(sku_id: int, quantity: int, brand_id: int | None = None):
"""Retorna a regra de desconto aplicável (por SKU ou por família).
- Tenta primeiro regra por SKU (`get_quantity_discount`).
- Se não houver, tenta regra por família (`get_family_quantity_discount`) usando `get_product_family`.
"""
# 1) Regra específica por SKU
sku_rule = repository.get_quantity_discount(sku_id=sku_id, quantity=quantity, brand_id=brand_id)
if sku_rule:
return sku_rule
# 2) Fallback: regra por família
product_family = repository.get_product_family(sku_id)
if not product_family:
return None
# Neste exemplo assumimos `quantity` como total da família; em produção agregue do payload/cart
family_rule = repository.get_family_quantity_discount(product_family, quantity)
return family_rule
# Uso de exemplo
if __name__ == "__main__":
rule = find_applicable_quantity_discount(1980206, 5)
if rule:
print("Regra aplicável:", rule)
else:
print("Nenhuma regra de desconto por quantidade encontrada")
Quando é aplicado:
- Aplicado após o motor SQL, mas sobrepõe os descontos do motor (base = PT)
- Aplicado antes das validações finais de limites (PT e Piso)
- Se o preço após desconto por quantidade estiver abaixo do piso, é ajustado para o piso
- Se o preço após desconto por quantidade estiver acima do PT, é ajustado para o PT
Exemplo de fluxo:
1. Cálculo normal: Preço = R$ 2.800,00
2. Cliente compra QTY 5
3. Sistema encontra regra: QTY 5-9 = R$ 2.450,00
4. Preço Final = R$ 2.450,00 (desconto por quantidade aplicado)
9. ⭐ Fator da Curva ABC (Curve Factor)
O que é?
Um multiplicador baseado na importância do produto. Produtos são classificados em curvas A, B, C, D ou E.
Por que importa?
Produtos mais importantes (curva A) podem ter descontos ajustados de forma diferente de produtos menos importantes (curva E).
Como funciona a Curva ABC:
- Curva A: Produtos mais vendidos (top 80% das vendas) → Fator: 1.2 (aumenta desconto em 20%)
- Curva B: Produtos médios (próximos 15%) → Fator: 1.0 (sem ajuste)
- Curva C: Produtos menos vendidos (últimos 5%) → Fator: 0.8 (reduz desconto em 20%)
- Curva D: Produtos sem vendas, mas com estoque → Fator: 0.5 (reduz desconto em 50%)
- Curva E: Produtos sem vendas e sem estoque → Fator: 0.3 (reduz desconto em 70%)
Exemplo prático:
- Desconto base: 15%
- Produto Curva A (fator 1.2) → Desconto final: 15% × 1.2 = 18%
- Produto Curva C (fator 0.8) → Desconto final: 15% × 0.8 = 12%
Como funciona no sistema:
O sistema consulta uma tabela que define o fator para cada curva. Se não encontrar, assume fator 1.0 (sem ajuste).
10. 📦 Fator do Nível de Estoque (Stock Level Factor)
O que é?
Um multiplicador baseado na situação de estoque do produto. Produtos são classificados em níveis: baixo, normal ou alto.
Por que importa?
- Estoque Alto: Queremos escoar estoque parado → Aumenta desconto
- Estoque Normal: Situação equilibrada → Sem ajuste
- Estoque Baixo: Queremos preservar estoque → Reduz desconto
Como funciona:
- Estoque Alto (high): Fator: 1.2 (aumenta desconto em 20% para escoar)
- Estoque Normal (normal): Fator: 1.0 (sem ajuste)
- Estoque Baixo (low): Fator: 0.8 (reduz desconto em 20% para preservar)
Exemplo prático:
- Desconto após curva: 18%
- Produto com Estoque Alto (fator 1.2) → Desconto final: 18% × 1.2 = 21.6%
- Produto com Estoque Baixo (fator 0.8) → Desconto final: 18% × 0.8 = 14.4%
Como funciona no sistema:
O sistema consulta uma tabela que define o fator para cada nível de estoque. Se não encontrar, assume fator 1.0 (sem ajuste).
11. 💰 Fator do Valor do Pedido (Order Value Factor) 🆕
O que é?
Um multiplicador baseado no valor total do pedido. Pedidos maiores podem receber descontos adicionais.
Por que importa?
- Pedidos de alto valor: Incentiva pedidos maiores oferecendo descontos extras
- Fidelização: Clientes que fazem pedidos grandes são recompensados
- Ticket médio: Aumenta o valor médio dos pedidos
Como funciona:
- Pedido ≥ R$ 20.000: Fator: 1.2 (aumenta desconto em +20%)
- Pedido ≥ R$ 10.000: Fator: 1.1 (aumenta desconto em +10%)
- Pedido ≥ R$ 5.000: Fator: 1.05 (aumenta desconto em +5%)
- Pedido < R$ 5.000: Fator: 1.0 (sem bônus)
Exemplo prático:
- Desconto base após curva e estoque: 8.40%
- Pedido de R$ 32.640,00 (fator 1.2) → Desconto efetivo: 8.40% × 1.2 = 10.08%
- Pedido de R$ 2.000,00 (fator 1.0) → Desconto efetivo: 8.40% × 1.0 = 8.40%
Como funciona no sistema:
O sistema consulta a tabela pricing_order_value_discounts que define faixas de valor e seus respectivos fatores. Se não encontrar, assume fator 1.0 (sem bônus).
Tabela de Descontos por Valor do Pedido:
As regras são armazenadas na tabela pricing_order_value_discounts com:
- Valor mínimo do pedido (min_order_value)
- Valor máximo do pedido (max_order_value) - pode ser NULL para "sem limite"
- Fator multiplicador (factor)
- Descrição da faixa
- Flag de ativo (is_active)
12. 💳 Desconto por Forma de Pagamento (Payment Term Discount) 🆕
O que é?
Um desconto adicional aplicado para produtos do segmento MACHINES quando o pagamento é feito em menos de 5 parcelas.
Por que importa?
- Melhora de caixa: Incentiva pagamentos à vista ou em poucas parcelas
- Segmento específico: Aplicado apenas para máquinas (produtos de alto valor)
- Flexibilidade: Cliente escolhe entre desconto ou mais parcelas
Como funciona:
- Apenas MACHINES: Desconto aplicado apenas para produtos do segmento MACHINES (segment_id = 1)
- Parcelas < 5: Desconto apenas para pagamentos em 0, 1, 2, 3 ou 4 parcelas
- Aplicado após cálculo normal: O desconto é aplicado sobre o preço já com desconto normal
Tabela de Descontos:
| Parcelas | Desconto |
|----------|----------|
| À vista (0) | 5% |
| 1x | 4% |
| 2x | 3% |
| 3x | 2% |
| 4x | 1% |
| 5x ou mais | 0% (sem desconto) |
Exemplo prático:
- Produto MACHINES: PT = R$ 3.264,00
- Desconto normal calculado: 8.4% → Preço após desconto normal: R$ 2.989,82
- Pagamento em 2x → Desconto adicional: 3%
- Preço após desconto de pagamento: R$ 2.989,82 × (1 - 0.03) = R$ 2.900,13
- Piso: R$ 2.549,18 → Preço Final: R$ 2.900,13 ✅
Como funciona no sistema:
1. O sistema verifica o segment_id do produto na tabela product_classification
2. Se segment_id = 1 (MACHINES) e installments < 5:
- Busca o desconto na tabela pricing_payment_term_discounts
- Aplica o desconto sobre o preço após desconto normal
3. Se não for MACHINES ou parcelas ≥ 5 → sem desconto adicional
Tabela de Configuração (pricing_payment_term_discounts):
- segment_id: ID do segmento (1 = MACHINES)
- installments: Número de parcelas (0 = à vista, 1 = 1x, etc.)
- discount_pct: Desconto percentual (0.050000 = 5%)
- is_active: Flag de ativo
- description: Descrição da regra
Ordem de Aplicação:
1. Cálculo normal (Tier, Brand Role, Curve, Stock Level, Order Value)
2. Desconto por Forma de Pagamento (se aplicável)
3. Validação do Piso
4. Preço Final
13. 📋 Último Preço Pago (Last Paid Price) 🆕
O que é?
O sistema verifica quanto o cliente pagou na última compra daquele produto e limita aumentos de preço para evitar surpresas negativas.
Por que importa?
- Consistência: Cliente espera pagar preço similar ao anterior
- Confiança: Evita "susto" com aumentos repentinos
- Fidelização: Demonstra que a empresa valoriza o histórico
- Proteção contra promoções: Não usa preço de promoção como referência
Como funciona:
- Clientes Tier V4 (top): Máximo +3% de aumento
- Clientes Tier V3: Máximo +4% de aumento
- Demais clientes: Máximo +5% de aumento
- Preço de promoção: Ignorado (usa preço médio)
Exemplo prático:
| Cenário | Último Preço | Preço Calculado | Resultado |
|---------|--------------|-----------------|-----------|
| Normal | R$ 2.940 | R$ 2.900 | R$ 2.900 ✅ (desconto) |
| Normal | R$ 2.940 | R$ 3.000 | R$ 3.000 ✅ (+2%, dentro do limite) |
| Normal | R$ 2.940 | R$ 3.200 | R$ 3.087 ⚠️ (limitado a +5%) |
| Promoção | R$ 2.200 | R$ 2.900 | R$ 2.900 ✅ (promoção ignorada) |
| Primeira compra | - | R$ 2.900 | R$ 2.900 ✅ (sem referência) |
Como funciona no sistema:
1. O sistema busca histórico de compras do cliente para o SKU
2. Verifica se o último preço era válido (não era promoção)
- Se last_price >= piso × 0.90 → preço válido, usar como referência
- Se last_price < piso × 0.90 → provavelmente promoção, usar preço médio
3. Calcula o máximo permitido: reference_price × (1 + max_increase_pct)
4. Se preço calculado > máximo permitido → limita ao máximo
Tabela de Regras (pricing_last_price_rules):
| Tier | Aumento Máximo | Histórico |
|------|----------------|-----------|
| V4 | +3% | 24 meses |
| V3 | +4% | 18 meses |
| Outros | +5% | 12 meses |
14. 🎁 Desconto Final
O que é?
O desconto que será realmente aplicado, após considerar todos os fatores.
Como é calculado:
Desconto Final = Desconto Base × Fator da Curva ABC × Fator do Stock Level × Fator do Order Value
Proteções de Segurança:
- Desconto nunca pode ser negativo (mínimo: 0%)
- Desconto nunca pode ser maior que 95% (máximo: 95%)
Exemplo prático:
- Desconto base: 15%
- Fator da curva: 1.2
- Fator do stock level: 1.2 (estoque alto)
- Fator do order value: 1.2 (pedido ≥ R$ 20k)
- Desconto final: 15% × 1.2 × 1.2 × 1.2 = 25.92% (limitado a 95% máx)
Outro exemplo:
- Desconto base: 8.4%
- Fator da curva: 1.0
- Fator do stock level: 1.0 (estoque normal)
- Fator do order value: 1.2 (pedido ≥ R$ 20k)
- Desconto final: 8.4% × 1.0 × 1.0 × 1.2 = 10.08%
15. 💵 Preço Candidato
O que é?
O preço calculado após aplicar o desconto ao Preço de Tela (PT).
Como é calculado:
Preço Candidato = Preço de Tela × (1 - Desconto Final)
Exemplo prático:
- Preço de Tela (PT): R$ 100,00
- Desconto Final: 18%
- Preço Candidato: R$ 100,00 × (1 - 0.18) = R$ 100,00 × 0.82 = R$ 82,00
16. 🚧 Respeitar o Piso (Price Floor)
O que é?
O preço mínimo que pode ser cobrado. É como um "chão" que o preço não pode ultrapassar para baixo.
Por que importa?
Garante que nunca vendamos abaixo do custo ou de um preço mínimo definido pela empresa.
Como funciona:
- Se o Preço Candidato for maior que o Piso → Usa o Preço Candidato ✅
- Se o Preço Candidato for menor que o Piso → Usa o Piso (proteção) 🛡️
Exemplo prático:
- Preço Candidato: R$ 82,00
- Piso Técnico: R$ 80,00
- Preço Final: R$ 82,00 (maior que o piso, então usa o candidato)
Outro exemplo:
- Preço Candidato: R$ 75,00
- Piso Técnico: R$ 80,00
- Preço Final: R$ 80,00 (menor que o piso, então usa o piso para proteger)
📐 Resumo do Cálculo Completo
Vamos ver um exemplo completo do início ao fim:
Cenário:
- Preço de Tela (PT): R$ 3.264,00
- Piso Técnico: R$ 2.549,18
- Cliente: Distribuidor (Non-Street), comprou R$ 97.998 nos últimos 12 meses
- Tier: V2 (baseado no volume)
- Marca: Secundária (Secondary Target)
- Produto: Curva A (produto importante)
- Estoque: Normal
- Valor do Pedido: R$ 32.640,00 (10 unidades)
Passo a passo:
- Contexto de Mercado: Non-Street ✅
- Volume 12 Meses: R$ 97.998
- Tier: V2
- Papel da Marca: Secondary Target
- Desconto Base (Tier V2 + Secondary): 8.4%
- Limite Street: Não aplica (é Non-Street) → Desconto permitido: 8.4%
- Fator da Curva A: 1.0
- Fator do Stock Level (Normal): 1.0
- Fator do Order Value (≥ R$ 20k): 1.2 🆕 (bônus +20%)
- Desconto Final: 8.4% × 1.0 × 1.0 × 1.2 = 10.08% (efetivo: 8.4%)
- Preço Após Desconto Normal: R$ 3.264,00 × (1 - 0.084) = R$ 2.989,82
- Desconto por Pagamento (2x, MACHINES): 3% 🆕 → Preço após desconto de pagamento: R$ 2.989,82 × (1 - 0.03) = R$ 2.900,13
- Respeitar Piso: R$ 2.900,13 > R$ 2.549,18 → Preço Final: R$ 2.900,13 ✅
Resultado: O cliente receberá o produto por R$ 2.900,13, com 8.4% de desconto normal + 3% de desconto por pagamento em 2x (totalizando 11.15% de desconto efetivo). O Order Value Factor de 1.2 ampliou o limite de desconto permitido, e o desconto por forma de pagamento foi aplicado adicionalmente.
🎓 Conceitos Importantes
Preço de Tela (PT):
O preço de referência máximo. É como o "preço de prateleira" antes de qualquer desconto.
Piso Técnico:
O preço mínimo que pode ser cobrado. É uma proteção para não vender abaixo de um valor definido.
Tier (Nível):
Uma classificação do cliente baseada no volume de compras. Quanto maior o tier, melhores as condições.
Curva ABC:
Uma classificação dos produtos baseada na importância (vendas). Produtos A são os mais importantes.
Desconto Final:
O desconto que será realmente aplicado, após considerar todos os fatores e proteções.
❓ Perguntas Frequentes
P: Por que o desconto é limitado a 95%?
R: Para proteger a empresa de erros ou configurações incorretas. Um desconto de 100% significaria vender de graça.
P: O que acontece se o Preço Candidato for menor que o Piso?
R: O sistema usa o Piso como preço final. Isso garante que nunca vendamos abaixo do mínimo definido.
P: Por que clientes Street têm desconto limitado a 12%?
R: Para proteger as margens no mercado varejo, onde os preços precisam ser mais estáveis e competitivos.
P: Como o sistema sabe qual curva ABC usar?
R: O sistema consulta uma tabela que classifica cada produto automaticamente baseado nas vendas dos últimos 12 meses.
P: Como o sistema sabe qual stock level usar?
R: O sistema consulta a tabela product_classification que calcula automaticamente o nível de estoque baseado em dias de estoque e vendas. Se não encontrar, assume 'normal' (sem ajuste).
P: O que acontece se não encontrar informações do cliente?
R: O sistema usa valores padrão (fallback) que são conservadores, garantindo que nunca dê descontos maiores do que deveria.
P: Como funciona o Order Value Factor? 🆕
R: O sistema verifica o valor total do pedido e aplica um bônus no desconto. Pedidos maiores (≥ R$ 20.000) recebem até +20% de bônus no desconto. Por exemplo, se o desconto base é 8.4% e o pedido vale R$ 32.640, o limite de desconto sobe para 10.08% (8.4% × 1.2).
P: O Order Value Factor é obrigatório?
R: Não. Se não houver configuração na tabela pricing_order_value_discounts ou o pedido for pequeno, o fator assume valor 1.0 (sem bônus).
P: Como funciona o Last Paid Price (Último Preço Pago)? 🆕
R: O sistema verifica o último preço que o cliente pagou por aquele produto. Se o novo preço calculado for maior que o último preço + 5% (variação máxima configurável), o sistema limita o aumento. Por exemplo, se o cliente pagou R$ 2.940 na última compra, o máximo que ele pode pagar agora é R$ 3.087 (2.940 × 1.05).
P: E se o último preço pago foi em uma promoção?
R: O sistema detecta automaticamente se o preço anterior era de promoção (quando o preço está abaixo de 90% do piso técnico). Nesse caso, o último preço é ignorado e o sistema usa o preço médio como referência.
P: O cliente sempre vai pagar o mesmo preço ou menos?
R: Não necessariamente. O sistema permite aumentos de até 5% (configurável por tier). Clientes V4 (top tier) têm limite de +3%, V3 têm +4%, e demais têm +5%. Reduções de preço não têm limite.
🔍 Fatores Calculados Internamente (Versão Técnica)
A stored procedure calcula os seguintes fatores em sequência:
1. Market Context (Contexto de Mercado)
Tabela: csuite_pricing.customer_market_context
- Valores possíveis:
'street'ou'non_street' - Fallback:
'non_street' - Impacto: Determina se o cliente está no mercado "street" (varejo direto) ou "non_street" (atacado/distribuição)
- Uso: Aplica cap de desconto de 12% para mercado street (MVP fixo)
SELECT COALESCE((
SELECT market_context
FROM csuite_pricing.customer_market_context
WHERE customer_id = p_customer_id AND is_active = 1
LIMIT 1
), 'non_street')
INTO v_market_context;
2. Volume 12 Meses
Tabela: csuite_pricing.vw_customer_volume_12m
- Descrição: Volume total de compras do cliente nos últimos 12 meses
- Fallback:
0.00 - Impacto: Usado para determinar o tier (nível) do cliente
- Uso: Base para classificação em tiers de volume (V1, V2, V3, etc.)
SELECT COALESCE((
SELECT volume_12m
FROM csuite_pricing.vw_customer_volume_12m
WHERE customer_id = p_customer_id
LIMIT 1
), 0.00)
INTO v_volume_12m;
3. Tier Code (Nível de Volume)
Tabela: csuite_pricing.pricing_volume_tiers
- Descrição: Classificação do cliente baseada no volume de compras
- Valores possíveis:
'V1','V2','V3', etc. (definidos na tabela) - Fallback:
'V1' - Lógica: O tier é determinado comparando
v_volume_12mcom os intervalosmin_volume_12memax_volume_12mda tabela - Impacto: Tiers maiores geralmente recebem descontos maiores
SELECT COALESCE((
SELECT tier_code
FROM csuite_pricing.pricing_volume_tiers
WHERE is_active = 1
AND v_volume_12m >= min_volume_12m
AND (max_volume_12m IS NULL OR v_volume_12m < max_volume_12m)
LIMIT 1
), 'V1')
INTO v_tier_code;
4. Brand Role (Papel da Marca)
Tabela: csuite_pricing.brands
- Descrição: Papel estratégico da marca para a organização
- Valores possíveis:
'primary_target','secondary_target', etc. - Fallback:
'secondary_target' - Impacto: Marcas primárias podem ter políticas de desconto diferentes
- Uso: Combinado com tier para determinar desconto máximo
SELECT COALESCE((
SELECT brand_role
FROM csuite_pricing.brands
WHERE brand_id = p_brand_id AND is_active = 1
LIMIT 1
), 'secondary_target')
INTO v_brand_role;
5. Desconto por Tier + Brand Role
Tabela: csuite_pricing.pricing_volume_tiers_by_brand_role
- Descrição: Desconto máximo permitido baseado na combinação de tier e brand role
- Fallback:
0.000000(sem desconto) - Lógica: Busca o
discount_maxpara a combinação específica detier_code+brand_role - Impacto: Define o desconto base antes de aplicar outros fatores
SELECT COALESCE((
SELECT discount_max
FROM csuite_pricing.pricing_volume_tiers_by_brand_role
WHERE is_active = 1
AND brand_role = v_brand_role
AND tier_code = v_tier_code
LIMIT 1
), 0.000000)
INTO v_discount_role;
6. Cap Street (Limite para Mercado Street)
- Descrição: Limite de desconto para clientes no mercado "street"
- Valor fixo (MVP):
12%(0.120000) - Lógica:
- Se
market_context = 'street': aplicaLEAST(v_discount_role, 0.120000) - Se
market_context != 'street': usav_discount_rolecompleto - Impacto: Protege margens no mercado street, limitando descontos mesmo para tiers altos
IF v_market_context = 'street' THEN
SET v_discount_allowed = LEAST(v_discount_role, 0.120000);
ELSE
SET v_discount_allowed = v_discount_role;
END IF;
7. Curve Factor (Fator da Curva ABC)
Tabela: csuite_pricing.pricing_curve_factors
- Descrição: Fator multiplicador baseado na curva ABC do produto
- Valores possíveis: A, B, C, D, E (definidos na tabela)
- Fallback:
1.000000(sem ajuste) - Lógica: Produtos de curva A (mais importantes) podem ter fatores diferentes de produtos de curva E
- Impacto: Multiplica o desconto permitido, ajustando por importância do produto
SELECT COALESCE((
SELECT factor
FROM csuite_pricing.pricing_curve_factors
WHERE machine_curve = p_machine_curve
AND is_active = 1
LIMIT 1
), 1.000000)
INTO v_curve_factor;
8. Stock Level Factor (Fator do Nível de Estoque)
Tabela: csuite_pricing.pricing_stock_level_factors
- Descrição: Fator multiplicador baseado no nível de estoque do produto
- Valores possíveis:
'low','normal','high'(definidos na tabela) - Fallback:
1.000000(sem ajuste) - Lógica:
- Estoque alto (
high): Aumenta desconto para escoar estoque (fator > 1.0) - Estoque normal (
normal): Sem ajuste (fator = 1.0) - Estoque baixo (
low): Reduz desconto para evitar esgotamento (fator < 1.0) - Impacto: Multiplica o desconto permitido, ajustando por situação de estoque
SELECT COALESCE((
SELECT factor
FROM csuite_pricing.pricing_stock_level_factors
WHERE stock_level = CAST(p_stock_level AS CHAR CHARACTER SET utf8mb4) COLLATE utf8mb4_unicode_ci
AND is_active = 1
LIMIT 1
), 1.000000)
INTO v_stock_level_factor;
9. Order Value Factor (Fator do Valor do Pedido) 🆕
Tabela: csuite_pricing.pricing_order_value_discounts
- Descrição: Fator multiplicador baseado no valor total do pedido
- Valores possíveis: Definidos por faixas de valor (min_order_value, max_order_value)
- Fallback:
1.000000(sem bônus) - Lógica:
- Pedidos maiores recebem bônus no desconto (fator > 1.0)
- Pedidos pequenos: sem bônus (fator = 1.0)
- Impacto: Multiplica o desconto permitido, incentivando pedidos de maior valor
Faixas padrão:
| Valor do Pedido | Fator | Bônus |
|-----------------|-------|-------|
| ≥ R$ 20.000 | 1.20 | +20% |
| ≥ R$ 10.000 | 1.10 | +10% |
| ≥ R$ 5.000 | 1.05 | +5% |
| < R$ 5.000 | 1.00 | 0% |
SELECT COALESCE((
SELECT factor
FROM csuite_pricing.pricing_order_value_discounts
WHERE p_order_value >= min_order_value
AND (max_order_value IS NULL OR p_order_value <= max_order_value)
AND is_active = 1
LIMIT 1
), 1.000000)
INTO v_order_value_factor;
10. Desconto Final
- Fórmula:
v_discount_final = v_discount_allowed * v_curve_factor * v_stock_level_factor * v_order_value_factor - Clamp de segurança: Limitado entre
0%e95% - Se
v_discount_final < 0: define como0 - Se
v_discount_final > 0.95: define como0.95 - Impacto: Desconto final que será aplicado ao PT (considera curva ABC e nível de estoque)
SET v_discount_final = v_discount_allowed * v_curve_factor * v_stock_level_factor * v_order_value_factor;
-- Clamp segurança
IF v_discount_final < 0 THEN SET v_discount_final = 0; END IF;
IF v_discount_final > 0.95 THEN SET v_discount_final = 0.95; END IF;
11. Preço Candidato (Cálculo Normal)
- Fórmula:
v_price_candidate = p_PT * (1 - v_discount_final) - Descrição: Preço calculado após aplicar o desconto final ao PT
- Impacto: Preço base antes de aplicar descontos por quantidade e verificar o piso
SET v_price_candidate = p_PT * (1 - v_discount_final);
Nota: Este é o preço calculado pelo motor SQL. Se houver desconto por quantidade configurado, ele será aplicado após este passo, no código Python.
12. Desconto por Quantidade (Aplicado no Python)
- Fonte: Tabela
pricing_quantity_discounts - Lógica: Se há regra para a quantidade solicitada, substitui ou ajusta o preço
- Tipos:
- Preço Fixo: Substitui o preço calculado pelo preço fixo da faixa
- Desconto Percentual: Aplica desconto percentual sobre o PT
- Impacto: Ajusta o preço final baseado na quantidade comprada
Nota: Este passo é executado no código Python (decision.py), não na stored procedure.
13. Respeitar Piso (Price Floor)
- Lógica:
- Se
v_price_candidate < p_PISO: preço final =p_PISO(status:'FLOOR') - Caso contrário: preço final =
v_price_candidate(status:'OK') - Impacto: Garante que o preço nunca fique abaixo do piso técnico
IF v_price_candidate < p_PISO THEN
SET o_final_price = p_PISO;
SET o_status = 'FLOOR';
ELSE
SET o_final_price = v_price_candidate;
SET o_status = 'OK';
END IF;
Fórmula Final
O cálculo completo pode ser resumido na seguinte fórmula:
// 1. Cálculo Normal (via sp_price_compute_v8)
Desconto_Role = f(tier_code, brand_role) // Tabela pricing_volume_tiers_by_brand_role
Desconto_Allowed = IF market_context = 'street'
THEN MIN(Desconto_Role, 12%)
ELSE Desconto_Role
Curve_Factor = f(machine_curve) // Tabela pricing_curve_factors
Stock_Level_Factor = f(stock_level) // Tabela pricing_stock_level_factors
Order_Value_Factor = f(order_value) // Tabela pricing_order_value_discounts 🆕
Desconto_Final = Desconto_Allowed * Curve_Factor * Stock_Level_Factor * Order_Value_Factor // Limitado entre 0% e 95%
Preço_Candidato_Normal = PT * (1 - Desconto_Final)
// 2. Desconto por Quantidade (se aplicável)
IF existe_regra_quantidade(sku_id, quantity):
Preço_Candidato = f(quantity) // Preço fixo ou desconto percentual da tabela pricing_quantity_discounts
ELSE:
Preço_Candidato = Preço_Candidato_Normal
// 3. Validação de Limites
Preço_Final = MAX(MIN(Preço_Candidato, PT), PISO)
Nota: Descontos por quantidade são aplicados após o cálculo normal, mas antes da validação de limites.
Variáveis de Saída
A stored procedure retorna as seguintes variáveis:
| Variável | Tipo | Descrição |
|---|---|---|
o_status |
VARCHAR(16) | 'OK' ou 'FLOOR' ou 'ERROR' |
o_reason |
VARCHAR(160) | Descrição do cálculo (tier, context, role, curve) |
o_final_price |
DECIMAL(18,6) | Preço final calculado |
o_discount_allowed |
DECIMAL(9,6) | Desconto final aplicado (já com curva) |
o_tier_code |
VARCHAR(8) | Tier do cliente (V1, V2, etc.) |
o_market_context |
VARCHAR(16) | Contexto de mercado (street/non_street) |
o_volume_12m |
DECIMAL(18,2) | Volume 12 meses do cliente |
o_brand_role |
VARCHAR(32) | Papel da marca |
o_curve_factor |
DECIMAL(9,6) | Fator da curva ABC aplicado |
o_stock_level_factor |
DECIMAL(9,6) | Fator do nível de estoque aplicado |
o_order_value_factor |
DECIMAL(9,6) | Fator do valor do pedido aplicado 🆕 |
Tabelas Consultadas
A stored procedure consulta as seguintes tabelas do schema csuite_pricing:
customer_market_context- Contexto de mercado do clientevw_customer_volume_12m- Volume 12 meses (view)pricing_volume_tiers- Definição de tiers por volumebrands- Informações da marca (brand_role)pricing_volume_tiers_by_brand_role- Descontos por tier + brand rolepricing_curve_factors- Fatores por curva ABCpricing_stock_level_factors- Fatores por nível de estoquepricing_order_value_discounts- Fatores por valor do pedido 🆕pricing_promotions- Promoções esporádicas (consultada antes do cálculo normal)pricing_quantity_discounts- Descontos por quantidade (consultada após cálculo normal)pricing_payment_term_discounts- Descontos por forma de pagamento (MACHINES, após cálculo normal) 🆕
Exemplo de Cálculo
Cenário:
- PT = R$ 3.264,00
- PISO = R$ 2.549,18
- Cliente: volume_12m = 97.998, market_context = 'non_street'
- Tier: V2 (baseado no volume)
- Brand Role: 'secondary_target'
- Machine Curve: 'A'
- Stock Level: 'normal'
- Order Value: R$ 32.640,00 (10 unidades)
- Desconto V2 + secondary_target: 8.4%
- Curve Factor A: 1.0
- Stock Level Factor (normal): 1.0
- Order Value Factor (≥ R$ 20k): 1.2
- Installments: 2x (MACHINES) → Payment Term Discount: 3% 🆕
Cálculo:
1. v_discount_role = 0.084 (8.4%)
2. v_discount_allowed = 0.084 (não é street, sem cap)
3. v_curve_factor = 1.0
4. v_stock_level_factor = 1.0
5. v_order_value_factor = 1.2 (pedido ≥ R$ 20k → bônus +20%)
6. v_discount_final = 0.084 * 1.0 * 1.0 * 1.2 = 0.1008 (10.08%)
7. v_price_candidate = 3264 * (1 - 0.1008) = R$ 2.935,39 (após desconto normal)
8. v_payment_term_discount = 0.03 (3% para 2x em MACHINES) 🆕
9. v_price_after_payment = 2935.39 * (1 - 0.03) = R$ 2.847,33 (após desconto de pagamento) 🆕
10. o_final_price = MAX(2847.33, 2549.18) = R$ 2.847,33 ✅
*Nota: O order_value_factor aumenta o limite de desconto permitido, mas o preço final ainda respeita os limites do corredor. O desconto por forma de pagamento é aplicado APÓS o cálculo normal, apenas para produtos MACHINES com parcelas < 5.
API e Payloads
Endpoint Principal
POST /run
Payload (Formato CSuite Pricing)
{
"org_id": 1,
"brand_id": 1,
"customer_id": 123,
"sku_id": 456,
"sku_qty": 5,
"order_value": 50000.00,
"payment_term": "standard", // standard | short | extended
"installments": null, // número de parcelas (0=à vista, 1=1x, 2=2x, etc.) - apenas para MACHINES 🆕
"stock_level": "normal", // normal | high
"machine_curve": "A" // A | B | C
}
Payload (Formato Legado - Compatibilidade)
{
"org_id": 0,
"sku": {
"id": "SKU-123",
"cost": 8000,
"margin": 20
},
"inventory": {
"days_on_hand": 45,
"turn_rate": 0.5
},
"market": {
"elasticity": -1.2,
"competitor_price": 10000
},
"policy": {
"floor_margin": 18,
"max_discount": 5
}
}
Resposta (Sucesso)
{
"decision": {
"decision_type": "PRICING.COMPUTED",
"confidence": 0.9,
"final_price": 9500.00,
"discount_allowed": 0.05,
"applied_mode": "CORRIDOR_PRICE",
"elasticity": "medium",
"screen_price_pt": 10000.00,
"floor_price": 8000.00,
"proposed_actions": [
{
"type": "UPDATE_PRICE",
"new_price": 9500.00,
"discount_pct": 5.0,
"elasticity": "medium"
}
]
},
"context": {
"org_id": 1,
"brand_id": 1,
"customer_id": 123,
"sku_id": 456,
"is_anchor_customer": false,
"price_screen_pt": 10000.00,
"price_floor": 8000.00,
"brand_role": "secondary_target"
},
"execution": {
"status": "EXECUTED",
"actions": [...]
},
"decision_log_id": 2001,
"calc_id": 1001
}
Resposta (Cliente Âncora)
{
"decision": {
"decision_type": "PRICING.ANCHOR",
"confidence": 1.0,
"final_price": 9500.00,
"applied_mode": "ANCHOR_TABLE",
"elasticity": "low",
"discount_pct": 0.05,
"reason": "Preço Âncora derivado da política vigente"
},
"context": {
"is_anchor_customer": true,
...
},
"calc_id": 1001
}
Resposta (Incidente)
{
"decision": {
"decision_type": "PRICING.INCIDENT",
"confidence": 0.0,
"reason": "PT_LEQ_PISO",
"proposed_actions": [
{
"type": "BLOCK_PRICE",
"reason": "PT_LEQ_PISO"
}
]
},
"incident_id": 5001
}
Como Usar a Resposta do Agent
A resposta do endpoint /run contém todas as informações necessárias para aplicar o preço calculado e tomar decisões baseadas no resultado. Esta seção explica como interpretar e usar cada parte da resposta.
Estrutura da Resposta
A resposta HTTP tem o seguinte formato:
{
"status": "success",
"agent": "CSuite.Pricing.Agent",
"result": {
"decision": { ... },
"context": { ... },
"execution": { ... },
"decision_log_id": 2001,
"run_id": 1001,
"calc_id": 1001
}
}
1. Verificar Status da Resposta
Primeiro passo: Verificar se a requisição foi bem-sucedida:
response = requests.post("https://csuite.internut.com.br/pricing/run", json=payload)
data = response.json()
if data["status"] != "success":
# Tratar erro
raise Exception(f"Agent retornou status: {data['status']}")
result = data["result"]
2. Interpretar o Tipo de Decisão
Segundo passo: Verificar o decision_type para saber como proceder:
decision = result["decision"]
decision_type = decision["decision_type"]
if decision_type == "PRICING.ANCHOR":
# Cliente Âncora: usar preço estável
final_price = decision["final_price"]
print(f"Preço Âncora: R$ {final_price:.2f}")
elif decision_type == "PRICING.COMPUTED":
# Preço calculado normalmente
final_price = decision["final_price"]
discount = decision.get("discount_allowed", 0) * 100
print(f"Preço Final: R$ {final_price:.2f} (Desconto: {discount:.2f}%)")
elif decision_type == "PRICING.INCIDENT":
# Incidente: PT <= Piso - NÃO APLICAR PREÇO
print(f"ERRO: {decision['reason']}")
print("Preço não pode ser aplicado. Verificar configuração de PT e Piso.")
# NÃO atualizar preço no sistema
elif decision_type == "PRICING.BLOCK":
# Bloqueado por validação de política
print(f"Preço bloqueado: {decision.get('reason', 'Validação falhou')}")
# Requer aprovação manual
3. Extrair o Preço Final
Terceiro passo: Obter o preço final para aplicar:
def get_final_price(result):
"""Extrai o preço final da resposta do agent"""
decision = result["decision"]
decision_type = decision["decision_type"]
# Apenas ANCHOR e COMPUTED têm preço válido
if decision_type in ["PRICING.ANCHOR", "PRICING.COMPUTED"]:
return decision.get("final_price")
# INCIDENT e BLOCK não têm preço aplicável
return None
final_price = get_final_price(result)
if final_price:
# Aplicar preço no sistema
update_product_price(sku_id, final_price)
print(f"Preço atualizado: R$ {final_price:.2f}")
else:
print("Preço não pode ser aplicado")
4. Verificar Metadados do Cálculo
Quarto passo: Usar informações adicionais para auditoria e análise:
decision = result["decision"]
context = result["context"]
# Informações de precificação
print(f"Preço de Tela (PT): R$ {decision.get('screen_price_pt', 0):.2f}")
print(f"Piso Técnico: R$ {decision.get('floor_price', 0):.2f}")
print(f"Desconto Aplicado: {decision.get('discount_allowed', 0) * 100:.2f}%")
print(f"Modo Aplicado: {decision.get('applied_mode', 'N/A')}")
print(f"Elasticidade: {decision.get('elasticity', 'N/A')}")
# Confiança da decisão
confidence = decision.get("confidence", 0)
if confidence >= 0.8:
print("✅ Alta confiança - pode aplicar automaticamente")
elif confidence >= 0.5:
print("⚠️ Confiança média - revisar antes de aplicar")
else:
print("❌ Baixa confiança - requer aprovação manual")
# IDs para auditoria
print(f"Decision Log ID: {result.get('decision_log_id')}")
print(f"Calculation ID: {result.get('calc_id')}")
print(f"Run ID: {result.get('run_id')}")
5. Executar Ações Propostas
Quinto passo: Executar as ações sugeridas pelo agent:
execution = result["execution"]
actions = execution.get("actions", [])
for action in actions:
action_type = action["type"]
if action_type == "UPDATE_PRICE":
# Atualizar preço no sistema
new_price = action["new_price"]
update_product_price(sku_id, new_price)
print(f"✅ Preço atualizado: R$ {new_price:.2f}")
elif action_type == "APPLY_ANCHOR_PRICE":
# Aplicar preço âncora
price = action["price"]
apply_anchor_price(sku_id, price)
print(f"✅ Preço âncora aplicado: R$ {price:.2f}")
elif action_type == "BLOCK_PRICE":
# Bloquear preço - não aplicar
reason = action.get("reason", "Política violada")
block_price(sku_id, reason)
print(f"🚫 Preço bloqueado: {reason}")
elif action_type == "ACTIVATE_PROMOTION":
# Ativar promoção
promotion_id = action.get("promotion_id")
activate_promotion(promotion_id)
print(f"🎯 Promoção ativada: {promotion_id}")
6. Tratamento de Erros
Sexto passo: Tratar erros e casos especiais:
try:
response = requests.post(url, json=payload, timeout=30)
response.raise_for_status()
data = response.json()
if data["status"] != "success":
raise Exception(f"Agent retornou erro: {data.get('detail', 'Erro desconhecido')}")
result = data["result"]
decision = result["decision"]
# Verificar se há incidente
if decision["decision_type"] == "PRICING.INCIDENT":
incident_id = result.get("incident_id")
log_incident(incident_id, decision["reason"])
notify_administrators(f"Incidente de precificação: {decision['reason']}")
return None # Não aplicar preço
# Verificar confiança
if decision.get("confidence", 0) < 0.5:
# Requer aprovação manual
send_for_approval(result)
return None
# Aplicar preço
return decision["final_price"]
except requests.exceptions.Timeout:
print("Timeout ao chamar Pricing Agent")
# Usar preço padrão ou cache
except requests.exceptions.HTTPError as e:
print(f"Erro HTTP: {e}")
# Tratar erro específico
except Exception as e:
print(f"Erro inesperado: {e}")
# Log e notificação
Exemplo Completo de Integração
import requests
from typing import Optional, Dict, Any
def calculate_price(
org_id: int,
brand_id: int,
customer_id: int,
sku_id: int,
sku_qty: int = 1,
order_value: float = 0,
machine_curve: str = "B"
) -> Optional[Dict[str, Any]]:
"""
Calcula preço usando Pricing Agent
Returns:
Dict com preço e metadados, ou None se houver erro
"""
url = "https://csuite.internut.com.br/pricing/run"
payload = {
"org_id": org_id,
"brand_id": brand_id,
"customer_id": customer_id,
"sku_id": sku_id,
"sku_qty": sku_qty,
"order_value": order_value,
"machine_curve": machine_curve,
"payment_term": "standard",
"stock_level": "normal"
}
try:
response = requests.post(url, json=payload, timeout=30)
response.raise_for_status()
data = response.json()
if data["status"] != "success":
print(f"Erro: {data.get('detail', 'Erro desconhecido')}")
return None
result = data["result"]
decision = result["decision"]
decision_type = decision["decision_type"]
# Verificar tipo de decisão
if decision_type == "PRICING.INCIDENT":
print(f"⚠️ Incidente: {decision['reason']}")
return {
"status": "incident",
"reason": decision["reason"],
"incident_id": result.get("incident_id")
}
if decision_type == "PRICING.BLOCK":
print(f"🚫 Bloqueado: {decision.get('reason', 'Validação falhou')}")
return {
"status": "blocked",
"reason": decision.get("reason"),
"requires_approval": True
}
# Extrair preço final
final_price = decision.get("final_price")
if not final_price:
print("❌ Preço final não disponível")
return None
# Retornar resultado completo
return {
"status": "success",
"final_price": final_price,
"screen_price_pt": decision.get("screen_price_pt"),
"floor_price": decision.get("floor_price"),
"discount_pct": decision.get("discount_allowed", 0) * 100,
"applied_mode": decision.get("applied_mode"),
"elasticity": decision.get("elasticity"),
"confidence": decision.get("confidence", 0),
"decision_type": decision_type,
"calc_id": result.get("calc_id"),
"decision_log_id": result.get("decision_log_id"),
"run_id": result.get("run_id")
}
except requests.exceptions.Timeout:
print("⏱️ Timeout ao chamar Pricing Agent")
return None
except requests.exceptions.HTTPError as e:
print(f"❌ Erro HTTP {e.response.status_code}: {e}")
return None
except Exception as e:
print(f"❌ Erro inesperado: {e}")
return None
# Uso prático
def apply_pricing_to_order(order_id: int, items: list):
"""Aplica precificação a um pedido completo"""
for item in items:
price_result = calculate_price(
org_id=item["org_id"],
brand_id=item["brand_id"],
customer_id=item["customer_id"],
sku_id=item["sku_id"],
sku_qty=item["quantity"],
order_value=item["order_total"],
machine_curve=item.get("curve", "B")
)
if price_result and price_result["status"] == "success":
# Aplicar preço
item["final_price"] = price_result["final_price"]
item["discount_pct"] = price_result["discount_pct"]
item["calc_id"] = price_result["calc_id"]
# Atualizar no banco
update_order_item_price(
order_id=order_id,
sku_id=item["sku_id"],
price=price_result["final_price"],
calc_id=price_result["calc_id"]
)
else:
# Tratar erro ou incidente
item["pricing_status"] = price_result.get("status", "error")
item["pricing_error"] = price_result.get("reason", "Erro desconhecido")
if price_result and price_result.get("requires_approval"):
send_for_manual_review(order_id, item)
Campos Importantes da Resposta
| Campo | Localização | Descrição | Uso |
|---|---|---|---|
final_price |
result.decision.final_price |
Preço final calculado | Aplicar no sistema |
screen_price_pt |
result.decision.screen_price_pt |
Preço de Tela (referência) | Exibir para usuário |
floor_price |
result.decision.floor_price |
Piso Técnico (mínimo) | Validação |
discount_allowed |
result.decision.discount_allowed |
Desconto aplicado (0-1) | Exibir desconto % |
applied_mode |
result.decision.applied_mode |
Modo aplicado (ANCHOR_TABLE/CORRIDOR_PRICE) | Auditoria |
elasticity |
result.decision.elasticity |
Nível de elasticidade | Análise |
confidence |
result.decision.confidence |
Confiança (0-1) | Decisão automática vs manual |
decision_type |
result.decision.decision_type |
Tipo de decisão | Roteamento |
calc_id |
result.calc_id |
ID do cálculo | Auditoria/rastreabilidade |
decision_log_id |
result.decision_log_id |
ID do log de decisão | Policy Engine |
run_id |
result.run_id |
ID da execução | Agent Registry |
Boas Práticas
- Sempre verificar
decision_typeantes de aplicar preço - Usar
calc_idpara rastreabilidade e auditoria - Verificar
confidencepara decidir se requer aprovação manual - Tratar incidentes adequadamente (não aplicar preço)
- Logar todas as decisões usando
decision_log_iderun_id - Implementar fallback para casos de timeout ou erro
- Validar preço final está dentro do corredor (PT >= preço >= Piso)
Módulos e Responsabilidades
main.py
- FastAPI application
- Endpoint
/runpara execução do agente - Tratamento de erros e validação de payload
decision.py
- Função principal:
run_decision(payload, db) - Orquestra todo o fluxo de decisão:
- Verifica Cliente Âncora
- Verifica Promoção Ativa
- Chama motor SQL para clientes normais
- Aplica Desconto por Quantidade (se aplicável)
- Valida limites (PT e Piso)
- Valida decisão contra políticas
- Registra auditoria
- Instrumenta Policy Engine
- Executa ações
context.py
- Função principal:
build_context(payload) - Enriquece contexto com dados do
csuite_pricing: price_screen_pt: Preço de Telaprice_floor: Piso Técnicobrand_role: Tipo de marca (principal/secundária)customer_type: Tipo de clienteis_anchor_customer: Flag de Cliente Âncoradecision_corridor: Espaço entre PT e Piso
engine.py
- Função principal:
compute_price_v8(...) - Integra com stored procedure
sp_price_compute_v8 - Fallback automático para
v7sev8não disponível - Retorna resultado estruturado com status, preço, elasticidade, etc.
anchor.py
- Funções:
is_anchor_customer(org_id, customer_id, brand_id): Verifica se é Cliente Âncoraget_anchor_price_for_sku(...): Obtém Preço Âncora do SKU- Consulta tabela
pricing_anchor_prices(read-only, derivada)
repository.py
- Funções de acesso a dados:
get_price_screen(org_id, sku_id): Obtém PTget_price_floor(org_id, sku_id): Obtém Pisoget_brand_role(org_id, brand_id): Obtém tipo de marcaget_customer_brand_profile(...): Obtém perfil do clienteget_anchor_price_by_sku_and_version(...): Obtém preço âncoraget_active_promotion(sku_id, brand_id): Obtém promoção ativaget_quantity_discount(sku_id, quantity, brand_id): Obtém desconto por quantidade
database.py
- Função principal:
get_pricing_db_session() - Gerencia conexões com schema
csuite_pricing - Usa
common_db_poolpara connection pooling
policies.py
- Função principal:
validate_decision(decision, context) - Valida decisão contra políticas:
- PT > Piso (obrigatório)
- Cliente Âncora: apenas ANCHOR_TABLE, elasticidade NONE/LOW
- Marca Principal: elasticidade máxima LOW
- Preço final dentro do corredor (PT >= final_price >= Piso)
- Função:
get_autonomy_level(context): Determina nível de autonomia (L0-L4)
audit.py
- Funções:
log_price_calculation(...): Registra emprice_calculationslog_price_incident(...): Registra emprice_incidents- Auditoria completa de todas as decisões
execution.py
- Função principal:
execute_actions(decision, context) - Executa ações propostas:
UPDATE_PRICE: Atualiza preçoAPPLY_ANCHOR_PRICE: Aplica preço âncoraBLOCK_PRICE: Bloqueia preço (incidentes)ACTIVATE_PROMOTION: Ativa promoçãoBLOCK_DISCOUNT: Bloqueia desconto
Fluxo de Decisão
O Pricing Agent segue uma ordem de prioridade específica ao calcular o preço:
Ordem de Prioridade
- Cliente Âncora (maior prioridade)
- Se o cliente é classificado como "âncora", usa preço da tabela
pricing_anchor_prices -
EXIT - não verifica mais nada
-
Promoção Ativa (segunda prioridade)
- Se o produto está em promoção (tabela
pricing_promotions), usa preço de promoção -
EXIT - não calcula preço normal
-
Cálculo Base (motor SQL) (terceira prioridade)
- Usa motor SQL (
sp_price_compute_v8) para obter PT, Piso, contexto epayment_term_discount -
O desconto normal do motor pode ser sobreposto por regras especiais
-
Combo/Bundle ou Desconto por Quantidade
- Se houver regra de combo/quantidade (SKU ou família), recalcula o preço a partir do PT
-
Aplica o desconto do combo/quantidade e apenas o desconto de prazo de pagamento
-
Último Preço Pago (LPP)
-
Pode limitar aumento de preço conforme histórico do cliente
-
Produto em Lançamento (teto) 🆕
-
Se SKU está em lançamento ativo, limita preço ao
launch_price -
Proteção Regional
-
Ajusta preço conforme regra regional (quando ativa)
-
Validações finais de limites
- Garante que o preço final está entre PT e Piso
- Ajusta se necessário
Fluxo Detalhado
Payload
↓
Context Builder (enriquece com dados do schema)
↓
┌─────────────────────────────────────┐
│ 1. Cliente Âncora? │
│ → SIM: Retorna Preço Âncora │
│ → NÃO: Continua │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 2. Promoção Ativa? │
│ → SIM: Retorna Preço Promoção │
│ → NÃO: Continua │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 3. Cálculo Normal (sp_price_compute_v8) │
│ - Market Context │
│ - Volume 12m → Tier │
│ - Brand Role │
│ - Desconto Base (Tier + Role) │
│ - Cap Street (se aplicável) │
│ - Curve Factor │
│ - Stock Level Factor │
│ - Order Value Factor 🆕 │
│ - Preço Candidato │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 4. Desconto por Quantidade? │
│ → SIM: Ajusta preço │
│ → NÃO: Mantém preço calculado │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 5. Validar Limites │
│ - Se < Piso: Ajusta para Piso │
│ - Se > PT: Ajusta para PT │
└─────────────────────────────────────┘
↓
Preço Final
Fluxo de Decisão (Detalhado)
Fluxo Principal
1. Recebe Payload
↓
2. build_context() → Enriquece com dados do schema
↓
3. Verifica is_anchor_customer?
├─ SIM → get_anchor_price_for_sku() → Preço Âncora (EXIT)
└─ NÃO → Continua
↓
4. Verifica Promoção Ativa?
├─ SIM → get_active_promotion() → Preço Promoção (EXIT)
└─ NÃO → Continua
↓
5. compute_price_v8() → Chama stored procedure (cálculo normal)
↓
6. Verifica engine_result["status"]
├─ "INCIDENT" → log_price_incident() → PRICING.INCIDENT
└─ "OK" → Continua
↓
7. Aplica Desconto por Quantidade?
├─ SIM → get_quantity_discount() → Ajusta preço
└─ NÃO → Mantém preço calculado
↓
8. Valida limites (PT e Piso) → Ajusta se necessário
↓
9. validate_decision() → Valida contra políticas
├─ FALHA → PRICING.BLOCK
└─ SUCESSO → Continua
↓
10. log_price_calculation() → Registra auditoria
↓
11. _instrument_decision() → Instrumenta Policy Engine
↓
12. execute_actions() → Executa ações propostas
↓
13. _register_agent_run() → Registra no Agent Registry
↓
14. Retorna resultado completo
Fluxo Cliente Âncora
1. build_context() → is_anchor_customer = True
↓
2. get_anchor_price_for_sku() → Consulta pricing_anchor_prices
↓
3. Retorna preço estável (não calcula desconto)
↓
4. log_price_calculation() → Registra como ANCHOR_TABLE
↓
5. execute_actions() → APPLY_ANCHOR_PRICE
↓
6. Retorna PRICING.ANCHOR
Fluxo Incidente
1. compute_price_v8() → Retorna status="INCIDENT"
↓
2. log_price_incident() → Registra em price_incidents
↓
3. Retorna PRICING.INCIDENT (não valida, não calcula)
Políticas e Validações
Regras Absolutas
- PT > Piso: Obrigatório. Se PT ≤ Piso → gera
PRICING.INCIDENT - Cliente Âncora:
- Apenas
ANCHOR_TABLEmode - Elasticidade:
NONEouLOWapenas - Não recebe desconto elástico
- Marca Principal:
- Elasticidade máxima:
LOW - Não pode escalar elasticidade
- Corredor de Decisão:
- Preço final:
PT >= final_price >= Piso - Se fora do corredor →
PRICING.BLOCK
Níveis de Autonomia
- L0: Sem autonomia (requer aprovação)
- L1: Autonomia baixa (elasticidade NONE/LOW)
- L2: Autonomia média (elasticidade MEDIUM)
- L3: Autonomia alta (elasticidade HIGH)
- L4: Autonomia máxima (elasticidade MAX)
Testes
Estrutura de Testes
tests/agents/pricing/
├── __init__.py
├── conftest.py # Fixtures compartilhadas
├── test_repository.py # Testes de repository
├── test_anchor.py # Testes de Cliente Âncora
├── test_policies.py # Testes de validação
├── test_context.py # Testes de contexto
├── test_decision.py # Testes de decisão
├── test_integration.py # Testes de integração
└── README.md # Documentação dos testes
Executar Testes
# Todos os testes
pytest tests/agents/pricing/ -v
# Apenas unitários
pytest tests/agents/pricing/ -v -m unit
# Apenas integração
pytest tests/agents/pricing/ -v -m integration
# Teste específico
pytest tests/agents/pricing/test_decision.py::TestDecision::test_run_decision_anchor_customer -v
Cobertura
- Repository: ~90%
- Anchor: ~95%
- Policies: ~85%
- Context: ~80%
- Decision: ~75%
- Integration: ~70%
Total: 38 testes, todos passando ✅
Exemplos de Uso
Exemplo 1: Cliente Normal
import requests
payload = {
"org_id": 1,
"brand_id": 1,
"customer_id": 123,
"sku_id": 456,
"sku_qty": 5,
"order_value": 50000.00,
"payment_term": "standard",
"installments": null, // número de parcelas (0=à vista, 1=1x, etc.) - apenas para MACHINES
"stock_level": "normal",
"machine_curve": "A"
}
response = requests.post("http://localhost:8000/run", json=payload)
result = response.json()
print(f"Preço final: {result['decision']['final_price']}")
print(f"Elasticidade: {result['decision']['elasticity']}")
print(f"Modo: {result['decision']['applied_mode']}")
Exemplo 2: Cliente Âncora
payload = {
"org_id": 1,
"brand_id": 1,
"customer_id": 999, # Cliente Âncora
"sku_id": 456
}
response = requests.post("http://localhost:8000/run", json=payload)
result = response.json()
assert result['decision']['decision_type'] == "PRICING.ANCHOR"
assert result['decision']['applied_mode'] == "ANCHOR_TABLE"
print(f"Preço Âncora: {result['decision']['final_price']}")
Exemplo 3: Incidente (PT ≤ Piso)
# Quando PT <= Piso, gera incidente
payload = {
"org_id": 1,
"brand_id": 1,
"sku_id": 789 # SKU com PT <= Piso
}
response = requests.post("http://localhost:8000/run", json=payload)
result = response.json()
assert result['decision']['decision_type'] == "PRICING.INCIDENT"
assert result['decision']['reason'] == "PT_LEQ_PISO"
print(f"Incidente registrado: {result.get('incident_id')}")
Exemplo 4: Uso Direto (Python)
from agents.pricing.decision import run_decision
import asyncio
async def main():
payload = {
"org_id": 1,
"brand_id": 1,
"customer_id": 123,
"sku_id": 456,
"sku_qty": 5,
"order_value": 50000.00
}
result = await run_decision(payload)
print(result)
asyncio.run(main())
Troubleshooting
Erro: "Access denied for user 'core'"
Causa: Credenciais de banco não configuradas ou incorretas.
Solução: Verificar variáveis de ambiente:
- DB_HOST
- DB_USER
- DB_PASSWORD
- DB_NAME (deve ser o schema csuite_pricing)
Erro: "sp_price_compute_v8 não disponível"
Causa: Stored procedure não existe no banco.
Solução:
- O agente faz fallback automático para v7
- Verificar se pelo menos sp_price_compute_v7 existe
- Criar stored procedure se necessário
Erro: "PRICING.BLOCK" em vez de "PRICING.COMPUTED"
Causa: Validação de políticas falhou.
Possíveis motivos:
- Preço final fora do corredor (PT >= preço >= Piso)
- Cliente Âncora com modo incorreto
- Marca Principal com elasticidade > LOW
Solução: Verificar logs para identificar qual validação falhou.
Erro: "Cliente Âncora não encontrado"
Causa: Tabela pricing_anchor_prices vazia ou SKU não tem preço âncora.
Solução:
- Verificar se preço âncora foi calculado para o SKU
- Executar job de cálculo de preços âncora
- Verificar policy_version usado na consulta
Performance: Consultas lentas
Causa: Falta de índices ou connection pooling inadequado.
Solução:
- Verificar índices nas tabelas do csuite_pricing
- Ajustar tamanho do connection pool
- Usar db parameter para reutilizar sessão
🚀 Produtos em Lançamento (12/01/2026)
O que é?
Sistema para gerenciar produtos em lançamento com preços promocionais temporários. Durante o período de lançamento, o motor de pricing:
- Ignora a regra de LPP (Last Paid Price) - Cliente pode pagar o preço de lançamento mesmo que seja maior que +5% do último preço
- Usa o preço de lançamento como teto - O preço final nunca excede o
launch_pricecadastrado - Suporta período de transição - Após o lançamento, há um período onde o LPP ainda é ignorado (
ignore_lpp_until)
Por que importa?
- Lançamentos: Produtos novos com preço promocional inicial não devem ser limitados pelo histórico
- Promoções especiais: Permite preços agressivos temporários sem afetar o LPP
- Transição suave: O período de transição permite que o preço regularize gradualmente
Tabela pricing_launch_products
| Campo | Tipo | Descrição |
|---|---|---|
sku_id |
BIGINT | ID do SKU |
brand_id |
BIGINT | ID da marca |
product_name |
VARCHAR | Nome do produto |
product_model |
VARCHAR | Modelo do produto |
launch_price |
DECIMAL | Preço de lançamento (teto durante o período) |
regular_price |
DECIMAL | Preço regular após lançamento |
price_pt |
DECIMAL | PT no momento do cadastro |
price_floor |
DECIMAL | Piso no momento do cadastro |
launch_start |
DATE | Início do lançamento |
launch_end |
DATE | Fim do período de lançamento |
ignore_lpp_until |
DATE | Ignorar LPP até esta data (transição) |
max_discount_pct |
DECIMAL | Desconto máximo adicional permitido |
notes |
VARCHAR | Observações |
is_active |
BOOLEAN | Flag de ativo |
Status do Lançamento
| Status | Condição | Comportamento |
|---|---|---|
SCHEDULED |
CURDATE() < launch_start |
Aguardando início |
ACTIVE |
CURDATE() BETWEEN launch_start AND launch_end |
LPP ignorado + preço teto = launch_price |
TRANSITION |
CURDATE() > launch_end AND CURDATE() <= ignore_lpp_until |
LPP ignorado, preço normal |
ENDED |
CURDATE() > ignore_lpp_until |
Comportamento normal |
Resposta da API
Quando um produto está em lançamento, a resposta inclui:
"launch_product": {
"is_launch": true,
"status": "ACTIVE",
"launch_price": 3200.00,
"regular_price": 3768.00,
"lpp_ignored": true,
"launch_price_applied": true,
"launch_end": "2026-01-31",
"ignore_lpp_until": "2026-03-12"
}
Interface de Administração
Acessível em /pricing/admin → 🚀 Produtos em Lançamento:
- ✅ Listagem com filtros por status (Ativo, Agendado, Transição, Encerrado)
- ✅ Busca de produtos por modelo/marca com autocomplete
- ✅ Criação com preenchimento automático de SKU, marca, PT e piso
- ✅ Edição e exclusão (soft delete)
- ✅ Visualização de dias restantes
Exemplo de Fluxo
1. Cadastrar produto em lançamento:
- SKU: 1981269
- launch_price: R$ 3.200,00
- regular_price: R$ 3.768,00
- launch_end: 2026-01-31
- ignore_lpp_until: 2026-03-12
2. Cliente solicita cotação durante lançamento:
- Motor calcula: R$ 3.372,36 (baseado em tier, curva, etc.)
- Motor detecta: SKU em lançamento ativo
- Motor limita: R$ 3.200,00 (launch_price)
- Resposta: final_price = R$ 3.200,00, launch_price_applied = true
3. Após launch_end, até ignore_lpp_until (transição):
- LPP ainda é ignorado
- Preço calculado normalmente (sem teto do launch_price)
4. Após ignore_lpp_until:
- Comportamento normal
- LPP aplicado normalmente
Referências
Changelog
v1.3.0 (2026-01-12)
- ✅ Produtos em Lançamento: Sistema completo para gerenciar lançamentos
- Nova tabela
pricing_launch_products - Motor limita preço ao
launch_pricedurante período ativo - Bypass de LPP durante lançamento e transição
- Campo
launch_productna resposta da API - Interface de admin com busca de produtos por modelo/marca
- ✅ Admin Melhorado: Tela de boas-vindas com cards de atalho
- ✅ Menu Lateral: Largura aumentada e textos sem quebra
v1.2.0 (2026-01-10)
- ✅ Last Paid Price: Limite de aumento baseado no último preço pago
- Clientes V4: máximo +3% de aumento
- Clientes V3: máximo +4% de aumento
- Demais: máximo +5% de aumento
- ✅ Nova tabela
pricing_last_price_rulespara configuração de regras por tier - ✅ Detecção automática de preços promocionais (ignora promoções antigas)
- ✅ Campo
last_price_infona resposta com histórico do cliente - ✅ Step na explicação mostrando análise do último preço
v1.1.0 (2026-01-10)
- ✅ Order Value Factor: Novo fator de desconto baseado no valor do pedido
- Pedidos ≥ R$ 20.000: bônus de +20% no desconto
- Pedidos ≥ R$ 10.000: bônus de +10% no desconto
- Pedidos ≥ R$ 5.000: bônus de +5% no desconto
- ✅ Nova tabela
pricing_order_value_discountspara configuração de faixas - ✅ Stored procedure
sp_price_compute_v8atualizada com novo parâmetroo_order_value_factor - ✅ Explicação do fator exibida no campo
explanationda resposta
v1.0.0 (2025-01-03)
- ✅ Integração completa com
csuite_pricingschema - ✅ Suporte a Cliente Âncora
- ✅ Motor SQL (
sp_price_compute_v8) - ✅ Validação de políticas
- ✅ Auditoria completa
- ✅ 38 testes unitários e de integração
- ✅ Compatibilidade com formato legado
Última atualização: 2026-01-12
Versão do Agente: 1.3.0
Status: ✅ Produção