Unificação do arquivo do registro de operações e maior detalhanmento em caso de update.

This commit is contained in:
Adriano Serighelli 2025-11-17 12:55:56 -03:00
parent 817d542631
commit e4c398eef0
2 changed files with 86 additions and 38 deletions

View File

@ -30,10 +30,49 @@ namespace Application
_rateLimiter?.Dispose();
}
private enum LogType { Info, Error, Operation, UpdateMeasurement }
private record LogItem(
LogType Tipo,
string Perfil,
string Ponto,
double DiaNum,
int Minuto,
string Status,
string Mensagem,
int Inseridos,
int Atualizados,
Medicao? Antes,
Medicao? Depois,
DateTime Timestamp)
{
public string ToCsvLine()
{
static string Esc(string? s) => (s ?? "").Replace(";", ",").Replace("\r", " ").Replace("\n", " ");
static string FMed(Medicao? m) =>
m is null ? "" : $"{Esc(m.Ponto)}|{m.DiaNum}|{m.Minuto}|{Esc(m.Origem)}|{m.AtivaConsumo?.ToString(CultureInfo.InvariantCulture) ?? ""}|{m.AtivaGeracao?.ToString(CultureInfo.InvariantCulture) ?? ""}|{m.ReativaConsumo?.ToString(CultureInfo.InvariantCulture) ?? ""}|{m.ReativaGeracao?.ToString(CultureInfo.InvariantCulture) ?? ""}";
return string.Join(";", new[]
{
Tipo.ToString(),
Esc(Perfil),
Esc(Ponto),
DiaNum.ToString(CultureInfo.InvariantCulture),
Minuto.ToString(),
Esc(Status),
Esc(Mensagem),
Inseridos.ToString(),
Atualizados.ToString(),
FMed(Antes),
FMed(Depois),
Timestamp.ToString("o")
});
}
}
public async Task ExecuteAsync(DateTime dataIni, DateTime dataFim, string caminhoLog, CancellationToken ct)
{
var errosPersistentes = new ConcurrentBag<string>();
var operacoesLog = new ConcurrentBag<string>();
var logs = new ConcurrentBag<LogItem>();
var perfis = (await _accessRepository.ObterPerfisAsync(ct)).ToList();
var endpoint = new Uri("https://servicos.ccee.org.br/ws/v2/MedidaCincoMinutosBSv2");
@ -41,42 +80,39 @@ namespace Application
await Parallel.ForEachAsync(perfis, async (perfil, ctPerfil) =>
{
//Console.WriteLine($"{DateTime.Now}: Iniciado ponto {perfil.CodigoSCDE}");
if (perfil.Codigo5Minutos == "0" || perfil.Codigo5Minutos == string.Empty || perfil.Codigo5Minutos == null)
{
Console.WriteLine($"Pular {perfil.CodigoSCDE} - (cod 5 min pendente)");
errosPersistentes.Add($"{perfil.Codigo5Minutos};{perfil.CodigoSCDE};{dataIni.ToOADate()};ERRO;cod_5min pendente");
logs.Add(new LogItem(LogType.Error, perfil.Codigo5Minutos ?? "", perfil.CodigoSCDE ?? "", dataIni.ToOADate(), 0, "ERRO", "cod_5min pendente", 0, 0, null, null, DateTime.UtcNow));
return;
}
var existentes = await ObterMedicoesComRetry(perfil.CodigoSCDE, dataIni, dataFim, ctPerfil, errosPersistentes);
var existentes = await ObterMedicoesComRetry(perfil.CodigoSCDE, dataIni, dataFim, ctPerfil, logs);
// Paraleliza os dias deste perfil; o semáforo limita as requisições simultâneas
await Parallel.ForEachAsync(datas, ctPerfil, async (dia, ctDia) =>
{
try
{
await ProcessarDiaAsync(perfil, dia, existentes, endpoint, errosPersistentes, operacoesLog, ctDia);
await ProcessarDiaAsync(perfil, dia, existentes, endpoint, logs, ctDia);
}
catch (Exception ex)
{
operacoesLog.Add($"{perfil.Codigo5Minutos};{perfil.CodigoSCDE};{dia.ToOADate()};ERRO;{ex.Message.Replace("\n", "-n-")}");
logs.Add(new LogItem(LogType.Error, perfil.Codigo5Minutos ?? "", perfil.CodigoSCDE ?? "", dia.ToOADate(), 0, "ERRO", ex.Message.Replace("\n", "-n-"), 0, 0, null, null, DateTime.UtcNow));
}
});
//Console.WriteLine($"{DateTime.Now}: Finalizado ponto {perfil.CodigoSCDE}");
});
// Cabeçalho do log
var linhasLog = new List<string> { "Perfil;Ponto;DiaNum;Status;Mensagem;Inseridos;Atualizados" };
linhasLog.AddRange(operacoesLog);
linhasLog.AddRange(errosPersistentes);
var linhas = new List<string>
{
"Tipo;Perfil;Ponto;DiaNum;Minuto;Status;Mensagem;Inseridos;Atualizados;Antes;Depois;Timestamp"
};
linhas.AddRange(logs.Select(l => l.ToCsvLine()));
File.WriteAllLines(caminhoLog, linhasLog);
File.WriteAllLines(caminhoLog, linhas);
}
private async Task<IDictionary<(string, double, int), Medicao>> ObterMedicoesComRetry(
string codigoSCDE, DateTime dataIni, DateTime dataFim, CancellationToken ct, ConcurrentBag<string> errosPersistentes)
string codigoSCDE, DateTime dataIni, DateTime dataFim, CancellationToken ct, ConcurrentBag<LogItem> logs)
{
int tentativas = 0;
while (tentativas < 3)
@ -90,7 +126,7 @@ namespace Application
tentativas++;
if (tentativas >= 3)
{
errosPersistentes.Add($";{codigoSCDE};{dataIni.ToOADate()};Erro;{ex.Message.Replace("\n", "-n-")}");
logs.Add(new LogItem(LogType.Error, "", codigoSCDE, dataIni.ToOADate(), 0, "Erro", ex.Message.Replace("\n", "-n-"), 0, 0, null, null, DateTime.UtcNow));
throw;
}
int backoff = (int)Math.Pow(2, tentativas) * 1000;
@ -104,21 +140,18 @@ namespace Application
private async Task ProcessarDiaAsync(
Perfil perfil,
DateTime dia,
IDictionary<(string, double, int),
Medicao> existentes,
IDictionary<(string, double, int), Medicao> existentes,
Uri endpoint,
ConcurrentBag<string> errosPersistentes,
ConcurrentBag<string> operacoesLog,
ConcurrentBag<LogItem> logs,
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}");
logs.Add(new LogItem(LogType.Info, perfil.Codigo5Minutos ?? "", perfil.CodigoSCDE ?? "", dia.ToOADate(), 0, "Fora da data de migração", $"Data de migração {perfil.DataDeMigracao} x {dia}", 0, 0, null, null, DateTime.UtcNow));
return;
}
// Acumulador de medidas (todas as páginas)
var acumulador = new List<XElement>();
int paginaAtual = 1;
@ -168,7 +201,7 @@ namespace Application
if (ex.ErrorCode == "4001" || ex.ErrorCode == "2001")
{
// erro persistente, registra e interrompe processamento deste dia/ponto
errosPersistentes.Add($"{perfil.Codigo5Minutos};{perfil.CodigoSCDE};{dia.ToOADate()};SOAP Fault: {ex.ErrorCode};{ex.ErrorMessage.Replace("\n", "-n-")}");
logs.Add(new LogItem(LogType.Error, perfil.Codigo5Minutos ?? "", perfil.CodigoSCDE ?? "", dia.ToOADate(), 0, "SOAP Fault", $"{ex.ErrorCode};{ex.ErrorMessage.Replace("\n", "-n-")}", 0, 0, null, null, DateTime.UtcNow));
return;
}
throw;
@ -199,7 +232,7 @@ namespace Application
tentativas++;
if (tentativas >= 5)
{
errosPersistentes.Add($"{perfil.Codigo5Minutos};{perfil.CodigoSCDE};{dia.ToOADate()};Erro;{ex.Message.Replace("\n", "-n-")}");
logs.Add(new LogItem(LogType.Error, perfil.Codigo5Minutos ?? "", perfil.CodigoSCDE ?? "", dia.ToOADate(), 0, "Erro", ex.Message.Replace("\n", "-n-"), 0, 0, null, null, DateTime.UtcNow));
// aborta o processamento do dia após falhas repetidas na mesma página
return;
}
@ -216,11 +249,11 @@ namespace Application
// ao final de todas as páginas, processa o XML acumulado
try
{
await ProcessarXMLAsync(acumulador, dia, perfil.Codigo5Minutos, perfil.CodigoSCDE, existentes, ct, operacoesLog);
await ProcessarXMLAsync(acumulador, dia, perfil.Codigo5Minutos, perfil.CodigoSCDE, existentes, ct, logs);
}
catch (Exception ex)
{
errosPersistentes.Add($"{perfil.Codigo5Minutos};{perfil.CodigoSCDE};{dia.ToOADate()};Erro;{ex.Message.Replace("\n", "-n-")}");
logs.Add(new LogItem(LogType.Error, perfil.Codigo5Minutos ?? "", perfil.CodigoSCDE ?? "", dia.ToOADate(), 0, "Erro", ex.Message.Replace("\n", "-n-"), 0, 0, null, null, DateTime.UtcNow));
}
}
@ -231,9 +264,10 @@ namespace Application
string ponto,
IDictionary<(string, double, int), Medicao> existentes,
CancellationToken ct,
ConcurrentBag<string>? operacoesLog = null)
ConcurrentBag<LogItem>? logs = null)
{
// Processa as medidas já acumuladas (antes chamadas faziam paginação)
logs ??= new ConcurrentBag<LogItem>();
XNamespace ns = "http://xmlns.energia.org.br/BO/v2";
var medidasProcessadas = acumulador
@ -305,7 +339,7 @@ namespace Application
{
if (faltantes.Count > 3)
{
// Se mais de 3 faltantes na hora, n<EFBFBD>o faz estimativa
// Se mais de 3 faltantes na hora, não faz estimativa
var estimada = new Medicao(
ponto + "P",
(dia.ToOADate() - dia.ToOADate() % 1),
@ -365,6 +399,20 @@ namespace Application
Math.Round(existente.ReativaGeracao ?? 0, 10) != Math.Round(m.ReativaGeracao ?? 0, 10))
{
alterados.Add(m);
// log detalhado por medição alterada
logs.Add(new LogItem(
LogType.UpdateMeasurement,
perfil,
m.Ponto,
m.DiaNum,
m.Minuto,
"ALTERADO",
"Medição alterada (antes x depois)",
0,
1,
existente,
m,
DateTime.UtcNow));
}
}
}
@ -373,19 +421,19 @@ namespace Application
{
await _postgresRepository.InserirMedicoesAsync(novos, ct);
Console.WriteLine($"Ponto {ponto}. Dia {dia:dd/MM/yyyy}. {novos.Count:D3} registros inseridos.");
operacoesLog?.Add($"{perfil};{ponto};{dia.ToOADate()};OK;Novos;{novos.Count};0");
logs.Add(new LogItem(LogType.Operation, perfil, ponto, dia.ToOADate(), 0, "OK", "Novos", novos.Count, 0, null, null, DateTime.UtcNow));
}
if (alterados.Any())
{
await _postgresRepository.AtualizarMedicoesAsync(alterados, ct);
Console.WriteLine($"Ponto {ponto}. Dia {dia:dd/MM/yyyy}. {alterados.Count:D3} registros atualizados.");
operacoesLog?.Add($"{perfil};{ponto};{dia.ToOADate()};OK;Atualizados;0;{alterados.Count}");
// logs.Add(new LogItem(LogType.Operation, perfil, ponto, dia.ToOADate(), 0, "OK", "Atualizados", 0, alterados.Count, null, null, DateTime.UtcNow));
}
if (!novos.Any() && !alterados.Any())
{
Console.WriteLine($"Ponto {ponto}. Dia {dia:dd/MM/yyyy}. 000 registros alterados.");
operacoesLog?.Add($"{perfil};{ponto};{dia.ToOADate()};OK;Sem alterações;0;0");
logs.Add(new LogItem(LogType.Info, perfil, ponto, dia.ToOADate(), 0, "OK", "Sem alterações", 0, 0, null, null, DateTime.UtcNow));
}
}

View File

@ -12,9 +12,9 @@ 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);
//junho finalizado
DateTime dataIni = new DateTime(inicio.Year, 11, 01);
DateTime dataFim = new DateTime(inicio.Year, 11, 14);
//agosto finalizado
DateTime dataIni = new DateTime(inicio.Year, 09, 01);
DateTime dataFim = new DateTime(inicio.Year, 10, 01);
// Configuração de dependências (pode usar um container DI depois)
var postgresRepo = new PostgresRepository(PG_CONN_STRING_PROD);