diff --git a/App/Program.cs b/App/Program.cs
deleted file mode 100644
index 6949f03..0000000
--- a/App/Program.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace App
-{
- internal class Program
- {
- static void Main(string[] args)
- {
- Console.WriteLine("Hello, World!");
- }
- }
-}
diff --git a/Infrastructure/Infra.csproj b/Application/Application.csproj
similarity index 56%
rename from Infrastructure/Infra.csproj
rename to Application/Application.csproj
index 125f4c9..a26b76f 100644
--- a/Infrastructure/Infra.csproj
+++ b/Application/Application.csproj
@@ -6,4 +6,9 @@
enable
+
+
+
+
+
diff --git a/Application/ProcessarMedicoesUseCase.cs b/Application/ProcessarMedicoesUseCase.cs
new file mode 100644
index 0000000..2fbddc7
--- /dev/null
+++ b/Application/ProcessarMedicoesUseCase.cs
@@ -0,0 +1,367 @@
+using System.Collections.Concurrent;
+using System.Text;
+using System.Xml.Linq;
+using Domain;
+using Infrastructure;
+using System.Globalization;
+
+namespace Application
+{
+ public class ProcessarMedicoesUseCase
+ {
+ private readonly IPostgresRepository _postgresRepository;
+ private readonly IAccessRepository _accessRepository;
+ private readonly HttpClient _httpClient;
+ private readonly RateLimiter _rateLimiter;
+
+ public ProcessarMedicoesUseCase(
+ IPostgresRepository postgresRepository,
+ IAccessRepository accessRepository,
+ HttpClient httpClient,
+ RateLimiter rateLimiter)
+ {
+ _postgresRepository = postgresRepository;
+ _accessRepository = accessRepository;
+ _httpClient = httpClient;
+ _rateLimiter = rateLimiter;
+ }
+
+ public async Task ExecuteAsync(DateTime dataIni, DateTime dataFim, string caminhoLog, CancellationToken ct)
+ {
+ var errosPersistentes = new ConcurrentBag();
+ var perfis = (await _accessRepository.ObterPerfisAsync(ct)).ToList();
+
+ _httpClient.DefaultRequestHeaders.Add("SOAPAction", "listarMedidaCincoMinutos");
+ var endpoint = new Uri("https://servicos.ccee.org.br/ws/v2/MedidaCincoMinutosBSv2");
+ 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)
+ {
+ 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}");
+ }
+ 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,
+ string perfil,
+ string ponto,
+ IDictionary<(string, double, int), Medicao> existentes,
+ CancellationToken ct,
+ int paginaAtual = 1,
+ List? acumulador = null,
+ int totalPaginas = 1)
+ {
+ var doc = XDocument.Parse(xml);
+ XNamespace ns = "http://xmlns.energia.org.br/BO/v2";
+
+ int.TryParse(doc.Descendants().FirstOrDefault(e => e.Name.LocalName == "totalPaginas")?.Value, out totalPaginas);
+ int.TryParse(doc.Descendants().FirstOrDefault(e => e.Name.LocalName == "numero")?.Value, out paginaAtual);
+
+ var medidas = doc.Descendants(ns + "medida")
+ .Where(x => (string)x.Element(ns + "tipoEnergia") == "L");
+
+ acumulador ??= new List();
+ acumulador.AddRange(medidas);
+
+ if (paginaAtual < totalPaginas)
+ {
+ // Requisita próxima página
+ string payload = Xml_requisicao(dia, perfil, ponto, paginaAtual + 1);
+ var conteudo = new StringContent(payload, Encoding.UTF8, "application/xml");
+ await _rateLimiter.WaitAsync(ct);
+ using var resp = await _httpClient.PostAsync("https://servicos.ccee.org.br/ws/v2/MedidaCincoMinutosBSv2", conteudo, ct);
+ string proxXml = await resp.Content.ReadAsStringAsync();
+
+ await ProcessarXMLAsync(proxXml, dia, perfil, ponto, existentes, ct, paginaAtual + 1, acumulador, totalPaginas);
+ return;
+ }
+
+ var medidasProcessadas = acumulador
+ .Select(m =>
+ {
+ string origem = m.Element(ns + "coletaMedicao")?.Element(ns + "tipo")?.Element(ns + "nome")?.Value ?? "";
+ string pontoMed = m.Element(ns + "medidor")?.Element(ns + "codigo")?.Value ?? "";
+ DateTime data = DateTime.Parse(m.Element(ns + "data")?.Value ?? "");
+ double diaNum = (data.ToOADate() - data.ToOADate() % 1);
+ int minuto = data.Hour * 60 + data.Minute;
+ if (minuto == 0) { minuto = 1440; diaNum--; }
+
+ double.TryParse(m.Element(ns + "energiaAtiva")?.Element(ns + "consumo")?.Element(ns + "valor")?.Value,
+ NumberStyles.Any, CultureInfo.InvariantCulture, out double ativa_c);
+ double.TryParse(m.Element(ns + "energiaAtiva")?.Element(ns + "geracao")?.Element(ns + "valor")?.Value,
+ NumberStyles.Any, CultureInfo.InvariantCulture, out double ativa_g);
+ double.TryParse(m.Element(ns + "energiaReativa")?.Element(ns + "consumo")?.Element(ns + "valor")?.Value,
+ NumberStyles.Any, CultureInfo.InvariantCulture, out double reat_c);
+ double.TryParse(m.Element(ns + "energiaReativa")?.Element(ns + "geracao")?.Element(ns + "valor")?.Value,
+ NumberStyles.Any, CultureInfo.InvariantCulture, out double reat_g);
+
+ return new Medicao(
+ pontoMed,
+ diaNum,
+ minuto,
+ origem,
+ ativa_c,
+ ativa_g,
+ reat_c,
+ reat_g
+ );
+ })
+ .GroupBy(x => new { x.Ponto, x.DiaNum, x.Minuto })
+ .Select(g =>
+ {
+ var logica = g.FirstOrDefault(x => x.Origem == "Inspeção Lógica");
+ return logica ?? g.First();
+ });
+
+
+ var minutosEsperados = new[] { 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60 };
+
+ var medidasPorHora = medidasProcessadas
+ .GroupBy(m => new { m.Ponto, m.DiaNum, Hora = (m.Minuto - 5) / 60 })
+ .ToList();
+
+ var medidasComEstimativa = new List();
+
+ foreach (var grupoHora in medidasPorHora)
+ {
+ var lista = grupoHora.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 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)
+ {
+ 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)
+ {
+ 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 estimada = new Medicao(
+ grupoHora.Key.Ponto,
+ grupoHora.Key.DiaNum,
+ minFaltante,
+ "Estimado",
+ ativaConsumo,
+ ativaGeracao,
+ reativaConsumo,
+ reativaGeracao
+ );
+ estimadas.Add(estimada);
+ }
+ }
+
+ // Adiciona todos (originais + estimados) ao resultado final
+ medidasComEstimativa.AddRange(lista);
+ medidasComEstimativa.AddRange(estimadas);
+ }
+
+ var novos = new List();
+ var alterados = new List();
+
+ foreach (var m in medidasComEstimativa)
+ {
+ var chave = (m.Ponto, m.DiaNum, m.Minuto);
+
+ if (!existentes.TryGetValue(chave, out var existente))
+ {
+ novos.Add(m);
+ }
+ else
+ {
+ if (existente.Origem != m.Origem ||
+ existente.AtivaConsumo != m.AtivaConsumo ||
+ existente.AtivaGeracao != m.AtivaGeracao ||
+ existente.ReativaConsumo != m.ReativaConsumo ||
+ existente.ReativaGeracao != m.ReativaGeracao)
+ {
+ alterados.Add(m);
+ }
+ }
+ }
+
+ if (novos.Any())
+ {
+ await _postgresRepository.InserirMedicoesAsync(novos, ct);
+ Console.WriteLine($"Inserido {novos.Count} registros. Ponto {ponto}. Dia {dia}");
+ }
+
+ if (alterados.Any())
+ {
+ await _postgresRepository.AtualizarMedicoesAsync(alterados, ct);
+ Console.WriteLine($"Atualizado {alterados.Count} registros. Ponto {ponto}. Dia {dia}");
+ }
+ }
+ private static string Xml_requisicao(DateTime data_req, string perfil, string cod_ponto, int pagina)
+ {
+ string cam_ent, tex_req, sdat_req;
+ cam_ent = @"X:\Back\Plataforma de Integração CCEE\RequestPaginate.txt";
+ cod_ponto += "P";
+ sdat_req = data_req.ToString("yyyy-MM-ddT00:00:00");
+ tex_req = File.ReadAllText(cam_ent);
+ tex_req = tex_req.Replace("DATAALTERADA", sdat_req);
+ tex_req = tex_req.Replace("PONTOMEDICAO", cod_ponto);
+ tex_req = tex_req.Replace("CODPERFIL", perfil);
+ tex_req = tex_req.Replace("PAGNUM", pagina.ToString());
+ return tex_req;
+ }
+ private static double? Interpolar(
+ double? xAnterior, double? yAnterior,
+ double? xPosterior, double? yPosterior,
+ double xProcurado)
+ {
+ if (xAnterior.HasValue && yAnterior.HasValue &&
+ (!xPosterior.HasValue || !yPosterior.HasValue))
+ {
+ return yAnterior.Value;
+ }
+
+ if (xPosterior.HasValue && yPosterior.HasValue &&
+ (!xAnterior.HasValue || !yAnterior.HasValue))
+ {
+ return yPosterior.Value;
+ }
+
+ if (xAnterior.HasValue && yAnterior.HasValue &&
+ xPosterior.HasValue && yPosterior.HasValue)
+ {
+ if (xPosterior.Value == xAnterior.Value)
+ throw new ArgumentException("xAnterior e xPosterior não podem ser iguais (divisão por zero).");
+
+ double yProcurado = yAnterior.Value +
+ ((yPosterior.Value - yAnterior.Value) / (xPosterior.Value - xAnterior.Value)) *
+ (xProcurado - xAnterior.Value);
+
+ return yProcurado;
+ }
+
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Data/AccessRepository.cs b/Data/AccessRepository.cs
deleted file mode 100644
index fe82719..0000000
--- a/Data/AccessRepository.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Data
-{
- internal class AccessRepository
- {
- }
-}
diff --git a/Data/Data.csproj b/Data/Data.csproj
deleted file mode 100644
index 125f4c9..0000000
--- a/Data/Data.csproj
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
- net9.0
- enable
- enable
-
-
-
diff --git a/Data/PostgresRepository.cs b/Data/PostgresRepository.cs
deleted file mode 100644
index 9019556..0000000
--- a/Data/PostgresRepository.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Data
-{
- internal class PostgresRepository
- {
- }
-}
diff --git a/Domain/IAccessRepository.cs b/Domain/IAccessRepository.cs
new file mode 100644
index 0000000..f367dd0
--- /dev/null
+++ b/Domain/IAccessRepository.cs
@@ -0,0 +1,7 @@
+namespace Domain
+{
+ public interface IAccessRepository
+ {
+ Task> ObterPerfisAsync(CancellationToken ct);
+ }
+}
diff --git a/Domain/IPostgresRepository.cs b/Domain/IPostgresRepository.cs
new file mode 100644
index 0000000..e06cf4a
--- /dev/null
+++ b/Domain/IPostgresRepository.cs
@@ -0,0 +1,12 @@
+namespace Domain
+{
+ public interface IPostgresRepository
+ {
+ Task>
+ ObterMedicoesAsync(string codigoSCDE, DateTime dataIni, DateTime dataFim, CancellationToken ct);
+
+ Task InserirMedicoesAsync(IEnumerable medicoes, CancellationToken ct);
+
+ Task AtualizarMedicoesAsync(IEnumerable medicoes, CancellationToken ct);
+ }
+}
diff --git a/Domain/Medicao.cs b/Domain/Medicao.cs
index 03b1133..1a78824 100644
--- a/Domain/Medicao.cs
+++ b/Domain/Medicao.cs
@@ -1,12 +1,13 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Domain
+namespace Domain
{
- internal class Medicao
- {
- }
+ public record Medicao(
+ string Ponto,
+ double DiaNum,
+ int Minuto,
+ string Origem,
+ double AtivaConsumo,
+ double AtivaGeracao,
+ double ReativaConsumo,
+ double ReativaGeracao
+ );
}
diff --git a/Domain/Perfil.cs b/Domain/Perfil.cs
index 9400048..75775ac 100644
--- a/Domain/Perfil.cs
+++ b/Domain/Perfil.cs
@@ -1,12 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Domain
+namespace Domain
{
- internal class Perfil
- {
- }
-}
+ public record Perfil(string? Codigo5Minutos, string? CodigoSCDE, DateTime? DataDeMigracao);
+}
\ No newline at end of file
diff --git a/Infrastructure/AccessRepository.cs b/Infrastructure/AccessRepository.cs
new file mode 100644
index 0000000..1cad825
--- /dev/null
+++ b/Infrastructure/AccessRepository.cs
@@ -0,0 +1,41 @@
+using System.Data.OleDb;
+using Domain;
+
+namespace Infrastructure
+{
+ public class AccessRepository : IAccessRepository
+ {
+ private readonly string _connectionString;
+
+ public AccessRepository(string connectionString)
+ {
+ _connectionString = connectionString;
+ }
+
+ public async Task> ObterPerfisAsync(CancellationToken ct)
+ {
+ var perfis = new List();
+
+ using var connection = new OleDbConnection(_connectionString);
+ await connection.OpenAsync(ct);
+
+ 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";
+
+ using var command = new OleDbCommand(sql, connection);
+ using var reader = await command.ExecuteReaderAsync(ct);
+
+ while (await reader.ReadAsync(ct))
+ {
+ var cod5min = reader.IsDBNull(0) ? null : reader.GetString(0);
+ var codigoSCDE = reader.IsDBNull(1) ? null : reader.GetString(1);
+ var dataMigracao = reader.IsDBNull(2) ? (DateTime?)null : reader.GetDateTime(2);
+ perfis.Add(new Perfil(cod5min, codigoSCDE, dataMigracao));
+ }
+
+ return perfis;
+ }
+ }
+}
diff --git a/Infrastructure/Infrastructure.csproj b/Infrastructure/Infrastructure.csproj
new file mode 100644
index 0000000..d0bae70
--- /dev/null
+++ b/Infrastructure/Infrastructure.csproj
@@ -0,0 +1,18 @@
+
+
+
+ net9.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Infrastructure/PostgresRepository.cs b/Infrastructure/PostgresRepository.cs
new file mode 100644
index 0000000..43fedbd
--- /dev/null
+++ b/Infrastructure/PostgresRepository.cs
@@ -0,0 +1,105 @@
+using Domain;
+using Npgsql;
+using NpgsqlTypes;
+
+namespace Infrastructure
+{
+ public class PostgresRepository : IPostgresRepository
+ {
+ private readonly NpgsqlDataSource _dataSource;
+
+ public PostgresRepository(string connectionString)
+ {
+ _dataSource = NpgsqlDataSource.Create(connectionString);
+ }
+
+ public async Task>
+ ObterMedicoesAsync(string codigoSCDE, DateTime dataIni, DateTime dataFim, CancellationToken ct)
+ {
+ var existentes = new Dictionary<(string, double, int), Medicao>();
+
+ string sql = @"
+ SELECT ponto, dia_num, minuto, origem, ativa_consumo, ativa_geracao, reativa_consumo, reativa_geracao
+ FROM med_5min
+ WHERE ponto = @ponto AND dia_num >= @data_ini AND dia_num <= @data_fim";
+
+ await using var command = _dataSource.CreateCommand(sql);
+ command.Parameters.AddWithValue("ponto", codigoSCDE + "P");
+ command.Parameters.AddWithValue("data_ini", dataIni.ToOADate());
+ command.Parameters.AddWithValue("data_fim", dataFim.ToOADate());
+
+ await using var reader = await command.ExecuteReaderAsync(ct);
+ while (await reader.ReadAsync(ct))
+ {
+ var medicao = new Medicao(
+ reader.GetString(0),
+ reader.GetDouble(1),
+ reader.GetInt32(2),
+ reader.GetString(3),
+ reader.GetDouble(4),
+ reader.GetDouble(5),
+ reader.GetDouble(6),
+ reader.GetDouble(7)
+ );
+
+ existentes[(medicao.Ponto, medicao.DiaNum, medicao.Minuto)] = medicao;
+ }
+
+ return existentes;
+ }
+
+ public async Task InserirMedicoesAsync(IEnumerable medicoes, CancellationToken ct)
+ {
+ await using var connection = await _dataSource.OpenConnectionAsync(ct);
+ using var writer = connection.BeginBinaryImport(
+ "COPY med_5min (origem, dia_num, minuto, ativa_consumo, ativa_geracao, reativa_consumo, reativa_geracao, ponto) FROM STDIN (FORMAT BINARY)");
+
+ foreach (var m in medicoes)
+ {
+ writer.StartRow();
+ writer.Write(m.Origem);
+ writer.Write(m.DiaNum, NpgsqlDbType.Numeric);
+ writer.Write(m.Minuto, NpgsqlDbType.Integer);
+ writer.Write(m.AtivaConsumo, NpgsqlDbType.Numeric);
+ writer.Write(m.AtivaGeracao, NpgsqlDbType.Numeric);
+ writer.Write(m.ReativaConsumo, NpgsqlDbType.Numeric);
+ writer.Write(m.ReativaGeracao, NpgsqlDbType.Numeric);
+ writer.Write(m.Ponto);
+ }
+
+ await writer.CompleteAsync();
+ }
+ public async Task AtualizarMedicoesAsync(IEnumerable medicoes, CancellationToken ct)
+ {
+ await using var connection = await _dataSource.OpenConnectionAsync(ct);
+ using var batch = new NpgsqlBatch(connection);
+
+ foreach (var m in medicoes)
+ {
+ var cmd = new NpgsqlBatchCommand(@"
+ UPDATE med_5min
+ SET origem = @origem,
+ ativa_consumo = @ativa_consumo,
+ ativa_geracao = @ativa_geracao,
+ reativa_consumo = @reativa_consumo,
+ reativa_geracao = @reativa_geracao
+ WHERE ponto = @ponto
+ AND dia_num = @dia_num
+ 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("ponto", m.Ponto);
+ cmd.Parameters.AddWithValue("dia_num", m.DiaNum);
+ cmd.Parameters.AddWithValue("minuto", m.Minuto);
+
+ batch.BatchCommands.Add(cmd);
+ }
+
+ await batch.ExecuteNonQueryAsync(ct);
+ }
+ }
+}
diff --git a/Infrastructure/RateLimiter.cs b/Infrastructure/RateLimiter.cs
index 9be40d3..faaa214 100644
--- a/Infrastructure/RateLimiter.cs
+++ b/Infrastructure/RateLimiter.cs
@@ -1,10 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Infra
+namespace Infrastructure
{
public class RateLimiter
{
diff --git a/Infrastructure/SoapFaultException.cs b/Infrastructure/SoapFaultException.cs
index 8a1b91c..37135a9 100644
--- a/Infrastructure/SoapFaultException.cs
+++ b/Infrastructure/SoapFaultException.cs
@@ -1,11 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using Infra;
-
-namespace Infra
+namespace Infrastructure
{
public class SoapFaultException : Exception
{
diff --git a/Infrastructure/SoapHelper.cs b/Infrastructure/SoapHelper.cs
index f998e46..b7f5929 100644
--- a/Infrastructure/SoapHelper.cs
+++ b/Infrastructure/SoapHelper.cs
@@ -1,11 +1,6 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System.Xml.Linq;
+using System.Xml.Linq;
-namespace Infra
+namespace Infrastructure
{
public static class SoapHelper
{
diff --git a/PI_Assync_SCDE.sln b/PI_Assync_SCDE.sln
index 9505a85..5e8f006 100644
--- a/PI_Assync_SCDE.sln
+++ b/PI_Assync_SCDE.sln
@@ -5,15 +5,13 @@ VisualStudioVersion = 17.1.32319.34
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "App_old", "App_old.csproj", "{48DFCC70-0352-4394-9821-2B243EB389AB}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infra", "Infrastructure\Infra.csproj", "{0910ED06-C154-41FF-B556-ADBA53A10427}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Data", "Data\Data.csproj", "{4B7EE8CE-E452-4B31-9B21-64113B6C48C8}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure", "Infrastructure\Infrastructure.csproj", "{0910ED06-C154-41FF-B556-ADBA53A10427}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Domain", "Domain\Domain.csproj", "{38CE64C7-8F03-4622-8DD5-5BA19F66E7AF}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Services", "Services\Services.csproj", "{2B2323E4-C409-4432-AA45-54C1642FDA88}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Application", "Application\Application.csproj", "{8B6C1411-58F1-4701-9736-D8A87F2FBB64}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "App", "App\App.csproj", "{2B43C133-911B-4F3D-9DAB-1DA342D3822B}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Presentation", "Presentation\Presentation.csproj", "{979738F9-0985-45D4-8E9C-2DE972084FE9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -29,22 +27,18 @@ Global
{0910ED06-C154-41FF-B556-ADBA53A10427}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0910ED06-C154-41FF-B556-ADBA53A10427}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0910ED06-C154-41FF-B556-ADBA53A10427}.Release|Any CPU.Build.0 = Release|Any CPU
- {4B7EE8CE-E452-4B31-9B21-64113B6C48C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {4B7EE8CE-E452-4B31-9B21-64113B6C48C8}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {4B7EE8CE-E452-4B31-9B21-64113B6C48C8}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {4B7EE8CE-E452-4B31-9B21-64113B6C48C8}.Release|Any CPU.Build.0 = Release|Any CPU
{38CE64C7-8F03-4622-8DD5-5BA19F66E7AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{38CE64C7-8F03-4622-8DD5-5BA19F66E7AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{38CE64C7-8F03-4622-8DD5-5BA19F66E7AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{38CE64C7-8F03-4622-8DD5-5BA19F66E7AF}.Release|Any CPU.Build.0 = Release|Any CPU
- {2B2323E4-C409-4432-AA45-54C1642FDA88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {2B2323E4-C409-4432-AA45-54C1642FDA88}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {2B2323E4-C409-4432-AA45-54C1642FDA88}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {2B2323E4-C409-4432-AA45-54C1642FDA88}.Release|Any CPU.Build.0 = Release|Any CPU
- {2B43C133-911B-4F3D-9DAB-1DA342D3822B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {2B43C133-911B-4F3D-9DAB-1DA342D3822B}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {2B43C133-911B-4F3D-9DAB-1DA342D3822B}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {2B43C133-911B-4F3D-9DAB-1DA342D3822B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8B6C1411-58F1-4701-9736-D8A87F2FBB64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8B6C1411-58F1-4701-9736-D8A87F2FBB64}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8B6C1411-58F1-4701-9736-D8A87F2FBB64}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8B6C1411-58F1-4701-9736-D8A87F2FBB64}.Release|Any CPU.Build.0 = Release|Any CPU
+ {979738F9-0985-45D4-8E9C-2DE972084FE9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {979738F9-0985-45D4-8E9C-2DE972084FE9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {979738F9-0985-45D4-8E9C-2DE972084FE9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {979738F9-0985-45D4-8E9C-2DE972084FE9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/App/App.csproj b/Presentation/Presentation.csproj
similarity index 58%
rename from App/App.csproj
rename to Presentation/Presentation.csproj
index fd4bd08..f741727 100644
--- a/App/App.csproj
+++ b/Presentation/Presentation.csproj
@@ -1,10 +1,15 @@

- Exe
net9.0
enable
enable
+ Exe
+
+
+
+
+
diff --git a/Presentation/Program.cs b/Presentation/Program.cs
new file mode 100644
index 0000000..4c6d63b
--- /dev/null
+++ b/Presentation/Program.cs
@@ -0,0 +1,36 @@
+using System.Data.OleDb;
+using Application;
+using Infrastructure;
+
+class Program
+{
+ static async Task Main()
+ {
+ DateTime inicio = DateTime.Now;
+ string PG_CONN_STRING_PROD = "Server = smart-energia-dev-pgsql.cykff7tj7mik.us-east-1.rds.amazonaws.com; Port = 5432; Database = smartenergiaprod; Username = postgres; Password = VfHml#Z78!%kvvNM; Timeout = 60; CommandTimeout = 60; ApplicationName = new_med_5_min; Connection Lifetime = 120; Minimum Pool Size = 2; Maximum Pool Size = 2;";
+ string ACCESS_CONN_STRING = @"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=\\srv-dados\documentos\Middle\Informativo Setorial\Modelo Word\BD1_dados cadastrais e faturas.accdb;Jet OLEDB:Database Password=gds21";
+ 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);
+
+ // Configuração de dependências (pode usar um container DI depois)
+ var postgresRepo = new PostgresRepository(PG_CONN_STRING_PROD);
+ var accessRepo = new AccessRepository(ACCESS_CONN_STRING);
+ var httpClient = new HttpClient(new HttpClientHandler
+ {
+ ClientCertificateOptions = ClientCertificateOption.Automatic,
+ //Proxy = new WebProxy("127.0.0.1", 8888),
+ //UseProxy = true,
+ //ServerCertificateCustomValidationCallback = (HttpRequestMessage req, X509Certificate2? cert, X509Chain? chain, SslPolicyErrors errors) => true
+ });
+ var rateLimiter = new RateLimiter(400, TimeSpan.FromMinutes(1));
+ var useCase = new ProcessarMedicoesUseCase(postgresRepo, accessRepo, httpClient, rateLimiter);
+
+ await useCase.ExecuteAsync(dataIni, dataFim, caminhoLog, CancellationToken.None);
+
+ Console.WriteLine($"ConcluÃdo. Tempo total: {DateTime.Now - inicio}");
+ Console.ReadKey();
+ }
+}
\ No newline at end of file
diff --git a/Program.cs b/Program.cs
index bacc53f..67debea 100644
--- a/Program.cs
+++ b/Program.cs
@@ -8,393 +8,8 @@ using Npgsql;
internal class Plat_integ
{
- static string PG_CONN_STRING_PROD = "Server = smart-energia-dev-pgsql.cykff7tj7mik.us-east-1.rds.amazonaws.com; Port = 5432; Database = smartenergiaprod; Username = postgres; Password = VfHml#Z78!%kvvNM; Timeout = 60; CommandTimeout = 60; ApplicationName = new_med_5_min; Connection Lifetime = 120; Minimum Pool Size = 2; Maximum Pool Size = 2;";
- static readonly HttpClient client = new HttpClient(new HttpClientHandler
- {
- ClientCertificateOptions = ClientCertificateOption.Automatic,
- //Proxy = new WebProxy("127.0.0.1", 8888),
- //UseProxy = true,
- //ServerCertificateCustomValidationCallback = (HttpRequestMessage req, X509Certificate2? cert, X509Chain? chain, SslPolicyErrors errors) => true
- });
-
static async Task Main()
{
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB");
- DateTime inicio = DateTime.Now;
-
- string caminhoBD = @"\\srv-dados\documentos\Middle\Informativo Setorial\Modelo Word\BD1_dados cadastrais e faturas.accdb";
- string caminhoLog = $@"\\srv-dados\documentos\Back\Carteira x.x\Codigo\Erros\log_erros_{DateTime.Now:MM_dd_HH_mm}.csv";
-
- DateTime now = DateTime.Now;
- //DateTime dataIni = new DateTime(now.Year, now.Month, 1);
- //DateTime dataFim = new DateTime(now.Year, now.Month, now.Day);
- DateTime dataIni = new DateTime(now.Year, 7, 1);
- DateTime dataFim = new DateTime(now.Year, 8, 1);
-
- var perfis = Busca_dad_BD(caminhoBD, dataIni);
-
- await ProcessarMedicoesAsync(dataIni, dataFim, perfis, caminhoLog);
-
- Console.WriteLine($"ConcluÃdo. Tempo total: {DateTime.Now - inicio}");
-
- Console.ReadKey();
- }
- public static List Busca_dad_BD(string caminho_BD, DateTime dataIni)
- {
- var lista = new List();
-
- string query = $"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 query = "SELECT Cod_5min, Codigo_SCDE 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 query = "SELECT Cod_5min, Codigo_SCDE FROM Dados_cadastrais WHERE LEN(Codigo_SCDE) > 5 and Cliente = 'ALMAVIVA' and Unidade = 'GUARULHOS' AND unidadade_gerenciada ORDER BY PerfilCCEE";
- //string query = "SELECT Cod_5min, Codigo_SCDE FROM Dados_cadastrais WHERE LEN(Codigo_SCDE) > 5 and Cliente = 'bramagran' AND Unidade_gerenciada ORDER BY PerfilCCEE";
- using (var connection = new OleDbConnection($"Provider=Microsoft.ACE.OLEDB.12.0;Data Source={caminho_BD};Jet OLEDB:Database Password=gds21"))
- {
- using var cmd = new OleDbCommand(query, connection);
- connection.Open();
- using var reader = cmd.ExecuteReader();
-
- while (reader.Read())
- {
- lista.Add(
- new perfil(
- reader["Cod_5min"].ToString(),
- reader["Codigo_SCDE"].ToString(),
- DateTime.Parse(reader["Data_de_Migracao"].ToString())
- )
- );
- }
- }
- return lista;
- }
-
- private static async Task ProcessarMedicoesAsync(DateTime dataIni, DateTime dataFim, List perfis, string caminhoLog)
- {
- var limiter = new RateLimiter(400, TimeSpan.FromMinutes(1));
-
- var errosPersistentes = new ConcurrentBag();
- var atualizados = new ConcurrentBag();
- var inseridos = new ConcurrentBag();
-
- await using var dataSource = NpgsqlDataSource.Create(PG_CONN_STRING_PROD);
-
- client.DefaultRequestHeaders.Add("SOAPAction", "listarMedidaCincoMinutos");
- var endpoint = new Uri("https://servicos.ccee.org.br/ws/v2/MedidaCincoMinutosBSv2");
-
- 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._Codigo_SCDE}");
- if (perfil._Cod_5min == "0" || perfil._Cod_5min == string.Empty)
- {
- Console.WriteLine($"Pular {perfil._Codigo_SCDE} - (cod 5 min pendente)");
- errosPersistentes.Add($"{perfil._Cod_5min};{perfil._Codigo_SCDE}; cod_5min pendente");
- return;
- }
- string sqlSelect = @"
- SELECT ponto, dia_num, minuto, origem,
- ativa_consumo, ativa_geracao, reativa_consumo, reativa_geracao
- FROM med_5min
- WHERE ponto = @ponto AND dia_num >= @data_ini AND dia_num < @data_fim;
- ";
-
- var existentes = new Dictionary<(string, double, int), dynamic>();
-
- await using (var command = dataSource.CreateCommand(sqlSelect))
- {
-
- command.Parameters.AddWithValue("ponto", perfil._Codigo_SCDE + "P");
- command.Parameters.AddWithValue("data_ini", dataIni.ToOADate());
- command.Parameters.AddWithValue("data_fim", dataFim.ToOADate());
-
- await using (var reader = await command.ExecuteReaderAsync(ct))
- {
- while (await reader.ReadAsync())
- {
- existentes[(reader.GetString(0), reader.GetDouble(1), reader.GetInt32(2))] = new
- {
- Origem = reader.GetString(3),
- AtivaC = reader.GetDouble(4),
- AtivaG = reader.GetDouble(5),
- ReatC = reader.GetDouble(6),
- ReatG = reader.GetDouble(7)
- };
- }
- }
- }
-
- foreach (DateTime dia in datas)
- {
- int tentativas = 0;
- bool sucesso = false;
-
- while (tentativas < 5 && !sucesso)
- {
- if (perfil._Data_de_Migracao > dia)
- {
- Console.WriteLine($"Pular {perfil._Codigo_SCDE} - {dia.ToShortDateString()} (antes da migração)");
- errosPersistentes.Add($"{perfil._Cod_5min};{perfil._Codigo_SCDE};Fora da data de migração {perfil._Data_de_Migracao} x {dia}");
- break; // não tentar antes da data de migração
- }
- try
- {
- string payload = Xml_requisicao(dia, perfil._Cod_5min, perfil._Codigo_SCDE, 1);
- var conteudo = new StringContent(payload, Encoding.UTF8, "application/xml");
-
- await limiter.WaitAsync(ct);
- using var response = await client.PostAsync(endpoint, conteudo, ct);
- string resposta = await response.Content.ReadAsStringAsync();
-
- if ((int)response.StatusCode >= 400)
- {
- try
- {
- 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._Cod_5min};{perfil._Codigo_SCDE};SOAP Fault: {ex.ErrorCode};{ex.ErrorMessage.Replace("\n", "-n-")}");
- break;
- }
- if (ex.ErrorCode == "2001") // Sem acesso
- {
- errosPersistentes.Add($"{perfil._Cod_5min};{perfil._Codigo_SCDE};SOAP Fault: {ex.ErrorCode};{ex.ErrorMessage.Replace("\n", "-n-")}");
- break;
- }
- else
- {
- throw;
- }
- }
- }
-
- await ProcessarXMLAsync(resposta, dataSource, dia, perfil._Cod_5min, perfil._Codigo_SCDE, existentes, limiter, ct, 1);
-
- sucesso = true;
- }
- catch (Exception ex)
- {
- tentativas++;
- if (tentativas >= 5)
- {
- errosPersistentes.Add($"{perfil._Cod_5min};{perfil._Codigo_SCDE};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._Codigo_SCDE}");
- }
- catch (Exception ex)
- {
- Console.WriteLine(ex.ToString());
- }
-
- });
-
- if (errosPersistentes.Count > 0)
- {
- File.WriteAllLines(caminhoLog, new[] { "Perfil;Ponto;Status;Message" }.Concat(errosPersistentes));
- }
- }
- public static string Xml_requisicao(DateTime data_req, string perfil, string cod_ponto, int pagina)
- {
- string cam_ent, tex_req, sdat_req;
- cam_ent = @"X:\Back\Plataforma de Integração CCEE\RequestPaginate.txt";
- cod_ponto += "P";
- sdat_req = data_req.ToString("yyyy-MM-ddT00:00:00");
- tex_req = File.ReadAllText(cam_ent);
- tex_req = tex_req.Replace("DATAALTERADA", sdat_req);
- tex_req = tex_req.Replace("PONTOMEDICAO", cod_ponto);
- tex_req = tex_req.Replace("CODPERFIL", perfil);
- tex_req = tex_req.Replace("PAGNUM", pagina.ToString());
- return tex_req;
- }
-
- private static async Task ProcessarXMLAsync(string xml, NpgsqlDataSource dataSource, DateTime dia, string perfil, string ponto, Dictionary<(string, double, int), dynamic> existentes, RateLimiter limiter, CancellationToken ct, int paginaAtual = 1, List acumulador = null, int totalPaginas = 1)
- {
- var doc = XDocument.Parse(xml);
- XNamespace ns = "http://xmlns.energia.org.br/BO/v2";
-
- int.TryParse(doc.Descendants().FirstOrDefault(e => e.Name.LocalName == "totalPaginas")?.Value, out totalPaginas);
- int.TryParse(doc.Descendants().FirstOrDefault(e => e.Name.LocalName == "numero")?.Value, out paginaAtual);
-
-
- var medidas = doc.Descendants(ns + "medida")
- .Where(x => (string)x.Element(ns + "tipoEnergia") == "L");
-
- acumulador ??= new List();
- acumulador.AddRange(medidas);
-
- if (paginaAtual < totalPaginas)
- {
- // Requisita próxima página
- string payload = Xml_requisicao(dia, perfil, ponto, paginaAtual + 1);
-
- var conteudo = new StringContent(payload, Encoding.UTF8, "application/xml");
- await limiter.WaitAsync(ct);
- using var resp = await client.PostAsync("https://servicos.ccee.org.br/ws/v2/MedidaCincoMinutosBSv2", conteudo);
-
- string proxXml = await resp.Content.ReadAsStringAsync();
-
- await ProcessarXMLAsync(proxXml, dataSource, dia, perfil, ponto, existentes, limiter, ct, paginaAtual + 1, acumulador, totalPaginas);
- return;
- }
-
- var medidasProcessadas = acumulador
- .Select(m =>
- {
- string origem = m.Element(ns + "coletaMedicao")?.Element(ns + "tipo")?.Element(ns + "nome")?.Value ?? "";
- string ponto = m.Element(ns + "medidor")?.Element(ns + "codigo")?.Value ?? "";
- DateTime data = DateTime.Parse(m.Element(ns + "data")?.Value ?? "");
- double diaNum = (data.ToOADate() - data.ToOADate() % 1);
- int minuto = data.Hour * 60 + data.Minute;
- if (minuto == 0) { minuto = 1440; diaNum--; }
-
- double.TryParse(m.Element(ns + "energiaAtiva")?.Element(ns + "consumo")?.Element(ns + "valor")?.Value, out double ativa_c);
- double.TryParse(m.Element(ns + "energiaAtiva")?.Element(ns + "geracao")?.Element(ns + "valor")?.Value, out double ativa_g);
- double.TryParse(m.Element(ns + "energiaReativa")?.Element(ns + "consumo")?.Element(ns + "valor")?.Value, out double reat_c);
- double.TryParse(m.Element(ns + "energiaReativa")?.Element(ns + "geracao")?.Element(ns + "valor")?.Value, out double reat_g);
-
- return new
- {
- Origem = origem,
- Ponto = ponto,
- DiaNum = diaNum,
- Minuto = minuto,
- AtivaC = ativa_c,
- AtivaG = ativa_g,
- ReatC = reat_c,
- ReatG = reat_g
- };
- })
- .GroupBy(x => new { x.Ponto, x.DiaNum, x.Minuto })
- .Select(g =>
- {
- // Se houver alguma Inspeção Lógica → prioriza
- var logica = g.FirstOrDefault(x => x.Origem == "Inspeção Lógica");
- return logica ?? g.First(); // se não tiver lógica, pega qualquer (coleta diária)
- });
-
- var novos = new List();
- var alterados = new List();
-
- foreach (var m in medidasProcessadas)
- {
- var chave = (m.Ponto, m.DiaNum, m.Minuto);
-
- if (!existentes.TryGetValue(chave, out var existente))
- {
- // não existe → inserir
- novos.Add(m);
- }
- else
- {
- // existe mas mudou → atualizar
- if (existente.Origem != m.Origem ||
- existente.AtivaC != m.AtivaC ||
- existente.AtivaG != m.AtivaG ||
- existente.ReatC != m.ReatC ||
- existente.ReatG != m.ReatG)
- {
- alterados.Add(m);
- }
- }
- }
-
- if (novos.Any())
- {
- await using (var connection = await dataSource.OpenConnectionAsync(ct))
- {
- using var writer = connection.BeginBinaryImport("COPY med_5min (origem, dia_num, minuto, ativa_consumo, ativa_geracao, reativa_consumo, reativa_geracao, ponto) FROM STDIN (FORMAT BINARY)");
-
- foreach (var m in novos)
- {
- writer.StartRow();
- writer.Write(m.Origem);
- writer.Write(m.DiaNum, NpgsqlTypes.NpgsqlDbType.Numeric);
- writer.Write(m.Minuto, NpgsqlTypes.NpgsqlDbType.Integer);
- writer.Write(m.AtivaC, NpgsqlTypes.NpgsqlDbType.Numeric);
- writer.Write(m.AtivaG, NpgsqlTypes.NpgsqlDbType.Numeric);
- writer.Write(m.ReatC, NpgsqlTypes.NpgsqlDbType.Numeric);
- writer.Write(m.ReatG, NpgsqlTypes.NpgsqlDbType.Numeric);
- writer.Write(m.Ponto);
- }
-
- await writer.CompleteAsync();
- }
- Console.WriteLine($"Inserido {novos.Count} registros. Ponto {ponto}. Dia {dia}");
- }
-
- if (alterados.Any())
- {
- await using (var connection = await dataSource.OpenConnectionAsync(ct))
- {
- using (var batch = new NpgsqlBatch(connection))
- {
- foreach (var m in alterados)
- {
- var cmd = new NpgsqlBatchCommand(@"
- UPDATE med_5min
- SET origem = @origem,
- ativa_consumo = @ativa_consumo,
- ativa_geracao = @ativa_geracao,
- reativa_consumo = @reativa_consumo,
- reativa_geracao = @reativa_geracao
- WHERE ponto = @ponto
- AND dia_num = @dia_num
- AND minuto = @minuto;");
-
- // Adiciona os parâmetros de forma segura
- cmd.Parameters.AddWithValue("origem", m.Origem);
- cmd.Parameters.AddWithValue("ativa_consumo", m.AtivaC);
- cmd.Parameters.AddWithValue("ativa_geracao", m.AtivaG);
- cmd.Parameters.AddWithValue("reativa_consumo", m.ReatC);
- cmd.Parameters.AddWithValue("reativa_geracao", m.ReatG);
- cmd.Parameters.AddWithValue("ponto", m.Ponto);
- cmd.Parameters.AddWithValue("dia_num", m.DiaNum);
- cmd.Parameters.AddWithValue("minuto", m.Minuto);
-
- batch.BatchCommands.Add(cmd);
- }
-
- // Executa o lote de comandos de uma vez
- await batch.ExecuteNonQueryAsync();
- Console.WriteLine($"Atualizado {alterados.Count} registros. Ponto {ponto}. Dia {dia}");
- }
- }
- }
- return;
- }
-}
-
-public class perfil
-{
- public string _Cod_5min { get; set; }
- public string _Codigo_SCDE { get; set; }
- public DateTime _Data_de_Migracao { get; set; }
-
- public perfil(string cod_5min, string codigo_scde, DateTime data_de_migracao)
- {
- _Cod_5min = cod_5min;
- _Codigo_SCDE = codigo_scde;
- _Data_de_Migracao = data_de_migracao;
}
}
\ No newline at end of file
diff --git a/Services/MedicaoService.cs b/Services/MedicaoService.cs
deleted file mode 100644
index 4a0c013..0000000
--- a/Services/MedicaoService.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Services
-{
- internal class MedicaoService
- {
- }
-}
diff --git a/Services/Services.csproj b/Services/Services.csproj
deleted file mode 100644
index 125f4c9..0000000
--- a/Services/Services.csproj
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
- net9.0
- enable
- enable
-
-
-
diff --git a/Services/SoapService.cs b/Services/SoapService.cs
deleted file mode 100644
index 5e63ae0..0000000
--- a/Services/SoapService.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Services
-{
- internal class SoapService
- {
- }
-}