From 987a9b4334db7529f5771b74a23867fc4d646fe7 Mon Sep 17 00:00:00 2001 From: Vanderlei Schmitz Date: Sat, 23 Nov 2024 09:49:16 -0300 Subject: [PATCH] =?UTF-8?q?Ajustes=20na=20importa=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Entity/DeputadoEstadualEmpenhoTemp.cs | 42 ++ OPS.Core/Utilities/StringExtension.cs | 31 + OPS.Core/Utilities/Utils.cs | 13 +- OPS.Importador/ALE/Acre.cs | 8 +- OPS.Importador/ALE/Alagoas.cs | 611 +++++++++++++++++- OPS.Importador/ALE/Amapa.cs | 18 +- OPS.Importador/ALE/Bahia.cs | 2 +- OPS.Importador/ALE/Despesa/ImportadorBase.cs | 206 +++--- .../ALE/Despesa/ImportadorDespesasArquivo.cs | 23 +- .../ALE/Despesa/ImportadorDespesasBase.cs | 61 +- .../ImportadorDespesasRestApiMensal.cs | 5 +- OPS.Importador/ALE/MatoGrosso.cs | 2 +- OPS.Importador/ALE/Paraiba.cs | 121 +++- OPS.Importador/ALE/Parana.cs | 6 +- .../Parlamentar/ImportadorParlamentarBase.cs | 39 +- OPS.Importador/ALE/Piaui.cs | 131 ++-- OPS.Importador/ALE/RioGrandeDoNorte.cs | 8 +- OPS.Importador/ALE/RioGrandeDoSul.cs | 2 +- OPS.Importador/ALE/Rondonia.cs | 41 +- OPS.Importador/ALE/Roraima.cs | 5 +- OPS.Importador/ALE/Sergipe.cs | 3 +- OPS.Importador/ALE/Tocantins.cs | 3 +- OPS.Importador/CamaraFederal.cs | 95 ++- OPS.Importador/Fornecedor.cs | 2 +- OPS.Importador/OPS.Importador.csproj | 2 + OPS.Importador/Program.cs | 128 ++-- OPS.Importador/Senado.cs | 211 +++--- .../Utilities/AngleSharpExtensions.cs | 19 +- OPS.Importador/Utilities/ComputerVisionOcr.cs | 91 +++ OPS.Importador/Utilities/ImportacaoUtils.cs | 2 +- .../Utilities/TraceContentLoggingExtension.cs | 128 ++-- OPS.Importador/appsettings.json | 3 +- 32 files changed, 1415 insertions(+), 647 deletions(-) create mode 100644 OPS.Core/Entity/DeputadoEstadualEmpenhoTemp.cs create mode 100644 OPS.Core/Utilities/StringExtension.cs create mode 100644 OPS.Importador/Utilities/ComputerVisionOcr.cs diff --git a/OPS.Core/Entity/DeputadoEstadualEmpenhoTemp.cs b/OPS.Core/Entity/DeputadoEstadualEmpenhoTemp.cs new file mode 100644 index 0000000..f3b541f --- /dev/null +++ b/OPS.Core/Entity/DeputadoEstadualEmpenhoTemp.cs @@ -0,0 +1,42 @@ +using System; +using System.Text.Json.Serialization; +using Dapper; + +namespace OPS.Core.Entity +{ + [Table("cl_empenho_temp", Schema = "ops_tmp")] + public class DeputadoEstadualEmpenhoTemp + { + [JsonIgnore] + [Column("id")] + public int Id { get; set; } + + [Column("nome_favorecido")] + public string NomeFavorecido { get; set; } + + [Column("cnpj_cpf")] + public string CNPJCPF { get; set; } + + //[Column("objeto")] + //public string Objeto { get; set; } + + //[Column("tipo_licitacao")] + //public string TipoLicitacao { get; set; } + + [Column("numero_empenho")] + public string NumeroEmpenho { get; set; } + + [Column("data")] + public DateOnly Data { get; set; } + + [Column("competencia")] + public DateOnly Competencia { get; set; } + + [Column("valor_empenhado")] + public decimal ValorEmpenhado { get; set; } + + [Column("valor_pago")] + public decimal ValorPago { get; set; } + + } +} diff --git a/OPS.Core/Utilities/StringExtension.cs b/OPS.Core/Utilities/StringExtension.cs new file mode 100644 index 0000000..184325d --- /dev/null +++ b/OPS.Core/Utilities/StringExtension.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OPS.Core.Utilities +{ + public static class StringExtension + { + public static string ReplaceFirst(this string text, string search, string replace) + { + int pos = text.IndexOf(search); + if (pos < 0) + { + return text; + } + return text.Substring(0, pos) + replace + text.Substring(pos + search.Length); + } + + public static string ReplaceLast(this string text, string search, string replace) + { + int pos = text.LastIndexOf(search); + if (pos < 0) + { + return text; + } + return text.Substring(0, pos) + replace + text.Substring(pos + search.Length); + } + } +} diff --git a/OPS.Core/Utilities/Utils.cs b/OPS.Core/Utilities/Utils.cs index e7e7944..1809a57 100644 --- a/OPS.Core/Utilities/Utils.cs +++ b/OPS.Core/Utilities/Utils.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; +using System.Linq; using System.Net; using System.Net.Mail; using System.Security.Cryptography; @@ -315,14 +316,10 @@ public static string RemoveAccents(this string text) { if (string.IsNullOrEmpty(text)) return text; - StringBuilder sbReturn = new StringBuilder(); - var arrayText = text.Normalize(NormalizationForm.FormD).ToCharArray(); - foreach (char letter in arrayText) - { - if (CharUnicodeInfo.GetUnicodeCategory(letter) != UnicodeCategory.NonSpacingMark) - sbReturn.Append(letter); - } - return sbReturn.ToString(); + return string.Concat( + text.Normalize(NormalizationForm.FormD) + .Where(ch => CharUnicodeInfo.GetUnicodeCategory(ch) != UnicodeCategory.NonSpacingMark) + ).Normalize(NormalizationForm.FormC); } public static string ForceWindows1252ToUtf8Encoding(this string text) diff --git a/OPS.Importador/ALE/Acre.cs b/OPS.Importador/ALE/Acre.cs index 7f6120b..29f61c7 100644 --- a/OPS.Importador/ALE/Acre.cs +++ b/OPS.Importador/ALE/Acre.cs @@ -40,7 +40,7 @@ public ImportadorDespesasAcre(IServiceProvider serviceProvider) : base(servicePr { BaseAddress = "https://app.al.ac.leg.br/financa/despesaVI", // TODO: Gastos totais mensais apenas Estado = Estado.Acre, - ChaveImportacao = ChaveDespesaTemp.Indefinido + ChaveImportacao = ChaveDespesaTemp.NomeParlamentar }; } @@ -72,7 +72,7 @@ public override Task Importar() List objDeputadosAcre = RestApiGet>(address); foreach (var parlamentar in objDeputadosAcre) - { + { var matricula = (uint)parlamentar.Id; DeputadoEstadual deputado = GetDeputadoByMatriculaOrNew(matricula); @@ -97,7 +97,7 @@ private async Task ObterDetalhesDoPerfil(DeputadoEstadual deputado) var document = await context.OpenAsyncAutoRetry(deputado.UrlPerfil); if (document.StatusCode != HttpStatusCode.OK) - { + { Console.WriteLine($"{config.BaseAddress} {document.StatusCode}"); }; @@ -116,7 +116,7 @@ private async Task ObterDetalhesDoPerfil(DeputadoEstadual deputado) logger.LogWarning("Verificar possivel mudança no perfil do parlamentar: {UrlPerfil}", deputado.UrlPerfil); } } - } +} public class DeputadoAcre { diff --git a/OPS.Importador/ALE/Alagoas.cs b/OPS.Importador/ALE/Alagoas.cs index 1b31566..1d1fadc 100644 --- a/OPS.Importador/ALE/Alagoas.cs +++ b/OPS.Importador/ALE/Alagoas.cs @@ -1,15 +1,32 @@ using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; using AngleSharp; using AngleSharp.Dom; +using AngleSharp.Dom.Events; using AngleSharp.Html.Dom; +using Dapper; +using iTextSharp.text.pdf; +using iTextSharp.text.pdf.parser; using Microsoft.Extensions.Logging; using OPS.Core; using OPS.Core.Entity; using OPS.Core.Enum; +using OPS.Core.Utilities; using OPS.Importador.ALE.Despesa; using OPS.Importador.ALE.Parlamentar; using OPS.Importador.Utilities; +using RestSharp; +using UglyToad.PdfPig.Content; +using static System.Net.WebRequestMethods; +using File = System.IO.File; +using PdfDocument = UglyToad.PdfPig.PdfDocument; namespace OPS.Importador.ALE; @@ -22,8 +39,12 @@ public Alagoas(IServiceProvider serviceProvider) : base(serviceProvider) } } -public class ImportadorDespesasAlagoas : ImportadorDespesasRestApiMensal +public class ImportadorDespesasAlagoas : ImportadorDespesasRestApiAnual { + public ComputerVisionOcr ocrService; + + private CultureInfo cultureInfo = CultureInfo.CreateSpecificCulture("pt-BR"); + public ImportadorDespesasAlagoas(IServiceProvider serviceProvider) : base(serviceProvider) { config = new ImportadorCotaParlamentarBaseConfig() @@ -32,11 +53,595 @@ public ImportadorDespesasAlagoas(IServiceProvider serviceProvider) : base(servic Estado = Estado.Alagoas, ChaveImportacao = ChaveDespesaTemp.Indefinido }; + + ocrService = new ComputerVisionOcr(); + } + + public override void ImportarDespesas(IBrowsingContext context, int ano) + { + var today = DateTime.Today; + var document = context.OpenAsyncAutoRetry($"{config.BaseAddress}/{ano}").GetAwaiter().GetResult(); + + var parlamentares = document.QuerySelectorAll("#content-core .tileHeadline a"); + foreach (var parlamentar in parlamentares) + { + var nomeParlamentar = parlamentar.TextContent; + var urlParlamentarMeses = parlamentar.Attributes["href"].Value; + + using (logger.BeginScope(new Dictionary { ["Parlamentar"] = nomeParlamentar })) + { + var subdocument = context.OpenAsyncAutoRetry(urlParlamentarMeses).GetAwaiter().GetResult(); + var meses = subdocument.QuerySelectorAll("#content-core .tileHeadline a"); + + foreach (var mes in meses) + { + var mesExtenso = mes.TextContent; + var competencia = new DateTime(ano, ResolveMes(mesExtenso), 1); + if (competencia.AddMonths(1) > today) continue; + + using (logger.BeginScope(new Dictionary { ["Mes"] = competencia.Month })) + { + var urlParlamentarMesDocumento = mes.Attributes["href"].Value; + var subsubdocument = context.OpenAsyncAutoRetry(urlParlamentarMesDocumento).GetAwaiter().GetResult(); + + var urlPdf = subsubdocument.QuerySelector("#content-core a").Attributes["href"].Value; + if (string.IsNullOrEmpty(urlPdf)) + { + logger.LogWarning("Despesas indisponiveis para {Mes:00}/{Ano}.", competencia.Month, competencia.Year); + continue; + } + + using (logger.BeginScope(new Dictionary { ["Url"] = urlPdf, ["Arquivo"] = $"CLAL-{ano}-{competencia.Month}-{nomeParlamentar}.pdf" })) + { + ImportarDespesasArquivo(competencia.Year, competencia.Month, urlPdf, nomeParlamentar, competencia).Wait(); + } + } + } + } + } + + //TODO + //ImportarEmpenhosParaComparacao(context, ano); + } + + + async Task ImportarDespesasArquivo(int ano, int mes, string urlPdf, string nomeParlamentar, DateTime competencia) + { + var fileName = $"{tempPath}/CLAL-{ano}-{mes}-{nomeParlamentar}.pdf"; + BaixarArquivo(urlPdf, fileName); + + var ocrFileName = fileName.Replace(".pdf", ".txt"); + if (!File.Exists(ocrFileName)) + { + using (PdfDocument document = PdfDocument.Open(fileName)) + { + var imageIndex = 0; + var pages = document.GetPages(); + if (pages.Count() > 1) + { + + var sb = new StringBuilder(); + foreach (Page page in pages) + { + IReadOnlyList letters = page.Letters; + string example = string.Join(string.Empty, letters.Select(x => x.Value)); + + IEnumerable words = page.GetWords(); + string example2 = string.Join(string.Empty, words.Select(x => x.Text)); + + IEnumerable images = page.GetImages(); + foreach (IPdfImage image in images) + { + string ext = ".jpg"; + byte[] rawBytes = null; + if (image.TryGetPng(out rawBytes)) + { + ext = ".png"; + } + else if (image.TryGetBytes(out var rawBytesReadOnly)) + { + rawBytes = rawBytesReadOnly.ToArray(); + } + else + { + rawBytes = image.RawBytes.ToArray(); + } + + var imageFileName = fileName.Replace(".pdf", ++imageIndex + ext); + using (var fs = new FileStream(imageFileName, FileMode.Create, FileAccess.Write)) + { + fs.Write(rawBytes, 0, rawBytes.Length); + + Console.WriteLine(fs.Name); + } + + sb.AppendLine(await ocrService.ReadFileLocal(imageFileName)); + } + } + + File.Delete(ocrFileName); + File.WriteAllText(ocrFileName, sb.ToString()); + + if (imageIndex == 0) + Console.WriteLine("revisar: " + fileName); + } + else + { + var ocrLines = await ocrService.ReadFileLocal(fileName); + File.WriteAllText(ocrFileName, ocrLines); + } + } + + //using (PdfReader pdfReader = new PdfReader(fileName)) + //{ + // if (pdfReader.NumberOfPages > 1) + // { + // File.Move(ocrFileName, fileName.Replace(".pdf", ".bkp.2.txt")); + // } + //} + } + + var lines = File.ReadAllLines(ocrFileName); + + var modo = "CABECALHO"; + var descricaoDespesa = string.Empty; + decimal valorCalculado = 0; + decimal valorArquivoCalculado = 0; + var totalValidado = false; + var itensComValor = 0; + var countValoresTotais = 0; + + foreach (var linha in lines) + { + //Console.WriteLine(modo + ": " + linha); + + if (modo == "CABECALHO") + { + var linhaTemp = linha.ToUpper().Trim(); + if (linhaTemp == "PENDÊNCIA DE ASSINATURA") + { + logger.LogError("Arquivo não disponivel por pendência de assinatura!"); + return; + } + + if (!linhaTemp.StartsWith("VLR R") && + !linhaTemp.StartsWith("VER R") && + !linhaTemp.StartsWith("VIR R") && + !linhaTemp.StartsWith("VLR. R") && + !linhaTemp.StartsWith("VIR. R") && + !linhaTemp.StartsWith("VALOR")) continue; + + totalValidado = false; + modo = "CONTEUDO"; + continue; + } + + if (modo == "CONTEUDO") + { + var parts = linha.Split(new char[] { ' ', '|' }); + if (parts[0].Length < 3 && int.TryParse(parts[0], cultureInfo, out int sequencial)) + { + descricaoDespesa = linha.Replace(parts[0], "").Replace("-", "").Replace("|", "").TrimStart(); + continue; + } + + if (linha.Length < 15) + { + string valor = ObterValorNumerico(linha); + + if (decimal.TryParse(valor, cultureInfo, out decimal value)) + { + if (value > 35000) + { + logger.LogError("Valor com formatação incorreta ignorado: {Valor}", value); + continue; + } + + if (value.ToString(cultureInfo).Split(",").Length > 2) + { + logger.LogError("Valor com mais de duas casas decimais: {Valor}", value); + } + + if (value > 0) + { + CamaraEstadualDespesaTemp despesaTemp = new CamaraEstadualDespesaTemp() + { + Nome = nomeParlamentar, + Ano = (short)competencia.Year, + Mes = (short)competencia.Month, + DataEmissao = competencia, + Valor = value, + TipoDespesa = ResolveDescricao(descricaoDespesa) + }; + + valorCalculado += value; + valorArquivoCalculado += value; + itensComValor++; + InserirDespesaTemp(despesaTemp); + } + + continue; + } + } + + if (linha.StartsWith("TOTA", StringComparison.InvariantCultureIgnoreCase)) + { + countValoresTotais++; + modo = "RODAPE"; + + if (linha.Contains("R$") || linha.Contains("=") || linha.Contains(":") || linha.Split(" ", StringSplitOptions.RemoveEmptyEntries).Length == 2) + { + var valor = linha.Replace("/", "").Trim().Split(" ").Last(); + + if (valor.Length < 15 && decimal.TryParse(valor, cultureInfo, out decimal value)) + { + ValidaValorTotal(valorCalculado, value); + + totalValidado = true; + valorCalculado = 0; + modo = "CABECALHO"; + } + } + + continue; + } + + if (linha.Length < 5) continue; + descricaoDespesa += linha; + } + + if (modo == "RODAPE") + { + if (linha.Trim() == "R$" || linha.Trim() == "RS" || linha.StartsWith("DECLARAÇÃO E TERMO DE RESPONSABILIDADE") || linha.Length < 5) continue; + + var valor = ObterValorNumerico(linha); + + if (valor.Length < 15 && decimal.TryParse(valor, cultureInfo, out decimal value)) + { + ValidaValorTotal(valorCalculado, value); + + totalValidado = true; + } + + valorCalculado = 0; + modo = "CABECALHO"; + } + } + + using (PdfReader pdfReader = new PdfReader(fileName)) + { + if (pdfReader.NumberOfPages != countValoresTotais) + { + logger.LogWarning("Foi encontrado apenas {NumeroDeValoresTotais} em {NumeroPaginasPdf} páginas com valor total de {ValorTotal}", countValoresTotais, pdfReader.NumberOfPages, valorArquivoCalculado); + } + } + + if (!totalValidado) + { + logger.LogError("Valor total não validado! Valor esperado: {ValorEsperado}", valorCalculado); + } + else if (valorArquivoCalculado < 1000) + { + logger.LogError("Valor total abaixo do minimo esperado: {Valor}", valorArquivoCalculado); + } + else if (lines.Count() < 10) + { + logger.LogError("Linhas do abaixo do minimo esperado: {Valor}", lines.Count()); + } + + //logger.LogInformation("{QtdItens} despesas com valor total de {Valor}", itensComValor, valorArquivoCalculado); + } + + void ValidaValorTotal(decimal valorCalculado, decimal value) + { + var diferenca = Math.Abs(valorCalculado - value); + + if (diferenca > 100) + { + logger.LogError("Valor Divergente! Esperado: {ValorTotalArquivo}; Encontrado: {ValorTotalDeputado}; Diferenca: {Diferenca}", + value, valorCalculado, diferenca); + } + else if (diferenca > 0) + { + logger.LogWarning("Valor Divergente! Esperado: {ValorTotalArquivo}; Encontrado: {ValorTotalDeputado}; Diferenca: {Diferenca}", + value, valorCalculado, diferenca); + } + } + + static string ObterValorNumerico(string linha) + { + var valor = linha.Replace("R$", "").Replace("RS", "").Replace("$", "").Replace("/", "").Split("-")[0].Trim().Replace(" ", "."); + if (valor.Split(".").Length == 3) + { + valor = valor.ReplaceLast(".", ","); + } + if (valor.Split(",").Length == 3) + { + valor = valor.ReplaceFirst(",", "."); + } + if (valor.Contains(".") && !valor.Contains(",") && valor.Split(".")[1].Length <= 2) + { + valor = valor.Replace(".", ","); + } + + return valor; + } + + string ResolveDescricao(string texto) + { + if (string.IsNullOrEmpty(texto)) return string.Empty; + + var partes = texto.Split(new char[] { '.', '|' }, StringSplitOptions.RemoveEmptyEntries); + + for (int i = partes.Length - 1; i >= 0; i--) + { + switch (partes[i].RemoveAccents()) + { + // (14) + case string x when x.StartsWith("curso", StringComparison.InvariantCultureIgnoreCase) + || x.StartsWith("palestra", StringComparison.InvariantCultureIgnoreCase) + || x.StartsWith("seminario", StringComparison.InvariantCultureIgnoreCase) + || x.StartsWith("simposio", StringComparison.InvariantCultureIgnoreCase) + || x.StartsWith("congresso", StringComparison.InvariantCultureIgnoreCase): + return "Participação do parlamentar dos servidores lotados no seu gabinete em cursos, palestras, seminários, simpósios, congressos ou eventos congêneres"; + + // 13 (15) + case string x when x.Contains("correspondencia", StringComparison.InvariantCultureIgnoreCase) + || x.Contains("Grafic", StringComparison.InvariantCultureIgnoreCase): + return "Portes de correspondência, registros postais, aéreos e telegramas"; + + // (12) + case string x when x.StartsWith("seguranca", StringComparison.InvariantCultureIgnoreCase): + return "Serviços de segurança prestados por empresa especializada"; + + // 12 (11) + case string x when x.StartsWith("fotocopias, edicao de jornais, livros, revistas", StringComparison.InvariantCultureIgnoreCase): + return "Fotocópias, edição de jornais, livros, revistas e impressos e gráficos para consumo do gabinete e divulgação da atividade parlamentar"; + + // 11 (10) + case string x when x.StartsWith("producao de videos", StringComparison.InvariantCultureIgnoreCase) + || x.StartsWith("contratacao de empresa especializada para producao", StringComparison.InvariantCultureIgnoreCase) + || x.Contains("documentarios", StringComparison.InvariantCultureIgnoreCase) + || x.Contains("midia online", StringComparison.InvariantCultureIgnoreCase) + || x.Contains("propaganda eleitoral", StringComparison.InvariantCultureIgnoreCase): + return "Contratação de empresa especializada para produção de videos ou documentários para utilização na TV, em telões ou reuniões comunitárias vedadas o uso em campanha ou propaganda"; + // Contratação de empresa especializada para produção, exibição, disseminação conteúdo em mídia online e off-line para divulgação da atividade parlamentar vedadas o uso em campanha ou propaganda eleitoral. + + // 10 (9) + case string x when x.StartsWith("alimentacao d", StringComparison.InvariantCultureIgnoreCase): + return "Alimentação do parlamentar e dos servidores lotados no seu gabinete, mesmo na Capital do Estado, quando a necessidade do apoio à atividade parlamentar ou o próprio exercício desta atividade parlamentar assim exigir"; + + // 9 (13) + case string x when x.Contains("software", StringComparison.InvariantCultureIgnoreCase) + || x.Contains("TV a cabo ou similar", StringComparison.InvariantCultureIgnoreCase): + return "Aquisição ou locação de software, serviços postais; assinaturas de jornais, revistas, livros, publicações, TV a cabo ou similar e de acesso à internet."; + + // 8 + case string x when x.Contains("material de expediente", StringComparison.InvariantCultureIgnoreCase) + || x.Contains("informatica", StringComparison.InvariantCultureIgnoreCase) + || x.Contains("mantimentos para gabinete", StringComparison.InvariantCultureIgnoreCase) + || x.Contains("materiais para ornamentacao", StringComparison.InvariantCultureIgnoreCase) + || x.Contains("material de limpeza", StringComparison.InvariantCultureIgnoreCase): + return "Aquisição de material de expediente e suprimentos de informática"; + // Materiais para ornamentação de escritório para gabinete. + + // 7 + case string x when x.Contains("atividade parlamentar", StringComparison.InvariantCultureIgnoreCase) + || (x.Contains("publicacao", StringComparison.InvariantCultureIgnoreCase) && x.Contains("periodicos", StringComparison.InvariantCultureIgnoreCase)) + || x.Contains("disciplinadas pela legislacao eleitoral", StringComparison.InvariantCultureIgnoreCase): + return "Divulgação da atividade parlamentar em todas as modalidades de mídia, observando-se as restrições disciplinadas pela legislação eleitoral"; + // Serviço especializado em publicação, periódicos + + // [6] + case string x when x.StartsWith("servicos juridicos", StringComparison.InvariantCultureIgnoreCase) + || x.StartsWith("assessoria juridica", StringComparison.InvariantCultureIgnoreCase): + return "Serviços Jurídicos especializado"; + + // 6 [4] + case string x when x.Contains("assessoria", StringComparison.InvariantCultureIgnoreCase) + || x.Contains("consultoria", StringComparison.InvariantCultureIgnoreCase) + || x.Contains("publicidade", StringComparison.InvariantCultureIgnoreCase) + || x.Contains("pesquisas", StringComparison.InvariantCultureIgnoreCase) + || x.Contains("especializados", StringComparison.InvariantCultureIgnoreCase) + || x.Contains("projetos sociais", StringComparison.InvariantCultureIgnoreCase) + || x.Contains("Apoio a atividade", StringComparison.InvariantCultureIgnoreCase): + return "Contratação, para fins de apoio à atividade parlamentar, de empresas de consultoria, assessorias, elaboração de projetos sociais, pesquisas e outros trabalhos técnicos especializados"; + // Serviço Especializado Publicidade/Propaganda + + // [5] + case string x when x.Contains("graficos", StringComparison.InvariantCultureIgnoreCase) + || x.Contains("impressora", StringComparison.InvariantCultureIgnoreCase) + || x.Contains("papelaria", StringComparison.InvariantCultureIgnoreCase): + return "Serviços e materiais Gráficos"; + // Impressora e papelaria. + + // 5 + case string x when x.Contains("combustive", StringComparison.InvariantCultureIgnoreCase) + || x.Contains("reparacao de veiculos", StringComparison.InvariantCultureIgnoreCase) + || x.Contains("lubrificantes", StringComparison.InvariantCultureIgnoreCase) + || x.Contains("veiculos proprios", StringComparison.InvariantCultureIgnoreCase) + || x.Contains("contratados de terceiros, utilizados para o apoio", StringComparison.InvariantCultureIgnoreCase): + return "Combustíveis, lubrificantes, seguros, peças de reposição e reparação de veículos próprios ou contratados de terceiros, utilizados para o apoio ou exercício da atividade parlamentar"; + + // 4 + case string x when x.StartsWith("locacao de automoveis", StringComparison.InvariantCultureIgnoreCase): + return "Locação de automóveis de Pessoas Jurídicas"; + + // 3 + case string x when x.StartsWith("locomocao do parlamentar", StringComparison.InvariantCultureIgnoreCase) + || x.StartsWith("Hospedagem", StringComparison.InvariantCultureIgnoreCase) + || x.Contains("passagens", StringComparison.InvariantCultureIgnoreCase) + || x.Contains("fretamento de aeronaves", StringComparison.InvariantCultureIgnoreCase) + || x.Contains("locacao do parlamentar e dos servidores lotados", StringComparison.InvariantCultureIgnoreCase) // Erro de OCR + || x.StartsWith("servicos de taxi, uber ou similares, pedagio ou estacionamento", StringComparison.InvariantCultureIgnoreCase): + return "Locomoção do parlamentar e dos servidores lotados em seu gabinete, compreendendo passagens, hospedagem, alimentação e locação de meios de transporte"; + // Locomoção do parlamentar e dos servidores lotados em seu gabinete, compreendendo passagens, locação ou fretamento de aeronaves ou fretamentos de veículos automotores, locação ou fretamento de embarcações, serviços de taxi, uber ou similares, pedágio ou estacionamento, hospedagem, alimentação e locação de meios de transporte com ou sem condutor. + + // 2 + case string x when x.StartsWith("uso de telefone fixo ou movel", StringComparison.InvariantCultureIgnoreCase) + || x.StartsWith("uso do telefone fixo ou movel", StringComparison.InvariantCultureIgnoreCase) + || x.Contains("telefon", StringComparison.InvariantCultureIgnoreCase): + return "Uso de telefone fixo ou móvel que esteja a sendo utilizado pelo Parlamentar ou por servidor lotado em seu gabinete para o apoio ou exercício da atividade parlamentar"; + // Uso de telefone fixo ou móvel inclusive com plano de dados, que esteja sendo utilizado pelo Parlamentar ou por servidor lotado em seu gabinete para o apolo ou exercício da atividade parlamentar. + + // 1 + case string x when x.StartsWith("Locacao de imoveis", StringComparison.InvariantCultureIgnoreCase) + || x.Contains("iptu", StringComparison.InvariantCultureIgnoreCase) + || x.Contains("taxas condominiais", StringComparison.InvariantCultureIgnoreCase) + || x.Contains("escritorio de apoio", StringComparison.InvariantCultureIgnoreCase) + || x.Contains("energia eletrica", StringComparison.InvariantCultureIgnoreCase) + || x.Contains("manutencao", StringComparison.InvariantCultureIgnoreCase) + || x.StartsWith("aluguel escritorio", StringComparison.InvariantCultureIgnoreCase): + return "Locação de imóveis, máquinas, equipamentos e utensílios utilizados exclusivamente em escritório de apoio ao exercício da atividade parlamentar, inclusive taxas condominiais, Imposto Predial e Territorial Urbano - IPTU, Taxas de Corpo de Bombeiros, consumo de água e energia elétrica e outras despesas de manutenção e conservação dos referidos bens móveis ou imóveis"; + // Locação de imóveis, ou mesmo contratação de espaço compartilhado de trabalho, na modalidade coworking, incluindo os serviços indispensáveis ao funcionamento da unidade, locação de máquinas, equipamentos e utensilios utilizados exclusivamente em escritório de apoio ao exercício da atividade parlamentar, inclusive taxas condominiais, Imposto Predial e Territorial Urbano - IPTU, Taxas de Corpo de Bombeiros, seguro contra incêndio, consumo de água, despesa com esgoto e energia elétrica e outras despesas de manutenção e conservação dos referidos bens móveis ou imóveis + // Manutenção gabinete (serviço de pintura- material e mão de obra) + } + } + + logger.LogError("Descrição '{Descricao}' não localizada!", texto); + return texto; + } + + int ResolveMes(string mes) => mes.Trim().ToLower() switch + { + "janeiro" => 1, + "fevereiro" => 2, + "março" => 3, + "abril" => 4, + "maio" => 5, + "junho" => 6, + "julho" => 7, + "agosto" => 8, + "setembro" => 9, + "outubro" => 10, + "novembro" => 11, + "dezembro" => 12, + _ => throw new ArgumentOutOfRangeException(nameof(mes), $"Mês invalido: {mes}"), + }; + + + private void ImportarEmpenhosParaComparacao(IBrowsingContext context, int ano) + { + var i = 0; + var NomeFavorecido = i++; + var CNPJCPF = i++; + var Objeto = i++; + var TipoLicitacao = i++; + var NumeroEmpenho = i++; + var Data = i++; + var ValorEmpenhado = i++; + var ValorPago = i++; + + var document = context.OpenAsyncAutoRetry($"https://www.al.al.leg.br/transparencia/orcamento-e-financas/empenhos-e-pagamentos/{ano}").GetAwaiter().GetResult(); + + var meses = document.QuerySelectorAll("#content-core .headline a"); + foreach (var mes in meses) + { + var urlEmpenhosDoMes = mes.Attributes["href"].Value; + + using (logger.BeginScope(new Dictionary { ["Mes"] = mes.TextContent })) + { + var competencia = new DateOnly(ano, ResolveMes(mes.TextContent), 1); + + var subdocument = context.OpenAsyncAutoRetry(urlEmpenhosDoMes).GetAwaiter().GetResult(); + var empenhos = subdocument.QuerySelectorAll("#content-core .listing tbody tr"); + + foreach (var empenho in empenhos) + { + var linhas = empenho.QuerySelectorAll("td"); + + var nome = linhas[NomeFavorecido].TextContent.Trim(); + if (string.IsNullOrEmpty(nome) || nome.StartsWith("MÊS") || nome == "Nome do Favorecido" || nome == "ASSEMBLEIA LEGISLATIVA ESTADUAL") continue; + if (linhas[Objeto].TextContent.Trim() != "IDENIZAÇÃO DE DESPESA COM O GABINETE") continue; + if (linhas[ValorEmpenhado].TextContent.Trim() == "0,00") continue; + + var data = linhas[Data].TextContent.Trim().Replace(".20233", ".2023"); + if (data == "27.032.2023") data = "27/02/2023"; + else if (data == "2808/2023") data = "28/08/2023"; + else if (data == "72018/12") data = $"18/12/{ano}"; + else if (data.EndsWith("-abr")) data = $"{data.Replace("-abr", "/04")}/{ano}"; + else if (data.EndsWith("-mai")) data = $"{data.Replace("-mai", "/05")}/{ano}"; + else if (data.EndsWith("/ago")) data = $"{data.Replace("/ago", "/08")}/{ano}"; + else if (data.EndsWith("/set")) data = $"{data.Replace("/set", "/09")}/{ano}"; + + var empenhoTemp = new DeputadoEstadualEmpenhoTemp(); + empenhoTemp.Competencia = competencia; + empenhoTemp.NomeFavorecido = nome; + empenhoTemp.CNPJCPF = linhas[CNPJCPF].TextContent.Trim(); + //empenhoTemp.Objeto = linhas[Objeto].TextContent; + //empenhoTemp.TipoLicitacao = linhas[TipoLicitacao].TextContent; + empenhoTemp.NumeroEmpenho = linhas[NumeroEmpenho].TextContent.Trim(); + empenhoTemp.Data = DateOnly.Parse(data, cultureInfo); + empenhoTemp.ValorEmpenhado = Convert.ToDecimal(linhas[ValorEmpenhado].TextContent.Trim().Replace("39,24,83", "3924,83"), cultureInfo); + empenhoTemp.ValorPago = Convert.ToDecimal(linhas[ValorPago].TextContent.Trim().Replace("39,24,83", "3924,83"), cultureInfo); + + connection.Insert(empenhoTemp); + } + } + } + } + + public override void ValidaImportacao(int ano) + { + base.ValidaImportacao(ano); + + connection.Execute(@" +UPDATE ops_tmp.cl_empenho_temp SET nome_deputado = nome_favorecido WHERE nome_favorecido NOT LIKE '%DEPUTADO%'; +UPDATE ops_tmp.cl_empenho_temp SET nome_deputado = 'RONALDO MEDEIROS' WHERE nome_favorecido like 'AMANDA DA SILVA FERRAZ (DELEGATÁRIA DO DEPUTADO RONALDO MEDEIROS)'; +UPDATE ops_tmp.cl_empenho_temp SET nome_deputado = 'YVAN BELTRÃO' WHERE nome_favorecido like 'ANDERSON RONDINELLY LIRA PALMEIRA (DELEGATÁRIO DO DEPUTADO YVAN BELTRÃO)'; +UPDATE ops_tmp.cl_empenho_temp SET nome_deputado = 'GALBA NOVAES' WHERE nome_favorecido like 'ANTONIO FARIAS DA SILVA JUNIOR (DELEGATÁRIO DO DEPUTADO GALBA NOVAES)'; +UPDATE ops_tmp.cl_empenho_temp SET nome_deputado = 'GALBA NOVAES' WHERE nome_favorecido like 'ANTONIO FARIAS DA SILVA JUNIOR (DELEGATÁRIO DO DEPUTADO GALBA NOVAIS)'; +UPDATE ops_tmp.cl_empenho_temp SET nome_deputado = 'ALEXANDRE AYRES' WHERE nome_favorecido like 'CAIO QUINTELLA JUCÁ DUARTE (DELEGATÁRIO DO DEPUTADO ALEXANDRE AYRES)'; +UPDATE ops_tmp.cl_empenho_temp SET nome_deputado = 'DAVI MAIA' WHERE nome_favorecido like 'CLAUDIA MARQUES FREIRE (DELEGATÁRIA DO DEPUTADO DAVI MAIA)'; +UPDATE ops_tmp.cl_empenho_temp SET nome_deputado = 'GALBA NOVAES' WHERE nome_favorecido like 'DANIEL HENRIQUE NOVASI DE OLIVEIRA (DELEGATÁRIO DO DEPUTADO GALBA NOVAIS)'; +UPDATE ops_tmp.cl_empenho_temp SET nome_deputado = 'JAIR LIRA' WHERE nome_favorecido like 'DIEGO MELO FREITAS (DELEGATÁRIO DO DEPUTADO JAIR LIRA)'; +UPDATE ops_tmp.cl_empenho_temp SET nome_deputado = 'MESAQUE PADILHA' WHERE nome_favorecido like 'DOUGLAS DOS SANTOS SILVA (DELEGATÁRIO DO DEPUTADO MESAQUE PADILHA)'; +UPDATE ops_tmp.cl_empenho_temp SET nome_deputado = 'ANDRÉ LUIZ' WHERE nome_favorecido like 'FABIANO GOMES DE SOUZA (DELEGATÁRIO DO DEPUTADO ANDRÉ LUIZ)'; +UPDATE ops_tmp.cl_empenho_temp SET nome_deputado = 'ANDRÉ SILVA' WHERE nome_favorecido like 'FABIANO GOMES DE SOUZA (DELEGATÁRIO DO DEPUTADO ANDRÉ SILVA)'; +UPDATE ops_tmp.cl_empenho_temp SET nome_deputado = 'HENRIQUE CHICAO' WHERE nome_favorecido like 'FABIANO GOMES DE SOUZA (DELEGATÁRIO DO DEPUTADO HENRIQUE CHICAO)'; +UPDATE ops_tmp.cl_empenho_temp SET nome_deputado = 'ANDRÉ SILVA' WHERE nome_favorecido like 'FABRICIO GOMES DE SOUZA (DELEGATÁRIO DO DEPUTADO ANDRÉ SILVA)'; +UPDATE ops_tmp.cl_empenho_temp SET nome_deputado = 'DUDU RONALSA' WHERE nome_favorecido like 'FERNANDO PRIMOLA PEDROSA CARVALHO (DELEGATÁRIO DO DEPUTADO DUDU RONALSA)'; +UPDATE ops_tmp.cl_empenho_temp SET nome_deputado = 'Leonam Pinheiro Rodrigues' WHERE nome_favorecido like 'FÁBIO MALTA ALCANTARA RODRIGUES DE LIMA (DELEGATÁRIO DO DEPUTADO LEONAM PINHEIRO)'; +UPDATE ops_tmp.cl_empenho_temp SET nome_deputado = 'GILVAN BARROS' WHERE nome_favorecido like 'GERÔNIMO BEZERRA (DELEGATÁRIO DO DEPUTADO GILVAN BARROS)'; +UPDATE ops_tmp.cl_empenho_temp SET nome_deputado = 'INÁCIO LOIOLA' WHERE nome_favorecido like 'HERMANN JOSÉ DE AMORIM VASCONCELOS (DELEGATÁRIO DO DEPUTADO INÁCIO LOIOLA)'; +UPDATE ops_tmp.cl_empenho_temp SET nome_deputado = 'BRUNO TOLEDO' WHERE nome_favorecido like 'IRIS DA SILVA GOUVEIA (DELEGATÁRIA DO DEPUTADO BRUNO TOLEDO)'; +UPDATE ops_tmp.cl_empenho_temp SET nome_deputado = 'ANDRÉ SILVA' WHERE nome_favorecido like 'JOÃO GABRIEL GAIA GOMES (DELEGATARIO DO DEPUTADO ANDRÉ SILVA)'; +UPDATE ops_tmp.cl_empenho_temp SET nome_deputado = 'MARCELO VICTOR' WHERE nome_favorecido like 'KLAYDSON RYTHCHARDSON MARQUES SILVA (DELEGATÁRIO DO DEPUTADO MARCELO VICTOR)'; +UPDATE ops_tmp.cl_empenho_temp SET nome_deputado = 'RICARDO PEREIRA' WHERE nome_favorecido like 'MANOEL ANGELINO DA SILVA (DELEGATÁRIO DO DEPUTADO RICARDO PEREIRA)'; +UPDATE ops_tmp.cl_empenho_temp SET nome_deputado = 'TARCIZO FREIRE' WHERE nome_favorecido like 'MICHAEL VIEIRA DANTAS (DELEGATÁRIO DO DEPUTADO TARCIZO FREIRE)'; +UPDATE ops_tmp.cl_empenho_temp SET nome_deputado = 'MARCOS BARBOSA' WHERE nome_favorecido like 'SAULO DE TACIO FERNANDES GOMES DA COSTA (DELEGATÁRIO DO DEPUTADO MARCOS BARBOSA)'; +UPDATE ops_tmp.cl_empenho_temp SET nome_deputado = 'REMI CALHEIROS' WHERE nome_favorecido like 'SHIRLEY RIBEIRO MELO DE OLIVEIRA SILVA(DELEGATÁRIA DEPUTADO REMI CALHEIROS)'; +UPDATE ops_tmp.cl_empenho_temp SET nome_deputado = 'REMI CALHEIROS' WHERE nome_favorecido like 'FRANCISCO JOSÉ DA SILVA (DELEGATÁRIO REMI CALHEIROS)'; +UPDATE ops_tmp.cl_empenho_temp SET nome_deputado = 'BRENO ALBUQUERQUE' WHERE nome_favorecido like 'SILVANA MARIA BARBOSA GOMES DE MELO (DELEGATÁRIA DO DEPUTADO BRENO ALBUQUERQUE)'; +UPDATE ops_tmp.cl_empenho_temp SET nome_deputado = 'MARCELO VICTOR' WHERE nome_favorecido like 'THIAGO PIMENTEL LEITE TEIXEIRA (DELEGATÁRIO DO DEPUTADO MARCELO VICTOR)'; +UPDATE ops_tmp.cl_empenho_temp SET nome_deputado = 'SÂMIA MASCARENHAS' WHERE nome_favorecido like 'ANA CLAUDIA BEZERRA(DELEGATÁRIA DA DEPUTADA SAMIA VASCONCELOS'; +UPDATE ops_tmp.cl_empenho_temp SET nome_deputado = 'SÂMIA MASCARENHAS' WHERE nome_favorecido like 'ANA CLAUDIA BEZERRA(DELEGATÁRIA DA DEPUTADA SÂMIA MASCARENHAS)'; +UPDATE ops_tmp.cl_empenho_temp SET nome_deputado = 'CIBELE MOURA' WHERE nome_favorecido like 'CHRISTIANO HENRIQUE NASCIMENTO FARIAS (DELEGATÁRIO DO DEPUTADA CIBELE MOURA)'; +UPDATE ops_tmp.cl_empenho_temp SET nome_deputado = 'ANGELA CANUTO' WHERE nome_favorecido like 'CLAUDIA KALINE DE FRAIAS LARGES TORRES (DELEGATÁRIA DO DEPUTADA ANGELA CANUTO)'; +UPDATE ops_tmp.cl_empenho_temp SET nome_deputado = 'ANGELA GARROTE' WHERE nome_favorecido like 'ENIRALDO RIBEIRO BALBINO (DELEGATÁRIO DA DEPUTADA ANGELA GARROTE)'; +UPDATE ops_tmp.cl_empenho_temp SET nome_deputado = 'FLAVIA CAVALCANTE' WHERE nome_favorecido like 'MIRELLA DE LIMA GOMES REGO (DELEGATÁRIA DA DEPUTADA (FLAVIA CAVALCANTE)'; +UPDATE ops_tmp.cl_empenho_temp SET nome_deputado = 'ROSE DAVINO' WHERE nome_favorecido like 'RALPH DA CRUZ ALBERMAZ (DELEGATÁRIO DA DEPUTADA ROSE DAVINO)'; +UPDATE ops_tmp.cl_empenho_temp SET nome_deputado = 'FLAVIA CAVALCANTE' WHERE nome_favorecido like 'MIRELLA DE LIMA GOMES REGO (DELEGATÁRIA FLAVIA CAVALCANTE'; +UPDATE ops_tmp.cl_empenho_temp SET nome_deputado = 'ANGELA GARROTE' WHERE nome_favorecido like 'RALPH DA CRUZ ALBERNAZ(DELEGATÁRIO ANGELA GARROTE)'; +UPDATE ops_tmp.cl_empenho_temp SET nome_deputado = 'ROSE DAVINO' WHERE nome_favorecido like 'RALPH DA CRUZ ALBERNAZ(DELEGATÁRIO ROSE DAVINO'; +UPDATE ops_tmp.cl_empenho_temp SET nome_deputado = 'GABRIELA GONÇALVES' WHERE nome_favorecido like 'VICTOR LIMA ALBUQUERQUE (DELEGATÁRIO GABRIELA GONÇALVES)'; +UPDATE ops_tmp.cl_empenho_temp SET nome_deputado = 'REMI CALHEIROS' WHERE nome_favorecido like 'FRANCISCO JOSÉ DA SILVA (DELEGATÁRIO REMI CALHEIROS)'; +UPDATE ops_tmp.cl_empenho_temp SET nome_deputado = 'ANTONIO RIBEIRO DE ALBUQUERQUE' where nome_deputado = 'ANTONIO RIBEIRO DE ALBUQUERQUE';"); + + + } + + private class ParlamentaresPR + { + public string status { get; set; } + public string content { get; set; } + public bool isLastPage { get; set; } } - public override void ImportarDespesas(IBrowsingContext context, int ano, int mes) + private class Fotos { - logger.LogWarning("Dados em PDF scaneado e de baixa qualidade!"); + public string pathXxlarge { get; set; } + public string name { get; set; } } } diff --git a/OPS.Importador/ALE/Amapa.cs b/OPS.Importador/ALE/Amapa.cs index 0e0385a..13c4250 100644 --- a/OPS.Importador/ALE/Amapa.cs +++ b/OPS.Importador/ALE/Amapa.cs @@ -74,14 +74,7 @@ public override void ImportarDespesas(IBrowsingContext context, int ano, int mes if (primeiraColuna.TextContent == "TOTAL") continue; var linkDetalhes = (primeiraColuna.QuerySelector("a") as IHtmlAnchorElement); - var despesaTemp = new CamaraEstadualDespesaTemp() - { - Nome = gabinete.Text.Split("-")[0].Trim().ToTitleCase(), - Cpf = deputado?.Matricula?.ToString(), - Ano = (short)ano, - Mes = (short)mes, - TipoDespesa = linkDetalhes.Text.Split(" - ")[1].Trim(), - }; + var subDocument = context.OpenAsyncAutoRetry(linkDetalhes.Href).GetAwaiter().GetResult(); var linhasDespesasDetalhes = subDocument.QuerySelectorAll(".ls-table tbody tr"); @@ -90,6 +83,15 @@ public override void ImportarDespesas(IBrowsingContext context, int ano, int mes var colunas = detalhes.QuerySelectorAll("td"); if (colunas[0].TextContent == "TOTAL") continue; + var despesaTemp = new CamaraEstadualDespesaTemp() + { + Nome = gabinete.Text.Split("-")[0].Trim().ToTitleCase(), + Cpf = deputado?.Matricula?.ToString(), + Ano = (short)ano, + Mes = (short)mes, + TipoDespesa = linkDetalhes.Text.Split(" - ")[1].Trim(), + }; + var empresaParts = colunas[0].TextContent.Split(" - "); despesaTemp.CnpjCpf = Utils.RemoveCaracteresNaoNumericos(empresaParts[0].Trim()); despesaTemp.Empresa = empresaParts[1].Trim(); diff --git a/OPS.Importador/ALE/Bahia.cs b/OPS.Importador/ALE/Bahia.cs index 1ab5f7d..9b5a034 100644 --- a/OPS.Importador/ALE/Bahia.cs +++ b/OPS.Importador/ALE/Bahia.cs @@ -41,7 +41,7 @@ public ImportadorDespesasBahia(IServiceProvider serviceProvider) : base(serviceP { BaseAddress = "https://www.al.ba.gov.br/", Estado = Estado.Bahia, - ChaveImportacao = ChaveDespesaTemp.Indefinido + ChaveImportacao = ChaveDespesaTemp.NomeParlamentar }; } diff --git a/OPS.Importador/ALE/Despesa/ImportadorBase.cs b/OPS.Importador/ALE/Despesa/ImportadorBase.cs index 7f5f0c3..000cef2 100644 --- a/OPS.Importador/ALE/Despesa/ImportadorBase.cs +++ b/OPS.Importador/ALE/Despesa/ImportadorBase.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using OPS.Core; @@ -15,110 +17,134 @@ public abstract class ImportadorBase protected IImportadorDespesas importadorDespesas { get; set; } + private bool importacaoIncremental { get; init; } + public ImportadorBase(IServiceProvider serviceProvider) { - logger = serviceProvider.GetService>(); + logger = serviceProvider.GetRequiredService>(); + + var configuration = serviceProvider.GetRequiredService(); + importacaoIncremental = Convert.ToBoolean(configuration["AppSettings:ImportacaoDespesas:Incremental"] ?? "false"); } public virtual void ImportarCompleto() { - var watch = System.Diagnostics.Stopwatch.StartNew(); - if (importadorParlamentar != null) { - using (logger.BeginScope("Perfil do Parlamentar")) - try - { - watch.Restart(); - - - importadorParlamentar.Importar().Wait(); - } - catch (Exception ex) - { - logger.LogError(ex, ex.Message); - } - finally - { - watch.Stop(); - logger.LogInformation("Processamento em {TimeElapsed:c}", watch.Elapsed); - } - - using (logger.BeginScope("Foto de Perfil")) - try - { - watch.Restart(); - - importadorParlamentar.DownloadFotos().Wait(); - } - catch (Exception ex) - { - logger.LogError(ex, ex.Message); - } - finally - { - watch.Stop(); - logger.LogInformation("Processamento em {TimeElapsed:c}", watch.Elapsed); - } + ImportarPerfilParlamentar(); + ImportarImagemParlamentar(); } if (importadorDespesas != null) { - using (logger.BeginScope("Despesas {Ano}", DateTime.Now.Year - 1)) - try - { - watch.Restart(); - - importadorDespesas.Importar(DateTime.Now.Year - 1); - } - catch (BusinessException) { } - catch (Exception ex) - { - logger.LogError(ex, ex.Message); - } - finally - { - watch.Stop(); - logger.LogInformation("Processamento em {TimeElapsed:c}", watch.Elapsed); - } - - using (logger.BeginScope("Despesas {Ano}", DateTime.Now.Year)) - try - { - watch.Restart(); - - importadorDespesas.Importar(DateTime.Now.Year); - } - catch (BusinessException) { } - catch (Exception ex) - { - logger.LogError(ex, ex.Message); - } - finally - { - watch.Stop(); - logger.LogInformation("Processamento em {TimeElapsed:c}", watch.Elapsed); - } + ImportarDespesasAnoAnterior(); + ImportarDespesasAnoAtual(); } - // using (logger.BeginScope("Remuneração")) - //try - //{ - // watch.Restart(); - - // var anoAtual = DateTime.Now; - // logger.LogInformation("Remuneração do(a) {CasaLegislativa}", config.SiglaEstado); - // ImportarRemuneracao(anoAtual.Year, anoAtual.Month); - //} - //catch (Exception ex) - //{ - // logger.LogError(ex, ex.Message); - //} - //finally - //{ - // watch.Stop(); - // logger.LogDebug("Processamento em {TimeElapsed:c}", watch.Elapsed); - //} + ImportarRemuneracao(); + } + + private void ImportarRemuneracao() + { + //var watch = System.Diagnostics.Stopwatch.StartNew(); + //using (logger.BeginScope("Remuneração")) + // try + // { + // watch.Restart(); + + // var anoAtual = DateTime.Now; + // logger.LogInformation("Remuneração do(a) {CasaLegislativa}", config.SiglaEstado); + // ImportarRemuneracao(anoAtual.Year, anoAtual.Month); + // } + // catch (Exception ex) + // { + // logger.LogError(ex, ex.Message); + // } + // finally + // { + // watch.Stop(); + // logger.LogDebug("Processamento em {TimeElapsed:c}", watch.Elapsed); + // } + } + + private void ImportarDespesasAnoAtual() + { + var watch = System.Diagnostics.Stopwatch.StartNew(); + using (logger.BeginScope("Despesas {Ano}", DateTime.Now.Year)) + try + { + importadorDespesas.Importar(DateTime.Now.Year); + } + catch (BusinessException) { } + catch (Exception ex) + { + logger.LogError(ex, ex.Message); + } + finally + { + watch.Stop(); + logger.LogInformation("Processamento em {TimeElapsed:c}", watch.Elapsed); + } + } + + private void ImportarDespesasAnoAnterior() + { + //if (importacaoIncremental) return; + + var watch = System.Diagnostics.Stopwatch.StartNew(); + using (logger.BeginScope("Despesas {Ano}", DateTime.Now.Year - 1)) + try + { + importadorDespesas.Importar(DateTime.Now.Year - 1); + } + catch (BusinessException) { } + catch (Exception ex) + { + logger.LogError(ex, ex.Message); + } + finally + { + watch.Stop(); + logger.LogInformation("Processamento em {TimeElapsed:c}", watch.Elapsed); + } + } + + private void ImportarImagemParlamentar() + { + var watch = System.Diagnostics.Stopwatch.StartNew(); + using (logger.BeginScope("Foto de Perfil")) + try + { + importadorParlamentar.DownloadFotos().Wait(); + } + catch (Exception ex) + { + logger.LogError(ex, ex.Message); + } + finally + { + watch.Stop(); + logger.LogInformation("Processamento em {TimeElapsed:c}", watch.Elapsed); + } + } + + private void ImportarPerfilParlamentar() + { + var watch = System.Diagnostics.Stopwatch.StartNew(); + using (logger.BeginScope("Perfil do Parlamentar")) + try + { + importadorParlamentar.Importar().Wait(); + } + catch (Exception ex) + { + logger.LogError(ex, ex.Message); + } + finally + { + watch.Stop(); + logger.LogInformation("Processamento em {TimeElapsed:c}", watch.Elapsed); + } } } } diff --git a/OPS.Importador/ALE/Despesa/ImportadorDespesasArquivo.cs b/OPS.Importador/ALE/Despesa/ImportadorDespesasArquivo.cs index 1db0a6f..dc36474 100644 --- a/OPS.Importador/ALE/Despesa/ImportadorDespesasArquivo.cs +++ b/OPS.Importador/ALE/Despesa/ImportadorDespesasArquivo.cs @@ -12,6 +12,7 @@ public ImportadorDespesasArquivo(IServiceProvider serviceProvider) : base(servic public virtual void Importar(int ano) { + var anoAtual = DateTime.Today.Year; using (logger.BeginScope(new Dictionary { ["Ano"] = ano })) { CarregarHashes(ano); @@ -25,16 +26,21 @@ public virtual void Importar(int ano) using (logger.BeginScope(new Dictionary { ["Url"] = _urlOrigem, ["Arquivo"] = System.IO.Path.GetFileName(caminhoArquivo) })) { - if (TentarBaixarArquivo(_urlOrigem, caminhoArquivo)) + var arquivoJaProcessado = BaixarArquivo(_urlOrigem, caminhoArquivo); + if(anoAtual != ano && importacaoIncremental && arquivoJaProcessado && arquivos.Count == 1 && config.Estado != Estado.Piaui) + { + logger.LogInformation("Importação ignorada para arquivo previamente importado!"); + return; + } + + try + { + ImportarDespesas(caminhoArquivo, ano); + } + catch (Exception ex) { - try - { - ImportarDespesas(caminhoArquivo, ano); - } - catch (Exception ex) - { - logger.LogError(ex, ex.Message); + logger.LogError(ex, ex.Message); #if !DEBUG //Excluir o arquivo para tentar importar novamente na proxima execução @@ -42,7 +48,6 @@ public virtual void Importar(int ano) System.IO.File.Delete(caminhoArquivo); #endif - } } } } diff --git a/OPS.Importador/ALE/Despesa/ImportadorDespesasBase.cs b/OPS.Importador/ALE/Despesa/ImportadorDespesasBase.cs index fdefc61..bb2bd43 100644 --- a/OPS.Importador/ALE/Despesa/ImportadorDespesasBase.cs +++ b/OPS.Importador/ALE/Despesa/ImportadorDespesasBase.cs @@ -50,6 +50,8 @@ public string tempPath public int idEstado { get { return config.Estado.GetHashCode(); } } + public bool importacaoIncremental { get; set; } + private int itensProcessadosAno { get; set; } private decimal valorTotalProcessadoAno { get; set; } @@ -57,7 +59,7 @@ public string tempPath protected Dictionary lstHash { get; private set; } private HttpClient _httpClient; - public HttpClient httpClient { get { return _httpClient ??= httpClientFactory.CreateClient("MyNamedClient"); } } + public HttpClient httpClient { get { return _httpClient ??= httpClientFactory.CreateClient("ResilientClient"); } } private IHttpClientFactory httpClientFactory { get; } @@ -71,6 +73,7 @@ public ImportadorDespesasBase(IServiceProvider serviceProvider) var configuration = serviceProvider.GetService(); rootPath = configuration["AppSettings:SiteRootFolder"]; _tempPath = configuration["AppSettings:SiteTempFolder"]; + importacaoIncremental = Convert.ToBoolean(configuration["AppSettings:ImportacaoDespesas:Incremental"] ?? "false"); httpClientFactory = serviceProvider.GetService(); @@ -101,7 +104,7 @@ public virtual void ProcessarDespesas(int ano) InsereDespesaFinal(ano); ValidaImportacao(ano); - + if (ano == DateTime.Now.Year) { try @@ -181,43 +184,18 @@ public virtual void AtualizaResumoMensal() { } public virtual Dictionary DefinirUrlOrigemCaminhoDestino(int ano) { return null; } - protected bool TentarBaixarArquivo(string urlOrigem, string caminhoArquivo) - { - var watch = System.Diagnostics.Stopwatch.StartNew(); - try - { - return BaixarArquivo(urlOrigem, caminhoArquivo); - } - catch (Exception ex) - { - logger.LogWarning("Erro ao baixar arquivo: {Message}", ex.Message); - - // Algumas vezes ocorre do arquivo não estar disponivel, precisamos aguardar alguns instantes e tentar novamente. - // Isso pode ser causado por um erro de rede ou atualização do arquivo. - Thread.Sleep((int)TimeSpan.FromMinutes(1).TotalMilliseconds); - - return BaixarArquivo(urlOrigem, caminhoArquivo); - } - finally - { - watch.Stop(); - logger.LogTrace("Arquivo baixado em {TimeElapsed:c}", watch.Elapsed); - } - } - - private bool BaixarArquivo(string urlOrigem, string caminhoArquivo) + protected bool BaixarArquivo(string urlOrigem, string caminhoArquivo) { - logger.LogTrace("Baixando arquivo '{CaminhoArquivo}' a partir de '{UrlOrigem}'", caminhoArquivo, urlOrigem); + if (importacaoIncremental && File.Exists(caminhoArquivo)) return false; + logger.LogTrace($"Baixando arquivo '{caminhoArquivo}' a partir de '{urlOrigem}'"); string diretorio = new FileInfo(caminhoArquivo).Directory.ToString(); if (!Directory.Exists(diretorio)) Directory.CreateDirectory(diretorio); else if (File.Exists(caminhoArquivo)) - return true; - //File.Delete(caminhoArquivo); + File.Delete(caminhoArquivo); httpClient.DownloadFile(urlOrigem, caminhoArquivo).Wait(); - return true; } @@ -327,7 +305,7 @@ FROM cl_deputado AND nome_civil IS NOT null );"); } - else // Nome + else if (config.ChaveImportacao == ChaveDespesaTemp.NomeParlamentar) { affected = connection.Execute(@$" INSERT INTO cl_deputado (nome_parlamentar, nome_civil, cpf_parcial, id_estado) @@ -340,6 +318,23 @@ FROM cl_deputado AND nome_parlamentar IS NOT null );"); } + else + { + var deputadosNaoLocalizados = connection.ExecuteScalar(@$" +SELECT GROUP_CONCAT(DISTINCT nome) +FROM ops_tmp.cl_despesa_temp +WHERE nome NOT IN ( + SELECT IFNULL(nome_importacao, nome_parlamentar) + FROM cl_deputado + WHERE id_estado = {idEstado} + AND nome_parlamentar IS NOT null +);"); + + if(!string.IsNullOrEmpty(deputadosNaoLocalizados)) + { + throw new Exception($"Deputados não cadastrados: {deputadosNaoLocalizados}"); + } + } if (affected > 0) { @@ -490,7 +485,7 @@ public virtual (int, decimal) RegistrosBaseDeDadosFinalAgregados(int ano) var sql = @$" select count(1) as itens, - IFNULL(sum(valor_liquido) as valor_total + IFNULL(sum(valor_liquido), 0) as valor_total from cl_despesa d join cl_deputado p on p.id = d.id_cl_deputado where p.id_estado = {idEstado} diff --git a/OPS.Importador/ALE/Despesa/ImportadorDespesasRestApiMensal.cs b/OPS.Importador/ALE/Despesa/ImportadorDespesasRestApiMensal.cs index 93e5212..2f94f57 100644 --- a/OPS.Importador/ALE/Despesa/ImportadorDespesasRestApiMensal.cs +++ b/OPS.Importador/ALE/Despesa/ImportadorDespesasRestApiMensal.cs @@ -31,7 +31,10 @@ public void Importar(int ano) //if (ano == 2019 && mes == 1) continue; if (ano == DateTime.Now.Year && mes > DateTime.Today.Month) break; - ImportarDespesas(context, ano, mes); + using (logger.BeginScope(new Dictionary { ["Mes"] = mes })) + { + ImportarDespesas(context, ano, mes); + } } ProcessarDespesas(ano); diff --git a/OPS.Importador/ALE/MatoGrosso.cs b/OPS.Importador/ALE/MatoGrosso.cs index 9d60a97..15e18ac 100644 --- a/OPS.Importador/ALE/MatoGrosso.cs +++ b/OPS.Importador/ALE/MatoGrosso.cs @@ -25,7 +25,7 @@ public ImportadorDespesasMatoGrosso(IServiceProvider serviceProvider) : base(ser { BaseAddress = "", Estado = Estado.MatoGrosso, - ChaveImportacao = ChaveDespesaTemp.Indefinido + ChaveImportacao = ChaveDespesaTemp.NomeParlamentar }; } diff --git a/OPS.Importador/ALE/Paraiba.cs b/OPS.Importador/ALE/Paraiba.cs index 896c600..b58a78e 100644 --- a/OPS.Importador/ALE/Paraiba.cs +++ b/OPS.Importador/ALE/Paraiba.cs @@ -3,6 +3,9 @@ using System.Globalization; using System.IO; using System.Linq; +using System.Net; +using System.Text.Json.Serialization; +using System.Threading.Tasks; using AngleSharp; using AngleSharp.Dom; using AngleSharp.Html.Dom; @@ -52,8 +55,6 @@ public override void ImportarDespesas(IBrowsingContext context, int ano, int mes { if (gabinete.Value == "0") continue; - - var dcForm = new Dictionary(); dcForm.Add("deputado", gabinete.Value); var subDocument = form.SubmitAsyncAutoRetry(dcForm, true).GetAwaiter().GetResult(); @@ -63,24 +64,23 @@ public override void ImportarDespesas(IBrowsingContext context, int ano, int mes using (logger.BeginScope(new Dictionary { ["Parlamentar"] = gabinete.Text, ["Url"] = linkPlanilha, ["Arquivo"] = Path.GetFileName(caminhoArquivo) })) { - if (TentarBaixarArquivo(linkPlanilha, caminhoArquivo)) + BaixarArquivo(linkPlanilha, caminhoArquivo); + + try + { + ImportarDespesas(caminhoArquivo, ano, mes, gabinete.Value, gabinete.Text); + } + catch (Exception ex) { - try - { - ImportarDespesas(caminhoArquivo, ano, mes, gabinete.Value, gabinete.Text); - } - catch (Exception) - { - //logger.LogError(ex, ex.Message); + logger.LogError(ex, ex.Message); #if !DEBUG - //Excluir o arquivo para tentar importar novamente na proxima execução - if(System.IO.File.Exists(caminhoArquivo)) - System.IO.File.Delete(caminhoArquivo); + //Excluir o arquivo para tentar importar novamente na proxima execução + if(System.IO.File.Exists(caminhoArquivo)) + System.IO.File.Delete(caminhoArquivo); #endif - } } } } @@ -210,45 +210,96 @@ private enum ColunasOds } -public class ImportadorParlamentarParaiba : ImportadorParlamentarCrawler +public class ImportadorParlamentarParaiba : ImportadorParlamentarRestApi { private CultureInfo cultureInfo = CultureInfo.CreateSpecificCulture("pt-BR"); public ImportadorParlamentarParaiba(IServiceProvider serviceProvider) : base(serviceProvider) { - Configure(new ImportadorParlamentarCrawlerConfig() + Configure(new ImportadorParlamentarConfig() { - BaseAddress = "http://sapl.al.pb.leg.br/sapl/consultas/parlamentar/parlamentar_index_html?hdn_num_legislatura=19", - SeletorListaParlamentares = ".tileItem", + BaseAddress = "https://sapl3.al.pb.leg.br/", Estado = Estado.Paraiba, }); } - public override DeputadoEstadual ColetarDadosLista(IElement item) + public override Task Importar() + { + ArgumentNullException.ThrowIfNull(config, nameof(config)); + + var legislatura = 20; // 2023-2027 + var address = $"{config.BaseAddress}api/parlamentares/legislatura/{legislatura}/parlamentares/?get_all=true"; + List parlamentares = RestApiGet>(address); + + foreach (var parlamentar in parlamentares) + { + var matricula = (uint)parlamentar.Id; + DeputadoEstadual deputado = GetDeputadoByNameOrNew(parlamentar.NomeParlamentar); + + deputado.UrlPerfil = $"https://sapl3.al.pb.leg.br/parlamentar/{parlamentar.Id}"; + deputado.NomeParlamentar = parlamentar.NomeParlamentar.ToTitleCase(); + deputado.IdPartido = BuscarIdPartido(parlamentar.Partido); + deputado.UrlFoto = parlamentar.Fotografia; + + ObterDetalhesDoPerfil(deputado).GetAwaiter().GetResult(); + + InsertOrUpdate(deputado); + } + + logger.LogInformation("Parlamentares Inseridos: {Inseridos}; Atualizados {Atualizados};", base.registrosInseridos, base.registrosAtualizados); + return Task.CompletedTask; + } + + private async Task ObterDetalhesDoPerfil(DeputadoEstadual deputado) { - var nomeCivil = item.QuerySelector(".tileHeadline a").TextContent.Trim().ToTitleCase(); - var deputado = GetDeputadoByFullNameOrNew(nomeCivil); + var angleSharpConfig = Configuration.Default.WithDefaultLoader(); + var context = BrowsingContext.New(angleSharpConfig); + + var document = await context.OpenAsyncAutoRetry(deputado.UrlPerfil); + if (document.StatusCode != HttpStatusCode.OK) + { + Console.WriteLine($"{config.BaseAddress} {document.StatusCode}"); + }; - deputado.UrlPerfil = (item.QuerySelector(".tileHeadline a") as IHtmlAnchorElement).Href; - deputado.IdPartido = BuscarIdPartido(item.QuerySelector(".parlamentar-partido .texto").TextContent.Trim()); + var perfil = document.QuerySelector("#content"); - return deputado; + deputado.NomeCivil = perfil.QuerySelector("#div_nome").TextContent.Split(":")[1].Trim().ToTitleCase(); + + var elementos = perfil.QuerySelectorAll(".form-group>p").Select(x => x.TextContent); + if (elementos.Any()) + { + deputado.Email = elementos.Where(x => x.StartsWith("E-mail")).FirstOrDefault()?.Split(':')[1].Trim().NullIfEmpty(); + deputado.Telefone = elementos.Where(x => x.StartsWith("Telefone")).FirstOrDefault()?.Split(':')[1].Trim().NullIfEmpty(); + } + else + { + logger.LogWarning("Verificar possivel mudança no perfil do parlamentar: {UrlPerfil}", deputado.UrlPerfil); + } } - public override void ColetarDadosPerfil(DeputadoEstadual deputado, IDocument subDocument) + private class Congressman { - deputado.NomeParlamentar = subDocument.QuerySelector("h1.firstHeading").TextContent.Trim().ToTitleCase(); - deputado.UrlFoto = (subDocument.QuerySelector("img.parlamentar") as IHtmlImageElement)?.Source; + [JsonPropertyName("id")] + public int Id { get; set; } + + [JsonPropertyName("nome_parlamentar")] + public string NomeParlamentar { get; set; } + + [JsonPropertyName("fotografia_cropped")] + public string FotografiaCropped { get; set; } - var perfil = subDocument.QuerySelectorAll("#texto-parlamentar b") - .Select(x => new { Key = x.TextContent.Replace(":", "").Trim(), Value = x.NextSibling.TextContent.Trim() }); + [JsonPropertyName("fotografia")] + public string Fotografia { get; set; } - if (!string.IsNullOrEmpty(perfil.First(x => x.Key == "Data Nascimento")?.Value)) - deputado.Nascimento = DateOnly.Parse(perfil.First(x => x.Key == "Data Nascimento").Value, cultureInfo); + [JsonPropertyName("ativo")] + public bool Ativo { get; set; } - deputado.Email = perfil.FirstOrDefault(x => x.Key == "E-mail")?.Value.NullIfEmpty(); - deputado.Telefone = perfil.FirstOrDefault(x => x.Key == "Telefone")?.Value.NullIfEmpty(); + [JsonPropertyName("partido")] + public string Partido { get; set; } - // ImportacaoUtils.MapearRedeSocial(deputado, subDocument.QuerySelectorAll(".deputado ul a")); // Todos são as redes sociaos da AL + [JsonPropertyName("titular")] + public string Titular { get; set; } } -} \ No newline at end of file +} + + diff --git a/OPS.Importador/ALE/Parana.cs b/OPS.Importador/ALE/Parana.cs index 4b77eb7..14b87b9 100644 --- a/OPS.Importador/ALE/Parana.cs +++ b/OPS.Importador/ALE/Parana.cs @@ -478,11 +478,13 @@ public override DeputadoEstadual ColetarDadosLista(IElement parlamentar) { var nomeParlamentar = parlamentar.QuerySelector(".nome-deps span").TextContent.Trim().ReduceWhitespace(); var nomeParlamentarLimpo = Utils.RemoveAccents(nomeParlamentar); + var urlPerfil = (parlamentar.QuerySelector("a") as IHtmlAnchorElement).Href; var deputado = deputados.Find(x => Utils.RemoveAccents(x.NomeImportacao ?? "").Equals(nomeParlamentarLimpo, StringComparison.InvariantCultureIgnoreCase) || Utils.RemoveAccents(x.NomeParlamentar).Equals(nomeParlamentarLimpo, StringComparison.InvariantCultureIgnoreCase) || - Utils.RemoveAccents(x.NomeCivil ?? "").Equals(nomeParlamentarLimpo, StringComparison.InvariantCultureIgnoreCase) + Utils.RemoveAccents(x.NomeCivil ?? "").Equals(nomeParlamentarLimpo, StringComparison.InvariantCultureIgnoreCase) || + x.UrlPerfil == urlPerfil ); if (deputado == null) @@ -498,7 +500,7 @@ public override DeputadoEstadual ColetarDadosLista(IElement parlamentar) deputado.NomeCivil = deputado.NomeParlamentar; deputado.IdPartido = BuscarIdPartido(parlamentar.QuerySelector(".nome-deps .partido").TextContent.Trim()); - deputado.UrlPerfil = (parlamentar.QuerySelector("a") as IHtmlAnchorElement).Href; + deputado.UrlPerfil = urlPerfil; deputado.UrlFoto = (parlamentar.QuerySelector(".foto-deps img") as IHtmlImageElement)?.Source; return deputado; diff --git a/OPS.Importador/ALE/Parlamentar/ImportadorParlamentarBase.cs b/OPS.Importador/ALE/Parlamentar/ImportadorParlamentarBase.cs index c60b01c..cfcccbe 100644 --- a/OPS.Importador/ALE/Parlamentar/ImportadorParlamentarBase.cs +++ b/OPS.Importador/ALE/Parlamentar/ImportadorParlamentarBase.cs @@ -75,10 +75,19 @@ protected DeputadoEstadual GetDeputadoByNameOrNew(string nomeParlamentar) }) .FirstOrDefault(); - if (Debugger.IsAttached && deputado == null) - Trace.WriteLine($"Deputado {nomeParlamentar} não localizado."); + if (deputado == null) + deputado = connection + .GetList(new + { + id_estado = config.Estado, + nome_importacao = nomeParlamentar + }) + .FirstOrDefault(); + + if (deputado != null) + return deputado; - return deputado ?? new DeputadoEstadual() + return new DeputadoEstadual() { IdEstado = (ushort)config.Estado, NomeParlamentar = nomeParlamentar @@ -95,10 +104,10 @@ protected DeputadoEstadual GetDeputadoByFullNameOrNew(string nome) }) .FirstOrDefault(); - if (Debugger.IsAttached && deputado == null) - Trace.WriteLine($"Deputado {nome} não localizado."); + if (deputado != null) + return deputado; - return deputado ?? new DeputadoEstadual() + return new DeputadoEstadual() { IdEstado = (ushort)config.Estado, NomeCivil = nome @@ -107,18 +116,22 @@ protected DeputadoEstadual GetDeputadoByFullNameOrNew(string nome) protected DeputadoEstadual GetDeputadoByMatriculaOrNew(uint matricula) { - return connection + var deputado = connection .GetList(new { id_estado = config.Estado, matricula }) - .FirstOrDefault() - ?? new DeputadoEstadual() - { - IdEstado = (ushort)config.Estado, - Matricula = matricula - }; + .FirstOrDefault(); + + if (deputado != null) + return deputado; + + return new DeputadoEstadual() + { + IdEstado = (ushort)config.Estado, + Matricula = matricula + }; } public void InsertOrUpdate(DeputadoEstadual deputado) diff --git a/OPS.Importador/ALE/Piaui.cs b/OPS.Importador/ALE/Piaui.cs index 1c4a94b..94bd551 100644 --- a/OPS.Importador/ALE/Piaui.cs +++ b/OPS.Importador/ALE/Piaui.cs @@ -2,8 +2,10 @@ using System.Collections.Generic; using System.Globalization; using System.IO; +using System.Linq; using System.Net; using System.Text; +using System.Text.Json.Serialization; using System.Threading.Tasks; using AngleSharp; using AngleSharp.Dom; @@ -179,112 +181,93 @@ private enum ColunasDespesas } -public class ImportadorParlamentarPiaui : ImportadorParlamentarCrawler +public class ImportadorParlamentarPiaui : ImportadorParlamentarRestApi { public ImportadorParlamentarPiaui(IServiceProvider serviceProvider) : base(serviceProvider) { - Configure(new ImportadorParlamentarCrawlerConfig() + Configure(new ImportadorParlamentarConfig() { - BaseAddress = "https://www.al.pi.leg.br/", - SeletorListaParlamentares = "#bloco-fotos-parlamentar>div", + BaseAddress = "https://sapl.al.pi.leg.br/", Estado = Estado.Piaui, }); } - public override async Task Importar() + public override Task Importar() { ArgumentNullException.ThrowIfNull(config, nameof(config)); - var angleSharpConfig = Configuration.Default.WithDefaultLoader(); - var context = BrowsingContext.New(angleSharpConfig); + var legislatura = 20; // 2023-2027 + var address = $"{config.BaseAddress}api/parlamentares/legislatura/{legislatura}/parlamentares/?get_all=true"; + List parlamentares = RestApiGet>(address); - var document = await context.OpenAsyncAutoRetry(config.BaseAddress); - if (document.StatusCode != HttpStatusCode.OK) - { - Console.WriteLine($"{config.BaseAddress} {document.StatusCode}"); - }; - - var parlamentares = document.QuerySelectorAll(config.SeletorListaParlamentares); foreach (var parlamentar in parlamentares) { - if (parlamentar.InnerHtml == "
") continue; + var matricula = (uint)parlamentar.Id; + DeputadoEstadual deputado = GetDeputadoByNameOrNew(parlamentar.NomeParlamentar); - var deputado = new DeputadoEstadual() - { - IdEstado = (ushort)config.Estado.GetHashCode() - }; + deputado.UrlPerfil = $"{config.BaseAddress}parlamentar/{parlamentar.Id}"; + deputado.NomeParlamentar = parlamentar.NomeParlamentar.ToTitleCase(); + deputado.IdPartido = BuscarIdPartido(parlamentar.Partido); + deputado.UrlFoto = parlamentar.Fotografia; - deputado.UrlPerfil = (parlamentar.QuerySelector("a") as IHtmlAnchorElement).Href; - deputado.UrlFoto = (parlamentar.QuerySelector("img") as IHtmlImageElement)?.Source; - - ArgumentException.ThrowIfNullOrEmpty(deputado.UrlPerfil, nameof(deputado.UrlPerfil)); - var subDocument = await context.OpenAsyncAutoRetry(deputado.UrlPerfil); - if (document.StatusCode != HttpStatusCode.OK) - { - logger.LogError("Erro ao consultar parlamentar: {NomeDeputado} {StatusCode}", deputado.UrlPerfil, subDocument.StatusCode); - continue; - }; - deputado.NomeParlamentar = subDocument.QuerySelector("h1#parent-fieldname-title").TextContent.Split("-")[0].Trim().ToTitleCase(); - - var deputadoJaExistente = GetDeputadoByNameOrNew(deputado.NomeParlamentar); - if (deputadoJaExistente.Id != 0) - { - deputadoJaExistente.NomeParlamentar = deputado.NomeParlamentar; - deputadoJaExistente.UrlPerfil = deputado.UrlPerfil; - deputadoJaExistente.UrlFoto = deputado.UrlFoto; - deputado = deputadoJaExistente; - } - - if (string.IsNullOrEmpty(deputado.NomeCivil)) - deputado.NomeCivil = deputado.NomeParlamentar; + ObterDetalhesDoPerfil(deputado).GetAwaiter().GetResult(); InsertOrUpdate(deputado); } logger.LogInformation("Parlamentares Inseridos: {Inseridos}; Atualizados {Atualizados};", base.registrosInseridos, base.registrosAtualizados); + return Task.CompletedTask; } - public override DeputadoEstadual ColetarDadosLista(IElement document) + private async Task ObterDetalhesDoPerfil(DeputadoEstadual deputado) { - throw new NotImplementedException(); - } + var angleSharpConfig = Configuration.Default.WithDefaultLoader(); + var context = BrowsingContext.New(angleSharpConfig); - public override void ColetarDadosPerfil(DeputadoEstadual deputado, IDocument subDocument) - { - throw new NotImplementedException(); - } + var document = await context.OpenAsyncAutoRetry(deputado.UrlPerfil); + if (document.StatusCode != HttpStatusCode.OK) + { + Console.WriteLine($"{config.BaseAddress} {document.StatusCode}"); + }; + var perfil = document.QuerySelector("#content"); - //public override DeputadoEstadual ColetarDadosLista(IElement item) - //{ - // if (item.InnerHtml == "
") return null; + deputado.NomeCivil = perfil.QuerySelector("#div_nome").TextContent.Split(":")[1].Trim().ToTitleCase(); - // var deputado = new DeputadoEstadual() - // { - // IdEstado = (ushort)config.Estado.GetHashCode() - // }; + var elementos = perfil.QuerySelectorAll(".form-group>p").Select(x => x.TextContent); + if (elementos.Any()) + { + deputado.Email = elementos.Where(x => x.StartsWith("E-mail")).FirstOrDefault()?.Split(':')[1].Trim().NullIfEmpty(); + deputado.Telefone = elementos.Where(x => x.StartsWith("Telefone")).FirstOrDefault()?.Split(':')[1].Trim().NullIfEmpty(); + } + else + { + logger.LogWarning("Verificar possivel mudança no perfil do parlamentar: {UrlPerfil}", deputado.UrlPerfil); + } + } - // deputado.UrlPerfil = (item.QuerySelector("a") as IHtmlAnchorElement).Href; - // deputado.UrlFoto = (item.QuerySelector("img") as IHtmlImageElement)?.Source; + private class Congressman + { + [JsonPropertyName("id")] + public int Id { get; set; } - // return deputado; - //} + [JsonPropertyName("nome_parlamentar")] + public string NomeParlamentar { get; set; } - //public override void ColetarDadosPerfil(DeputadoEstadual deputado, IDocument subDocument) - //{ - // deputado.NomeParlamentar = subDocument.QuerySelector("h1#parent-fieldname-title").TextContent.Split("-")[0].Trim().ToTitleCase(); + [JsonPropertyName("fotografia_cropped")] + public string FotografiaCropped { get; set; } - // var deputadoJaExistente = GetDeputadoByNameOrNew(deputado.NomeParlamentar); - // if (deputadoJaExistente.Id != 0) - // { - // deputadoJaExistente.NomeParlamentar = deputado.NomeParlamentar; - // deputadoJaExistente.UrlPerfil = deputado.UrlPerfil; - // deputadoJaExistente.UrlFoto = deputado.UrlFoto; - // deputado = deputadoJaExistente; - // } + [JsonPropertyName("fotografia")] + public string Fotografia { get; set; } - // if (string.IsNullOrEmpty(deputado.NomeCivil)) - // deputado.NomeCivil = deputado.NomeParlamentar; - //} + [JsonPropertyName("ativo")] + public bool Ativo { get; set; } + + [JsonPropertyName("partido")] + public string Partido { get; set; } + + [JsonPropertyName("titular")] + public string Titular { get; set; } + } } diff --git a/OPS.Importador/ALE/RioGrandeDoNorte.cs b/OPS.Importador/ALE/RioGrandeDoNorte.cs index 71a2611..f72641e 100644 --- a/OPS.Importador/ALE/RioGrandeDoNorte.cs +++ b/OPS.Importador/ALE/RioGrandeDoNorte.cs @@ -7,6 +7,7 @@ using AngleSharp; using AngleSharp.Dom; using AngleSharp.Html.Dom; +using Dapper; using Microsoft.Extensions.Logging; using OPS.Core; using OPS.Core.Entity; @@ -37,7 +38,7 @@ public ImportadorDespesasRioGrandeDoNorte(IServiceProvider serviceProvider) : ba { BaseAddress = "https://www.al.rn.leg.br/portal/verbas", Estado = Estado.RioGrandeDoNorte, - ChaveImportacao = ChaveDespesaTemp.Gabinete + ChaveImportacao = ChaveDespesaTemp.NomeParlamentar }; // TODO: Filtrar legislatura atual @@ -60,7 +61,10 @@ public override void ImportarDespesas(IBrowsingContext context, int ano, int mes var filename = $"{tempPath}/CLRN-{ano}-{mes}-{gabinete.Value}.pdf"; if (!File.Exists(filename)) { - //var deputado = deputados.Find(x => gabinete.Value.Contains(x.Gabinete.ToString())); + //var deputado = deputados.Find(x => + // gabinete.Text.Equals( x.NomeImportacao, StringComparison.InvariantCultureIgnoreCase) || + // gabinete.Text.Equals(x.NomeParlamentar, StringComparison.InvariantCultureIgnoreCase) + //); //if (deputado == null) //{ // logger.LogError($"Deputado {gabinete.Value}: {gabinete.Text} não existe ou não possui gabinete relacionado!"); diff --git a/OPS.Importador/ALE/RioGrandeDoSul.cs b/OPS.Importador/ALE/RioGrandeDoSul.cs index 6493c89..72d2899 100644 --- a/OPS.Importador/ALE/RioGrandeDoSul.cs +++ b/OPS.Importador/ALE/RioGrandeDoSul.cs @@ -39,7 +39,7 @@ public ImportadorDespesasRioGrandeDoSul(IServiceProvider serviceProvider) : base { BaseAddress = "https://www2.al.rs.gov.br/transparenciaalrs/GabinetesParlamentares/Centrodecustos/tabid/5666/Default.aspx", Estado = Estado.RioGrandeDoSul, - ChaveImportacao = ChaveDespesaTemp.Gabinete + ChaveImportacao = ChaveDespesaTemp.NomeParlamentar // O Gabinete muda a cada legislatura }; } diff --git a/OPS.Importador/ALE/Rondonia.cs b/OPS.Importador/ALE/Rondonia.cs index 002e1be..8b5e64c 100644 --- a/OPS.Importador/ALE/Rondonia.cs +++ b/OPS.Importador/ALE/Rondonia.cs @@ -79,26 +79,29 @@ private void ImportarDiarias(IBrowsingContext context, int ano, int mes) if (nomeDeputado == "Luiz Eduardo Schincaglia") nomeDeputado = "Luis Eduardo Schincaglia"; - var deputado = deputados.Find(x => (x.NomeImportacao ?? Utils.RemoveAccents(x.NomeCivil)).Equals(nomeDeputado, StringComparison.InvariantCultureIgnoreCase)); - if (deputado == null || deputado.Gabinete == null) + using (logger.BeginScope(new Dictionary { ["Tipo"] = "VerbaIndenizatoria", ["Parlamentar"] = nomeDeputado })) { - logger.LogError("Parlamentar {Parlamentar} não existe ou não possui gabinete relacionado!", colunas[idxCredor].TextContent); - } + var deputado = deputados.Find(x => (x.NomeImportacao ?? Utils.RemoveAccents(x.NomeCivil)).Equals(nomeDeputado, StringComparison.InvariantCultureIgnoreCase)); + if (deputado == null || deputado.Gabinete == null) + { + logger.LogError("Parlamentar {Parlamentar} não existe ou não possui gabinete relacionado!", colunas[idxCredor].TextContent); + } - var despesaTemp = new CamaraEstadualDespesaTemp() - { - Nome = colunas[idxCredor].TextContent, - Cpf = deputado?.Gabinete.ToString(), - Ano = (short)ano, - Mes = (short)mes, - TipoDespesa = "Diárias", - DataEmissao = new DateTime(ano, mes, 1), - Valor = Convert.ToDecimal(colunas[idxValor].TextContent, cultureInfo), - Observacao = $"Diárias: {colunas[idxQuantidade].TextContent}; Trecho: {colunas[idxDestino].TextContent}; Transporte: {colunas[idxMeioTransporte].TextContent}; Link: {link}", - }; - - - InserirDespesaTemp(despesaTemp); + var despesaTemp = new CamaraEstadualDespesaTemp() + { + Nome = colunas[idxCredor].TextContent, + Cpf = deputado?.Gabinete.ToString(), + Ano = (short)ano, + Mes = (short)mes, + TipoDespesa = "Diárias", + DataEmissao = new DateTime(ano, mes, 1), + Valor = Convert.ToDecimal(colunas[idxValor].TextContent, cultureInfo), + Observacao = $"Diárias: {colunas[idxQuantidade].TextContent}; Trecho: {colunas[idxDestino].TextContent}; Transporte: {colunas[idxMeioTransporte].TextContent}; Link: {link}", + }; + + + InserirDespesaTemp(despesaTemp); + } } } @@ -113,7 +116,7 @@ private void ImportarCotaParlamentar(IBrowsingContext context, int ano, int mes) var gabinete = item as IHtmlOptionElement; if (string.IsNullOrEmpty(gabinete.Value)) continue; - using (logger.BeginScope(new Dictionary { ["Parlamentar"] = gabinete.Text })) + using (logger.BeginScope(new Dictionary { ["Tipo"] = "VerbaIndenizatoria", ["Parlamentar"] = gabinete.Text })) { var deputado = deputados.Find(x => gabinete.Value.Contains(x.Gabinete.ToString())); if (deputado == null) diff --git a/OPS.Importador/ALE/Roraima.cs b/OPS.Importador/ALE/Roraima.cs index 4a03b3a..bd52f75 100644 --- a/OPS.Importador/ALE/Roraima.cs +++ b/OPS.Importador/ALE/Roraima.cs @@ -62,6 +62,8 @@ public override void ImportarDespesas(IBrowsingContext context, int ano) var titulo = item.QuerySelector(".package-title a").TextContent.Replace("Dep.", "").Trim(); if (titulo == "Renato de Souza Silva Junho - 2024") titulo = "Renato de Souza Silva - Junho 2024"; + else if (titulo == "Renato de Souza Silva Julho - 2024") + titulo = "Renato de Souza Silva - Julho 2024"; var tituloPartes = titulo.Split(new[] { '-', '–' }); var nomeParlamentar = tituloPartes[0].Trim(); @@ -77,8 +79,7 @@ public override void ImportarDespesas(IBrowsingContext context, int ano) private void ImportarDespesasArquivo(int ano, int mes, string nomeParlamentar, string urlPdf) { var filename = $"{tempPath}/CLRR-{ano}-{mes}-{nomeParlamentar}.odt"; - if (!File.Exists(filename)) - httpClient.DownloadFile(urlPdf, filename).GetAwaiter().GetResult(); + BaixarArquivo(urlPdf, filename); // structure will contain all the data returned form the ODT to HTML conversion OdtConvertedData convertedData = null; diff --git a/OPS.Importador/ALE/Sergipe.cs b/OPS.Importador/ALE/Sergipe.cs index 3969b35..8cf2477 100644 --- a/OPS.Importador/ALE/Sergipe.cs +++ b/OPS.Importador/ALE/Sergipe.cs @@ -102,8 +102,7 @@ public override void ImportarDespesas(IBrowsingContext context, int ano) private void ImportarDespesasArquivo(int ano, int mes, string urlPdf) { var filename = $"{tempPath}/CLSE-{ano}-{mes}.pdf"; - if (!File.Exists(filename)) - httpClient.DownloadFile(urlPdf, filename).GetAwaiter().GetResult(); + BaixarArquivo(urlPdf, filename); using (PdfDocument document = PdfDocument.Open(filename, new ParsingOptions() { ClipPaths = true })) { diff --git a/OPS.Importador/ALE/Tocantins.cs b/OPS.Importador/ALE/Tocantins.cs index 664a08c..9562945 100644 --- a/OPS.Importador/ALE/Tocantins.cs +++ b/OPS.Importador/ALE/Tocantins.cs @@ -98,8 +98,7 @@ public override void ImportarDespesas(IBrowsingContext context, int ano, int mes private void ImportarDespesasArquivo(int ano, int mes, IHtmlOptionElement gabinete, string urlPdf) { var filename = $"{tempPath}/CLTO-{ano}-{mes}-{gabinete.Value}.pdf"; - if (!File.Exists(filename)) - httpClient.DownloadFile(urlPdf, filename).GetAwaiter().GetResult(); + BaixarArquivo(urlPdf, filename); using (PdfDocument document = PdfDocument.Open(filename, new ParsingOptions() { ClipPaths = true })) { diff --git a/OPS.Importador/CamaraFederal.cs b/OPS.Importador/CamaraFederal.cs index bcd631b..e71870f 100644 --- a/OPS.Importador/CamaraFederal.cs +++ b/OPS.Importador/CamaraFederal.cs @@ -62,7 +62,7 @@ public ImportadorParlamentarCamaraFederal(IServiceProvider serviceProvider) rootPath = configuration["AppSettings:SiteRootFolder"]; tempPath = configuration["AppSettings:SiteTempFolder"]; - httpClient = serviceProvider.GetService().CreateClient("MyNamedClient"); + httpClient = serviceProvider.GetService().CreateClient("ResilientClient"); } public Task Importar() @@ -444,6 +444,7 @@ public class ImportadorDespesasCamaraFederal : IImportadorDespesas public string rootPath { get; set; } public string tempPath { get; set; } + private bool importacaoIncremental { get; init; } private int itensProcessadosAno { get; set; } private decimal valorTotalProcessadoAno { get; set; } @@ -459,8 +460,9 @@ public ImportadorDespesasCamaraFederal(IServiceProvider serviceProvider) var configuration = serviceProvider.GetService(); rootPath = configuration["AppSettings:SiteRootFolder"]; tempPath = configuration["AppSettings:SiteTempFolder"]; + importacaoIncremental = Convert.ToBoolean(configuration["AppSettings:ImportacaoDespesas:Incremental"] ?? "false"); - httpClient = serviceProvider.GetService().CreateClient("MyNamedClient"); + httpClient = serviceProvider.GetService().CreateClient("ResilientClient"); } #region Importação Deputados @@ -1181,6 +1183,7 @@ GROUP BY id_cf_sessao public void Importar(int ano) { + var anoAtual = DateTime.Today.Year; logger.LogDebug("Despesas do(a) Camara Federal de {Ano}", ano); Dictionary arquivos = DefinirUrlOrigemCaminhoDestino(ano); @@ -1190,30 +1193,34 @@ public void Importar(int ano) var _urlOrigem = arquivo.Key; var caminhoArquivo = arquivo.Value; - if (TentarBaixarArquivo(_urlOrigem, caminhoArquivo)) + bool arquivoJaProcessado = BaixarArquivo(_urlOrigem, caminhoArquivo); + if (anoAtual != ano && importacaoIncremental && arquivoJaProcessado) { - try - { - if (caminhoArquivo.EndsWith(".zip")) - { - DescompactarArquivo(caminhoArquivo); - caminhoArquivo = caminhoArquivo.Replace(".zip", ""); - } + logger.LogInformation("Importação ignorada para arquivo previamente importado!"); + return; + } - ImportarDespesas(caminhoArquivo, ano); - } - catch (Exception ex) + try + { + if (caminhoArquivo.EndsWith(".zip")) { + DescompactarArquivo(caminhoArquivo); + caminhoArquivo = caminhoArquivo.Replace(".zip", ""); + } + + ImportarDespesas(caminhoArquivo, ano); + } + catch (Exception ex) + { - logger.LogError(ex, ex.Message); + logger.LogError(ex, ex.Message); #if !DEBUG - //Excluir o arquivo para tentar importar novamente na proxima execução - if(File.Exists(caminhoArquivo)) - File.Delete(caminhoArquivo); + //Excluir o arquivo para tentar importar novamente na proxima execução + if(File.Exists(caminhoArquivo)) + File.Delete(caminhoArquivo); #endif - } } } } @@ -1243,32 +1250,9 @@ public Dictionary DefinirUrlOrigemCaminhoDestino(int ano) return arquivos; } - protected bool TentarBaixarArquivo(string urlOrigem, string caminhoArquivo) - { - var watch = System.Diagnostics.Stopwatch.StartNew(); - try - { - return BaixarArquivo(urlOrigem, caminhoArquivo); - } - catch (Exception ex) - { - logger.LogWarning(ex, "Erro ao baixar arquivo: {UrlArquivo}", urlOrigem); - - // Algumas vezes ocorre do arquivo não estar disponivel, precisamos aguardar alguns instantes e tentar novamente. - // Isso pode ser causado por um erro de rede ou atualização do arquivo. - Thread.Sleep((int)TimeSpan.FromMinutes(1).TotalMilliseconds); - - return BaixarArquivo(urlOrigem, caminhoArquivo); - } - finally - { - watch.Stop(); - logger.LogTrace($"Arquivo baixado em {watch.Elapsed:c}"); - } - } - private bool BaixarArquivo(string urlOrigem, string caminhoArquivo) { + if (importacaoIncremental && File.Exists(caminhoArquivo)) return false; logger.LogTrace($"Baixando arquivo '{caminhoArquivo}' a partir de '{urlOrigem}'"); string diretorio = new FileInfo(caminhoArquivo).Directory.ToString(); @@ -1388,7 +1372,7 @@ public void ImportarDespesas(string file, int ano) despesaTemp.UrlDocumento = "0"; } - if (!string.IsNullOrEmpty(despesaTemp.Passageiro)) + if (!string.IsNullOrEmpty(despesaTemp.Passageiro)) { despesaTemp.Passageiro = despesaTemp.Passageiro.ToString().Split(";")[0]; string[] partes = despesaTemp.Passageiro.ToString().Split(new[] { '/', ';' }); @@ -1456,7 +1440,7 @@ public void ImportarDespesas(string file, int ano) } } - private void InsereDespesasTemp( List despesasTemp, Dictionary lstHash) + private void InsereDespesasTemp(List despesasTemp, Dictionary lstHash) { JsonSerializerOptions options = new() { @@ -2014,11 +1998,8 @@ public void ConsultaRemuneracao(int ano, int mes) try { - if (!File.Exists(caminhoArquivo)) // TODO: Remover IF - { - var possuiNovosItens = TentarBaixarArquivo(urlOrigem, caminhoArquivo); - if (!possuiNovosItens) return; - } + bool arquivoJaProcessado = BaixarArquivo(urlOrigem, caminhoArquivo); + if (arquivoJaProcessado) return; CarregaRemuneracaoCsv(caminhoArquivo, Convert.ToInt32(ano.ToString() + mes.ToString("00"))); } @@ -2235,8 +2216,7 @@ public void ColetaDadosDeputados() SELECT DISTINCT cd.id as id_cf_deputado, cd.situacao FROM cf_deputado cd JOIN cf_mandato m ON m.id_cf_deputado = cd.id --- WHERE id_legislatura = 57 --- and cd.id >= 141428 +WHERE id_legislatura = 57 -- and cd.processado = 0 order by cd.situacao, cd.id "; @@ -2288,7 +2268,7 @@ private async Task ProcessaFilaColetaDadosDeputados(ConcurrentQueue logger, IDbConnection connection, IConfigu this.connection = connection; this.configuration = configuration; - httpClient = serviceProvider.GetService().CreateClient("MyNamedClient"); + httpClient = serviceProvider.GetService().CreateClient("ResilientClient"); } //public void AtualizaFornecedorDoador() diff --git a/OPS.Importador/OPS.Importador.csproj b/OPS.Importador/OPS.Importador.csproj index 68121c0..754df69 100644 --- a/OPS.Importador/OPS.Importador.csproj +++ b/OPS.Importador/OPS.Importador.csproj @@ -27,6 +27,7 @@ + @@ -50,6 +51,7 @@ + diff --git a/OPS.Importador/Program.cs b/OPS.Importador/Program.cs index 3630f03..8fbe510 100644 --- a/OPS.Importador/Program.cs +++ b/OPS.Importador/Program.cs @@ -5,11 +5,11 @@ using System.IO; using System.Net; using System.Net.Http; -using System.Net.Mail; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; +using AngleSharp.Io; using CsvHelper; using Dapper; using Microsoft.Extensions.Configuration; @@ -102,7 +102,7 @@ public static void Main(string[] args) services.AddScoped(); //services.AddRedaction(); - services.AddHttpClient("MyNamedClient", config => + services.AddHttpClient("ResilientClient", config => { //config.BaseAddress = new Uri("https://localhost:5001/api/"); config.Timeout = TimeSpan.FromSeconds(300); @@ -116,7 +116,9 @@ public static void Main(string[] args) AllowAutoRedirect = false, }; }) - .AddPolicyHandler((services, request) => HttpPolicyExtensions.HandleTransientHttpError().WaitAndRetryAsync(new[] + .AddPolicyHandler((services, request) => HttpPolicyExtensions.HandleTransientHttpError() + //.OrResult(response => response.Content.ReadAsStringAsync().GetAwaiter().GetResult().Contains("Timeout")) + .WaitAndRetryAsync(new[] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5), @@ -124,10 +126,10 @@ public static void Main(string[] args) TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(60), }, - onRetry: (outcome, timespan, retryAttempt, context) => + onRetry: (message, timespan, attempt, context) => { services.GetService>()? - .LogWarning("Delaying for {delay}ms, then making retry {retry}.", timespan.TotalMilliseconds, retryAttempt); + .LogWarning("Delaying for {delay} seconds, then making retry {retry}. Url: {Url}", timespan.TotalSeconds, attempt, message?.Result?.RequestMessage?.RequestUri); } )); //.AddLogger(wrapHandlersPipeline: true) @@ -165,35 +167,35 @@ public static void Main(string[] args) var types = new Type[] { - typeof(Senado), // csv - typeof(CamaraFederal), // csv - //typeof(Acre), // Portal sem dados detalhados! - //typeof(Alagoas), // Dados em PDF scaneado e de baixa qualidade! - typeof(Amapa), // crawler mensal/deputado (Apenas BR) - typeof(Amazonas), // crawler mensal/deputado (Apenas BR) - typeof(Bahia), // crawler anual - typeof(Ceara), // csv mensal - typeof(DistritoFederal), // xlsx (Apenas BR) - typeof(EspiritoSanto), // crawler mensal/deputado (Apenas BR) - typeof(Goias), // crawler mensal/deputado - typeof(Maranhao), // Valores mensais por categoria - //typeof(MatoGrosso), - typeof(MatoGrossoDoSul), // crawler anual - typeof(MinasGerais), // xml api mensal/deputado (Apenas BR) - typeof(Para), // json api anual - typeof(Paraiba), // arquivo ods mensal/deputado - typeof(Parana), // json api mensal/deputado - typeof(Pernambuco), // json api mensal/deputado - typeof(Piaui), // csv por legislatura (download manual) - typeof(RioDeJaneiro), // json api mensal/deputado - typeof(RioGrandeDoNorte), // crawler & pdf mensal/deputado - typeof(RioGrandeDoSul), // crawler mensal/deputado (Apenas BR) - typeof(Rondonia), // crawler mensal/deputado - typeof(Roraima), // crawler & odt mensal/deputado - typeof(SantaCatarina), // csv anual - typeof(SaoPaulo), // xml anual - typeof(Sergipe), // crawler & pdf mensal/deputado - typeof(Tocantins), // crawler & pdf mensal/deputado + typeof(Senado), // csv + typeof(CamaraFederal), // csv + //typeof(Acre), // Portal sem dados detalhados por parlamentar! + typeof(Alagoas), // Dados em PDF scaneado e de baixa qualidade! + typeof(Amapa), // crawler mensal/deputado (Apenas BR) + typeof(Amazonas), // crawler mensal/deputado (Apenas BR) + typeof(Bahia), // crawler anual + typeof(Ceara), // csv mensal + typeof(DistritoFederal), // xlsx (Apenas BR) + typeof(EspiritoSanto), // crawler mensal/deputado (Apenas BR) + typeof(Goias), // crawler mensal/deputado + typeof(Maranhao), // Valores mensais por categoria + //typeof(MatoGrosso), + typeof(MatoGrossoDoSul), // crawler anual + typeof(MinasGerais), // xml api mensal/deputado (Apenas BR) + typeof(Para), // json api anual + typeof(Paraiba), // arquivo ods mensal/deputado + typeof(Parana), // json api mensal/deputado + typeof(Pernambuco), // json api mensal/deputado + typeof(Piaui), // csv por legislatura (download manual) + typeof(RioDeJaneiro), // json api mensal/deputado + typeof(RioGrandeDoNorte), // crawler & pdf mensal/deputado + typeof(RioGrandeDoSul), // crawler mensal/deputado (Apenas BR) + typeof(Rondonia), // crawler mensal/deputado + typeof(Roraima), // crawler & odt mensal/deputado + typeof(SantaCatarina), // csv anual + typeof(SaoPaulo), // xml anual + typeof(Sergipe), // crawler & pdf mensal/deputado + typeof(Tocantins), // crawler & pdf mensal/deputado }; // ImportarCompleto(serviceProvider, types); @@ -217,9 +219,9 @@ public static void Main(string[] args) } Task.WaitAll(tasks.ToArray()); - //var importador = serviceProvider.GetService(); + var importador = serviceProvider.GetService(); - //var mesAtual = DateTime.Today.AddDays(-(DateTime.Today.Day-1)); + //var mesAtual = DateTime.Today.AddDays(-(DateTime.Today.Day - 1)); //var mesConsulta = new DateTime(2023, 02, 01); //do @@ -229,7 +231,6 @@ public static void Main(string[] args) // mesConsulta = mesConsulta.AddMonths(1); //} while (mesConsulta < mesAtual); - //importador.ColetaDadosDeputados(); //importador.ColetaRemuneracaoSecretarios(); @@ -246,48 +247,6 @@ public static void Main(string[] args) //} while (mesConsulta < mesAtual); - - //var importador = serviceProvider.GetService(); - //importador.ImportarArquivoDespesas(0); - - - //for (int ano = 2013; ano <= 2022; ano++) - //{ - // Log.Information("Despesas de {Ano}", ano); - // objCamaraDistritoFederal.ImportarArquivoDespesas(ano); - //} - - //for (int ano = 2011; ano <= 2022; ano++) - //{ - // Log.Information("Despesas de {Ano}", ano); - // objCamaraSantaCatarina.ImportarArquivoDespesas(ano); - //} - - //for (int ano = 2010; ano <= 2022; ano++) - //{ - // Log.Information("Despesas de {Ano}", ano); - // objCamaraSaoPaulo.ImportarArquivoDespesas(ano); - //} - - //for (int ano = 2019; ano <= 2022; ano++) - //{ - // Log.Information("Despesas de {Ano}", ano); - // objCamaraMatoGrossoDoSul.ImportarArquivoDespesas(ano); - //} - - //for (int ano = 2020; ano <= 2022; ano++) - //{ - // Log.Information("Despesas de {Ano}", ano); - // //objCamaraGoias.ImportarArquivoDespesas(ano); - // objCamaraBahia.ImportarArquivoDespesas(ano); - //} - - //for (int ano = 2021; ano <= 2022; ano++) - //{ - // Log.Information("Despesas de {Ano}", ano); - // objCamaraCeara.ImportarArquivoDespesas(ano); - //} - //var cand = new Candidatos(); //cand.ImportarCandidatos(@"C:\\temp\consulta_cand_2018_BRASIL.csv"); //cand.ImportarDespesasPagas(@"C:\\temp\despesas_pagas_candidatos_2018_BRASIL.csv"); @@ -320,17 +279,6 @@ public static void Main(string[] args) Console.ReadKey(); } - private static void ImportarCompleto(ServiceProvider serviceProvider, Type[] types) - { - foreach (var type in types) - { - Log.Warning("Dados do(a) {CasaLegislativa}", type.Name); - - var importador = (ImportadorBase)serviceProvider.GetService(type); - importador.ImportarCompleto(); - } - } - private static void ImportarPartidos() { var cultureInfo = CultureInfo.CreateSpecificCulture("pt-BR"); diff --git a/OPS.Importador/Senado.cs b/OPS.Importador/Senado.cs index 2323e1c..fcb35d5 100644 --- a/OPS.Importador/Senado.cs +++ b/OPS.Importador/Senado.cs @@ -36,8 +36,8 @@ public class ImportadorParlamentarSenado : IImportadorParlamentar { protected readonly ILogger logger; protected readonly IDbConnection connection; - public string rootPath { get; set; } - public string tempPath { get; set; } + public string rootPath { get; init; } + public string tempPath { get; init; } public HttpClient httpClient { get; } @@ -50,7 +50,7 @@ public ImportadorParlamentarSenado(IServiceProvider serviceProvider) rootPath = configuration["AppSettings:SiteRootFolder"]; tempPath = configuration["AppSettings:SiteTempFolder"]; - httpClient = serviceProvider.GetService().CreateClient("MyNamedClient"); + httpClient = serviceProvider.GetService().CreateClient("ResilientClient"); } public Task Importar() @@ -207,87 +207,90 @@ INSERT INTO sf_senador ( identificacaoParlamentar.NomeParlamentar = identificacaoParlamentar.NomeCompletoParlamentar; } - banco.AddParameter("id", identificacaoParlamentar.CodigoParlamentar); - banco.AddParameter("codigo", identificacaoParlamentar.CodigoPublicoNaLegAtual); - banco.AddParameter("nome", identificacaoParlamentar.NomeParlamentar); - banco.AddParameter("nome_completo", identificacaoParlamentar.NomeCompletoParlamentar); - banco.AddParameter("sexo", identificacaoParlamentar.SexoParlamentar[0].ToString()); - banco.AddParameter("sigla_partido", identificacaoParlamentar.SiglaPartidoParlamentar); - banco.AddParameter("sigla_uf", identificacaoParlamentar.UfParlamentar); - banco.AddParameter("email", identificacaoParlamentar.EmailParlamentar); - banco.AddParameter("site", identificacaoParlamentar.UrlPaginaParticular); - - var dadosBasicos = senador.DetalheParlamentar.Parlamentar.DadosBasicosParlamentar; - banco.AddParameter("nascimento", dadosBasicos?.DataNascimento); - banco.AddParameter("naturalidade", dadosBasicos?.Naturalidade); - banco.AddParameter("sigla_uf_naturalidade", dadosBasicos?.UfNaturalidade); - request = new RestRequest("http://legis.senado.gov.br/dadosabertos/senador/" + idSenador.ToString() + "/profissao?v=1"); request.AddHeader("Accept", "application/json"); RestResponse resProfissoes = restClient.GetWithAutoRetry(request); - JsonDocument jSenadorProfissoes = JsonDocument.Parse(resProfissoes.Content); - var jParlamentarProfissoes = jSenadorProfissoes.RootElement.GetProperty("ProfissaoParlamentar").GetProperty("Parlamentar"); - - var lstSenadorProfissao = new List(); - JsonElement jProfissoes; - if (jParlamentarProfissoes.TryGetProperty("Profissoes", out jProfissoes)) + if (!resProfissoes.Content.Contains("HistoricoAcademicoParlamentar")) { - if (jProfissoes.GetProperty("Profissao").ValueKind == JsonValueKind.Array) + banco.AddParameter("id", identificacaoParlamentar.CodigoParlamentar); + banco.AddParameter("codigo", identificacaoParlamentar.CodigoPublicoNaLegAtual); + banco.AddParameter("nome", identificacaoParlamentar.NomeParlamentar); + banco.AddParameter("nome_completo", identificacaoParlamentar.NomeCompletoParlamentar); + banco.AddParameter("sexo", identificacaoParlamentar.SexoParlamentar[0].ToString()); + banco.AddParameter("sigla_partido", identificacaoParlamentar.SiglaPartidoParlamentar); + banco.AddParameter("sigla_uf", identificacaoParlamentar.UfParlamentar); + banco.AddParameter("email", identificacaoParlamentar.EmailParlamentar); + banco.AddParameter("site", identificacaoParlamentar.UrlPaginaParticular); + + var dadosBasicos = senador.DetalheParlamentar.Parlamentar.DadosBasicosParlamentar; + banco.AddParameter("nascimento", dadosBasicos?.DataNascimento); + banco.AddParameter("naturalidade", dadosBasicos?.Naturalidade); + banco.AddParameter("sigla_uf_naturalidade", dadosBasicos?.UfNaturalidade); + + JsonDocument jSenadorProfissoes = JsonDocument.Parse(resProfissoes.Content); + var jParlamentarProfissoes = jSenadorProfissoes.RootElement.GetProperty("ProfissaoParlamentar").GetProperty("Parlamentar"); + + var lstSenadorProfissao = new List(); + JsonElement jProfissoes; + if (jParlamentarProfissoes.TryGetProperty("Profissoes", out jProfissoes)) { - lstSenadorProfissao = JsonSerializer.Deserialize>(jProfissoes.GetProperty("Profissao")); + if (jProfissoes.GetProperty("Profissao").ValueKind == JsonValueKind.Array) + { + lstSenadorProfissao = JsonSerializer.Deserialize>(jProfissoes.GetProperty("Profissao")); + } + else + { + lstSenadorProfissao.Add(JsonSerializer.Deserialize(jProfissoes.GetProperty("Profissao"))); + } + } + + if (lstSenadorProfissao.Any()) + { + banco.AddParameter("profissao", string.Join(", ", lstSenadorProfissao.Select(obj => obj.NomeProfissao))); } else { - lstSenadorProfissao.Add(JsonSerializer.Deserialize(jProfissoes.GetProperty("Profissao"))); + banco.AddParameter("profissao", DBNull.Value); } - } - if (lstSenadorProfissao.Any()) - { - banco.AddParameter("profissao", string.Join(", ", lstSenadorProfissao.Select(obj => obj.NomeProfissao))); - } - else - { - banco.AddParameter("profissao", DBNull.Value); - } - - banco.AddParameter("ativo", lstSenadorAtivo.Contains(identificacaoParlamentar.CodigoParlamentar) ? "S" : "N"); - - banco.ExecuteNonQuery(@" - UPDATE sf_senador SET - codigo = @codigo - , nome = @nome - , nome_completo = @nome_completo - , sexo = @sexo - , nascimento = @nascimento - , naturalidade = @naturalidade - , id_estado_naturalidade = (SELECT id FROM estado where sigla like @sigla_uf_naturalidade) - , profissao = @profissao - , id_partido = (SELECT id FROM partido where sigla like @sigla_partido OR nome like @sigla_partido) - , id_estado = (SELECT id FROM estado where sigla like @sigla_uf) - , email = @email - , site = @site - , ativo = @ativo - WHERE id = @id - "); - - if (lstSenadorProfissao.Any()) - { - foreach (var profissao in lstSenadorProfissao) + banco.AddParameter("ativo", lstSenadorAtivo.Contains(identificacaoParlamentar.CodigoParlamentar) ? "S" : "N"); + + banco.ExecuteNonQuery(@" + UPDATE sf_senador SET + codigo = @codigo + , nome = @nome + , nome_completo = @nome_completo + , sexo = @sexo + , nascimento = @nascimento + , naturalidade = @naturalidade + , id_estado_naturalidade = (SELECT id FROM estado where sigla like @sigla_uf_naturalidade) + , profissao = @profissao + , id_partido = (SELECT id FROM partido where sigla like @sigla_partido OR nome like @sigla_partido) + , id_estado = (SELECT id FROM estado where sigla like @sigla_uf) + , email = @email + , site = @site + , ativo = @ativo + WHERE id = @id + "); + + if (lstSenadorProfissao.Any()) { - if (!lstProfissao.ContainsKey(profissao.NomeProfissao)) + foreach (var profissao in lstSenadorProfissao) { - banco.AddParameter("descricao", profissao.NomeProfissao); - var idProfissao = banco.ExecuteScalar(@"INSERT INTO profissao (descricao) values (@descricao); SELECT LAST_INSERT_ID();"); + if (!lstProfissao.ContainsKey(profissao.NomeProfissao)) + { + banco.AddParameter("descricao", profissao.NomeProfissao); + var idProfissao = banco.ExecuteScalar(@"INSERT INTO profissao (descricao) values (@descricao); SELECT LAST_INSERT_ID();"); - lstProfissao.Add(profissao.NomeProfissao, Convert.ToInt32(idProfissao)); - } + lstProfissao.Add(profissao.NomeProfissao, Convert.ToInt32(idProfissao)); + } - banco.AddParameter("id_sf_senador", idSenador); - banco.AddParameter("id_profissao", lstProfissao[profissao.NomeProfissao]); + banco.AddParameter("id_sf_senador", idSenador); + banco.AddParameter("id_profissao", lstProfissao[profissao.NomeProfissao]); - banco.ExecuteNonQuery(@"INSERT IGNORE INTO sf_senador_profissao (id_sf_senador, id_profissao) values (@id_sf_senador, @id_profissao)"); + banco.ExecuteNonQuery(@"INSERT IGNORE INTO sf_senador_profissao (id_sf_senador, id_profissao) values (@id_sf_senador, @id_profissao)"); + } } } @@ -544,11 +547,13 @@ public class ImportadorDespesasSenado : IImportadorDespesas protected readonly ILogger logger; protected readonly IDbConnection connection; - public string rootPath { get; set; } - public string tempPath { get; set; } + public string rootPath { get; init; } + public string tempPath { get; init; } private int linhasProcessadasAno { get; set; } + public bool importacaoIncremental { get; init; } + public HttpClient httpClient { get; } public ImportadorDespesasSenado(IServiceProvider serviceProvider) @@ -559,8 +564,9 @@ public ImportadorDespesasSenado(IServiceProvider serviceProvider) var configuration = serviceProvider.GetService(); rootPath = configuration["AppSettings:SiteRootFolder"]; tempPath = configuration["AppSettings:SiteTempFolder"]; + importacaoIncremental = Convert.ToBoolean(configuration["AppSettings:ImportacaoDespesas:Incremental"] ?? "false"); - httpClient = serviceProvider.GetService().CreateClient("MyNamedClient"); + httpClient = serviceProvider.GetService().CreateClient("ResilientClient"); } //public string AtualizaCadastroParlamentarCompleto() @@ -899,24 +905,23 @@ public void Importar(int ano) var _urlOrigem = arquivo.Key; var caminhoArquivo = arquivo.Value; - if (TentarBaixarArquivo(_urlOrigem, caminhoArquivo)) + BaixarArquivo(_urlOrigem, caminhoArquivo); + + try + { + ImportarDespesas(caminhoArquivo, ano); + } + catch (Exception ex) { - try - { - ImportarDespesas(caminhoArquivo, ano); - } - catch (Exception ex) - { - logger.LogError(ex, ex.Message); + logger.LogError(ex, ex.Message); #if !DEBUG - //Excluir o arquivo para tentar importar novamente na proxima execução - if(File.Exists(caminhoArquivo)) - File.Delete(caminhoArquivo); + //Excluir o arquivo para tentar importar novamente na proxima execução + if(File.Exists(caminhoArquivo)) + File.Delete(caminhoArquivo); #endif - } } } } @@ -934,33 +939,10 @@ public Dictionary DefinirUrlOrigemCaminhoDestino(int ano) return arquivos; } - protected bool TentarBaixarArquivo(string urlOrigem, string caminhoArquivo) - { - var watch = System.Diagnostics.Stopwatch.StartNew(); - try - { - return BaixarArquivo(urlOrigem, caminhoArquivo); - } - catch (Exception ex) - { - logger.LogWarning("Erro ao baixar arquivo: {Message}", ex.Message); - - // Algumas vezes ocorre do arquivo não estar disponivel, precisamos aguardar alguns instantes e tentar novamente. - // Isso pode ser causado por um erro de rede ou atualização do arquivo. - Thread.Sleep((int)TimeSpan.FromMinutes(1).TotalMilliseconds); - - return BaixarArquivo(urlOrigem, caminhoArquivo); - } - finally - { - watch.Stop(); - //logger.LogTrace("Arquivo baixado em {TimeElapsed:c}", watch.Elapsed); - } - } - - private bool BaixarArquivo(string urlOrigem, string caminhoArquivo) + protected bool BaixarArquivo(string urlOrigem, string caminhoArquivo) { - logger.LogTrace("Baixando arquivo '{CaminhoArquivo}' a partir de '{UrlOrigem}'", caminhoArquivo, urlOrigem); + if (importacaoIncremental && File.Exists(caminhoArquivo)) return false; + logger.LogTrace($"Baixando arquivo '{caminhoArquivo}' a partir de '{urlOrigem}'"); string diretorio = new FileInfo(caminhoArquivo).Directory.ToString(); if (!Directory.Exists(diretorio)) @@ -970,7 +952,6 @@ private bool BaixarArquivo(string urlOrigem, string caminhoArquivo) File.Delete(caminhoArquivo); httpClient.DownloadFile(urlOrigem, caminhoArquivo).Wait(); - return true; } @@ -1449,10 +1430,14 @@ public void ImportarRemuneracao(int ano, int mes) try { - var possuiNovosItens = TentarBaixarArquivo(urlOrigem, caminhoArquivo); + var arquivoJaProcessado = BaixarArquivo(urlOrigem, caminhoArquivo); + if (importacaoIncremental && arquivoJaProcessado) + { + logger.LogInformation("Importação ignorada para arquivo previamente importado!"); + return; + } - if (possuiNovosItens) - CarregaRemuneracaoCsv(caminhoArquivo, anomes); + CarregaRemuneracaoCsv(caminhoArquivo, anomes); } catch (Exception ex) { diff --git a/OPS.Importador/Utilities/AngleSharpExtensions.cs b/OPS.Importador/Utilities/AngleSharpExtensions.cs index 23fdb08..e479327 100644 --- a/OPS.Importador/Utilities/AngleSharpExtensions.cs +++ b/OPS.Importador/Utilities/AngleSharpExtensions.cs @@ -6,13 +6,14 @@ using AngleSharp; using AngleSharp.Dom; using AngleSharp.Html.Dom; +using Org.BouncyCastle.Utilities.Net; using Serilog; namespace OPS.Importador.Utilities; public static class AngleSharpExtensions { - public static async Task OpenAsyncAutoRetry(this IBrowsingContext context, String address, int totalRetries = 3) + public static async Task OpenAsyncAutoRetry(this IBrowsingContext context, String address, int totalRetries = 5) { int retries = 0; do @@ -23,16 +24,17 @@ public static async Task OpenAsyncAutoRetry(this IBrowsingContext con if (doc.StatusCode == HttpStatusCode.OK) { var html = doc.ToHtml(); - if (!string.IsNullOrEmpty(html) && html != "") // Validate empty response + if (!doc.Url.Contains("error") && !string.IsNullOrEmpty(html) && html != "") // Validate empty response and page error redirect return doc; } - Log.Warning("Try {Retries} on {Address} - Status Code {StatusCode}", retries, address, doc.StatusCode); // - {doc.ToHtml()} - Thread.Sleep(TimeSpan.FromSeconds(1)); + var waitSeconds = Math.Pow(2, retries); + Log.Information("Try {Retries} of {MaxRetries} on {Address}. Wait for {WaitSeconds} seconds.", retries, totalRetries, address, waitSeconds); + Thread.Sleep(TimeSpan.FromSeconds(waitSeconds)); } while (retries < totalRetries); - throw new Exception($"Error: {address}"); + throw new Exception($"Error Get Request: {address}"); } public static async Task SubmitAsyncAutoRetry(this IHtmlFormElement form, IDictionary fields, bool createMissing = false, int totalRetries = 3) @@ -46,12 +48,13 @@ public static async Task SubmitAsyncAutoRetry(this IHtmlFormElement f if (doc.StatusCode == HttpStatusCode.OK) { var html = doc.ToHtml(); - if (!string.IsNullOrEmpty(html) && html != "") // Validate empty response + if (!string.IsNullOrEmpty(html) && html != "") // Validate empty response and page error redirect return doc; } - Log.Warning("Try {Retries} on {Address} - Status Code {StatusCode}", retries, form.BaseUri.ToString(), doc.StatusCode); // - {doc.ToHtml()} - Thread.Sleep(TimeSpan.FromSeconds(1)); + var waitSeconds = Math.Pow(2, retries); + Log.Information("Try {Retries} of {MaxRetries} on {Address}. Wait for {WaitSeconds} seconds.", retries, totalRetries, form.BaseUri.ToString(), waitSeconds); + Thread.Sleep(TimeSpan.FromSeconds(waitSeconds)); } while (retries < totalRetries); diff --git a/OPS.Importador/Utilities/ComputerVisionOcr.cs b/OPS.Importador/Utilities/ComputerVisionOcr.cs new file mode 100644 index 0000000..fbaad4c --- /dev/null +++ b/OPS.Importador/Utilities/ComputerVisionOcr.cs @@ -0,0 +1,91 @@ +/* + * Computer Vision SDK QuickStart + * + * Examples included: + * - Authenticate + * - OCR (Read API): Read file from URL + # - OCR (Read API): Read file from local + * + * Prerequisites: + * - Visual Studio 2019 (or 2017, but note this is a .Net Core console app, not .Net Framework) + * - NuGet library: Microsoft.Azure.CognitiveServices.Vision.ComputerVision + * - Azure Computer Vision resource from https://ms.portal.azure.com + * - Create a .Net Core console app, then copy/paste this Program.cs file into it. Be sure to update the namespace if it's different. + * - Download local images (celebrities.jpg, objects.jpg, handwritten_text.jpg, and printed_text.jpg) + * from the link below then add to your bin/Debug/netcoreapp2.2 folder. + * https://github.com/Azure-Samples/cognitive-services-sample-data-files/tree/master/ComputerVision/Images + * + * How to run: + * - Once your prerequisites are complete, press the Start button in Visual Studio. + * - Each example displays a printout of its results. + * + * References: + * - .NET SDK: https://docs.microsoft.com/en-us/dotnet/api/overview/azure/cognitiveservices/client/computervision?view=azure-dotnet + * - API (testing console): https://westus.dev.cognitive.microsoft.com/docs/services/computer-vision-v3-2/operations/5d986960601faab4bf452005 + * - Computer Vision documentation: https://docs.microsoft.com/en-us/azure/cognitive-services/computer-vision/ + */ + +using System; +using System.Collections.Generic; +using Microsoft.Azure.CognitiveServices.Vision.ComputerVision; +using Microsoft.Azure.CognitiveServices.Vision.ComputerVision.Models; +using System.Threading.Tasks; +using System.IO; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System.Threading; +using System.Linq; +using System.Text; + +namespace OPS.Importador.Utilities +{ + public class ComputerVisionOcr + { + // Add your Computer Vision key and endpoint + static string key = "52a29fecf4e44d428fd0f9cbb3d2f370"; + static string endpoint = "https://ops-ocr.cognitiveservices.azure.com/"; + + private readonly ComputerVisionClient client; + + public ComputerVisionOcr() + { + client = new ComputerVisionClient(new ApiKeyServiceClientCredentials(key)) { Endpoint = endpoint }; + } + + public async Task ReadFileLocal(string localFile) + { + // Read text from URL + var textHeaders = await client.ReadInStreamAsync(File.OpenRead(localFile)); + // After the request, get the operation location (operation ID) + string operationLocation = textHeaders.OperationLocation; + Thread.Sleep(2000); + + // Retrieve the URI where the recognized text will be stored from the Operation-Location header. + // We only need the ID and not the full URL + const int numberOfCharsInOperationId = 36; + string operationId = operationLocation.Substring(operationLocation.Length - numberOfCharsInOperationId); + + // Extract the text + ReadOperationResult results; + //Console.WriteLine($"Reading text from local file {Path.GetFileName(localFile)}..."); + //Console.WriteLine(); + do + { + results = await client.GetReadResultAsync(Guid.Parse(operationId)); + } + while ((results.Status == OperationStatusCodes.Running || results.Status == OperationStatusCodes.NotStarted)); + + // Display the found text. + var textUrlFileResulsts = results.AnalyzeResult.ReadResults; + var sb = new StringBuilder(); + foreach (ReadResult page in textUrlFileResulsts) + { + foreach (Line line in page.Lines) + { + sb.AppendLine(line.Text); + } + } + return sb.ToString(); + } + } +} \ No newline at end of file diff --git a/OPS.Importador/Utilities/ImportacaoUtils.cs b/OPS.Importador/Utilities/ImportacaoUtils.cs index 642018f..797dddf 100644 --- a/OPS.Importador/Utilities/ImportacaoUtils.cs +++ b/OPS.Importador/Utilities/ImportacaoUtils.cs @@ -65,7 +65,7 @@ public static IEnumerable ReadPdfFile(string fileName) { if (File.Exists(fileName)) { - PdfReader pdfReader = new PdfReader(fileName); + using PdfReader pdfReader = new PdfReader(fileName); ITextExtractionStrategy strategy = new SimpleTextExtractionStrategy(); for (int page = 1; page <= pdfReader.NumberOfPages; page++) diff --git a/OPS.Importador/Utilities/TraceContentLoggingExtension.cs b/OPS.Importador/Utilities/TraceContentLoggingExtension.cs index b83d3c0..2174375 100644 --- a/OPS.Importador/Utilities/TraceContentLoggingExtension.cs +++ b/OPS.Importador/Utilities/TraceContentLoggingExtension.cs @@ -1,76 +1,76 @@ -using System.Linq; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Http; -using Microsoft.Extensions.Logging; +//using System.Linq; +//using System.Net.Http; +//using System.Threading; +//using System.Threading.Tasks; +//using Microsoft.Extensions.DependencyInjection; +//using Microsoft.Extensions.Http; +//using Microsoft.Extensions.Logging; -namespace OPS.Importador.Utilities -{ - /// HTTP request wrapper that logs request/response body. - /// Use this for debugging issues with third party services. - class TraceContentLoggingHandler(HttpMessageHandler innerHandler, ILogger logger) : DelegatingHandler(innerHandler) - { - protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - if (logger is null || !logger.IsEnabled(LogLevel.Trace)) - return await base.SendAsync(request, cancellationToken); // If not tracing, skip logging +//namespace OPS.Importador.Utilities +//{ +// /// HTTP request wrapper that logs request/response body. +// /// Use this for debugging issues with third party services. +// class TraceContentLoggingHandler(HttpMessageHandler innerHandler, ILogger logger) : DelegatingHandler(innerHandler) +// { +// protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) +// { +// if (logger is null || !logger.IsEnabled(LogLevel.Trace)) +// return await base.SendAsync(request, cancellationToken); // If not tracing, skip logging - if (request.Content is not null) - logger.LogTrace(""" - Request body {URI} - {Content} - """, - request.RequestUri, - await request.Content.ReadAsStringAsync(cancellationToken)); +// if (request.Content is not null) +// logger.LogTrace(""" +// Request body {URI} +// {Content} +// """, +// request.RequestUri, +// await request.Content.ReadAsStringAsync(cancellationToken)); - var response = await base.SendAsync(request, cancellationToken); // This is disposable, but if we dispose it the ultimate caller can't read the content +// var response = await base.SendAsync(request, cancellationToken); // This is disposable, but if we dispose it the ultimate caller can't read the content - if (response.Content is not null) - logger.LogTrace(""" - Response {Code} {Status} - {Content} - """, - (int)response.StatusCode, - response.ReasonPhrase, - await ReadableResponse(response, cancellationToken)); +// if (response.Content is not null) +// logger.LogTrace(""" +// Response {Code} {Status} +// {Content} +// """, +// (int)response.StatusCode, +// response.ReasonPhrase, +// await ReadableResponse(response, cancellationToken)); - return response; - } +// return response; +// } - static async Task ReadableResponse(HttpResponseMessage response, CancellationToken cancellationToken) - { - string? contentType = response.Content.Headers.GetValues("Content-Type")?.FirstOrDefault(); - if (contentType == "application/zip") - return $"ZIP file {response.Content.Headers.GetValues("Content-Length").FirstOrDefault()} bytes"; +// static async Task ReadableResponse(HttpResponseMessage response, CancellationToken cancellationToken) +// { +// string? contentType = response.Content.Headers.GetValues("Content-Type")?.FirstOrDefault(); +// if (contentType == "application/zip") +// return $"ZIP file {response.Content.Headers.GetValues("Content-Length").FirstOrDefault()} bytes"; - return await response.Content.ReadAsStringAsync(cancellationToken); - } +// return await response.Content.ReadAsStringAsync(cancellationToken); +// } - } +// } - public static class TraceContentLoggingExtension - { +// public static class TraceContentLoggingExtension +// { - /// When trace logging is enabled, log request/response bodies. - /// However, this means that trace logging will be slower. - public static IHttpClientBuilder AddTraceContentLogging(this IHttpClientBuilder httpClientBuilder) - { - // Get the logger for the named HttpClient - var sp = httpClientBuilder.Services.BuildServiceProvider(); - var logger = sp.GetService()?.CreateLogger($"System.Net.Http.HttpClient.{httpClientBuilder.Name}.Content"); +// /// When trace logging is enabled, log request/response bodies. +// /// However, this means that trace logging will be slower. +// public static IHttpClientBuilder AddTraceContentLogging(this IHttpClientBuilder httpClientBuilder) +// { +// // Get the logger for the named HttpClient +// var sp = httpClientBuilder.Services.BuildServiceProvider(); +// var logger = sp.GetService()?.CreateLogger($"System.Net.Http.HttpClient.{httpClientBuilder.Name}.Content"); - // If trace logging is enabled, add the logging handler - if (logger?.IsEnabled(LogLevel.Trace) ?? false) - httpClientBuilder.Services.Configure( - httpClientBuilder.Name, - (HttpClientFactoryOptions options) => - options.HttpMessageHandlerBuilderActions.Add(b => - b.PrimaryHandler = new TraceContentLoggingHandler(b.PrimaryHandler, logger) - )); +// // If trace logging is enabled, add the logging handler +// if (logger?.IsEnabled(LogLevel.Trace) ?? false) +// httpClientBuilder.Services.Configure( +// httpClientBuilder.Name, +// (HttpClientFactoryOptions options) => +// options.HttpMessageHandlerBuilderActions.Add(b => +// b.PrimaryHandler = new TraceContentLoggingHandler(b.PrimaryHandler, logger) +// )); - return httpClientBuilder; - } - } -} +// return httpClientBuilder; +// } +// } +//} diff --git a/OPS.Importador/appsettings.json b/OPS.Importador/appsettings.json index dbff58f..9861b76 100644 --- a/OPS.Importador/appsettings.json +++ b/OPS.Importador/appsettings.json @@ -6,7 +6,8 @@ "SiteRootFolder": "/var/www/ops.net.br", "SendGridAPIKey": "", "TelegramApiToken": "", - "ReceitaWsApiToken": "" + "ReceitaWsApiToken": "", + "ImportacaoDespesas:Incremental": "true" }, "EPPlus": { "ExcelPackage": {