From 73cadcaf82b1af59cd218717e052d7e274ef5562 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sig=20Narv=C3=A1ez?= Date: Sun, 10 Mar 2024 00:24:32 -0800 Subject: [PATCH 1/7] Entity & DTO clean-up --- .../Dtos/RequestObjects/RecordingRequest.cs | 5 +---- .../Dtos/RequestObjects/SnapshotRequest.cs | 3 --- .../Dtos/ResponseObjects/ConfigResponse.cs | 1 - .../Dtos/ResponseObjects/PlayerResponse.cs | 12 ------------ rest_service/Entities/Config.cs | 4 +--- rest_service/Entities/Event.cs | 4 +--- rest_service/Entities/Player.cs | 7 +------ rest_service/Entities/PlayerUnique.cs | 18 +++++++----------- rest_service/Entities/Position.cs | 10 +++++----- .../Entities/SessionStatisticsPlain.cs | 4 ++-- rest_service/Entities/Snapshot.cs | 6 ++---- 11 files changed, 20 insertions(+), 54 deletions(-) diff --git a/rest_service/Dtos/RequestObjects/RecordingRequest.cs b/rest_service/Dtos/RequestObjects/RecordingRequest.cs index 74f51aa..3fb0057 100644 --- a/rest_service/Dtos/RequestObjects/RecordingRequest.cs +++ b/rest_service/Dtos/RequestObjects/RecordingRequest.cs @@ -1,10 +1,7 @@ -using System.Diagnostics.CodeAnalysis; -using RestService.Entities; +using RestService.Entities; namespace RestService.Dtos.RequestObjects; -[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")] -[SuppressMessage("ReSharper", "CollectionNeverUpdated.Global")] public class RecordingRequest { public SessionStatisticsPlain? SessionStatisticsPlain { get; set; } diff --git a/rest_service/Dtos/RequestObjects/SnapshotRequest.cs b/rest_service/Dtos/RequestObjects/SnapshotRequest.cs index fceaf0e..f71fe43 100644 --- a/rest_service/Dtos/RequestObjects/SnapshotRequest.cs +++ b/rest_service/Dtos/RequestObjects/SnapshotRequest.cs @@ -1,10 +1,7 @@ -using System.Diagnostics.CodeAnalysis; using RestService.Entities; namespace RestService.Dtos.RequestObjects; -[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")] -[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")] public class SnapshotRequest { public Position? Position { get; set; } diff --git a/rest_service/Dtos/ResponseObjects/ConfigResponse.cs b/rest_service/Dtos/ResponseObjects/ConfigResponse.cs index fe26cf8..08db688 100644 --- a/rest_service/Dtos/ResponseObjects/ConfigResponse.cs +++ b/rest_service/Dtos/ResponseObjects/ConfigResponse.cs @@ -3,7 +3,6 @@ namespace RestService.Dtos.ResponseObjects; -[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")] public class ConfigResponse { public float RoundDuration { get; set; } diff --git a/rest_service/Dtos/ResponseObjects/PlayerResponse.cs b/rest_service/Dtos/ResponseObjects/PlayerResponse.cs index 0301465..1ec4a40 100644 --- a/rest_service/Dtos/ResponseObjects/PlayerResponse.cs +++ b/rest_service/Dtos/ResponseObjects/PlayerResponse.cs @@ -1,11 +1,8 @@ -using System.Diagnostics.CodeAnalysis; -using MongoDB.Bson.Serialization.Attributes; using Newtonsoft.Json; using RestService.Entities; namespace RestService.Dtos.ResponseObjects; -[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")] public class PlayerResponse { [JsonProperty("_id")] public string? Id { get; set; } @@ -22,13 +19,4 @@ public PlayerResponse(Player player) Team = player.Team; Location = player.Location; } - - /* - public PlayerResponse(PlayerUnique player) - { - Id = player.Id.ToString(); - Name = player.Name; - Location = player.Location; - } - */ } \ No newline at end of file diff --git a/rest_service/Entities/Config.cs b/rest_service/Entities/Config.cs index 3886ca7..2e30ed7 100644 --- a/rest_service/Entities/Config.cs +++ b/rest_service/Entities/Config.cs @@ -1,11 +1,9 @@ -using System.Diagnostics.CodeAnalysis; using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; namespace RestService.Entities; -[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")] -[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")] +[BsonIgnoreExtraElements] public class Config { [BsonElement("_id")] public ObjectId? Id { get; set; } diff --git a/rest_service/Entities/Event.cs b/rest_service/Entities/Event.cs index 306fe1c..ba102b0 100644 --- a/rest_service/Entities/Event.cs +++ b/rest_service/Entities/Event.cs @@ -1,10 +1,8 @@ -using System.Diagnostics.CodeAnalysis; using MongoDB.Bson.Serialization.Attributes; namespace RestService.Entities; -[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")] -[SuppressMessage("ReSharper", "PropertyCanBeMadeInitOnly.Global")] +[BsonIgnoreExtraElements] public class Event { [BsonElement("_id")] public string? Id { get; set; } diff --git a/rest_service/Entities/Player.cs b/rest_service/Entities/Player.cs index 4b6c33e..d77c31e 100644 --- a/rest_service/Entities/Player.cs +++ b/rest_service/Entities/Player.cs @@ -1,20 +1,15 @@ -using System.Diagnostics.CodeAnalysis; using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; -using Newtonsoft.Json; -using RestService.Dtos.RequestObjects; namespace RestService.Entities; -[SuppressMessage("ReSharper", "PropertyCanBeMadeInitOnly.Global")] +[BsonIgnoreExtraElements] public class Player { [BsonElement("_id")] public ObjectId? Id { get; set; } [BsonElement("Nickname")] public string? Name { get; set; } [BsonElement("TeamName")] public string? Team { get; set; } [BsonElement("Email")] public string? Email { get; set; } - - [JsonProperty("location")] [BsonElement("location")] public string? Location { get; set; } } \ No newline at end of file diff --git a/rest_service/Entities/PlayerUnique.cs b/rest_service/Entities/PlayerUnique.cs index 221835c..9b6a414 100644 --- a/rest_service/Entities/PlayerUnique.cs +++ b/rest_service/Entities/PlayerUnique.cs @@ -1,18 +1,14 @@ using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; -using Newtonsoft.Json; +namespace RestService.Entities; -namespace RestService.Entities +[BsonIgnoreExtraElements] +public class PlayerUnique { - public class PlayerUnique - { - [BsonId] - [JsonProperty("Name")] - public string Name { get; set; } + [BsonId] + public string Name { get; set; } - [JsonProperty("location")] - [BsonElement("location")] - public string? Location { get; set; } - } + [BsonElement("location")] + public string? Location { get; set; } } \ No newline at end of file diff --git a/rest_service/Entities/Position.cs b/rest_service/Entities/Position.cs index 58c72cb..7c27d9e 100644 --- a/rest_service/Entities/Position.cs +++ b/rest_service/Entities/Position.cs @@ -1,11 +1,11 @@ -using System.Diagnostics.CodeAnalysis; +using MongoDB.Bson.Serialization.Attributes; namespace RestService.Entities; -[SuppressMessage("ReSharper", "PropertyCanBeMadeInitOnly.Global")] +[BsonIgnoreExtraElements] public class Position { - public float X { get; set; } - public float Y { get; set; } - public float Z { get; set; } + public double X { get; set; } + public double Y { get; set; } + public double Z { get; set; } } \ No newline at end of file diff --git a/rest_service/Entities/SessionStatisticsPlain.cs b/rest_service/Entities/SessionStatisticsPlain.cs index 3057399..edb8bee 100644 --- a/rest_service/Entities/SessionStatisticsPlain.cs +++ b/rest_service/Entities/SessionStatisticsPlain.cs @@ -1,8 +1,8 @@ -using System.Diagnostics.CodeAnalysis; +using MongoDB.Bson.Serialization.Attributes; namespace RestService.Entities; -[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")] +[BsonIgnoreExtraElements] public class SessionStatisticsPlain { public int BulletsFired { get; set; } diff --git a/rest_service/Entities/Snapshot.cs b/rest_service/Entities/Snapshot.cs index 3d5a955..dc8e66d 100644 --- a/rest_service/Entities/Snapshot.cs +++ b/rest_service/Entities/Snapshot.cs @@ -1,10 +1,8 @@ -using System.Diagnostics.CodeAnalysis; - -#pragma warning disable CS8618 +using MongoDB.Bson.Serialization.Attributes; namespace RestService.Entities; -[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")] +[BsonIgnoreExtraElements] public class Snapshot { public Position Position { get; set; } From 3445d87b27219a8f2ccb48bc796451293e6e0297 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sig=20Narv=C3=A1ez?= Date: Sun, 10 Mar 2024 00:30:13 -0800 Subject: [PATCH 2/7] REST similar recordings work --- deployment/game_database/vector_index.js | 26 +++++++ .../Controllers/RecordingsController.cs | 77 +++++++++++++++++++ .../SimilarRecordingResponse.cs | 22 ++++++ rest_service/Entities/Recording.cs | 12 ++- 4 files changed, 134 insertions(+), 3 deletions(-) create mode 100644 deployment/game_database/vector_index.js create mode 100644 rest_service/Dtos/ResponseObjects/SimilarRecordingResponse.cs diff --git a/deployment/game_database/vector_index.js b/deployment/game_database/vector_index.js new file mode 100644 index 0000000..4c3481c --- /dev/null +++ b/deployment/game_database/vector_index.js @@ -0,0 +1,26 @@ +// Create the following atlas vector search index: + +// ========================== +// named: vector_index +// collection: recordings +// ========================== +{ + "fields": [ + { + "path": "Player.Nickname", + "type": "filter" + }, + { + "numDimensions": 589, + "path": "speed_vector", + "similarity": "euclidean", + "type": "vector" + }, + { + "numDimensions": 588, + "path": "accel_vector", + "similarity": "euclidean", + "type": "vector" + } + ] +} \ No newline at end of file diff --git a/rest_service/Controllers/RecordingsController.cs b/rest_service/Controllers/RecordingsController.cs index 8654eb6..e661b8a 100644 --- a/rest_service/Controllers/RecordingsController.cs +++ b/rest_service/Controllers/RecordingsController.cs @@ -1,8 +1,11 @@ using Microsoft.AspNetCore.Mvc; using MongoDB.Driver; using RestService.Dtos.RequestObjects; +using RestService.Dtos.ResponseObjects; using RestService.Entities; using RestService.Exceptions; +using MongoDB.Bson; +using MongoDB.Bson.Serialization; namespace RestService.Controllers; @@ -129,4 +132,78 @@ private async Task AddPlayer(Recording recording) throw new MultiplePlayersFoundException(); } } + + [HttpGet("similarBySpeed", Name = "GetSimilarBySpeed")] + public async Task> SimilarBySpeed([FromQuery] PlayerRequest playerRequest) + { + // Get the highest scoring run for this player + Recording topRecording = _recordingsCollection + .Find(r => r.Player.Name.Equals(playerRequest.Name)) + .SortByDescending(r => r.SessionStatisticsPlain.Score) + .Limit(1).ToList().First(); + + // Now get similar recordings + List similarRecordings = _recordingsCollection.Aggregate() + .VectorSearch( + r => r.SpeedVector, + topRecording.SpeedVector, + 3, + new VectorSearchOptions() + { + IndexName = "vector_index", + NumberOfCandidates = 1000, + Filter = Builders.Filter + .Where(r => !r.Player.Name.Equals(playerRequest.Name)) + }) + .ToList(); + + List response = new() + { + new SimilarRecordingResponse(topRecording) + }; + response.AddRange( + similarRecordings + .Select(r => new SimilarRecordingResponse(r)) + .ToList()); + + return response; + } + + + [HttpGet("similarByAcceleration", Name = "GetSimilarByAcceleration")] + public async Task> SimilarByAcceleration([FromQuery] PlayerRequest playerRequest) + { + // Get the highest scoring run for this player + Recording topRecording = _recordingsCollection + .Find(r => r.Player.Name.Equals(playerRequest.Name)) + .SortByDescending(r => r.SessionStatisticsPlain.Score) + .Limit(1).ToList().First(); + + // Now get similar recordings + List similarRecordings = _recordingsCollection.Aggregate() + .VectorSearch( + r => r.AccelVector, + topRecording.AccelVector, + 3, + new VectorSearchOptions() + { + IndexName = "vector_index", + NumberOfCandidates = 1000, + Filter = Builders.Filter + .Where(r => !r.Player.Name.Equals(playerRequest.Name)) + }) + .ToList(); + + List response = new() + { + new SimilarRecordingResponse(topRecording) + }; + response.AddRange( + similarRecordings + .Select(r => new SimilarRecordingResponse(r)) + .ToList()); + + return response; + } + } \ No newline at end of file diff --git a/rest_service/Dtos/ResponseObjects/SimilarRecordingResponse.cs b/rest_service/Dtos/ResponseObjects/SimilarRecordingResponse.cs new file mode 100644 index 0000000..50b66f9 --- /dev/null +++ b/rest_service/Dtos/ResponseObjects/SimilarRecordingResponse.cs @@ -0,0 +1,22 @@ +using Newtonsoft.Json; +using RestService.Entities; + +namespace RestService.Dtos.ResponseObjects; + +public class SimilarRecordingResponse +{ + [JsonProperty("_id")] + public string? Id { get; set; } + [JsonProperty("sessionStatisticsPlain")] + public SessionStatisticsPlain? SessionStatisticsPlain { get; set; } + [JsonProperty("name")] + public string? Name { get; set; } + + public SimilarRecordingResponse(Recording recording) + { + Id = recording.Id.ToString(); + SessionStatisticsPlain = recording.SessionStatisticsPlain; + Name = recording.Player.Name; + } +} + diff --git a/rest_service/Entities/Recording.cs b/rest_service/Entities/Recording.cs index 81a5ec0..4dc0fdf 100644 --- a/rest_service/Entities/Recording.cs +++ b/rest_service/Entities/Recording.cs @@ -1,15 +1,21 @@ -using System.Diagnostics.CodeAnalysis; +using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; namespace RestService.Entities; -[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")] +[BsonIgnoreExtraElements] public class Recording { - [BsonElement("location")] public string? Location { get; set; } + [BsonElement("_id")] public ObjectId? Id { get; set; } + [BsonElement("location")] + public string? Location { get; set; } public SessionStatisticsPlain? SessionStatisticsPlain { get; set; } public DateTime? DateTime { get; set; } public RecordingPlayer Player { get; set; } public List? Snapshots { get; set; } public RecordingEvent Event { get; set; } + [BsonElement("speed_vector")] + public double[]? SpeedVector { get; set; } + [BsonElement("accel_vector")] + public double[]? AccelVector { get; set; } } \ No newline at end of file From 0e4b401e86a83347a7d645a243b98ab5b0811022 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sig=20Narv=C3=A1ez?= Date: Sun, 10 Mar 2024 00:31:04 -0800 Subject: [PATCH 3/7] WEB similar recordings work --- website/Data/SimilarRecordings.cs | 25 +++++ website/Pages/PlayerHome.razor | 19 ++-- website/Pages/PlayerSimilar.razor | 156 ++++++++++++++++++++++++++++++ website/Utils/Constants.cs | 2 + 4 files changed, 196 insertions(+), 6 deletions(-) create mode 100644 website/Data/SimilarRecordings.cs create mode 100644 website/Pages/PlayerSimilar.razor diff --git a/website/Data/SimilarRecordings.cs b/website/Data/SimilarRecordings.cs new file mode 100644 index 0000000..5513e35 --- /dev/null +++ b/website/Data/SimilarRecordings.cs @@ -0,0 +1,25 @@ +using Newtonsoft.Json; +namespace website.Data; + +public class SessionStatisticsPlain +{ + public int BulletsFired { get; set; } + public int DamageDone { get; set; } + public int PelletsDestroyedSmall { get; set; } + public int PelletsDestroyedMedium { get; set; } + public int PelletsDestroyedLarge { get; set; } + public int Score { get; set; } + public int PowerUpBulletDamageCollected { get; set; } + public int PowerUpBulletSpeedCollected { get; set; } + public int PowerUpPlayerSpeedCollected { get; set; } +} + +public class SimilarRecordings +{ + [JsonProperty("id")] + public string? Id { get; set; } + [JsonProperty("sessionStatisticsPlain")] + public SessionStatisticsPlain? SessionStatisticsPlain { get; set; } + [JsonProperty("name")] + public string? Name { get; set; } +} \ No newline at end of file diff --git a/website/Pages/PlayerHome.razor b/website/Pages/PlayerHome.razor index 4fd4afb..767df7f 100644 --- a/website/Pages/PlayerHome.razor +++ b/website/Pages/PlayerHome.razor @@ -115,6 +115,13 @@ } else { +
+
+     + View Similar Players to @Player.Name +     +
+
} @@ -139,18 +146,18 @@ _eventId = queryParameters[Constants.QueryParameterEventId]; var eventsFilter = new Dictionary -{ -{ "id", _eventId } -}; + { + { "id", _eventId } + }; string eventsUrlWithQuery = UrlHelper.BuildUrlWithQuery(Constants.RestServiceEndpointEvents, eventsFilter); var events = await _restClient.GetJsonAsync>(eventsUrlWithQuery); Event = events.First(); _name = queryParameters[Constants.QueryParameterName]; var playerFilter = new Dictionary -{ -{ "name", _name } -}; + { + { "name", _name } + }; string playersUrlWithQuery = UrlHelper.BuildUrlWithQuery(Constants.RestServiceEndpointPlayers, playerFilter); var players = await _restClient.GetJsonAsync>(playersUrlWithQuery); Player = players.First(); diff --git a/website/Pages/PlayerSimilar.razor b/website/Pages/PlayerSimilar.razor new file mode 100644 index 0000000..cb10dea --- /dev/null +++ b/website/Pages/PlayerSimilar.razor @@ -0,0 +1,156 @@ +@page "/playerSimilar" +@using RestSharp +@using website.Data +@using website.Utils +@using dotenv.net + +@inject NavigationManager NavigationManager +@inject Blazored.LocalStorage.ILocalStorageService localStore +@inject IJSRuntime JSRuntime + + + +logo + +
+ @if (Player == null) + { +

+ Loading... +

+ } + else + { +
+

Player Dashboard

+

+ Similar Players by Speed and Acceleration +

+

+ @_errorMessage +

+ +
+
+
+ + + +
+ +
+ +
+
+ + + +
+ +
+ +
+
+ + + +
+ + +
+ +
+ Play Now +
+
+ +
+ } + +
+ + +
+ @if (Player == null) + { +

+ Loading... +

+ } + else + { +

Similar Players by Speed

+ // Iterate through every similar by speed + foreach (SimilarRecordings rec in SimilarBySpeed) + { + //TO-DO: Get Chart URL and ID from helper and env constants +
@rec.Name
+ string chartUrl = string.Concat("https://charts.mongodb.com/charts-aws-reinvent-2023-bojgz/embed/charts?id=0a7ef9ba-e9ab-40dc-b291-a8c836d39381&maxDataAge=3600&theme=light&autoRefresh=true", + "&filter={%27_id%27:{%27$oid%27:%27", + rec.Id.ToString(), + "%27}}"); + + } + } +
+ + +@code { + + private Player Player { get; set; } = new(); + private List SimilarBySpeed { get; set; } = new(); + //private List SimilarByAccel { get; set; } = new(); + + private string _name = string.Empty; + private string _chartUrl = string.Empty; + private string _errorMessage = string.Empty; + private readonly RestClient _restClient = RestServiceClient.Create(); + + private string chartFilter = string.Empty; + + protected override async Task OnInitializedAsync() + { + try + { + var queryParameters = UrlHelper.GetParameters(NavigationManager.Uri); + + _name = queryParameters[Constants.QueryParameterName]; + var playerFilter = new Dictionary + { + { "name", _name } + }; + + string playersUrlWithQuery = UrlHelper.BuildUrlWithQuery(Constants.RestServiceEndpointPlayers, playerFilter); + var players = await _restClient.GetJsonAsync>(playersUrlWithQuery); + Player = players.First(); + + string similarBySpeedUrlWithQuery = UrlHelper.BuildUrlWithQuery(Constants.RestServiceEndpointSimilarBySpeed, playerFilter); + SimilarBySpeed = await _restClient.GetJsonAsync>(similarBySpeedUrlWithQuery); + + //string similarByAccelUrlWithQuery = UrlHelper.BuildUrlWithQuery(Constants.RestServiceEndpointSimilarByAccel, playerFilter); + //SimilarByAccel = await _restClient.GetJsonAsync>(similarByAccelUrlWithQuery); + } + catch (Exception e) + { + Console.WriteLine(e.Message); + Console.WriteLine(e.StackTrace); + _errorMessage = "PLAYER NOT FOUND"; + } + } +} \ No newline at end of file diff --git a/website/Utils/Constants.cs b/website/Utils/Constants.cs index 4683e16..75256fa 100644 --- a/website/Utils/Constants.cs +++ b/website/Utils/Constants.cs @@ -8,6 +8,8 @@ public static class Constants public const string RestServiceEndpointPlayers = "players"; public const string RestServiceEndpointPlayersAutoComplete = "players/autocomplete"; public const string RestServiceEndpointPlayersSearch = "players/search"; + public const string RestServiceEndpointSimilarBySpeed = "recordings/similarBySpeed"; + public const string RestServiceEndpointSimilarByAccel = "recordings/similarByAcceleration"; public const string QueryParameterEventId = "EventId"; public const string QueryParameterName = "Name"; } \ No newline at end of file From 3e307b1d70787104bfa908ef9d42cf8b999b21e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sig=20Narv=C3=A1ez?= Date: Sun, 10 Mar 2024 18:49:22 -0700 Subject: [PATCH 4/7] Speed and Acceleration vector calculation --- .../Controllers/RecordingsController.cs | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/rest_service/Controllers/RecordingsController.cs b/rest_service/Controllers/RecordingsController.cs index e661b8a..b460e4d 100644 --- a/rest_service/Controllers/RecordingsController.cs +++ b/rest_service/Controllers/RecordingsController.cs @@ -41,6 +41,7 @@ public async Task PostRecording([FromBody] RecordingRequest recor var newRecording = new Recording() { + Id = ObjectId.GenerateNewId(), SessionStatisticsPlain = recordingRequest.SessionStatisticsPlain, DateTime = DateTime.UtcNow, Player = new RecordingPlayer { Name = recordingRequest.PlayerName }, @@ -56,6 +57,15 @@ public async Task PostRecording([FromBody] RecordingRequest recor }).ToList() }; + // Calculate vectors + try + { + newRecording.SpeedVector = CalculateSpeedVector(newRecording.Snapshots); + newRecording.AccelVector = CalculateAcceleration(newRecording.SpeedVector); + } catch (Exception) { + // Favor persisting Recording over setting vectors + } + try { await AddLocation(newRecording); @@ -133,6 +143,39 @@ private async Task AddPlayer(Recording recording) } } + private static double[] CalculateSpeedVector(List snapshots) + { + long vectorSize = snapshots.Count - 1; + if (vectorSize != 589) // 590 represents movements in 60s + return Array.Empty(); + + double[] speed = new double[vectorSize]; + for (int i = 0; i < vectorSize; i++) + { + double speedX = snapshots[i + 1].Position.X - snapshots[i].Position.X; + //double Y = snapshots[i + 1].Position.X - snapshots[i].Position.X; + double speedY = snapshots[i + 1].Position.Z - snapshots[i].Position.Z; + speed[i] = Math.Sqrt(Math.Pow(speedX, 2) + Math.Pow(speedY, 2)); + } + + return speed; + } + + private static double[] CalculateAcceleration(double[] speedVector) + { + long vectorSize = speedVector.Length - 1; + if (speedVector.Length != 589) // speed vector should be 1 less 60s run + return Array.Empty(); + + double dt = 1; // Assuming a constant time step of 1 unit. + double[] accelVector = new double[vectorSize]; + + for (int i = 0; i < speedVector.Length - 1; i++) + accelVector[i] = (speedVector[i + 1] - speedVector[i]) / dt; + + return accelVector; + } + [HttpGet("similarBySpeed", Name = "GetSimilarBySpeed")] public async Task> SimilarBySpeed([FromQuery] PlayerRequest playerRequest) { From b679494bc8ab5ea92175fa8a54abacb279342582 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sig=20Narv=C3=A1ez?= Date: Mon, 11 Mar 2024 10:39:12 -0700 Subject: [PATCH 5/7] Serialization fixes --- rest_service/Entities/RecordingEvent.cs | 29 ++++++++++++++++-- rest_service/Entities/RecordingPlayer.cs | 38 ++++++++++++++++++++++-- rest_service/Program.cs | 8 +++-- 3 files changed, 67 insertions(+), 8 deletions(-) diff --git a/rest_service/Entities/RecordingEvent.cs b/rest_service/Entities/RecordingEvent.cs index f079872..198a5aa 100644 --- a/rest_service/Entities/RecordingEvent.cs +++ b/rest_service/Entities/RecordingEvent.cs @@ -1,9 +1,11 @@ using MongoDB.Bson; using MongoDB.Bson.Serialization; +using MongoDB.Bson.Serialization.Attributes; using MongoDB.Bson.Serialization.Serializers; namespace RestService.Entities; +[BsonIgnoreExtraElements] public class RecordingEvent : Event { // This class is empty intentionally. @@ -12,11 +14,15 @@ public class RecordingEvent : Event // https://www.mongodb.com/blog/post/building-with-patterns-the-extended-reference-pattern } -public class EventForRecordingSerializer : SerializerBase +public class RecordingEventSerializer : SerializerBase, IBsonDocumentSerializer { public override RecordingEvent Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) { - return BsonSerializer.Deserialize(context.Reader); + var deserialized = BsonDocumentSerializer.Instance.Deserialize(context); + return new RecordingEvent() + { + Id = deserialized.GetValue("_id").ToString() + }; } public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, RecordingEvent value) @@ -28,4 +34,23 @@ public override void Serialize(BsonSerializationContext context, BsonSerializati BsonDocumentSerializer.Instance.Serialize(context, document); } + + public bool TryGetMemberSerializationInfo(string memberName, out BsonSerializationInfo? serializationInfo) + { + switch (memberName) + { + case "Id": + serializationInfo = new BsonSerializationInfo("_id", ObjectIdSerializer.Instance, typeof(ObjectId)); + return true; + case "Name": + serializationInfo = new BsonSerializationInfo("name", StringSerializer.Instance, typeof(string)); + return true; + case "Location": + serializationInfo = new BsonSerializationInfo("location", StringSerializer.Instance, typeof(string)); + return true; + default: + serializationInfo = null; + return false; + } + } } \ No newline at end of file diff --git a/rest_service/Entities/RecordingPlayer.cs b/rest_service/Entities/RecordingPlayer.cs index 3bc0ce9..3da2b5d 100644 --- a/rest_service/Entities/RecordingPlayer.cs +++ b/rest_service/Entities/RecordingPlayer.cs @@ -1,22 +1,29 @@ using MongoDB.Bson; using MongoDB.Bson.Serialization; +using MongoDB.Bson.Serialization.Attributes; using MongoDB.Bson.Serialization.Serializers; namespace RestService.Entities; +[BsonIgnoreExtraElements] public class RecordingPlayer : Player { // This class is empty intentionally. // Its purpose is to override the serialization to store a subset of the parent class // Thus implementing the Extended Reference Schema Design Pattern: - // https://www.mongodb.com/blog/post/building-with-patterns-the-extended-reference-pattern + // https://www.mongodb.com/blog/post/building-with-patterns-the-extended-reference-pattern } -public class PlayerForRecordingsSerializer : SerializerBase +public class RecordingPlayerSerializer : SerializerBase, IBsonDocumentSerializer { public override RecordingPlayer Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) { - return BsonSerializer.Deserialize(context.Reader); + var deserialized = BsonDocumentSerializer.Instance.Deserialize(context); + return new RecordingPlayer() + { + Name = deserialized.GetValue("Nickname").ToString(), + Location = deserialized.GetValue("location").ToString() + }; } public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, RecordingPlayer value) @@ -29,4 +36,29 @@ public override void Serialize(BsonSerializationContext context, BsonSerializati BsonDocumentSerializer.Instance.Serialize(context, document); } + + public bool TryGetMemberSerializationInfo(string memberName, out BsonSerializationInfo? serializationInfo) + { + switch (memberName) + { + case "Id": + serializationInfo = new BsonSerializationInfo("_id", new ObjectIdSerializer(), typeof(ObjectId)); + return true; + case "Name": + serializationInfo = new BsonSerializationInfo("Nickname", new StringSerializer(), typeof(string)); + return true; + case "Team": + serializationInfo = new BsonSerializationInfo("TeamName", new StringSerializer(), typeof(string)); + return true; + case "Email": + serializationInfo = new BsonSerializationInfo("Email", new StringSerializer(), typeof(string)); + return true; + case "Location": + serializationInfo = new BsonSerializationInfo("location", new StringSerializer(), typeof(string)); + return true; + default: + serializationInfo = null; + return false; + } + } } \ No newline at end of file diff --git a/rest_service/Program.cs b/rest_service/Program.cs index b766a73..9812b5e 100644 --- a/rest_service/Program.cs +++ b/rest_service/Program.cs @@ -8,7 +8,10 @@ builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); + +// required for methods in RecordingsController with diff name but same signature builder.Services.AddSwaggerGen(); + builder.Services.AddCors(options => { options.AddPolicy("NewPolicy", builder => @@ -33,7 +36,6 @@ app.MapControllers(); // Register custom serializers -BsonSerializer.RegisterSerializer(new PlayerForRecordingsSerializer()); -BsonSerializer.RegisterSerializer(new EventForRecordingSerializer()); - +BsonSerializer.RegisterSerializer(new RecordingPlayerSerializer()); +BsonSerializer.RegisterSerializer(new RecordingEventSerializer()); app.Run(); \ No newline at end of file From ee2f525131a649f3f771ada52ed59c7b85164f40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sig=20Narv=C3=A1ez?= Date: Mon, 11 Mar 2024 11:29:46 -0700 Subject: [PATCH 6/7] Add ATLAS_CHART_ID_SIMILAR to website env variables Plus other web improvements --- .../Controllers/RecordingsController.cs | 2 + website/.env.template | 1 + website/Data/SimilarRecordings.cs | 2 +- website/Pages/PlayerSimilar.razor | 40 +++++++++++-------- website/Utils/ChartsUrl.cs | 8 ++++ 5 files changed, 36 insertions(+), 17 deletions(-) diff --git a/rest_service/Controllers/RecordingsController.cs b/rest_service/Controllers/RecordingsController.cs index b460e4d..8ee7b81 100644 --- a/rest_service/Controllers/RecordingsController.cs +++ b/rest_service/Controllers/RecordingsController.cs @@ -200,6 +200,7 @@ public async Task> SimilarBySpeed([FromQuery] Pla }) .ToList(); + // Return this player's top recording + top similar List response = new() { new SimilarRecordingResponse(topRecording) @@ -237,6 +238,7 @@ public async Task> SimilarByAcceleration([FromQue }) .ToList(); + // Return this player's top recording + top similar List response = new() { new SimilarRecordingResponse(topRecording) diff --git a/website/.env.template b/website/.env.template index 107eb16..2d32f61 100644 --- a/website/.env.template +++ b/website/.env.template @@ -4,4 +4,5 @@ ATLAS_CHART_EMBED_DASHBOARD_URL= ATLAS_CHART_ID_EVENT= ATLAS_CHART_ID_PLAYER= ATLAS_CHART_ID_HOME= +ATLAS_CHART_ID_SIMILAR= GAME_CLIENT_PORT=8000 diff --git a/website/Data/SimilarRecordings.cs b/website/Data/SimilarRecordings.cs index 5513e35..2d83a85 100644 --- a/website/Data/SimilarRecordings.cs +++ b/website/Data/SimilarRecordings.cs @@ -14,7 +14,7 @@ public class SessionStatisticsPlain public int PowerUpPlayerSpeedCollected { get; set; } } -public class SimilarRecordings +public class SimilarRecording { [JsonProperty("id")] public string? Id { get; set; } diff --git a/website/Pages/PlayerSimilar.razor b/website/Pages/PlayerSimilar.razor index cb10dea..3ec4c2e 100644 --- a/website/Pages/PlayerSimilar.razor +++ b/website/Pages/PlayerSimilar.razor @@ -39,7 +39,7 @@

Player Dashboard

- Similar Players by Speed and Acceleration + Similar Players by Speed based on Highest Score Run

@_errorMessage @@ -74,10 +74,6 @@ - -
- Play Now -

@@ -96,17 +92,23 @@ else {

Similar Players by Speed

- // Iterate through every similar by speed - foreach (SimilarRecordings rec in SimilarBySpeed) + foreach (SimilarRecording rec in SimilarBySpeed) { - //TO-DO: Get Chart URL and ID from helper and env constants
@rec.Name
- string chartUrl = string.Concat("https://charts.mongodb.com/charts-aws-reinvent-2023-bojgz/embed/charts?id=0a7ef9ba-e9ab-40dc-b291-a8c836d39381&maxDataAge=3600&theme=light&autoRefresh=true", - "&filter={%27_id%27:{%27$oid%27:%27", - rec.Id.ToString(), - "%27}}"); + string chartUrl = ChartsUrl.CreateSimilarUrl(_atlasChartIdSimilar, rec.Id); } + + //TO-DO: Revisit acceleration + } @@ -114,10 +116,11 @@ @code { private Player Player { get; set; } = new(); - private List SimilarBySpeed { get; set; } = new(); - //private List SimilarByAccel { get; set; } = new(); + private List SimilarBySpeed { get; set; } = new(); + private List SimilarByAccel { get; set; } = new(); private string _name = string.Empty; + private string _atlasChartIdSimilar = string.Empty; private string _chartUrl = string.Empty; private string _errorMessage = string.Empty; private readonly RestClient _restClient = RestServiceClient.Create(); @@ -141,10 +144,15 @@ Player = players.First(); string similarBySpeedUrlWithQuery = UrlHelper.BuildUrlWithQuery(Constants.RestServiceEndpointSimilarBySpeed, playerFilter); - SimilarBySpeed = await _restClient.GetJsonAsync>(similarBySpeedUrlWithQuery); + SimilarBySpeed = await _restClient.GetJsonAsync>(similarBySpeedUrlWithQuery); + //TO-DO: Revisit acceleration //string similarByAccelUrlWithQuery = UrlHelper.BuildUrlWithQuery(Constants.RestServiceEndpointSimilarByAccel, playerFilter); - //SimilarByAccel = await _restClient.GetJsonAsync>(similarByAccelUrlWithQuery); + //SimilarByAccel = await _restClient.GetJsonAsync>(similarByAccelUrlWithQuery); + + DotEnv.Load(); + var envVars = DotEnv.Read(); + _atlasChartIdSimilar = envVars["ATLAS_CHART_ID_SIMILAR"]; } catch (Exception e) { diff --git a/website/Utils/ChartsUrl.cs b/website/Utils/ChartsUrl.cs index fa46e8b..95827da 100644 --- a/website/Utils/ChartsUrl.cs +++ b/website/Utils/ChartsUrl.cs @@ -26,6 +26,14 @@ public static string CreatePlayerUrl(string chartsId, string name, string locati + "%27}"; } + public static string CreateSimilarUrl(string chartsId, string recordingId) + { + return CreateBaseUrl(chartsId) + + "&filter={%27_id%27:{%27$oid%27:%27" + + recordingId + + "%27}}"; + } + public static string CreateHomeUrl(string chartsId, string eventId, string eventLocation) { return CreateBaseUrl(chartsId) From ffa600657938370f976a73840d514d017f0d80a2 Mon Sep 17 00:00:00 2001 From: Carlos Castro Date: Tue, 12 Mar 2024 07:26:19 +0000 Subject: [PATCH 7/7] Add ATLAS_CHART_ID_SIMILAR_PROD and ATLAS_CHART_ID_SIMILAR_STAGING to deployment.yml --- .github/workflows/deployment.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index 7fccd84..f008c3d 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -21,6 +21,7 @@ env: ATLAS_CHART_ID_EVENT_PROD: ${{ secrets.ATLAS_CHART_ID_EVENT_PROD }} ATLAS_CHART_ID_PLAYER_PROD: ${{ secrets.ATLAS_CHART_ID_PLAYER_PROD }} ATLAS_CHART_ID_HOME_PROD: ${{ secrets.ATLAS_CHART_ID_HOME_PROD }} + ATLAS_CHART_ID_SIMILAR_PROD: ${{ secrets.ATLAS_CHART_ID_SIMILAR_PROD }} CONNECTION_STRING_STAGING: ${{ secrets.CONNECTION_STRING_STAGING }} REST_SERVICE_IP_STAGING: ${{ secrets.REST_SERVICE_IP_STAGING }} @@ -29,6 +30,7 @@ env: ATLAS_CHART_ID_EVENT_STAGING: ${{ secrets.ATLAS_CHART_ID_EVENT_STAGING }} ATLAS_CHART_ID_PLAYER_STAGING: ${{ secrets.ATLAS_CHART_ID_PLAYER_STAGING}} ATLAS_CHART_ID_HOME_STAGING: ${{ secrets.ATLAS_CHART_ID_HOME_STAGING }} + ATLAS_CHART_ID_SIMILAR_STAGING: ${{ secrets.ATLAS_CHART_ID_SIMILAR_STAGING }} jobs: deployment: @@ -49,6 +51,7 @@ jobs: echo "ATLAS_CHART_ID_EVENT=${ATLAS_CHART_ID_EVENT_STAGING}" >> $GITHUB_ENV echo "ATLAS_CHART_ID_PLAYER=${ATLAS_CHART_ID_PLAYER_STAGING}" >> $GITHUB_ENV echo "ATLAS_CHART_ID_HOME=${ATLAS_CHART_ID_HOME_STAGING}" >> $GITHUB_ENV + echo "ATLAS_CHART_ID_SIMILAR=${ATLAS_CHART_ID_SIMILAR_STAGING}" >> $GITHUB_ENV echo "GAME_CLIENT_PORT=${GAME_CLIENT_PORT}" >> $GITHUB_ENV @@ -63,6 +66,7 @@ jobs: echo "ATLAS_CHART_ID_EVENT=${ATLAS_CHART_ID_EVENT_PROD}" >> $GITHUB_ENV echo "ATLAS_CHART_ID_PLAYER=${ATLAS_CHART_ID_PLAYER_PROD}" >> $GITHUB_ENV echo "ATLAS_CHART_ID_HOME=${ATLAS_CHART_ID_HOME_PROD}" >> $GITHUB_ENV + echo "ATLAS_CHART_ID_SIMILAR=${ATLAS_CHART_ID_SIMILAR_PROD}" >> $GITHUB_ENV echo "GAME_CLIENT_PORT=${GAME_CLIENT_PORT}" >> $GITHUB_ENV @@ -116,6 +120,7 @@ jobs: echo "ATLAS_CHART_ID_PLAYER=${{ env.ATLAS_CHART_ID_PLAYER }}" >> ./website/.env echo "ATLAS_CHART_ID_HOME=${{ env.ATLAS_CHART_ID_HOME }}" >> ./website/.env echo "ATLAS_CHART_ID_HOME=${{ env.ATLAS_CHART_ID_HOME }}" >> ./website/.env + echo "ATLAS_CHART_ID_SIMILAR=${{ env.ATLAS_CHART_ID_SIMILAR }}" >> ./website/.env echo "GAME_CLIENT_PORT=${{ env.GAME_CLIENT_PORT }}" >> ./website/.env