init commit
All checks were successful
build-api-image / build (push) Successful in 51s

This commit is contained in:
Antonio Lopes dos Santos
2026-08-04 16:52:02 -03:00
commit afec302c3e
11594 changed files with 1636728 additions and 0 deletions

View File

@@ -0,0 +1,996 @@
<?php
namespace App\Services\AtencaoBasica;
use App\Enums\AtencaoBasica\Parametrizacao\AreaTipo;
use App\Enums\AtencaoBasica\VisitaDomiciliar\MotivoVisita;
use App\Enums\AtencaoBasica\RelatorioCuidadoDesenvolvimentoInfantil\CboBoasPratricasAeBEnum;
use App\Enums\AtencaoBasica\RelatorioCuidadoDesenvolvimentoInfantil\CboBoasPratricasCEnum;
use App\Enums\AtencaoBasica\RelatorioCuidadoDesenvolvimentoInfantil\CboBoasPratricasDEnum;
use App\Enums\AtencaoBasica\RelatorioCuidadoDesenvolvimentoInfantil\ClassificacaoEnum;
use App\Enums\AtencaoBasica\RelatorioCuidadoDesenvolvimentoInfantil\ProcedimentosBoasPraticasC;
use App\Enums\AtencaoBasica\RelatorioCuidadoDesenvolvimentoInfantil\VacinasEsquemaVacinalEnum;
use App\Enums\Cadastro\SujeitoAtencao\Sexo;
use App\Services\Service;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
class RelatorioCuidadoDesenvolvimentoInfantilService extends Service
{
private const MAPA_INDICADORES = [
'A' => 'indicadorA',
'B' => 'indicadorB',
'C' => 'indicadorC',
'D' => 'indicadorD',
'E' => 'indicadorE',
];
private const CONSULTAS_MINIMAS = 9;
private const DIAS_LIMITE_PRIMEIRA_CONSULTA = 30;
private const ANOS_LIMITE_PUBLICO_ALVO = 2;
private const DIAS_LIMITE_SEGUNDA_VISITA = 180;
public static function create()
{
return [
'form' => [
'unidades' => [],
'infraestruturas' => [],
'equipes' => [],
'anos' => self::getAnos(),
'quadrimestres' => self::getQuadrimestres(),
'sexo' => [
['id' => Sexo::MASCULINO, 'sexo' => 'Masculino'],
['id' => Sexo::FEMININO, 'sexo' => 'Feminino'],
],
'boas_praticas' => self::getBoasPraticas(),
],
'model' => [
'periodo' => ['start' => null, 'end' => null],
'ano' => null,
'quadrimestre' => null,
'unidade_id' => null,
'infraestrutura_id' => null,
'equipe_id' => null,
'sexo_id' => null,
'boasPraticas' => ['A', 'B', 'C', 'D', 'E'],
'faixa_etaria' => [
'inicial' => ['anos' => null, 'meses' => null, 'dias' => null],
'final' => ['anos' => null, 'meses' => null, 'dias' => null],
],
'paciente_id' => null,
'buscaAtiva' => null,
'foraArea' => false,
]
];
}
public static function index($request)
{
$params = self::getParams($request);
$dadosPaciente = self::getDadosPaciente($params);
$boasPraticasSelecionadas = !empty($params->boas_praticas) ? $params->boas_praticas : ['A', 'B', 'C', 'D', 'E'];
$dadosMensais = self::getDadosPacientePorMes($params);
$totais = self::calcularTotais($dadosPaciente);
$metricasBoasPraticas = self::calcularMetricasBoasPraticas($dadosPaciente, $totais['totalPacientes'], $boasPraticasSelecionadas);
$pontosPorPaciente = self::calcularPontosPorPaciente($dadosPaciente, $boasPraticasSelecionadas);
$denominador = $totais['totalPacientes'];
$numerador = array_sum($pontosPorPaciente);
$resultado = self::calcularResultadoMedioMensal($dadosMensais, $boasPraticasSelecionadas);
return [
'totalizadores' => self::getTotalizadores($totais, $metricasBoasPraticas, $denominador, $numerador, $resultado),
'resultadoPorEquipe' => self::getResultadoPorEquipe($dadosPaciente, $boasPraticasSelecionadas, $dadosMensais),
'buscaAtiva' => $params->busca_ativa ? self::getBuscaAtiva($dadosPaciente, $boasPraticasSelecionadas) : [],
'data' => $dadosPaciente,
'boasPraticasSelecionadas' => $boasPraticasSelecionadas,
];
}
public static function list($request)
{
$params = self::getParams($request);
switch (request()->listar) {
case 'unidades': {
$dadosListagem = self::getDadosListagem($params);
return [
'unidades' => self::getUnidades($params, $dadosListagem),
'infraestruturas' => self::getInfraestruturas($params, $dadosListagem),
'equipes' => self::getEquipes($params, $dadosListagem),
];
}
case 'infraestruturas': {
$dadosListagem = self::getDadosListagem($params);
return [
'infraestruturas' => self::getInfraestruturas($params, $dadosListagem),
'equipes' => self::getEquipes($params, $dadosListagem),
];
}
case 'equipes': {
$dadosListagem = self::getDadosListagem($params);
return [
'equipes' => self::getEquipes($params, $dadosListagem),
];
}
}
return [];
}
private static function calcularTotais($dados)
{
return [
'totalPacientes' => $dados->pluck('pacienteId')->unique()->count(),
'totalEquipes' => $dados->pluck('equipeId')->unique()->count(),
'totalMicroareas' => $dados->pluck('equipeDescricao')->unique()->count(),
];
}
private static function calcularMetricasBoasPraticas($dados, $totalPacientes, array $boasPraticasSelecionadas)
{
$metricas = [];
foreach ($boasPraticasSelecionadas as $pratica) {
$campo = self::MAPA_INDICADORES[$pratica] ?? null;
if (!$campo) continue;
$count = $dados->where($campo, true)->pluck('pacienteId')->unique()->count();
$metricas[$pratica] = [
'pacientes' => $count,
'percentual' => $totalPacientes > 0 ? round(($count / $totalPacientes) * 100, 2) : 0,
];
}
return $metricas;
}
private static function calcularPontosPorPaciente($dados, array $boasPraticasSelecionadas)
{
$pontos = [];
foreach ($dados->pluck('pacienteId')->unique() as $pacienteId) {
$paciente = $dados->where('pacienteId', $pacienteId)->first();
$pontos[$pacienteId] = self::calcularPontosPaciente($paciente, $boasPraticasSelecionadas);
}
return $pontos;
}
private static function calcularPontosPaciente($paciente, array $boasPraticasSelecionadas)
{
$pontos = 0;
foreach ($boasPraticasSelecionadas as $pratica) {
$campo = self::MAPA_INDICADORES[$pratica] ?? null;
if ($campo && $paciente->{$campo}) {
$pontos += 20;
}
}
return $pontos;
}
private static function calcularResultadoFinal($denominador, $numerador, $maxPontosPorPaciente = 100)
{
if ($denominador <= 0 || $numerador <= 0) {
return 0;
}
return round(($numerador / ($denominador * $maxPontosPorPaciente)) * 100, 2);
}
private static function gerarSubperiodosMensais(array $periodo): array
{
$subperiodos = [];
$current = $periodo['start']->copy()->startOfMonth()->startOfDay();
$periodoEnd = $periodo['end']->copy();
while ($current->lte($periodoEnd)) {
$monthEnd = $current->copy()->endOfMonth()->endOfDay();
$subStart = $current->lt($periodo['start']) ? $periodo['start']->copy() : $current->copy();
$subEnd = $monthEnd->gt($periodoEnd) ? $periodoEnd->copy() : $monthEnd;
$subperiodos[] = ['start' => $subStart, 'end' => $subEnd];
$current->addMonthNoOverflow()->startOfMonth();
}
return $subperiodos;
}
private static function getDadosPacientePorMes($params): array
{
$dadosMensais = [];
foreach (self::gerarSubperiodosMensais($params->periodo) as $subperiodo) {
$paramsmes = (object) (array) $params;
$paramsmes->periodo = $subperiodo;
$dadosMensais[] = self::getDadosPaciente($paramsmes);
}
return $dadosMensais;
}
private static function calcularResultadoMedioMensal(array $dadosMensais, array $boasPraticasSelecionadas): float
{
$resultados = [];
$maxPontos = count($boasPraticasSelecionadas) * 20;
foreach ($dadosMensais as $dadosMes) {
$totais = self::calcularTotais($dadosMes);
$pontos = self::calcularPontosPorPaciente($dadosMes, $boasPraticasSelecionadas);
$resultados[] = self::calcularResultadoFinal(
$totais['totalPacientes'],
array_sum($pontos),
$maxPontos
);
}
return count($resultados) > 0
? round(array_sum($resultados) / count($resultados), 2)
: 0;
}
private static function calcularPercentuaisPorEquipe($pacientes, $totalPacientes, array $boasPraticasSelecionadas)
{
$percentuais = [];
foreach ($boasPraticasSelecionadas as $pratica) {
$campo = self::MAPA_INDICADORES[$pratica] ?? null;
if (!$campo) continue;
$count = $pacientes->where($campo, true)->pluck('pacienteId')->unique()->count();
$percentuais['percentual' . $pratica] = $totalPacientes > 0
? round(($count / $totalPacientes) * 100, 2)
: 0;
}
return $percentuais;
}
private static function getTotalizadores($totais, $metricasBoasPraticas, $denominador, $numerador, $resultado)
{
return [
'totalPacientes' => $totais['totalPacientes'],
'totalEquipes' => $totais['totalEquipes'],
'totalMicroareas' => $totais['totalMicroareas'],
'denominador' => $denominador,
'numerador' => $numerador,
'resultado' => $resultado,
'boasPraticas' => $metricasBoasPraticas,
'semPublicoAlvo' => $denominador === 0,
];
}
private static function getClassificacao($resultado)
{
if ($resultado > 75) return ClassificacaoEnum::OTIMO;
if ($resultado > 50) return ClassificacaoEnum::BOM;
if ($resultado > 25) return ClassificacaoEnum::SUFICIENTE;
return ClassificacaoEnum::REGULAR;
}
private static function getResultadoPorEquipe($dados, array $boasPraticasSelecionadas, array $dadosMensais = [])
{
$maxPontos = count($boasPraticasSelecionadas) * 20;
$resultadosMensaisPorEquipe = [];
foreach ($dadosMensais as $dadosMes) {
foreach ($dadosMes->groupBy('equipeId') as $equipeId => $pacientesMes) {
$totalMes = $pacientesMes->pluck('pacienteId')->unique()->count();
$pontosMes = array_sum(self::calcularPontosPorPaciente($pacientesMes, $boasPraticasSelecionadas));
$resultadosMensaisPorEquipe[$equipeId][] = self::calcularResultadoFinal($totalMes, $pontosMes, $maxPontos);
}
}
$resultado = [];
foreach ($dados->groupBy('equipeId') as $equipeId => $pacientes) {
$primeiro = $pacientes->first();
$totalPacientes = $pacientes->pluck('pacienteId')->unique()->count();
$percentuais = self::calcularPercentuaisPorEquipe($pacientes, $totalPacientes, $boasPraticasSelecionadas);
$pontos = self::calcularPontosPorPaciente($pacientes, $boasPraticasSelecionadas);
$denominador = $totalPacientes;
$numerador = array_sum($pontos);
if (!empty($resultadosMensaisPorEquipe[$equipeId])) {
$mensais = $resultadosMensaisPorEquipe[$equipeId];
$resultadoEquipe = round(array_sum($mensais) / count($mensais), 2);
} else {
$resultadoEquipe = self::calcularResultadoFinal($denominador, $numerador, $maxPontos);
}
$resultado[] = array_merge([
'unidadeNomeFantasia' => $primeiro->unidadeNomeFantasia,
'equipeDescricao' => $primeiro->equipeDescricao,
'denominador' => $denominador,
'numerador' => $numerador,
'resultado' => $resultadoEquipe,
'classificacao' => self::getClassificacao($resultadoEquipe),
], $percentuais);
}
return $resultado;
}
private static function getBuscaAtiva($dados, array $boasPraticasSelecionadas)
{
$sexoMap = self::obterMapaSexo();
$resultado = [];
foreach ($dados->unique('pacienteId') as $paciente) {
$pontos = 0;
$pendencias = [];
foreach ($boasPraticasSelecionadas as $pratica) {
$campo = self::MAPA_INDICADORES[$pratica] ?? null;
if (!$campo) continue;
if ($paciente->$campo) {
$pontos += 20;
} else {
$pendencias[] = $pratica;
}
}
$dataNascimento = Carbon::parse($paciente->pacienteDataNascimento);
$idade = $dataNascimento->diffInYears(Carbon::now());
$meses = $dataNascimento->copy()->addYears($idade)->diffInMonths(Carbon::now());
if (empty($pendencias)) {
continue;
}
$resultado[] = [
'unidadeNomeFantasia' => $paciente->unidadeNomeFantasia,
'equipeDescricao' => $paciente->equipeDescricao,
'pacienteNome' => $paciente->pacienteNome,
'pacienteId' => $paciente->pacienteId,
'pacienteSexo' => $sexoMap[$paciente->pacienteSexo] ?? 'Indefinido',
'idadeCompleta' => "$idade anos e $meses meses",
'usuarioResponsavelNome' => $paciente->usuarioResponsavelNome,
'pacienteDataNascimento' => $dataNascimento->format('d/m/Y'),
'pacienteTelefone' => $paciente->pacienteTelefone,
'pacienteEndereco' => $paciente->pacienteEndereco,
'areaMicroareaDomicilio' => $paciente->areaMicroareaDomicilio,
'indicadorA' => $paciente->indicadorA,
'indicadorB' => $paciente->indicadorB,
'indicadorC' => $paciente->indicadorC,
'indicadorD' => $paciente->indicadorD,
'indicadorE' => $paciente->indicadorE,
'areaTipoId' => $paciente->area_tipo_id,
'pontuacaoTotal' => $pontos,
];
}
return $resultado;
}
private static function baseQueryCadastroIndividual($params)
{
$periodoInicial = $params->periodo['start']->copy()->startOfDay();
return DB::table('atencao_basica_cadastro_individual')
->join('cadastro_paciente', 'cadastro_paciente.id', 'atencao_basica_cadastro_individual.paciente_id')
->join('cadastro_cidadao', 'cadastro_cidadao.id', 'cadastro_paciente.cidadao_id')
->leftJoin('cadastro_cidadao_telefone', function ($join) {
$join->on('cadastro_cidadao_telefone.cidadao_id', 'cadastro_cidadao.id')
->where('cadastro_cidadao_telefone.principal', 1)
->whereNull('cadastro_cidadao_telefone.deleted_at');
})
->leftJoin('cadastro_domicilio', 'cadastro_domicilio.id', 'cadastro_cidadao.domicilio_id')
->leftJoin('cadastro_endereco', 'cadastro_endereco.id', 'cadastro_domicilio.endereco_id')
->leftJoin('cadastro_logradouro', 'cadastro_logradouro.id', 'cadastro_endereco.logradouro_id')
->leftJoin('cadastro_bairro', 'cadastro_bairro.id', 'cadastro_logradouro.bairro_id')
->join('cadastro_usuario', 'cadastro_usuario.id', 'atencao_basica_cadastro_individual.usuario_id')
->join('cadastro_cidadao as cidadaoUsuario', 'cidadaoUsuario.id', 'cadastro_usuario.cidadao_id')
->join('atencao_basica_parametrizacao_equipe', 'atencao_basica_parametrizacao_equipe.id', 'atencao_basica_cadastro_individual.equipe_id')
->join('cadastro_infraestrutura', 'cadastro_infraestrutura.id', 'atencao_basica_parametrizacao_equipe.infraestrutura_id')
->join('cadastro_unidade', 'cadastro_unidade.id', 'cadastro_infraestrutura.unidade_id')
->join('atencao_basica_parametrizacao_area', 'atencao_basica_parametrizacao_area.id', 'atencao_basica_parametrizacao_equipe.area_id')
->leftJoin('atencao_basica_parametrizacao_microarea', 'atencao_basica_parametrizacao_microarea.id', 'atencao_basica_parametrizacao_equipe.microarea_id')
->leftJoin('atencao_basica_cadastro_domiciliar', 'atencao_basica_cadastro_domiciliar.domicilio_id', 'cadastro_cidadao.domicilio_id')
->leftJoin('atencao_basica_parametrizacao_microarea as microareaDomicilio', 'microareaDomicilio.id', 'atencao_basica_cadastro_domiciliar.microarea_id')
->leftJoin('atencao_basica_parametrizacao_area as areaDomicilio', 'areaDomicilio.id', 'microareaDomicilio.area_id')
->leftJoin('cadastro_obito', function ($join) {
$join->on('cadastro_cidadao.id', 'cadastro_obito.cidadao_id')
->whereNull('cadastro_obito.deleted_at');
})
->where(function ($query) use ($periodoInicial) {
$query->whereNull('cadastro_obito.id')
->orWhere('cadastro_obito.obito_em', '>=', $periodoInicial);
})
->where(function ($query) use ($periodoInicial) {
$query->whereNull('atencao_basica_cadastro_individual.saida_cidadao_id')
->orWhere('atencao_basica_cadastro_individual.motivo_saida_data', '>=', $periodoInicial);
})
->where('cadastro_cidadao.data_nascimento', '>=', self::getDataNascimentoLimite($params))
->whereIn('atencao_basica_parametrizacao_area.tipo_id', self::getAreaTipos())
->when($params->unidade_id, fn ($q) => $q->where('cadastro_unidade.id', $params->unidade_id))
->when($params->infraestrutura_id, fn ($q) => $q->where('cadastro_infraestrutura.id', $params->infraestrutura_id))
->when($params->equipe_id, fn ($q) => $q->where('atencao_basica_parametrizacao_equipe.id', $params->equipe_id))
->when(!$params->fora_area, function ($q) {
$q->whereNotNull('atencao_basica_cadastro_domiciliar.id')
->where(function ($sub) {
$sub->whereNull('atencao_basica_cadastro_domiciliar.fora_area')
->orWhere('atencao_basica_cadastro_domiciliar.fora_area', 0);
});
})
->when(self::hasFaixaEtaria($params), fn ($q) => self::aplicarFiltroFaixaEtaria($q, $params));
}
private static function selectEquipeDescricao()
{
return DB::raw('concat(
"Area: ", COALESCE(atencao_basica_parametrizacao_area.descricao, "Não informada"),
" Microarea: ", COALESCE(atencao_basica_parametrizacao_microarea.descricao, "Não informada"),
" - INE: ", COALESCE(atencao_basica_parametrizacao_area.numero_ine, "Não informada")
) as equipeDescricao');
}
private static function getDadosListagem($params)
{
return self::baseQueryCadastroIndividual($params)
->select([
'cadastro_unidade.id as unidadeId',
'cadastro_unidade.nome_fantasia as unidadeNomeFantasia',
'cadastro_infraestrutura.id as infraestruturaId',
DB::raw('COALESCE(cadastro_infraestrutura.nome, cadastro_unidade.nome_fantasia) as infraestruturaNome'),
'atencao_basica_parametrizacao_equipe.id as equipeId',
self::selectEquipeDescricao(),
])
->distinct()
->get();
}
public static function getDadosPaciente($params)
{
$pacientes = self::baseQueryCadastroIndividual($params)
->select([
'atencao_basica_cadastro_individual.id',
'cadastro_cidadao.nome as pacienteNome',
'cadastro_paciente.id as pacienteId',
'cadastro_cidadao.data_nascimento as pacienteDataNascimento',
'cadastro_cidadao.sexo_id as pacienteSexo',
'cadastro_unidade.id as unidadeId',
'cadastro_unidade.nome_fantasia as unidadeNomeFantasia',
'cadastro_infraestrutura.id as infraestruturaId',
'cidadaoUsuario.nome as usuarioResponsavelNome',
'cadastro_cidadao_telefone.numero as pacienteTelefone',
'atencao_basica_parametrizacao_area.numero_ine as pacienteIne',
'atencao_basica_parametrizacao_area.tipo_id as area_tipo_id',
'atencao_basica_cadastro_domiciliar.fora_area as pacienteForaArea',
DB::raw('CASE
WHEN areaDomicilio.descricao IS NULL AND microareaDomicilio.descricao IS NULL THEN "Não informada"
ELSE concat("Area: ", COALESCE(areaDomicilio.descricao, "Não informada"), " / Microarea: ", COALESCE(microareaDomicilio.descricao, "Não informada"))
END as areaMicroareaDomicilio'),
DB::raw('concat(cadastro_logradouro.nome, ", ", cadastro_endereco.numero, " - ", cadastro_bairro.nome, " - ", cadastro_logradouro.cep) as pacienteEndereco'),
DB::raw('COALESCE(cadastro_infraestrutura.nome, cadastro_unidade.nome_fantasia) as infraestruturaNome'),
'atencao_basica_parametrizacao_equipe.id as equipeId',
self::selectEquipeDescricao(),
])
->when($params->sexo_id, fn ($q) => $q->where('cadastro_cidadao.sexo_id', $params->sexo_id))
->when($params->paciente_id, fn ($q) => $q->where('cadastro_paciente.id', $params->paciente_id))
->get();
if ($pacientes->isEmpty()) {
return $pacientes;
}
$periodoFinal = Carbon::parse($params->periodo['end'])->endOfDay();
$pacienteIds = $pacientes->pluck('pacienteId')->unique()->toArray();
$indicadoresA = self::getBoaPraticaA($pacienteIds, $periodoFinal);
$indicadoresB = self::getBoaPraticaB($pacienteIds, $periodoFinal);
$indicadoresC = self::getBoaPraticaC($pacienteIds, $periodoFinal);
$indicadoresD = self::getBoaPraticaD($pacienteIds, $periodoFinal);
$indicadoresE = self::getBoaPraticaE($pacienteIds, $pacientes, $periodoFinal);
return $pacientes->map(function ($item) use ($indicadoresA, $indicadoresB, $indicadoresC, $indicadoresD, $indicadoresE) {
$keyIne = trim($item->pacienteId) . '-' . trim($item->pacienteIne ?? '');
$item->indicadorA = $indicadoresA[$keyIne] ?? false;
$item->indicadorB = $indicadoresB[$keyIne] ?? false;
$item->indicadorC = $indicadoresC[$keyIne] ?? false;
$item->indicadorD = $indicadoresD[$keyIne] ?? false;
$item->indicadorE = $indicadoresE[$item->pacienteId] ?? false;
return $item;
});
}
private static function getBoaPraticaA(array $pacienteIds, $periodoFinal): array
{
return DB::table('atencao_basica_atendimento_individual as ai')
->join('cadastro_paciente as cp', 'cp.id', 'ai.paciente_id')
->join('cadastro_cidadao as cc', 'cc.id', 'cp.cidadao_id')
->join('sigtap.ocupacao as oc', 'oc.id', 'ai.usuario_ocupacao_id')
->join('atencao_basica_parametrizacao_equipe as pe', 'pe.id', 'ai.equipe_id')
->join('atencao_basica_parametrizacao_area as pa', 'pa.id', 'pe.area_id')
->select(['ai.paciente_id', 'pa.numero_ine'])
->where('ai.data_usuario_responsavel', '<=', $periodoFinal)
->whereIn('oc.codigo', CboBoasPratricasAeBEnum::all())
->whereIn('ai.paciente_id', $pacienteIds)
->whereRaw('TIMESTAMPDIFF(DAY, cc.data_nascimento, ai.data_usuario_responsavel) <= ' . self::DIAS_LIMITE_PRIMEIRA_CONSULTA)
->distinct()
->get()
->mapWithKeys(fn ($registro) => [trim($registro->paciente_id) . '-' . trim($registro->numero_ine) => true])
->toArray();
}
private static function getBoaPraticaB(array $pacienteIds, $periodoFinal): array
{
return DB::table('atencao_basica_atendimento_individual as ai')
->join('cadastro_paciente as cp', 'cp.id', 'ai.paciente_id')
->join('cadastro_cidadao as cc', 'cc.id', 'cp.cidadao_id')
->join('sigtap.ocupacao as oc', 'oc.id', 'ai.usuario_ocupacao_id')
->join('atencao_basica_parametrizacao_equipe as pe', 'pe.id', 'ai.equipe_id')
->join('atencao_basica_parametrizacao_area as pa', 'pa.id', 'pe.area_id')
->select(['ai.paciente_id', 'pa.numero_ine'])
->where('ai.data_usuario_responsavel', '<=', $periodoFinal)
->whereIn('oc.codigo', CboBoasPratricasAeBEnum::all())
->whereIn('ai.paciente_id', $pacienteIds)
->whereRaw('TIMESTAMPDIFF(YEAR, cc.data_nascimento, ai.data_usuario_responsavel) <= ' . self::ANOS_LIMITE_PUBLICO_ALVO)
->groupBy(['ai.paciente_id', 'pa.numero_ine'])
->havingRaw('COUNT(ai.id) >= ' . self::CONSULTAS_MINIMAS)
->get()
->mapWithKeys(fn ($registro) => [trim($registro->paciente_id) . '-' . trim($registro->numero_ine) => true])
->toArray();
}
private static function getBoaPraticaC(array $pacienteIds, $periodoFinal): array
{
$counts = [];
$accumulate = function ($registros) use (&$counts) {
foreach ($registros as $registro) {
$key = trim($registro->paciente_id) . '-' . trim($registro->numero_ine);
$counts[$key] = ($counts[$key] ?? 0) + $registro->cnt;
}
};
$accumulate(
DB::table('atencao_basica_atendimento_individual as ai')
->join('atencao_basica_parametrizacao_equipe as pe', 'pe.id', 'ai.equipe_id')
->join('atencao_basica_parametrizacao_area as pa', 'pa.id', 'pe.area_id')
->join('sigtap.ocupacao as oc', 'oc.id', 'ai.usuario_ocupacao_id')
->select(['ai.paciente_id', 'pa.numero_ine', DB::raw('COUNT(*) as cnt')])
->where('ai.data_usuario_responsavel', '<=', $periodoFinal)
->whereIn('oc.codigo', CboBoasPratricasCEnum::all())
->whereIn('ai.paciente_id', $pacienteIds)
->whereNotNull('ai.peso')
->whereNotNull('ai.altura')
->groupBy(['ai.paciente_id', 'pa.numero_ine'])
->get()
);
$accumulate(
DB::table('atencao_basica_atividade_coletiva as ac')
->join('atencao_basica_atividade_coletiva_participante as acp', 'acp.atividade_coletiva_id', 'ac.id')
->join('atencao_basica_parametrizacao_equipe as pe', 'pe.id', 'ac.equipe_id')
->join('atencao_basica_parametrizacao_area as pa', 'pa.id', 'pe.area_id')
->join('sigtap.ocupacao as oc', 'oc.id', 'ac.usuario_ocupacao_id')
->select(['acp.paciente_id', 'pa.numero_ine', DB::raw('COUNT(*) as cnt')])
->where('ac.data_cadastro', '<=', $periodoFinal)
->whereIn('oc.codigo', CboBoasPratricasCEnum::all())
->whereIn('acp.paciente_id', $pacienteIds)
->whereNotNull('acp.peso')
->whereNotNull('acp.altura')
->groupBy(['acp.paciente_id', 'pa.numero_ine'])
->get()
);
$accumulate(
DB::table('atencao_basica_visita_domiciliar as vd')
->join('atencao_basica_parametrizacao_equipe as pe', 'pe.id', 'vd.equipe_id')
->join('atencao_basica_parametrizacao_area as pa', 'pa.id', 'pe.area_id')
->join('sigtap.ocupacao as oc', 'oc.id', 'vd.usuario_ocupacao_id')
->select(['vd.paciente_id', 'pa.numero_ine', DB::raw('COUNT(*) as cnt')])
->where('vd.data_usuario_responsavel', '<=', $periodoFinal)
->whereIn('oc.codigo', CboBoasPratricasCEnum::all())
->whereIn('vd.paciente_id', $pacienteIds)
->whereNotNull('vd.peso')
->whereNotNull('vd.altura')
->groupBy(['vd.paciente_id', 'pa.numero_ine'])
->get()
);
$accumulate(
DB::table('atencao_basica_procedimento as p')
->join('atencao_basica_parametrizacao_equipe as pe', 'pe.id', 'p.equipe_id')
->join('atencao_basica_parametrizacao_area as pa', 'pa.id', 'pe.area_id')
->join('atencao_basica_procedimento_atendimento as pa_att', 'pa_att.procedimento_id', 'p.id')
->join('atencao_basica_procedimento_atendimento_procedimento as pap', 'pap.procedimento_atendimento_id', 'pa_att.id')
->join('sigtap.ocupacao as oc', 'oc.id', 'p.ocupacao_id')
->select(['pa_att.paciente_id', 'pa.numero_ine', DB::raw('COUNT(*) as cnt')])
->where('p.data_usuario_responsavel', '<=', $periodoFinal)
->whereIn('oc.codigo', CboBoasPratricasCEnum::all())
->whereIn('pap.procedimento_id', ProcedimentosBoasPraticasC::all())
->whereIn('pa_att.paciente_id', $pacienteIds)
->groupBy(['pa_att.paciente_id', 'pa.numero_ine'])
->get()
);
return array_filter($counts, fn ($totalRegistros) => $totalRegistros >= self::CONSULTAS_MINIMAS);
}
private static function getBoaPraticaD(array $pacienteIds, $periodoFinal): array
{
return DB::table('atencao_basica_visita_domiciliar as vd')
->join('cadastro_paciente as cp', 'cp.id', 'vd.paciente_id')
->join('cadastro_cidadao as cc', 'cc.id', 'cp.cidadao_id')
->join('sigtap.ocupacao as oc', 'oc.id', 'vd.usuario_ocupacao_id')
->join('atencao_basica_parametrizacao_equipe as pe', 'pe.id', 'vd.equipe_id')
->join('atencao_basica_parametrizacao_area as pa', 'pa.id', 'pe.area_id')
->select(['vd.paciente_id', 'pa.numero_ine'])
->where('vd.data_usuario_responsavel', '<=', $periodoFinal)
->whereIn('oc.codigo', CboBoasPratricasDEnum::all())
->whereIn('pa.tipo_id', [AreaTipo::EQUIPE_DE_ATENCAO_PRIMARIA])
->whereIn('vd.paciente_id', $pacienteIds)
->whereExists(function ($subquery) {
$subquery->select(DB::raw(1))
->from('atencao_basica_visita_domiciliar_motivo_visita')
->whereColumn('atencao_basica_visita_domiciliar_motivo_visita.visita_domiciliar_id', 'vd.id')
->whereIn('atencao_basica_visita_domiciliar_motivo_visita.motivo_visita_id', [MotivoVisita::RECEM_NASCIDO, MotivoVisita::CRIANCA]);
})
->groupBy(['vd.paciente_id', 'pa.numero_ine'])
->havingRaw('SUM(CASE WHEN TIMESTAMPDIFF(DAY, cc.data_nascimento, vd.data_usuario_responsavel) <= ' . self::DIAS_LIMITE_PRIMEIRA_CONSULTA . ' THEN 1 ELSE 0 END) >= 1')
->havingRaw('SUM(CASE WHEN TIMESTAMPDIFF(DAY, cc.data_nascimento, vd.data_usuario_responsavel) > ' . self::DIAS_LIMITE_PRIMEIRA_CONSULTA . ' AND TIMESTAMPDIFF(DAY, cc.data_nascimento, vd.data_usuario_responsavel) <= ' . self::DIAS_LIMITE_SEGUNDA_VISITA . ' THEN 1 ELSE 0 END) >= 1')
->get()
->mapWithKeys(fn ($registro) => [trim($registro->paciente_id) . '-' . trim($registro->numero_ine) => true])
->toArray();
}
private static function getBoaPraticaE(array $pacienteIds, $pacientes, $periodoFinal): array
{
$gruposVacinais = self::getGruposVacinais();
$allVacinas = array_unique(array_merge(...array_column($gruposVacinais, 'vacinas')));
$aplicacoes = DB::table('imunizacao_registro_aplicacao as ira')
->join('imunizacao_entrada_vacina as iev', 'iev.id', 'ira.entrada_vacina_id')
->join('cadastro_vacina as cv', 'cv.id', 'iev.vacina_id')
->select(['ira.paciente_id', 'ira.data_aplicacao', 'cv.codigo_ria'])
->where('ira.data_aplicacao', '<=', $periodoFinal)
->whereIn('ira.paciente_id', $pacienteIds)
->whereIn('cv.codigo_ria', $allVacinas)
->distinct()
->get()
->groupBy('paciente_id');
$atualizacoes = DB::table('imunizacao_registro_atualizacao as iru')
->join('cadastro_vacina as cv', 'cv.id', 'iru.vacina_id')
->select(['iru.paciente_id', 'iru.data_atualizacao', 'cv.codigo_ria'])
->where('iru.data_atualizacao', '<=', $periodoFinal)
->whereIn('iru.paciente_id', $pacienteIds)
->whereIn('cv.codigo_ria', $allVacinas)
->distinct()
->get()
->groupBy('paciente_id');
$dataNascimentoMap = $pacientes->pluck('pacienteDataNascimento', 'pacienteId')->toArray();
$resultado = [];
foreach ($pacienteIds as $pacienteId) {
$registros = ($aplicacoes[$pacienteId] ?? collect())->merge($atualizacoes[$pacienteId] ?? collect());
$dataNascimento = Carbon::parse($dataNascimentoMap[$pacienteId] ?? null);
$valido = true;
foreach ($gruposVacinais as $grupo) {
$registrosGrupo = $registros->whereIn('codigo_ria', $grupo['vacinas'])->values();
$dnValidacao = isset($grupo['idadeMinMeses']) ? $dataNascimento : null;
if (!self::validarGrupoVacinal($registrosGrupo, $grupo['dosesMinimas'], $grupo['intervaloDias'], $dnValidacao, $grupo['idadeMinMeses'] ?? null)) {
$valido = false;
break;
}
}
$resultado[$pacienteId] = $valido;
}
return $resultado;
}
private static function getGruposVacinais(): array
{
return [
['vacinas' => [
VacinasEsquemaVacinalEnum::VACINA_HEPATITE_B_RECOMBINANTE,
VacinasEsquemaVacinalEnum::VACINA_HAEMOPHILUS_INFLUENZA_B_CONJUGADA,
VacinasEsquemaVacinalEnum::VACINA_DTPA_HIB_POLIO_INATIVA,
VacinasEsquemaVacinalEnum::VACINA_DTP_HIB,
VacinasEsquemaVacinalEnum::VACINA_DTP_HB_HIB,
VacinasEsquemaVacinalEnum::VACINA_HEXAVALENTE,
VacinasEsquemaVacinalEnum::VACINA_TRIPLA_BACTERIANA,
VacinasEsquemaVacinalEnum::VACINA_TRIPLA_ACELULAR_INFANTIL,
], 'dosesMinimas' => 3, 'intervaloDias' => 30],
['vacinas' => [
VacinasEsquemaVacinalEnum::VACINA_POLIOMIELITE_INATIVADA,
VacinasEsquemaVacinalEnum::VACINA_DTPA_HIB_POLIO_INATIVA,
VacinasEsquemaVacinalEnum::VACINA_HEXAVALENTE,
], 'dosesMinimas' => 3, 'intervaloDias' => 30],
['vacinas' => [
VacinasEsquemaVacinalEnum::VACINA_TRIPLA_VIRAL,
], 'dosesMinimas' => 2, 'intervaloDias' => 0, 'idadeMinMeses' => 12],
['vacinas' => [
VacinasEsquemaVacinalEnum::VACINA_PNEUMOCOCICA_10V,
VacinasEsquemaVacinalEnum::VACINA_PNEUMOCOCICA_13V,
VacinasEsquemaVacinalEnum::VACINA_PNEUMOCOCICA_15V,
], 'dosesMinimas' => 2, 'intervaloDias' => 30],
['vacinas' => [
VacinasEsquemaVacinalEnum::VACINA_TRIPLA_ACELULAR_POLIOMIELITE_INATIVADA,
], 'dosesMinimas' => 1, 'intervaloDias' => 0],
['vacinas' => [
VacinasEsquemaVacinalEnum::VACINA_TETRA_VIRAL,
], 'dosesMinimas' => 1, 'intervaloDias' => 0],
['vacinas' => [
VacinasEsquemaVacinalEnum::VACINA_PNEUMOCOCICA_20V,
], 'dosesMinimas' => 1, 'intervaloDias' => 0],
];
}
private static function validarGrupoVacinal($registros, $dosesMinimas, $intervaloDias = 0, $dataNascimento = null, $idadeMinMeses = null)
{
$registros = $registros->map(function ($item) {
$item->data = $item->data_aplicacao ?? $item->data_atualizacao;
return $item;
})->sortBy('data')->values();
if ($idadeMinMeses !== null && $dataNascimento) {
$registros = $registros->filter(function ($item) use ($dataNascimento, $idadeMinMeses) {
return Carbon::parse($item->data)->diffInMonths($dataNascimento) >= $idadeMinMeses;
})->values();
}
$dosesValidas = [];
$ultimaDataValida = null;
foreach ($registros as $item) {
$dataAtual = Carbon::parse($item->data);
if (!$ultimaDataValida) {
$dosesValidas[] = $dataAtual;
$ultimaDataValida = $dataAtual;
continue;
}
if ($intervaloDias === 0) {
$dosesValidas[] = $dataAtual;
continue;
}
if ($ultimaDataValida->diffInDays($dataAtual) >= $intervaloDias) {
$dosesValidas[] = $dataAtual;
$ultimaDataValida = $dataAtual;
}
}
return count($dosesValidas) >= $dosesMinimas;
}
private static function getEquipes($params, $dadosListagem = null)
{
$dadosListagem = $dadosListagem ?? self::getDadosListagem($params);
return $dadosListagem
->when($params->unidade_id, fn ($collection) => $collection->where('unidadeId', $params->unidade_id))
->when($params->infraestrutura_id, fn ($collection) => $collection->where('infraestruturaId', $params->infraestrutura_id))
->map(fn ($item) => (object) ['id' => $item->equipeId, 'descricao' => $item->equipeDescricao])
->filter(fn ($item) => !is_null($item->id))
->unique('id')
->sortBy('descricao')
->values();
}
private static function getInfraestruturas($params, $dadosListagem = null)
{
$dadosListagem = $dadosListagem ?? self::getDadosListagem($params);
return $dadosListagem
->when($params->unidade_id, fn ($collection) => $collection->where('unidadeId', $params->unidade_id))
->map(fn ($item) => (object) ['id' => $item->infraestruturaId, 'nome' => $item->infraestruturaNome])
->filter(fn ($item) => !is_null($item->id))
->unique('id')
->sortBy('nome')
->values();
}
private static function getUnidades($params, $dadosListagem = null)
{
$dadosListagem = $dadosListagem ?? self::getDadosListagem($params);
return $dadosListagem
->map(fn ($item) => (object) ['id' => $item->unidadeId, 'nome_fantasia' => $item->unidadeNomeFantasia])
->filter(fn ($item) => !is_null($item->id))
->unique('id')
->sortBy('nome_fantasia')
->values();
}
private static function getParams($request)
{
$faixaEtaria = self::formatarFaixaEtaria($request->input('faixa_etaria', []));
return (object) [
'periodo' => self::obterPeriodoRequest($request),
'quadrimestre' => $request->input('quadrimestre'),
'unidade_id' => $request->input('unidade_id'),
'infraestrutura_id' => $request->input('infraestrutura_id'),
'equipe_id' => $request->input('equipe_id'),
'sexo_id' => $request->input('sexo_id'),
'boas_praticas' => $request->input('boas_praticas') ?? [],
'faixa_etaria' => $faixaEtaria,
'paciente_id' => $request->input('paciente_id'),
'busca_ativa' => $request->input('busca_ativa') ?? false,
'fora_area' => $request->input('fora_area') ?? false,
];
}
private static function obterPeriodoRequest($request)
{
$periodoPorQuadrimestre = self::getPeriodoPorQuadrimestre($request->input('quadrimestre'));
if ($periodoPorQuadrimestre) {
return $periodoPorQuadrimestre;
}
$periodo = $request->input('periodo');
return [
'start' => Carbon::createFromFormat('d/m/Y', $periodo['start'])->setTime(0, 0, 0),
'end' => Carbon::createFromFormat('d/m/Y', $periodo['end'])->setTime(23, 59, 59),
];
}
private static function getPeriodoPorQuadrimestre($quadrimestre)
{
if (empty($quadrimestre) || !str_contains($quadrimestre, '-')) {
return null;
}
[$ano, $numero] = array_pad(explode('-', $quadrimestre), 2, null);
$faixas = [
1 => ['inicio' => [1, 1], 'fim' => [4, 30]],
2 => ['inicio' => [5, 1], 'fim' => [8, 31]],
3 => ['inicio' => [9, 1], 'fim' => [12, 31]],
];
$numero = (int) $numero;
$ano = (int) $ano;
if (!isset($faixas[$numero]) || $ano < 2020) {
return null;
}
return [
'start' => Carbon::create($ano, $faixas[$numero]['inicio'][0], $faixas[$numero]['inicio'][1])->startOfDay(),
'end' => Carbon::create($ano, $faixas[$numero]['fim'][0], $faixas[$numero]['fim'][1])->endOfDay(),
];
}
private static function formatarFaixaEtaria($faixaEtaria)
{
$sanitizar = function ($value, $max) {
if ($value === null || $value === '') {
return null;
}
return max(0, min((int) $value, $max));
};
return [
'inicial' => [
'anos' => $sanitizar($faixaEtaria['inicial']['anos'] ?? null, 2),
'meses' => $sanitizar($faixaEtaria['inicial']['meses'] ?? null, 11),
'dias' => $sanitizar($faixaEtaria['inicial']['dias'] ?? null, 30),
],
'final' => [
'anos' => $sanitizar($faixaEtaria['final']['anos'] ?? null, 2),
'meses' => $sanitizar($faixaEtaria['final']['meses'] ?? null, 11),
'dias' => $sanitizar($faixaEtaria['final']['dias'] ?? null, 30),
],
];
}
private static function hasFaixaEtaria($params)
{
$faixaEtaria = $params->faixa_etaria ?? ['inicial' => [], 'final' => []];
$inicial = $faixaEtaria['inicial'] ?? [];
$final = $faixaEtaria['final'] ?? [];
return isset($inicial['anos']) || isset($inicial['meses']) || isset($inicial['dias'])
|| isset($final['anos']) || isset($final['meses']) || isset($final['dias']);
}
private static function idadeParaDataNascimento(Carbon $referencia, array $idade)
{
return $referencia
->copy()
->subYears((int) ($idade['anos'] ?? 0))
->subMonths((int) ($idade['meses'] ?? 0))
->subDays((int) ($idade['dias'] ?? 0));
}
private static function aplicarFiltroFaixaEtaria($query, $params)
{
$referencia = Carbon::today()->endOfDay();
$inicial = $params->faixa_etaria['inicial'];
$final = $params->faixa_etaria['final'];
$temInicial = isset($inicial['anos']) || isset($inicial['meses']) || isset($inicial['dias']);
$temFinal = isset($final['anos']) || isset($final['meses']) || isset($final['dias']);
if ($temFinal) {
$nascimentoMinimo = self::idadeParaDataNascimento($referencia, $final);
$query->whereDate('cadastro_cidadao.data_nascimento', '>', $nascimentoMinimo->toDateString());
}
if ($temInicial) {
$nascimentoMaximo = self::idadeParaDataNascimento($referencia, $inicial);
$query->whereDate('cadastro_cidadao.data_nascimento', '<=', $nascimentoMaximo->toDateString());
}
return $query;
}
private static function getDataNascimentoLimite($params)
{
return $params->periodo['end']->copy()->subYears(2);
}
private static function getAreaTipos()
{
return [
AreaTipo::EQUIPE_DE_SAUDE_DA_FAMILIA,
AreaTipo::EQUIPE_DE_ATENCAO_PRIMARIA,
];
}
private static function obterMapaSexo()
{
return [
Sexo::MASCULINO => 'Masculino',
Sexo::FEMININO => 'Feminino',
];
}
private static function getAnos()
{
$anos = [];
for ($ano = now()->year; $ano >= 2020; $ano--) {
$anos[] = ['id' => $ano, 'descricao' => (string) $ano];
}
return $anos;
}
private static function getQuadrimestres()
{
return [
['id' => 1, 'descricao' => '1º Quadrimestre (Jan - Abr)', 'inicio' => '01-01', 'fim' => '04-30'],
['id' => 2, 'descricao' => '2º Quadrimestre (Mai - Ago)', 'inicio' => '05-01', 'fim' => '08-31'],
['id' => 3, 'descricao' => '3º Quadrimestre (Set - Dez)', 'inicio' => '09-01', 'fim' => '12-31'],
];
}
private static function getBoasPraticas()
{
return collect([
['value' => 'A', 'text' => 'A - 1ª consulta até 30 dias de idade'],
['value' => 'B', 'text' => 'B - 9 consultas até 2 anos de idade'],
['value' => 'C', 'text' => 'C - 9 registros simultâneos de peso e altura'],
['value' => 'D', 'text' => 'D - 2 visitas domiciliares'],
['value' => 'E', 'text' => 'E - Vacinação completa'],
]);
}
}