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("replay_id", "Replay ID", Category.score, Type.number, false, "identifier for replay"),
InternalSchemaField("score", "Score", Category.score, Type.number, false, "score value"), InternalSchemaField("score", "Score", Category.score, Type.number, false, "score value"),
InternalSchemaField("ur", "UR", Category.score, Type.number, false, "unstable rate"), 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("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 hit window"), 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("snaps", "Snaps", Category.score, Type.number, false, "rapid cursor movements"),
InternalSchemaField("adjusted_ur", "Adj. UR", Category.score, Type.number, true, "adjusted unstable rate"), 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"), InternalSchemaField("mean_error", "Mean Error", Category.score, Type.number, false, "average timing error"),

View File

@ -1,10 +1,8 @@
package com.nisemoe.nise.database package com.nisemoe.nise.database
import com.nisemoe.generated.tables.records.ScoresJudgementsRecord
import com.nisemoe.generated.tables.records.ScoresRecord import com.nisemoe.generated.tables.records.ScoresRecord
import com.nisemoe.generated.tables.references.BEATMAPS import com.nisemoe.generated.tables.references.*
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.nise.* import com.nisemoe.nise.*
import com.nisemoe.nise.osu.Mod import com.nisemoe.nise.osu.Mod
import com.nisemoe.nise.service.AuthService import com.nisemoe.nise.service.AuthService
@ -374,9 +372,11 @@ class ScoreService(
val judgementsRecord = dslContext.select(SCORES.JUDGEMENTS) val judgementsRecord = dslContext.select(SCORES.JUDGEMENTS)
.from(SCORES) .from(SCORES)
.where(SCORES.ID.eq(scoreId)) .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!!) 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"> <ng-container *ngFor="let field of fields">
<div *ngIf="field.category === category"> <div *ngIf="field.category === category">
<label> <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> {{ field.name }} <span class="text-muted" style="margin-left: 6px">{{ field.description }}</span>
</label> </label>
</div> </div>
@ -46,7 +46,7 @@
<div class="text-center mt-2"> <div class="text-center mt-2">
<button (click)="exportSettings()">Export settings</button> <button (click)="exportSettings()">Export settings</button>
<button (click)="fileInput.click()" style="margin-left: 5px">Import 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>
<div class="text-center mt-1"> <div class="text-center mt-1">
<button (click)="search()" class="mb-2" style="font-size: 18px">Search</button> <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 {RouterLink} from "@angular/router";
import {CalculatePageRangePipe} from "../../corelib/calculate-page-range.pipe"; import {CalculatePageRangePipe} from "../../corelib/calculate-page-range.pipe";
import {DownloadFilesService} from "../../corelib/service/download-files.service"; import {DownloadFilesService} from "../../corelib/service/download-files.service";
import {LocalCacheService} from "../../corelib/service/local-cache.service";
interface SchemaField { interface SchemaField {
name: string; name: string;
@ -99,7 +98,6 @@ interface SearchResponseEntry {
export class SearchComponent implements OnInit { export class SearchComponent implements OnInit {
constructor(private httpClient: HttpClient, constructor(private httpClient: HttpClient,
private localCacheService: LocalCacheService,
public downloadFilesService: DownloadFilesService) { } public downloadFilesService: DownloadFilesService) { }
isError = false; isError = false;
@ -124,28 +122,23 @@ export class SearchComponent implements OnInit {
} }
private loadPreviousFromLocalStorage() { private loadPreviousFromLocalStorage() {
const storedQueries = localStorage.getItem('search_queries'); const storedQueries = localStorage.getItem('search_settings');
if (storedQueries) { if (storedQueries) {
this.queries = JSON.parse(storedQueries); let queries1 = JSON.parse(storedQueries);
} else { this.queries = queries1.queries;
this.queries = []; this.sortingOrder = queries1.sortingOrder;
}
// Load active/inactive status from localStorage
const storedStatus = localStorage.getItem('columns_status');
if (storedStatus) {
const statusMap = JSON.parse(storedStatus);
this.fields.forEach(field => { this.fields.forEach(field => {
if (statusMap.hasOwnProperty(field.name)) { if (queries1.columns.hasOwnProperty(field.name)) {
field.active = statusMap[field.name]; field.active = queries1.columns[field.name];
} }
}); });
} else {
this.queries = [];
this.sortingOrder = {
field: 'user_id',
order: 'ASC'
};
} }
this.sortingOrder = {
field: 'user_id',
order: 'ASC'
};
} }
deselectEntireFieldCategory(categoryName: string): void { deselectEntireFieldCategory(categoryName: string): void {
@ -154,7 +147,7 @@ export class SearchComponent implements OnInit {
field.active = false; field.active = false;
} }
}); });
this.saveColumnsStatusToLocalStorage(); this.saveSettingsToLocalStorage();
} }
selectEntireFieldCategory(categoryName: string): void { selectEntireFieldCategory(categoryName: string): void {
@ -163,7 +156,7 @@ export class SearchComponent implements OnInit {
field.active = true; field.active = true;
} }
}); });
this.saveColumnsStatusToLocalStorage(); this.saveSettingsToLocalStorage();
} }
mapSchemaFieldsToFields(): Field[] { mapSchemaFieldsToFields(): Field[] {
@ -175,36 +168,55 @@ export class SearchComponent implements OnInit {
}); });
} }
updateLocalStorage(): void { private serializeSettings() {
console.warn('Updating local storage'); return {
localStorage.setItem('search_queries', JSON.stringify(this.queries)); 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 { exportSettings(): void {
const settings = { const settings = this.serializeSettings();
queries: this.queries, this.downloadFilesService.downloadJSON(settings as any, 'nise-settings');
sorting: this.sortingOrder }
} as any;
this.downloadFilesService.downloadJSON(settings, '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[] { getColumns(): string[] {
return this.fields.map(field => field.name); return this.fields.map(field => field.name);
} }
importSettings(event: any): void { uploadSettingsFile(event: any): void {
const file = event.target.files[0]; const file = event.target.files[0];
if (file) { if (file) {
const fileReader = new FileReader(); const fileReader = new FileReader();
fileReader.onload = (e) => { fileReader.onload = (e) => {
try { try {
const json = JSON.parse(fileReader.result as string); const json = JSON.parse(fileReader.result as string);
if (this.verifySchema(json)) { this.importSettings(json);
this.queries = json.queries;
this.sortingOrder = json.sorting;
} else {
console.error('Invalid file schema');
}
} catch (error) { } catch (error) {
console.error('Error parsing JSON', error); console.error('Error parsing JSON', error);
} }
@ -215,16 +227,7 @@ export class SearchComponent implements OnInit {
verifySchema(json: any): boolean { verifySchema(json: any): boolean {
// TODO: Implement schema verification logic here // TODO: Implement schema verification logic here
return 'queries' in json && 'sorting' in json; return 'queries' in json && 'sortingOrder' in json && 'columns' 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));
} }
search(pageNumber: number = 1): void { search(pageNumber: number = 1): void {
@ -243,12 +246,12 @@ export class SearchComponent implements OnInit {
this.response = response; this.response = response;
this.isLoading = false; this.isLoading = false;
}, },
error: (error) => { error: () => {
this.isError = true; this.isError = true;
this.isLoading = false; this.isLoading = false;
} }
}); });
this.updateLocalStorage(); this.saveSettingsToLocalStorage();
} }
// Add this method to the SearchComponent class // Add this method to the SearchComponent class
@ -258,4 +261,5 @@ export class SearchComponent implements OnInit {
protected readonly countryCodeToFlag = countryCodeToFlag; protected readonly countryCodeToFlag = countryCodeToFlag;
protected readonly Math = Math; protected readonly Math = Math;
} }

View File

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