Ir para o conteúdo

Handlers

src.graph.handlers

Handlers de processamento do grafo de atendimento.

Cada handler encapsula a logica de um tipo especifico de processamento no fluxo de atendimento.

Example
from src.graph.handlers import (
    processar_pedido,
    processar_troca,
    processar_remocao,
    processar_saudacao,
    processar_carrinho,
    processar_confirmacao,
    processar_cancelamento,
)

ResultadoClarificacao(tipo: str, resposta: str, modo: MODOS, carrinho: list[dict] = list(), fila: list[dict] = list(), tentativas: int = 0) dataclass

Resultado do processamento de clarificação.

Attributes:

Name Type Description
tipo str

Tipo do resultado ('sucesso', 'invalida', 'erro').

resposta str

Texto da resposta para o usuário.

etapa str

Próxima etapa do fluxo.

carrinho list[dict]

Carrinho atualizado.

fila list[dict]

Fila de clarificação atualizada.

tentativas int

Contador de tentativas atual.

to_dict() -> RetornoNode

Converte para dicionário compatível com LangGraph State.

Source code in src/graph/handlers/clarificacao.py
def to_dict(self) -> RetornoNode:
    """Converte para dicionário compatível com LangGraph State."""
    return {
        'resposta': self.resposta,
        'modo': self.modo,
        'carrinho': self.carrinho,
        'fila_clarificacao': self.fila,
        'tentativas_clarificacao': self.tentativas,
    }

ResultadoPedir(carrinho: list[dict] = list(), fila: list[dict] = list(), resposta: str = '') dataclass

Resultado do processamento de pedido.

Attributes:

Name Type Description
carrinho list[dict]

Carrinho atualizado.

fila list[dict]

Itens pendentes de clarificacao.

resposta str

Texto formatado para o usuario.

to_dict() -> RetornoNode

Converte para dicionario compativel com LangGraph State.

Source code in src/graph/handlers/pedido_handler.py
def to_dict(self) -> RetornoNode:
    """Converte para dicionario compativel com LangGraph State."""
    return {
        'carrinho': self.carrinho,
        'fila_clarificacao': self.fila,
        'resposta': self.resposta,
        'modo': 'clarificando' if self.fila else 'coletando',
    }

ResultadoRemover(carrinho: list[dict] = list(), resposta: str = '', modo: MODOS = 'ocioso') dataclass

Resultado do processamento de remocao.

Attributes:

Name Type Description
carrinho list[dict]

Carrinho atualizado apos remocao.

resposta str

Texto formatado para o usuario.

etapa str

Proxima etapa do fluxo.

to_dict() -> RetornoNode

Converte para dicionario compativel com LangGraph State.

Source code in src/graph/handlers/remocao_handler.py
def to_dict(self) -> RetornoNode:
    """Converte para dicionario compativel com LangGraph State."""
    return {
        'carrinho': self.carrinho,
        'resposta': self.resposta,
        'modo': self.modo,
    }

ResultadoTrocar(carrinho: list[dict] = list(), resposta: str = '', modo: MODOS = 'coletando') dataclass

Resultado do processamento de troca.

Attributes:

Name Type Description
carrinho list[dict]

Carrinho atualizado apos troca.

resposta str

Texto formatado para o usuario.

etapa str

Proxima etapa do fluxo.

to_dict() -> RetornoNode

Converte para dicionario compativel com LangGraph State.

Source code in src/graph/handlers/troca_handler.py
def to_dict(self) -> RetornoNode:
    """Converte para dicionario compativel com LangGraph State."""
    return {
        'carrinho': self.carrinho,
        'resposta': self.resposta,
        'modo': self.modo,
    }

processar_cancelamento(carrinho_dicts: list[dict]) -> RetornoNode

Processa cancelamento do pedido.

Parameters:

Name Type Description Default
carrinho_dicts list[dict]

Lista de dicts do carrinho no State.

required

Returns:

Type Description
RetornoNode

Dicionario com resposta, etapa e carrinho atualizados.

Source code in src/graph/handlers/cancelar_handler.py
def processar_cancelamento(carrinho_dicts: list[dict]) -> RetornoNode:
    """Processa cancelamento do pedido.

    Args:
        carrinho_dicts: Lista de dicts do carrinho no State.

    Returns:
        Dicionario com ``resposta``, ``etapa`` e ``carrinho`` atualizados.
    """
    if not carrinho_dicts:
        return {'resposta': 'Não há pedido para cancelar.', 'modo': 'ocioso'}

    carrinho = Carrinho.from_state_dicts(carrinho_dicts)
    total = carrinho.total_reais()

    return {
        'resposta': f'Pedido cancelado. Total descartado: R$ {total:.2f}',
        'modo': 'ocioso',
        'carrinho': [],
        'fila_clarificacao': [],
        'tentativas_clarificacao': 0,
    }

processar_carrinho(carrinho_dicts: list[dict]) -> RetornoNode

Gera resposta com o conteudo atual do carrinho.

Parameters:

Name Type Description Default
carrinho_dicts list[dict]

Lista de dicts do carrinho no State.

required

Returns:

Type Description
RetornoNode

Dicionario com resposta e etapa atualizados.

