MemberTrackRecord.cs 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. using System.ComponentModel.DataAnnotations;
  2. using System.ComponentModel.DataAnnotations.Schema;
  3. namespace Domain.Entities.Members;
  4. /// <summary>
  5. /// 회원 예측 트랙레코드 집계 (D2 M4 예측 채점+트랙레코드, d2 §③).
  6. /// PredictionSettlementService(M4)가 채점 후 회원별로 재집계한다 — 매 실행마다 해당 회원의
  7. /// 전체 non-Pending 예측으로 처음부터 다시 계산하므로 멱등하다.
  8. /// MemberID = PK(FK Member). Predictions=채점 완료 총계(Hits+Misses+Voids), HitRate=Hits/(Hits+Misses)*100(Void 제외).
  9. /// 리더보드는 채점 표본(Hits+Misses) 10+ 회원만 HitRate 내림차순 필터 인덱스로 조회한다.
  10. /// </summary>
  11. public class MemberTrackRecord
  12. {
  13. [ForeignKey(nameof(MemberID))]
  14. public virtual Member Member { get; private set; } = null!;
  15. /// <summary>회원 ID (PK, FK Member)</summary>
  16. [Key]
  17. public int MemberID { get; private set; }
  18. /// <summary>채점 완료 예측 총계 (Hits + Misses + Voids)</summary>
  19. public int Predictions { get; private set; }
  20. /// <summary>적중 수</summary>
  21. public int Hits { get; private set; }
  22. /// <summary>실패 수</summary>
  23. public int Misses { get; private set; }
  24. /// <summary>무효 수 (거래정지·상폐·시세결측 — 적중률 표본 제외)</summary>
  25. public int Voids { get; private set; }
  26. /// <summary>적중률 % = Hits/(Hits+Misses)*100 (Void 제외). 표본 0 이면 0.</summary>
  27. public decimal HitRate { get; private set; }
  28. /// <summary>현재 연속 적중 수 (가장 최근 채점부터의 Hit 연속, Void 무시)</summary>
  29. public int CurrentStreak { get; private set; }
  30. /// <summary>역대 최고 연속 적중 수</summary>
  31. public int BestStreak { get; private set; }
  32. /// <summary>배지 등급 (0=None/1=Bronze/2=Silver/3=Gold/4=GoldAnt)</summary>
  33. public TrackRecordTier Tier { get; private set; }
  34. public DateTime UpdatedAt { get; private set; } = DateTime.UtcNow;
  35. private MemberTrackRecord() { }
  36. /// <summary>빈 트랙레코드 생성 (최초 채점 전). 이후 <see cref="Recompute"/> 로 채운다.</summary>
  37. public static MemberTrackRecord Create(int memberID)
  38. {
  39. ArgumentOutOfRangeException.ThrowIfNegativeOrZero(memberID);
  40. return new MemberTrackRecord { MemberID = memberID };
  41. }
  42. /// <summary>
  43. /// 집계값 일괄 갱신 (M4 채점 배치가 회원 예측 전체를 다시 계산해 호출 — 멱등 재집계).
  44. /// hitRate 는 백분율(0~100), tier 는 (hits+misses, hitRate) 로 산정한 값.
  45. /// </summary>
  46. public void Recompute(int predictions, int hits, int misses, int voids, decimal hitRate, int currentStreak, int bestStreak, TrackRecordTier tier)
  47. {
  48. Predictions = predictions;
  49. Hits = hits;
  50. Misses = misses;
  51. Voids = voids;
  52. HitRate = hitRate;
  53. CurrentStreak = currentStreak;
  54. BestStreak = bestStreak;
  55. Tier = tier;
  56. UpdatedAt = DateTime.UtcNow;
  57. }
  58. }