Handler.cs 2.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
  1. using Application.Abstractions.Data;
  2. using Application.Abstractions.Messaging;
  3. using Domain.Entities.Members;
  4. using Microsoft.EntityFrameworkCore;
  5. namespace Application.Features.Api.TrackRecord.GetLeaderboard;
  6. /// <summary>
  7. /// 예측 적중률 리더보드 조회 (D2 M4). 필터 인덱스(IX_MemberTrackRecord_HitRate WHERE Predictions>=10)를 활용해
  8. /// 채점 표본(Hits+Misses) 10+ 회원을 HitRate 내림차순으로 페이징한다. Redis 캐시는 v1 미사용(필터 인덱스로 충분).
  9. /// </summary>
  10. internal sealed class Handler(IAppDbContext db) : IQueryHandler<Query, Response>
  11. {
  12. private const int PageSize = 50;
  13. /// <summary>리더보드 게이트 — 채점 표본(Hits+Misses) 최소치 (d2 §③ 표본 10+).</summary>
  14. private const int MinSample = TrackRecordTiers.MinSample;
  15. public async Task<Response> Handle(Query request, CancellationToken ct)
  16. {
  17. var page = request.Page < 1 ? 1 : request.Page;
  18. // 표본(채점 완료) = Hits + Misses. 필터 인덱스는 Predictions>=10 이지만 Void 가 껴 실제 표본이 10 미만일 수
  19. // 있어 Hits+Misses>=10 을 명시적으로 재확인한다(정확성 우선, 인덱스는 후보 축소).
  20. var query = db.MemberTrackRecord.AsNoTracking().Where(c => c.Hits + c.Misses >= MinSample);
  21. var total = await query.CountAsync(ct);
  22. var rows = await query
  23. .OrderByDescending(c => c.HitRate).ThenByDescending(c => c.Hits + c.Misses).ThenBy(c => c.MemberID)
  24. .Skip((page - 1) * PageSize)
  25. .Take(PageSize)
  26. .Select(c => new
  27. {
  28. c.Member.SID,
  29. c.Member.Name,
  30. c.Tier,
  31. c.HitRate,
  32. c.Predictions,
  33. c.Hits,
  34. c.Misses,
  35. c.BestStreak
  36. })
  37. .ToListAsync(ct);
  38. var baseRank = (page - 1) * PageSize;
  39. return new Response
  40. {
  41. Total = total,
  42. Period = "all",
  43. List = [..rows.Select((c, i) => new Response.Row
  44. {
  45. Rank = baseRank + i + 1,
  46. SID = c.SID,
  47. Name = c.Name ?? string.Empty,
  48. Tier = c.Tier.ToString(),
  49. TierCode = (byte)c.Tier,
  50. HitRate = c.HitRate,
  51. Predictions = c.Predictions,
  52. Hits = c.Hits,
  53. Misses = c.Misses,
  54. BestStreak = c.BestStreak
  55. })]
  56. };
  57. }
  58. }