stuff and refactor
This commit is contained in:
@@ -1,15 +1,14 @@
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OpenCand.Repository;
|
||||
|
||||
namespace OpenCand.Parser.Services
|
||||
{
|
||||
public class CsvFixerService
|
||||
{
|
||||
private readonly ILogger<CsvParserService> logger;
|
||||
private readonly ILogger<CsvFixerService> logger;
|
||||
|
||||
public CsvFixerService(
|
||||
ILogger<CsvParserService> logger)
|
||||
ILogger<CsvFixerService> logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
150
OpenCand.ETL/Parser/CsvServices/CsvParserService.cs
Normal file
150
OpenCand.ETL/Parser/CsvServices/CsvParserService.cs
Normal file
@@ -0,0 +1,150 @@
|
||||
using System.Globalization;
|
||||
using CsvHelper;
|
||||
using CsvHelper.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OpenCand.ETL.Contracts;
|
||||
|
||||
namespace OpenCand.Parser.Services
|
||||
{
|
||||
public class CsvParserService<CsvObj> : IDisposable
|
||||
{
|
||||
private readonly ILogger<CsvParserService<CsvObj>> logger;
|
||||
private readonly CsvFixerService csvFixerService;
|
||||
private readonly IParserService<CsvObj> parserService;
|
||||
|
||||
private readonly CsvConfiguration parserConfig;
|
||||
|
||||
// Progress tracking fields
|
||||
private long processedCount;
|
||||
private long totalCount;
|
||||
private string currentTask = string.Empty;
|
||||
private Timer? progressTimer;
|
||||
private readonly object progressLock = new object();
|
||||
|
||||
public CsvParserService(
|
||||
ILogger<CsvParserService<CsvObj>> logger,
|
||||
IParserService<CsvObj> parserService,
|
||||
CsvFixerService csvFixerService)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.csvFixerService = csvFixerService;
|
||||
this.parserService = parserService;
|
||||
|
||||
parserConfig = new CsvConfiguration(CultureInfo.InvariantCulture)
|
||||
{
|
||||
Delimiter = ";",
|
||||
HasHeaderRecord = true,
|
||||
PrepareHeaderForMatch = args => args.Header.ToLower(),
|
||||
MissingFieldFound = null,
|
||||
TrimOptions = TrimOptions.Trim,
|
||||
Encoding = System.Text.Encoding.UTF8
|
||||
};
|
||||
}
|
||||
|
||||
public async Task ParseFolderAsync(string filePath)
|
||||
{
|
||||
logger.LogInformation($"ParseFolderAsync - Starting to parse '{filePath}'");
|
||||
|
||||
filePath = csvFixerService.FixCsvFile(filePath);
|
||||
|
||||
// Fix the CSV file if necessary
|
||||
if (string.IsNullOrEmpty(filePath))
|
||||
{
|
||||
logger.LogError($"ParseFolderAsync - Failed to fix CSV file at '{filePath}'");
|
||||
throw new InvalidOperationException($"Failed to fix CSV file at '{filePath}'");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var reader = new StreamReader(filePath);
|
||||
using var csv = new CsvReader(reader, parserConfig);
|
||||
var po = new ParallelOptions
|
||||
{
|
||||
MaxDegreeOfParallelism = 40
|
||||
};
|
||||
|
||||
//csv.Context.RegisterClassMap<ClassMap<CsvObj>>(); // optional for advanced mapping, not needed
|
||||
var records = csv.GetRecords<CsvObj>().ToList();
|
||||
|
||||
StartProgressTracking($"Parsing {nameof(CsvObj)} - {Path.GetFileName(filePath)}", records.Count);
|
||||
|
||||
await Parallel.ForEachAsync(records, po, async (record, ct) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await parserService.ParseObject(record);
|
||||
|
||||
// Increment progress
|
||||
IncrementProgress();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, $"ParseFolderAsync - Error processing:");
|
||||
IncrementProgress();
|
||||
}
|
||||
});
|
||||
|
||||
StopProgressTracking();
|
||||
|
||||
logger.LogInformation($"ParseFolderAsync - Finished parsing from {filePath}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, $"ParseFolderAsync - Error parsing file {filePath}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
// Progress tracking methods
|
||||
private void StartProgressTracking(string taskName, long total)
|
||||
{
|
||||
lock (progressLock)
|
||||
{
|
||||
currentTask = taskName;
|
||||
processedCount = 0;
|
||||
totalCount = total;
|
||||
|
||||
progressTimer?.Dispose();
|
||||
progressTimer = new Timer(LogProgress, null, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5));
|
||||
|
||||
logger.LogInformation("Progress - Task: {Task}, Total: {Total}", currentTask, totalCount);
|
||||
}
|
||||
}
|
||||
|
||||
private void IncrementProgress()
|
||||
{
|
||||
Interlocked.Increment(ref processedCount);
|
||||
}
|
||||
|
||||
private void StopProgressTracking()
|
||||
{
|
||||
lock (progressLock)
|
||||
{
|
||||
progressTimer?.Dispose();
|
||||
progressTimer = null;
|
||||
|
||||
// Log final progress
|
||||
var percentage = totalCount > 0 ? (double)processedCount / totalCount * 100 : 0;
|
||||
logger.LogInformation("Progress - Task: {Task}, Processed: {Processed}, Total: {Total}, Progress: {Percentage:F2}%",
|
||||
currentTask, processedCount, totalCount, percentage);
|
||||
}
|
||||
}
|
||||
|
||||
private void LogProgress(object? state)
|
||||
{
|
||||
lock (progressLock)
|
||||
{
|
||||
if (string.IsNullOrEmpty(currentTask)) return;
|
||||
|
||||
var percentage = totalCount > 0 ? (double)processedCount / totalCount * 100 : 0;
|
||||
logger.LogInformation("Progress - Task: {Task}, Processed: {Processed}, Total: {Total}, Progress: {Percentage:F2}%",
|
||||
currentTask, processedCount, totalCount, percentage);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
progressTimer?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
@@ -2,116 +2,81 @@ using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using OpenCand.Config;
|
||||
using OpenCand.Parser.Models;
|
||||
using OpenCand.Parser.Services;
|
||||
|
||||
namespace OpenCand.Parser
|
||||
{
|
||||
public class ParserManager
|
||||
{
|
||||
private readonly CsvParserService csvParserService;
|
||||
private readonly ILogger<ParserManager> logger;
|
||||
private readonly CsvSettings csvSettings;
|
||||
private readonly IConfiguration configuration;
|
||||
|
||||
private readonly CsvParserService<CandidatoCSV> candidatoParserService;
|
||||
private readonly CsvParserService<BemCandidatoCSV> bemCandidatoParserService;
|
||||
private readonly CsvParserService<RedeSocialCSV> redeSocialParserService;
|
||||
|
||||
private readonly string BasePath;
|
||||
|
||||
public ParserManager(
|
||||
CsvParserService csvParserService,
|
||||
IOptions<CsvSettings> csvSettings,
|
||||
ILogger<ParserManager> logger,
|
||||
IConfiguration configuration)
|
||||
IConfiguration configuration,
|
||||
CsvParserService<CandidatoCSV> candidatoParserService,
|
||||
CsvParserService<BemCandidatoCSV> bemCandidatoParserService,
|
||||
CsvParserService<RedeSocialCSV> redeSocialParserService)
|
||||
{
|
||||
this.csvParserService = csvParserService;
|
||||
this.logger = logger;
|
||||
this.csvSettings = csvSettings.Value;
|
||||
this.configuration = configuration;
|
||||
|
||||
this.candidatoParserService = candidatoParserService;
|
||||
this.bemCandidatoParserService = bemCandidatoParserService;
|
||||
this.redeSocialParserService = redeSocialParserService;
|
||||
|
||||
// Get the base path from either SampleFolder in csvSettings or the BasePath in configuration
|
||||
BasePath = configuration.GetValue<string>("BasePath") ?? string.Empty;
|
||||
if (string.IsNullOrEmpty(BasePath))
|
||||
{
|
||||
throw new Exception("ParseFullDataAsync - BasePath is not configured in appsettings.json or CsvSettings.SampleFolder");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ParseFullDataAsync()
|
||||
{
|
||||
logger.LogInformation("ParseFullDataAsync - Starting parsing");
|
||||
logger.LogInformation("ParseFullDataAsync - Processing will happen with BasePath: {BasePath}", BasePath);
|
||||
|
||||
// Get the base path from either SampleFolder in csvSettings or the BasePath in configuration
|
||||
var basePath = configuration.GetValue<string>("BasePath");
|
||||
var candidatosDirectory = Path.Combine(BasePath, csvSettings.CandidatosFolder);
|
||||
var bensCandidatosDirectory = Path.Combine(BasePath, csvSettings.BensCandidatosFolder);
|
||||
var redesSociaisDirectory = Path.Combine(BasePath, csvSettings.RedesSociaisFolder);
|
||||
|
||||
if (string.IsNullOrEmpty(basePath))
|
||||
//await ParseFolder(candidatosDirectory, candidatoParserService);
|
||||
await ParseFolder(bensCandidatosDirectory, bemCandidatoParserService);
|
||||
await ParseFolder(redesSociaisDirectory, redeSocialParserService);
|
||||
|
||||
logger.LogInformation("ParseFullDataAsync - Full data parsing completed!");
|
||||
}
|
||||
|
||||
private async Task ParseFolder<CsvObj>(string csvDirectory, CsvParserService<CsvObj> csvParserService)
|
||||
{
|
||||
if (Directory.Exists(csvDirectory))
|
||||
{
|
||||
logger.LogError("ParseFullDataAsync - BasePath is not configured in appsettings.json or CsvSettings.SampleFolder");
|
||||
return;
|
||||
foreach (var filePath in Directory.GetFiles(csvDirectory, "*.csv"))
|
||||
{
|
||||
// Check if filePath contains "fix_" prefix
|
||||
if (filePath.Contains("fix_"))
|
||||
{
|
||||
logger.LogInformation("ParseFolder - Skipping already fixed file: {FilePath}", filePath);
|
||||
continue;
|
||||
}
|
||||
|
||||
logger.LogInformation("ParseFolder - Parsing data from {FilePath}", filePath);
|
||||
await csvParserService.ParseFolderAsync(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
logger.LogInformation("ParseFullDataAsync - Processing will happen with BasePath: {BasePath}", basePath);
|
||||
|
||||
try
|
||||
else
|
||||
{
|
||||
var candidatosDirectory = Path.Combine(basePath, csvSettings.CandidatosFolder);
|
||||
var bensCandidatosDirectory = Path.Combine(basePath, csvSettings.BensCandidatosFolder);
|
||||
var redesSociaisDirectory = Path.Combine(basePath, csvSettings.RedesSociaisFolder);
|
||||
|
||||
if (Directory.Exists(candidatosDirectory))
|
||||
{
|
||||
foreach (var filePath in Directory.GetFiles(candidatosDirectory, "*.csv"))
|
||||
{
|
||||
// Check if filePath contains "fix_" prefix
|
||||
if (filePath.Contains("fix_"))
|
||||
{
|
||||
logger.LogInformation("ParseFullDataAsync - Skipping already fixed file: {FilePath}", filePath);
|
||||
continue;
|
||||
}
|
||||
|
||||
logger.LogInformation("ParseFullDataAsync - Parsing candidatos data from {FilePath}", filePath);
|
||||
await csvParserService.ParseCandidatosAsync(filePath);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogWarning("ParseFullDataAsync - 'Candidatos' directory not found at {Directory}", candidatosDirectory);
|
||||
}
|
||||
|
||||
if (Directory.Exists(bensCandidatosDirectory))
|
||||
{
|
||||
foreach (var filePath in Directory.GetFiles(bensCandidatosDirectory, "*.csv"))
|
||||
{
|
||||
// Check if filePath contains "fix_" prefix
|
||||
if (filePath.Contains("fix_"))
|
||||
{
|
||||
logger.LogInformation("ParseFullDataAsync - Skipping already fixed file: {FilePath}", filePath);
|
||||
continue;
|
||||
}
|
||||
|
||||
logger.LogInformation("ParseFullDataAsync - Parsing bens candidatos data from {FilePath}", filePath);
|
||||
await csvParserService.ParseBensCandidatosAsync(filePath);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogWarning("ParseFullDataAsync - 'Bens candidatos' directory not found at {Directory}", bensCandidatosDirectory);
|
||||
}
|
||||
|
||||
if (Directory.Exists(redesSociaisDirectory))
|
||||
{
|
||||
foreach (var filePath in Directory.GetFiles(redesSociaisDirectory, "*.csv"))
|
||||
{
|
||||
// Check if filePath contains "fix_" prefix
|
||||
if (filePath.Contains("fix_"))
|
||||
{
|
||||
logger.LogInformation("ParseFullDataAsync - Skipping already fixed file: {FilePath}", filePath);
|
||||
continue;
|
||||
}
|
||||
|
||||
logger.LogInformation("ParseFullDataAsync - Parsing redes sociais data from {FilePath}", filePath);
|
||||
await csvParserService.ParseRedeSocialAsync(filePath);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogWarning("ParseFullDataAsync - 'Redes sociais' directory not found at {Directory}", redesSociaisDirectory);
|
||||
}
|
||||
|
||||
logger.LogInformation("ParseFullDataAsync - Full data parsing completed!");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "ParseFullDataAsync - Error parsing full data set");
|
||||
throw;
|
||||
logger.LogWarning("ParseFolder - Directory not found at {Directory}", csvDirectory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,52 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OpenCand.Core.Models;
|
||||
using System.Globalization;
|
||||
using OpenCand.ETL.Contracts;
|
||||
using OpenCand.Parser.Models;
|
||||
using OpenCand.Services;
|
||||
using OpenCand.Parser.Services;
|
||||
|
||||
namespace OpenCand.ETL.Parser.ParserServices
|
||||
{
|
||||
public class BemCandidatoParserService : IParserService<BemCandidatoCSV>
|
||||
{
|
||||
private readonly ILogger<BemCandidatoParserService> logger;
|
||||
private readonly BemCandidatoService bemCandidatoService;
|
||||
|
||||
public BemCandidatoParserService(
|
||||
ILogger<BemCandidatoParserService> logger,
|
||||
BemCandidatoService bemCandidatoService)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.bemCandidatoService = bemCandidatoService;
|
||||
}
|
||||
|
||||
public async Task ParseObject(BemCandidatoCSV record)
|
||||
{
|
||||
// Parse decimal value
|
||||
decimal? valor = null;
|
||||
if (!string.IsNullOrEmpty(record.ValorBemCandidato))
|
||||
{
|
||||
string normalizedValue = record.ValorBemCandidato.Replace(".", "").Replace(",", ".");
|
||||
if (decimal.TryParse(normalizedValue, NumberStyles.Any, CultureInfo.InvariantCulture, out var parsedValue))
|
||||
{
|
||||
valor = parsedValue;
|
||||
}
|
||||
}
|
||||
|
||||
var bemCandidato = new BemCandidato
|
||||
{
|
||||
SqCandidato = record.SequencialCandidato,
|
||||
Ano = record.AnoEleicao,
|
||||
SiglaUF = record.SiglaUF,
|
||||
NomeUE = record.NomeUE,
|
||||
OrdemBem = record.NumeroOrdemBemCandidato,
|
||||
TipoBem = record.DescricaoTipoBemCandidato,
|
||||
Descricao = record.DescricaoBemCandidato,
|
||||
Valor = valor
|
||||
};
|
||||
|
||||
await bemCandidatoService.AddBemCandidatoAsync(bemCandidato);
|
||||
}
|
||||
}
|
||||
}
|
100
OpenCand.ETL/Parser/ParserServices/CandidatoParserService.cs
Normal file
100
OpenCand.ETL/Parser/ParserServices/CandidatoParserService.cs
Normal file
@@ -0,0 +1,100 @@
|
||||
using System.Globalization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OpenCand.Core.Models;
|
||||
using OpenCand.ETL.Contracts;
|
||||
using OpenCand.Parser.Models;
|
||||
using OpenCand.Services;
|
||||
|
||||
namespace OpenCand.ETL.Parser.ParserServices
|
||||
{
|
||||
public class CandidatoParserService : IParserService<CandidatoCSV>
|
||||
{
|
||||
private readonly ILogger<CandidatoParserService> logger;
|
||||
private readonly CandidatoService candidatoService;
|
||||
|
||||
public CandidatoParserService(
|
||||
ILogger<CandidatoParserService> logger,
|
||||
CandidatoService candidatoService)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.candidatoService = candidatoService;
|
||||
}
|
||||
|
||||
public async Task ParseObject(CandidatoCSV record)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(record.CPFCandidato) || record.CPFCandidato.Length <= 3)
|
||||
{
|
||||
record.CPFCandidato = null; // Handle null/empty/whitespace CPF
|
||||
}
|
||||
|
||||
if (record.NomeCandidato == "NÃO DIVULGÁVEL" ||
|
||||
string.IsNullOrEmpty(record.NomeCandidato) ||
|
||||
record.NomeCandidato == "#NULO")
|
||||
{
|
||||
logger.LogCritical($"ParseCandidatosAsync - Candidate with id {record.SequencialCandidato} with invalid name, skipping...");
|
||||
return; // Skip candidates with invalid name
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(record.Apelido) ||
|
||||
record.Apelido.Contains("#NUL") ||
|
||||
record.Apelido.Contains("NULO#") ||
|
||||
record.Apelido.Contains("#NE"))
|
||||
{
|
||||
record.Apelido = null;
|
||||
}
|
||||
|
||||
var candidato = new Candidato
|
||||
{
|
||||
Cpf = record.CPFCandidato,
|
||||
SqCandidato = record.SequencialCandidato,
|
||||
Nome = record.NomeCandidato,
|
||||
Apelido = record.Apelido,
|
||||
Email = record.Email.Contains("@") ? record.Email : null,
|
||||
Sexo = record.Genero,
|
||||
EstadoCivil = record.EstadoCivil,
|
||||
Escolaridade = record.GrauInstrucao,
|
||||
Ocupacao = record.Ocupacao,
|
||||
Eleicoes = new List<CandidatoMapping>()
|
||||
{
|
||||
new CandidatoMapping
|
||||
{
|
||||
Cpf = record.CPFCandidato,
|
||||
Nome = record.NomeCandidato,
|
||||
Apelido = record.Apelido,
|
||||
SqCandidato = record.SequencialCandidato,
|
||||
Ano = record.AnoEleicao,
|
||||
TipoEleicao = record.TipoAbrangencia,
|
||||
NomeUE = record.NomeUE,
|
||||
SiglaUF = record.SiglaUF,
|
||||
Cargo = record.DescricaoCargo,
|
||||
NrCandidato = record.NumeroCandidato,
|
||||
Resultado = record.SituacaoTurno,
|
||||
Partido = new Partido
|
||||
{
|
||||
Sigla = record.SiglaPartido,
|
||||
Nome = record.NomePartido,
|
||||
Numero = record.NumeroPartido,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(record.DataNascimento) &&
|
||||
record.DataNascimento != "#NULO")
|
||||
{
|
||||
if (DateTime.TryParseExact(record.DataNascimento, "dd/MM/yyyy",
|
||||
CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out var dataNascimento))
|
||||
{
|
||||
// Convert to UTC DateTime to work with PostgreSQL timestamp with time zone
|
||||
candidato.DataNascimento = DateTime.SpecifyKind(dataNascimento, DateTimeKind.Utc);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
candidato.DataNascimento = null; // Handle null/empty/whitespace date
|
||||
}
|
||||
|
||||
await candidatoService.AddCandidatoAsync(candidato);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OpenCand.Core.Models;
|
||||
using OpenCand.ETL.Contracts;
|
||||
using OpenCand.Parser.Models;
|
||||
using OpenCand.Parser.Services;
|
||||
using OpenCand.Services;
|
||||
|
||||
namespace OpenCand.ETL.Parser.ParserServices
|
||||
{
|
||||
public class RedeSocialParserService : IParserService<RedeSocialCSV>
|
||||
{
|
||||
private readonly ILogger<RedeSocialParserService> logger;
|
||||
private readonly RedeSocialService redeSocialService;
|
||||
|
||||
public RedeSocialParserService(
|
||||
ILogger<RedeSocialParserService> logger,
|
||||
RedeSocialService redeSocialService)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.redeSocialService = redeSocialService;
|
||||
}
|
||||
|
||||
public async Task ParseObject(RedeSocialCSV record)
|
||||
{
|
||||
var redeSocial = new RedeSocial
|
||||
{
|
||||
SqCandidato = record.SequencialCandidato,
|
||||
Ano = record.DataEleicao,
|
||||
SiglaUF = record.SiglaUF,
|
||||
Link = record.Url,
|
||||
Rede = string.Empty
|
||||
};
|
||||
|
||||
await redeSocialService.AddRedeSocialAsync(redeSocial);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,276 +0,0 @@
|
||||
using System.Globalization;
|
||||
using CsvHelper;
|
||||
using CsvHelper.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OpenCand.Core.Models;
|
||||
using OpenCand.ETL.Parser.CsvMappers;
|
||||
using OpenCand.Parser.CsvMappers;
|
||||
using OpenCand.Parser.Models;
|
||||
using OpenCand.Services;
|
||||
|
||||
namespace OpenCand.Parser.Services
|
||||
{
|
||||
public class CsvParserService
|
||||
{
|
||||
private readonly ILogger<CsvParserService> logger;
|
||||
private readonly CandidatoService candidatoService;
|
||||
private readonly BemCandidatoService bemCandidatoService;
|
||||
private readonly RedeSocialService redeSocialService;
|
||||
private readonly CsvFixerService csvFixerService;
|
||||
|
||||
private readonly CsvConfiguration parserConfig;
|
||||
|
||||
public CsvParserService(
|
||||
ILogger<CsvParserService> logger,
|
||||
CandidatoService candidatoService,
|
||||
BemCandidatoService bemCandidatoService,
|
||||
RedeSocialService redeSocialService,
|
||||
CsvFixerService csvFixerService)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.candidatoService = candidatoService;
|
||||
this.bemCandidatoService = bemCandidatoService;
|
||||
this.redeSocialService = redeSocialService;
|
||||
this.csvFixerService = csvFixerService;
|
||||
|
||||
parserConfig = new CsvConfiguration(CultureInfo.InvariantCulture)
|
||||
{
|
||||
Delimiter = ";",
|
||||
HasHeaderRecord = true,
|
||||
PrepareHeaderForMatch = args => args.Header.ToLower(),
|
||||
MissingFieldFound = null,
|
||||
TrimOptions = TrimOptions.Trim,
|
||||
Encoding = System.Text.Encoding.UTF8
|
||||
};
|
||||
}
|
||||
|
||||
public async Task ParseCandidatosAsync(string filePath)
|
||||
{
|
||||
logger.LogInformation($"ParseCandidatosAsync - Starting to parse 'candidatos' from '{filePath}'");
|
||||
|
||||
filePath = csvFixerService.FixCsvFile(filePath);
|
||||
|
||||
// Fix the CSV file if necessary
|
||||
if (string.IsNullOrEmpty(filePath))
|
||||
{
|
||||
logger.LogError($"ParseCandidatosAsync - Failed to fix CSV file at '{filePath}'");
|
||||
throw new InvalidOperationException($"Failed to fix CSV file at '{filePath}'");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var reader = new StreamReader(filePath);
|
||||
using var csv = new CsvReader(reader, parserConfig);
|
||||
var po = new ParallelOptions
|
||||
{
|
||||
MaxDegreeOfParallelism = 25
|
||||
};
|
||||
|
||||
csv.Context.RegisterClassMap<CandidatoMap>();
|
||||
|
||||
var records = csv.GetRecords<CandidatoCSV>();
|
||||
|
||||
await Parallel.ForEachAsync(records, po, async (record, ct) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(record.CPFCandidato) || record.CPFCandidato.Length <= 3)
|
||||
{
|
||||
record.CPFCandidato = null; // Handle null/empty/whitespace CPF
|
||||
}
|
||||
|
||||
if (record.NomeCandidato == "NÃO DIVULGÁVEL" ||
|
||||
string.IsNullOrEmpty(record.NomeCandidato) ||
|
||||
record.NomeCandidato == "#NULO")
|
||||
{
|
||||
logger.LogCritical($"ParseCandidatosAsync - Candidate with id {record.SequencialCandidato} with invalid name, skipping...");
|
||||
return; // Skip candidates with invalid name
|
||||
}
|
||||
|
||||
var candidato = new Candidato
|
||||
{
|
||||
Cpf = record.CPFCandidato,
|
||||
SqCandidato = record.SequencialCandidato,
|
||||
Nome = record.NomeCandidato,
|
||||
Apelido = record.Apelido,
|
||||
Email = record.Email.Contains("@") ? record.Email : null,
|
||||
Sexo = record.Genero,
|
||||
EstadoCivil = record.EstadoCivil,
|
||||
Escolaridade = record.GrauInstrucao,
|
||||
Ocupacao = record.Ocupacao,
|
||||
Eleicoes = new List<CandidatoMapping>()
|
||||
{
|
||||
new CandidatoMapping
|
||||
{
|
||||
Cpf = record.CPFCandidato,
|
||||
Nome = record.NomeCandidato,
|
||||
Apelido = record.Apelido,
|
||||
SqCandidato = record.SequencialCandidato,
|
||||
Ano = record.AnoEleicao,
|
||||
TipoEleicao = record.TipoAbrangencia,
|
||||
NomeUE = record.NomeUE,
|
||||
SiglaUF = record.SiglaUF,
|
||||
Cargo = record.DescricaoCargo,
|
||||
NrCandidato = record.NumeroCandidato,
|
||||
Resultado = record.SituacaoTurno,
|
||||
Partido = new Partido
|
||||
{
|
||||
Sigla = record.SiglaPartido,
|
||||
Nome = record.NomePartido,
|
||||
Numero = record.NumeroPartido,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(record.DataNascimento) &&
|
||||
record.DataNascimento != "#NULO")
|
||||
{
|
||||
if (DateTime.TryParseExact(record.DataNascimento, "dd/MM/yyyy",
|
||||
CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out var dataNascimento))
|
||||
{
|
||||
// Convert to UTC DateTime to work with PostgreSQL timestamp with time zone
|
||||
candidato.DataNascimento = DateTime.SpecifyKind(dataNascimento, DateTimeKind.Utc);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
candidato.DataNascimento = null; // Handle null/empty/whitespace date
|
||||
}
|
||||
|
||||
await candidatoService.AddCandidatoAsync(candidato);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "ParseCandidatosAsync - Error processing candidate with id {CandidatoId}", record.SequencialCandidato);
|
||||
}
|
||||
});
|
||||
|
||||
logger.LogInformation("ParseCandidatosAsync - Finished parsing candidatos from {FilePath}", filePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "ParseCandidatosAsync - Error parsing candidatos file {FilePath}", filePath);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ParseBensCandidatosAsync(string filePath)
|
||||
{
|
||||
logger.LogInformation($"ParseBensCandidatosAsync - Starting to parse bens candidatos from '{filePath}'");
|
||||
|
||||
filePath = csvFixerService.FixCsvFile(filePath);
|
||||
|
||||
// Fix the CSV file if necessary
|
||||
if (string.IsNullOrEmpty(filePath))
|
||||
{
|
||||
logger.LogError($"ParseBensCandidatosAsync - Failed to fix CSV file at '{filePath}'");
|
||||
throw new InvalidOperationException($"Failed to fix CSV file at '{filePath}'");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var reader = new StreamReader(filePath);
|
||||
using var csv = new CsvReader(reader, parserConfig);
|
||||
csv.Context.RegisterClassMap<BemCandidatoMap>();
|
||||
|
||||
var records = csv.GetRecords<BemCandidatoCSV>();
|
||||
|
||||
foreach (var record in records)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Parse decimal value
|
||||
decimal? valor = null;
|
||||
if (!string.IsNullOrEmpty(record.ValorBemCandidato))
|
||||
{
|
||||
string normalizedValue = record.ValorBemCandidato.Replace(".", "").Replace(",", ".");
|
||||
if (decimal.TryParse(normalizedValue, NumberStyles.Any, CultureInfo.InvariantCulture, out var parsedValue))
|
||||
{
|
||||
valor = parsedValue;
|
||||
}
|
||||
}
|
||||
|
||||
var bemCandidato = new BemCandidato
|
||||
{
|
||||
SqCandidato = record.SequencialCandidato,
|
||||
Ano = record.AnoEleicao,
|
||||
SiglaUF = record.SiglaUF,
|
||||
NomeUE = record.NomeUE,
|
||||
OrdemBem = record.NumeroOrdemBemCandidato,
|
||||
TipoBem = record.DescricaoTipoBemCandidato,
|
||||
Descricao = record.DescricaoBemCandidato,
|
||||
Valor = valor
|
||||
};
|
||||
|
||||
await bemCandidatoService.AddBemCandidatoAsync(bemCandidato);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "ParseBensCandidatosAsync - Error processing bem candidato with id {CandidatoId} and ordem {OrdemBem}",
|
||||
record.SequencialCandidato, record.NumeroOrdemBemCandidato);
|
||||
}
|
||||
}
|
||||
|
||||
logger.LogInformation("ParseBensCandidatosAsync - Finished parsing bens candidatos from {FilePath}", filePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "ParseBensCandidatosAsync - Error parsing bens candidatos file {FilePath}", filePath);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ParseRedeSocialAsync(string filePath)
|
||||
{
|
||||
logger.LogInformation($"ParseRedeSocialAsync - Starting to parse redes sociais from '{filePath}'");
|
||||
|
||||
filePath = csvFixerService.FixCsvFile(filePath);
|
||||
|
||||
// Fix the CSV file if necessary
|
||||
if (string.IsNullOrEmpty(filePath))
|
||||
{
|
||||
logger.LogError($"ParseRedeSocialAsync - Failed to fix CSV file at '{filePath}'");
|
||||
throw new InvalidOperationException($"Failed to fix CSV file at '{filePath}'");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var reader = new StreamReader(filePath);
|
||||
using var csv = new CsvReader(reader, parserConfig);
|
||||
csv.Context.RegisterClassMap<RedeSocialMap>();
|
||||
|
||||
var records = csv.GetRecords<RedeSocialCSV>();
|
||||
|
||||
foreach (var record in records)
|
||||
{
|
||||
try
|
||||
{
|
||||
var redeSocial = new RedeSocial
|
||||
{
|
||||
SqCandidato = record.SequencialCandidato,
|
||||
Ano = record.DataEleicao,
|
||||
SiglaUF = record.SiglaUF,
|
||||
Link = record.Url,
|
||||
Rede = string.Empty
|
||||
};
|
||||
|
||||
await redeSocialService.AddRedeSocialAsync(redeSocial);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "ParseRedeSocialAsync - Error processing redes sociais with id {SequencialCandidato} and link {Url}",
|
||||
record.SequencialCandidato, record.Url);
|
||||
}
|
||||
}
|
||||
|
||||
logger.LogInformation("ParseRedeSocialAsync - Finished parsing redes sociais from {FilePath}", filePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "ParseRedeSocialAsync - Error parsing redes sociais file {FilePath}", filePath);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user