Backported hit judgements, fixed bugs

This commit is contained in:
nise.moe 2024-02-24 19:29:10 +01:00
parent 79557b170a
commit 59a2d0448a
5 changed files with 92 additions and 59 deletions

View File

@ -207,8 +207,8 @@ class SearchController(
InternalSchemaField("replay_id", "Replay ID", Category.score, Type.number, false, "identifier for replay"),
InternalSchemaField("score", "Score", Category.score, Type.number, false, "score value"),
InternalSchemaField("ur", "UR", Category.score, Type.number, false, "unstable rate"),
InternalSchemaField("frametime", "Frame Time", Category.score, Type.number, false, "average frame time during play"),
InternalSchemaField("edge_hits", "Edge Hits", Category.score, Type.number, false, "hits at the edge of hit window"),
InternalSchemaField("frametime", "Frame Time", Category.score, Type.number, false, "median frame time during play"),
InternalSchemaField("edge_hits", "Edge Hits", Category.score, Type.number, false, "hits at the edge of the hitobject (<1px)"),
InternalSchemaField("snaps", "Snaps", Category.score, Type.number, false, "rapid cursor movements"),
InternalSchemaField("adjusted_ur", "Adj. UR", Category.score, Type.number, true, "adjusted unstable rate"),
InternalSchemaField("mean_error", "Mean Error", Category.score, Type.number, false, "average timing error"),

View File

@ -1,10 +1,8 @@
package com.nisemoe.nise.database
import com.nisemoe.generated.tables.records.ScoresJudgementsRecord
import com.nisemoe.generated.tables.records.ScoresRecord
import com.nisemoe.generated.tables.references.BEATMAPS
import com.nisemoe.generated.tables.references.SCORES
import com.nisemoe.generated.tables.references.SCORES_SIMILARITY
import com.nisemoe.generated.tables.references.USERS
import com.nisemoe.generated.tables.references.*
import com.nisemoe.nise.*
import com.nisemoe.nise.osu.Mod
import com.nisemoe.nise.service.AuthService
@ -374,9 +372,11 @@ class ScoreService(
val judgementsRecord = dslContext.select(SCORES.JUDGEMENTS)
.from(SCORES)
.where(SCORES.ID.eq(scoreId))
.fetchOneInto(ScoresRecord::class.java) ?: return emptyMap()
.fetchOneInto(ScoresRecord::class.java)
if(judgementsRecord.judgements == null) return emptyMap()
if(judgementsRecord?.judgements == null) {
return this.getHitDistributionLegacy(scoreId)
}
val judgements = compressJudgements.deserialize(judgementsRecord.judgements!!)
@ -404,4 +404,33 @@ class ScoreService(
}
}
fun getHitDistributionLegacy(scoreId: Int): Map<Int, DistributionEntry> {
val judgements = dslContext.selectFrom(SCORES_JUDGEMENTS)
.where(SCORES_JUDGEMENTS.SCORE_ID.eq(scoreId))
.fetchInto(ScoresJudgementsRecord::class.java)
val errorDistribution = mutableMapOf<Int, MutableMap<String, Int>>()
var totalHits = 0
judgements.forEach { hit ->
val error = (hit.error!!.roundToInt() / 2) * 2
val judgementType = hit.type // Assuming this is how you get the judgement type
errorDistribution.getOrPut(error) { mutableMapOf("Miss" to 0, "300" to 0, "100" to 0, "50" to 0) }
.apply {
this[judgementType.toString()] = this.getOrDefault(judgementType.toString(), 0) + 1
}
totalHits += 1
}
return errorDistribution.mapValues { (_, judgementCounts) ->
judgementCounts.values.sum()
DistributionEntry(
percentageMiss = (judgementCounts.getOrDefault("Miss", 0).toDouble() / totalHits) * 100,
percentage300 = (judgementCounts.getOrDefault("300", 0).toDouble() / totalHits) * 100,
percentage100 = (judgementCounts.getOrDefault("100", 0).toDouble() / totalHits) * 100,
percentage50 = (judgementCounts.getOrDefault("50", 0).toDouble() / totalHits) * 100
)
}
}
}

View File

@ -9,7 +9,7 @@
<ng-container *ngFor="let field of fields">
<div *ngIf="field.category === category">
<label>
<input type="checkbox" [(ngModel)]="field.active" (change)="this.saveColumnsStatusToLocalStorage()"/>
<input type="checkbox" [(ngModel)]="field.active" (change)="this.saveSettingsToLocalStorage()"/>
{{ field.name }} <span class="text-muted" style="margin-left: 6px">{{ field.description }}</span>
</label>
</div>
@ -46,7 +46,7 @@
<div class="text-center mt-2">
<button (click)="exportSettings()">Export settings</button>
<button (click)="fileInput.click()" style="margin-left: 5px">Import settings</button>
<input type="file" #fileInput style="display: none" (change)="importSettings($event)" accept=".json">
<input type="file" #fileInput style="display: none" (change)="uploadSettingsFile($event)" accept=".json">
</div>
<div class="text-center mt-1">
<button (click)="search()" class="mb-2" style="font-size: 18px">Search</button>

View File

