Add a nullable ClassId axis to PackDrawCardWeightEntry. Null means the row applies to any draw (the existing behavior for non-RS packs); 1..8 restricts the row to a specific class draw on a RotationStarterCardPack. Slot rates are class-invariant (verified against per-klan captures), so PackDrawSlotRateEntry is unchanged. PackOpenService.Draw now takes an optional classId parameter. With a class supplied, the card pool is filtered to (ClassId == classId || ClassId == null); without one, class-tagged rows are excluded so a mistaken non-RS call against a mixed table can't leak class-specific cards. PackController.Open lifts the 501 guard on RotationStarterCardPack and plumbs class_id through. Class_id is now required (and validated 1..8) for RS packs and rejected as 400 BadRequest on non-RS packs. The skin overload (target_card_id) remains 501 — out of scope for this work. Seed JSON regenerated end-to-end: 32 RS packs now carry 30,160 class- tagged weight rows; 247 non-RS packs keep their existing ~87k null-class rows. Slot rate fix from the upstream extractor change replaces the ~99.99% conditional-rate sums on 97xxx/93xxx packs with the page's authoritative 1.5/6/25/67.5 distribution. Migration AddPackDrawCardWeightClassId. End-to-end bootstrap verified against fresh Postgres; pack 93025 reads back exactly 122/119/120/120/ 120/121/118/119 weights for classes 1-8. Tests: 3 new PackOpenServiceTests (class filter happy path, disjoint pools, defensive null-class isolation); existing class_id-on-non-RS controller test updated 501 → 400 BadRequest; one stub-importer test corrected to match extractor-authored gacha_detail. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
31 lines
1.2 KiB
C#
31 lines
1.2 KiB
C#
using System.ComponentModel.DataAnnotations.Schema;
|
|
using SVSim.Database.Common;
|
|
using SVSim.Database.Enums;
|
|
|
|
namespace SVSim.Database.Models;
|
|
|
|
/// <summary>
|
|
/// Per-card-rate fact: which card prints in which (pack, slot, tier) at what rate.
|
|
/// RatePct is nullable for rate-less "Guaranteed Leader Card" rows (sampler uses
|
|
/// "uniform over (pool minus owned)" in that case).
|
|
/// ClassId is nullable: null means the row applies to any class draw (the default
|
|
/// for non-rotation-starter packs), an integer 1..8 means the row only applies
|
|
/// when the viewer selects that class. RotationStarterCardPack rows always carry
|
|
/// a non-null ClassId; cards shared across multiple classes are duplicated per
|
|
/// class.
|
|
/// </summary>
|
|
public class PackDrawCardWeightEntry : BaseEntity<long>
|
|
{
|
|
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
|
public override long Id { get; set; }
|
|
|
|
public int PackId { get; set; }
|
|
public DrawSlot Slot { get; set; }
|
|
public DrawTier Tier { get; set; }
|
|
public int? ClassId { get; set; }
|
|
public long CardId { get; set; }
|
|
public double? RatePct { get; set; }
|
|
public bool IsLeader { get; set; }
|
|
public bool IsAltArt { get; set; }
|
|
}
|