using MongoDB.Driver; namespace Nalu.Jobs; public class NameRepository { private readonly IMongoCollection _nomes; private readonly IMongoCollection _jobRuns; public NameRepository(IMongoDatabase db) { _nomes = db.GetCollection("nomes_br"); _jobRuns = db.GetCollection("job_runs"); } public async Task EnsureIndexesAsync(CancellationToken ct) { var model = new CreateIndexModel( Builders.IndexKeys.Ascending(x => x.Nome), new CreateIndexOptions { Unique = true, Name = "idx_nome_unique" }); await _nomes.Indexes.CreateOneAsync(model, cancellationToken: ct); } /// /// Upserts a single name. /// New doc: inserts all fields. Existing: increments frequencia, merges genero, updates timestamp. /// Returns true if inserted (new), false if updated. /// public async Task UpsertAsync(AggregatedName name, DateTime now, CancellationToken ct) { var nomeDisplay = ToTitleCase(name.Nome); var filter = Builders.Filter.Eq(x => x.Nome, name.Nome); var existing = await _nomes.Find(filter).FirstOrDefaultAsync(ct); UpdateDefinition update; if (existing is null) { update = Builders.Update .SetOnInsert(x => x.Nome, name.Nome) .SetOnInsert(x => x.NomeDisplay, nomeDisplay) .SetOnInsert(x => x.Tipo, "primeiro_nome") .SetOnInsert(x => x.Genero, name.Genero) .SetOnInsert(x => x.Fonte, "ibge_censo") .Inc(x => x.Frequencia, name.Frequencia) .Set(x => x.UltimaAtualizacao, now); } else { var mergedGenero = existing.Genero == name.Genero ? existing.Genero : "N"; update = Builders.Update .Inc(x => x.Frequencia, name.Frequencia) .Set(x => x.Genero, mergedGenero) .Set(x => x.UltimaAtualizacao, now); } var result = await _nomes.UpdateOneAsync(filter, update, new UpdateOptions { IsUpsert = true }, ct); return result.UpsertedId is not null; } public async Task SaveJobRunAsync(JobRun run, CancellationToken ct) => await _jobRuns.InsertOneAsync(run, null, ct); private static string ToTitleCase(string upper) => string.Join(" ", upper.Split(' ') .Select(w => w.Length == 0 ? w : char.ToUpperInvariant(w[0]) + w[1..].ToLowerInvariant())); }