diff --git a/src/TrueCV.Infrastructure/Jobs/ProcessCVCheckJob.cs b/src/TrueCV.Infrastructure/Jobs/ProcessCVCheckJob.cs index 70c517c..cfc6cf5 100644 --- a/src/TrueCV.Infrastructure/Jobs/ProcessCVCheckJob.cs +++ b/src/TrueCV.Infrastructure/Jobs/ProcessCVCheckJob.cs @@ -261,26 +261,48 @@ public sealed class ProcessCVCheckJob var score = BaseScore; var flags = new List(); - // Penalty for unverified companies - foreach (var verification in verifications.Where(v => !v.IsVerified)) + // Penalty for unverified companies (deduplicated by company name) + var unverifiedByCompany = verifications + .Where(v => !v.IsVerified) + .GroupBy(v => v.ClaimedCompany, StringComparer.OrdinalIgnoreCase) + .ToList(); + + foreach (var companyGroup in unverifiedByCompany) { score -= UnverifiedCompanyPenalty; + var firstInstance = companyGroup.First(); + var instanceCount = companyGroup.Count(); + + var description = instanceCount > 1 + ? $"Could not verify employment at '{firstInstance.ClaimedCompany}' ({instanceCount} roles). {firstInstance.VerificationNotes}" + : $"Could not verify employment at '{firstInstance.ClaimedCompany}'. {firstInstance.VerificationNotes}"; flags.Add(new FlagResult { Category = FlagCategory.Employment.ToString(), Severity = FlagSeverity.Warning.ToString(), Title = "Unverified Company", - Description = $"Could not verify employment at '{verification.ClaimedCompany}'. {verification.VerificationNotes}", + Description = description, ScoreImpact = -UnverifiedCompanyPenalty }); } // Process company verification flags (incorporation date, dissolution, dormant, etc.) + // Deduplicate by (company, flag type) to avoid penalizing same issue multiple times + var processedCompanyFlags = new HashSet<(string Company, string FlagType)>( + new CompanyFlagComparer()); + foreach (var verification in verifications.Where(v => v.Flags.Count > 0)) { foreach (var companyFlag in verification.Flags) { + var key = (verification.ClaimedCompany, companyFlag.Type); + if (!processedCompanyFlags.Add(key)) + { + // Already processed this flag for this company, skip + continue; + } + var penalty = Math.Abs(companyFlag.ScoreImpact); score -= penalty; @@ -660,4 +682,24 @@ public sealed class ProcessCVCheckJob // Level 1: Junior / Entry-level return 1; } + + /// + /// Comparer for deduplicating company flags by (company name, flag type). + /// Uses case-insensitive comparison for company names. + /// + private sealed class CompanyFlagComparer : IEqualityComparer<(string Company, string FlagType)> + { + public bool Equals((string Company, string FlagType) x, (string Company, string FlagType) y) + { + return string.Equals(x.Company, y.Company, StringComparison.OrdinalIgnoreCase) && + string.Equals(x.FlagType, y.FlagType, StringComparison.OrdinalIgnoreCase); + } + + public int GetHashCode((string Company, string FlagType) obj) + { + return HashCode.Combine( + obj.Company?.ToUpperInvariant() ?? "", + obj.FlagType?.ToUpperInvariant() ?? ""); + } + } }