É com grande orgulho que anunciamos a lista de membros da nossa comunidade que estarão participando do maior hackathon de saúde de Santa Catarina, o Hackathon Summit Saúde 2024!
Nosso grupo de desenvolvedores está pronto para enfrentar desafios, colaborar com especialistas do setor e criar soluções inovadoras que podem transformar a saúde e salvar vidas.
Veja os membros da Python Floripa que participaram desta edição
Jardel Godinho
End-to-End Developer
Rodolfo Barbosa
Data Engineer
Sofia Godinho
Web Developer
Otávio Augusto
Backend Developer
Felipe Angeli
Software Engineer
Daniel Martins
Senior Python Developer
Bento Luiz
Fullstack Developer
Enrico Tomaselli
Fullstack Developer
Bruno Vianna
Systems Analyst
Léo Moraes
Software Engineer
Felipe Escobar
Data Scientist
Iuli Hardt
Data Scientist
Jonathan Batista
Software Engineer
Queremos convidar toda a comunidade Python Floripa a acompanhar este evento e torcer pelo sucesso dos nossos representantes! Sua participação e apoio são fundamentais para motivar e inspirar nossos membros durante essa jornada.
Fique ligado em nossas redes e acompanhe as histórias, conquistas e aprendizados dessa experiência incrível!
Vamos mostrar toda a força e união da Python Floripa e torcer para que nossos membros brilhem no evento!
Alguns Insights
Como usamos LLM para extrair dados estruturados de um relato de atendimento médico
Formulário Base
<!-- Forms -->
<div class="container">
<!-- Top Bar with Icons (Tabs) -->
<div class="top-bar">
<!-- Tab for 'Relato' -->
<div class="section-icon active" data-section="relato">
<!-- Icon for 'Relato' -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480L40 480c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24l0 112c0 13.3 10.7 24 24 24s24-10.7 24-24l0-112c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z" fill="currentColor"/>
</svg>
<span>Relato</span>
</div>
<!-- Tab for 'Dados' -->
<div class="section-icon" data-section="dados">
<!-- Icon for 'Dados' -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512">
<path d="M192 96a48 48 0 1 0 0-96 48 48 0 1 0 0 96zm-8 384l0-128 16 0 0 128c0 17.7 14.3 32 32 32s32-14.3 32-32l0-288 56 0 64 0 16 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-16 0 0-64 192 0 0 192-192 0 0-32-64 0 0 48c0 26.5 21.5 48 48 48l224 0c26.5 0 48-21.5 48-48l0-224c0-26.5-21.5-48-48-48L368 0c-26.5 0-48 21.5-48 48l0 80-76.9 0-65.9 0c-33.7 0-64.9 17.7-82.3 46.6l-58.3 97c-9.1 15.1-4.2 34.8 10.9 43.9s34.8 4.2 43.9-10.9L120 256.9 120 480c0 17.7 14.3 32 32 32s32-14.3 32-32z" fill="currentColor"/>
</svg>
<span>Dados</span>
</div>
</div>
<!-- Forms -->
<!-- Relato Form -->
<form id="relatoForm" data-section="relato" class="dynamicForm active" method="post">
<section id="relato-section">
<label class="form-label">Relato do Atendimento</label>
<textarea name="relato_text" class="form-textarea relato-comments"></textarea>
<button type="button" id="saveRelatoBtn">Salvar</button>
</section>
</form>
<!-- Dados Form -->
<form id="dadosForm" data-section="dados" class="dynamicForm" method="post" style="display: none;">
<section id="dados-section">
<!-- Dados Pessoais -->
<h3>Dados Pessoais</h3>
<div class="personal-data-section">
<div class="column">
<label>Nome do Paciente</label>
<input type="text" name="nome_paciente" required>
<label>Idade do Paciente</label>
<input type="number" name="idade_paciente" required>
</div>
<div class="column">
<label>Data do Atendimento</label>
<input type="date" name="data_atendimento" required>
<label>Horário do Atendimento</label>
<input type="time" name="horario_atendimento" required>
</div>
</div>
<!-- Seção SOAP -->
<h3>SOAP</h3>
<div class="soap-section">
<div class="quadrant">
<h3>Subjetivo</h3>
<label>Queixa Principal</label>
<textarea name="queixa_principal" required></textarea>
<label>Histórico Médico Relevante</label>
<textarea name="historico_medico"></textarea>
<label>Sintomas e Características</label>
<textarea name="sintomas_caracteristicas"></textarea>
<label>Histórico Familiar Relevante</label>
<textarea name="historico_familiar"></textarea>
<label>Estilo de Vida / Hábitos</label>
<textarea name="estilo_vida"></textarea>
</div>
<div class="quadrant">
<h3>Objetivo</h3>
<label>Pressão Arterial (PA)</label>
<input type="text" name="pressao_arterial">
<label>Frequência Cardíaca (FC)</label>
<input type="text" name="frequencia_cardiaca">
<label>Temperatura Corporal</label>
<input type="text" name="temperatura_corporal">
<label>Saturação de Oxigênio</label>
<input type="text" name="saturacao_oxigenio">
<label>Exame Físico e Observações Objetivas</label>
<textarea name="exame_fisico"></textarea>
<label>Resultados de Exames</label>
<textarea name="resultados_exames"></textarea>
</div>
<div class="quadrant">
<h3>Avaliação</h3>
<label>Diagnóstico Principal</label>
<textarea name="diagnostico_principal"></textarea>
<label>Diagnósticos Diferenciais</label>
<textarea name="diagnosticos_diferenciais"></textarea>
<label>Problemas Ativos Relacionados</label>
<textarea name="problemas_ativos"></textarea>
</div>
<div class="quadrant">
<h3>Plano</h3>
<label>Tratamento Prescrito / Medicações</label>
<textarea name="tratamento_prescrito"></textarea>
<label>Acompanhamento e Retorno</label>
<textarea name="acompanhamento_retorno"></textarea>
<label>Exames Solicitados</label>
<textarea name="exames_solicitados"></textarea>
<label>Encaminhamentos</label>
<textarea name="encaminhamentos"></textarea>
<label>Instruções e Orientações para o Paciente</label>
<textarea name="instrucoes_orientacoes"></textarea>
</div>
</div>
<!-- Botão Salvar -->
<button type="button" id="saveDadosBtn">Salvar</button>
</section>
</form>
<!-- Search Bar -->
<div class="search-area">
<div class="search-container">
<div class="search-bar">
<textarea rows="1" placeholder="Faça o relato do atendimento" id="searchText"></textarea>
<div class="icons" id="searchIcons">
<!--<button class="icon-button" id="attachBtn">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
<path d="M364.2 83.8c-24.4-24.4-64-24.4-88.4 0l-184 184c-42.1 42.1-42.1 110.3 0 152.4s110.3 42.1 152.4 0l152-152c10.9-10.9 28.7-10.9 39.6 0s10.9 28.7 0 39.6l-152 152c-64 64-167.6 64-231.6 0s-64-167.6 0-231.6l184-184c46.3-46.3 121.3-46.3 167.6 0s46.3 121.3 0 167.6l-176 176c-28.6 28.6-75 28.6-103.6 0s-28.6-75 0-103.6l144-144c10.9-10.9 28.7-10.9 39.6 0s10.9 28.7 0 39.6l-144 144c-6.7 6.7-6.7 17.7 0 24.4s17.7 6.7 24.4 0l176-176c24.4-24.4 24.4-64 0-88.4z" fill="currentColor"></path>
</svg>
</button>-->
<button class="icon-button" id="microphoneBtn">
<!-- Microphone icon SVG -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512">
<path d="M96 96v160c0 53 43 96 96 96s96-43 96-96V96c0-53-43-96-96-96S96 43 96 96zm224 144v16c0 70.7-57.3 128-128 128S64 326.7 64 256v-40c0-13.3-10.7-24-24-24S16 202.7 16 216v40c0 89.1 66.2 162.7 152 174.4V464h-48c-13.3 0-24 10.7-24 24s10.7 24 24 24h192c13.3 0 24-10.7 24-24s-10.7-24-24-24h-48v-33.6c85.8-11.7 152-85.3 152-174.4v-40c0-13.3-10.7-24-24-24s-24 10.7-24 24v24z" fill="currentColor"></path>
</svg>
</button>
</div>
</div>
<div class="send-button-container">
<button class="send-button" id="sendBtn">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path d="M16.1 260.2c-22.6 12.9-20.5 47.3 3.6 57.3L160 376l0 103.3c0 18.1 14.6 32.7 32.7 32.7c9.7 0 18.9-4.3 25.1-11.8l62-74.3 123.9 51.6c18.9 7.9 40.8-4.5 43.9-24.7l64-416c1.9-12.1-3.4-24.3-13.5-31.2s-23.3-7.5-34-1.4l-448 256zm52.1 25.5L409.7 90.6 190.1 336l1.2 1L68.2 285.7zM403.3 425.4L236.7 355.9 450.8 116.6 403.3 425.4z" fill="currentColor"/>
</svg>
</button>
</div>
</div>
</div>
<!-- Indicador de Carregamento -->
<div id="loadingIndicator" class="loading-indicator" aria-live="assertive" aria-busy="true">
<div class="spinner"></div>
<p>Processando, por favor aguarde...</p>
</div>
</div>
Fluxo em JS para enviar dados para o backend
/* ====== Evento para o Botão Salvar no Relato ====== */
document.getElementById('saveRelatoBtn').addEventListener('click', function() {
const relatoText = document.querySelector('textarea[name="relato_text"]').value.trim();
if (relatoText === '') {
alert('Por favor, insira o relato do atendimento.');
return;
}
// Exibir o indicador de carregamento
document.getElementById('loadingIndicator').classList.add('active');
// Desabilitar o formulário de dados enquanto aguarda
document.getElementById('dadosForm').classList.add('disabled');
// Enviar o texto via AJAX
fetch(medicoAjax.ajaxurl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
},
body: new URLSearchParams({
action: 'processar_relato_medico',
relato_text: relatoText,
nonce: medicoAjax.nonce
})
})
.then(response => response.json())
.then(data => {
// Ocultar o indicador de carregamento
document.getElementById('loadingIndicator').classList.remove('active');
if (data.success) {
// Preencher o formulário com os dados recebidos
preencherFormularioDados(data.data.dados);
// Navegar para a próxima seção
setActiveSection('dados');
} else {
alert('Erro ao processar o relato: ' + data.data.message);
}
})
.catch(error => {
console.error('Erro:', error);
alert('Erro na solicitação. Verifique a conexão ou tente novamente.');
// Ocultar o indicador de carregamento
document.getElementById('loadingIndicator').classList.remove('active');
});
});
Handler AJAX – recebe do frontend e prepara prompt
<?php
/* ====== Handler para o AJAX do Relato Médico ====== */
function processar_relato_medico() {
// Verificar o nonce de segurança
check_ajax_referer('medico_nonce', 'nonce');
// Obter o texto do relato
$relato_text = isset($_POST['relato_text']) ? sanitize_text_field($_POST['relato_text']) : '';
if (empty($relato_text)) {
wp_send_json_error(['message' => 'Relato vazio.']);
}
// Montar o prompt para a OpenAI
$prompt = "# Extraia as seguintes informações do relato médico fornecido e retorne em formato JSON com os seguintes campos:
{
\"nome_paciente\": \"\",
\"idade_paciente\": \"\",
\"queixa_principal\": \"\",
\"historico_medico\": \"\",
\"sintomas_caracteristicas\": \"\",
\"historico_familiar\": \"\",
\"estilo_vida\": \"\",
\"pressao_arterial\": \"\",
\"frequencia_cardiaca\": \"\",
\"temperatura_corporal\": \"\",
\"saturacao_oxigenio\": \"\",
\"exame_fisico\": \"\",
\"resultados_exames\": \"\",
\"diagnostico_principal\": \"\",
\"diagnosticos_diferenciais\": \"\",
\"problemas_ativos\": \"\",
\"tratamento_prescrito\": \"\",
\"acompanhamento_retorno\": \"\",
\"exames_solicitados\": \"\",
\"encaminhamentos\": \"\",
\"instrucoes_orientacoes\": \"\"
}
# Se alguma informação não estiver disponível no relato, preencha com \"Não informado\".
# Relato: \"$relato_text\"";
// Chamar a API da OpenAI
$messages = [
['role' => 'system', 'content' => 'Você é um assistente que extrai informações de relatos médicos.'],
['role' => 'user', 'content' => $prompt],
];
$response = openai_gpt_35_turbo($messages);
// Verificar se houve erro na resposta
if (strpos($response, 'Erro') === 0) {
wp_send_json_error(['message' => $response]);
}
// Verificar se a resposta está vazia
if (empty($response)) {
wp_send_json_error(['message' => 'Resposta vazia da OpenAI.']);
}
// Remover delimitadores de código, como ```json e ```
$cleaned_response = preg_replace('/^```json|```$/', '', trim($response));
// Tentar decodificar o JSON retornado
$dados = json_decode($cleaned_response, true);
// Tratamento de erros de decodificação JSON
if (json_last_error() !== JSON_ERROR_NONE) {
error_log("Erro ao decodificar JSON da OpenAI: " . json_last_error_msg());
error_log("Resposta da OpenAI: " . $cleaned_response); // Registrar a resposta completa para depuração
wp_send_json_error(['message' => 'Erro ao decodificar a resposta da OpenAI.']);
}
// Verificar se a estrutura esperada está presente
if (!is_array($dados) || empty($dados)) {
wp_send_json_error(['message' => 'Estrutura de resposta inesperada da OpenAI.']);
}
// Retornar os dados decodificados para o frontend
wp_send_json_success(['dados' => $dados]);
}
add_action('wp_ajax_processar_relato_medico', 'processar_relato_medico');
add_action('wp_ajax_nopriv_processar_relato_medico', 'processar_relato_medico');
?>
Chamada de API padrão OpenAi
<?php
/* ====== Envia Solicitação para a API da OpenAI ==== */
function openai_gpt_35_turbo($messages, $temperature = 0.5) {
$url = "https://api.openai.com/v1/chat/completions";
$api_key = OPENAI_API_KEY;
if (empty($api_key) || $api_key === 'OPENAI_API_KEY') {
error_log('OpenAI API key not defined or placeholder used.');
return 'Erro: Chave da API OpenAI não definida.';
}
$request_data = json_encode([
'model' => 'gpt-4o-mini', //'gpt-3.5-turbo',
'messages' => $messages,
'temperature' => $temperature,
'max_tokens' => 3500
]);
error_log('OpenAI request data: ' . mb_substr($request_data, 0, 300) . '...');
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Authorization: Bearer ' . $api_key
]);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $request_data);
$response = curl_exec($ch);
if ($response === false) {
$error_message = curl_error($ch);
curl_close($ch);
error_log('OpenAI request error: ' . $error_message);
return 'Erro na solicitação da OpenAI: ' . $error_message;
}
error_log('OpenAI response: ' . mb_substr($response, 0, 500) . '...');
curl_close($ch);
$response_data = json_decode($response, true);
if (isset($response_data['choices'][0]['message']['content'])) {
error_log('OpenAI response content retrieved successfully');
return trim($response_data['choices'][0]['message']['content']);
} else {
error_log('Unexpected OpenAI response: ' . print_r($response_data, true));
return 'Erro: Resposta inesperada da OpenAI.';
}
}
?>
Dica extra – Use o Web Speech API direto no front para receber o relato em audio e converter em texto.
Jardel Godinho6 contribuições
Sou um empreendedor e desenvolvedor comprometido em fortalecer conexões humanas e impulsionar o crescimento no mundo digital. Com um e-commerce especializado em flores e cestas, minha missão é aproximar as pessoas por meio de gestos de amor e carinho. Paralelamente, lidero uma software house que desenvolve soluções avançadas para e-commerces e MVPs inovadores para startups. Na comunidade Python Floripa, atuo como Host dos eventos, promovendo a união e a troca de conhecimento. Acredito que a colaboração supera a competição, permitindo que todos cresçam juntos e tornem nossa comunidade mais forte. Minha paixão pela Python Floripa se reflete em meu trabalho para criar um ambiente onde ideias, projetos e, principalmente, pessoas possam prosperar coletivamente.