Compare commits

..

No commits in common. "e57b3162dbf07ef5537074e0654f09f102219af0" and "b9908b36b7fd75683438f02a3c6af35c609ad5d7" have entirely different histories.

29 changed files with 135 additions and 863 deletions

View File

@ -22,11 +22,6 @@ 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);
}
@ -57,17 +52,5 @@ 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,14 +21,4 @@ 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,7 +59,6 @@ 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

@ -1,42 +0,0 @@
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,7 +12,6 @@ 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;
@ -21,7 +20,6 @@ namespace OpenCand.API.Services
OpenCandRepository openCandRepository,
CandidatoRepository candidatoRepository,
BemCandidatoRepository bemCandidatoRepository,
DespesaReceitaRepository despesaReceitaRepository,
IOptions<FotosSettings> fotoSettings,
IConfiguration configuration,
ILogger<OpenCandService> logger)
@ -29,7 +27,6 @@ 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;
@ -116,33 +113,5 @@ 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,7 +40,6 @@ 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

@ -1,25 +0,0 @@
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

@ -1,26 +0,0 @@
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,7 +5,5 @@ 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

@ -1,12 +0,0 @@
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

@ -0,0 +1,15 @@
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

@ -0,0 +1,15 @@
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

@ -0,0 +1,14 @@
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,10 +73,8 @@ 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
// consider as normal line
break;
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.LogWarning($"FixCsvFile - Line {i + 1} has {columns.Length} columns, expected {headerCount}. Attempting to fix [i = {lineJump}]...");

View File

@ -11,9 +11,6 @@ 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

@ -1,97 +0,0 @@
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

@ -1,94 +0,0 @@
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,8 +15,6 @@ 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;
@ -26,9 +24,7 @@ namespace OpenCand.Parser
IConfiguration configuration,
CsvParserService<CandidatoCSV> candidatoParserService,
CsvParserService<BemCandidatoCSV> bemCandidatoParserService,
CsvParserService<RedeSocialCSV> redeSocialParserService,
CsvParserService<DespesasCSV> despesaParserService,
CsvParserService<ReceitasCSV> receitaParserService)
CsvParserService<RedeSocialCSV> redeSocialParserService)
{
this.logger = logger;
this.csvSettings = csvSettings.Value;
@ -36,8 +32,6 @@ 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;
@ -55,14 +49,10 @@ 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(despesasDirectory, despesaParserService);
await ParseFolder(receitasDirectory, receitaParserService);
await ParseFolder(candidatosDirectory, candidatoParserService);
await ParseFolder(bensCandidatosDirectory, bemCandidatoParserService);
await ParseFolder(redesSociaisDirectory, redeSocialParserService);
logger.LogInformation("ParseFullDataAsync - Full data parsing completed!");
}

View File

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

View File

@ -63,7 +63,6 @@ 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

@ -1,88 +0,0 @@
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

@ -1,84 +0,0 @@
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,7 +6,6 @@ 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;
@ -61,19 +60,13 @@ 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, turno, tipoeleicao, siglauf, nomeue, cargo, nrcandidato, sgpartido, resultado)
VALUES (@idcandidato, @cpf, @nome, @apelido, @sqcandidato, @ano, @turno, @tipoeleicao, @siglauf, @nomeue, @cargo, @nrcandidato, @sgpartido, @resultado)
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)
ON CONFLICT DO NOTHING;",
new
{
@ -60,7 +60,6 @@ namespace OpenCand.Repository
apelido = candidatoMapping.Apelido,
sqcandidato = candidatoMapping.SqCandidato,
ano = candidatoMapping.Ano,
turno = candidatoMapping.Turno,
tipoeleicao = candidatoMapping.TipoEleicao,
siglauf = candidatoMapping.SiglaUF,
nomeue = candidatoMapping.NomeUE,
@ -84,18 +83,6 @@ 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

@ -1,139 +0,0 @@
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

@ -1,54 +0,0 @@
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,15 +12,11 @@
"CsvSettings": {
"CandidatosFolder": "data/consulta_cand",
"BensCandidatosFolder": "data/bem_candidato",
"RedesSociaisFolder": "data/rede_social",
"DespesaCandidatoFolder": "data/despesas_candidato",
"ReceitaCandidatoFolder": "data/receitas_candidato"
"RedesSociaisFolder": "data/rede_social"
},
"ParserSettings": {
"DefaultThreads": 40,
"CandidatoCSVThreads": 5,
"DepesasCSVThreads": 50,
"ReceitasCSVThreads": 50
"CandidatoCSVThreads": 5
},
"BasePath": "sample"
}

