Do dataset ao dashboard: construindo uma pipeline de Machine Learning ponta a ponta

python
machine learning
data engineering
dashboard
Uma pipeline de Machine Learning para previsão de demanda de bicicletas.
Author

Daniela Carvalho

Published

August 31, 2026

No post anterior, apresentei o laboratório de previsão de demanda de bicicletas como um ecossistema: diferentes componentes tecnológicos que, quando articulados, transformam dados brutos em informação disponível para consumo por outros sistemas e por pessoas.

Neste post, o objetivo é percorrer tecnicamente essa arquitetura, mostrando como cada componente foi incorporado ao projeto.

A implementação seguiu aproximadamente este fluxo:

Bike Sharing Dataset
        ↓
Ingestão
        ↓
PostgreSQL
        ↓
Feature Engineering
        ↓
Dataset de treinamento
        ↓
Treinamento e avaliação
        ↓
Modelo persistido
        ↓
Prediction
        ├── FastAPI
        └── previsões em lote
                ↓
        PostgreSQL
                ↓
        Vizro Dashboard

1. Estrutura do projeto e arquitetura

Antes da implementação da pipeline, foi criada uma estrutura modular para separar as responsabilidades do sistema.

O projeto foi inicializado com o uv, utilizado para gerenciamento do ambiente Python e das dependências:

uv init

As dependências foram incorporadas progressivamente à medida que os componentes passaram a ser desenvolvidos.

A arquitetura adotada separou responsabilidades em módulos como:

src/
├── api/
├── common/
├── dashboard/
├── features/
├── ingestion/
├── monitoring/
├── prediction/
└── training/

tests/
data/
models/

Os arquivos __init__.py foram adicionados aos pacotes Python.

A intenção não era simplesmente organizar arquivos visualmente. A divisão estabelecia limites entre as responsabilidades da aplicação: ingestão não deveria conhecer a interface do dashboard; o dashboard não deveria ser responsável por treinar o modelo; e a API deveria reutilizar o componente de predição em vez de implementar novamente a inferência.

Essa estrutura também permitiu que cada incremento fosse testado antes da implementação da etapa seguinte.


2. Ingestão de dados com SQLAlchemy e PostgreSQL

O estudo de caso utilizou o Bike Sharing Dataset, disponibilizado pelo UCI Machine Learning Repository.

A primeira etapa da pipeline consistiu em carregar os dados originais e verificar sua integridade antes de persistir qualquer informação.

Foram criados testes para validar características importantes do dataset, incluindo:

  • carregamento do arquivo;
  • quantidade esperada de colunas;
  • unicidade do identificador instant;
  • consistência entre cnt e seus componentes.

O teste isolado dessa camada foi executado com:

uv run pytest tests/test_raw_dataset.py

Os quatro testes foram aprovados.

Com a estrutura dos dados validada, o próximo componente foi o PostgreSQL.

A conexão foi centralizada no módulo common, utilizando SQLAlchemy e o driver Psycopg:

postgresql+psycopg://...

As informações sensíveis e específicas do ambiente foram retiradas do código e armazenadas em variáveis de ambiente no arquivo .env.

A partir dessas variáveis, a aplicação passou a construir a DATABASE_URL e criar o engine utilizado pelos demais módulos.

A ingestão foi então implementada no módulo correspondente, persistindo o dataset bruto no PostgreSQL.

Essa decisão criou uma separação importante:

Arquivo original
        ↓
Validação
        ↓
Raw data no PostgreSQL

O dado bruto passou a funcionar como a fonte persistida para as transformações posteriores, evitando que cada etapa da aplicação dependesse diretamente do arquivo original.

Uma dificuldade encontrada

Ao executar toda a suíte:

uv run pytest

os testes relacionados ao dataset passaram, mas os testes de conexão e ingestão falharam.

A mensagem indicava:

connection refused
localhost:5432

O problema não estava no SQLAlchemy nem no código de ingestão: o PostgreSQL simplesmente não estava disponível naquele momento.

Esse problema acabou reforçando a necessidade de controlar também a infraestrutura da aplicação, algo que posteriormente seria resolvido com Docker Compose.


3. Feature Engineering

Com os dados brutos persistidos, a etapa seguinte foi construir uma representação apropriada para o modelo.

Foi criado um módulo específico de features.

