adding despesas e receitas

This commit is contained in:
Jose Henrique 2025-06-07 11:56:03 -03:00
parent b9908b36b7
commit 9a107ce9e8
29 changed files with 922 additions and 58 deletions

View File

@ -22,6 +22,11 @@ namespace OpenCand.API.Controllers
[EnableRateLimiting(RateLimitingConfig.CandidatoSearchPolicy)]
public async Task<CandidatoSearchResult> CandidatoSearch([FromQuery] string q)
{
if (string.IsNullOrEmpty(q) || q.Length == 1)
{
throw new ArgumentException("Query parameter 'q' cannot be null/empty.", nameof(q));
}
return await openCandService.SearchCandidatosAsync(q);
}
@ -52,5 +57,17 @@ namespace OpenCand.API.Controllers
await Task.Delay(randomWait);
return await openCandService.GetCandidatoCpfById(id);
}
[HttpGet("{id}/despesas")]
public async Task<DespesasResult> GetCandidatoDespesas([FromRoute] Guid id)
{
return await openCandService.GetDespesasByIdAndYear(id);
}
[HttpGet("{id}/receitas")]
public async Task<ReceitaResult> GetCandidatoReceitas([FromRoute] Guid id)
{
return await openCandService.GetReceitasByIdAndYear(id);
}
}
}

View File

@ -21,4 +21,14 @@ namespace OpenCand.API.Model
{
public string Cpf { get; set; }
}
public class DespesasResult
{
public List<Despesa> Despesas { get; set; }
}
public class ReceitaResult
{
public List<Receita> Receitas { get; set; }
}
}

View File

@ -59,6 +59,7 @@ namespace OpenCand.API
builder.Services.AddScoped<OpenCandRepository>();
builder.Services.AddScoped<CandidatoRepository>();
builder.Services.AddScoped<BemCandidatoRepository>();
builder.Services.AddScoped<DespesaReceitaRepository>();
builder.Services.AddScoped<OpenCandService>();
}
}

View File

@ -0,0 +1,42 @@
using Dapper;
using Microsoft.Extensions.Configuration;
using Npgsql;
using OpenCand.Core.Models;
namespace OpenCand.Repository
{
public class DespesaReceitaRepository : BaseRepository
{
public DespesaReceitaRepository(IConfiguration configuration) : base(configuration)
{
}
public async Task<List<Despesa>> GetDespesasByCandidatoIdYearAsync(Guid idcandidato)
{
using (var connection = new NpgsqlConnection(ConnectionString))
{
return (await connection.QueryAsync<Despesa>(@"
SELECT * FROM despesas_candidato
WHERE idcandidato = @idcandidato
ORDER BY valor DESC;", new
{
idcandidato
})).AsList();
}
}
public async Task<List<Receita>> GetReceitasByCandidatoIdYearAsync(Guid idcandidato)
{
using (var connection = new NpgsqlConnection(ConnectionString))
{
return (await connection.QueryAsync<Receita>(@"
SELECT * FROM receitas_candidato
WHERE idcandidato = @idcandidato
ORDER BY valor DESC;", new
{
idcandidato
})).AsList();
}
}
}
}

View File

@ -12,6 +12,7 @@ namespace OpenCand.API.Services
private readonly OpenCandRepository openCandRepository;
private readonly CandidatoRepository candidatoRepository;
private readonly BemCandidatoRepository bemCandidatoRepository;
private readonly DespesaReceitaRepository despesaReceitaRepository;
private readonly IConfiguration configuration;
private readonly FotosSettings fotoSettings;
private readonly ILogger<OpenCandService> logger;
@ -20,6 +21,7 @@ namespace OpenCand.API.Services
OpenCandRepository openCandRepository,
CandidatoRepository candidatoRepository,
BemCandidatoRepository bemCandidatoRepository,
DespesaReceitaRepository despesaReceitaRepository,
IOptions<FotosSettings> fotoSettings,
IConfiguration configuration,
ILogger<OpenCandService> logger)
@ -27,6 +29,7 @@ namespace OpenCand.API.Services
this.openCandRepository = openCandRepository;
this.candidatoRepository = candidatoRepository;
this.bemCandidatoRepository = bemCandidatoRepository;
this.despesaReceitaRepository = despesaReceitaRepository;
this.fotoSettings = fotoSettings.Value;
this.configuration = configuration;
this.logger = logger;
@ -113,5 +116,33 @@ namespace OpenCand.API.Services
Cpf = result
};
}
public async Task<DespesasResult> GetDespesasByIdAndYear(Guid idcandidato)
{
var result = await despesaReceitaRepository.GetDespesasByCandidatoIdYearAsync(idcandidato);
if (result == null)
{
return new DespesasResult();
}
return new DespesasResult()
{
Despesas = result.OrderByDescending(d => d.Ano).ThenByDescending(d => d.Valor).ToList()
};
}
public async Task<ReceitaResult> GetReceitasByIdAndYear(Guid idcandidato)
{
var result = await despesaReceitaRepository.GetReceitasByCandidatoIdYearAsync(idcandidato);
if (result == null)
{
return new ReceitaResult();
}
return new ReceitaResult()
{
Receitas = result.OrderByDescending(d => d.Ano).ThenByDescending(d => d.Valor).ToList()
};
}
}
}

View File

@ -40,6 +40,7 @@ namespace OpenCand.Core.Models
public string Apelido { get; set; }
public string SqCandidato { get; set; }
public int Ano { get; set; }
public string Turno { get; set; }
public string TipoEleicao { get; set; }
public string SiglaUF { get; set; }
public string NomeUE { get; set; }