Source code in src/graph/handlers/carrinho_handler.py
def processar_carrinho(carrinho_dicts: list[dict]) -> RetornoNode:
    """Gera resposta com o conteudo atual do carrinho.

    Args:
        carrinho_dicts: Lista de dicts do carrinho no State.

    Returns:
        Dicionario com ``resposta`` e ``etapa`` atualizados.
    """
    if not carrinho_dicts:
        return {'resposta': 'Seu carrinho esta vazio!', 'modo': 'coletando'}

    carrinho = Carrinho.from_state_dicts(carrinho_dicts)
    resposta = 'Seu pedido:\n' + carrinho.formatar()
    return {'resposta': resposta, 'modo': 'coletando'}

clarificar(fila: list[dict], mensagem: str, tentativas: int, thread_id: str = '') -> ResultadoClarificacao

Processa a resposta do usuário durante clarificação de variante.

Tenta extrair uma variante válida da mensagem usando o extrator spaCy. Se não encontrar, usa fuzzy matching como fallback. Se válida, calcula o preço e adiciona ao carrinho. Se inválida, incrementa o contador de tentativas e faz re-prompt.

Parameters:

Name Type Description Default
fila list[dict]

Fila de itens pendentes de clarificação.

required
mensagem str

Mensagem do usuário com a resposta.

required
tentativas int

Contador atual de tentativas falhas.

required
thread_id str

Identificador da sessão para observabilidade.

''

Returns:

Type Description
ResultadoClarificacao

ResultadoClarificacao com estado atualizado.

Source code in src/graph/handlers/clarificacao.py
def clarificar(
    fila: list[dict],
    mensagem: str,
    tentativas: int,
    thread_id: str = '',
) -> ResultadoClarificacao:
    """Processa a resposta do usuário durante clarificação de variante.

    Tenta extrair uma variante válida da mensagem usando o extrator
    spaCy. Se não encontrar, usa fuzzy matching como fallback. Se
    válida, calcula o preço e adiciona ao carrinho. Se inválida,
    incrementa o contador de tentativas e faz re-prompt.

    Args:
        fila: Fila de itens pendentes de clarificação.
        mensagem: Mensagem do usuário com a resposta.
        tentativas: Contador atual de tentativas falhas.
        thread_id: Identificador da sessão para observabilidade.

    Returns:
        ResultadoClarificacao com estado atualizado.
    """
    if not fila:
        return ResultadoClarificacao(
            tipo='sucesso',
            resposta='',
            modo='ocioso',
            fila=[],
            carrinho=[],
            tentativas=0,
        )

    item_fila = fila[0]
    item_id = item_fila['item_id']
    nome = item_fila['nome']
    opcoes = item_fila['opcoes']
    item_dados = item_fila['item']

    # Tenta extrair via EntityRuler
    variante = extrair_variante(mensagem, item_id)

    # Valida: se EntityRuler retornou parcial (ex: "limão" em vez de "limão 300ml"),
    # usa fuzzy matching para encontrar a variante exata
    if variante is not None and variante not in opcoes:
        variante_fuzzy, _score = fuzzy_match_variante(mensagem, opcoes)
        variante = variante_fuzzy or None

    # Fallback: fuzzy matching se EntityRuler não encontrou
    if variante is None:
        variante_fuzzy, _score = fuzzy_match_variante(mensagem, opcoes)
        if variante_fuzzy:
            variante = variante_fuzzy

    if variante is not None:
        resultado = _processar_variante_valida(fila, item_id, item_dados, variante)
    else:
        resultado = _processar_variante_invalida(fila, nome, opcoes, tentativas)

    _log_clarificacao(
        thread_id=thread_id,
        item_id=item_id,
        nome_item=nome,
        opcoes=opcoes,
        mensagem=mensagem,
        tentativas=tentativas,
        resultado=resultado,
        variante=variante,
    )

    return resultado

processar_confirmacao(carrinho_dicts: list[dict]) -> RetornoNode

Processa confirmacao do pedido pelo usuario.

Parameters:

Name Type Description Default
carrinho_dicts list[dict]

Lista de dicts do carrinho no State.

required

Returns:

Type Description
RetornoNode

Dicionario com resposta, etapa e carrinho atualizados.

Source code in src/graph/handlers/confirmar_handler.py
def processar_confirmacao(carrinho_dicts: list[dict]) -> RetornoNode:
    """Processa confirmacao do pedido pelo usuario.

    Args:
        carrinho_dicts: Lista de dicts do carrinho no State.

    Returns:
        Dicionario com ``resposta``, ``etapa`` e ``carrinho`` atualizados.
    """
    if not carrinho_dicts:
        return {'resposta': 'Não há pedido para confirmar.'}

    carrinho = Carrinho.from_state_dicts(carrinho_dicts)
    total = carrinho.total_reais()

    return {
        'resposta': f'Pedido confirmado! Total: R$ {total:.2f}',
        'modo': 'finalizado',
        'carrinho': [],
    }

processar_pedido(itens_extraidos: list[dict], carrinho_existente: list[dict]) -> ResultadoPedir

Processa itens extraidos e os adiciona ao carrinho.

Para cada item extraido, verifica se possui preco fixo ou variantes. Itens com variantes invalidas ou nao especificadas vao para a fila de clarificacao.

Parameters:

Name Type Description Default
itens_extraidos list[dict]

