diff --git a/src/RealCV.Infrastructure/Services/TimelineAnalyserService.cs b/src/RealCV.Infrastructure/Services/TimelineAnalyserService.cs index 7482c1a..5a07239 100644 --- a/src/RealCV.Infrastructure/Services/TimelineAnalyserService.cs +++ b/src/RealCV.Infrastructure/Services/TimelineAnalyserService.cs @@ -120,6 +120,15 @@ public sealed class TimelineAnalyserService : ITimelineAnalyserService var earlier = sortedEmployment[i]; var later = sortedEmployment[j]; + // Skip overlaps at the same company (internal promotions/transfers) + if (IsSameCompany(earlier.CompanyName, later.CompanyName)) + { + _logger.LogDebug( + "Ignoring overlap at same company: {Company1} -> {Company2}", + earlier.CompanyName, later.CompanyName); + continue; + } + var overlap = CalculateOverlap(earlier, later); if (overlap is not null && overlap.Value.Months > AllowedOverlapMonths) @@ -143,6 +152,59 @@ public sealed class TimelineAnalyserService : ITimelineAnalyserService return overlaps; } + /// + /// Determines if two company names refer to the same company. + /// Handles variations like "BMW" vs "BMW UK" vs "BMW Group". + /// + private static bool IsSameCompany(string? company1, string? company2) + { + if (string.IsNullOrWhiteSpace(company1) || string.IsNullOrWhiteSpace(company2)) + { + return false; + } + + // Normalize names for comparison + var name1 = NormalizeCompanyName(company1); + var name2 = NormalizeCompanyName(company2); + + // Exact match after normalization + if (name1.Equals(name2, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + // Check if one contains the other (for "BMW" vs "BMW UK" cases) + if (name1.Length >= 3 && name2.Length >= 3) + { + if (name1.StartsWith(name2, StringComparison.OrdinalIgnoreCase) || + name2.StartsWith(name1, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + return false; + } + + private static string NormalizeCompanyName(string name) + { + // Remove common suffixes and normalize + var normalized = name.Trim(); + + string[] suffixes = ["Ltd", "Ltd.", "Limited", "PLC", "Plc", "Inc", "Inc.", + "Corporation", "Corp", "Corp.", "UK", "Group", "(UK)", "& Co", "& Co."]; + + foreach (var suffix in suffixes) + { + if (normalized.EndsWith(" " + suffix, StringComparison.OrdinalIgnoreCase)) + { + normalized = normalized[..^(suffix.Length + 1)].Trim(); + } + } + + return normalized; + } + private static (DateOnly Start, DateOnly End, int Months)? CalculateOverlap( EmploymentEntry earlier, EmploymentEntry later)