using TrueCV.Application.Data; using TrueCV.Application.Interfaces; using TrueCV.Application.Models; namespace TrueCV.Infrastructure.Services; public class EducationVerifierService : IEducationVerifierService { private const int MinimumDegreeYears = 1; private const int MaximumDegreeYears = 8; private const int MinimumGraduationAge = 18; public EducationVerificationResult Verify(EducationEntry education) { var institution = education.Institution; // Check for diploma mill first (highest priority flag) if (DiplomaMills.IsDiplomaMill(institution)) { return new EducationVerificationResult { ClaimedInstitution = institution, Status = "DiplomaMill", IsVerified = false, IsDiplomaMill = true, IsSuspicious = true, VerificationNotes = "Institution is on the diploma mill blacklist", ClaimedStartDate = education.StartDate, ClaimedEndDate = education.EndDate, DatesArePlausible = true, ClaimedQualification = education.Qualification, ClaimedSubject = education.Subject }; } // Check for suspicious patterns if (DiplomaMills.HasSuspiciousPattern(institution)) { return new EducationVerificationResult { ClaimedInstitution = institution, Status = "Suspicious", IsVerified = false, IsDiplomaMill = false, IsSuspicious = true, VerificationNotes = "Institution name contains suspicious patterns common in diploma mills", ClaimedStartDate = education.StartDate, ClaimedEndDate = education.EndDate, DatesArePlausible = true, ClaimedQualification = education.Qualification, ClaimedSubject = education.Subject }; } // Check if it's a recognised UK institution var officialName = UKInstitutions.GetOfficialName(institution); if (officialName != null) { var (datesPlausible, dateNotes) = CheckDatePlausibility(education.StartDate, education.EndDate); return new EducationVerificationResult { ClaimedInstitution = institution, MatchedInstitution = officialName, Status = "Recognised", IsVerified = true, IsDiplomaMill = false, IsSuspicious = false, VerificationNotes = institution.Equals(officialName, StringComparison.OrdinalIgnoreCase) ? "Verified UK higher education institution" : $"Matched to official name: {officialName}", ClaimedStartDate = education.StartDate, ClaimedEndDate = education.EndDate, DatesArePlausible = datesPlausible, DatePlausibilityNotes = dateNotes, ClaimedQualification = education.Qualification, ClaimedSubject = education.Subject }; } // Not in our database - could be international or unrecognised return new EducationVerificationResult { ClaimedInstitution = institution, Status = "Unknown", IsVerified = false, IsDiplomaMill = false, IsSuspicious = false, VerificationNotes = "Institution not found in UK recognised institutions database. May be an international institution.", ClaimedStartDate = education.StartDate, ClaimedEndDate = education.EndDate, DatesArePlausible = true, ClaimedQualification = education.Qualification, ClaimedSubject = education.Subject }; } public List VerifyAll( List education, List? employment = null) { var results = new List(); foreach (var edu in education) { var result = Verify(edu); // If we have employment data, check for timeline issues if (employment?.Count > 0 && result.ClaimedEndDate.HasValue) { var (timelinePlausible, timelineNotes) = CheckEducationEmploymentTimeline( result.ClaimedEndDate.Value, employment); if (!timelinePlausible) { result = result with { DatesArePlausible = false, DatePlausibilityNotes = CombineNotes(result.DatePlausibilityNotes, timelineNotes) }; } } results.Add(result); } // Check for overlapping education periods CheckOverlappingEducation(results); return results; } private static (bool isPlausible, string? notes) CheckDatePlausibility(DateOnly? startDate, DateOnly? endDate) { if (!startDate.HasValue || !endDate.HasValue) { return (true, null); } var start = startDate.Value; var end = endDate.Value; // End date should be after start date if (end <= start) { return (false, "End date is before or equal to start date"); } // Check course duration is reasonable var years = (end.ToDateTime(TimeOnly.MinValue) - start.ToDateTime(TimeOnly.MinValue)).TotalDays / 365.25; if (years < MinimumDegreeYears) { return (false, $"Course duration ({years:F1} years) is unusually short for a degree"); } if (years > MaximumDegreeYears) { return (false, $"Course duration ({years:F1} years) is unusually long"); } // Check if graduation date is in the future if (end > DateOnly.FromDateTime(DateTime.UtcNow)) { return (true, "Graduation date is in the future - possibly currently studying"); } return (true, null); } private static (bool isPlausible, string? notes) CheckEducationEmploymentTimeline( DateOnly graduationDate, List employment) { // Find the earliest employment start date var earliestEmployment = employment .Where(e => e.StartDate.HasValue) .OrderBy(e => e.StartDate) .FirstOrDefault(); if (earliestEmployment?.StartDate == null) { return (true, null); } var employmentStart = earliestEmployment.StartDate.Value; // If someone claims to have started full-time work significantly before graduating, // that's suspicious (unless it's clearly an internship/part-time role) var monthsBeforeGraduation = (graduationDate.ToDateTime(TimeOnly.MinValue) - employmentStart.ToDateTime(TimeOnly.MinValue)).TotalDays / 30; if (monthsBeforeGraduation > 24) // More than 2 years before graduation { var isLikelyInternship = earliestEmployment.JobTitle.Contains("intern", StringComparison.OrdinalIgnoreCase) || earliestEmployment.JobTitle.Contains("placement", StringComparison.OrdinalIgnoreCase) || earliestEmployment.JobTitle.Contains("trainee", StringComparison.OrdinalIgnoreCase); if (!isLikelyInternship) { return (false, $"Employment at {earliestEmployment.CompanyName} started {monthsBeforeGraduation:F0} months before claimed graduation"); } } return (true, null); } private static void CheckOverlappingEducation(List results) { var datedResults = results .Where(r => r.ClaimedStartDate.HasValue && r.ClaimedEndDate.HasValue) .ToList(); for (var i = 0; i < datedResults.Count; i++) { for (var j = i + 1; j < datedResults.Count; j++) { var edu1 = datedResults[i]; var edu2 = datedResults[j]; if (PeriodsOverlap( edu1.ClaimedStartDate!.Value, edu1.ClaimedEndDate!.Value, edu2.ClaimedStartDate!.Value, edu2.ClaimedEndDate!.Value)) { // Find the actual index in the original results list var idx1 = results.IndexOf(edu1); var idx2 = results.IndexOf(edu2); if (idx1 >= 0) { results[idx1] = edu1 with { DatePlausibilityNotes = CombineNotes( edu1.DatePlausibilityNotes, $"Overlaps with education at {edu2.ClaimedInstitution}") }; } if (idx2 >= 0) { results[idx2] = edu2 with { DatePlausibilityNotes = CombineNotes( edu2.DatePlausibilityNotes, $"Overlaps with education at {edu1.ClaimedInstitution}") }; } } } } } private static bool PeriodsOverlap(DateOnly start1, DateOnly end1, DateOnly start2, DateOnly end2) { return start1 < end2 && start2 < end1; } private static string? CombineNotes(string? existing, string? additional) { if (string.IsNullOrEmpty(additional)) return existing; if (string.IsNullOrEmpty(existing)) return additional; return $"{existing}; {additional}"; } }