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
{
@ -36,8 +37,6 @@ namespace Application
var datas = Enumerable.Range(0, (dataFim - dataIni).Days).Select(i => dataIni.AddDays(i));
await Parallel.ForEachAsync(perfis, async (perfil, ct) =>
{
try
{
Console.WriteLine($"{DateTime.Now}: Iniciado ponto {perfil.CodigoSCDE}");
if (perfil.Codigo5Minutos == "0" || perfil.Codigo5Minutos == string.Empty)
@ -47,21 +46,66 @@ namespace Application
return;
}
var existentes = (await _postgresRepository.ObterMedicoesAsync(perfil.CodigoSCDE, dataIni, dataFim, ct));
var existentes = await ObterMedicoesComRetry(perfil.CodigoSCDE, dataIni, dataFim, ct, errosPersistentes);
foreach (DateTime dia in datas)
{
int tentativas = 0;
bool sucesso = false;
await ProcessarDiaAsync(perfil, dia, existentes, endpoint, errosPersistentes, ct);
}
Console.WriteLine($"{DateTime.Now}: Finalizado ponto {perfil.CodigoSCDE}");
});
while (tentativas < 5 && !sucesso)
if (errosPersistentes.Count > 0)
{
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}");
break; // não tentar antes da data de migração
return;
}
int tentativas = 0;
bool sucesso = false;
while (tentativas < 5 && !sucesso)
{
try
{
string payload = Xml_requisicao(dia, perfil.Codigo5Minutos, perfil.CodigoSCDE, 1);
@ -79,33 +123,24 @@ namespace Application
}
catch (SoapFaultException ex)
{
if (ex.ErrorCode == "2003") // limite de requisições
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); // tentar de novo sem contar como falha
await Task.Delay(delay, ct);
continue;
}
if (ex.ErrorCode == "4001") // Dados não encontrados
if (ex.ErrorCode == "4001" || ex.ErrorCode == "2001")
{
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)
@ -117,26 +152,13 @@ namespace Application
}
else
{
int backoff = (int)Math.Pow(2.4, tentativas) * 1000; // exponencial
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);
await Task.Delay(backoff, ct);
}
}
}
}
Console.WriteLine($"{DateTime.Now}: Finalizado ponto {perfil.CodigoSCDE}");
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
});
if (errosPersistentes.Count > 0)
{
File.WriteAllLines(caminhoLog, new[] { "Perfil;Ponto;Status;Message" }.Concat(errosPersistentes));
}
}
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)
{
if (faltantes.Count > 3)
{
// 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
{
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();
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 > minFim).OrderBy(m => m.Minuto).FirstOrDefault();
var posterior = reais.Values.Where(m => m.Minuto > faltante).OrderBy(m => m.Minuto).FirstOrDefault();
foreach (var minFaltante in seq)
{
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;
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);