← Back← Voltar
AI-powered B2B fleet maintenance and management platform.
Plataforma B2B de gestão e manutenção de frotas com IA.
TypeScriptReactNodeNext.jsTailwindPostgreSQLPrismaWhatsApp APIDockerAI/LLM Integration

CIME is a B2B fleet maintenance and management platform designed to help small and medium-sized companies optimize vehicle operations, reduce downtime, and improve maintenance efficiency without requiring expensive hardware or telematics.

CIME é uma plataforma B2B de gestão e manutenção de frotas feita para ajudar pequenas e médias empresas a otimizar a operação dos veículos, reduzir paradas e melhorar a eficiência da manutenção sem exigir hardware caro ou telemetria.

One of the core ideas behind the platform is that field workers shouldn’t need to learn enterprise software. Drivers and mechanics already communicate through WhatsApp everyday. Instead of forcing them into structured forms, or a new application CIME wants to use natural language as the primary interface to the system.

Uma das ideias centrais da plataforma é que quem está em campo não deveria precisar aprender a usar software corporativo. Motoristas e mecânicos já se comunicam por WhatsApp todo dia. Em vez de empurrar formulários estruturados ou mais um aplicativo, o CIME usa linguagem natural como interface principal do sistema.

That introduces a different engineering problem. Human conversations are ambiguous, while fleet operations require deterministic execution. A message like “the truck is making that noise again” isn’t a database operation — it needs to be interpreted, validated, associated with the correct vehicle and company, and ultimately translated into a business action.

Isso cria um problema de engenharia diferente. Conversas humanas são ambíguas, enquanto a operação de frota exige execução determinística. Uma mensagem como “o caminhão está fazendo aquele barulho de novo” não é uma operação de banco de dados — ela precisa ser interpretada, validada, associada ao veículo e à empresa corretos e, por fim, traduzida em uma ação de negócio.

The architecture was designed around that boundary: allowing an LLM to understand natural language while keeping business logic, authorization, and persistence inside deterministic application services.

A arquitetura foi desenhada em torno dessa fronteira: deixar um LLM entender a linguagem natural, mantendo regra de negócio, autorização e persistência dentro de serviços determinísticos da aplicação.

System Architecture
Arquitetura do Sistema

CIME is split into four layers that hand a request off in sequence — from the people using it, through the AI and the API, down to the database.

  • Clients — two entry points: a Next.js / React dashboard for managers, and WhatsApp itself for drivers and mechanics in the field, so there’s no app to install.
  • AI & Integrations — WhatsApp text and voice flow through a Node + Baileys bot on Fly.io, which uses OpenAI to turn natural language into structured commands.
  • Backend · Fly.io — an Express + TypeScript API with JWT / CORS middleware, REST routes, and controllers / services that hold the business logic.
  • Data · Supabase — Prisma gives type-safe access to a Supabase-managed PostgreSQL database, the single source of truth for fleet and maintenance data.

O CIME é dividido em quatro camadas que repassam a requisição em sequência — das pessoas que usam, passando pela IA e pela API, até o banco de dados.

  • Clientes — dois pontos de entrada: um painel em Next.js / React para gestores e o próprio WhatsApp para motoristas e mecânicos em campo, sem app para instalar.
  • IA & Integrações — texto e áudio do WhatsApp passam por um bot Node + Baileys na Fly.io, que usa a OpenAI para transformar linguagem natural em comandos estruturados.
  • Backend · Fly.io — uma API Express + TypeScript com middleware de JWT / CORS, rotas REST e controllers / services que concentram a regra de negócio.
  • Dados · Supabase — o Prisma dá acesso tipado a um PostgreSQL gerenciado pelo Supabase, a fonte única de verdade dos dados de frota e manutenção.
AI Architecture
Arquitetura de IA

Every request flows through five layers before reaching the database. The language model understands the request, but every operation is executed by deterministic backend services.

  • AI Reasoning — GPT-4 reads the text or voice message, classifies the intent, plans the steps, and emits structured calls through OpenAI function calling.
  • AI Orchestration — a tool registry dispatches each call to the right tool, while conversation memory feeds earlier context back into the model.
  • Available Tools — a typed surface for vehicles, maintenance, work orders, inspections, and reporting: the only actions the model is allowed to take.
  • Domain Layer — domain services apply authorization, tenant isolation, and business rules, so every request is scoped to the user’s company.
  • Persistence — Prisma writes to PostgreSQL with type-safe queries, the same store the dashboard reads from.

Toda requisição atravessa cinco camadas antes de chegar ao banco. O modelo de linguagem entende o pedido, mas cada operação é executada por serviços determinísticos do backend.

  • Raciocínio da IA — o GPT-4 lê a mensagem de texto ou voz, classifica a intenção, planeja os passos e emite chamadas estruturadas via function calling da OpenAI.
  • Orquestração da IA — um registro de ferramentas despacha cada chamada para a ferramenta certa, enquanto a memória da conversa devolve o contexto anterior ao modelo.
  • Ferramentas disponíveis — uma superfície tipada para veículos, manutenção, ordens de serviço, inspeções e relatórios: as únicas ações que o modelo pode tomar.
  • Camada de domínio — os serviços de domínio aplicam autorização, isolamento por tenant e regras de negócio, então toda requisição fica restrita à empresa do usuário.
  • Persistência — o Prisma escreve no PostgreSQL com queries tipadas, o mesmo banco que o painel lê.
