namespace Fragalytics.Web.Models; public static class DemoStatuses { public const string Uploaded = "uploaded"; public const string Queued = "queued"; public const string Processing = "processing"; public const string Parsed = "parsed"; public const string Failed = "failed"; } public static class ClipJobStatuses { public const string Pending = "pending"; public const string Rendering = "rendering"; public const string Rendered = "rendered"; public const string Failed = "failed"; } public static class EventTypes { public const string Kill = "kill"; } public static class ClipMediaFormats { public const string Mp4Extension = ".mp4"; public const string WebMExtension = ".webm"; public const string Mp4ContentType = "video/mp4"; public const string WebMContentType = "video/webm"; public static readonly HashSet AllowedExtensions = new(StringComparer.OrdinalIgnoreCase) { Mp4Extension, WebMExtension }; public static string InferContentType(string extension) { return extension.Equals(WebMExtension, StringComparison.OrdinalIgnoreCase) ? WebMContentType : Mp4ContentType; } public static string GetSuggestedFileExtension(string outputFormat) { return outputFormat.Equals("webm", StringComparison.OrdinalIgnoreCase) ? WebMExtension : Mp4Extension; } } public sealed class AppState { public int NextDemoId { get; set; } = 1; public int NextMatchId { get; set; } = 1; public int NextPlayerId { get; set; } = 1; public int NextRoundId { get; set; } = 1; public int NextEventId { get; set; } = 1; public int NextHighlightId { get; set; } = 1; public int NextClipJobId { get; set; } = 1; public int NextClipAssetId { get; set; } = 1; public int NextScoringProfileId { get; set; } = 1; public List Demos { get; set; } = []; public List Matches { get; set; } = []; public List Players { get; set; } = []; public List Rounds { get; set; } = []; public List Events { get; set; } = []; public List Highlights { get; set; } = []; public List ClipJobs { get; set; } = []; public List ClipAssets { get; set; } = []; public List ScoringProfiles { get; set; } = []; } public sealed class DemoRecord { public int Id { get; set; } public string OriginalFileName { get; set; } = string.Empty; public string StoredPath { get; set; } = string.Empty; public string ParserBackend { get; set; } = string.Empty; public string Status { get; set; } = DemoStatuses.Uploaded; public string? ErrorMessage { get; set; } public DateTime UploadedAtUtc { get; set; } = DateTime.UtcNow; public DateTime? ProcessedAtUtc { get; set; } public int? MatchId { get; set; } } public sealed class MatchRecord { public int Id { get; set; } public int DemoId { get; set; } public string? MapName { get; set; } public string? ServerName { get; set; } public int? TickRate { get; set; } public int TotalRounds { get; set; } public string? TeamCtName { get; set; } public string? TeamTName { get; set; } public DateTime ParsedAtUtc { get; set; } = DateTime.UtcNow; } public sealed class PlayerRecord { public int Id { get; set; } public int MatchId { get; set; } public string SteamId { get; set; } = string.Empty; public string Name { get; set; } = string.Empty; public string? TeamName { get; set; } public string? Side { get; set; } } public sealed class RoundRecord { public int Id { get; set; } public int MatchId { get; set; } public int Number { get; set; } public int? StartTick { get; set; } public int? EndTick { get; set; } public string? WinnerSide { get; set; } public string? WinnerTeamName { get; set; } public string? Reason { get; set; } } public sealed class EventRecord { public int Id { get; set; } public int MatchId { get; set; } public int? RoundId { get; set; } public string EventType { get; set; } = EventTypes.Kill; public int Tick { get; set; } public double? Second { get; set; } public int? ActorPlayerId { get; set; } public int? TargetPlayerId { get; set; } public string? Weapon { get; set; } public bool IsHeadshot { get; set; } public bool IsWallbang { get; set; } public bool IsSmokeKill { get; set; } public int? KillerAliveCount { get; set; } public int? EnemyAliveCount { get; set; } public string? KillerNameSnapshot { get; set; } public string? VictimNameSnapshot { get; set; } } public sealed class HighlightRecord { public int Id { get; set; } public int MatchId { get; set; } public int? RoundId { get; set; } public int SourceEventId { get; set; } public double Score { get; set; } public List Tags { get; set; } = []; public List Explanation { get; set; } = []; public string ScoringVersion { get; set; } = string.Empty; public int StartTick { get; set; } public int EndTick { get; set; } public DateTime CreatedAtUtc { get; set; } = DateTime.UtcNow; } public sealed class ScoreExplanationRecord { public string Reason { get; set; } = string.Empty; public double Value { get; set; } public string Detail { get; set; } = string.Empty; } public sealed class ClipJobRecord { public int Id { get; set; } public int HighlightId { get; set; } public string Renderer { get; set; } = RenderBackendNames.FutureRenderer; public string Status { get; set; } = ClipJobStatuses.Pending; public int StartTick { get; set; } public int EndTick { get; set; } public Dictionary Payload { get; set; } = []; public DateTime CreatedAtUtc { get; set; } = DateTime.UtcNow; public int? ClipAssetId { get; set; } public int AttemptCount { get; set; } public int MaxAttempts { get; set; } = 2; public string? ErrorMessage { get; set; } public DateTime? LastAttemptedAtUtc { get; set; } public DateTime? RetryAfterUtc { get; set; } public DateTime? StartedAtUtc { get; set; } public DateTime? CompletedAtUtc { get; set; } } public sealed class RenderJobContract { public const string CurrentVersion = "1"; public const string DefaultCameraMode = "killer_pov"; public const string DefaultOutputFormat = "mp4"; public const string DefaultResolution = "1920x1080"; public const string DefaultFps = "60"; public string Version { get; set; } = CurrentVersion; public int DemoId { get; set; } public int MatchId { get; set; } public int HighlightId { get; set; } public int SourceEventId { get; set; } public int StartTick { get; set; } public int EndTick { get; set; } public string CameraMode { get; set; } = DefaultCameraMode; public string OutputFormat { get; set; } = DefaultOutputFormat; public string Resolution { get; set; } = DefaultResolution; public string Fps { get; set; } = DefaultFps; public Dictionary ToPayload() { return new Dictionary { ["version"] = Version, ["demoId"] = DemoId.ToString(), ["matchId"] = MatchId.ToString(), ["highlightId"] = HighlightId.ToString(), ["sourceEventId"] = SourceEventId.ToString(), ["startTick"] = StartTick.ToString(), ["endTick"] = EndTick.ToString(), ["cameraMode"] = CameraMode, ["outputFormat"] = OutputFormat, ["resolution"] = Resolution, ["fps"] = Fps }; } public static RenderJobContract FromPayload(IReadOnlyDictionary payload) { return new RenderJobContract { Version = ReadString(payload, "version", CurrentVersion), DemoId = ReadInt(payload, "demoId"), MatchId = ReadInt(payload, "matchId"), HighlightId = ReadInt(payload, "highlightId"), SourceEventId = ReadInt(payload, "sourceEventId"), StartTick = ReadInt(payload, "startTick"), EndTick = ReadInt(payload, "endTick"), CameraMode = ReadString(payload, "cameraMode", DefaultCameraMode), OutputFormat = ReadString(payload, "outputFormat", DefaultOutputFormat), Resolution = ReadString(payload, "resolution", DefaultResolution), Fps = ReadString(payload, "fps", DefaultFps) }; } public string GetSuggestedFileExtension() { return ClipMediaFormats.GetSuggestedFileExtension(OutputFormat); } private static int ReadInt(IReadOnlyDictionary payload, string key) { return payload.TryGetValue(key, out var raw) && int.TryParse(raw, out var value) ? value : 0; } private static string ReadString(IReadOnlyDictionary payload, string key, string fallback) { return payload.TryGetValue(key, out var value) && !string.IsNullOrWhiteSpace(value) ? value : fallback; } } public sealed class ClipAssetRecord { public int Id { get; set; } public int ClipJobId { get; set; } public string FileName { get; set; } = string.Empty; public string StoredPath { get; set; } = string.Empty; public string ContentType { get; set; } = ClipMediaFormats.Mp4ContentType; public long FileSizeBytes { get; set; } public DateTime CreatedAtUtc { get; set; } = DateTime.UtcNow; } public sealed class ScoringProfileRecord { public int Id { get; set; } public string Name { get; set; } = "default"; public string Version { get; set; } = string.Empty; public bool IsActive { get; set; } = true; public DateTime CreatedAtUtc { get; set; } = DateTime.UtcNow; public HighlightScoringRules Rules { get; set; } = new(); } public sealed class HighlightScoringRules { public string Name { get; set; } = "default"; public Dictionary Weights { get; set; } = new(); public ClipWindowRules Clip { get; set; } = new(); public ScoreThresholdRules Thresholds { get; set; } = new(); } public sealed class ClipWindowRules { public int PreTicks { get; set; } = 160; public int PostTicks { get; set; } = 96; } public sealed class ScoreThresholdRules { public double HighlightScoreMin { get; set; } = 18; }