Este é o documento do backend do projeto MIH em Django. Ele serve como guia para manutenção, onboarding e evolução do sistema.
O backend foi migrado de uma base anterior em FastAPI para Django + Django REST Framework (DRF), com os seguintes objetivos:
- padronizar a stack de backend
- manter compatibilidade com o frontend
- usar PostgreSQL como banco principal
- suportar autenticação por sessão e JWT
- consolidar persistência clínica em modelo OMOP
- Python 3.11+
- Django 6.x
- Django REST Framework
- PostgreSQL (
psycopg2-binary) social-auth-app-django(Google OAuth)djangorestframework-simplejwt(JWT)django-cors-headers
server_mih/manage.py: entrada principal de execuçãoserver_mih/server_mih/: settings, urls, wsgi, asgimih/: app principal com modelos, serializers, views e testes
As entidades clínicas legadas (Patient, Mih, TrackingRecord) não são mais persistidas como tabelas próprias. O backend é OMOP para dados clínicos, mantendo compatibilidade de contratos HTTP por meio de camada de mapeamento nas views.
- Python 3.11+
- Docker + Docker Compose
pipevenv
- Entrar na pasta do backend:
cd server-mih-dev-migration/server-django- Criar e ativar ambiente virtual:
python -m venv .venv
source .venv/bin/activate- Instalar dependências:
pip install -r requirements.txt- Subir a infraestrutura via Docker Compose:
cd dbms
export MINIO_ACCESS_KEY=minioadmin
export MINIO_SECRET_KEY=minioadmin123
docker compose -f docker-compose-model.yml up -d
docker compose -f docker-compose-model.yml ps
cd ..- Definir variáveis de ambiente do backend:
export DB_NAME=postgres
export DB_USER=postgres
export DB_PASSWORD=postgres
export DB_HOST=127.0.0.1
export DB_PORT=5432
export SECRET_KEY='trocar-em-producao'
export ALLOWED_HOSTS='localhost,127.0.0.1,0.0.0.0'- Executar migrations:
python server_mih/manage.py migrate- Subir servidor de desenvolvimento:
python server_mih/manage.py runserverServidor padrão: http://127.0.0.1:8000
cd server-mih-dev-migration/server-django
source .venv/bin/activate
cd dbms
export MINIO_ACCESS_KEY=minioadmin MINIO_SECRET_KEY=minioadmin123
docker compose -f docker-compose-model.yml up -d
cd ..
export DB_NAME=postgres DB_USER=postgres DB_PASSWORD=postgres DB_HOST=127.0.0.1 DB_PORT=5432 SECRET_KEY='trocar-em-producao' ALLOWED_HOSTS='localhost,127.0.0.1,0.0.0.0'
python server_mih/manage.py runservercd server-mih-dev-migration/server-django
source .venv/bin/activate
export DB_NAME=postgres DB_USER=postgres DB_PASSWORD=postgres DB_HOST=127.0.0.1 DB_PORT=5432 SECRET_KEY='trocar-em-producao' ALLOWED_HOSTS='localhost,127.0.0.1,0.0.0.0'
python server_mih/manage.py test --verbosity=2Cobertura atual inclui:
- modelos OMOP base
- autenticação (sessão → JWT,
/user/me/,/users/) - upload de imagem via multipart
dbms/docker-compose-model.yml
Esse compose sobe:
db: PostgreSQL 16 (porta5432)minio: armazenamento S3 compatível (API9000, console9001)
Pré-requisito para o MinIO (no shell atual):
export MINIO_ACCESS_KEY=minioadmin
export MINIO_SECRET_KEY=minioadmin123Subir infraestrutura:
cd server-mih-dev-migration/server-django/dbms
docker compose -f docker-compose-model.yml up -d
docker compose -f docker-compose-model.yml psParar infraestrutura:
docker compose -f docker-compose-model.yml downObservações importantes:
- os volumes são bind mounts para
../../volumes/pg-data,../../volumes/pg-impexpe../../volumes/minio - ajuste permissões/pastas se estiver em outro ambiente
- se já houver PostgreSQL local usando
5432, ajuste a porta no compose para evitar conflito
No backend Django desta branch, o upload em POST /api/images/ persiste arquivo no MinIO (bucket definido em MINIO_IMAGES_BUCKET) e retorna {"id": ...}.
Para consumo da imagem pelo frontend, utilize:
GET /api/images/{id}/content/
O arquivo binário é servido pelo endpoint GET /api/images/{id}/content/ a partir do objeto armazenado no MinIO.
Base local padrão: http://127.0.0.1:8000
GET /user/me/— retorna usuário autenticado (compatibilidade com frontend legado).PUT /users/— cria/atualiza dados de perfil do usuário autenticado.GET /api/auth/user/— retorna usuário autenticado (rota API equivalente ao/user/me/).POST /api/auth/token/— converte sessão autenticada em token JWT.
As rotas são expostas por social_django sob o prefixo /auth/:
GET /auth/login/google-oauth2/— inicia fluxo de login Google.GET /auth/complete/google-oauth2/— callback do provedor após autenticação.GET /auth/logout/— encerra sessão social/autenticada.
Os recursos abaixo seguem padrão de ViewSet REST (list, retrieve, create, update, destroy):
-
GET /api/patients/— lista pacientes -
GET /api/patients/{id}/— detalha paciente -
POST /api/patients/— cria paciente -
PUT /api/patients/{id}/— atualiza paciente -
DELETE /api/patients/{id}/— remove paciente -
GET /api/mih/— lista casos MIH -
GET /api/mih/{id}/— detalha caso MIH -
POST /api/mih/— cria caso MIH -
PUT /api/mih/{id}/— atualiza caso MIH -
DELETE /api/mih/{id}/— remove caso MIH -
GET /api/tracking-records/— lista acompanhamentos -
GET /api/tracking-records/{id}/— detalha acompanhamento -
POST /api/tracking-records/— cria acompanhamento -
PUT /api/tracking-records/{id}/— atualiza acompanhamento -
DELETE /api/tracking-records/{id}/— remove acompanhamento -
GET /api/images/— lista imagens -
GET /api/images/{id}/— detalha imagem -
GET /api/images/{id}/content/— retorna o arquivo da imagem porid -
POST /api/images/— upload de imagem (multipart/form-data, campofile) -
PUT /api/images/{id}/— atualiza metadados de imagem -
DELETE /api/images/{id}/— remove imagem
GET /admin/— painel administrativo Django.
POST /api/images/ com multipart/form-data.
Exemplo:
curl -X POST -F "file=@photo.jpg" \
-b "sessionid=<SESSION_COOKIE>" \
http://127.0.0.1:8000/api/images/Resposta esperada (201):
{"id": 1}Armazenamento: MinIO (MINIO_IMAGES_BUCKET), com chave de objeto persistida na tabela Image.
O backend persiste os dados clínicos nas estruturas OMOP Person, ConditionOccurrence, Observation, Measurement, VisitOccurrence e FactRelationship, com a tradução de payloads concentrada em mih/views.py.
Para paciente, Person representa identidade clínica base (id, person_source_value, year_of_birth, month_of_birth, day_of_birth, birth_datetime, gender_concept_id). Os indicadores clínicos (highFever, premature, deliveryProblems, lowWeight) são gravados em Observation com observation_concept_id clínico e value_as_concept_id (YES/NO). Campos categóricos (deliveryType, consultType) usam Observation com conceito da pergunta em observation_concept_id e conceito da resposta em value_as_concept_id. brothersNumber é persistido em Observation.value_as_number.
Para MIH, o registro é representado como episódio clínico em ConditionOccurrence com condition_concept_id (Molar incisor hypomineralization). As datas de início e fim do registro são persistidas em condition_start_date e condition_end_date. A intensidade de dor (painLevel) é mapeada para Measurement.value_as_number com measurement_concept_id (escala numérica de dor 0–10). Sensibilidade e mancha são representadas em Observation.value_as_concept_id usando os conceitos clínicos 4247583 e 440758, respectivamente, com resposta booleana codificada (YES/NO). O campo de desconforto estético permanece com conceito local de observação por ausência de um único concept_id OMOP padrão para essa pergunta específica. Notas do responsável, notas do especialista e diagnóstico seguem em Observation.value_as_string, e as referências de imagem (photo_id1, photo_id2, photo_id3) seguem em Observation.value_as_number com conceito local de aplicação.
Para acompanhamento (tracking-records), o texto é gravado em Observation (observation_concept_id), image_id em value_as_number e o vínculo com o caso MIH é representado por FactRelationship.
O padrão adotado para IDs foi:
- priorizar IDs OMOP oficiais (OHDSI Athena) quando há conceito clínico claro e estável;
- separar conceito da pergunta e conceito da resposta em campos categóricos;
- usar conceitos custom locais somente quando não há um único conceito OMOP padrão para a pergunta de negócio.
Fonte principal dos IDs oficiais: OHDSI Athena (https://athena.ohdsi.org).
Mapeamento adotado no backend (mih/views.py):
highFever→44810013(Observation)premature→4272248(Observation)deliveryProblems→43530950(Observation)lowWeight→4171115(Observation)deliveryType(conceito-pergunta) →4145318(Observation)brothersNumber(conceito-pergunta) →4072485(Observation)mih(condição principal) →44783854(ConditionOccurrence)painLevel(escala 0–10) →43055141(Measurement)sensitivityField→4247583(Observation)stain→440758(Observation)YES/NO(booleanos) →4188539/4188540(value_as_concept_id)
Valores categóricos com IDs oficiais utilizados:
deliveryType=cesarean→4015701deliveryType=normal→4125611consultType=public→44804377consultType=private→44803901
Conceitos locais (IDs técnicos da aplicação) usados no backend:
deliveryProblemsTypes(conceito-pergunta) →910006consultType(conceito-pergunta) →910007tracking-recordstexto livre (Observation) →920002userObservations(notas do responsável) →920003specialistObservations(notas do especialista) →920004diagnosis(texto de diagnóstico) →920005aestheticDiscomfort(conceito-pergunta de MIH) →920008photo_id1(referência de mídia) →920009photo_id2(referência de mídia) →920010photo_id3(referência de mídia) →920011
Conceitos que permanecem custom locais por ausência de ID OMOP único para a pergunta de negócio:
deliveryProblemsTypes(910006)consultType(910007)aestheticDiscomfort(920008)userObservations,specialistObservationsediagnosis(920003,920004,920005)photo_id1,photo_id2,photo_id3(920009,920010,920011)tracking-recordstexto livre (920002)
Observação importante: quando houver atualização oficial do dicionário de mapeamento do MolarCheck com novos concept_id, os valores locais custom devem ser substituídos e versionados em migration dedicada.
UserProfile: perfil de usuário, role e dados de autorizaçãoImage: arquivo enviado pelo usuário e metadados de uploadPatientNonClinicalInfos: metadados não clínicos do paciente para suporte de contrato da APIProviderNonClinicalInfos: metadados não clínicos do especialista para suporte de autorização e contato
Esses modelos continuam fora do OMOP por serem dados de infraestrutura/aplicação, não eventos clínicos padronizados.