View File

@ -0,0 +1,25 @@
namespace OpenCand.Core.Models
{
public class Despesa
{
public Guid IdDespesa { get; set; }
public Guid IdCandidato { get; set; }
public int Ano { get; set; }
public int Turno { get; set; }
public string SqCandidato { get; set; }
public string SgPartido { get; set; }
public string TipoFornecedor { get; set; }
public string CpfFornecedor { get; set; }
public string CnpjFornecedor { get; set; }
public string NomeFornecedor { get; set; }
public string NomeFornecedorRFB { get; set; }
public string MunicipioFornecedor { get; set; }
public string TipoDocumento { get; set; }
public DateTime? DataDespesa { get; set; }
public string OrigemDespesa { get; set; }
public string Descricao { get; set; }
public float Valor { get; set; }
}
}

View File

@ -0,0 +1,26 @@
namespace OpenCand.Core.Models
{
public class Receita
{
public Guid IdReceita { get; set; }
public Guid IdCandidato { get; set; }
public int Ano { get; set; }
public int Turno { get; set; }
public string SqCandidato { get; set; }
public string SgPartido { get; set; }
public string FonteReceita { get; set; }
public string OrigemReceita { get; set; }
public string NaturezaReceita { get; set; }
public string EspecieReceita { get; set; }
public string CpfDoador { get; set; }
public string CnpjDoador { get; set; }
public string NomeDoador { get; set; }
public string NomeDoadorRFB { get; set; }
public string MunicipioDoador { get; set; }
public DateTime? DataReceita { get; set; }
public string Descricao { get; set; }
public float Valor { get; set; }
}
}

View File

@ -5,5 +5,7 @@ namespace OpenCand.Config
public string CandidatosFolder { get; set; } = string.Empty;
public string BensCandidatosFolder { get; set; } = string.Empty;
public string RedesSociaisFolder { get; set; } = string.Empty;
public string ReceitaCandidatoFolder { get; set; } = string.Empty;
public string DespesaCandidatoFolder { get; set; } = string.Empty;
}
}

View File

@ -0,0 +1,12 @@
namespace OpenCand.ETL.Extensions
{
public static class StringExtensions
{
public static bool IsNullOrEmpty(this string value)
{
return string.IsNullOrEmpty(value) ||
value == "#NE" ||
value == "#NULO";
}
}
}

View File

@ -1,15 +0,0 @@
using CsvHelper.Configuration;
using OpenCand.Parser.Models;
using System.Globalization;
namespace OpenCand.Parser.CsvMappers
{
public class BemCandidatoMap : ClassMap<BemCandidatoCSV>
{
public BemCandidatoMap()
{
AutoMap(CultureInfo.InvariantCulture);
// Explicitly handle any special mappings if needed
}
}
}

View File

@ -1,15 +0,0 @@
using CsvHelper.Configuration;
using OpenCand.Parser.Models;
using System.Globalization;
namespace OpenCand.Parser.CsvMappers
{
public class CandidatoMap : ClassMap<CandidatoCSV>
{
public CandidatoMap()
{
AutoMap(CultureInfo.InvariantCulture);
// Explicitly handle any special mappings if needed
}
}
}

View File

@ -1,14 +0,0 @@
using System.Globalization;
using CsvHelper.Configuration;
using OpenCand.Parser.Models;
namespace OpenCand.ETL.Parser.CsvMappers
{
public class RedeSocialMap : ClassMap<BemCandidatoCSV>
{
public RedeSocialMap()
{
AutoMap(CultureInfo.InvariantCulture);
}
}
}

View File

