diff --git a/src/services/StatisticsService.js b/src/services/StatisticsService.js index fc4ce6f..97e9408 100644 --- a/src/services/StatisticsService.js +++ b/src/services/StatisticsService.js @@ -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. @@ -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) diff --git a/test/unit/StatisticsService.test.js b/test/unit/StatisticsService.test.js index 799a0fa..5078da0 100644 --- a/test/unit/StatisticsService.test.js +++ b/test/unit/StatisticsService.test.js @@ -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({