refactor: Rename TrueCV to RealCV throughout codebase

- Renamed all directories (TrueCV.* -> RealCV.*)
- Renamed all project files (.csproj)
- Renamed solution file (TrueCV.sln -> RealCV.sln)
- Updated all namespaces in C# and Razor files
- Updated project references
- Updated CSS variable names

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-22 20:47:55 +00:00
parent 6f384f8d09
commit 92a3b60878
107 changed files with 693 additions and 554 deletions

View File

@@ -0,0 +1,271 @@
using RealCV.Application.Data;
using RealCV.Application.Interfaces;
using RealCV.Application.Models;
namespace RealCV.Infrastructure.Services;
public sealed 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
var isUnknownInstitution = string.IsNullOrWhiteSpace(institution) ||
institution.Equals("Unknown Institution", StringComparison.OrdinalIgnoreCase) ||
institution.Equals("Unknown", StringComparison.OrdinalIgnoreCase);
return new EducationVerificationResult
{
ClaimedInstitution = institution,
Status = "Unknown",
IsVerified = false,
IsDiplomaMill = false,
IsSuspicious = false,
VerificationNotes = isUnknownInstitution ? null : "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<EducationVerificationResult> VerifyAll(
List<EducationEntry> education,
List<EmploymentEntry>? employment = null)
{
var results = new List<EducationVerificationResult>();
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<EmploymentEntry> 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<EducationVerificationResult> 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}";
}
}