Lista de itens extraidos da mensagem.

required
carrinho_existente list[dict]

Carrinho atual do estado (para mesclar).

required

Returns:

Type Description
ResultadoPedir

ResultadoPedir com carrinho, fila e resposta atualizados.

Source code in src/graph/handlers/pedido_handler.py
def processar_pedido(
    itens_extraidos: list[dict],
    carrinho_existente: list[dict],
) -> ResultadoPedir:
    """Processa itens extraidos e os adiciona ao carrinho.

    Para cada item extraido, verifica se possui preco fixo ou variantes.
    Itens com variantes invalidas ou nao especificadas vao para a fila
    de clarificacao.

    Args:
        itens_extraidos: Lista de itens extraidos da mensagem.
        carrinho_existente: Carrinho atual do estado (para mesclar).

    Returns:
        ResultadoPedir com carrinho, fila e resposta atualizados.
    """
    carrinho = Carrinho.from_state_dicts(carrinho_existente)
    fila: list[dict] = []
    itens_adicionados: list[CarrinhoItem] = []

    for item in itens_extraidos:
        item_data = get_item_por_id(item['item_id'])
        if item_data is None:
            continue

        preco_total = _calcular_preco_item(item, item_data)

        if preco_total is not None:
            carrinho_item = CarrinhoItem(
                item_id=item['item_id'],
                quantidade=item['quantidade'],
                preco_centavos=preco_total,
                variante=item.get('variante'),
            )
            carrinho.adicionar(carrinho_item)
            itens_adicionados.append(carrinho_item)
        else:
            fila.append(
                {
                    'item': item,
                    'item_id': item['item_id'],
                    'nome': item_data['nome'],
                    'campo': 'variante',
                    'opcoes': get_variantes(item['item_id']),
                }
            )

    if fila:
        proxima = fila[0]
        opcoes = ', '.join(proxima['opcoes'])
        resposta = f'{proxima["nome"]}: qual opcao? {opcoes}'
    elif itens_adicionados:
        resposta = carrinho.formatar()
    else:
        resposta = ''

    return ResultadoPedir(
        carrinho=carrinho.to_state_dicts(),
        fila=fila,
        resposta=resposta,
    )

processar_remocao(carrinho_dicts: list[dict], mensagem: str) -> ResultadoRemover

Processa remocao de itens do carrinho.

Source code in src/graph/handlers/remocao_handler.py
def processar_remocao(
    carrinho_dicts: list[dict],
    mensagem: str,
) -> ResultadoRemover:
    """Processa remocao de itens do carrinho."""
    if not carrinho_dicts:
        return ResultadoRemover(
            resposta='Seu carrinho esta vazio! Nao ha nada para remover.',
            modo='ocioso',
        )

    itens_para_remover = extrair_item_carrinho(mensagem, carrinho_dicts)

    if not itens_para_remover:
        return ResultadoRemover(
            carrinho=carrinho_dicts,
            resposta='Não encontrei esse item no seu carrinho.',
            modo='coletando',
        )

    indices_para_remover: set[int] = set()
    for item in itens_para_remover:
        indices_para_remover.update(item['indices'])

    carrinho = Carrinho.from_state_dicts(carrinho_dicts)
    carrinho.remover_indices(indices_para_remover)

    if carrinho.vazio():
        return ResultadoRemover(
            carrinho=[],
            resposta='Todos os itens foram removidos do seu pedido.',
            modo='ocioso',
        )

    resposta = 'Itens removidos!\nSeu pedido:\n' + carrinho.formatar()
    return ResultadoRemover(
        carrinho=carrinho.to_state_dicts(),
        resposta=resposta,
        modo='coletando',
    )

processar_saudacao() -> RetornoNode

Gera resposta de saudacao com o nome do restaurante.

Returns:

Type Description
RetornoNode

Dicionario com resposta e etapa atualizados.

Source code in src/graph/handlers/saudacao_handler.py
def processar_saudacao() -> RetornoNode:
    """Gera resposta de saudacao com o nome do restaurante.

    Returns:
        Dicionario com ``resposta`` e ``etapa`` atualizados.
    """
    nome_restaurante = get_tenant_nome()
    resposta = f'Ola! Seja bem-vindo(a) a {nome_restaurante}!\nComo posso ajudar?'
    return {'resposta': resposta, 'modo': 'ocioso'}

processar_troca(carrinho_dicts: list[dict], mensagem: str) -> ResultadoTrocar

Processa troca de variantes de itens no carrinho.

Source code in src/graph/handlers/troca_handler.py
def processar_troca(
    carrinho_dicts: list[dict],
    mensagem: str,
) -> ResultadoTrocar:
    """Processa troca de variantes de itens no carrinho."""
    if not carrinho_dicts:
        return ResultadoTrocar(
            resposta='Não há pedido para trocar.',
            modo='ocioso',
        )

    extracao = extrair_itens_troca(mensagem, carrinho_dicts)
    caso = extracao['caso']
    item_original = extracao['item_original']
    variante_nova = extracao['variante_nova']

    if caso == 'vazio':
        return ResultadoTrocar(
            carrinho=carrinho_dicts,
            resposta="Não entendi o que quer trocar. Ex: 'muda pra duplo'",
        )

    if caso == 'A':
        return ResultadoTrocar(
            carrinho=carrinho_dicts,
            resposta="Por enquanto só consigo trocar variantes. Ex: 'muda pra duplo'",
        )

    if caso == 'B':
        nome_item_mencionado = None
        if item_original is None:
            nome_item_mencionado = _extrair_primeiro_item_da_mensagem(mensagem)
        return _processar_caso_b(
            carrinho_dicts, item_original, variante_nova, nome_item_mencionado
        )

    if caso == 'C':
        return _processar_caso_c(carrinho_dicts, variante_nova)

    return ResultadoTrocar(
        carrinho=carrinho_dicts,
        resposta="Não entendi o que quer trocar. Ex: 'muda pra duplo'",
    )

