9 minutes
Automatização dos dados do CV do interno - Rewrite it in Go!
Há mais de 4 anos atrás, construí uma aplicação para extrair dos sistemas clínicos a informação necessária para o CV do internato de Medicina Geral e Familiar. Esta foi baseada numa aplicação anterior que extraía a medicação da PEM para os utentes, necessária para completar o CV dos colegas com Medicine One, e um ano depois eu próprio.
O modo de funcionamento foi baseado largamente no ClinicoMais, com a navegação automatizada no SClinico, mas a extração dos dados em si ocorria no Processo Clínico Eletrónico e na PEM, onde não é necessário navegar com base em reconhecimento de padrões de imagem.
Este sistema era, provavelmente, a única forma de obter os dados do CV sem ser através de consulta manual de 1800 processos ou fazer pedidos às instituições e rezar para que o interno esteja num local em que esses pedidos são aceites. Obviamente, nada disto devia ser necessário mas esse é um debate institucional que deve ser tido entre a Ordem dos Médicos e a ACSS/SPMS, um ao qual eu devotei tempo enquanto trabalhei nesta última entidade (e nota-se a diferença que fiz). Mas este texto é de cariz técnico, o que pretendo é apresentar o meu trabalho, explicar alguns processos e as conclusões a que cheguei.
A aplicação foi escrita em Python, a linguagem a que mais estava habituado na altura. O processo de extração era francamente lento por diversas razões, entre elas:
- A natureza sequencial dos processos de consulta;
- A biblioteca de automatização utilizada (PyAutoGUI) precisava do OpenCV para reconhecer as imagens mais rapidamente e eu não queria incluir uma dependência tão grande;
Estes “problemas” significavam que a extração de informação podia demorar algo entre 8 e 24 horas, partidos entre algumas sessões pelo interno. Qualquer pessoa com experiência em análise de dados percebe que isto é muito tempo, mas é um pouco como o tempo de funcionamento de uma máquina de lavar, é tempo em que o interno pode estar a escrever as partes descritivas do CV ou a estudar para o exame, enquanto a aplicação trabalha.
No final de 2023, com a saída de uma amiga do internato para breve, decidi reescrever a aplicação em Go. Os objetivos:
- Paralelizar os 3 processos: navegação SClinico, PEM e PCE;
- Acelerar os processos de extração em si;
- Aumentar o âmbito de dados um pouco mais;
- Aumentar a resiliência da extração (ligar graciosamente com as situações dos sistemas clínicos “bloquearem”);
- Aumentar a protecção dos dados pessoais dos utentes;
Sobre este último ponto, é necessário dar duas notas: A aplicação em python, tal como a de Go, funcionavam puramente localmente, os dados nunca saiam do computador do trabalho; a natureza de extração manual por parte dos internos pode ser feita ao longo do ano ou no final do ano, sendo que os dados tendem a ser colocados num ficheiro Excel, que na melhor hipótese (remota) fica sempre nesse computador, na pior hipótese anda a circular durante 365 dias numa Pen, com backups aqui e ali. A encriptação é quase sempre um mistério para a generalidade da população.
Mas voltemos à parte técnica…
Com a minha experiência e o facto de que estaria a reescrever algo e não a fazê-lo do zero, contei demorar algumas semanas, o que revela a total inaptidão em estimar tempos de desenvolvimento ainda que a informação seja razoavelmente adequada. Felizmente, a demora não foi crítica e a aplicação foi entregue a duas internas e uma especialista a tempo de extrairem os dados que necessitavam. Talvez não na sua forma mais refinada que idealizei, mas certamente melhor do que a alternativa de lamber processos um-a-um.
Os problemas
Os testes
O primeiro e maior problema com que me deparei foi a testability. Quando fiz a aplicação em python há anos, era interno ou médico e tinha a possibilidade de testar o sistema fácil e rapidamente. Tinha acesso a uma lista de utentes, a uma estação de trabalho e interesse legítimo em colher os dados. Inclusivamente, acredito que ao longo da utilização do sistema por parte dos internos, descobriram-se problemas nos processos de alguns utentes que poderiam ter passado despercebidos de outra forma. A rapidez com que podia fazer uma alteração e testá-la, ainda que muitíssimo inferior a testes unitários, era bastante satisfatória. Já nesta nova etapa da minha vida, os testes tiveram de ter os internos como intermediário, o que significava que o código não podia ser incrementado em pequenos passos mas sim maiores saltos. Felizmente, a type safety do Go reduziu o impacto de erros inesperados que tinha com o python, mas ainda assim é muito difícil gerir uma integração sem especificações ou acesso adequado.
Paralelismo
O paralelismo foi muito mais difícil de obter do que pensava. Isto porque os três processos paralelos eram dependentes. Não era possível (ou recomendável) extrair dados de dois utentes em simultâneo para a mesma aplicação, mas concomitantemente era imperioso poder extrair dados de um programa para um utente enquanto já se navega para o próximo utente. Além disso, esta fase de desenvolvimento aconteceu mais tardiamente, com maior pressão para ter a aplicação pronta.
O paralelismo também trouxe algumas dificuldades na introdução de dados na base de dados, que foi resolvido com alguma investigação acerca dos pragmas disponíveis no SQLite e o funcionamento das BDs no Go.
Encriptação
Curiosamente, este problema foi auto-gerado. Os meus conhecimentos das bibliotecas de criptografia disponíveis em Go eram suficientes, e não tive de implementar a minha própria encriptação, o que seria uma péssima ideia. No entanto, as minhas pesquisas levaram-me por bibliotecas como o SQLCipher, que teria sido uma ótima escolha, não fosse o seu suporte relativamente precário em Go. Decidi-me por cifrar os dados identificadores dos utentes, incluindo dados de consultas, com uma camada de tradução entre o repositório de dados e os “executores” (nome pomposo para os agentes responsáveis por extrair os dados ou trabalhá-los), com uma derivação de password do utilizador e uma notificação que se o utilizador perder a password, os dados são irrecuperáveis.
Tempo
Todo o ciclo de investigação, programação e testes demorou muito mais do que o esperado. Particularmente, fazê-lo aos fins-de-semana e finais do dia, com a constante ameaça de estar demasiado cansado ou ter tarefas domésticas para fazer tornou-se um problema por si só. Aquilo que esperaria despachar num mês foi trabalho de vários meses e centenas de horas. A motivação inicial do projeto também foi reduzida gradualmente, à medida que os desafios técnicos foram ultrapassados e restou apenas “querer acabar um projeto”. Se formos medir o custo monetário da empreitada, assumindo a rate média de um programador em Go, rapidamente entramos em depressão. Mas como antigo funcionário público, o martírio faz parte de mim.
Resiliência
No final, a minha aplicação nova continuou a ter os mesmos problemas que a anterior, nomeadamente a dificuldade em lidar com os bloqueios aleatórios das 3 fontes de dados envolvidas. Apesar de que isto poderia ser melhor solucionado, explico à frente porque isto não é um problema assim tão grave.
Trojan.ClipBanker
A minha utilização da área de transferência (clipboard) para copiar a agenda do SClinico resultou na identificação heurística do windows da minha aplicação como um trojan chamado ClipBanker. Obviamente que isto era um falso positivo e o código responsável é partilhado entre uma biblioteca de clipboard do go e o trojan em questão. Para mitigar, e visto que só utilizava uma pequena porção do código, copiei apenas a parte necessária para o meu código, com resultado idêntico. Acabei por reescrever o algoritmo de obtenção dos dados do clipboard, o que resolveu o problema, apenas para descobrir no final que bastava incluir um “sleep(0)” (dormir por zero segundos) no meio do código para não dar um falso positivo. Ou seja, uma linha de código resolvia o mesmo que uma hora a reescrever um algoritmo.
Vomitar SQL
Nunca escrevi tanto SQL na minha vida. Quando terminei, o sistema de extração tinha mais de 70 pesquisas diferentes, quase todas francamente complexas. Não foi, certamente, a parte mais glamorosa do processo.
Os pontos positivos
Falhar, falhar, falhar
Falhar, utilizar mal os recursos e instituir más soluções é uma ótima coisa de fazer num projeto pessoal, pois significa que é menos provável cometer os mesmos erros em sistemas empresariais no futuro. Fazer este projeto levou-me a cometer vários erros mas aprender imenso com eles. Aqueles pedaços de código com testes unitários nunca falharam enquanto que outros nem tanto. Escrever SQL nunca foi problema, abstrações idiotas foram fontes de frustração.
I AM SPEEEEEED
No final, a extração de dados foi reduzida para 4 horas, assumindo alguma estabilidade dos sistemas clínicos (algo provável ao final do dia e raro durante o horário de trabalho). Esta velocidade resultou de quatro factores:
- Paralelismo dos processos de navegação do SClinico, PCE e PEM - a aplicação consegue extrair dados do PCE e PEM do doente 1 enquanto já está a preparar o processo do utente 2 no SClinico;
- Extração inteligente do PCE - abstração do mesmo com extração direta via requests aos URLs obtidos na janela do PCE, sem navegação por interação com o browser;
- Extração inteligente da PEM - injeção de scripts no browser da PEM, com obtenção mais direta da medicação do utente, sem necessidade de navegar entre as diferentes modals do sistema;
- Reconhecimento de imagem muito mais rápido - loops de reconhecimento de píxeis implementados em Go, permitindo encontrar uma imagem no ecrã em 100 milissegundos em vez de 1 segundo;
- Redução de compassos de espera - a possibilidade de pesquisar imagens muito mais rapidamente e com menos carga permitiu não poluir o código com “sleeps”, compassos de espera para que o SClinico chegasse ao sítio indicado;
Alguns destes aspetos contribuíram também para uma eventual redução da carga nos sistemas clínicos, pois ocorreu menos navegação entre janelas, com obtenção mais direta dos dados.
Segregação de responsabilidades
A encriptação da base de dados local, combinada com a possibilidade de cross-compilation do Go significa que pude separar mais o processo de extração e análise, com a criação de uma aplicação para cada parte do processo. A aplicação de análise de dados foi compilada também para Mac OS, algo impossível no passado.
Conhecimento adquirido
Esta empreitada resultou na necessidade de me familiarizar mais com diversas bibliotecas e processos, entre os quais:
Da standard library:
- Context
- SQL
- image
- Zip
- FS
Third party:
- Templ
- HTMX
Conclusão
O meu alvo inicial de proporcionar uma ferramenta de extração de dados dos sistemas clínicos de MGF do SNS foi bem-sucedido, tendo cumprido os objetivos iniciais, excepto o aumento de resiliência.
Medindo o tempo gasto neste rewrite, efetuei um exercício absurdo que me tirou vários fins de semana, feriados e tempos de férias. Podia ter estado a jogar computador ou a aprender a tocar guitarra mas escolhi diariamente terminar o projeto. Pude ajudar uma amiga, o que foi positivo, e aprendi imenso, tendo aplicado o conhecimento adquirido várias vezes desde então.
A longevidade da aplicação é também questionável, e espero sinceramente que a SPMS torne todo este processo obsoleto com a introdução de um sistema nativo. Até lá, se alguém necessitar de obter dados para análise de lista, pode contactar-me.