diff --git a/.gitignore b/.gitignore index 934595a5..1ca6bb66 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ credentials/ target-base/ *.DS_Store /dbt_packages +.state/ \ No newline at end of file diff --git a/dbt_project.yml b/dbt_project.yml index c2e74356..5c235f32 100644 --- a/dbt_project.yml +++ b/dbt_project.yml @@ -277,6 +277,14 @@ models: dado_pessoal: sim dado_sensivel: sim dominio: historico_clinico_app + escritoriodados: + identidade_unica_app: + +tags: ["daily", "identidade_unica_app"] + +labels: + dado_publico: nao + dado_pessoal: sim + dado_sensivel: sim + dominio: identidade_unica_app tests: rj_sms: diff --git a/models/marts/escritoriodados/identidade_unica_app/_mart_identidade_unica_app_schema.yml b/models/marts/escritoriodados/identidade_unica_app/_mart_identidade_unica_app_schema.yml new file mode 100644 index 00000000..5668ca63 --- /dev/null +++ b/models/marts/escritoriodados/identidade_unica_app/_mart_identidade_unica_app_schema.yml @@ -0,0 +1,179 @@ +version: 2 +models: + - name: mart_identidade_unica_app__episodio + description: > + Tabela com uma visão consolidada dos atendimentos realizados em + estabelecimentos de saúde. + columns: + - name: cpf + description: > + Cadastro de Pessoas Físicas do paciente. + - name: id_episodio + description: | + Fingerprint determinístico para o atendimento. + - name: entry_datetime + description: > + Data e hora de início do atendimento. Representa o momento em que o + atendimento começou. + - name: exit_datetime + description: > + Data e hora de término do atendimento. Representa o momento em que o + atendimento terminou. + - name: location + description: | + Nome do estabelecimento onde ocorreu o atendimento + - name: type + description: | + Tipo de atendimento + - name: subtype + description: | + Subtipo do atendimento, obtido a partir do tipo de atendimento. + - name: exhibition_type + description: | + Tipo de exibicao do episodio. + - name: clinical_exams + description: | + Exames clinicos realizados durante o episodio. + - name: clinical_exams.type + description: | + Tipo do exame realizado. + - name: clinical_exams.description + description: | + Descrição do exame realizado. + - name: procedures + description: | + Procedimentos realizados durante o episodio. + - name: prescription + description: | + Precrição realizada durante o episodio. + - name: medicines_administered + description: | + Medicamentos administrados. + - name: cids + description: | + Cids associados ao atendimento. + - name: cids.description + description: | + Descrição do cid associados ao atendimento. + - name: cids.status + description: | + Situação do CID associado ao atendimento. Pode assumir os valores "ATIVO", "NAO ESPECIFICADO" OU "RESOLVIDO" + - name: cids_summarized + description: | + Resumo dos cids associados ao atendimento. + - name: responsible + description: | + Lista dos profissionais de saúde responsáveis pelo atendimento. + - name: responsible.name + description: | + Nome do proficional de saude responsavel pelo atendimento. + - name: responsible.role + description: | + Especialidade do proficional de saude responsavel pelo atendimento. + - name: clinical_motivation + description: | + Motivo do atendimento. + - name: clinical_outcome + description: | + Desfecho do atendimento. + - name: deceased + description: | + Indicador de obito. + - name: filter_tags + description: | + Lista de tipos de estabelecimentos. + - name: exibicao + description: | + Flags de exibição + - name: exibicao.indicador + description: | + Indicador de exibição + - name: exibicao.episodio_sem_informacao + description: | + Indicador episodio sem informação + - name: exibicao.paciente_restrito + description: | + Indicador paciente restrito + - name: exibicao.paciente_sem_cpf + description: | + Indicador Paciente sem CPF + - name: exibicao.subtipo_proibido_vitacare + description: | + Indicador subtipo proibido para vitacare + - name: exibicao.episodio_vacinacao + description: | + Indicador episodio de vacinação + - name: exibicao.exame_sem_subtipo + description: | + Indicador exame sem subtipo + - name: provider + description: | + Fornecedor do prontuário. + - name: cpf_particao + description: | + Coluna de particionamento de CPF + - name: mart_identidade_unica_app__paciente + description: > + Tabela com uma visão consolidada dos pacientes atendidos em + estabelecimentos de saúde. + columns: + - name: registration_name + description: Nome de registro. + - name: social_name + description: Nome social. + - name: cpf + description: CPF do paciente. + - name: cns + description: CNS do paciente. + - name: birth_date + description: Data de nascimento. + - name: gender + description: "Gênero do paciente. Os valores possíveis são 'Masculino', 'Feminino' e nulo (não informado)." + - name: race + description: "Raça do paciente. Os valores possíveis são 'Amarela','Branca', 'Indigena', 'Parda', 'Preta' e nulo (não informado)." + - name: deceased + description: Indica se o paciente foi a óbito no atendimento. + - name: phone + description: Telefone do paciente. + - name: family_clinic + description: Informaçõe sobre a clínica da família. + - name: family_clinic.cnes + description: CNES da clínica da família. + - name: family_clinic.name + description: Nome da clínica da família. + - name: family_clinic.phone + description: Telefone para contato com a clínica da família. + - name: family_health_team + description: Informações sobre a equipee de saúde da família. + - name: family_health_team.ine_code + description: Identificador da equipe de saúde da família. + - name: family_health_team.name + description: Nome da equipe de saúde da família. + - name: family_health_team.phone + description: Telefone para contato com a equipe de saúde da família. + - name: medical_responsible + description: Relação de médicos da equipe de saúde da família. + - name: medical_responsible.registry + description: Identificador do profissional de saúde no CNES. + - name: medical_responsible.name + description: Nome do profissional de saúde. + - name: nursing_responsible + description: Relação de enfermeiros da equipe de saúde da família. + - name: nursing_responsible.registry + description: Identificador do profissional de saúde no CNES. + - name: nursing_responsible.name + description: Nome do profissional de saúde. + - name: validated + description: Indica se a identidade do paciente foi validada em alguma base federal. + - name: cpf_particao + description: Coluna de particionamento de CPF + - name: exibicao + description: Flags de exibicao. + - name: exibicao.indicador + description: Indicador de exibição. + - name: exibicao.motivos + description: Motivos de exibição. + - name: exibicao.ap_cadastro + description: AP cadastro. + - name: exibicao.unidades_cadastro + description: Unidades cadastro. \ No newline at end of file diff --git a/models/marts/escritoriodados/identidade_unica_app/mart_identidade_unica_app__episodio.sql b/models/marts/escritoriodados/identidade_unica_app/mart_identidade_unica_app__episodio.sql new file mode 100644 index 00000000..0f132b0c --- /dev/null +++ b/models/marts/escritoriodados/identidade_unica_app/mart_identidade_unica_app__episodio.sql @@ -0,0 +1,233 @@ +-- dbt run -s state:modified+ --defer --state .state/ --target dev + +{{ + config( + alias="episodio_assistencial", + schema="app_identidade_unica", + materialized="table", + cluster_by="cpf", + partition_by={ + "field": "cpf_particao", + "data_type": "int64", + "range": {"start": 0, "end": 100000000000, "interval": 34722222}, + }, + ) +}} + +with + paciente_restritos as ( + select cpf + from {{ ref("mart_historico_clinico_app__paciente") }} + where exibicao.indicador = false + ), + episodios_com_cid as ( + select id_episodio + from {{ ref("mart_historico_clinico__episodio") }}, unnest(condicoes) as cid + where cid.id is not null + ), + episodios_com_procedimento as ( + select id_episodio + from {{ ref("mart_historico_clinico__episodio") }} + where procedimentos_realizados is not null + ), + todos_episodios as ( + select + *, + -- Flag de Paciente com Restrição + case + when paciente_cpf in (select cpf from paciente_restritos) + then true + else false + end as flag__paciente_tem_restricao, + + -- Flag de Paciente sem CPF + case + when paciente_cpf is null then true else false + end as flag__paciente_sem_cpf, + + -- Flag de Exame sem Subtipo + case + when tipo = 'Exame' and subtipo is null then true else false + end as flag__exame_sem_subtipo, + + -- Flag de Episódio de Vacinação + case + when tipo = 'Vacinação' then true else false + end as flag__episodio_vacinacao, + + -- Flag de Episódio não informativo + case + when tipo like '%Exame%' + then false + when + tipo not like '%Exame%' + and ( + id_episodio in (select * from episodios_com_cid) + or id_episodio in (select * from episodios_com_procedimento) + or motivo_atendimento is not null + or desfecho_atendimento is not null + ) + then false + else true + end as flag__episodio_sem_informacao, + + -- Flag de Subtipo Proibido + case + when + prontuario.fornecedor = 'vitacare' + and subtipo in ( + 'Consulta de Fisioterapia', + 'Consulta de Assistente Social', + 'Atendimento de Nutrição NASF', + 'Ficha da Aula', + 'Consulta de Atendimento Farmacêutico', + 'Consulta de Fonoaudiologia', + 'Consulta de Terapia Ocupacional', + 'Gestão de arquivo não médico', + 'Gestão de Arquivo Assistente Social NASF', + 'Gestão de Arquivo de Professor NASF', + 'Gestão de Arquivo Não Médico NASF', + 'Gestão de Arquivo Fisioterapeuta NASF', + 'Atendimento de Nutrição Modelo B', + 'Gestão de Arquivo Não Médico', + 'Gestão de Arquivo Fonoaudiólogo NASF', + 'Atendimento de Fisioterapia Modelo B', + 'Atendimento de Fonoaudiologia Modelo B', + 'Atendimento de Assistente Social Modelo B', + 'Gestão de Arquivo Farmacêutico NASF', + 'Gestão de Arquivo de Terapeuta Ocupacional NASF', + 'Consulta de Acupuntura', + 'Ato Gestão de Arquivo não Médico', + 'Gestão de Arquivo não Médico', + 'Atendimento Nutricionismo' + ) + then true + else false + end as flag__subtipo_proibido_vitacare + + from {{ ref("mart_historico_clinico__episodio") }} + ), + encounter_medicines as ( + select distinct + ep.id_episodio, + concat(med.nome,-- lala, , s , + ', ', + IF(med.unidade_medida is null, '',med.unidade_medida), + ', ', + IF(med.uso is null,'',med.uso), + ', ', + IF(med.via_administracao is null,'',med.via_administracao), + IF(med.quantidade is null,'',concat('. QUANTIDADE: ',med.quantidade)) + ) as medicamento_administrado + from {{ ref("mart_historico_clinico__episodio") }} as ep, unnest(ep.medicamentos_administrados) as med + where med.nome is not null + ), + encounter_medicines_agg as ( + select + id_episodio, + string_agg(regexp_replace(medicamento_administrado,'(, )+',', '),'\n') as medicines_administered + from encounter_medicines + group by 1 + ), + encounter_prescription as ( + select + distinct ep.id_episodio, + concat(p.nome,' ',p.concentracao) as prescricao + from {{ ref("mart_historico_clinico__episodio") }} as ep, unnest(ep.prescricoes) as p + ), + encounter_prescription_agg as ( + select + id_episodio, + string_agg(prescricao,'\n') as prescription + from encounter_prescription + group by 1 + ), + + -- -=--=--=--=--=--=--=--=--=--=--=--=--=--=--=--=--=--=-- + -- FORMATAÇÃO + -- -=--=--=--=--=--=--=--=--=--=--=--=--=--=--=--=--=--=-- + formatado as ( + select + paciente_cpf as cpf, + todos_episodios.id_episodio, + safe_cast(entrada_datahora as string) as entry_datetime, + safe_cast(saida_datahora as string) as exit_datetime, + safe_cast(estabelecimento.nome as string) as location, + safe_cast(tipo as string) as type, + safe_cast(subtipo as string) as subtype, + safe_cast( + case + when tipo = 'Exame' then 'clinical_exam' else 'default' + end as string + ) as exhibition_type, + array( + select struct(tipo as type, descricao as description) + from unnest(exames_realizados) + where tipo is not null + ) as clinical_exams, + safe_cast(procedimentos_realizados as string) as procedures, + safe_cast(prescription as string) as prescription, + safe_cast(medicines_administered as string) as medicines_administered, + array( + select struct(descricao as description , situacao as status) + from unnest(condicoes) + where descricao is not null + ) as cids, + array( + select distinct resumo + from unnest(condicoes) + where resumo is not null and resumo != '' + ) as cids_summarized, + case + when + profissional_saude_responsavel.nome is not null + and profissional_saude_responsavel.especialidade is not null + then + struct( + profissional_saude_responsavel.nome as name, + profissional_saude_responsavel.especialidade as role + ) + else null + end as responsible, + motivo_atendimento as clinical_motivation, + desfecho_atendimento as clinical_outcome, + obito_indicador as deceased, + case + when estabelecimento.estabelecimento_tipo is null + then [] + when + estabelecimento.estabelecimento_tipo + in ('CLINICA DA FAMILIA', 'CENTRO MUNICIPAL DE SAUDE') + then ['CF/CMS'] + else array(select estabelecimento.estabelecimento_tipo) + end as filter_tags, + struct( + not ( + flag__episodio_sem_informacao + or flag__paciente_tem_restricao + or flag__paciente_sem_cpf + or flag__subtipo_proibido_vitacare + or flag__episodio_vacinacao + or flag__exame_sem_subtipo + ) as indicador, + flag__episodio_sem_informacao as episodio_sem_informacao, + flag__paciente_tem_restricao as paciente_restrito, + flag__paciente_sem_cpf as paciente_sem_cpf, + flag__subtipo_proibido_vitacare as subtipo_proibido_vitacare, + flag__episodio_vacinacao as episodio_vacinacao, + flag__exame_sem_subtipo as exame_sem_subtipo + ) as exibicao, + prontuario.fornecedor as provider, + safe_cast(paciente_cpf as int64) as cpf_particao + from todos_episodios + left join encounter_prescription_agg + on todos_episodios.id_episodio = encounter_prescription_agg.id_episodio + left join encounter_medicines_agg + on todos_episodios.id_episodio = encounter_medicines_agg.id_episodio + ) +-- -=--=--=--=--=--=--=--=--=--=--=--=--=--=--=--=--=--=-- +-- FINAL +-- -=--=--=--=--=--=--=--=--=--=--=--=--=--=--=--=--=--=-- +select * +from formatado +where {{ validate_cpf("cpf") }} \ No newline at end of file diff --git a/models/marts/escritoriodados/identidade_unica_app/mart_identidade_unica_app__paciente.sql b/models/marts/escritoriodados/identidade_unica_app/mart_identidade_unica_app__paciente.sql new file mode 100644 index 00000000..2cf6aead --- /dev/null +++ b/models/marts/escritoriodados/identidade_unica_app/mart_identidade_unica_app__paciente.sql @@ -0,0 +1,108 @@ +{{ + config( + alias="paciente", + schema="app_identidade_unica", + materialized="table", + partition_by={ + "field": "cpf_particao", + "data_type": "int64", + "range": {"start": 0, "end": 100000000000, "interval": 34722222}, + }, + ) +}} + +with + todos_pacientes as ( + select + * + from {{ ref('mart_historico_clinico__paciente') }} + ), + ---=--=--=--=--=--=--=--=--=--=--=--=--=--=--=--=--=--=-- + -- INFORMAÇÕES DE CADASTRO + ---=--=--=--=--=--=--=--=--=--=--=--=--=--=--=--=--=--=-- + unidade_de_cadastros_dos_pacientes as ( + select + cpf, cadastro.id_cnes as id_cnes + from todos_pacientes, unnest(prontuario) as cadastro + ), + unidades_saude as ( + select + id_cnes, area_programatica + from {{ ref('dim_estabelecimento') }} + ), + ap_de_cadastro_dos_pacientes as ( + select + distinct cpf, unidades_saude.area_programatica as ap + from unidade_de_cadastros_dos_pacientes + inner join unidades_saude using (id_cnes) + ), + ap_cadastro_por_paciente as ( + select + cpf, array_agg(ap ignore nulls) as ap_cadastro + from ap_de_cadastro_dos_pacientes + group by cpf + ), + unidades_cadastro_por_paciente as ( + select + cpf, array_agg(id_cnes ignore nulls) as unidades_cadastro + from unidade_de_cadastros_dos_pacientes + group by cpf + ), + ---=--=--=--=--=--=--=--=--=--=--=--=--=--=--=--=--=--=-- + -- FORMATAÇÃO + ---=--=--=--=--=--=--=--=--=--=--=--=--=--=--=--=--=--=-- + formatado as ( + select + dados.nome as registration_name, + dados.nome_social as social_name, + todos_pacientes.cpf, + todos_pacientes.cns[safe_offset(0)] as cns, + safe_cast(dados.data_nascimento as string) as birth_date, + dados.genero as gender, + dados.raca as race, + dados.obito_indicador as deceased, + contato.telefone[safe_offset(0)].valor as phone, + struct( + equipe_saude_familia[safe_offset(0)].clinica_familia.id_cnes as cnes, + equipe_saude_familia[safe_offset(0)].clinica_familia.nome as name, + equipe_saude_familia[safe_offset(0)].clinica_familia.telefone as phone + ) as family_clinic, + struct( + equipe_saude_familia[safe_offset(0)].id_ine as ine_code, + equipe_saude_familia[safe_offset(0)].nome as name, + equipe_saude_familia[safe_offset(0)].telefone as phone + ) as family_health_team, + array( + select + struct( + id_profissional_sus as registry, + nome as name + ) + from unnest(equipe_saude_familia[safe_offset(0)].medicos) + ) as medical_responsible, + array( + select + struct( + id_profissional_sus as registry, + nome as name + ) + from unnest(equipe_saude_familia[safe_offset(0)].enfermeiros) + ) as nursing_responsible, + dados.identidade_validada_indicador as validated, + safe_cast(todos_pacientes.cpf as int64) as cpf_particao + from todos_pacientes + ) +---=--=--=--=--=--=--=--=--=--=--=--=--=--=--=--=--=--=-- +-- JUNTANDO INFORMAÇÕES DE EXIBICAO +---=--=--=--=--=--=--=--=--=--=--=--=--=--=--=--=--=--=-- +select + formatado.*, + struct( + true as indicador, + array[] as motivos, + ap_cadastro_por_paciente.ap_cadastro, + unidades_cadastro_por_paciente.unidades_cadastro + ) as exibicao +from formatado + left join ap_cadastro_por_paciente on ap_cadastro_por_paciente.cpf = formatado.cpf + left join unidades_cadastro_por_paciente on unidades_cadastro_por_paciente.cpf = formatado.cpf \ No newline at end of file diff --git a/tools/run-diff.ps1 b/tools/run-diff.ps1 deleted file mode 100644 index e638e1f7..00000000 --- a/tools/run-diff.ps1 +++ /dev/null @@ -1,23 +0,0 @@ -# Verifica se um argumento foi passado -if (-not $args[0]) { - Write-Host "Uso: .\script.ps1 " - exit 1 -} - -$BranchName = $args[0] - -Write-Host "" -Write-Host ">>>>> Checkout na branch 'master'" -git checkout master - -Write-Host "" -Write-Host ">>>>> Gerando artefatos para o ambiente base em 'target-base'" -dbt docs generate --target prod --target-path .target-base/ - -Write-Host "" -Write-Host ">>>>> Checkout na branch '$BranchName'" -git checkout $BranchName - -Write-Host "" -Write-Host ">>>>> Executando dbt" -dbt run -s "state:modified+" --defer --state .target-base/ --target ci diff --git a/tools/run-diff.py b/tools/run-diff.py new file mode 100644 index 00000000..82be2f5d --- /dev/null +++ b/tools/run-diff.py @@ -0,0 +1,68 @@ +import subprocess +import sys +from typing import Optional + +def run_command(command, shell=False): + """Executa um comando no terminal e exibe informações detalhadas em caso de erro.""" + try: + print(f"Executing: {' '.join(command)}") + result = subprocess.run( + command, + shell=shell, + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + print(result.stdout) + except subprocess.CalledProcessError as e: + print(f"\nERROR IN COMMAND: {' '.join(command)}") + print("Command output (stdout):") + print(e.stdout) + print("\nCommand error (stderr):") + print(e.stderr) + sys.exit(1) + +def main(): + if len(sys.argv) < 2: + print(f"Usage: {sys.argv[0]} [target] [full_refresh]") + sys.exit(1) + + branch_name = sys.argv[1] + target = sys.argv[2] if len(sys.argv) > 2 else "dev" + full_refresh = sys.argv[3] if len(sys.argv) > 3 else "" + + # Determinar flag de full_refresh + full_refresh_flag = "--full-refresh" if full_refresh == "full_refresh" else "" + + print("\nSTEP 1") + print(">>>> CHECKING OUT 'master' BRANCH") + run_command(["git", "checkout", "master"]) + run_command(["git", "pull"]) + + print("\nSTEP 2") + print(">>>> GENERATING STATE '.state/' BASED ON 'master' BRANCH") + run_command(["dbt", "docs", "generate", "--target", "prod", "--target-path", ".state/"]) + + print("\nSTEP 3") + print(f">>>> CHECKING OUT BRANCH '{branch_name}'") + run_command(["git", "checkout", branch_name]) + + print("\nSTEP 4") + print(">>>> EXECUTING DBT MATERIALIZATIONS") + print(f">>>>>> TARGET: {target}") + print(f">>>>>> FULL REFRESH: {full_refresh_flag}") + dbt_command = [ + "dbt", "run", + "-s", "state:modified+", + "--defer", "--state", ".state/", + "--target", target + ] + if full_refresh_flag: + dbt_command.append(full_refresh_flag) + run_command(dbt_command) + + print("\nENDING") + +if __name__ == "__main__": + main() diff --git a/tools/run-diff.sh b/tools/run-diff.sh deleted file mode 100644 index 6561add1..00000000 --- a/tools/run-diff.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash - -# Verifica se um argumento foi passado -if [ -z "$1" ]; then - echo "Uso: $0 " - exit 1 -fi - -BRANCH_NAME=$1 - -echo "" -echo ">>>>> Checkout na branch 'master'" -git checkout master -git pull - -echo "" -echo ">>>>> Gerando artefatos para o ambiente base em 'target-base'" -dbt docs generate --target prod --target-path target-base/ - -echo "" -echo ">>>>> Checkout na branch '$BRANCH_NAME'" -git checkout "$BRANCH_NAME" - -echo "" -echo ">>>>> Executando dbt" -dbt run -s "state:modified+" --defer --state target-base/ \ No newline at end of file