@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 PdfReportService PdfReportService Verification Report - TrueCV
@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) {
@_report.OverallScore /100
@_report.ScoreLabel
Veracity Score

@_report.EmploymentVerifications.Count

Employers Checked

@_report.TimelineAnalysis.TotalGapMonths

Gap Months

@_report.Flags.Count

Flags Raised
Employment Verification
@for (int i = 0; i < _report.EmploymentVerifications.Count; i++) { var verification = _report.EmploymentVerifications[i]; var index = i; @if (!string.IsNullOrEmpty(verification.VerificationNotes)) { } }
Claimed Employer Period Matched Company Match Score Status Points
@verification.ClaimedCompany @if (verification.ClaimedStartDate.HasValue) { @verification.ClaimedStartDate.Value.ToString("MMM yyyy") - @if (verification.ClaimedEndDate.HasValue) { @verification.ClaimedEndDate.Value.ToString("MMM yyyy") } else { Present } } else { Not specified } @if (!string.IsNullOrEmpty(verification.MatchedCompanyName)) { @verification.MatchedCompanyName @if (!string.IsNullOrEmpty(verification.MatchedCompanyNumber)) {
@verification.MatchedCompanyNumber } } else { No match found }
@verification.MatchScore% @if (verification.IsVerified) { Verified } else { Unverified } @{ var companyPoints = GetPointsForCompany(verification.ClaimedCompany, verification.MatchedCompanyName, index); } @if (companyPoints < 0) { @companyPoints } else { 0 }
@verification.VerificationNotes
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

} }
} }
@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 = $"TrueCV_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 GetScoreHeaderClass(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 company (pre-computed when report loads) private HashSet _firstOccurrenceIndices = new(); private void ComputeFirstOccurrences() { _firstOccurrenceIndices.Clear(); if (_report?.EmploymentVerifications is null) return; var seenCompanies = new HashSet(StringComparer.OrdinalIgnoreCase); for (int i = 0; i < _report.EmploymentVerifications.Count; i++) { var company = _report.EmploymentVerifications[i].ClaimedCompany; if (seenCompanies.Add(company)) { _firstOccurrenceIndices.Add(i); } } } private int GetPointsForCompany(string claimedCompany, string? matchedCompany, int index) { if (_report?.Flags is null) return 0; // Only show points for the first occurrence of each company if (!_firstOccurrenceIndices.Contains(index)) { return 0; } // Sum up all flags that mention this company in their description 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(); return companyFlags.Sum(f => f.ScoreImpact); } }