@page "/report/{Id:guid}" @attribute [Authorize] @rendermode InteractiveServer @implements IDisposable @inject ICVCheckService CVCheckService @inject NavigationManager NavigationManager @inject AuthenticationStateProvider AuthenticationStateProvider @inject ILogger Logger @inject IJSRuntime JSRuntime @inject IAuditService AuditService @inject IPdfReportService PdfReportService Verification Report - RealCV
@if (_isLoading) {
Loading report...

Loading verification report...

} else if (_errorMessage is not null) { Back to Dashboard } else if (_check is not null && _check.Status != "Completed") {
@if (_check.Status == "Processing") {
Processing...

Processing Your CV

Our AI is analysing the document. This usually takes 1-2 minutes.

} else if (_check.Status == "Pending") {

Queued for Processing

Your CV is in the queue and will be processed shortly.

} else if (_check.Status == "Failed") {

Processing Failed

We encountered an error processing your CV. Please try uploading again.

}

File: @_check.OriginalFileName
Uploaded: @_check.CreatedAt.ToString("dd MMM yyyy HH:mm")

} else if (_report is not null && _check is not null) {
@{ var circumference = 339.292; var progressLength = circumference * _report.OverallScore / 100; var gapLength = circumference - progressLength; } @if (_report.OverallScore > 0) { }
@_report.OverallScore /100
RealCV Score
@_report.EmploymentVerifications.Count
Employers Checked
@_report.TimelineAnalysis.TotalGapMonths
Gap Months
@_report.Flags.Count
Flags Raised
Employment Verification
Employer
Period
Match
Pts
@for (int i = 0; i < _report.EmploymentVerifications.Count; i++) { var verification = _report.EmploymentVerifications[i]; var index = i; var companyPoints = GetPointsForCompany(verification.ClaimedCompany, verification.MatchedCompanyName, index);
@if (verification.IsVerified) { } else { }
@verification.ClaimedCompany @if (!string.IsNullOrEmpty(verification.VerificationNotes)) { @verification.VerificationNotes }
@if (verification.ClaimedStartDate.HasValue) { @verification.ClaimedStartDate.Value.ToString("MMM yyyy") – @(verification.ClaimedEndDate?.ToString("MMM yyyy") ?? "Present") } else { }
@verification.MatchScore%
@if (companyPoints < 0) { @companyPoints } else if (!verification.IsVerified && !_firstOccurrenceIndices.Contains(index)) { } else { 0 }
}
Employment Gaps
@if (_report.TimelineAnalysis.Gaps.Count > 0) {
    @foreach (var gap in _report.TimelineAnalysis.Gaps) {
  • @gap.StartDate.ToString("MMM yyyy") - @gap.EndDate.ToString("MMM yyyy") @gap.Months months
  • }
Total gap time: @_report.TimelineAnalysis.TotalGapMonths months
} else {

No significant gaps detected

}
Timeline Overlaps
@if (_report.TimelineAnalysis.Overlaps.Count > 0) {
    @foreach (var overlap in _report.TimelineAnalysis.Overlaps) {
  • @overlap.Company1 & @overlap.Company2
    @overlap.Months months
    @overlap.OverlapStart.ToString("MMM yyyy") - @overlap.OverlapEnd.ToString("MMM yyyy")
  • }
Total overlap time: @_report.TimelineAnalysis.TotalOverlapMonths months
} else {

No overlapping positions detected

}
@if (_report.Flags.Count > 0) {
Flags Raised
@{ // Deduplicate flags and fix any variable-style names var uniqueFlags = _report.Flags .GroupBy(f => (f.Title, f.Description)) .Select(g => g.First()) .Select(f => new { f.Severity, Title = FormatFlagTitle(f.Title), f.Description, ScoreImpact = Math.Abs(f.ScoreImpact) }) .ToList(); var criticalFlags = uniqueFlags.Where(f => f.Severity == "Critical").ToList(); var warningFlags = uniqueFlags.Where(f => f.Severity == "Warning").ToList(); var infoFlags = uniqueFlags.Where(f => f.Severity == "Info").ToList(); } @if (criticalFlags.Count > 0) {
Critical Issues
@foreach (var flag in criticalFlags) {
@flag.Title -@flag.ScoreImpact pts

@flag.Description

} } @if (warningFlags.Count > 0) {
Warnings
@foreach (var flag in warningFlags) {
@flag.Title -@flag.ScoreImpact pts

@flag.Description

} } @if (infoFlags.Count > 0) {
Information
@foreach (var flag in infoFlags) {
@flag.Title

@flag.Description

} }
}
Important Information

This report is for informational purposes only. The verification results are based on publicly available data from Companies House and other official sources. This analysis should be used as one input among many in your hiring decision-making process.

Limitations: This automated verification cannot confirm whether a specific individual actually worked at a verified company, only that the company exists and was active during the claimed employment period. Education verification is based on institutional recognition status only.

Not a substitute for thorough background checks: We recommend supplementing this report with direct reference checks, qualification verification with issuing institutions, and other appropriate due diligence measures.

Candidate rights: Data subjects have the right to request access to, correction of, or deletion of their personal data. For enquiries, please contact us via our website.

}
@code { [Parameter] public Guid Id { get; set; } private CVCheckDto? _check; private VeracityReport? _report; private bool _isLoading = true; private string? _errorMessage; private Guid _userId; private System.Threading.Timer? _pollingTimer; private bool _isPolling; protected override async Task OnInitializedAsync() { await LoadData(); StartPollingIfNeeded(); } private void StartPollingIfNeeded() { if (_check is not null && (_check.Status == "Processing" || _check.Status == "Pending") && !_isPolling) { _isPolling = true; _pollingTimer = new System.Threading.Timer(async _ => { await InvokeAsync(async () => { await LoadData(); // Stop polling if processing is complete or failed if (_check is null || _check.Status == "Completed" || _check.Status == "Failed") { StopPolling(); } StateHasChanged(); }); }, null, TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(3)); } } private void StopPolling() { _isPolling = false; _pollingTimer?.Dispose(); _pollingTimer = null; } public void Dispose() { StopPolling(); } private async Task LoadData() { _isLoading = true; _errorMessage = null; try { var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); var userIdClaim = authState.User.FindFirst(ClaimTypes.NameIdentifier)?.Value; if (string.IsNullOrEmpty(userIdClaim) || !Guid.TryParse(userIdClaim, out _userId)) { _errorMessage = "Unable to identify user. Please log in again."; return; } _check = await CVCheckService.GetCheckForUserAsync(Id, _userId); if (_check is null) { _errorMessage = "Report not found or you don't have access to view it."; return; } if (_check.Status == "Completed") { _report = await CVCheckService.GetReportAsync(Id, _userId); if (_report is null) { _errorMessage = "Unable to load the report data."; } else { ComputeFirstOccurrences(); // Pre-compute which companies are first occurrences await AuditService.LogAsync(_userId, AuditActions.ReportViewed, "CVCheck", Id, $"Score: {_report.OverallScore}"); } } } catch (Exception ex) { Logger.LogError(ex, "Error loading report data"); _errorMessage = "An error occurred while loading the report. Please try again."; } finally { _isLoading = false; } } private async Task RefreshStatus() { await LoadData(); } private async Task DownloadReport() { if (_report is null || _check is null) return; try { var candidateName = Path.GetFileNameWithoutExtension(_check.OriginalFileName); var pdfBytes = PdfReportService.GenerateSingleReport(candidateName, _report); var fileName = $"RealCV_Report_{candidateName}_{DateTime.Now:yyyyMMdd}.pdf"; await DownloadFileAsync(fileName, pdfBytes, "application/pdf"); } catch (Exception ex) { Logger.LogError(ex, "Error downloading report"); } } private async Task DownloadFileAsync(string fileName, byte[] bytes, string contentType) { var base64 = Convert.ToBase64String(bytes); await JSRuntime.InvokeVoidAsync("downloadFile", fileName, base64, contentType); } private static string GetScoreColorClass(int score) { return score switch { > 70 => "score-high", >= 50 => "score-medium", _ => "score-low" }; } private static string GetMatchScoreBadgeClass(int score) { return score switch { >= 80 => "bg-success", >= 50 => "bg-warning text-dark", _ => "bg-danger" }; } private static string FormatFlagTitle(string title) { // Convert variable-style names to readable sentences return title switch { "UnverifiedDirectorClaim" => "Unverified Director Claim", "EmploymentBeforeIncorporation" => "Employment Before Company Existed", "EmploymentAtDissolvedCompany" => "Employment at Dissolved Company", "CurrentEmploymentAtDissolvedCompany" => "Current Employment at Dissolved Company", "EmploymentAtDormantCompany" => "Employment at Dormant Company", "SeniorRoleAtMicroCompany" => "Senior Role at Micro Company", "SicCodeMismatch" => "Role/Industry Mismatch", "ImplausibleJobTitle" => "Implausible Job Title", _ => title }; } // Lookup for first occurrence of each sequential group of the same company (pre-computed when report loads) private HashSet _firstOccurrenceIndices = new(); private void ComputeFirstOccurrences() { _firstOccurrenceIndices.Clear(); if (_report?.EmploymentVerifications is null) return; // Only mark as duplicate if the PREVIOUS entry (sequential) is the same company // Non-sequential entries of the same company should each count separately for (int i = 0; i < _report.EmploymentVerifications.Count; i++) { var currentCompany = _report.EmploymentVerifications[i].ClaimedCompany; if (i == 0) { // First entry is always a first occurrence _firstOccurrenceIndices.Add(i); } else { var previousCompany = _report.EmploymentVerifications[i - 1].ClaimedCompany; // Only skip if same company as immediately preceding entry if (!string.Equals(currentCompany, previousCompany, StringComparison.OrdinalIgnoreCase)) { _firstOccurrenceIndices.Add(i); } } } } private int GetPointsForCompany(string claimedCompany, string? matchedCompany, int index) { if (_report?.Flags is null || _report?.EmploymentVerifications is null) return 0; // Only show points for the first occurrence of each company if (!_firstOccurrenceIndices.Contains(index)) { return 0; } var totalPoints = 0; // Get the verification result for this company var verification = _report.EmploymentVerifications.ElementAtOrDefault(index); // Check if there's an "Unverified Company" flag for this company // Search by both title pattern and description containing company name var companyFlags = _report.Flags .Where(f => f.ScoreImpact < 0 && ((!string.IsNullOrEmpty(f.Description) && f.Description.Contains(claimedCompany, StringComparison.OrdinalIgnoreCase)) || (!string.IsNullOrEmpty(matchedCompany) && !string.IsNullOrEmpty(f.Description) && f.Description.Contains(matchedCompany, StringComparison.OrdinalIgnoreCase)))) .GroupBy(f => (f.Title, f.Description)) .Select(g => g.First()) .ToList(); totalPoints = companyFlags.Sum(f => f.ScoreImpact); // If company is unverified but no flag was found in description search, // check if there's a generic "Unverified Company" flag that might use different wording if (verification != null && !verification.IsVerified && totalPoints == 0) { // Look for any Unverified Company flag that might apply var unverifiedFlag = _report.Flags .FirstOrDefault(f => f.Title == "Unverified Company" && f.ScoreImpact < 0 && !string.IsNullOrEmpty(f.Description) && f.Description.Contains(claimedCompany, StringComparison.OrdinalIgnoreCase)); if (unverifiedFlag != null) { totalPoints = unverifiedFlag.ScoreImpact; } else { // Company is unverified but no specific flag found - show the standard penalty totalPoints = -10; } } return totalPoints; } }