A ideia foi manter duas camadas distintas no banco:

Raw Dataset
      ↓
Feature Engineering
      ↓
Training Dataset

Assim, os dados originais permanecem preservados enquanto outra representação é construída especificamente para Machine Learning.

Entre as informações utilizadas pelo modelo estavam características temporais e meteorológicas presentes no Bike Sharing Dataset, como hora, dia, mês, estação, feriado, temperatura e umidade.

Além das features, foi definido o atributo alvo correspondente à demanda de bicicletas.

Essa separação também evitou colocar transformações diretamente dentro do treinamento. O módulo de treinamento passou a receber um dataset já preparado para aquela finalidade.


4. Treinamento e avaliação do modelo

Com o dataset de treinamento disponível, iniciou-se a camada de Machine Learning.

Uma decisão importante foi respeitar a natureza temporal do problema.

Em vez de embaralhar aleatoriamente as observações, os dados foram separados temporalmente:

Período anterior
      ↓
Treinamento

Período posterior
      ↓
Teste

Isso permite avaliar uma situação mais próxima do uso real: utilizar o passado para estimar um período posterior.

Também foi utilizado um modelo baseline para estabelecer uma referência mínima de desempenho.

O modelo principal escolhido foi:

HistGradientBoostingRegressor

A avaliação utilizou três métricas:

MAE — Mean Absolute Error

Representa, em média, quantas bicicletas separam a previsão do valor observado.

RMSE — Root Mean Squared Error

Também representa erro em quantidade de bicicletas, mas penaliza erros maiores com mais intensidade.

R² — Coeficiente de determinação

Indica quanto da variabilidade observada é explicada pelo modelo.

No experimento realizado, o modelo chegou aproximadamente a:

MAE:   40,61 bicicletas
RMSE:  62,93 bicicletas
R²:    91,85%

O modelo treinado foi então persistido utilizando joblib.

Training Dataset
        ↓
HistGradientBoostingRegressor
        ↓
Avaliação
        ↓
.joblib

A partir desse momento, não era mais necessário treinar novamente o modelo para produzir cada previsão.


5. Prediction e API REST com FastAPI

O passo seguinte foi separar treinamento de inferência.

Foi criado um módulo de prediction responsável por carregar o modelo persistido e produzir previsões.

Essa separação permitiu que diferentes interfaces utilizassem o mesmo modelo.

Uma dessas interfaces foi uma API construída com FastAPI.

Conceitualmente:

Cliente
   ↓
POST /predict
   ↓
Pydantic
   ↓
Prediction
   ↓
Modelo .joblib
   ↓
Previsão
   ↓
JSON

O Pydantic foi utilizado para definir e validar a estrutura esperada na entrada da API.

O FastAPI também disponibilizou automaticamente a documentação interativa Swagger, permitindo inspecionar e testar os endpoints pelo navegador.

Assim, o modelo deixou de existir apenas como um objeto Python e passou a oferecer uma interface de comunicação para outros sistemas.


6. Testes automatizados com Pytest

Os testes não foram acrescentados apenas no final do projeto.

Eles começaram ainda na configuração do PostgreSQL e acompanharam a evolução da pipeline.

O Pytest foi adicionado como dependência de desenvolvimento e configurado para localizar o projeto e os testes corretamente.

A configuração utilizada incluiu:

