perf(analytics): project minimal columns in GetStats/GetAnalyticsOverview #3

Merged
cesnimda merged 13 commits from perf/wave1-perf into main 2026-07-05 21:18:56 +02:00
Showing only changes of commit 6cb593ab5c - Show all commits
@@ -1750,8 +1750,13 @@ Canonical profile:
{ {
var now = DateTime.Now; var now = DateTime.Now;
// Project to only the columns the stats need instead of materialising full
// JobApplication rows (which drag large Description/TranslatedDescription/
// TailoredCvText/Notes blobs). Aggregation stays in memory over a small
// per-tenant set. Mirrors the projection pattern used by GetTagTrends.
var all = await _db.JobApplications var all = await _db.JobApplications
.AsNoTracking() .AsNoTracking()
.Select(j => new { j.IsDeleted, j.Status, j.DateApplied })
.ToListAsync(cancellationToken); .ToListAsync(cancellationToken);
var active = all.Where(j => !j.IsDeleted).ToList(); var active = all.Where(j => !j.IsDeleted).ToList();
@@ -2668,10 +2673,21 @@ Candidate master CV:
[HttpGet("analytics-overview")] [HttpGet("analytics-overview")]
public async Task<ActionResult<AnalyticsOverviewDto>> GetAnalyticsOverview(CancellationToken cancellationToken) public async Task<ActionResult<AnalyticsOverviewDto>> GetAnalyticsOverview(CancellationToken cancellationToken)
{ {
// Project to only the fields the overview needs instead of Include-ing full
// Company + JobApplication rows (avoids loading large description/CV blobs).
var activeJobs = await _db.JobApplications var activeJobs = await _db.JobApplications
.AsNoTracking() .AsNoTracking()
.Include(j => j.Company)
.Where(j => !j.IsDeleted) .Where(j => !j.IsDeleted)
.Select(j => new
{
j.Status,
j.ResponseReceived,
j.ResponseDate,
j.DateApplied,
j.CompanyId,
CompanyName = j.Company.Name,
CompanySource = j.Company.Source
})
.ToListAsync(cancellationToken); .ToListAsync(cancellationToken);
var funnelMap = new Dictionary<string, int> var funnelMap = new Dictionary<string, int>
@@ -2686,7 +2702,7 @@ Candidate master CV:
var funnel = funnelMap.Select(x => new FunnelStagePoint(x.Key, x.Value)).ToList(); var funnel = funnelMap.Select(x => new FunnelStagePoint(x.Key, x.Value)).ToList();
var responseRateBySource = activeJobs var responseRateBySource = activeJobs
.GroupBy(j => string.IsNullOrWhiteSpace(j.Company?.Source) ? "Unknown source" : j.Company!.Source!.Trim()) .GroupBy(j => string.IsNullOrWhiteSpace(j.CompanySource) ? "Unknown source" : j.CompanySource!.Trim())
.Select(g => new ResponseRatePoint( .Select(g => new ResponseRatePoint(
g.Key, g.Key,
g.Count(), g.Count(),
@@ -2699,7 +2715,7 @@ Candidate master CV:
.ToList(); .ToList();
var topCompanies = activeJobs var topCompanies = activeJobs
.GroupBy(j => new { j.CompanyId, Name = j.Company.Name }) .GroupBy(j => new { j.CompanyId, Name = j.CompanyName })
.Select(g => new CompanyActivityPoint( .Select(g => new CompanyActivityPoint(
g.Key.CompanyId, g.Key.CompanyId,
g.Key.Name, g.Key.Name,