O MedPulse é um painel de TV em espaço público: a tela é lida por quem passa, e o navegador pode ser uma smart TV compartilhada. O projeto trata isso como requisito de arquitetura, não como configuração.
O navegador recebe agregados, não registros. A única projeção de atendimento que atravessa a borda — REST, snapshot do socket e eventos — é definida por CAMPOS_PAINEL:
# services/realtime.py
CAMPOS_PAINEL = ("id", "hospital_id", "status", "prioridade",
"hora_inicio", "hora_final", "atualizado_em")paciente, protocolo, setor_id e qualquer outro campo de identificação ficam no servidor. O que a tela mostra no lugar de dados individuais é distribuição: faixas de tempo de espera, contagens por prioridade e por etapa.
Isso é uma garantia de serialização, não de CSS. Ocultar um campo que foi enviado continua sendo enviar o campo; para um dado pessoal aparecer na tela, alguém precisa alterar o backend — e os testes de tests/test_realtime.py cobrem justamente a ausência desses campos no payload.
Escopo de tenant aplicado na consulta. Um local é identificado pelo token UUID na URL; _restringir_ao_local() (routes/api.py) retorna 404 para local inexistente e 403 quando a consulta alcança um hospital de outro tenant. Vale para /resumo, /atendimentos e /hospitais, então não existe caminho de leitura transversal por parâmetro.
Frontend não guarda estado de paciente. static/js/state.js armazena indicadores, vocabulário e contadores; nenhum objeto de paciente existe no cliente, e a seleção persistida em localStorage é só a lista de ids de hospital por local.
Dados clínicos não existem no projeto. O campo paciente é um rótulo de exibição (até 120 caracteres) e não deve receber nome real; no ambiente de demonstração ele é sempre fictício, sorteado de listas fixas.
O gerador escreve exclusivamente em hospitais com demo=True e revalida a flag a cada ciclo, levantando ShowcaseIsolamento antes de persistir fora do ambiente demonstrativo. SHOWCASE_HOSPITAIS permite allowlist por id e SHOWCASE_MAX_ABERTOS limita o volume. Detalhes em docs/DEMO.md.
| Superfície | Controle | Risco se mal configurado |
|---|---|---|
POST /api/webhook/atendimentos |
X-Webhook-Token quando WEBHOOK_TOKEN está definido; lote ≤ 500; idempotência por protocolo |
Escrita indevida de atendimentos — por isso o token é obrigatório na prática em qualquer implantação |
| Token UUID do local na URL | Escopo de leitura por tenant | É um identificador, não autenticação: quem o conhece vê os agregados daquele local |
GET /api/* |
Escopo por local/hospitais, projeção agregada |
Sem token de local, as unidades ativas são visíveis entre si |
| Socket.IO | Sala hospital:<id>, projeção CAMPOS_PAINEL |
cors_allowed_origins="*" aceita origem de qualquer página |
Não há autenticação de operadores nem RBAC: o painel é de monitoramento, não de operação. Controle de acesso por local (token assinado com expiração) e papéis estão listados como próximo passo — enquanto isso, a proteção real é de rede: o painel deve ser alcançável apenas pela rede do hospital.
Antes de pendurar na parede:
- Defina
WEBHOOK_TOKEN(nunca deixe vazio fora de desenvolvimento) eSECRETgerado, não o fallbackdev-secret-informe-no-env. - Restrinja
CORSà origem real em vez de*. - Sirva
wsgi.pypor um WSGI server (gunicorn -k gthread -w 1 --threads 8 wsgi:app) comDEBUG=False; multi-worker exige message queue (Redis) para o Socket.IO. - Banco com usuário de privilégios mínimos,
DB_URIsó no.env, migrações aditivas. SHOWCASE_ENABLED=falseem produção — dado sintético não pertence a um hospital real.TIMEZONEno fuso IANA da operação (é a referência do relógio exibido).- HTTPS/reverse proxy na borda e acesso à URL do painel limitado à rede interna; o token do local na URL é segredo de fato.
- Sem CDN: os assets de terceiros (
static/vendor/) são vendorizados, então uma TV em rede isolada não puxa nada de fora.
.env está no .gitignore; .env.example só contém nomes e valores ilustrativos. Documentação, screenshots e testes usam tokens fictícios ou de teste — nenhuma captura expõe token de local real, credencial de banco ou header de webhook. Antes de publicar uma imagem da interface, confirme que a URL visível não revela um token de produção. Se um segredo for commitado, revogue-o (histórico reescrito não torna a credencial segura) e abra questão para limpeza.
Use um security advisory privado em vez de issue pública. Inclua versão/commit, condição de reprodução e impacto; retorno esperado em poucos dias úteis. Mudanças em projeção de payload, escopo de tenant ou autenticação de webhook devem incluir teste que prove a garantia (o padrão está em tests/test_realtime.py e tests/test_api.py).