using System.Text.Json; using JobTrackerApi.Data; using JobTrackerApi.Services.JobImport; using Microsoft.EntityFrameworkCore; namespace JobTrackerApi.Services; public sealed class JobEnrichmentHostedService : BackgroundService { private readonly IServiceProvider _services; private readonly ILogger _logger; private readonly IStartupReadiness _startupReadiness; public JobEnrichmentHostedService(IServiceProvider services, ILogger logger, IStartupReadiness startupReadiness) { _services = services; _logger = logger; _startupReadiness = startupReadiness; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { await _startupReadiness.WaitUntilReadyAsync(stoppingToken); await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken); while (!stoppingToken.IsCancellationRequested) { try { using var scope = _services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); var summarizer = scope.ServiceProvider.GetRequiredService(); var jobs = await db.JobApplications .Where(j => !j.IsDeleted) .Where(j => string.IsNullOrWhiteSpace(j.ShortSummary) || string.IsNullOrWhiteSpace(j.Tags)) .OrderByDescending(j => j.DateApplied) .Take(20) .ToListAsync(stoppingToken); var changed = 0; foreach (var job in jobs) { var sourceText = string.IsNullOrWhiteSpace(job.Description) ? job.Notes : job.Description; if (string.IsNullOrWhiteSpace(job.Tags) && !string.IsNullOrWhiteSpace(sourceText)) { var tags = SkillTagger.Detect(sourceText) .Distinct(StringComparer.OrdinalIgnoreCase) .OrderBy(x => x, StringComparer.OrdinalIgnoreCase) .ToList(); if (tags.Count > 0) { job.Tags = JsonSerializer.Serialize(tags); changed++; } } if (string.IsNullOrWhiteSpace(job.ShortSummary) && !string.IsNullOrWhiteSpace(sourceText)) { try { var shortSummary = await summarizer.SummarizeAsync(sourceText, 160, 60); if (!string.IsNullOrWhiteSpace(shortSummary)) { job.ShortSummary = shortSummary; changed++; } } catch { // Best effort; leave for a later pass. } } } if (changed > 0) { await db.SaveChangesAsync(stoppingToken); _logger.LogInformation("Backfilled tags/summaries for {Count} job fields.", changed); } } catch (Exception ex) { _logger.LogWarning(ex, "Job enrichment background pass failed."); } await Task.Delay(TimeSpan.FromMinutes(10), stoppingToken); } } }