From f49d107061388acff36fb7eddec14eb98cf2c1c8 Mon Sep 17 00:00:00 2001 From: Peter Foster Date: Wed, 21 Jan 2026 17:22:14 +0000 Subject: [PATCH] feat: Add Education Verification section and use neutral language MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add Education Verification section to report UI showing institution verification status, qualifications, and dates - Add differentiated icons for Information flags (career, timeline, management, education types) - Change potentially defamatory language to neutral terms: - "Diploma Mill" → "Not Accredited" - "Suspicious" → "Unrecognised" - Flag descriptions now recommend manual verification rather than making definitive claims about institution legitimacy 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../Jobs/ProcessCVCheckJob.cs | 8 +- .../Services/EducationVerifierService.cs | 4 +- src/RealCV.Web/Components/Pages/Report.razor | 324 +++++++++++++++++- 3 files changed, 328 insertions(+), 8 deletions(-) diff --git a/src/RealCV.Infrastructure/Jobs/ProcessCVCheckJob.cs b/src/RealCV.Infrastructure/Jobs/ProcessCVCheckJob.cs index ee8efe0..f8127e9 100644 --- a/src/RealCV.Infrastructure/Jobs/ProcessCVCheckJob.cs +++ b/src/RealCV.Infrastructure/Jobs/ProcessCVCheckJob.cs @@ -398,8 +398,8 @@ public sealed class ProcessCVCheckJob { Category = FlagCategory.Education.ToString(), Severity = FlagSeverity.Critical.ToString(), - Title = "Diploma Mill Detected", - Description = $"'{edu.ClaimedInstitution}' is a known diploma mill. {edu.VerificationNotes}", + Title = "Unaccredited Institution", + Description = $"'{edu.ClaimedInstitution}' was not found in accredited institutions databases. Manual verification recommended.", ScoreImpact = -DiplomaMillPenalty }); } @@ -413,8 +413,8 @@ public sealed class ProcessCVCheckJob { Category = FlagCategory.Education.ToString(), Severity = FlagSeverity.Warning.ToString(), - Title = "Suspicious Institution", - Description = $"'{edu.ClaimedInstitution}' has suspicious characteristics. {edu.VerificationNotes}", + Title = "Unrecognised Institution", + Description = $"'{edu.ClaimedInstitution}' was not found in recognised institutions databases. Manual verification recommended.", ScoreImpact = -SuspiciousInstitutionPenalty }); } diff --git a/src/RealCV.Infrastructure/Services/EducationVerifierService.cs b/src/RealCV.Infrastructure/Services/EducationVerifierService.cs index 26be4ba..41313ab 100644 --- a/src/RealCV.Infrastructure/Services/EducationVerifierService.cs +++ b/src/RealCV.Infrastructure/Services/EducationVerifierService.cs @@ -24,7 +24,7 @@ public sealed class EducationVerifierService : IEducationVerifierService IsVerified = false, IsDiplomaMill = true, IsSuspicious = true, - VerificationNotes = "Institution is on the diploma mill blacklist", + VerificationNotes = "Institution not found in accredited institutions database", ClaimedStartDate = education.StartDate, ClaimedEndDate = education.EndDate, DatesArePlausible = true, @@ -43,7 +43,7 @@ public sealed class EducationVerifierService : IEducationVerifierService IsVerified = false, IsDiplomaMill = false, IsSuspicious = true, - VerificationNotes = "Institution name contains suspicious patterns common in diploma mills", + VerificationNotes = "Institution not found in recognised institutions database", ClaimedStartDate = education.StartDate, ClaimedEndDate = education.EndDate, DatesArePlausible = true, diff --git a/src/RealCV.Web/Components/Pages/Report.razor b/src/RealCV.Web/Components/Pages/Report.razor index 6fbe87c..63f963e 100644 --- a/src/RealCV.Web/Components/Pages/Report.razor +++ b/src/RealCV.Web/Components/Pages/Report.razor @@ -281,6 +281,100 @@ + + @if (_report.EducationVerifications.Count > 0) + { +
+
+
+ + + + + Education Verification +
+
+
+
+
+
+
Institution
+
Qualification
+
Period
+
Status
+
+ @foreach (var edu in _report.EducationVerifications) + { +
+
+ @if (edu.IsDiplomaMill) + { + + + + + } + else if (edu.IsSuspicious) + { + + + + } + else if (edu.IsVerified) + { + + + + } + else + { + + + + + } +
+
+ @edu.ClaimedInstitution + @if (!string.IsNullOrEmpty(edu.MatchedInstitution) && !edu.ClaimedInstitution.Equals(edu.MatchedInstitution, StringComparison.OrdinalIgnoreCase)) + { + Matched: @edu.MatchedInstitution + } + @if (!string.IsNullOrEmpty(edu.VerificationNotes)) + { + @edu.VerificationNotes + } +
+
+ @if (!string.IsNullOrEmpty(edu.ClaimedQualification) || !string.IsNullOrEmpty(edu.ClaimedSubject)) + { + @(edu.ClaimedQualification ?? "") @(edu.ClaimedSubject ?? "") + } + else + { + + } +
+
+ @if (edu.ClaimedStartDate.HasValue || edu.ClaimedEndDate.HasValue) + { + @(edu.ClaimedStartDate?.ToString("yyyy") ?? "?") – @(edu.ClaimedEndDate?.ToString("yyyy") ?? "?") + } + else + { + + } +
+
+ @GetEducationStatusLabel(edu) +
+
+ } +
+
+
+ } +
@@ -462,8 +556,13 @@ @foreach (var flag in infoFlags) {
- @flag.Title -

@flag.Description

+
+ @GetInfoFlagIcon(flag.Title) +
+ @flag.Title +

@flag.Description

+
+
} } @@ -623,6 +722,13 @@ border-left-color: var(--realcv-primary); } + .info-flag-icon { + flex-shrink: 0; + color: var(--realcv-primary); + opacity: 0.85; + margin-top: 2px; + } + .flag-title { font-weight: 600; margin-bottom: 0.25rem; @@ -745,6 +851,122 @@ font-size: 0.8125rem; } + /* Education List - Compact Row Layout */ + .education-list { + border-top: 1px solid #e5e7eb; + } + + .education-header { + display: grid; + grid-template-columns: 24px 1fr 1fr 100px 90px; + gap: 0.5rem; + padding: 0.5rem 0.75rem; + background-color: #f8fafc; + border-bottom: 1px solid #e5e7eb; + font-size: 0.75rem; + font-weight: 600; + color: #475569; + text-transform: uppercase; + letter-spacing: 0.025em; + align-items: center; + } + + .education-header div { + display: flex; + align-items: center; + } + + .education-header div:nth-child(4), + .education-header div:nth-child(5) { + justify-content: center; + text-align: center; + } + + .education-row { + display: grid; + grid-template-columns: 24px 1fr 1fr 100px 90px; + gap: 0.5rem; + align-items: center; + padding: 0.5rem 0.75rem; + border-bottom: 1px solid #e5e7eb; + transition: background-color 0.15s ease; + } + + .education-row:hover { + background-color: #f9fafb; + } + + .education-row-verified { + border-left: 3px solid #22c55e; + } + + .education-row-unknown { + border-left: 3px solid #f59e0b; + } + + .education-row-suspicious { + border-left: 3px solid #ef4444; + background-color: #fef2f2; + } + + .education-row-diploma-mill { + border-left: 3px solid #dc2626; + background-color: #fef2f2; + } + + .education-status-icon { + display: flex; + align-items: center; + justify-content: center; + } + + .education-institution { + display: flex; + flex-direction: column; + justify-content: center; + gap: 0.125rem; + min-width: 0; + } + + .education-institution-name { + font-weight: 600; + font-size: 0.8125rem; + color: #1f2937; + } + + .education-matched-name { + font-size: 0.75rem; + color: #059669; + font-style: italic; + } + + .education-note-inline { + font-size: 0.75rem; + color: #4b5563; + line-height: 1.4; + } + + .education-qualification { + font-size: 0.8125rem; + color: #4b5563; + } + + .education-dates { + display: flex; + align-items: center; + justify-content: center; + font-size: 0.8125rem; + color: #4b5563; + white-space: nowrap; + text-align: center; + } + + .education-status { + display: flex; + align-items: center; + justify-content: center; + } + /* Mobile Responsiveness */ @@media (max-width: 768px) { .score-header .row { @@ -800,6 +1022,37 @@ .employment-note-inline { display: none; } + + .education-row { + grid-template-columns: 20px 1fr 70px; + gap: 0.25rem 0.5rem; + padding: 0.5rem; + } + + .education-header { + grid-template-columns: 20px 1fr 70px; + } + + .education-header div:nth-child(3), + .education-header div:nth-child(4) { + display: none; + } + + .education-qualification { + display: none; + } + + .education-dates { + display: none; + } + + .education-note-inline { + display: none; + } + + .education-matched-name { + display: none; + } } @@ -955,6 +1208,37 @@ }; } + private static string GetEducationRowClass(EducationVerificationResult edu) + { + if (edu.IsDiplomaMill) return "education-row-diploma-mill"; + if (edu.IsSuspicious) return "education-row-suspicious"; + if (edu.IsVerified) return "education-row-verified"; + return "education-row-unknown"; + } + + private static string GetEducationStatusBadgeClass(EducationVerificationResult edu) + { + return edu.Status switch + { + "Recognised" => "bg-success", + "DiplomaMill" => "bg-danger", + "Suspicious" => "bg-danger", + _ => "bg-warning text-dark" + }; + } + + private static string GetEducationStatusLabel(EducationVerificationResult edu) + { + return edu.Status switch + { + "Recognised" => "Verified", + "DiplomaMill" => "Not Accredited", + "Suspicious" => "Unrecognised", + "Unknown" => "Unverified", + _ => edu.Status + }; + } + private static string FormatFlagTitle(string title) { // Convert variable-style names to readable sentences @@ -972,6 +1256,42 @@ }; } + private static MarkupString GetInfoFlagIcon(string title) + { + var icon = title switch + { + // Career timeline icons + "Career Span" => """""", + "Employment Gap" => """""", + "Concurrent Employment" => """""", + "Average Tenure" or "Frequent Job Changes" => """""", + + // Employment status icons + "Current Status" => """""", + "Long Tenure" => """""", + + // Leadership & experience icons + "Management Experience" => """""", + "Individual Contributor" => """""", + "Director Experience" => """""", + "Public Company Experience" => """""", + + // Company & trajectory icons + "Company Size Pattern" => """""", + + // Education icons + "Unverified Institution" => """""", + + // Default - career trajectory or generic + _ when title.StartsWith("Career Trajectory") => """""", + + // Fallback generic info icon + _ => """""" + }; + + return new MarkupString(icon); + } + // Lookup for first occurrence of each sequential group of the same company (pre-computed when report loads) private HashSet _firstOccurrenceIndices = new();