@ -9,7 +9,6 @@ import {Field, Query, QueryBuilderComponent} from "../../corelib/components/quer
import {RouterLink} from "@angular/router";
import {CalculatePageRangePipe} from "../../corelib/calculate-page-range.pipe";
import {DownloadFilesService} from "../../corelib/service/download-files.service";
import {LocalCacheService} from "../../corelib/service/local-cache.service";
interface SchemaField {
name: string;
@ -99,7 +98,6 @@ interface SearchResponseEntry {
export class SearchComponent implements OnInit {
constructor(private httpClient: HttpClient,
private localCacheService: LocalCacheService,
public downloadFilesService: DownloadFilesService) { }
isError = false;
@ -124,29 +122,24 @@ export class SearchComponent implements OnInit {
}
private loadPreviousFromLocalStorage() {
const storedQueries = localStorage.getItem('search_queries');
const storedQueries = localStorage.getItem('search_settings');
if (storedQueries) {
this.queries = JSON.parse(storedQueries);
} else {
this.queries = [];
}
// Load active/inactive status from localStorage
const storedStatus = localStorage.getItem('columns_status');
if (storedStatus) {
const statusMap = JSON.parse(storedStatus);
let queries1 = JSON.parse(storedQueries);
this.queries = queries1.queries;
this.sortingOrder = queries1.sortingOrder;
this.fields.forEach(field => {
if (statusMap.hasOwnProperty(field.name)) {
field.active = statusMap[field.name];
if (queries1.columns.hasOwnProperty(field.name)) {
field.active = queries1.columns[field.name];
}
});
}
} else {
this.queries = [];
this.sortingOrder = {
field: 'user_id',
order: 'ASC'
};
}
}
deselectEntireFieldCategory(categoryName: string): void {
this.fields.forEach(field => {
@ -154,7 +147,7 @@ export class SearchComponent implements OnInit {
field.active = false;
}
});
this.saveColumnsStatusToLocalStorage();
this.saveSettingsToLocalStorage();
}
selectEntireFieldCategory(categoryName: string): void {
@ -163,7 +156,7 @@ export class SearchComponent implements OnInit {
field.active = true;
}
});
this.saveColumnsStatusToLocalStorage();
this.saveSettingsToLocalStorage();
}
mapSchemaFieldsToFields(): Field[] {
@ -175,36 +168,55 @@ export class SearchComponent implements OnInit {
});
}
updateLocalStorage(): void {
console.warn('Updating local storage');
localStorage.setItem('search_queries', JSON.stringify(this.queries));
private serializeSettings() {
return {
queries: this.queries,
sortingOrder: this.sortingOrder,
columns: this.getColumnSettings()
};
}
private getColumnSettings() {
return this.fields.reduce<{ [key: string]: boolean }>((acc, field) => {
acc[field.name] = field.active;
return acc;
}, {});
}
saveSettingsToLocalStorage(): void {
const settings = this.serializeSettings();
localStorage.setItem('search_settings', JSON.stringify(settings));
}
exportSettings(): void {
const settings = {
queries: this.queries,
sorting: this.sortingOrder
} as any;
this.downloadFilesService.downloadJSON(settings, 'nise-settings');
const settings = this.serializeSettings();
this.downloadFilesService.downloadJSON(settings as any, 'nise-settings');
}
importSettings(json: any): void {
if (this.verifySchema(json)) {
this.queries = json.queries;
this.sortingOrder = json.sortingOrder;
this.fields.forEach(field => {
if (json.columns.hasOwnProperty(field.name)) {
field.active = json.columns[field.name];
}
});
}
}
getColumns(): string[] {
return this.fields.map(field => field.name);
}
importSettings(event: any): void {
uploadSettingsFile(event: any): void {
const file = event.target.files[0];
if (file) {
const fileReader = new FileReader();
fileReader.onload = (e) => {
try {
const json = JSON.parse(fileReader.result as string);
if (this.verifySchema(json)) {
this.queries = json.queries;
this.sortingOrder = json.sorting;
} else {
console.error('Invalid file schema');
}
this.importSettings(json);
} catch (error) {
console.error('Error parsing JSON', error);
}
@ -215,16 +227,7 @@ export class SearchComponent implements OnInit {
verifySchema(json: any): boolean {
// TODO: Implement schema verification logic here
return 'queries' in json && 'sorting' in json;
}
saveColumnsStatusToLocalStorage(): void {
const statusMap = this.fields.reduce<{ [key: string]: boolean }>((acc, field) => {
acc[field.name] = field.active;
return acc;
}, {});
localStorage.setItem('columns_status', JSON.stringify(statusMap));
return 'queries' in json && 'sortingOrder' in json && 'columns' in json;
}
search(pageNumber: number = 1): void {
@ -243,12 +246,12 @@ export class SearchComponent implements OnInit {
this.response = response;
this.isLoading = false;
},
error: (error) => {
error: () => {
this.isError = true;
this.isLoading = false;
}
});
this.updateLocalStorage();
this.saveSettingsToLocalStorage();
}
// Add this method to the SearchComponent class
@ -258,4 +261,5 @@ export class SearchComponent implements OnInit {
protected readonly countryCodeToFlag = countryCodeToFlag;
protected readonly Math = Math;
}

View File

@ -110,7 +110,7 @@ export class ViewScoreComponent implements OnInit {
let errorDistribution = Object.entries(this.replayData.error_distribution);
if (errorDistribution.length > 1) {
if (errorDistribution.length >= 1) {
const sortedEntries = errorDistribution
.sort((a, b) => parseInt(a[0]) - parseInt(b[0]));