src.graph.handlers.carrinho

Modelos de carrinho de pedidos.

Classes OO para representar o carrinho e seus itens, substituindo o uso de dicts soltos.

Example
from src.graph.handlers.carrinho import Carrinho, CarrinhoItem

carrinho = Carrinho()
carrinho.adicionar(CarrinhoItem('lanche_001', 2, 3000, 'duplo'))
carrinho.total_reais()  # 60.0

CarrinhoItem(item_id: str, quantidade: int, preco_centavos: int, variante: str | None = None) dataclass

Item individual no carrinho.

Attributes:

Name Type Description
item_id str

ID do item no cardapio.

quantidade int

Quantidade deste item.

preco_centavos int

Preco unitario em centavos.

variante str | None

Variante selecionada (ou None).

preco_reais() -> float

Retorna preco unitario em reais.

Source code in src/graph/handlers/carrinho.py
def preco_reais(self) -> float:
    """Retorna preco unitario em reais."""
    return self.preco_centavos / 100

subtotal() -> int

Retorna subtotal (preco * quantidade) em centavos.

Source code in src/graph/handlers/carrinho.py
def subtotal(self) -> int:
    """Retorna subtotal (preco * quantidade) em centavos."""
    return self.preco_centavos * self.quantidade

subtotal_reais() -> float

Retorna subtotal em reais.

Source code in src/graph/handlers/carrinho.py
def subtotal_reais(self) -> float:
    """Retorna subtotal em reais."""
    return self.subtotal() / 100

to_dict() -> dict

Converte para dict compativel com State.

Source code in src/graph/handlers/carrinho.py
def to_dict(self) -> dict:
    """Converte para dict compativel com State."""
    return {
        'item_id': self.item_id,
        'quantidade': self.quantidade,
        'preco': self.preco_centavos,
        'variante': self.variante,
    }

from_dict(data: dict) -> CarrinhoItem classmethod

Cria CarrinhoItem a partir de dict do State.

Source code in src/graph/handlers/carrinho.py
@classmethod
def from_dict(cls, data: dict) -> CarrinhoItem:
    """Cria CarrinhoItem a partir de dict do State."""
    return cls(
        item_id=data['item_id'],
        quantidade=data.get('quantidade', 1),
        preco_centavos=data['preco'],
        variante=data.get('variante'),
    )

Carrinho(itens: list[CarrinhoItem] = list()) dataclass

Carrinho de pedidos com metodos de negocio.

Attributes:

Name Type Description
itens list[CarrinhoItem]

Lista de itens no carrinho.

adicionar(item: CarrinhoItem) -> None

Adiciona um item ao carrinho.

Source code in src/graph/handlers/carrinho.py
def adicionar(self, item: CarrinhoItem) -> None:
    """Adiciona um item ao carrinho."""
    self.itens.append(item)

remover_indices(indices: set[int]) -> None

Remove itens nos indices especificados.

Source code in src/graph/handlers/carrinho.py
def remover_indices(self, indices: set[int]) -> None:
    """Remove itens nos indices especificados."""
    self.itens = [i for idx, i in enumerate(self.itens) if idx not in indices]

limpar() -> None

Limpa todos os itens do carrinho.

Source code in src/graph/handlers/carrinho.py
def limpar(self) -> None:
    """Limpa todos os itens do carrinho."""
    self.itens.clear()

total_centavos() -> int

Retorna total do carrinho em centavos.

Source code in src/graph/handlers/carrinho.py
def total_centavos(self) -> int:
    """Retorna total do carrinho em centavos."""
    return sum(item.subtotal() for item in self.itens)

total_reais() -> float

Retorna total do carrinho em reais.

Source code in src/graph/handlers/carrinho.py
def total_reais(self) -> float:
    """Retorna total do carrinho em reais."""
    return self.total_centavos() / 100

vazio() -> bool

Retorna True se o carrinho esta vazio.

Source code in src/graph/handlers/carrinho.py
def vazio(self) -> bool:
    """Retorna True se o carrinho esta vazio."""
    return not self.itens

tamanho() -> int

Retorna numero de itens no carrinho.

Source code in src/graph/handlers/carrinho.py
def tamanho(self) -> int:
    """Retorna numero de itens no carrinho."""
    return len(self.itens)

formatar() -> str

Formata o carrinho como texto legivel.

Returns:

Type Description
str

String com um item por linha.