[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["."]

O laboratório passou a possuir testes para diferentes responsabilidades, entre elas:

configuração
dataset bruto
conexão com banco
ingestão
pipeline
API

A execução completa era realizada com:

uv run pytest

Esse ponto foi especialmente importante porque os testes não verificaram apenas algoritmos.

Eles também permitiram identificar problemas de infraestrutura. Foi justamente um teste que evidenciou que o PostgreSQL não estava disponível durante uma das primeiras execuções.

Dessa forma, os testes passaram a funcionar como mecanismo de confiança sobre diferentes partes do ecossistema.


7. Docker Compose e ambiente reproduzível

À medida que a solução cresceu, passou a existir um problema diferente.

Já não bastava executar um script Python.

O laboratório precisava coordenar:

PostgreSQL
FastAPI
Vizro

O PostgreSQL inicialmente precisava estar disponível separadamente para que os testes e a ingestão funcionassem.

Para controlar essa infraestrutura foi adotado Docker Compose.

O compose.yaml passou a definir os serviços do laboratório.

O PostgreSQL foi configurado utilizando variáveis de ambiente:

environment:
  POSTGRES_DB: ${DATABASE_NAME}
  POSTGRES_USER: ${DATABASE_USER}
  POSTGRES_PASSWORD: ${DATABASE_PASSWORD}

Foi utilizado um volume persistente:

volumes:
  - bike_postgres_data:/var/lib/postgresql/data

Também foi criado um healthcheck com pg_isready.

Isso foi particularmente importante porque a API não deveria simplesmente iniciar alguns segundos depois do banco. Ela deveria iniciar quando o PostgreSQL estivesse efetivamente pronto para receber conexões.

A dependência passou a ser expressa como:

depends_on:
  postgres:
    condition: service_healthy

Posteriormente, API e dashboard foram incorporados ao mesmo ambiente.

Assim, o laboratório passou a ser iniciado por:

docker compose up -d

e encerrado com:

docker compose down

O Docker Compose transformou aquilo que antes era uma sequência manual de inicializações em uma descrição reproduzível do ecossistema.


8. Previsões, persistência e dashboard com Vizro

A última etapa foi tornar o comportamento do modelo observável.

Para isso, não bastava manter a inferência disponível somente pela API.

Foi criado um processo para gerar previsões sobre o conjunto temporal reservado para teste.

O módulo carregava os dados, realizava o mesmo temporal_train_test_split, carregava o modelo persistido e produzia uma estrutura contendo:

timestamp
actual
predicted
absolute_error

Essas informações foram persistidas em uma tabela PostgreSQL:

bike_demand_predictions

A partir dela, o Vizro passou a consultar os resultados e apresentá-los visualmente.

O dashboard incorporou indicadores de desempenho, entre eles:

Erro Médio (MAE)
40,61 bicicletas

Erro Quadrático (RMSE)
62,93 bicicletas

Precisão do Modelo (R²)
91,85%

Além dos indicadores, a visualização permitiu observar a relação entre demanda real e prevista ao longo do período.

O dashboard, portanto, não executa o treinamento.

Ele ocupa outra posição na arquitetura:

Modelo
   ↓
Predictions
   ↓
PostgreSQL
   ↓
Vizro
   ↓
Interpretação

Essa distinção é importante porque separa produção de informação de sua visualização.


O ecossistema completo

Ao final do laboratório, aquilo que inicialmente era apenas um dataset passou a constituir um pequeno sistema de Machine Learning:

                Bike Sharing Dataset
                         ↓
                    Ingestion
                         ↓
                    PostgreSQL
                         ↓
                Feature Engineering
                         ↓
                 Training Dataset
                         ↓
                  Model Training
                         ↓
                      joblib
                    ↙       ↘
             FastAPI       Batch Prediction
                                ↓
                           PostgreSQL
                                ↓
                              Vizro

Cada componente possui uma responsabilidade específica, mas nenhum deles explica sozinho o funcionamento do sistema.

O modelo não possui utilidade operacional sem dados adequadamente preparados. A API não produz previsões sem o modelo. O dashboard não apresenta resultados sem as previsões persistidas. E todos esses componentes dependem de uma infraestrutura capaz de mantê-los disponíveis e integrados.

É nesse sentido que retorno à ideia apresentada no post anterior: o objeto computacional não está apenas nos componentes, mas também nas relações estabelecidas entre eles.

O resultado técnico deste laboratório não foi somente um modelo capaz de prever demanda de bicicletas. Foi a construção de um fluxo reproduzível que transforma dados brutos em informação disponível para interpretação.

Visite o repositório do labs, baixe e execute o projeto para reproduzir o fluxo completo de Machine Learning.

Reproduzindo o laboratório

O código-fonte completo deste laboratório está disponível no repositório do projeto no GitHub. Disponível em: https://github.com/danicarvalhomf/python-especialista/tree/main/labs/lab-bike-demand-mlops

Para reproduzir o ambiente e executar o fluxo completo de Machine Learning em sua máquina, consulte o README.md do laboratório, que contém os requisitos, a configuração das variáveis de ambiente e os comandos necessários para inicialização dos serviços e execução da pipeline.

O projeto foi estruturado para permitir a reprodução do ecossistema por meio do Docker Compose, incluindo PostgreSQL, API FastAPI e dashboard Vizro.