feature: Digitization Workstation UI

This commit is contained in:
voltsrage
2026-06-27 12:15:43 +08:00
parent 88e70b3dbe
commit e22d33b654
55 changed files with 6411 additions and 143 deletions
@@ -9,10 +9,12 @@ using Microsoft.EntityFrameworkCore;
public class WorkQueueService : IWorkQueueService
{
private readonly AppDbContext _db;
private readonly ILogger<WorkQueueService> _logger;
public WorkQueueService(AppDbContext db)
public WorkQueueService(AppDbContext db, ILogger<WorkQueueService> logger)
{
_db = db;
_logger = logger;
}
public async Task<WorkQueueResponse> GetVerificationQueueAsync(int page, int pageSize)
@@ -80,4 +82,77 @@ public class WorkQueueService : IWorkQueueService
return new WorkQueueResponse(queueName, items, page, pageSize, totalCount, totalPages);
}
public async Task<WorkQueueOverviewResponse> GetOverviewAsync()
{
var now = DateTimeOffset.UtcNow;
var statusGroups = await _db.DigitizationBatches
.AsNoTracking()
.GroupBy(b => b.Status)
.Select(g => new { Status = g.Key, Count = g.Count() })
.ToListAsync();
var statusCounts = new Dictionary<string, int>();
foreach (var status in Enum.GetValues<BatchStatus>())
{
var count = statusGroups.FirstOrDefault(g => g.Status == status)?.Count ?? 0;
statusCounts[status.ToDbString()] = count;
}
var pendingBatches = await _db.DigitizationBatches
.AsNoTracking()
.Where(b => b.Status == BatchStatus.PendingVerification)
.Select(b => b.UpdatedAt)
.ToListAsync();
double avgTimeInQueueMinutes = 0;
double oldestPendingMinutes = 0;
if (pendingBatches.Count > 0)
{
var ages = pendingBatches
.Select(updatedAt => (now - updatedAt).TotalMinutes)
.ToList();
avgTimeInQueueMinutes = ages.Average();
oldestPendingMinutes = ages.Max();
}
var cutoff = now.AddHours(-24);
var recentEvents = await _db.DigitizationEvents
.AsNoTracking()
.Where(e => e.OccurredAt >= cutoff)
.Where(e => e.EventType == DigitizationEventType.Rejected
|| e.EventType == DigitizationEventType.Verified
|| e.EventType == DigitizationEventType.VerifiedPendingClinical)
.GroupBy(e => e.EventType)
.Select(g => new { EventType = g.Key, Count = g.Count() })
.ToListAsync();
var rejections = recentEvents
.Where(e => e.EventType == DigitizationEventType.Rejected)
.Sum(e => e.Count);
var verifications = recentEvents
.Where(e => e.EventType == DigitizationEventType.Verified
|| e.EventType == DigitizationEventType.VerifiedPendingClinical)
.Sum(e => e.Count);
var totalDecisions = rejections + verifications;
var rejectRate = totalDecisions > 0
? (double)rejections / totalDecisions
: 0.0;
_logger.LogDebug(
"Work queue overview: {PendingCount} pending, avg queue {AvgMinutes:F1}m, reject rate {RejectRate:P1}",
pendingBatches.Count, avgTimeInQueueMinutes, rejectRate);
return new WorkQueueOverviewResponse(
StatusCounts: statusCounts,
AverageTimeInQueueMinutes: Math.Round(avgTimeInQueueMinutes, 1),
RejectRate: Math.Round(rejectRate, 4),
OldestPendingVerificationMinutes: Math.Round(oldestPendingMinutes, 1));
}
}