Source code in src/graph/handlers/carrinho.py
def formatar(self) -> str:
    """Formata o carrinho como texto legivel.

    Returns:
        String com um item por linha.
    """
    if self.vazio():
        return 'Seu carrinho esta vazio.'

    linhas = []
    for item in self.itens:
        nome = get_nome_item(item.item_id) or item.item_id
        if item.variante:
            nome = f'{nome} ({item.variante})'
        linhas.append(f'{item.quantidade}x {nome} — R$ {item.subtotal_reais():.2f}')

    linhas.append(f'\nTotal: R$ {self.total_reais():.2f}')
    return '\n'.join(linhas)

to_state_dicts() -> list[dict]

Converte para lista de dicts compativel com State.

Source code in src/graph/handlers/carrinho.py
def to_state_dicts(self) -> list[dict]:
    """Converte para lista de dicts compativel com State."""
    return [item.to_dict() for item in self.itens]

from_state_dicts(dados: list[dict]) -> Carrinho classmethod

Cria Carrinho a partir de lista de dicts do State.

Source code in src/graph/handlers/carrinho.py
@classmethod
def from_state_dicts(cls, dados: list[dict]) -> Carrinho:
    """Cria Carrinho a partir de lista de dicts do State."""
    return cls(itens=[CarrinhoItem.from_dict(d) for d in dados])

src.graph.handlers.pedido_handler

Handler de processamento de pedidos.

Processa itens extraidos da mensagem do usuario, calcula precos, adiciona ao carrinho e envia itens pendentes para fila de clarificacao.

Example
from src.graph.handlers.pedido_handler import processar_pedido

itens = [
    {'item_id': 'lanche_002', 'quantidade': 1, 'variante': None, 'remocoes': []}
]
result = processar_pedido(itens, [])
len(result.carrinho)
1

ResultadoPedir(carrinho: list[dict] = list(), fila: list[dict] = list(), resposta: str = '') dataclass

Resultado do processamento de pedido.

Attributes:

Name Type Description
carrinho list[dict]

Carrinho atualizado.

fila list[dict]

Itens pendentes de clarificacao.

resposta str

Texto formatado para o usuario.

to_dict() -> RetornoNode

Converte para dicionario compativel com LangGraph State.

Source code in src/graph/handlers/pedido_handler.py
def to_dict(self) -> RetornoNode:
    """Converte para dicionario compativel com LangGraph State."""
    return {
        'carrinho': self.carrinho,
        'fila_clarificacao': self.fila,
        'resposta': self.resposta,
        'modo': 'clarificando' if self.fila else 'coletando',
    }

processar_pedido(itens_extraidos: list[dict], carrinho_existente: list[dict]) -> ResultadoPedir

Processa itens extraidos e os adiciona ao carrinho.

Para cada item extraido, verifica se possui preco fixo ou variantes. Itens com variantes invalidas ou nao especificadas vao para a fila de clarificacao.

Parameters:

Name Type Description Default
itens_extraidos list[dict]

Lista de itens extraidos da mensagem.

required
carrinho_existente list[dict]

Carrinho atual do estado (para mesclar).

required

Returns:

Type Description
ResultadoPedir

ResultadoPedir com carrinho, fila e resposta atualizados.

Source code in src/graph/handlers/pedido_handler.py
def processar_pedido(
    itens_extraidos: list[dict],
    carrinho_existente: list[dict],
) -> ResultadoPedir:
    """Processa itens extraidos e os adiciona ao carrinho.

    Para cada item extraido, verifica se possui preco fixo ou variantes.
    Itens com variantes invalidas ou nao especificadas vao para a fila
    de clarificacao.

    Args:
        itens_extraidos: Lista de itens extraidos da mensagem.
        carrinho_existente: Carrinho atual do estado (para mesclar).

    Returns:
        ResultadoPedir com carrinho, fila e resposta atualizados.
    """
    carrinho = Carrinho.from_state_dicts(carrinho_existente)
    fila: list[dict] = []
    itens_adicionados: list[CarrinhoItem] = []

    for item in itens_extraidos:
        item_data = get_item_por_id(item['item_id'])
        if item_data is None:
            continue

        preco_total = _calcular_preco_item(item, item_data)

        if preco_total is not None:
            carrinho_item = CarrinhoItem(
                item_id=item['item_id'],
                quantidade=item['quantidade'],
                preco_centavos=preco_total,
                variante=item.get('variante'),
            )
            carrinho.adicionar(carrinho_item)
            itens_adicionados.append(carrinho_item)
        else:
            fila.append(
                {
                    'item': item,
                    'item_id': item['item_id'],
                    'nome': item_data['nome'],
                    'campo': 'variante',
                    'opcoes': get_variantes(item['item_id']),
                }
            )

    if fila:
        proxima = fila[0]
        opcoes = ', '.join(proxima['opcoes'])
        resposta = f'{proxima["nome"]}: qual opcao? {opcoes}'
    elif itens_adicionados:
        resposta = carrinho.formatar()
    else:
        resposta = ''

    return ResultadoPedir(
        carrinho=carrinho.to_state_dicts(),
        fila=fila,
        resposta=resposta,
    )

src.graph.handlers.saudacao_handler

Handler de saudacao.

Gera resposta de saudacao com o nome do restaurante.

processar_saudacao() -> RetornoNode

Gera resposta de saudacao com o nome do restaurante.

Returns:

Type Description
RetornoNode

Dicionario com resposta e etapa atualizados.