View File

@ -8,91 +8,4 @@ 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
## 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 | ⛔ |
| - | - | - |
* React - for the front-end

79
db/db.sql Normal file
View File

@ -0,0 +1,79 @@
DROP TABLE IF EXISTS bem_candidato CASCADE;
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;
CREATE TABLE candidato (
idcandidato UUID NOT NULL PRIMARY KEY,
cpf VARCHAR(11),
nome VARCHAR(255) NOT NULL,
apelido VARCHAR(255),
datanascimento TIMESTAMPTZ,
email TEXT,
sexo CHAR(15),
estadocivil VARCHAR(50),
escolaridade VARCHAR(50),
ocupacao VARCHAR(150)
);
CREATE INDEX idx_candidato_nome ON candidato (nome);
CREATE INDEX idx_candidato_apelido ON candidato (apelido);
-- Each candidato (idcandidato, cpf, nome) will be mapped to a (sqcandidato, ano, tipo_eleicao, sg_uf, cargo, resultado)
CREATE TABLE candidato_mapping (
idcandidato UUID NOT NULL,
cpf VARCHAR(11),
nome VARCHAR(255) NOT NULL,
apelido VARCHAR(255),
sqcandidato TEXT,
ano INT NOT NULL,
tipoeleicao VARCHAR(50),
siglauf VARCHAR(2),
nomeue VARCHAR(100),
cargo VARCHAR(50),
sgpartido VARCHAR(50),
nrcandidato VARCHAR(20),
resultado VARCHAR(50),
CONSTRAINT pk_candidato_mapping PRIMARY KEY (idcandidato, ano, siglauf, nomeue, cargo, nrcandidato, resultado),
CONSTRAINT fk_candidato_mapping_candidato FOREIGN KEY (idcandidato) REFERENCES candidato(idcandidato) ON DELETE CASCADE ON UPDATE CASCADE
);
CREATE INDEX idx_candidato_mapping_cpf ON candidato_mapping (cpf);
CREATE INDEX idx_candidato_mapping_nome ON candidato_mapping (nome);
CREATE INDEX idx_candidato_mapping_apelido ON candidato_mapping (apelido);
CREATE INDEX idx_candidato_mapping_ano ON candidato_mapping (ano);
CREATE INDEX idx_candidato_mapping_sqcandidato ON candidato_mapping (sqcandidato);
---- Table for storing assets of candidates
CREATE TABLE bem_candidato (
idcandidato UUID NOT NULL,
ano INT NOT NULL,
ordembem INT,
tipobem VARCHAR(150),
descricao VARCHAR(500),
valor NUMERIC(20, 2),
CONSTRAINT fk_bem_candidato_candidato FOREIGN KEY (idcandidato) REFERENCES candidato(idcandidato) ON DELETE CASCADE ON UPDATE CASCADE
);
ALTER TABLE bem_candidato ADD CONSTRAINT pk_bem_candidato PRIMARY KEY (idcandidato, ano, ordembem);
CREATE INDEX idx_bem_candidato_idcandidato ON bem_candidato (idcandidato);
CREATE INDEX idx_bem_candidato_valor ON bem_candidato (valor);
---- Table for storing social media links of candidates
CREATE TABLE rede_social (
idcandidato UUID NOT NULL,
rede VARCHAR(50) NOT NULL,
siglauf VARCHAR(2),
ano INT NOT NULL,
link TEXT NOT NULL,
CONSTRAINT pk_rede_social PRIMARY KEY (idcandidato, rede, siglauf, ano),
CONSTRAINT fk_rede_social_candidato FOREIGN KEY (idcandidato) REFERENCES candidato(idcandidato) ON DELETE CASCADE ON UPDATE CASCADE
);
CREATE INDEX idx_rede_social_idcandidato ON rede_social (idcandidato);
---- Table for storing party information
CREATE TABLE partido (
sigla VARCHAR(50) NOT NULL PRIMARY KEY,
nome VARCHAR(255) NOT NULL,
numero INT NOT NULL
);
CREATE INDEX idx_partido_nome ON partido (nome);
CREATE INDEX idx_partido_numero ON partido (numero);