From 8d4172a6f9c7e7e4f773ff14120f2fa89982aa14 Mon Sep 17 00:00:00 2001 From: Pedro Nascimento Date: Tue, 27 Aug 2024 15:29:04 -0300 Subject: [PATCH 1/2] feat: integrating with HCI App Models --- app/config/base.py | 11 ++- app/routers/frontend.py | 192 ++++++++-------------------------------- app/types/frontend.py | 1 + 3 files changed, 48 insertions(+), 156 deletions(-) diff --git a/app/config/base.py b/app/config/base.py index 7729784..7b41b71 100644 --- a/app/config/base.py +++ b/app/config/base.py @@ -6,8 +6,17 @@ # Logging LOG_LEVEL = getenv_or_action("LOG_LEVEL", default="INFO") -# BigQuery Project +# BigQuery Integration BIGQUERY_PROJECT = getenv_or_action("BIGQUERY_PROJECT", action="raise") +BIGQUERY_PATIENT_HEADER_TABLE_ID = getenv_or_action( + "BIGQUERY_PATIENT_HEADER_TABLE_ID", action="raise" +) +BIGQUERY_PATIENT_SUMMARY_TABLE_ID = getenv_or_action( + "BIGQUERY_PATIENT_SUMMARY_TABLE_ID", action="raise" +) +BIGQUERY_PATIENT_ENCOUNTERS_TABLE_ID = getenv_or_action( + "BIGQUERY_PATIENT_ENCOUNTERS_TABLE_ID", action="raise" +) # JWT configuration JWT_SECRET_KEY = getenv_or_action("JWT_SECRET_KEY", default=token_bytes(32).hex()) diff --git a/app/routers/frontend.py b/app/routers/frontend.py index f255d01..aa71077 100644 --- a/app/routers/frontend.py +++ b/app/routers/frontend.py @@ -4,6 +4,7 @@ from typing import Annotated, List from fastapi import APIRouter, Depends, HTTPException from basedosdados import read_sql +from tortoise.exceptions import ValidationError from app.dependencies import ( get_current_frontend_user @@ -15,8 +16,13 @@ Encounter, UserInfo, ) -from app.config import BIGQUERY_PROJECT -from app.utils import read_timestamp, normalize_case +from app.validators import CPFValidator +from app.config import ( + BIGQUERY_PROJECT, + BIGQUERY_PATIENT_HEADER_TABLE_ID, + BIGQUERY_PATIENT_SUMMARY_TABLE_ID, + BIGQUERY_PATIENT_ENCOUNTERS_TABLE_ID +) router = APIRouter(prefix="/frontend", tags=["Frontend Application"]) @@ -45,80 +51,29 @@ async def get_patient_header( _: Annotated[User, Depends(get_current_frontend_user)], cpf: str, ) -> PatientHeader: + validator = CPFValidator() + try: + validator(cpf) + except ValidationError: + raise HTTPException(status_code=400, detail="Invalid CPF") + results_json = read_sql( f""" SELECT * - FROM `{BIGQUERY_PROJECT}`.`saude_dados_mestres`.`paciente` + FROM `{BIGQUERY_PROJECT}`.{BIGQUERY_PATIENT_HEADER_TABLE_ID} WHERE cpf = '{cpf}' """, from_file="/tmp/credentials.json", ).to_json(orient="records") + try: + results = json.loads(results_json) + except Exception: + results = [] - results = json.loads(results_json) - - if len(results) > 0: - patient_record = results[0] - else: + if len(results) == 0: raise HTTPException(status_code=404, detail="Patient not found") - - data = patient_record["dados"] - - cns_principal = None - if len(patient_record["cns"]) > 0: - cns_principal = patient_record["cns"][0] - - telefone_principal = None - if len(patient_record["contato"]["telefone"]) > 0: - telefone_principal = patient_record["contato"]["telefone"][0]["valor"] - - clinica_principal, equipe_principal = {}, {} - medicos, enfermeiros = [], [] - if len(patient_record["equipe_saude_familia"]) > 0: - equipe_principal = patient_record["equipe_saude_familia"][0] - - # Pega Clínica da Família - if equipe_principal["clinica_familia"]: - clinica_principal = equipe_principal["clinica_familia"] - - for equipe in patient_record["equipe_saude_familia"]: - medicos.extend(equipe["medicos"]) - enfermeiros.extend(equipe["enfermeiros"]) - - for medico in medicos: - medico['registry'] = medico.pop('id_profissional_sus') - medico['name'] = medico.pop('nome') - - for enfermeiro in enfermeiros: - enfermeiro['registry'] = enfermeiro.pop('id_profissional_sus') - enfermeiro['name'] = enfermeiro.pop('nome') - - data_nascimento = None - if data.get("data_nascimento") is not None: - data_nascimento = read_timestamp(data.get("data_nascimento"), output_format='date') - - return { - "registration_name": data.get("nome"), - "social_name": data.get("nome_social"), - "cpf": f"{cpf[:3]}.{cpf[3:6]}.{cpf[6:9]}-{cpf[9:]}", - "cns": cns_principal, - "birth_date": data_nascimento, - "gender": data.get("genero"), - "race": data.get("raca"), - "phone": telefone_principal, - "family_clinic": { - "cnes": clinica_principal.get("id_cnes"), - "name": clinica_principal.get("nome"), - "phone": clinica_principal.get("telefone"), - }, - "family_health_team": { - "ine_code": equipe_principal.get("id_ine"), - "name": equipe_principal.get("nome"), - "phone": equipe_principal.get("telefone"), - }, - "medical_responsible": medicos, - "nursing_responsible": enfermeiros, - "validated": data.get("identidade_validada_indicador"), - } + else: + return results[0] @@ -128,51 +83,19 @@ async def get_patient_summary( cpf: str, ) -> PatientSummary: - query = f""" - with - base as (select '{cpf}' as cpf), - alergias_grouped as ( - select - cpf, - alergias as allergies - from `saude_historico_clinico.alergia` - where cpf = '{cpf}' - ), - medicamentos_cronicos_single as ( - select - cpf, - med.nome as nome_medicamento - from `saude_historico_clinico.medicamentos_cronicos`, - unnest(medicamentos) as med - where cpf = '{cpf}' - ), - medicamentos_cronicos_grouped as ( - select - cpf, - array_agg(nome_medicamento) as continuous_use_medications - from medicamentos_cronicos_single - group by cpf - ) - select - alergias_grouped.allergies, - medicamentos_cronicos_grouped.continuous_use_medications - from base - left join alergias_grouped on alergias_grouped.cpf = base.cpf - left join medicamentos_cronicos_grouped on medicamentos_cronicos_grouped.cpf = base.cpf - """ results_json = read_sql( - query, - from_file="/tmp/credentials.json" + f""" + SELECT * + FROM `{BIGQUERY_PROJECT}`.{BIGQUERY_PATIENT_SUMMARY_TABLE_ID} + WHERE cpf = '{cpf}' + """, + from_file="/tmp/credentials.json", ).to_json(orient="records") - - result = json.loads(results_json) - if len(result) > 0: - return result[0] - - return { - "allergies": [], - "continuous_use_medications": [] - } + results = json.loads(results_json) + if len(results) == 0: + raise HTTPException(status_code=404, detail="Patient not found") + else: + return results[0] @router.get("/patient/filter_tags") async def get_filter_tags( @@ -199,51 +122,10 @@ async def get_patient_encounters( results_json = read_sql( f""" SELECT * - FROM `{BIGQUERY_PROJECT}`.`saude_historico_clinico`.`episodio_assistencial` - WHERE paciente.cpf = '{cpf}' + FROM `{BIGQUERY_PROJECT}`.{BIGQUERY_PATIENT_ENCOUNTERS_TABLE_ID} + WHERE cpf = '{cpf}' """, from_file="/tmp/credentials.json", ).to_json(orient="records") - - encounters = [] - for result in json.loads(results_json): - # Responsible professional - professional = result.get('profissional_saude_responsavel') - if professional: - if isinstance(professional, list): - professional = professional[0] if len(professional) > 0 else {} - - if not professional['nome'] and not professional['especialidade']: - professional = None - else: - professional = { - "name": professional.get('nome'), - "role": professional.get('especialidade') - } - - # Filter Tags - unit_type = result['estabelecimento']['estabelecimento_tipo'] - if unit_type in [ - 'CLINICA DA FAMILIA', - 'CENTRO MUNICIPAL DE SAUDE' - ]: - unit_type = 'CF/CMS' - - encounter = { - "entry_datetime": read_timestamp(result['entrada_datahora'], output_format='datetime'), - "exit_datetime": read_timestamp(result['saida_datahora'], output_format='datetime'), - "location": result['estabelecimento']['nome'], - "type": result['tipo'], - "subtype": result['subtipo'], - "active_cids": [cid['descricao'] for cid in result['condicoes'] if cid['descricao']], - "responsible": professional, - "clinical_motivation": normalize_case(result['motivo_atendimento']), - "clinical_outcome": normalize_case(result['desfecho_atendimento']), - "filter_tags": [unit_type], - } - encounters.append(encounter) - - # Sort Encounters by entry_datetime - encounters = sorted(encounters, key=lambda x: x['entry_datetime'], reverse=True) - - return encounters + results = json.loads(results_json) + return results diff --git a/app/types/frontend.py b/app/types/frontend.py index 24b6bc5..b4432bf 100644 --- a/app/types/frontend.py +++ b/app/types/frontend.py @@ -36,6 +36,7 @@ class Encounter(BaseModel): location: str type: str subtype: Optional[str] + exhibition_type: str = 'default' active_cids: List[str] responsible: Optional[Responsible] clinical_motivation: Optional[str] From 65be0021f2265c8edf21c54329867767f891b75c Mon Sep 17 00:00:00 2001 From: Pedro Nascimento Date: Wed, 28 Aug 2024 10:29:23 -0300 Subject: [PATCH 2/2] feat: Restrict Patients and Encounters data --- app/routers/frontend.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/app/routers/frontend.py b/app/routers/frontend.py index aa71077..4c59468 100644 --- a/app/routers/frontend.py +++ b/app/routers/frontend.py @@ -72,8 +72,15 @@ async def get_patient_header( if len(results) == 0: raise HTTPException(status_code=404, detail="Patient not found") - else: - return results[0] + + dados = results[0] + configuracao_exibicao = dados.get('exibicao', {}) + + if configuracao_exibicao.get('indicador', False) is False: + message = ",".join(configuracao_exibicao.get('motivos', [])) + raise HTTPException(status_code=204, detail=message) + + return dados @@ -123,7 +130,7 @@ async def get_patient_encounters( f""" SELECT * FROM `{BIGQUERY_PROJECT}`.{BIGQUERY_PATIENT_ENCOUNTERS_TABLE_ID} - WHERE cpf = '{cpf}' + WHERE cpf = '{cpf}' and exibicao.indicador = true """, from_file="/tmp/credentials.json", ).to_json(orient="records")