diff --git a/Application/ProcessarMedicoesUseCase.cs b/Application/ProcessarMedicoesUseCase.cs index 2fbddc7..ba41507 100644 --- a/Application/ProcessarMedicoesUseCase.cs +++ b/Application/ProcessarMedicoesUseCase.cs @@ -1,9 +1,10 @@ using System.Collections.Concurrent; +using System.Globalization; using System.Text; using System.Xml.Linq; using Domain; using Infrastructure; -using System.Globalization; +using static System.Runtime.InteropServices.JavaScript.JSType; namespace Application { @@ -37,99 +38,21 @@ namespace Application await Parallel.ForEachAsync(perfis, async (perfil, ct) => { - try + Console.WriteLine($"{DateTime.Now}: Iniciado ponto {perfil.CodigoSCDE}"); + if (perfil.Codigo5Minutos == "0" || perfil.Codigo5Minutos == string.Empty) { - Console.WriteLine($"{DateTime.Now}: Iniciado ponto {perfil.CodigoSCDE}"); - if (perfil.Codigo5Minutos == "0" || perfil.Codigo5Minutos == string.Empty) - { - Console.WriteLine($"Pular {perfil.CodigoSCDE} - (cod 5 min pendente)"); - errosPersistentes.Add($"{perfil.Codigo5Minutos};{perfil.CodigoSCDE}; cod_5min pendente"); - return; - } - - var existentes = (await _postgresRepository.ObterMedicoesAsync(perfil.CodigoSCDE, dataIni, dataFim, ct)); - - foreach (DateTime dia in datas) - { - int tentativas = 0; - bool sucesso = false; - - while (tentativas < 5 && !sucesso) - { - if (perfil.DataDeMigracao > dia) - { - Console.WriteLine($"Pular {perfil.CodigoSCDE} - {dia.ToShortDateString()} (antes da migração)"); - errosPersistentes.Add($"{perfil.Codigo5Minutos};{perfil.CodigoSCDE};Fora da data de migração {perfil.DataDeMigracao} x {dia}"); - break; // não tentar antes da data de migração - } - try - { - string payload = Xml_requisicao(dia, perfil.Codigo5Minutos, perfil.CodigoSCDE, 1); - var conteudo = new StringContent(payload, Encoding.UTF8, "application/xml"); - - await _rateLimiter.WaitAsync(ct); - using var response = await _httpClient.PostAsync(endpoint, conteudo, ct); - string resposta = await response.Content.ReadAsStringAsync(); - - if ((int)response.StatusCode >= 400) - { - try - { - SoapHelper.VerificarRespostaSOAP(resposta); - } - catch (SoapFaultException ex) - { - if (ex.ErrorCode == "2003") // limite de requisições - { - var now = DateTime.UtcNow; - var delay = 60000 - (now.Second * 1000 + now.Millisecond); - Console.WriteLine($"!! Limite de requisições atingido. Aguardando até {DateTime.Now.AddMilliseconds(delay)}"); - await Task.Delay(delay, ct); // tentar de novo sem contar como falha - continue; - } - if (ex.ErrorCode == "4001") // Dados não encontrados - { - errosPersistentes.Add($"{perfil.Codigo5Minutos};{perfil.CodigoSCDE};SOAP Fault: {ex.ErrorCode};{ex.ErrorMessage.Replace("\n", "-n-")}"); - break; - } - if (ex.ErrorCode == "2001") // Sem acesso - { - errosPersistentes.Add($"{perfil.Codigo5Minutos};{perfil.CodigoSCDE};SOAP Fault: {ex.ErrorCode};{ex.ErrorMessage.Replace("\n", "-n-")}"); - break; - } - else - { - throw; - } - } - } - - await ProcessarXMLAsync(resposta, dia, perfil.Codigo5Minutos, perfil.CodigoSCDE, existentes, ct, 1); - - sucesso = true; - } - catch (Exception ex) - { - tentativas++; - if (tentativas >= 5) - { - errosPersistentes.Add($"{perfil.Codigo5Minutos};{perfil.CodigoSCDE};Erro;{ex.Message.Replace("\n", "-n-")}"); - } - else - { - int backoff = (int)Math.Pow(2.4, tentativas) * 1000; // exponencial - Console.WriteLine($"Erro na requisição ({ex.Message}), tentativa {tentativas}. Aguardando {backoff / 1000}s..."); - await Task.Delay(backoff); - } - } - } - } - Console.WriteLine($"{DateTime.Now}: Finalizado ponto {perfil.CodigoSCDE}"); + Console.WriteLine($"Pular {perfil.CodigoSCDE} - (cod 5 min pendente)"); + errosPersistentes.Add($"{perfil.Codigo5Minutos};{perfil.CodigoSCDE}; cod_5min pendente"); + return; } - catch (Exception ex) + + var existentes = await ObterMedicoesComRetry(perfil.CodigoSCDE, dataIni, dataFim, ct, errosPersistentes); + + foreach (DateTime dia in datas) { - Console.WriteLine(ex.ToString()); + await ProcessarDiaAsync(perfil, dia, existentes, endpoint, errosPersistentes, ct); } + Console.WriteLine($"{DateTime.Now}: Finalizado ponto {perfil.CodigoSCDE}"); }); if (errosPersistentes.Count > 0) @@ -137,6 +60,105 @@ namespace Application File.WriteAllLines(caminhoLog, new[] { "Perfil;Ponto;Status;Message" }.Concat(errosPersistentes)); } } + + private async Task> ObterMedicoesComRetry( + string codigoSCDE, DateTime dataIni, DateTime dataFim, CancellationToken ct, ConcurrentBag errosPersistentes) + { + int tentativas = 0; + while (tentativas < 3) + { + try + { + return await _postgresRepository.ObterMedicoesAsync(codigoSCDE, dataIni, dataFim, ct); + } + catch (Exception ex) + { + tentativas++; + if (tentativas >= 3) + { + errosPersistentes.Add($"{codigoSCDE};Erro;{ex.Message.Replace("\n", "-n-")}"); + throw; + } + int backoff = (int)Math.Pow(2, tentativas) * 1000; + Console.WriteLine($"Erro ao acessar Postgres ({ex.Message}), tentativa {tentativas}. Aguardando {backoff / 1000}s..."); + await Task.Delay(backoff, ct); + } + } + return new Dictionary<(string, double, int), Medicao>(); + } + + private async Task ProcessarDiaAsync( + Perfil perfil, + DateTime dia, + IDictionary<(string, double, int), Medicao> existentes, + Uri endpoint, + ConcurrentBag errosPersistentes, + CancellationToken ct) + { + if (perfil.DataDeMigracao > dia) + { + Console.WriteLine($"Pular {perfil.CodigoSCDE} - {dia.ToShortDateString()} (antes da migração)"); + errosPersistentes.Add($"{perfil.Codigo5Minutos};{perfil.CodigoSCDE};Fora da data de migração {perfil.DataDeMigracao} x {dia}"); + return; + } + + int tentativas = 0; + bool sucesso = false; + while (tentativas < 5 && !sucesso) + { + try + { + string payload = Xml_requisicao(dia, perfil.Codigo5Minutos, perfil.CodigoSCDE, 1); + var conteudo = new StringContent(payload, Encoding.UTF8, "application/xml"); + + await _rateLimiter.WaitAsync(ct); + using var response = await _httpClient.PostAsync(endpoint, conteudo, ct); + string resposta = await response.Content.ReadAsStringAsync(); + + if ((int)response.StatusCode >= 400) + { + try + { + SoapHelper.VerificarRespostaSOAP(resposta); + } + catch (SoapFaultException ex) + { + if (ex.ErrorCode == "2003") + { + var now = DateTime.UtcNow; + var delay = 60000 - (now.Second * 1000 + now.Millisecond); + Console.WriteLine($"!! Limite de requisições atingido. Aguardando até {DateTime.Now.AddMilliseconds(delay)}"); + await Task.Delay(delay, ct); + continue; + } + if (ex.ErrorCode == "4001" || ex.ErrorCode == "2001") + { + errosPersistentes.Add($"{perfil.Codigo5Minutos};{perfil.CodigoSCDE};SOAP Fault: {ex.ErrorCode};{ex.ErrorMessage.Replace("\n", "-n-")}"); + break; + } + throw; + } + } + + await ProcessarXMLAsync(resposta, dia, perfil.Codigo5Minutos, perfil.CodigoSCDE, existentes, ct, 1); + sucesso = true; + } + catch (Exception ex) + { + tentativas++; + if (tentativas >= 5) + { + errosPersistentes.Add($"{perfil.Codigo5Minutos};{perfil.CodigoSCDE};Erro;{ex.Message.Replace("\n", "-n-")}"); + } + else + { + int backoff = (int)Math.Pow(2.4, tentativas) * 1000; + Console.WriteLine($"Erro na requisição ({ex.Message}), tentativa {tentativas}. Aguardando {backoff / 1000}s..."); + await Task.Delay(backoff, ct); + } + } + } + } private async Task ProcessarXMLAsync( string xml, DateTime dia, @@ -208,8 +230,7 @@ namespace Application { var logica = g.FirstOrDefault(x => x.Origem == "Inspeção Lógica"); return logica ?? g.First(); - }); - + }).ToList(); var minutosEsperados = new[] { 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60 }; @@ -218,55 +239,53 @@ namespace Application .ToList(); var medidasComEstimativa = new List(); - - foreach (var grupoHora in medidasPorHora) + for (int hora = 0; hora < 24; hora++) { - var lista = grupoHora.OrderBy(m => m.Minuto).ToList(); + var grupoHora = medidasPorHora.Where(h => h.Key.Hora == hora).ToList(); + var lista = grupoHora.SelectMany(g => g).OrderBy(m => m.Minuto).ToList(); var minutosPresentes = lista.Select(m => m.Minuto).ToHashSet(); - var minutosEsperadosAbsolutos = minutosEsperados.Select(m => m + (60 * grupoHora.Key.Hora)).ToList(); + var minutosEsperadosAbsolutos = minutosEsperados.Select(m => m + (60 * hora)).ToList(); var faltantes = minutosEsperadosAbsolutos.Except(minutosPresentes).OrderBy(m => m).ToList(); // Use apenas valores reais para interpolação var reais = lista.ToDictionary(m => m.Minuto, m => m); - // Identifique sequências de minutos faltantes consecutivos - var sequencias = new List>(); - List atual = null; - int? ultimo = null; - foreach (var min in faltantes) - { - if (atual == null || ultimo == null || min != ultimo + 5) - { - atual = new List(); - sequencias.Add(atual); - } - atual.Add(min); - ultimo = min; - } - var estimadas = new List(); - foreach (var seq in sequencias) + foreach (var faltante in faltantes) { - int minIni = seq.First(); - int minFim = seq.Last(); - - // Busca anterior real - var anterior = reais.Values.Where(m => m.Minuto < minIni).OrderByDescending(m => m.Minuto).FirstOrDefault(); - // Busca posterior real - var posterior = reais.Values.Where(m => m.Minuto > minFim).OrderBy(m => m.Minuto).FirstOrDefault(); - - foreach (var minFaltante in seq) + if (faltantes.Count > 3) { - var ativaConsumo = Interpolar(anterior?.Minuto, anterior?.AtivaConsumo, posterior?.Minuto, posterior?.AtivaConsumo, minFaltante) ?? 0; - var ativaGeracao = Interpolar(anterior?.Minuto, anterior?.AtivaGeracao, posterior?.Minuto, posterior?.AtivaGeracao, minFaltante) ?? 0; - var reativaConsumo = Interpolar(anterior?.Minuto, anterior?.ReativaConsumo, posterior?.Minuto, posterior?.ReativaConsumo, minFaltante) ?? 0; - var reativaGeracao = Interpolar(anterior?.Minuto, anterior?.ReativaGeracao, posterior?.Minuto, posterior?.ReativaGeracao, minFaltante) ?? 0; + // Se mais de 3 faltantes na hora, não faz estimativa + var estimada = new Medicao( + ponto + "P", + (dia.ToOADate() - dia.ToOADate() % 1), + faltante, + "Faltante", + null, + null, + null, + null + ); + estimadas.Add(estimada); + continue; + } + else + { + // Busca anterior real + var anterior = reais.Values.Where(m => m.Minuto < faltante).OrderByDescending(m => m.Minuto).FirstOrDefault(); + // Busca posterior real + var posterior = reais.Values.Where(m => m.Minuto > faltante).OrderBy(m => m.Minuto).FirstOrDefault(); + + var ativaConsumo = Interpolar(anterior?.Minuto, anterior?.AtivaConsumo, posterior?.Minuto, posterior?.AtivaConsumo, faltante) ?? 0; + var ativaGeracao = Interpolar(anterior?.Minuto, anterior?.AtivaGeracao, posterior?.Minuto, posterior?.AtivaGeracao, faltante) ?? 0; + var reativaConsumo = Interpolar(anterior?.Minuto, anterior?.ReativaConsumo, posterior?.Minuto, posterior?.ReativaConsumo, faltante) ?? 0; + var reativaGeracao = Interpolar(anterior?.Minuto, anterior?.ReativaGeracao, posterior?.Minuto, posterior?.ReativaGeracao, faltante) ?? 0; var estimada = new Medicao( - grupoHora.Key.Ponto, - grupoHora.Key.DiaNum, - minFaltante, + grupoHora.First().Key.Ponto, + grupoHora.First().Key.DiaNum, + faltante, "Estimado", ativaConsumo, ativaGeracao, diff --git a/Domain/Medicao.cs b/Domain/Medicao.cs index 1a78824..ddd9486 100644 --- a/Domain/Medicao.cs +++ b/Domain/Medicao.cs @@ -5,9 +5,9 @@ double DiaNum, int Minuto, string Origem, - double AtivaConsumo, - double AtivaGeracao, - double ReativaConsumo, - double ReativaGeracao + double? AtivaConsumo, + double? AtivaGeracao, + double? ReativaConsumo, + double? ReativaGeracao ); } diff --git a/Infrastructure/AccessRepository.cs b/Infrastructure/AccessRepository.cs index 1cad825..0865961 100644 --- a/Infrastructure/AccessRepository.cs +++ b/Infrastructure/AccessRepository.cs @@ -21,8 +21,8 @@ namespace Infrastructure string sql = $"SELECT Cod_5min, Codigo_SCDE, Data_de_Migracao FROM Dados_cadastrais WHERE LEN(Codigo_SCDE) > 5 AND Unidade_gerenciada ORDER BY cod_smart_unidade"; //string sql = "SELECT Cod_5min, Codigo_SCDE, Data_de_Migracao FROM Dados_cadastrais WHERE LEN(Codigo_SCDE) > 5 and (Cliente = 'RMC ALIMENTOS' OR Cliente = 'FERREIRA SUPERMERCADO' OR Cliente = 'VANGUARDA ALIMENTOS') AND Unidade_gerenciada ORDER BY PerfilCCEE"; - //string sql = "SELECT Cod_5min, Codigo_SCDE, Data_de_Migracao FROM Dados_cadastrais WHERE LEN(Codigo_SCDE) > 5 and Cliente = 'ALMAVIVA' and Unidade = 'GUARULHOS' AND unidadade_gerenciada ORDER BY PerfilCCEE"; - //string sql = "SELECT Cod_5min, Codigo_SCDE, Data_de_Migracao FROM Dados_cadastrais WHERE LEN(Codigo_SCDE) > 5 and Cliente = 'aeroflex' AND Unidade_gerenciada ORDER BY PerfilCCEE"; + //string sql = "SELECT Cod_5min, Codigo_SCDE, Data_de_Migracao FROM Dados_cadastrais WHERE LEN(Codigo_SCDE) > 5 and Cliente = 'ABEVÊ' and Unidade = 'ABV LOJA 29 - COXIM' AND Unidade_gerenciada ORDER BY PerfilCCEE"; + //string sql = "SELECT Cod_5min, Codigo_SCDE, Data_de_Migracao FROM Dados_cadastrais WHERE LEN(Codigo_SCDE) > 5 and Cliente = 'calvi granitos' AND Unidade_gerenciada ORDER BY PerfilCCEE"; using var command = new OleDbCommand(sql, connection); using var reader = await command.ExecuteReaderAsync(ct); diff --git a/Infrastructure/PostgresRepository.cs b/Infrastructure/PostgresRepository.cs index 43fedbd..1bf36c0 100644 --- a/Infrastructure/PostgresRepository.cs +++ b/Infrastructure/PostgresRepository.cs @@ -36,10 +36,10 @@ namespace Infrastructure reader.GetDouble(1), reader.GetInt32(2), reader.GetString(3), - reader.GetDouble(4), - reader.GetDouble(5), - reader.GetDouble(6), - reader.GetDouble(7) + reader.IsDBNull(4) ? (double?)null : reader.GetDouble(4), + reader.IsDBNull(5) ? (double?)null : reader.GetDouble(5), + reader.IsDBNull(6) ? (double?)null : reader.GetDouble(6), + reader.IsDBNull(7) ? (double?)null : reader.GetDouble(7) ); existentes[(medicao.Ponto, medicao.DiaNum, medicao.Minuto)] = medicao; @@ -88,10 +88,10 @@ namespace Infrastructure AND minuto = @minuto;"); cmd.Parameters.AddWithValue("origem", m.Origem); - cmd.Parameters.AddWithValue("ativa_consumo", m.AtivaConsumo); - cmd.Parameters.AddWithValue("ativa_geracao", m.AtivaGeracao); - cmd.Parameters.AddWithValue("reativa_consumo", m.ReativaConsumo); - cmd.Parameters.AddWithValue("reativa_geracao", m.ReativaGeracao); + cmd.Parameters.AddWithValue("ativa_consumo", NpgsqlDbType.Numeric, m.AtivaConsumo ?? (object)DBNull.Value); + cmd.Parameters.AddWithValue("ativa_geracao", NpgsqlDbType.Numeric, m.AtivaGeracao ?? (object)DBNull.Value); + cmd.Parameters.AddWithValue("reativa_consumo", NpgsqlDbType.Numeric, m.ReativaConsumo ?? (object)DBNull.Value); + cmd.Parameters.AddWithValue("reativa_geracao", NpgsqlDbType.Numeric, m.ReativaGeracao ?? (object)DBNull.Value); cmd.Parameters.AddWithValue("ponto", m.Ponto); cmd.Parameters.AddWithValue("dia_num", m.DiaNum); cmd.Parameters.AddWithValue("minuto", m.Minuto); diff --git a/Presentation/Program.cs b/Presentation/Program.cs index 4c6d63b..63555ac 100644 --- a/Presentation/Program.cs +++ b/Presentation/Program.cs @@ -12,8 +12,8 @@ class Program string caminhoLog = $@"\\srv-dados\documentos\Back\Carteira x.x\Codigo\Erros\log_erros_{inicio:MM_dd_HH_mm}.csv"; //DateTime dataIni = new DateTime(inicio.Year, inicio.Month, 1); //DateTime dataFim = new DateTime(inicio.Year, inicio.Month, inicio.Day); - DateTime dataIni = new DateTime(inicio.Year, 1, 1); - DateTime dataFim = new DateTime(inicio.Year, 2, 1); + DateTime dataIni = new DateTime(inicio.Year, 10, 1); + DateTime dataFim = new DateTime(inicio.Year, 10, 14); // Configuração de dependências (pode usar um container DI depois) var postgresRepo = new PostgresRepository(PG_CONN_STRING_PROD);