From 193e8c0e56cd04f098963a6d200552b8d60137bf Mon Sep 17 00:00:00 2001 From: Samuel Cattini-Schultz Date: Sat, 30 Nov 2024 15:09:28 +1100 Subject: [PATCH 1/6] Add mods_json field to scores --- common/osu/osuapi.py | 18 ++++++++++++++++-- profiles/migrations/0024_score_mods_json.py | 19 +++++++++++++++++++ profiles/models.py | 2 ++ profiles/serialisers.py | 1 + profiles/services.py | 1 + 5 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 profiles/migrations/0024_score_mods_json.py diff --git a/common/osu/osuapi.py b/common/osu/osuapi.py index 0224df3f..5b9c3612 100644 --- a/common/osu/osuapi.py +++ b/common/osu/osuapi.py @@ -10,7 +10,7 @@ from ossapi import Beatmap, GameMode, Ossapi, Score, ScoreType, User, UserLookupKey from common.osu.enums import BeatmapStatus, Gamemode -from common.osu.utils import get_bitwise_mods +from common.osu.utils import get_bitwise_mods, get_json_mods class MalformedResponseError(Exception): @@ -272,6 +272,7 @@ def from_apiv1(cls, data: dict) -> "UserData": class ScoreData(NamedTuple): beatmap_id: int mods: int + mods_json: list[dict] is_classic: bool score: int @@ -292,6 +293,7 @@ def as_json(self): return { "beatmap_id": self.beatmap_id, "mods": self.mods, + "mods_json": self.mods_json, "is_classic": self.is_classic, "score": self.score, "best_combo": self.best_combo, @@ -312,6 +314,7 @@ def from_json(cls, data: dict) -> "ScoreData": return cls( beatmap_id=data["beatmap_id"], mods=data["mods"], + mods_json=data["mods_json"], is_classic=data["is_classic"], score=data["score"], best_combo=data["best_combo"], @@ -374,6 +377,8 @@ def from_apiv1( if data["countmiss"] != "0": statistics["miss"] = int(data["countmiss"]) + is_classic = data["score"] != "0" # lazer uses new scoring + return cls( beatmap_id=( beatmap_id_override @@ -381,7 +386,8 @@ def from_apiv1( else int(data["beatmap_id"]) ), mods=int(data["enabled_mods"]), - is_classic=data["score"] != "0", # lazer uses new scoring + mods_json=get_json_mods(int(data["enabled_mods"]), is_classic), + is_classic=is_classic, score=int(data["score"]), best_combo=int(data["maxcombo"]), count_300=int(data["count300"]), @@ -591,6 +597,13 @@ def __score_data_from_ossapi( bitwise_mods, is_classic = get_bitwise_mods([mod.acronym for mod in score.mods]) + mods_json = [] + for mod in score.mods: + mod_json = {"acronym": mod.acronym} + if mod.settings is not None: + mod_json["settings"] = mod.settings + mods_json.append(mod_json) + if gamemode == Gamemode.STANDARD: count_300 = ( score.statistics.great if score.statistics.great is not None else 0 @@ -698,6 +711,7 @@ def __score_data_from_ossapi( return ScoreData( beatmap_id=beatmap_id, mods=bitwise_mods, + mods_json=mods_json, is_classic=is_classic, score=score.legacy_total_score if is_classic else score.total_score, best_combo=score.max_combo, diff --git a/profiles/migrations/0024_score_mods_json.py b/profiles/migrations/0024_score_mods_json.py new file mode 100644 index 00000000..fe523cbe --- /dev/null +++ b/profiles/migrations/0024_score_mods_json.py @@ -0,0 +1,19 @@ +# Generated by Django 5.1.3 on 2024-11-30 03:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("profiles", "0023_score_statistics"), + ] + + operations = [ + migrations.AddField( + model_name="score", + name="mods_json", + field=models.JSONField(default=[]), + preserve_default=False, + ), + ] diff --git a/profiles/models.py b/profiles/models.py index d66ec6de..9680848d 100644 --- a/profiles/models.py +++ b/profiles/models.py @@ -461,6 +461,7 @@ class Score(models.Model): best_combo = models.IntegerField() perfect = models.BooleanField() mods = models.IntegerField() + mods_json = models.JSONField() is_classic = models.BooleanField() rank = models.CharField() date = models.DateTimeField() @@ -538,6 +539,7 @@ def get_nochoke_mutation(self): count_geki=self.count_geki, count_katu=self.count_katu, mods=self.mods, + mods_json=self.mods_json.copy(), is_classic=self.is_classic, date=self.date, beatmap=self.beatmap, diff --git a/profiles/serialisers.py b/profiles/serialisers.py index 070c7891..074f287b 100644 --- a/profiles/serialisers.py +++ b/profiles/serialisers.py @@ -146,6 +146,7 @@ class Meta: "best_combo", "perfect", "mods", + "mods_json", "rank", "date", # osuchan data diff --git a/profiles/services.py b/profiles/services.py index 71aac2c7..94f55aeb 100644 --- a/profiles/services.py +++ b/profiles/services.py @@ -332,6 +332,7 @@ def add_scores_from_data(user_stats: UserStats, score_data_list: list[ScoreData] score.best_combo = score_data.best_combo score.perfect = score_data.perfect score.mods = score_data.mods + score.mods_json = score_data.mods_json score.is_classic = score_data.is_classic score.rank = score_data.rank score.date = score_data.date From c0796e138c6266e9bc8ed9b028f9853fa185e074 Mon Sep 17 00:00:00 2001 From: Samuel Cattini-Schultz Date: Sat, 30 Nov 2024 15:21:04 +1100 Subject: [PATCH 2/6] Rename is_classic to is_stable --- common/osu/difficultycalculator.py | 12 +++++----- common/osu/osuapi.py | 18 +++++++-------- common/osu/stubdata/osuapi/scores.json | 22 +++++++++---------- common/osu/stubdata/osuapi/user_best.json | 10 ++++----- common/osu/stubdata/osuapi/user_recent.json | 6 ++--- conftest.py | 2 +- .../management/commands/refetchlazerscores.py | 8 +++---- .../0025_rename_is_classic_score_is_stable.py | 18 +++++++++++++++ profiles/models.py | 4 ++-- profiles/services.py | 4 ++-- 10 files changed, 60 insertions(+), 44 deletions(-) create mode 100644 profiles/migrations/0025_rename_is_classic_score_is_stable.py diff --git a/common/osu/difficultycalculator.py b/common/osu/difficultycalculator.py index 8224f0ef..7677e3fe 100644 --- a/common/osu/difficultycalculator.py +++ b/common/osu/difficultycalculator.py @@ -35,7 +35,7 @@ class Score(NamedTuple): beatmap_id: str mods: int | None = None - is_classic: bool = True + is_stable: bool = True statistics: dict[str, int] = {} combo: int | None = None @@ -144,7 +144,7 @@ def _difficalcy_score_from_score(self, score: Score) -> dict: for k, v in { "BeatmapId": score.beatmap_id, "Mods": get_json_mods( - score.mods if score.mods is not None else 0, score.is_classic + score.mods if score.mods is not None else 0, score.is_stable ), "Combo": score.combo, "Misses": score.statistics.get("miss", 0), @@ -177,7 +177,7 @@ def _difficalcy_score_from_score(self, score: Score) -> dict: for k, v in { "BeatmapId": score.beatmap_id, "Mods": get_json_mods( - score.mods if score.mods is not None else 0, score.is_classic + score.mods if score.mods is not None else 0, score.is_stable ), "Combo": score.combo, "Misses": score.statistics.get("miss", 0), @@ -209,7 +209,7 @@ def _difficalcy_score_from_score(self, score: Score) -> dict: for k, v in { "BeatmapId": score.beatmap_id, "Mods": get_json_mods( - score.mods if score.mods is not None else 0, score.is_classic + score.mods if score.mods is not None else 0, score.is_stable ), "Combo": score.combo, "Misses": score.statistics.get("miss", 0), @@ -242,7 +242,7 @@ def _difficalcy_score_from_score(self, score: Score) -> dict: for k, v in { "BeatmapId": score.beatmap_id, "Mods": get_json_mods( - score.mods if score.mods is not None else 0, score.is_classic + score.mods if score.mods is not None else 0, score.is_stable ), "Combo": score.combo, "Misses": score.statistics.get("miss", 0), @@ -279,7 +279,7 @@ def _difficalcy_score_from_score(self, score: Score) -> dict: for k, v in { "BeatmapId": score.beatmap_id, "Mods": get_json_mods( - score.mods if score.mods is not None else 0, score.is_classic + score.mods if score.mods is not None else 0, score.is_stable ), "Combo": score.combo, "Misses": score.statistics.get("miss", 0), diff --git a/common/osu/osuapi.py b/common/osu/osuapi.py index 5b9c3612..b5606de4 100644 --- a/common/osu/osuapi.py +++ b/common/osu/osuapi.py @@ -273,7 +273,7 @@ class ScoreData(NamedTuple): beatmap_id: int mods: int mods_json: list[dict] - is_classic: bool + is_stable: bool score: int best_combo: int @@ -294,7 +294,7 @@ def as_json(self): "beatmap_id": self.beatmap_id, "mods": self.mods, "mods_json": self.mods_json, - "is_classic": self.is_classic, + "is_stable": self.is_stable, "score": self.score, "best_combo": self.best_combo, "count_300": self.count_300, @@ -315,7 +315,7 @@ def from_json(cls, data: dict) -> "ScoreData": beatmap_id=data["beatmap_id"], mods=data["mods"], mods_json=data["mods_json"], - is_classic=data["is_classic"], + is_stable=data["is_stable"], score=data["score"], best_combo=data["best_combo"], count_300=data["count_300"], @@ -377,7 +377,7 @@ def from_apiv1( if data["countmiss"] != "0": statistics["miss"] = int(data["countmiss"]) - is_classic = data["score"] != "0" # lazer uses new scoring + is_stable = data["score"] != "0" # lazer uses new scoring return cls( beatmap_id=( @@ -386,8 +386,8 @@ def from_apiv1( else int(data["beatmap_id"]) ), mods=int(data["enabled_mods"]), - mods_json=get_json_mods(int(data["enabled_mods"]), is_classic), - is_classic=is_classic, + mods_json=get_json_mods(int(data["enabled_mods"]), is_stable), + is_stable=is_stable, score=int(data["score"]), best_combo=int(data["maxcombo"]), count_300=int(data["count300"]), @@ -595,7 +595,7 @@ def __score_data_from_ossapi( else: beatmap_id = beatmap_id_override - bitwise_mods, is_classic = get_bitwise_mods([mod.acronym for mod in score.mods]) + bitwise_mods, is_stable = get_bitwise_mods([mod.acronym for mod in score.mods]) mods_json = [] for mod in score.mods: @@ -712,8 +712,8 @@ def __score_data_from_ossapi( beatmap_id=beatmap_id, mods=bitwise_mods, mods_json=mods_json, - is_classic=is_classic, - score=score.legacy_total_score if is_classic else score.total_score, + is_stable=is_stable, + score=score.legacy_total_score if is_stable else score.total_score, best_combo=score.max_combo, count_300=count_300, count_100=count_100, diff --git a/common/osu/stubdata/osuapi/scores.json b/common/osu/stubdata/osuapi/scores.json index 9402b2cc..2627060c 100644 --- a/common/osu/stubdata/osuapi/scores.json +++ b/common/osu/stubdata/osuapi/scores.json @@ -5,7 +5,7 @@ { "beatmap_id": 362949, "mods": 72, - "is_classic": true, + "is_stable": true, "score": 3502092, "best_combo": 404, "count_300": 278, @@ -25,7 +25,7 @@ { "beatmap_id": 362949, "mods": 1024, - "is_classic": true, + "is_stable": true, "score": 2475834, "best_combo": 342, "count_300": 283, @@ -46,7 +46,7 @@ { "beatmap_id": 362949, "mods": 66, - "is_classic": true, + "is_stable": true, "score": 1718512, "best_combo": 404, "count_300": 279, @@ -66,7 +66,7 @@ { "beatmap_id": 362949, "mods": 2, - "is_classic": true, + "is_stable": true, "score": 1602032, "best_combo": 404, "count_300": 289, @@ -86,7 +86,7 @@ { "beatmap_id": 362949, "mods": 74, - "is_classic": true, + "is_stable": true, "score": 1078214, "best_combo": 311, "count_300": 274, @@ -107,7 +107,7 @@ { "beatmap_id": 362949, "mods": 1088, - "is_classic": true, + "is_stable": true, "score": 1060393, "best_combo": 180, "count_300": 263, @@ -129,7 +129,7 @@ { "beatmap_id": 362949, "mods": 1032, - "is_classic": true, + "is_stable": true, "score": 870281, "best_combo": 119, "count_300": 282, @@ -150,7 +150,7 @@ { "beatmap_id": 362949, "mods": 1600, - "is_classic": true, + "is_stable": true, "score": 681752, "best_combo": 128, "count_300": 238, @@ -172,7 +172,7 @@ { "beatmap_id": 362949, "mods": 1089, - "is_classic": true, + "is_stable": true, "score": 334024, "best_combo": 103, "count_300": 264, @@ -194,7 +194,7 @@ { "beatmap_id": 362949, "mods": 1026, - "is_classic": true, + "is_stable": true, "score": 302882, "best_combo": 94, "count_300": 259, @@ -216,7 +216,7 @@ { "beatmap_id": 362949, "mods": 1097, - "is_classic": true, + "is_stable": true, "score": 214157, "best_combo": 52, "count_300": 234, diff --git a/common/osu/stubdata/osuapi/user_best.json b/common/osu/stubdata/osuapi/user_best.json index b22d53a2..e050396e 100644 --- a/common/osu/stubdata/osuapi/user_best.json +++ b/common/osu/stubdata/osuapi/user_best.json @@ -4,7 +4,7 @@ { "beatmap_id": 307618, "mods": 104, - "is_classic": true, + "is_stable": true, "score": 137805806, "best_combo": 2757, "count_300": 1739, @@ -24,7 +24,7 @@ { "beatmap_id": 264364, "mods": 104, - "is_classic": true, + "is_stable": true, "score": 4543964, "best_combo": 448, "count_300": 346, @@ -44,7 +44,7 @@ { "beatmap_id": 323769, "mods": 104, - "is_classic": true, + "is_stable": true, "score": 10001459, "best_combo": 645, "count_300": 540, @@ -63,7 +63,7 @@ { "beatmap_id": 422328, "mods": 0, - "is_classic": false, + "is_stable": false, "score": 0, "best_combo": 127, "count_300": 333, @@ -88,7 +88,7 @@ { "beatmap_id": 1386221, "mods": 0, - "is_classic": true, + "is_stable": true, "score": 883194, "best_combo": 268, "count_300": 212, diff --git a/common/osu/stubdata/osuapi/user_recent.json b/common/osu/stubdata/osuapi/user_recent.json index 9063e89c..07755aae 100644 --- a/common/osu/stubdata/osuapi/user_recent.json +++ b/common/osu/stubdata/osuapi/user_recent.json @@ -4,7 +4,7 @@ { "beatmap_id": 209276, "mods": 8, - "is_classic": true, + "is_stable": true, "score": 1881030, "best_combo": 294, "count_300": 337, @@ -25,7 +25,7 @@ { "beatmap_id": 1117451, "mods": 8, - "is_classic": true, + "is_stable": true, "score": 2572332, "best_combo": 174, "count_300": 731, @@ -47,7 +47,7 @@ { "beatmap_id": 638017, "mods": 24, - "is_classic": true, + "is_stable": true, "score": 9393700, "best_combo": 707, "count_300": 537, diff --git a/conftest.py b/conftest.py index 302d7cfc..ccd139a4 100644 --- a/conftest.py +++ b/conftest.py @@ -123,7 +123,7 @@ def score(user_stats: UserStats, beatmap: Beatmap): best_combo=2757, perfect=False, mods=Mods.DOUBLETIME + Mods.HIDDEN + Mods.SUDDEN_DEATH, - is_classic=True, + is_stable=True, rank="SH", date=datetime(2023, 1, 1, tzinfo=timezone.utc), beatmap=beatmap, diff --git a/profiles/management/commands/refetchlazerscores.py b/profiles/management/commands/refetchlazerscores.py index cfd3eda2..5d115b10 100644 --- a/profiles/management/commands/refetchlazerscores.py +++ b/profiles/management/commands/refetchlazerscores.py @@ -9,9 +9,7 @@ class Command(BaseCommand): - help = ( - "Calculates and populates score statistics for CL scores from legacy attributes" - ) + help = "Deletes and refetches all lazer scores" def handle(self, *args, **options): self.refetch_lazer_scores(Gamemode.STANDARD) @@ -27,10 +25,10 @@ def refetch_lazer_scores(self, gamemode: Gamemode): time.sleep(0.1) beatmap_ids = list( - stats.scores.filter(is_classic=False) + stats.scores.filter(is_stable=False) .values_list("beatmap_id", flat=True) .distinct() ) - stats.scores.filter(beatmap_id__in=beatmap_ids, is_classic=False).delete() + stats.scores.filter(beatmap_id__in=beatmap_ids, is_stable=False).delete() fetch_scores(stats.user_id, beatmap_ids, gamemode) diff --git a/profiles/migrations/0025_rename_is_classic_score_is_stable.py b/profiles/migrations/0025_rename_is_classic_score_is_stable.py new file mode 100644 index 00000000..b2823a86 --- /dev/null +++ b/profiles/migrations/0025_rename_is_classic_score_is_stable.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.3 on 2024-11-30 04:13 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("profiles", "0024_score_mods_json"), + ] + + operations = [ + migrations.RenameField( + model_name="score", + old_name="is_classic", + new_name="is_stable", + ), + ] diff --git a/profiles/models.py b/profiles/models.py index 9680848d..330df8d2 100644 --- a/profiles/models.py +++ b/profiles/models.py @@ -462,7 +462,7 @@ class Score(models.Model): perfect = models.BooleanField() mods = models.IntegerField() mods_json = models.JSONField() - is_classic = models.BooleanField() + is_stable = models.BooleanField() rank = models.CharField() date = models.DateTimeField() @@ -540,7 +540,7 @@ def get_nochoke_mutation(self): count_katu=self.count_katu, mods=self.mods, mods_json=self.mods_json.copy(), - is_classic=self.is_classic, + is_stable=self.is_stable, date=self.date, beatmap=self.beatmap, user_stats=self.user_stats, diff --git a/profiles/services.py b/profiles/services.py index 94f55aeb..f03bedd2 100644 --- a/profiles/services.py +++ b/profiles/services.py @@ -333,7 +333,7 @@ def add_scores_from_data(user_stats: UserStats, score_data_list: list[ScoreData] score.perfect = score_data.perfect score.mods = score_data.mods score.mods_json = score_data.mods_json - score.is_classic = score_data.is_classic + score.is_stable = score_data.is_stable score.rank = score_data.rank score.date = score_data.date @@ -587,7 +587,7 @@ def calculate_performance_values( DifficultyCalculatorScore( beatmap_id=str(performance_calculation.score.beatmap_id), mods=performance_calculation.score.mods, - is_classic=performance_calculation.score.is_classic, + is_stable=performance_calculation.score.is_stable, statistics=performance_calculation.score.statistics, combo=performance_calculation.score.best_combo, ) From 54a04f8a7ef4921ce87735aa0e9c0c46a7fef264 Mon Sep 17 00:00:00 2001 From: Samuel Cattini-Schultz Date: Sat, 30 Nov 2024 16:42:17 +1100 Subject: [PATCH 3/6] Add mods_json to stub data --- common/osu/stubdata/osuapi/scores.json | 127 ++++++++++++++++++++ common/osu/stubdata/osuapi/user_best.json | 48 ++++++++ common/osu/stubdata/osuapi/user_recent.json | 27 +++++ conftest.py | 6 + 4 files changed, 208 insertions(+) diff --git a/common/osu/stubdata/osuapi/scores.json b/common/osu/stubdata/osuapi/scores.json index 2627060c..ddf5acdb 100644 --- a/common/osu/stubdata/osuapi/scores.json +++ b/common/osu/stubdata/osuapi/scores.json @@ -5,6 +5,17 @@ { "beatmap_id": 362949, "mods": 72, + "mods_json": [ + { + "acronym": "HD" + }, + { + "acronym": "DT" + }, + { + "acronym": "CL" + } + ], "is_stable": true, "score": 3502092, "best_combo": 404, @@ -25,6 +36,14 @@ { "beatmap_id": 362949, "mods": 1024, + "mods_json": [ + { + "acronym": "FL" + }, + { + "acronym": "CL" + } + ], "is_stable": true, "score": 2475834, "best_combo": 342, @@ -46,6 +65,17 @@ { "beatmap_id": 362949, "mods": 66, + "mods_json": [ + { + "acronym": "EZ" + }, + { + "acronym": "DT" + }, + { + "acronym": "CL" + } + ], "is_stable": true, "score": 1718512, "best_combo": 404, @@ -66,6 +96,14 @@ { "beatmap_id": 362949, "mods": 2, + "mods_json": [ + { + "acronym": "EZ" + }, + { + "acronym": "CL" + } + ], "is_stable": true, "score": 1602032, "best_combo": 404, @@ -86,6 +124,20 @@ { "beatmap_id": 362949, "mods": 74, + "mods_json": [ + { + "acronym": "EZ" + }, + { + "acronym": "HD" + }, + { + "acronym": "DT" + }, + { + "acronym": "CL" + } + ], "is_stable": true, "score": 1078214, "best_combo": 311, @@ -107,6 +159,17 @@ { "beatmap_id": 362949, "mods": 1088, + "mods_json": [ + { + "acronym": "DT" + }, + { + "acronym": "FL" + }, + { + "acronym": "CL" + } + ], "is_stable": true, "score": 1060393, "best_combo": 180, @@ -129,6 +192,17 @@ { "beatmap_id": 362949, "mods": 1032, + "mods_json": [ + { + "acronym": "HD" + }, + { + "acronym": "FL" + }, + { + "acronym": "CL" + } + ], "is_stable": true, "score": 870281, "best_combo": 119, @@ -150,6 +224,17 @@ { "beatmap_id": 362949, "mods": 1600, + "mods_json": [ + { + "acronym": "NC" + }, + { + "acronym": "FL" + }, + { + "acronym": "CL" + } + ], "is_stable": true, "score": 681752, "best_combo": 128, @@ -172,6 +257,20 @@ { "beatmap_id": 362949, "mods": 1089, + "mods_json": [ + { + "acronym": "NF" + }, + { + "acronym": "DT" + }, + { + "acronym": "FL" + }, + { + "acronym": "CL" + } + ], "is_stable": true, "score": 334024, "best_combo": 103, @@ -194,6 +293,17 @@ { "beatmap_id": 362949, "mods": 1026, + "mods_json": [ + { + "acronym": "EZ" + }, + { + "acronym": "FL" + }, + { + "acronym": "CL" + } + ], "is_stable": true, "score": 302882, "best_combo": 94, @@ -216,6 +326,23 @@ { "beatmap_id": 362949, "mods": 1097, + "mods_json": [ + { + "acronym": "NF" + }, + { + "acronym": "HD" + }, + { + "acronym": "DT" + }, + { + "acronym": "FL" + }, + { + "acronym": "CL" + } + ], "is_stable": true, "score": 214157, "best_combo": 52, diff --git a/common/osu/stubdata/osuapi/user_best.json b/common/osu/stubdata/osuapi/user_best.json index e050396e..67eb5df0 100644 --- a/common/osu/stubdata/osuapi/user_best.json +++ b/common/osu/stubdata/osuapi/user_best.json @@ -4,6 +4,20 @@ { "beatmap_id": 307618, "mods": 104, + "mods_json": [ + { + "acronym": "HD" + }, + { + "acronym": "SD" + }, + { + "acronym": "DT" + }, + { + "acronym": "CL" + } + ], "is_stable": true, "score": 137805806, "best_combo": 2757, @@ -24,6 +38,20 @@ { "beatmap_id": 264364, "mods": 104, + "mods_json": [ + { + "acronym": "HD" + }, + { + "acronym": "SD" + }, + { + "acronym": "DT" + }, + { + "acronym": "CL" + } + ], "is_stable": true, "score": 4543964, "best_combo": 448, @@ -44,6 +72,20 @@ { "beatmap_id": 323769, "mods": 104, + "mods_json": [ + { + "acronym": "HD" + }, + { + "acronym": "SD" + }, + { + "acronym": "DT" + }, + { + "acronym": "CL" + } + ], "is_stable": true, "score": 10001459, "best_combo": 645, @@ -63,6 +105,7 @@ { "beatmap_id": 422328, "mods": 0, + "mods_json": [], "is_stable": false, "score": 0, "best_combo": 127, @@ -88,6 +131,11 @@ { "beatmap_id": 1386221, "mods": 0, + "mods_json": [ + { + "acronym": "CL" + } + ], "is_stable": true, "score": 883194, "best_combo": 268, diff --git a/common/osu/stubdata/osuapi/user_recent.json b/common/osu/stubdata/osuapi/user_recent.json index 07755aae..21665076 100644 --- a/common/osu/stubdata/osuapi/user_recent.json +++ b/common/osu/stubdata/osuapi/user_recent.json @@ -4,6 +4,14 @@ { "beatmap_id": 209276, "mods": 8, + "mods_json": [ + { + "acronym": "HD" + }, + { + "acronym": "CL" + } + ], "is_stable": true, "score": 1881030, "best_combo": 294, @@ -25,6 +33,14 @@ { "beatmap_id": 1117451, "mods": 8, + "mods_json": [ + { + "acronym": "HD" + }, + { + "acronym": "CL" + } + ], "is_stable": true, "score": 2572332, "best_combo": 174, @@ -47,6 +63,17 @@ { "beatmap_id": 638017, "mods": 24, + "mods_json": [ + { + "acronym": "HD" + }, + { + "acronym": "HR" + }, + { + "acronym": "CL" + } + ], "is_stable": true, "score": 9393700, "best_combo": 707, diff --git a/conftest.py b/conftest.py index ccd139a4..7969be45 100644 --- a/conftest.py +++ b/conftest.py @@ -123,6 +123,12 @@ def score(user_stats: UserStats, beatmap: Beatmap): best_combo=2757, perfect=False, mods=Mods.DOUBLETIME + Mods.HIDDEN + Mods.SUDDEN_DEATH, + mods_json=[ + {"acronym": "DT"}, + {"acronym": "HD"}, + {"acronym": "SD"}, + {"acronym": "CL"}, + ], is_stable=True, rank="SH", date=datetime(2023, 1, 1, tzinfo=timezone.utc), From e81cf9603623b965a42a6d6d59d4ab65bc45d1d4 Mon Sep 17 00:00:00 2001 From: Samuel Cattini-Schultz Date: Sat, 30 Nov 2024 15:21:28 +1100 Subject: [PATCH 4/6] Use legacy_score_id to determine stable scores --- common/osu/osuapi.py | 4 +++- common/osu/utils.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/common/osu/osuapi.py b/common/osu/osuapi.py index b5606de4..142c5f03 100644 --- a/common/osu/osuapi.py +++ b/common/osu/osuapi.py @@ -595,7 +595,9 @@ def __score_data_from_ossapi( else: beatmap_id = beatmap_id_override - bitwise_mods, is_stable = get_bitwise_mods([mod.acronym for mod in score.mods]) + is_stable = score.legacy_score_id is not None + + bitwise_mods = get_bitwise_mods([mod.acronym for mod in score.mods]) mods_json = [] for mod in score.mods: diff --git a/common/osu/utils.py b/common/osu/utils.py index af7ede43..4365e8ca 100644 --- a/common/osu/utils.py +++ b/common/osu/utils.py @@ -275,7 +275,7 @@ def get_json_mods(mods: int, add_classic: bool) -> list[dict]: return json_mods -def get_bitwise_mods(acronyms: list[str]) -> tuple[int, bool]: +def get_bitwise_mods(acronyms: list[str]) -> int: bitwise_mods = 0 for acronym in acronyms: for mod, mod_acronym in mod_acronyms.items(): @@ -283,4 +283,4 @@ def get_bitwise_mods(acronyms: list[str]) -> tuple[int, bool]: bitwise_mods |= mod break - return bitwise_mods, "CL" in acronyms + return bitwise_mods From 19c583eff7bc13f64bbbfd11211a2eb032f0ab6a Mon Sep 17 00:00:00 2001 From: Samuel Cattini-Schultz Date: Sat, 30 Nov 2024 15:23:54 +1100 Subject: [PATCH 5/6] Add temporary backfilljsonmods command --- .../management/commands/backfilljsonmods.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 profiles/management/commands/backfilljsonmods.py diff --git a/profiles/management/commands/backfilljsonmods.py b/profiles/management/commands/backfilljsonmods.py new file mode 100644 index 00000000..16b9e39a --- /dev/null +++ b/profiles/management/commands/backfilljsonmods.py @@ -0,0 +1,22 @@ +from django.core.management.base import BaseCommand +from django.core.paginator import Paginator +from tqdm import tqdm + +from common.osu.utils import get_json_mods +from profiles.models import Score + + +class Command(BaseCommand): + help = "Generates json mods for stable scores from legacy attributes" + + def handle(self, *args, **options): + scores = Score.objects.filter(is_stable=True).order_by("id") + paginator = Paginator(scores, per_page=2000) + + with tqdm(total=scores.count(), smoothing=0) as pbar: + for page in paginator: + for score in page: + score.mods_json = get_json_mods(score.mods, score.is_stable) + pbar.update() + + Score.objects.bulk_update(page, ["mods_json"]) From c48258f84e9c0ce8d1062e8e65c1f11a10e85d0b Mon Sep 17 00:00:00 2001 From: Samuel Cattini-Schultz Date: Sat, 30 Nov 2024 17:06:20 +1100 Subject: [PATCH 6/6] Fix get_bitwise_mods not including DT/SD for NC/PF --- common/osu/test_utils.py | 42 ++++++++++++++++++++++++++++++++++++++++ common/osu/utils.py | 5 +++++ 2 files changed, 47 insertions(+) diff --git a/common/osu/test_utils.py b/common/osu/test_utils.py index 45de7d6a..7295d16f 100644 --- a/common/osu/test_utils.py +++ b/common/osu/test_utils.py @@ -2,11 +2,13 @@ from common.osu.utils import ( calculate_pp_total, get_ar, + get_bitwise_mods, get_bpm, get_classic_accuracy, get_cs, get_gamemode_from_gamemode_string, get_gamemode_string_from_gamemode, + get_json_mods, get_length, get_mods_string, get_od, @@ -139,3 +141,43 @@ def test_get_mods_string(): assert ( get_mods_string(Mods.HIDDEN + Mods.DOUBLETIME + Mods.SUDDEN_DEATH) == "HD,SD,DT" ) + + +def test_get_json_mods(): + assert get_json_mods(Mods.HIDDEN + Mods.SUDDEN_DEATH + Mods.DOUBLETIME, True) == [ + {"acronym": "HD"}, + {"acronym": "SD"}, + {"acronym": "DT"}, + {"acronym": "CL"}, + ] + + assert get_json_mods( + Mods.HARDROCK + + Mods.FLASHLIGHT + + Mods.SUDDEN_DEATH + + Mods.PERFECT + + Mods.DOUBLETIME + + Mods.NIGHTCORE, + False, + ) == [ + {"acronym": "HR"}, + {"acronym": "NC"}, + {"acronym": "FL"}, + {"acronym": "PF"}, + ] + + +def test_get_bitwise_mods(): + assert ( + get_bitwise_mods(["HD", "DT", "SD", "CL"]) + == Mods.HIDDEN + Mods.SUDDEN_DEATH + Mods.DOUBLETIME + ) + assert ( + get_bitwise_mods(["HR", "FL", "PF", "NC"]) + == Mods.HARDROCK + + Mods.FLASHLIGHT + + Mods.SUDDEN_DEATH + + Mods.PERFECT + + Mods.DOUBLETIME + + Mods.NIGHTCORE + ) diff --git a/common/osu/utils.py b/common/osu/utils.py index 4365e8ca..352f4677 100644 --- a/common/osu/utils.py +++ b/common/osu/utils.py @@ -283,4 +283,9 @@ def get_bitwise_mods(acronyms: list[str]) -> int: bitwise_mods |= mod break + if bitwise_mods & Mods.NIGHTCORE: + bitwise_mods |= Mods.DOUBLETIME + if bitwise_mods & Mods.PERFECT: + bitwise_mods |= Mods.SUDDEN_DEATH + return bitwise_mods