Request lifecycle
Ciclo de vida da requisição

The diagrams above show the components, not the execution loop. Two forks decide what a message costs: known phrasings never reach the model at all, and the ones that do fork again on what the model asked for — a read stays inside a read-only transaction, a write goes through the REST API and Prisma.

Os diagramas acima mostram os componentes, não o laço de execução. Duas bifurcações definem quanto custa uma mensagem: frases já conhecidas nunca chegam ao modelo, e as que chegam se dividem de novo conforme o que o modelo pediu — uma leitura fica dentro de uma transação somente-leitura, uma escrita passa pela API REST e pelo Prisma.

  1. 01

    A WhatsApp text or voice note reaches the bot on Fly.io. Voice notes are transcribed by Whisper and re-enter as text.Uma mensagem de texto ou áudio do WhatsApp chega ao bot na Fly.io. Áudios são transcritos pelo Whisper e voltam como texto.

  2. 02

    The phone number resolves which user and company the message belongs to.O número de telefone resolve a qual usuário e empresa a mensagem pertence.

  3. 03

    Deterministic regex handlers run first — problem reports, fleet lists, order lists, the checkup flow. Only what they do not match reaches the model.Handlers determinísticos de regex rodam primeiro — relatos de problema, listas de frota, listas de ordens, o fluxo de checkup. Só o que eles não casam chega ao modelo.

    matched — the model is never calledcasou — o modelo nunca é chamado“hose problem, plate 1234” stops here: the issue handler parses the plate and description itself, resolves the truck, and creates the order through the same REST API, then confirms on WhatsApp.“problema na mangueira, placa 1234” para aqui: o handler de ocorrência extrai a placa e a descrição sozinho, resolve o caminhão, cria a ordem pela mesma API REST e confirma no WhatsApp.
    no match — everything below runsnão casou — tudo abaixo roda“how many open orders does truck ABC1234 have?”, “how many trucks are in the fleet?”, “open a work order for ABC1234, burst hose”. Free phrasing, questions, and anything the patterns were not written for.“quantas ordens abertas o caminhão ABC1234 tem?”, “quantos caminhões há na frota?”, “abre uma ordem para o ABC1234, mangueira estourada”. Frases livres, perguntas e tudo aquilo para o que os padrões não foram escritos.
  4. 04

    The message, session context, and a system prompt built from the Prisma schema go to OpenAI with three function definitions.A mensagem, o contexto da sessão e um system prompt montado a partir do schema do Prisma vão para a OpenAI junto das três definições de função.

Which function did the model call?

Qual função o modelo chamou?

read

leitura

  1. 05

    “how many open orders does truck ABC1234 have?”

    “quantas ordens abertas o caminhão ABC1234 tem?”

    run_report — the model sends a semantic plan (metric, period, filters) and the backend builds the SQL. The model never writes SQL here.o modelo envia um plano semântico (métrica, período, filtros) e o backend monta o SQL. Aqui o modelo nunca escreve SQL.

  2. 06

    “how many trucks are in the fleet?” → SELECT COUNT(*) FROM truck

    “quantos caminhões há na frota?” → SELECT COUNT(*) FROM truck

    run_sql — one SELECT, table allow-list, no SELECT *, company filter injected, automatic LIMIT 200.um SELECT, lista de tabelas permitidas, sem SELECT *, filtro de empresa injetado, LIMIT 200 automático.

  3. 07

    Runs in a READ ONLY transaction behind statement and lock timeouts, with an EXPLAIN cost gate. Nothing is written.Roda em uma transação READ ONLY protegida por timeouts de statement e de lock, com barreira de custo via EXPLAIN. Nada é escrito.

write

escrita

  1. 08

    “open a work order for ABC1234, burst hose”

    “abre uma ordem para o ABC1234, mangueira estourada”

    create_order — refused outright when the message reads like a question (“how many…”, “list…”, “show…”).recusada de saída quando a mensagem soa como pergunta (“quantos…”, “lista…”, “mostra…”).

  2. 09

    The plate is resolved to a truck id, then POST /api/trucks/:id/maintenance-items with the scoped token.A placa é resolvida para um id de caminhão e então vai um POST /api/trucks/:id/maintenance-items com o token restrito.

  3. 10

    Express validates the payload, Prisma writes, and PostgreSQL persists the maintenance order.O Express valida o payload, o Prisma escreve e o PostgreSQL persiste a ordem de manutenção.

  1. 11

    The function result is appended to the conversation and sent back to the model in a second call.O resultado da função é anexado à conversa e devolvido ao modelo numa segunda chamada.

  2. 12

    The model writes the reply and the bot sends it on WhatsApp. Managers see the same record in the dashboard, through the same API.O modelo escreve a resposta e o bot envia no WhatsApp. Os gestores veem o mesmo registro no painel, pela mesma API.