Source code in src/graph/handlers/saudacao_handler.py
def processar_saudacao() -> RetornoNode:
    """Gera resposta de saudacao com o nome do restaurante.

    Returns:
        Dicionario com ``resposta`` e ``etapa`` atualizados.
    """
    nome_restaurante = get_tenant_nome()
    resposta = f'Ola! Seja bem-vindo(a) a {nome_restaurante}!\nComo posso ajudar?'
    return {'resposta': resposta, 'modo': 'ocioso'}

src.graph.handlers.remocao_handler

Handler de remocao de itens do carrinho.

Extrai itens mencionados na mensagem e remove do carrinho. Suporta remocao por nome do item e por variante especifica.

Example
from src.graph.handlers.remocao_handler import processar_remocao

carrinho_dicts = [
    {
        'item_id': 'lanche_001',
        'quantidade': 1,
        'preco': 1500,
        'variante': 'simples',
    },
]
result = processar_remocao(carrinho_dicts, 'tira o hamburguer')
result.carrinho
[]

ResultadoRemover(carrinho: list[dict] = list(), resposta: str = '', modo: MODOS = 'ocioso') dataclass

Resultado do processamento de remocao.

Attributes:

Name Type Description
carrinho list[dict]

Carrinho atualizado apos remocao.

resposta str

Texto formatado para o usuario.

etapa str

Proxima etapa do fluxo.

to_dict() -> RetornoNode

Converte para dicionario compativel com LangGraph State.

Source code in src/graph/handlers/remocao_handler.py
def to_dict(self) -> RetornoNode:
    """Converte para dicionario compativel com LangGraph State."""
    return {
        'carrinho': self.carrinho,
        'resposta': self.resposta,
        'modo': self.modo,
    }

processar_remocao(carrinho_dicts: list[dict], mensagem: str) -> ResultadoRemover

Processa remocao de itens do carrinho.

Source code in src/graph/handlers/remocao_handler.py
def processar_remocao(
    carrinho_dicts: list[dict],
    mensagem: str,
) -> ResultadoRemover:
    """Processa remocao de itens do carrinho."""
    if not carrinho_dicts:
        return ResultadoRemover(
            resposta='Seu carrinho esta vazio! Nao ha nada para remover.',
            modo='ocioso',
        )

    itens_para_remover = extrair_item_carrinho(mensagem, carrinho_dicts)

    if not itens_para_remover:
        return ResultadoRemover(
            carrinho=carrinho_dicts,
            resposta='Não encontrei esse item no seu carrinho.',
            modo='coletando',
        )

    indices_para_remover: set[int] = set()
    for item in itens_para_remover:
        indices_para_remover.update(item['indices'])

    carrinho = Carrinho.from_state_dicts(carrinho_dicts)
    carrinho.remover_indices(indices_para_remover)

    if carrinho.vazio():
        return ResultadoRemover(
            carrinho=[],
            resposta='Todos os itens foram removidos do seu pedido.',
            modo='ocioso',
        )

    resposta = 'Itens removidos!\nSeu pedido:\n' + carrinho.formatar()
    return ResultadoRemover(
        carrinho=carrinho.to_state_dicts(),
        resposta=resposta,
        modo='coletando',
    )

src.graph.handlers.troca_handler

Handler de troca de variantes de itens do carrinho.

Extrai itens e variantes mencionados na mensagem e troca a variante de itens existentes no carrinho, recalculando o preco.

Example
from src.graph.handlers.troca_handler import processar_troca

carrinho_dicts = [
    {
        'item_id': 'lanche_001',
        'quantidade': 1,
        'preco': 1500,
        'variante': 'simples',
    },
]
result = processar_troca(carrinho_dicts, 'muda pra duplo')
result.carrinho[0]['variante']
'duplo'

ResultadoTrocar(carrinho: list[dict] = list(), resposta: str = '', modo: MODOS = 'coletando') dataclass

Resultado do processamento de troca.

Attributes:

Name Type Description
carrinho list[dict]

Carrinho atualizado apos troca.

resposta str

Texto formatado para o usuario.

etapa str

Proxima etapa do fluxo.

to_dict() -> RetornoNode

Converte para dicionario compativel com LangGraph State.

Source code in src/graph/handlers/troca_handler.py
def to_dict(self) -> RetornoNode:
    """Converte para dicionario compativel com LangGraph State."""
    return {
        'carrinho': self.carrinho,
        'resposta': self.resposta,
        'modo': self.modo,
    }

processar_troca(carrinho_dicts: list[dict], mensagem: str) -> ResultadoTrocar

Processa troca de variantes de itens no carrinho.