@ -73,8 +73,10 @@ namespace OpenCand.Parser.Services
{
if (columns.Length > headerCount)
{
logger.LogCritical($"FixCsvFile - Line {i + 1} has {columns.Length} columns, expected {headerCount}. Halting process.");
return string.Empty; // Critical error, cannot fix this line => needs manual intervention
// logger.LogCritical($"FixCsvFile - Line {i + 1} has {columns.Length} columns, expected {headerCount}. Halting process.");
// return string.Empty; // Critical error, cannot fix this line => needs manual intervention
// consider as normal line
break;
}
logger.LogWarning($"FixCsvFile - Line {i + 1} has {columns.Length} columns, expected {headerCount}. Attempting to fix [i = {lineJump}]...");

View File

@ -11,6 +11,9 @@ namespace OpenCand.Parser.Models
[Name("TP_ABRANGENCIA")]
public string TipoAbrangencia { get; set; }
[Name("NR_TURNO")]
public string Turno { get; set; }
[Name("SG_UF")]
public string SiglaUF { get; set; }

View File

@ -0,0 +1,97 @@
using CsvHelper.Configuration.Attributes;
namespace OpenCand.Parser.Models
{
public class DespesasCSV
{
[Name("AA_ELEICAO")]
public int AnoEleicao { get; set; }
[Name("ST_TURNO")]
public string Turno { get; set; }
[Name("DT_PRESTACAO_CONTAS")]
public string DataPrestacaoContas { get; set; }
[Name("SQ_PRESTADOR_CONTAS")]
public string SequencialPrestadorContas { get; set; }
[Name("SG_UF")]
public string SiglaUF { get; set; }
[Name("NM_UE")]
public string NomeUE { get; set; }
[Name("NR_CNPJ_PRESTADOR_CONTA")]
public string CnpjPrestadorConta { get; set; }
[Name("SQ_CANDIDATO")]
public string SequencialCandidato { get; set; }
[Name("NR_CPF_CANDIDATO")]
public string CpfCandidato { get; set; }
[Name("SG_PARTIDO")]
public string SiglaPartido { get; set; }
[Name("DS_TIPO_FORNECEDOR")]
public string TipoFornecedor { get; set; }
[Name("CD_CNAE_FORNECEDOR")]
public string CodigoCnaeFornecedor { get; set; }
[Name("DS_CNAE_FORNECEDOR")]
public string DescricaoCnaeFornecedor { get; set; }
[Name("NR_CPF_CNPJ_FORNECEDOR")]
public string CpfCnpjFornecedor { get; set; }
[Name("NM_FORNECEDOR")]
public string NomeFornecedor { get; set; }
[Name("NM_FORNECEDOR_RFB")]
public string NomeFornecedorRFB { get; set; }
[Name("SG_UF_FORNECEDOR")]
public string SiglaUFFornecedor { get; set; }
[Name("NM_MUNICIPIO_FORNECEDOR")]
public string NomeMunicipioFornecedor { get; set; }
[Name("SQ_CANDIDATO_FORNECEDOR")]
public string SequencialCandidatoFornecedor { get; set; }
[Name("NR_CANDIDATO_FORNECEDOR")]
public string NumeroCandidatoFornecedor { get; set; }
[Name("DS_CARGO_FORNECEDOR")]
public string CargoFornecedor { get; set; }
[Name("NR_PARTIDO_FORNECEDOR")]
public string NumeroPartidoFornecedor { get; set; }
[Name("SG_PARTIDO_FORNECEDOR")]
public string SiglaPartidoFornecedor { get; set; }
[Name("NM_PARTIDO_FORNECEDOR")]
public string NomePartidoFornecedor { get; set; }
[Name("DS_TIPO_DOCUMENTO")]
public string TipoDocumento { get; set; }
[Name("DS_ORIGEM_DESPESA")]
public string OrigemDespesa { get; set; }
[Name("SQ_DESPESA")]
public string SequencialDespesa { get; set; }
[Name("DT_DESPESA")]
public string DataDespesa { get; set; }
[Name("DS_DESPESA")]
public string DescricaoDespesa { get; set; }
[Name("VR_DESPESA_CONTRATADA")]
public float ValorDespesaContratada { get; set; }
}
}

View File

@ -0,0 +1,94 @@
using CsvHelper.Configuration.Attributes;
namespace OpenCand.Parser.Models
{
public class ReceitasCSV
{
[Name("AA_ELEICAO")]
public int AnoEleicao { get; set; }
[Name("ST_TURNO")]
public string Turno { get; set; }
[Name("SQ_PRESTADOR_CONTAS")]
public string SequencialPrestadorContas { get; set; }
[Name("SG_UF")]
public string SiglaUF { get; set; }
[Name("NM_UE")]
public string NomeUE { get; set; }
[Name("NR_CNPJ_PRESTADOR_CONTA")]
public string CnpjPrestadorConta { get; set; }
[Name("SQ_CANDIDATO")]
public string SequencialCandidato { get; set; }
[Name("NR_CPF_CANDIDATO")]
public string CpfCandidato { get; set; }
[Name("SG_PARTIDO")]
public string SiglaPartido { get; set; }
[Name("DS_FONTE_RECEITA")]
public string FonteReceita { get; set; }
[Name("DS_ORIGEM_RECEITA")]
public string OrigemReceita { get; set; }
[Name("DS_NATUREZA_RECEITA")]
public string NaturezaReceita { get; set; }
[Name("DS_ESPECIE_RECEITA")]
public string EspecieReceita { get; set; }
[Name("CD_CNAE_DOADOR")]
public string CodigoCnaeDoador { get; set; }
[Name("DS_CNAE_DOADOR")]
public string DescricaoCnaeDoador { get; set; }
[Name("NR_CPF_CNPJ_DOADOR")]
public string CpfCnpjDoador { get; set; }
[Name("NM_DOADOR")]
public string NomeDoador { get; set; }
[Name("NM_DOADOR_RFB")]
public string NomeDoadorRFB { get; set; }
[Name("SG_UF_DOADOR")]
public string SiglaUFDoaror { get; set; }
[Name("NM_MUNICIPIO_DOADOR")]
public string NomeMunicipioDoador { get; set; }
[Name("SQ_CANDIDATO_DOADOR")]
public string SequencialCandidatoDoador { get; set; }
[Name("SG_PARTIDO_DOADOR")]
public string SiglaPartidoDoador { get; set; }
[Name("NR_RECIBO_DOACAO")]
public string NumeroReciboDoacao { get; set; }
[Name("NR_DOCUMENTO_DOACAO")]
public string NumeroDocumentoDoacao { get; set; }
[Name("SQ_RECEITA")]
public string SequencialReceita { get; set; }
[Name("DT_RECEITA")]
public string DataReceita { get; set; }
[Name("DS_RECEITA")]
public string DescricaoReceita { get; set; }
[Name("VR_RECEITA")]
public float ValorReceita { get; set; }
[Name("DS_GENERO")]
public string Genero { get; set; }
}
}

View File

@ -15,6 +15,8 @@ namespace OpenCand.Parser
private readonly CsvParserService<CandidatoCSV> candidatoParserService;
private readonly CsvParserService<BemCandidatoCSV> bemCandidatoParserService;
private readonly CsvParserService<RedeSocialCSV> redeSocialParserService;
private readonly CsvParserService<DespesasCSV> despesaParserService;
private readonly CsvParserService<ReceitasCSV> receitaParserService;
private readonly string BasePath;
@ -24,7 +26,9 @@ namespace OpenCand.Parser
IConfiguration configuration,
CsvParserService<CandidatoCSV> candidatoParserService,
CsvParserService<BemCandidatoCSV> bemCandidatoParserService,
CsvParserService<RedeSocialCSV> redeSocialParserService)
CsvParserService<RedeSocialCSV> redeSocialParserService,
CsvParserService<DespesasCSV> despesaParserService,
CsvParserService<ReceitasCSV> receitaParserService)
{
this.logger = logger;
this.csvSettings = csvSettings.Value;
@ -32,6 +36,8 @@ namespace OpenCand.Parser
this.candidatoParserService = candidatoParserService;
this.bemCandidatoParserService = bemCandidatoParserService;
this.redeSocialParserService = redeSocialParserService;
this.despesaParserService = despesaParserService;
this.receitaParserService = receitaParserService;
// Get the base path from either SampleFolder in csvSettings or the BasePath in configuration
BasePath = configuration.GetValue<string>("BasePath") ?? string.Empty;
@ -49,10 +55,14 @@ namespace OpenCand.Parser
var candidatosDirectory = Path.Combine(BasePath, csvSettings.CandidatosFolder);
var bensCandidatosDirectory = Path.Combine(BasePath, csvSettings.BensCandidatosFolder);
var redesSociaisDirectory = Path.Combine(BasePath, csvSettings.RedesSociaisFolder);
var despesasDirectory = Path.Combine(BasePath, csvSettings.DespesaCandidatoFolder);
var receitasDirectory = Path.Combine(BasePath, csvSettings.ReceitaCandidatoFolder);
await ParseFolder(candidatosDirectory, candidatoParserService);
await ParseFolder(bensCandidatosDirectory, bemCandidatoParserService);
await ParseFolder(redesSociaisDirectory, redeSocialParserService);
//await ParseFolder(candidatosDirectory, candidatoParserService);
//await ParseFolder(bensCandidatosDirectory, bemCandidatoParserService);
//await ParseFolder(redesSociaisDirectory, redeSocialParserService);
await ParseFolder(despesasDirectory, despesaParserService);
await ParseFolder(receitasDirectory, receitaParserService);
logger.LogInformation("ParseFullDataAsync - Full data parsing completed!");
}

