fix: folder-to-go metric is wrong (#1681)

This commit is contained in:
zk
2026-06-17 22:10:40 +01:00
committed by GitHub
parent 245b7e454e
commit f6ba08b6c9
2 changed files with 90 additions and 21 deletions
@@ -69,43 +69,62 @@ export async function GetSessionFolderRaises(
}
for (const [metric, conf] of Object.entries(enumMetrics)) {
if (!info.isNewScore) {
const enumIndexes = score.scoreData.enumIndexes;
const newIdx = enumIndexes?.[metric as keyof typeof enumIndexes];
if (newIdx === undefined) {
continue;
}
// Index of this metric before this score. New scores had no previous
// PB, so they crossed every threshold from the bottom.
let oldIdx: number;
if (info.isNewScore) {
oldIdx = -1;
} else {
const delta = info.deltas[metric];
if (delta === undefined || delta <= 0) {
continue;
}
oldIdx = newIdx - delta;
}
const enumIndexes = score.scoreData.enumIndexes;
const idx = enumIndexes?.[metric as keyof typeof enumIndexes];
const minIdx = conf.values.indexOf(conf.minimumRelevantValue);
if (idx === undefined) {
continue;
}
// This view is cumulative: reaching e.g. EX HARD CLEAR also counts as
// a HARD CLEAR (and CLEAR, ...). So this score raised every enum
// value-or-better bucket it crossed, i.e. every threshold in
// (oldIdx, newIdx]. Only surface thresholds above the
// minimum-relevant value (so we don't show NO PLAY / FAILED rows).
const startIdx = Math.max(oldIdx, minIdx) + 1;
if (idx <= conf.values.indexOf(conf.minimumRelevantValue)) {
continue;
}
const valueRaw = (score.scoreData as Record<string, unknown>)[metric];
if (typeof valueRaw !== "string") {
if (startIdx > newIdx) {
continue;
}
const folderIds = await folderIdsForChartCached(score.chartID, chartFolderCache);
for (const folderId of folderIds) {
const key = bucketKey(folderId, metric, valueRaw);
let set = bucket.get(key);
for (let vi = startIdx; vi <= newIdx; vi++) {
const value = conf.values[vi];
if (!set) {
set = new Set();
bucket.set(key, set);
if (value === undefined) {
continue;
}
set.add(score.chartID);
for (const folderId of folderIds) {
const key = bucketKey(folderId, metric, value);
let set = bucket.get(key);
if (!set) {
set = new Set();
bucket.set(key, set);
}
set.add(score.chartID);
}
}
}
}
@@ -143,7 +162,10 @@ export async function GetSessionFolderRaises(
continue;
}
const previousCount = dist.enumDist[metric]?.[value] ?? 0;
// Cumulative: how many charts were already at this value-or-better before
// the session. Pairs with the cumulative raise buckets above so that
// `previousCount + raisedCharts.length` is the true value-or-better total.
const previousCount = dist.cumulativeEnumDist[metric]?.[value] ?? 0;
out.push({
folder,
@@ -165,6 +165,53 @@ describe("GET /api/v1/sessions/:sessionID/folder-raises", () => {
expect(typeof hit.type).toBe("string");
expect(typeof hit.value).toBe("string");
});
it("treats raises cumulatively (an EX HARD CLEAR is also a HARD CLEAR)", async () => {
const { sessionId } = await seedSessionFixture();
const folderId = `F_folder_sess_cumulative_${sessionId}`;
await DB.insertInto("folder")
.values({
id: folderId,
legacy_id: folderId,
game: "iidx-sp",
inactive: false,
title: "Session Folder Cumulative Test",
slug: folderId,
where: `chart.id = '${CHART_PG}'`,
version_filter: null,
search_terms: [],
})
.execute();
await DB.insertInto("folder_chart_lookup")
.values({ folder_id: folderId, chart_id: CHART_PG })
.execute();
const res = await mockApi.get(`/api/v1/sessions/${sessionId}/folder-raises`);
expect(res.status).toBe(200);
expect(res.body.success).toBe(true);
const lampRows = (
res.body.body as Array<{
folder: { folderID: string };
raisedCharts: Array<string>;
type: string;
value: string;
}>
).filter((r) => r.folder.folderID === folderId && r.type === "lamp");
// The seeded score is an EX HARD CLEAR, so it should raise the EX HARD
// CLEAR bucket *and* the HARD CLEAR (and CLEAR) value-or-better buckets.
const exhc = lampRows.find((r) => r.value === "EX HARD CLEAR");
const hc = lampRows.find((r) => r.value === "HARD CLEAR");
expect(exhc).toBeDefined();
expect(exhc?.raisedCharts).toContain(CHART_PG);
expect(hc).toBeDefined();
expect(hc?.raisedCharts).toContain(CHART_PG);
});
});
describe("GET /api/v1/sessions/:sessionID/adjacent", () => {