HayaDev
APIConcluído

Skinner API (Django)

Backend em Django REST Framework e PostgreSQL de uma plataforma SaaS multi-tenant para clínicas de ABA, com jogos de biofeedback e relatórios em PDF. Desenvolvido na Self.

  • Python
  • Django
  • Django REST Framework
  • PostgreSQL
  • AWS Lambda
  • Zappa
  • ReportLab
  • Matplotlib

Projeto sob confidencialidade. Esta página descreve apenas a natureza técnica do trabalho, sem detalhes internos, dados ou código proprietário.

A Skinner API (Django) é a primeira geração do backend do Skinner, uma plataforma SaaS para clínicas de ABA com jogos de biofeedback. É um monólito em Django REST Framework e PostgreSQL que gerencia clínicas, pacientes, equipes, o ciclo de treinos e sessões e relatórios em PDF. Foi desenvolvida na Self e evoluída de 2023 a 2026 — a partir de maio de 2023 o banco de dados e a estrutura de apps foram refeitos —, e está concluída: as reescritas em NestJS, o projeto Skinner e a KeepGames API, descritos em outras páginas, a substituem.

O que segue são as decisões técnicas por trás dela e o que ela ensinou.

Um monólito por domínio, isolado por clínica

São 27 apps Django, cada um com a mesma anatomia — models, views, serializers, rotas e permissões —, e o isolamento entre clínicas é a regra que organiza o resto. Toda entidade pertence a uma clínica:

  • A clínica vem do usuário autenticado, nunca do corpo da requisição, e é atribuída pelo servidor na criação.
  • Chaves estrangeiras recebidas num payload — paciente, treino, aplicador — são validadas como pertencentes à mesma clínica.
  • Dados de referência globais são somente leitura, e os dados “do sistema”, sem clínica, podem ser lidos, mas não alterados.
  • Um checklist de isolamento, documentado no repositório, vale para todo endpoint novo ou alterado.
  • Os papéis são grupos do Django, com classes de permissão sobre as permissões de modelo e uma checagem de posse do objeto pela clínica.

A autenticação segue o mesmo cuidado: tokens próprios, um por dispositivo e com validade, e login por e-mail ou apelido sem diferenciar maiúsculas. Os jogos têm uma autenticação separada — uma chave de sessão que eles trocam pelo contexto —, e esse padrão nasceu aqui e foi mantido nas reescritas.

Consultas que só pagam quando precisam

As regras do projeto exigem select_related e prefetch_related contra consultas N+1, com uma nuance do DRF: get_queryset() também é chamado em PATCH e DELETE, para achar o objeto, então carregar relacionamentos ali faz a escrita pagar por joins que não usa. A otimização vale só para a leitura, como nas views de usuários e pacientes:

class ReportDetail(generics.RetrieveUpdateDestroyAPIView):
    def get_queryset(self):
        queryset = Report.objects.all()
        # PATCH e DELETE também passam por aqui, e não precisam dos joins.
        if self.request.method == 'GET':
            queryset = queryset.select_related('owner').prefetch_related('tags')
        return queryset

Serializers simples e reutilizáveis, um por app, evitam a duplicação e os imports circulares entre serializers.

Relatórios em PDF, gerados no servidor

Os relatórios das partidas com sensor são um pipeline dentro da própria API. O sinal bruto de frequência cardíaca é processado, com filtro de artefatos e RMSSD em janela deslizante, e os gráficos do Matplotlib são gerados em paralelo. O PDF, montado com ReportLab com a identidade visual da clínica, vai para o S3, e o relatório é registrado com update_or_create sobre o paciente, o jogo e o momento da partida, então gerar de novo não duplica nada. Há uma variante para partidas sem sensor. O deploy é em AWS Lambda, com o Zappa, um manipulador enxuto e bastante memória.

Uma agenda que nunca foi ao ar

Existe uma agenda no repositório — disponibilidade do aplicador, sessões, recorrências, indisponibilidades e detecção de conflitos entre o aplicador e o paciente —, mas ela foi acrescentada por cima do modelo de dados existente e nunca chegou à produção nem foi validada em uso real. Com o banco de dados já complexo demais, a decisão foi não insistir nela: o novo Skinner foi construído do zero, já com a agenda no desenho.

Um assistente de IA com escopo de clínica

Existe um assistente de IA para a gestão da clínica, que responde consultando os dados dela por meio de “ferramentas”. O ponto de segurança é o mesmo do resto do sistema: a clínica passada às ferramentas vem do usuário autenticado, e não do que o modelo devolve. O protocolo de chamada de ferramentas foi implementado à mão, sobre o texto da resposta.

O que ela ensinou

Três anos de domínio real deixaram lições concretas, e cada uma virou uma decisão nas reescritas:

  • Regra de domínio na camada de transporte. A progressão de um treino, de aprendizado para manutenção, acontece dentro do método de validação de um serializer, com escritas no banco, e a disponibilidade de tarefas vive num filtro de view. Funciona, mas é difícil de testar e de reutilizar. Nas reescritas, as decisões são funções puras, e a gravação acontece numa transação.
  • Uma fase modelada como objeto clonado. Promover um treino de fase criava outro treino, o que fragmentava o histórico. Nas reescritas, a fase é um estado da tarefa, com um evento a cada transição.
  • Uma tabela por jogo. Cada jogo tinha o seu modelo. Nas reescritas, um par de tabelas serve a todos, com JSON para o que é específico de cada um.
  • Recorrência como lista de dias. Na agenda que não foi ao ar, a recorrência é um array de dias da semana, expandido na leitura num único método de cerca de 240 linhas, com três numerações de dia da semana convivendo no código. No novo Skinner, a recorrência é uma regra RFC 5545, expandida sob demanda, com uma convenção só.
  • Testes concentrados. Os testes automatizados se concentram em usuários e pacientes: 142 métodos de integração sobre a API. Os demais apps não têm cobertura, e a agenda, que nunca chegou à produção, tem testes escritos, mas nunca foi validada em uso real. As reescritas nasceram com testes unitários e e2e por módulo.

Qualidade e operação

São 27 apps, 72 models, 240 migrations e cerca de 31 mil linhas de Python, sem contar as migrations. A documentação da API sai do código, com OpenAPI pelo drf-spectacular, em Swagger e ReDoc, e cada domínio complexo — cálculo de variabilidade cardíaca e API de jogos — tem um documento próprio no repositório. O código é formatado com Black. A API roda em AWS Lambda com o Zappa, em Python 3.11, com PostgreSQL, S3 para arquivos e SES para e-mail, e o repositório também traz a configuração para o Elastic Beanstalk.

O que este projeto demonstra

Do ponto de vista de engenharia, o interesse aqui é a trajetória: um monólito Django que acumulou três anos de domínio real — multi-tenant, jogos com telemetria e relatórios em PDF — e cujas lições, regra fora da camada de transporte, fatos imutáveis, ocorrências virtuais e testes, orientaram as reescritas.

O projeto está concluído.

Voltar para os projetos