The model decides what to ask for, never how it runs: both branches end in the same deterministic services the web dashboard uses, and the reply is written only after the result comes back.

O modelo decide o que pedir, nunca como aquilo roda: os dois caminhos terminam nos mesmos serviços determinísticos que o painel web usa, e a resposta só é escrita depois que o resultado volta.

Function calling as the boundary
Function calling como fronteira

The model never gets a database connection or arbitrary backend code. It can call exactly three typed functions — one that reports, one that reads, one that writes:

O modelo nunca recebe conexão com o banco nem código arbitrário do backend. Ele pode chamar exatamente três funções tipadas — uma que gera relatório, uma que lê e uma que escreve:

run_report({
  metric: "orders"
  agg: "count" | "list"
  time: { preset: "today" | "this_week" | "last_7d"
                 | "this_month" | "all" | "custom" }
  group_by: "status" | "plate" | "author" | "day" | "week"
  filters: { plate?, status_in?, type?, search? }
  limit: 1..500
})

run_sql({
  query: string   // a single SELECT, nothing else
})

create_order({
  plate: string
  title: string
})

Preferring run_report is the point: the model sends a semantic plan — metric, period, filters — and the backend composes the SQL, so the common case never has the model writing queries at all. Whichever it picks, the backend still validates the arguments, resolves the company from the authenticated phone number, injects the tenant filter, and executes.

Preferir o run_report é justamente o ponto: o modelo envia um plano semântico — métrica, período, filtros — e o backend monta o SQL, de modo que no caso comum o modelo nunca escreve query nenhuma. Escolha o que escolher, o backend ainda valida os argumentos, resolve a empresa a partir do telefone autenticado, injeta o filtro de tenant e executa.

Guardrails
Garantias

Guardrails are the limits that define what an AI feature is able to do, separately from what the model decides to ask for. They answer three questions: what can be called, what can be read, and what can be written — and they are enforced by the application, so the answers stay the same whatever arrives in the conversation.

These are the ones I built into CIME, and what each of them prevents.

  • Tool allow-list — three functions, nothing else. An unknown function name is an error, not an attempt.
  • Typed schemas — parameters are JSON-schema constrained (enums for periods and groupings, 1–500 on limits) and validated before anything runs.
  • Trusted tenant context — the company comes from a phone-number lookup on the authenticated identity, never from the model, and is injected into the query as a filter rather than being asked for.
  • Reads are SELECT-only — writes, DDL, and multi-statement queries are rejected outright; so are SELECT * and any table outside a four-table allow-list on the public schema.
  • Read-only transactions — analytical queries run inside BEGIN READ ONLY with statement, lock, and idle timeouts, so a read cannot mutate or block production data.
  • Cost gate — every query is run through EXPLAIN first and refused if the plan is too expensive; non-count queries get an automatic LIMIT.
  • Intent guard on writes — the create path is refused when the message reads like a question, so “how many orders are open?” can never open one.
  • Writes go through the API — never straight to the database: the REST endpoint, its Express validation, and Prisma are the only way in, exactly as the dashboard uses them.
  • Bounded responses — the whole model path is capped by a timeout and failures return a controlled message instead of hanging the conversation.

Garantias são os limites que definem o que um recurso de IA consegue fazer, separadamente do que o modelo decide pedir. Elas respondem a três perguntas: o que pode ser chamado, o que pode ser lido e o que pode ser escrito — e são impostas pela aplicação, então as respostas continuam as mesmas independentemente do que chegue na conversa.

Estas são as que eu construí no CIME, e o que cada uma evita.

  • Lista de ferramentas permitidas — três funções, mais nada. Um nome de função desconhecido é um erro, não uma tentativa.
  • Schemas tipados — os parâmetros são restritos por JSON Schema (enums para períodos e agrupamentos, 1–500 nos limites) e validados antes de qualquer execução.
  • Contexto de tenant confiável — a empresa vem de uma consulta pelo telefone da identidade autenticada, nunca do modelo, e é injetada na query como filtro em vez de ser pedida.
  • Leituras são só SELECT — escritas, DDL e queries com múltiplos comandos são rejeitadas de saída; o mesmo vale para SELECT * e qualquer tabela fora de uma lista de quatro tabelas permitidas no schema público.
  • Transações somente-leitura — consultas analíticas rodam dentro de BEGIN READ ONLY com timeouts de statement, de lock e de ociosidade, então uma leitura não consegue alterar nem travar dados de produção.
  • Barreira de custo — toda query passa antes por EXPLAIN e é recusada se o plano for caro demais; consultas que não são contagem ganham um LIMIT automático.
  • Guarda de intenção nas escritas — o caminho de criação é recusado quando a mensagem soa como pergunta, então “quantas ordens estão abertas?” nunca abre uma.
  • Escritas passam pela API — nunca direto no banco: o endpoint REST, a validação no Express e o Prisma são a única via, do mesmo jeito que o painel usa.
  • Respostas limitadas — todo o caminho do modelo é limitado por timeout, e falhas retornam uma mensagem controlada em vez de travar a conversa.