@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 Verification Report - TrueCV
@if (_isLoading) {
Loading...

Loading 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 analyzing 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) {

Verification Report

@_check.OriginalFileName | Generated @_report.GeneratedAt.ToString("dd MMM yyyy HH:mm")

@_report.OverallScore
@_report.ScoreLabel
Veracity Score

@_report.EmploymentVerifications.Count

Employers Checked

@_report.TimelineAnalysis.TotalGapMonths

Months of Gaps

@_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.ScoreImpact pts

@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 reportData = new { CandidateName = _check.OriginalFileName, GeneratedAt = _report.GeneratedAt, OverallScore = _report.OverallScore, ScoreLabel = _report.ScoreLabel, EmploymentVerifications = _report.EmploymentVerifications.Select(v => new { v.ClaimedCompany, ClaimedStartDate = v.ClaimedStartDate?.ToString("yyyy-MM"), ClaimedEndDate = v.ClaimedEndDate?.ToString("yyyy-MM"), v.MatchedCompanyName, v.MatchedCompanyNumber, v.MatchScore, v.IsVerified, v.VerificationNotes }), TimelineAnalysis = new { _report.TimelineAnalysis.TotalGapMonths, _report.TimelineAnalysis.TotalOverlapMonths, Gaps = _report.TimelineAnalysis.Gaps.Select(g => new { StartDate = g.StartDate.ToString("yyyy-MM"), EndDate = g.EndDate.ToString("yyyy-MM"), g.Months }), Overlaps = _report.TimelineAnalysis.Overlaps.Select(o => new { o.Company1, o.Company2, OverlapStart = o.OverlapStart.ToString("yyyy-MM"), OverlapEnd = o.OverlapEnd.ToString("yyyy-MM"), o.Months }) }, Flags = _report.Flags.Select(f => new { f.Severity, f.Title, f.Description, f.ScoreImpact }) }; var json = System.Text.Json.JsonSerializer.Serialize(reportData, new System.Text.Json.JsonSerializerOptions { WriteIndented = true }); var fileName = $"TrueCV_Report_{Path.GetFileNameWithoutExtension(_check.OriginalFileName)}_{DateTime.Now:yyyyMMdd}.json"; await DownloadFileAsync(fileName, json, "application/json"); } catch (Exception ex) { Logger.LogError(ex, "Error downloading report"); } } private async Task DownloadFileAsync(string fileName, string content, string contentType) { var bytes = System.Text.Encoding.UTF8.GetBytes(content); 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 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); } }