From e4c398eef0ef3bcef7331e87d33037392086b263 Mon Sep 17 00:00:00 2001 From: Adriano Serighelli Date: Mon, 17 Nov 2025 12:55:56 -0300 Subject: [PATCH] =?UTF-8?q?Unifica=C3=A7=C3=A3o=20do=20arquivo=20do=20regi?= =?UTF-8?q?stro=20de=20opera=C3=A7=C3=B5es=20e=20maior=20detalhanmento=20e?= =?UTF-8?q?m=20caso=20de=20update.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Application/ProcessarMedicoesUseCase.cs | 118 +++++++++++++++++------- Presentation/Program.cs | 6 +- 2 files changed, 86 insertions(+), 38 deletions(-) diff --git a/Application/ProcessarMedicoesUseCase.cs b/Application/ProcessarMedicoesUseCase.cs index 0126b90..60a8db7 100644 --- a/Application/ProcessarMedicoesUseCase.cs +++ b/Application/ProcessarMedicoesUseCase.cs @@ -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(); - var operacoesLog = new ConcurrentBag(); + var logs = new ConcurrentBag(); 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); - - // Paraleliza os dias deste perfil; o semáforo limita as requisições simultâneas + var existentes = await ObterMedicoesComRetry(perfil.CodigoSCDE, dataIni, dataFim, ctPerfil, logs); + 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 { "Perfil;Ponto;DiaNum;Status;Mensagem;Inseridos;Atualizados" }; - linhasLog.AddRange(operacoesLog); - linhasLog.AddRange(errosPersistentes); + var linhas = new List + { + "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> ObterMedicoesComRetry( - string codigoSCDE, DateTime dataIni, DateTime dataFim, CancellationToken ct, ConcurrentBag errosPersistentes) + string codigoSCDE, DateTime dataIni, DateTime dataFim, CancellationToken ct, ConcurrentBag 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 errosPersistentes, - ConcurrentBag operacoesLog, + ConcurrentBag 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(); int paginaAtual = 1; @@ -138,7 +171,7 @@ namespace Application // Aguarda token do rate limiter antes de cada requisição await _rateLimiter.WaitAsync(ct); - + HttpResponseMessage response; string resposta; @@ -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? operacoesLog = null) + ConcurrentBag? logs = null) { - // Processa as medidas já acumuladas (antes chamadas faziam paginação) + logs ??= new ConcurrentBag(); + 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�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)); } } diff --git a/Presentation/Program.cs b/Presentation/Program.cs index aa94cc0..11393a0 100644 --- a/Presentation/Program.cs +++ b/Presentation/Program.cs @@ -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);