Source code in src/graph/handlers/troca_handler.py
def processar_troca(
    carrinho_dicts: list[dict],
    mensagem: str,
) -> ResultadoTrocar:
    """Processa troca de variantes de itens no carrinho."""
    if not carrinho_dicts:
        return ResultadoTrocar(
            resposta='Não há pedido para trocar.',
            modo='ocioso',
        )

    extracao = extrair_itens_troca(mensagem, carrinho_dicts)
    caso = extracao['caso']
    item_original = extracao['item_original']
    variante_nova = extracao['variante_nova']

    if caso == 'vazio':
        return ResultadoTrocar(
            carrinho=carrinho_dicts,
            resposta="Não entendi o que quer trocar. Ex: 'muda pra duplo'",
        )

    if caso == 'A':
        return ResultadoTrocar(
            carrinho=carrinho_dicts,
            resposta="Por enquanto só consigo trocar variantes. Ex: 'muda pra duplo'",
        )

    if caso == 'B':
        nome_item_mencionado = None
        if item_original is None:
            nome_item_mencionado = _extrair_primeiro_item_da_mensagem(mensagem)
        return _processar_caso_b(
            carrinho_dicts, item_original, variante_nova, nome_item_mencionado
        )

    if caso == 'C':
        return _processar_caso_c(carrinho_dicts, variante_nova)

    return ResultadoTrocar(
        carrinho=carrinho_dicts,
        resposta="Não entendi o que quer trocar. Ex: 'muda pra duplo'",
    )

src.graph.handlers.confirmar_handler

Handler de confirmacao de pedido.

Calcula o total do carrinho, gera mensagem de confirmacao e limpa o carrinho apos o pedido ser finalizado.

processar_confirmacao(carrinho_dicts: list[dict]) -> RetornoNode

Processa confirmacao do pedido pelo usuario.

Parameters:

Name Type Description Default
carrinho_dicts list[dict]

Lista de dicts do carrinho no State.

required

Returns:

Type Description
RetornoNode

Dicionario com resposta, etapa e carrinho atualizados.

Source code in src/graph/handlers/confirmar_handler.py
def processar_confirmacao(carrinho_dicts: list[dict]) -> RetornoNode:
    """Processa confirmacao do pedido pelo usuario.

    Args:
        carrinho_dicts: Lista de dicts do carrinho no State.

    Returns:
        Dicionario com ``resposta``, ``etapa`` e ``carrinho`` atualizados.
    """
    if not carrinho_dicts:
        return {'resposta': 'Não há pedido para confirmar.'}

    carrinho = Carrinho.from_state_dicts(carrinho_dicts)
    total = carrinho.total_reais()

    return {
        'resposta': f'Pedido confirmado! Total: R$ {total:.2f}',
        'modo': 'finalizado',
        'carrinho': [],
    }

src.graph.handlers.cancelar_handler

Handler de cancelamento de pedido.

Limpa o carrinho e reseta o estado do fluxo.

processar_cancelamento(carrinho_dicts: list[dict]) -> RetornoNode

Processa cancelamento do pedido.

Parameters:

Name Type Description Default
carrinho_dicts list[dict]

Lista de dicts do carrinho no State.

required

Returns:

Type Description
RetornoNode

Dicionario com resposta, etapa e carrinho atualizados.

Source code in src/graph/handlers/cancelar_handler.py
def processar_cancelamento(carrinho_dicts: list[dict]) -> RetornoNode:
    """Processa cancelamento do pedido.

    Args:
        carrinho_dicts: Lista de dicts do carrinho no State.

    Returns:
        Dicionario com ``resposta``, ``etapa`` e ``carrinho`` atualizados.
    """
    if not carrinho_dicts:
        return {'resposta': 'Não há pedido para cancelar.', 'modo': 'ocioso'}

    carrinho = Carrinho.from_state_dicts(carrinho_dicts)
    total = carrinho.total_reais()

    return {
        'resposta': f'Pedido cancelado. Total descartado: R$ {total:.2f}',
        'modo': 'ocioso',
        'carrinho': [],
        'fila_clarificacao': [],
        'tentativas_clarificacao': 0,
    }

src.graph.handlers.carrinho_handler

Handler de exibicao do carrinho.

Lista todos os itens no carrinho com quantidades e precos.

processar_carrinho(carrinho_dicts: list[dict]) -> RetornoNode

Gera resposta com o conteudo atual do carrinho.

Parameters:

Name Type Description Default
carrinho_dicts list[dict]

Lista de dicts do carrinho no State.

required

Returns:

Type Description
RetornoNode

Dicionario com resposta e etapa atualizados.

Source code in src/graph/handlers/carrinho_handler.py
def processar_carrinho(carrinho_dicts: list[dict]) -> RetornoNode:
    """Gera resposta com o conteudo atual do carrinho.

    Args:
        carrinho_dicts: Lista de dicts do carrinho no State.

    Returns:
        Dicionario com ``resposta`` e ``etapa`` atualizados.
    """
    if not carrinho_dicts:
        return {'resposta': 'Seu carrinho esta vazio!', 'modo': 'coletando'}

    carrinho = Carrinho.from_state_dicts(carrinho_dicts)
    resposta = 'Seu pedido:\n' + carrinho.formatar()
    return {'resposta': resposta, 'modo': 'coletando'}

src.graph.handlers.clarificacao

Handler de clarificação de variantes.

Processa respostas do usuário durante a clarificação de itens pendentes no pedido. Suporta validação via extrator spaCy, re-prompt com limite de tentativas e avanço automático na fila.

Example
from src.graph.handlers.clarificacao import clarificar

fila = [
    {
        'item': {
            'item_id': 'lanche_001',
            'quantidade': 1,
            'variante': None,
            'remocoes': [],
        },
        'item_id': 'lanche_001',
        'nome': 'Hambúrguer',
        'campo': 'variante',
        'opcoes': ['simples', 'duplo'],
    }
]
result = clarificar(fila, 'duplo', 0)
result.tipo
'sucesso'

