258 lines
9.1 KiB
C#
258 lines
9.1 KiB
C#
using MongoDB.Bson;
|
|
using MongoDB.Bson.Serialization.Attributes;
|
|
|
|
namespace QuestionsApi.Models;
|
|
|
|
public static class CreditEconomy
|
|
{
|
|
public const int RollingEarningLimit = 30;
|
|
public static readonly TimeSpan EarningWindow = TimeSpan.FromHours(24);
|
|
public const string ExamTokenSku = "exam_token_1";
|
|
public const string QBankResetTokenSku = "qbank_reset_1";
|
|
public const int ExamTokenPrice = 100;
|
|
public const int QBankResetTokenPrice = 300;
|
|
|
|
public static readonly IReadOnlyList<RzStoreProduct> Products =
|
|
[
|
|
new()
|
|
{
|
|
Sku = ExamTokenSku,
|
|
Name = "Exam Simulation Token",
|
|
Description = "Starts one new full exam simulation.",
|
|
CoinPrice = ExamTokenPrice,
|
|
Grant = RzStoreGrant.ExamToken
|
|
},
|
|
new()
|
|
{
|
|
Sku = QBankResetTokenSku,
|
|
Name = "QBank Reset Token",
|
|
Description = "Makes every QBank question eligible again without erasing progress.",
|
|
CoinPrice = QBankResetTokenPrice,
|
|
Grant = RzStoreGrant.QBankResetToken
|
|
}
|
|
];
|
|
|
|
public static RzStoreProduct? FindProduct(string sku) =>
|
|
Products.FirstOrDefault(value => value.Sku.Equals(sku, StringComparison.Ordinal));
|
|
|
|
public static int FirstCorrectAmount(int difficulty) => difficulty >= 5 ? 3 : difficulty >= 4 ? 2 : 1;
|
|
public static int ReviewAmount(bool delayedRecovery) => delayedRecovery ? 3 : 1;
|
|
}
|
|
|
|
public static class SlotMachineEconomy
|
|
{
|
|
public const int SpinCost = 0; // TODO: restore to 50 once testing is done
|
|
public const int RollingSpinLimit = 200;
|
|
public static readonly TimeSpan SpinWindow = TimeSpan.FromHours(24);
|
|
|
|
public static readonly IReadOnlyList<SpinReward> Rewards =
|
|
[
|
|
new() { Key = "jackpot", Label = "10,000 RZ Coins", Description = "Jackpot!", Grant = RzStoreGrant.Coins, CoinAmount = 10000, Probability = 0.005 },
|
|
new() { Key = "epic", Label = "2,000 RZ Coins", Description = "Epic Reward", Grant = RzStoreGrant.Coins, CoinAmount = 2000, Probability = 0.02 },
|
|
new() { Key = "great", Label = "500 RZ Coins", Description = "Great Reward", Grant = RzStoreGrant.Coins, CoinAmount = 500, Probability = 0.10 },
|
|
new() { Key = "exam_token", Label = "Exam Simulation Token", Description = "Starts one new full exam simulation.", Grant = RzStoreGrant.ExamToken, Probability = 0.15 },
|
|
new() { Key = "qbank_reset", Label = "QBank Reset Token", Description = "Makes every QBank question eligible again.", Grant = RzStoreGrant.QBankResetToken, Probability = 0.20 },
|
|
new() { Key = "nice", Label = "100 RZ Coins", Description = "Nice!", Grant = RzStoreGrant.Coins, CoinAmount = 100, Probability = 0.30 },
|
|
new() { Key = "good_luck", Label = "50 RZ Coins", Description = "Good luck!", Grant = RzStoreGrant.Coins, CoinAmount = 50, Probability = 0.225 },
|
|
];
|
|
|
|
public static SpinReward RollReward(double roll)
|
|
{
|
|
var cumulative = 0.0;
|
|
foreach (var reward in Rewards)
|
|
{
|
|
cumulative += reward.Probability;
|
|
if (roll < cumulative) return reward;
|
|
}
|
|
return Rewards[^1];
|
|
}
|
|
|
|
public static SpinReward? FindReward(string key) =>
|
|
Rewards.FirstOrDefault(value => value.Key.Equals(key, StringComparison.Ordinal));
|
|
}
|
|
|
|
public enum RzStoreGrant { ExamToken, QBankResetToken, Coins }
|
|
|
|
public sealed class SpinReward
|
|
{
|
|
public string Key { get; set; } = "";
|
|
public string Label { get; set; } = "";
|
|
public string Description { get; set; } = "";
|
|
public RzStoreGrant Grant { get; set; }
|
|
public int CoinAmount { get; set; }
|
|
public double Probability { get; set; }
|
|
}
|
|
|
|
public sealed class RzStoreProduct
|
|
{
|
|
public string Sku { get; set; } = "";
|
|
public string Name { get; set; } = "";
|
|
public string Description { get; set; } = "";
|
|
public int CoinPrice { get; set; }
|
|
public RzStoreGrant Grant { get; set; }
|
|
}
|
|
|
|
public sealed class CreditEvent
|
|
{
|
|
public string OperationId { get; set; } = "";
|
|
public int CoinAmount { get; set; } = 1;
|
|
public DateTime CreatedUtc { get; set; }
|
|
}
|
|
|
|
public static class CreditReasonCodes
|
|
{
|
|
public const string FirstCorrect = "qbank_first_correct";
|
|
public const string DifficultCorrect = "qbank_difficult_correct";
|
|
public const string ScheduledReview = "scheduled_review_correct";
|
|
public const string DelayedRecovery = "delayed_recovery";
|
|
public const string MasteryPracticing = "mastery_practicing";
|
|
public const string MasteryStrong = "mastery_strong";
|
|
public const string MasteryMastered = "mastery_mastered";
|
|
public const string ExamSimulationComplete = "exam_simulation_complete";
|
|
public const string SessionGoalComplete = "session_goal_complete";
|
|
public const string DailyMissionComplete = "daily_mission_complete";
|
|
public const string Purchase = "purchase";
|
|
}
|
|
|
|
public sealed class CreditLedgerEntry
|
|
{
|
|
[BsonId]
|
|
public string Id { get; set; } = "";
|
|
public string UserId { get; set; } = "";
|
|
public string OperationId { get; set; } = "";
|
|
public int RequestedAmount { get; set; }
|
|
public int Amount { get; set; }
|
|
public string ReasonCode { get; set; } = "";
|
|
public string Description { get; set; } = "";
|
|
public string SourceType { get; set; } = "";
|
|
public string SourceId { get; set; } = "";
|
|
public bool SubjectToRollingCap { get; set; }
|
|
public string State { get; set; } = "pending";
|
|
public DateTime CreatedUtc { get; set; }
|
|
public DateTime? AppliedUtc { get; set; }
|
|
}
|
|
|
|
public sealed class CreditAward
|
|
{
|
|
public int Amount { get; set; }
|
|
public string ReasonCode { get; set; } = "";
|
|
public string Description { get; set; } = "";
|
|
}
|
|
|
|
public sealed class CreditAwardResult
|
|
{
|
|
public CreditAward? Award { get; set; }
|
|
public CreditStatusResponse Status { get; set; } = new();
|
|
}
|
|
|
|
public sealed class CreditLedgerPage
|
|
{
|
|
public List<CreditLedgerEntryResponse> Items { get; set; } = [];
|
|
public DateTime? NextBefore { get; set; }
|
|
}
|
|
|
|
public sealed class CreditLedgerEntryResponse
|
|
{
|
|
public int Amount { get; set; }
|
|
public string ReasonCode { get; set; } = "";
|
|
public string Description { get; set; } = "";
|
|
public DateTime CreatedUtc { get; set; }
|
|
}
|
|
|
|
public sealed class CreditPurchaseEvent
|
|
{
|
|
public string OperationId { get; set; } = "";
|
|
public string Sku { get; set; } = "";
|
|
public int CoinCost { get; set; }
|
|
public DateTime CreatedUtc { get; set; }
|
|
}
|
|
|
|
public sealed class SpinEvent
|
|
{
|
|
public string OperationId { get; set; } = "";
|
|
public string RewardKey { get; set; } = "";
|
|
public int CoinsWon { get; set; }
|
|
public DateTime CreatedUtc { get; set; }
|
|
}
|
|
|
|
public sealed class UserCreditAccount
|
|
{
|
|
[BsonId]
|
|
[BsonRepresentation(BsonType.ObjectId)]
|
|
public string? Id { get; set; }
|
|
public string UserId { get; set; } = "";
|
|
public int Credits { get; set; }
|
|
public int ExamTokens { get; set; }
|
|
public int QBankResetTokens { get; set; }
|
|
public int BankCycle { get; set; }
|
|
public bool InitialExamGrantApplied { get; set; }
|
|
public bool MasteryAwardBaselineApplied { get; set; }
|
|
public List<CreditEvent> RecentEarnings { get; set; } = [];
|
|
public List<CreditPurchaseEvent> Purchases { get; set; } = [];
|
|
public List<SpinEvent> Spins { get; set; } = [];
|
|
public List<CreditEvent> RecentSpins { get; set; } = [];
|
|
public List<string> AppliedOperations { get; set; } = [];
|
|
public DateTime UpdatedUtc { get; set; } = DateTime.UtcNow;
|
|
}
|
|
|
|
public sealed class CreditStatusResponse
|
|
{
|
|
public int Credits { get; set; }
|
|
public int EarnedInRollingWindow { get; set; }
|
|
public int RemainingEarningCapacity { get; set; }
|
|
public int RollingWindowHours { get; set; } = 24;
|
|
public int ExamTokens { get; set; }
|
|
public int QBankResetTokens { get; set; }
|
|
public int BankCycle { get; set; }
|
|
public IReadOnlyList<RzStoreProduct> Products { get; set; } = CreditEconomy.Products;
|
|
}
|
|
|
|
public sealed record CreditAwardRequest(
|
|
string OperationId,
|
|
int Amount,
|
|
string ReasonCode,
|
|
string Description,
|
|
string SourceType,
|
|
string SourceId,
|
|
bool SubjectToRollingCap = true);
|
|
|
|
public sealed class CreditPurchaseRequest
|
|
{
|
|
public string Sku { get; set; } = "";
|
|
public string OperationId { get; set; } = "";
|
|
}
|
|
|
|
public sealed class QBankResetRequest
|
|
{
|
|
public string Confirmation { get; set; } = "";
|
|
public string OperationId { get; set; } = "";
|
|
}
|
|
|
|
public sealed class SpinRequest
|
|
{
|
|
public string OperationId { get; set; } = "";
|
|
}
|
|
|
|
public enum CreditOperationResult { Succeeded, InsufficientCredits, InsufficientTokens, OperationConflict, RateLimited }
|
|
|
|
public sealed class CreditOperationResponse
|
|
{
|
|
public CreditOperationResult Result { get; set; }
|
|
public CreditStatusResponse Status { get; set; } = new();
|
|
}
|
|
|
|
public sealed class SpinConfigResponse
|
|
{
|
|
public int SpinCost { get; set; } = SlotMachineEconomy.SpinCost;
|
|
public IReadOnlyList<SpinReward> Rewards { get; set; } = SlotMachineEconomy.Rewards;
|
|
}
|
|
|
|
public sealed class SpinResponse
|
|
{
|
|
public CreditOperationResult Result { get; set; }
|
|
public string RewardKey { get; set; } = "";
|
|
public string RewardLabel { get; set; } = "";
|
|
public int CoinsWon { get; set; }
|
|
public CreditStatusResponse Status { get; set; } = new();
|
|
}
|