View File

@ -5,6 +5,7 @@ using OpenCand.ETL.Contracts;
using OpenCand.Parser.Models;
using OpenCand.Services;
using OpenCand.Parser.Services;
using OpenCand.ETL.Extensions;
namespace OpenCand.ETL.Parser.ParserServices
{
@ -25,7 +26,7 @@ namespace OpenCand.ETL.Parser.ParserServices
{
// Parse decimal value
decimal? valor = null;
if (!string.IsNullOrEmpty(record.ValorBemCandidato))
if (!record.ValorBemCandidato.IsNullOrEmpty())
{
string normalizedValue = record.ValorBemCandidato.Replace(".", "").Replace(",", ".");
if (decimal.TryParse(normalizedValue, NumberStyles.Any, CultureInfo.InvariantCulture, out var parsedValue))

View File

@ -63,6 +63,7 @@ namespace OpenCand.ETL.Parser.ParserServices
Apelido = record.Apelido,
SqCandidato = record.SequencialCandidato,
Ano = record.AnoEleicao,
Turno = record.Turno,
TipoEleicao = record.TipoAbrangencia,
NomeUE = record.NomeUE,
SiglaUF = record.SiglaUF,

View File

@ -0,0 +1,88 @@
using Microsoft.Extensions.Logging;
using OpenCand.Core.Models;
using OpenCand.ETL.Contracts;
using OpenCand.ETL.Extensions;
using OpenCand.ETL.Services;
using OpenCand.Parser.Models;
namespace OpenCand.ETL.Parser.ParserServices
{
public class DespesaParserService : IParserService<DespesasCSV>
{
private readonly ILogger<DespesaParserService> logger;
private readonly DespesaReceitaService despesaReceitaService;
public DespesaParserService(
ILogger<DespesaParserService> logger,
DespesaReceitaService despesaReceitaService)
{
this.logger = logger;
this.despesaReceitaService = despesaReceitaService;
}
public async Task ParseObject(DespesasCSV record)
{
var despesa = new Despesa
{
SgPartido = record.SiglaPartido,
Ano = record.AnoEleicao,
Turno = int.Parse(record.Turno),
Descricao = record.DescricaoDespesa,
OrigemDespesa = record.OrigemDespesa,
MunicipioFornecedor = record.NomeMunicipioFornecedor,
NomeFornecedor = record.NomeFornecedor,
NomeFornecedorRFB = record.NomeFornecedorRFB,
SqCandidato = record.SequencialCandidato,
TipoDocumento = record.TipoDocumento,
TipoFornecedor = record.TipoFornecedor,
Valor = record.ValorDespesaContratada / 100
};
if (DateTime.TryParse(record.DataDespesa, out var dataDespesa))
{
despesa.DataDespesa = dataDespesa;
}
else
{
despesa.DataDespesa = null;
}
if (record.CpfCnpjFornecedor.Length == 0 || record.CpfCnpjFornecedor == "-4")
{
despesa.CpfFornecedor = null;
despesa.CnpjFornecedor = null;
}
else if (record.CpfCnpjFornecedor.Length == 11)
{
despesa.CpfFornecedor = record.CpfCnpjFornecedor;
despesa.CnpjFornecedor = null;
}
else if (record.CpfCnpjFornecedor.Length == 14)
{
despesa.CnpjFornecedor = record.CpfCnpjFornecedor;
despesa.CpfFornecedor = null;
}
if (despesa.Descricao.IsNullOrEmpty())
despesa.Descricao = null;
if (despesa.OrigemDespesa.IsNullOrEmpty())
despesa.OrigemDespesa = null;
if (despesa.MunicipioFornecedor.IsNullOrEmpty())
despesa.MunicipioFornecedor = null;
if (despesa.NomeFornecedor.IsNullOrEmpty())
despesa.NomeFornecedor = null;
if (despesa.NomeFornecedorRFB.IsNullOrEmpty())
despesa.NomeFornecedorRFB = null;
if (despesa.TipoDocumento.IsNullOrEmpty())
despesa.TipoDocumento = null;
if (despesa.CpfFornecedor.IsNullOrEmpty())
despesa.CpfFornecedor = null;
if (despesa.CnpjFornecedor.IsNullOrEmpty())
despesa.CnpjFornecedor = null;
if (despesa.TipoFornecedor.IsNullOrEmpty())
despesa.TipoFornecedor = null;
await despesaReceitaService.AddDespesaAsync(despesa);
}
}
}

View File

@ -0,0 +1,84 @@
using Microsoft.Extensions.Logging;
using OpenCand.Core.Models;
using OpenCand.ETL.Contracts;
using OpenCand.ETL.Extensions;
using OpenCand.ETL.Services;
using OpenCand.Parser.Models;
namespace OpenCand.ETL.Parser.ParserServices
{
public class ReceitaParserService : IParserService<ReceitasCSV>
{
private readonly ILogger<ReceitaParserService> logger;
private readonly DespesaReceitaService despesaReceitaService;
public ReceitaParserService(
ILogger<ReceitaParserService> logger,
DespesaReceitaService despesaReceitaService)
{
this.logger = logger;
this.despesaReceitaService = despesaReceitaService;
}
public async Task ParseObject(ReceitasCSV record)
{
var receita = new Receita
{
EspecieReceita = record.EspecieReceita,
MunicipioDoador = record.NomeMunicipioDoador,
SgPartido = record.SiglaPartido,
FonteReceita = record.FonteReceita,
NaturezaReceita = record.NaturezaReceita,
Ano = record.AnoEleicao,
Descricao = record.DescricaoReceita,
NomeDoador = record.NomeDoador,
NomeDoadorRFB = record.NomeDoadorRFB,
OrigemReceita = record.OrigemReceita,
Turno = int.Parse(record.Turno),
SqCandidato = record.SequencialCandidato,
Valor = record.ValorReceita / 100
};
if (DateTime.TryParse(record.DataReceita, out var dataReceita))
{
receita.DataReceita = dataReceita;
}
else
{
receita.DataReceita = null;
}
if (record.CpfCnpjDoador.Length == 0 || record.CpfCnpjDoador == "-4") {
receita.CpfDoador = null;
receita.CnpjDoador = null;
}
else if (record.CpfCnpjDoador.Length == 11)
{
receita.CpfDoador = record.CpfCnpjDoador;
receita.CnpjDoador = null;
}
else if (record.CpfCnpjDoador.Length == 14)
{
receita.CnpjDoador = record.CpfCnpjDoador;
receita.CpfDoador = null;
}
if (receita.Descricao.IsNullOrEmpty())
receita.Descricao = null;
if (receita.MunicipioDoador.IsNullOrEmpty())
receita.MunicipioDoador = null;
if (receita.NomeDoador.IsNullOrEmpty())
receita.NomeDoador = null;
if (receita.NomeDoadorRFB.IsNullOrEmpty())
receita.NomeDoadorRFB = null;
if (receita.OrigemReceita.IsNullOrEmpty())
receita.OrigemReceita = null;
if (receita.CpfDoador.IsNullOrEmpty())
receita.CpfDoador = null;
if (receita.CnpjDoador.IsNullOrEmpty())
receita.CnpjDoador = null;
await despesaReceitaService.AddReceitaAsync(receita);
}
}
}

View File

@ -6,6 +6,7 @@ using OpenCand.Config;
using OpenCand.ETL.Contracts;
using OpenCand.ETL.Parser.ParserServices;
using OpenCand.ETL.Repository;
using OpenCand.ETL.Services;
using OpenCand.Parser;
using OpenCand.Parser.Models;
using OpenCand.Parser.Services;
@ -60,13 +61,19 @@ namespace OpenCand
services.AddTransient<IParserService<CandidatoCSV>, CandidatoParserService>();
services.AddTransient<IParserService<BemCandidatoCSV>, BemCandidatoParserService>();
services.AddTransient<IParserService<RedeSocialCSV>, RedeSocialParserService>();
services.AddTransient<IParserService<DespesasCSV>, DespesaParserService>();
services.AddTransient<IParserService<ReceitasCSV>, ReceitaParserService>();
services.AddTransient<CsvParserService<CandidatoCSV>>();
services.AddTransient<CsvParserService<BemCandidatoCSV>>();
services.AddTransient<CsvParserService<RedeSocialCSV>>();
services.AddTransient<CsvParserService<DespesasCSV>>();
services.AddTransient<CsvParserService<ReceitasCSV>>();
services.AddTransient<ParserManager>();
services.AddTransient<DespesaReceitaService>();
services.AddTransient<CandidatoService>();
services.AddTransient<BemCandidatoService>();
services.AddTransient<RedeSocialService>();
services.AddTransient<DespesaReceitaRepository>();
services.AddTransient<CandidatoRepository>();
services.AddTransient<BemCandidatoRepository>();
services.AddTransient<RedeSocialRepository>();

View File

@ -49,8 +49,8 @@ namespace OpenCand.Repository
using (var connection = new NpgsqlConnection(ConnectionString))
{
await connection.ExecuteAsync(@"
INSERT INTO candidato_mapping (idcandidato, cpf, nome, apelido, sqcandidato, ano, tipoeleicao, siglauf, nomeue, cargo, nrcandidato, sgpartido, resultado)
VALUES (@idcandidato, @cpf, @nome, @apelido, @sqcandidato, @ano, @tipoeleicao, @siglauf, @nomeue, @cargo, @nrcandidato, @sgpartido, @resultado)
INSERT INTO candidato_mapping (idcandidato, cpf, nome, apelido, sqcandidato, ano, turno, tipoeleicao, siglauf, nomeue, cargo, nrcandidato, sgpartido, resultado)
VALUES (@idcandidato, @cpf, @nome, @apelido, @sqcandidato, @ano, @turno, @tipoeleicao, @siglauf, @nomeue, @cargo, @nrcandidato, @sgpartido, @resultado)
ON CONFLICT DO NOTHING;",
new
{
@ -60,6 +60,7 @@ namespace OpenCand.Repository
apelido = candidatoMapping.Apelido,
sqcandidato = candidatoMapping.SqCandidato,
ano = candidatoMapping.Ano,
turno = candidatoMapping.Turno,
tipoeleicao = candidatoMapping.TipoEleicao,
siglauf = candidatoMapping.SiglaUF,
nomeue = candidatoMapping.NomeUE,
@ -83,6 +84,18 @@ namespace OpenCand.Repository
}
}
public async Task<Guid?> GetIdCandidatoBySqCandidato(string sqcandidato)
{
using (var connection = new NpgsqlConnection(ConnectionString))
{
var query = @"
SELECT idcandidato
FROM candidato_mapping
WHERE sqcandidato = @sqcandidato";
return await connection.QueryFirstOrDefaultAsync<Guid>(query, new { sqcandidato });
}
}
public async Task<Candidato?> GetCandidatoByNome(string nome, DateTime datanascimento)
{
using (var connection = new NpgsqlConnection(ConnectionString))

View File

@ -0,0 +1,139 @@
using Dapper;
using Microsoft.Extensions.Configuration;
using Npgsql;
using OpenCand.Core.Models;
namespace OpenCand.Repository
{
public class DespesaReceitaRepository : BaseRepository
{
public DespesaReceitaRepository(IConfiguration configuration) : base(configuration)
{
}
public async Task AddDespesaAsync(Despesa despesa)
{
using (var connection = new NpgsqlConnection(ConnectionString))
{
await connection.ExecuteAsync(@"
INSERT INTO despesas_candidato (
idcandidato,
ano,
turno,
sqcandidato,
sgpartido,
tipofornecedor,
cnpjfornecedor,
cpffornecedor,
nomefornecedor,
nomefornecedorrfb,
municipiofornecedor,
tipodocumento,
datadespesa,
descricao,
origemdespesa,
valor
) VALUES (
@idCandidato,
@ano,
@turno,
@sqCandidato,
@sgPartido,
@tipoFornecedor,
@cnpjFornecedor,
@cpfFornecedor,
@nomeFornecedor,
@nomeFornecedorRFB,
@municipioFornecedor,
@tipoDocumento,
@dataDespesa,
@descricao,
@origemdespesa,
@valor
)", new
{
idCandidato = despesa.IdCandidato,
ano = despesa.Ano,
turno = despesa.Turno,
sqCandidato = despesa.SqCandidato,
sgPartido = despesa.SgPartido,
tipoFornecedor = despesa.TipoFornecedor,
cnpjFornecedor = despesa.CnpjFornecedor,
cpfFornecedor = despesa.CpfFornecedor,
nomeFornecedor = despesa.NomeFornecedor,
nomeFornecedorRFB = despesa.NomeFornecedorRFB,
municipioFornecedor = despesa.MunicipioFornecedor,
tipoDocumento = despesa.TipoDocumento,
dataDespesa = despesa.DataDespesa,
descricao = despesa.Descricao,
origemdespesa = despesa.OrigemDespesa,
valor = despesa.Valor
});
}
}
public async Task AddReceitaAsync(Receita receita)
{
using (var connection = new NpgsqlConnection(ConnectionString))
{
await connection.ExecuteAsync(@"
INSERT INTO receitas_candidato (
idcandidato,
ano,
turno,
sqcandidato,
sgpartido,
fontereceita,
origemreceita,
naturezareceita,
especiereceita,
cnpjdoador,
cpfdoador,
nomedoador,
nomedoadorrfb,
municipiodoador,
datareceita,
descricao,
valor
) VALUES (
@idCandidato,
@ano,
@turno,
@sqCandidato,
@sgPartido,
@fonteReceita,
@origemReceita,
@naturezaReceita,
@especieReceita,
@cnpjDoador,
@cpfDoador,
@nomeDoador,
@nomeDoadorRFB,
@municipioDoador,
@dataReceita,
@descricao,
@valor
)", new
{
idCandidato = receita.IdCandidato,
ano = receita.Ano,
turno = receita.Turno,
sqCandidato = receita.SqCandidato,
sgPartido = receita.SgPartido,
fonteReceita = receita.FonteReceita,
origemReceita = receita.OrigemReceita,
naturezaReceita = receita.NaturezaReceita,
especieReceita = receita.EspecieReceita,
cnpjDoador = receita.CnpjDoador,
cpfDoador = receita.CpfDoador,
nomeDoador = receita.NomeDoador,
nomeDoadorRFB = receita.NomeDoadorRFB,
municipioDoador = receita.MunicipioDoador,
dataReceita = receita.DataReceita,
descricao = receita.Descricao,
valor = receita.Valor
});
}
}
}
}

View File

@ -0,0 +1,54 @@
using OpenCand.Core.Models;
using OpenCand.Repository;
namespace OpenCand.ETL.Services
{
public class DespesaReceitaService
{
private readonly DespesaReceitaRepository despesaReceitaRepository;
private readonly CandidatoRepository candidatorepository;
public DespesaReceitaService(DespesaReceitaRepository despesaReceitaRepository, CandidatoRepository candidatoRepository)
{
this.despesaReceitaRepository = despesaReceitaRepository;
this.candidatorepository = candidatoRepository;
}
public async Task AddDespesaAsync(Despesa despesa)
{
if (despesa == null || string.IsNullOrEmpty(despesa.SqCandidato))
{
throw new ArgumentNullException(nameof(despesa), "Despesa cannot be null");
}
var idCandidato = await candidatorepository.GetIdCandidatoBySqCandidato(despesa.SqCandidato);
if (idCandidato == Guid.Empty)
{
throw new ArgumentException($"Candidato with SqCandidato {despesa.SqCandidato} not found.");
}
despesa.IdCandidato = (Guid)idCandidato;
await despesaReceitaRepository.AddDespesaAsync(despesa);
}
public async Task AddReceitaAsync(Receita receita)
{
if (receita == null || string.IsNullOrEmpty(receita.SqCandidato))
{
throw new ArgumentNullException(nameof(receita), "Receita cannot be null");
}
var idCandidato = await candidatorepository.GetIdCandidatoBySqCandidato(receita.SqCandidato);
if (idCandidato == Guid.Empty)
{
throw new ArgumentException($"Candidato with SqCandidato {receita.SqCandidato} not found.");
}
receita.IdCandidato = (Guid)idCandidato;
await despesaReceitaRepository.AddReceitaAsync(receita);
}
}
}

View File

@ -12,11 +12,15 @@
"CsvSettings": {
"CandidatosFolder": "data/consulta_cand",
"BensCandidatosFolder": "data/bem_candidato",
"RedesSociaisFolder": "data/rede_social"
"RedesSociaisFolder": "data/rede_social",
"DespesaCandidatoFolder": "data/despesas_candidato",
"ReceitaCandidatoFolder": "data/receitas_candidato"
},
"ParserSettings": {
"DefaultThreads": 40,
"CandidatoCSVThreads": 5
"CandidatoCSVThreads": 5,
"DepesasCSVThreads": 50,
"ReceitasCSVThreads": 50
},
"BasePath": "sample"
}

View File

@ -8,4 +8,91 @@ OpenCand is built using:
* .NET 8 - for parsing initial information from CSV files to the PostgreSQL database using Entity Framework.
* .NET Core 8 - for the API
* PostgreSQL - for the database
* React - for the front-end
* React - for the front-end
## Disponibilidade de dados (Prod)
* ✅ = Disponível
* ❌ = Não disponível
* ⛔ = Sem dados
| Nome do dado | Ano | Disponível? |
|-------------------|------|-------------|
| Candidatos | 2024 | ✅ |
| Bem Candidato | 2024 | ✅ |
| Despesas/Receitas | 2024 | ✅ |
| Rede Social | 2024 | ✅ |
| Fotos | 2024 | ✅ |
| - | - | - |
| Candidatos | 2022 | ✅ |
| Bem Candidato | 2022 | ✅ |
| Despesas/Receitas | 2022 | ✅ |
| Rede Social | 2022 | ✅ |
| Fotos | 2022 | ❌ |
| - | - | - |
| Candidatos | 2020 | ✅ |
| Bem Candidato | 2020 | ✅ |
| Despesas/Receitas | 2020 | ✅ |
| Rede Social | 2020 | ✅ |
| Fotos | 2020 | ❌ |
| - | - | - |
| Candidatos | 2018 | ✅ |
| Bem Candidato | 2018 | ✅ |
| Despesas/Receitas | 2018 | ❌ |
| Rede Social | 2018 | ❌ |
| Fotos | 2018 | ❌ |
| - | - | - |
| Candidatos | 2016 | ✅ |
| Bem Candidato | 2016 | ✅ |
| Despesas/Receitas | 2016 | ❌ |
| Rede Social | 2016 | ❌ |
| Fotos | 2016 | ❌ |
| - | - | - |
| Candidatos | 2014 | ✅ |
| Bem Candidato | 2014 | ✅ |
| Despesas/Receitas | 2014 | ❌ |
| Rede Social | 2014 | ❌ |
| Fotos | 2014 | ❌ |
| - | - | - |
| Candidatos | 2012 | ✅ |
| Bem Candidato | 2012 | ✅ |
| Despesas/Receitas | 2012 | ❌ |
| Rede Social | 2012 | ❌ |
| Fotos | 2012 | ❌ |
| - | - | - |
| Candidatos | 2010 | ✅ |
| Bem Candidato | 2010 | ✅ |
| Despesas/Receitas | 2010 | ❌ |
| Rede Social | 2010 | ❌ |
| Fotos | 2010 | ❌ |
| - | - | - |
| Candidatos | 2008 | ✅ |
| Bem Candidato | 2008 | ✅ |
| Despesas/Receitas | 2008 | ❌ |
| Rede Social | 2008 | ❌ |
| Fotos | 2008 | ❌ |
| - | - | - |
| Candidatos | 2006 | ✅ |
| Bem Candidato | 2006 | ✅ |
| Despesas/Receitas | 2006 | ❌ |
| Rede Social | 2006 | ❌ |
| Fotos | 2006 | ❌ |
| - | - | - |
| Candidatos | 2004 | ✅ |
| Bem Candidato | 2004 | ❌ |
| Despesas/Receitas | 2004 | ❌ |
| Rede Social | 2004 | ❌ |
| Fotos | 2004 | ❌ |
| - | - | - |
| Candidatos | 2002 | ✅ |
| Bem Candidato | 2002 | ❌ |
| Despesas/Receitas | 2002 | ❌ |
| Rede Social | 2002 | ❌ |
| Fotos | 2002 | ❌ |
| - | - | - |
| Candidatos | 2000 | ✅ |
| Bem Candidato | 2000 | ⛔ |
| Despesas/Receitas | 2000 | ⛔ |
| Rede Social | 2000 | ⛔ |
| Fotos | 2000 | ⛔ |
| - | - | - |

View File

@ -3,6 +3,8 @@ DROP TABLE IF EXISTS candidato_mapping CASCADE;
DROP TABLE IF EXISTS rede_social CASCADE;
DROP TABLE IF EXISTS candidato CASCADE;
DROP TABLE IF EXISTS partido CASCADE;
DROP TABLE IF EXISTS despesas_candidato CASCADE;
DROP TABLE IF EXISTS receitas_candidato CASCADE;
CREATE TABLE candidato (
idcandidato UUID NOT NULL PRIMARY KEY,
@ -25,7 +27,8 @@ CREATE TABLE candidato_mapping (
cpf VARCHAR(11),
nome VARCHAR(255) NOT NULL,
apelido VARCHAR(255),
sqcandidato TEXT,
sqcandidato VARCHAR(50) NOT NULL,
turno VARCHAR(2) NOT NULL,
ano INT NOT NULL,
tipoeleicao VARCHAR(50),
siglauf VARCHAR(2),
@ -76,4 +79,58 @@ CREATE TABLE partido (
numero INT NOT NULL
);
CREATE INDEX idx_partido_nome ON partido (nome);
CREATE INDEX idx_partido_numero ON partido (numero);
CREATE INDEX idx_partido_numero ON partido (numero);
---- Tables for storing despesas e receitas of candidacies
CREATE TABLE despesas_candidato (
idreceita UUID NOT NULL DEFAULT gen_random_uuid(),
idcandidato UUID NOT NULL,
ano INT NOT NULL,
turno VARCHAR(2) NOT NULL,
sqcandidato VARCHAR(50) NOT NULL,
sgpartido VARCHAR(50) NOT NULL,
tipofornecedor VARCHAR(150),
cnpjfornecedor VARCHAR(14),
cpffornecedor VARCHAR(11),
nomefornecedor VARCHAR(255),
nomefornecedorrfb VARCHAR(255),
municipiofornecedor VARCHAR(100),
tipodocumento VARCHAR(50),
datadespesa TIMESTAMPTZ,
descricao TEXT,
origemdespesa TEXT,
valor NUMERIC(20, 2),
CONSTRAINT pk_despesas_candidato PRIMARY KEY (idreceita),
CONSTRAINT fk_despesas_candidato_candidato FOREIGN KEY (idcandidato) REFERENCES candidato(idcandidato) ON DELETE CASCADE ON UPDATE CASCADE
);
CREATE INDEX idx_despesas_candidato_idcandidato ON despesas_candidato (idcandidato);
CREATE INDEX idx_despesas_candidato_ano ON despesas_candidato (ano);
CREATE INDEX idx_despesas_candidato_sqcandidato ON despesas_candidato (sqcandidato);
CREATE INDEX idx_despesas_candidato_sgpartido ON despesas_candidato (sgpartido);
CREATE TABLE receitas_candidato (
idreceita UUID NOT NULL DEFAULT gen_random_uuid(),
idcandidato UUID NOT NULL,
ano INT NOT NULL,
turno VARCHAR(2) NOT NULL,
sqcandidato VARCHAR(50) NOT NULL,
sgpartido VARCHAR(50) NOT NULL,
fontereceita VARCHAR(150),
origemreceita VARCHAR(250),
naturezareceita VARCHAR(250),
especiereceita VARCHAR(250),
cnpjdoador VARCHAR(14),
cpfdoador VARCHAR(11),
nomedoador VARCHAR(255),
nomedoadorrfb VARCHAR(255),
municipiodoador VARCHAR(100),
datareceita TIMESTAMPTZ,
descricao TEXT,
valor NUMERIC(20, 2),
CONSTRAINT pk_receitas_candidato PRIMARY KEY (idreceita),
CONSTRAINT fk_receitas_candidato_candidato FOREIGN KEY (idcandidato) REFERENCES candidato(idcandidato) ON DELETE CASCADE ON UPDATE CASCADE
);
CREATE INDEX idx_receitas_candidato_idcandidato ON receitas_candidato (idcandidato);
CREATE INDEX idx_receitas_candidato_ano ON receitas_candidato (ano);
CREATE INDEX idx_receitas_candidato_sqcandidato ON receitas_candidato (sqcandidato);
CREATE INDEX idx_receitas_candidato_sgpartido ON receitas_candidato (sgpartido);