Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion src/services/StatisticsService.js
Original file line number Diff line number Diff line change
Expand Up @@ -1265,6 +1265,21 @@ function filterUnifiedHistoryRowsToCompletedChallenges (rows, challengeMetadataB
})
}

/**
* Check whether a history row belongs to a configured rating path.
* Rating-path rows intentionally keep their stored type even when the source
* challenge was a native Challenge or Marathon Match event.
* @param {Object} row unified history row annotated with stored track/type names
* @returns {boolean} true when the row should preserve its stored rating-path dimensions
*/
function isConfiguredRatingPathHistoryRow (row) {
return !!(
getConfiguredRatingPath(config.RATING_PATHS, row && row.typeName) ||
getConfiguredRatingPath(config.RATING_PATHS, row && row.typeId) ||
getConfiguredRatingPathByTypeId(config.RATING_PATHS, row && row.typeId)
)
}

/**
* Attach canonical challenge ids and names to unified history rows before shaping
* the response payload consumed by the profiles UI.
Expand Down Expand Up @@ -1292,7 +1307,7 @@ function enrichUnifiedHistoryRowsWithChallengeMetadata (rows, challengeMetadataB
: _.get(challenge, 'legacyRecord.legacySystemId')
)
const preserveLegacyChallengeId = isLegacyNumericMarathonHistoryRow(row)
const dimension = challenge.trackId && challenge.typeId
const dimension = !isConfiguredRatingPathHistoryRow(row) && challenge.trackId && challenge.typeId
? resolveStatsDimensionForChallengeRow({
trackId: String(challenge.trackId),
typeId: String(challenge.typeId)
Expand Down
60 changes: 60 additions & 0 deletions test/unit/StatisticsService.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,66 @@ describe('statistics service unit tests', () => {
}
})

it('getHistoryStats should preserve configured rating path dimensions after challenge metadata enrichment', async () => {
const ratingDate = new Date('2026-06-18T05:41:34.931Z')
const { service, restore } = loadStatisticsService({
ratingPaths: [
{ name: 'AI Engineering', track: 'DATA_SCIENCE', tags: ['AI', 'AI Exponential League'] }
],
prismaStub: {
$queryRaw: async () => [],
memberStats: {
findMany: async () => [{
trackId: 'track-ds-id',
typeId: 'rating-path-ai-engineering',
challenges: 1,
mostRecentEventDate: ratingDate
}]
},
memberStatsHistory: {
findMany: async () => [{
trackId: 'track-ds-id',
typeId: 'rating-path-ai-engineering',
challengeId: 'ai-history-challenge',
challengeName: null,
newRating: 840,
eventDate: ratingDate,
placement: 1,
mostRecent: true
}]
}
},
challengeRows: [{
id: 'ai-history-challenge',
legacyId: null,
name: 'AI Engineering Challenge',
status: 'COMPLETED',
trackId: 'track-dev-id',
typeId: 'type-challenge-id',
endDate: ratingDate,
track: { name: 'Development' },
type: { name: 'Challenge' },
metadata: [],
legacyRecord: null
}]
})

try {
const result = await service.getHistoryStats({ isMachine: true }, 'devtest1400', {})

result.should.have.length(1)
should.exist(result[0].DATA_SCIENCE)
should.exist(result[0].DATA_SCIENCE['AI Engineering'])
result[0].DATA_SCIENCE['AI Engineering'].history.should.have.length(1)
result[0].DATA_SCIENCE['AI Engineering'].history[0].challengeId.should.equal('ai-history-challenge')
result[0].DATA_SCIENCE['AI Engineering'].history[0].challengeName.should.equal('AI Engineering Challenge')
result[0].DATA_SCIENCE['AI Engineering'].history[0].newRating.should.equal(840)
should.not.exist(result[0].DEVELOP)
} finally {
restore()
}
})

it('rerateMemberStats should route configured rating paths to the Marathon Match engine', async () => {
let capturedOptions
const { service, restore } = loadStatisticsService({
Expand Down
Loading