MAX_TENTATIVAS = 3 module-attribute

Número máximo de tentativas antes de desistir do item.

ResultadoClarificacao(tipo: str, resposta: str, modo: MODOS, carrinho: list[dict] = list(), fila: list[dict] = list(), tentativas: int = 0) dataclass

Resultado do processamento de clarificação.

Attributes:

Name Type Description
tipo str

Tipo do resultado ('sucesso', 'invalida', 'erro').

resposta str

Texto da resposta para o usuário.

etapa str

Próxima etapa do fluxo.

carrinho list[dict]

Carrinho atualizado.

fila list[dict]

Fila de clarificação atualizada.

tentativas int

Contador de tentativas atual.

to_dict() -> RetornoNode

Converte para dicionário compatível com LangGraph State.

Source code in src/graph/handlers/clarificacao.py
def to_dict(self) -> RetornoNode:
    """Converte para dicionário compatível com LangGraph State."""
    return {
        'resposta': self.resposta,
        'modo': self.modo,
        'carrinho': self.carrinho,
        'fila_clarificacao': self.fila,
        'tentativas_clarificacao': self.tentativas,
    }

clarificar(fila: list[dict], mensagem: str, tentativas: int, thread_id: str = '') -> ResultadoClarificacao

Processa a resposta do usuário durante clarificação de variante.

Tenta extrair uma variante válida da mensagem usando o extrator spaCy. Se não encontrar, usa fuzzy matching como fallback. Se válida, calcula o preço e adiciona ao carrinho. Se inválida, incrementa o contador de tentativas e faz re-prompt.

Parameters:

Name Type Description Default
fila list[dict]

Fila de itens pendentes de clarificação.

required
mensagem str

Mensagem do usuário com a resposta.

required
tentativas int

Contador atual de tentativas falhas.

required
thread_id str

Identificador da sessão para observabilidade.

''

Returns:

Type Description
ResultadoClarificacao

ResultadoClarificacao com estado atualizado.

Source code in src/graph/handlers/clarificacao.py
def clarificar(
    fila: list[dict],
    mensagem: str,
    tentativas: int,
    thread_id: str = '',
) -> ResultadoClarificacao:
    """Processa a resposta do usuário durante clarificação de variante.

    Tenta extrair uma variante válida da mensagem usando o extrator
    spaCy. Se não encontrar, usa fuzzy matching como fallback. Se
    válida, calcula o preço e adiciona ao carrinho. Se inválida,
    incrementa o contador de tentativas e faz re-prompt.

    Args:
        fila: Fila de itens pendentes de clarificação.
        mensagem: Mensagem do usuário com a resposta.
        tentativas: Contador atual de tentativas falhas.
        thread_id: Identificador da sessão para observabilidade.

    Returns:
        ResultadoClarificacao com estado atualizado.
    """
    if not fila:
        return ResultadoClarificacao(
            tipo='sucesso',
            resposta='',
            modo='ocioso',
            fila=[],
            carrinho=[],
            tentativas=0,
        )

    item_fila = fila[0]
    item_id = item_fila['item_id']
    nome = item_fila['nome']
    opcoes = item_fila['opcoes']
    item_dados = item_fila['item']

    # Tenta extrair via EntityRuler
    variante = extrair_variante(mensagem, item_id)

    # Valida: se EntityRuler retornou parcial (ex: "limão" em vez de "limão 300ml"),
    # usa fuzzy matching para encontrar a variante exata
    if variante is not None and variante not in opcoes:
        variante_fuzzy, _score = fuzzy_match_variante(mensagem, opcoes)
        variante = variante_fuzzy or None

    # Fallback: fuzzy matching se EntityRuler não encontrou
    if variante is None:
        variante_fuzzy, _score = fuzzy_match_variante(mensagem, opcoes)
        if variante_fuzzy:
            variante = variante_fuzzy

    if variante is not None:
        resultado = _processar_variante_valida(fila, item_id, item_dados, variante)
    else:
        resultado = _processar_variante_invalida(fila, nome, opcoes, tentativas)

    _log_clarificacao(
        thread_id=thread_id,
        item_id=item_id,
        nome_item=nome,
        opcoes=opcoes,
        mensagem=mensagem,
        tentativas=tentativas,
        resultado=resultado,
        variante=variante,
    )

    return resultado

src.graph.handlers.desconhecido

Handler para intents desconhecidas.

node_handler_desconhecido(state: State) -> RetornoNode

Gera resposta de esclarecimento para intents desconhecidas.

Parameters:

Name Type Description Default
state State

Estado atual do grafo de atendimento.

required

Returns:

Type Description
RetornoNode

Dicionário com resposta e etapa atualizados.

Source code in src/graph/handlers/desconhecido.py
def node_handler_desconhecido(state: State) -> RetornoNode:
    """Gera resposta de esclarecimento para intents desconhecidas.

    Args:
        state: Estado atual do grafo de atendimento.

    Returns:
        Dicionário com ``resposta`` e ``etapa`` atualizados.
    """
    return {
        'resposta': 'Não entendi. Pode reformular sua mensagem? '
        'Posso ajudar com pedidos, ver o carrinho, confirmar ou cancelar.',
        'modo': 'ocioso',
    }