Melhoria do processo de erros definitivos e retry.

Melhoria do processo de estimativa de dados faltantes para atender metodologia da CCEE.

Adapatação de modelos para poder inserir dados nulos (faltantes) no banco de dados.
This commit is contained in:
Adriano Serighelli 2025-10-15 11:09:23 -03:00
parent 460598c6b5
commit a8d87addad
5 changed files with 163 additions and 144 deletions

View File

@ -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<IDictionary<(string, double, int), Medicao>> ObterMedicoesComRetry(
string codigoSCDE, DateTime dataIni, DateTime dataFim, CancellationToken ct, ConcurrentBag<string> 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<string> 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<Medicao>();
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<int>>();
List<int> atual = null;
int? ultimo = null;
foreach (var min in faltantes)
{
if (atual == null || ultimo == null || min != ultimo + 5)
{
atual = new List<int>();
sequencias.Add(atual);
}
atual.Add(min);
ultimo = min;
}
var estimadas = new List<Medicao>();
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,

View File

@ -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
);
}

View File

@ -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);

View File

@ -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);

View File

@ -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);