Skip to content

Commit 7a8f91e

Browse files
committed
PM-5397: Preserve AI Engineering history dimensions
What was broken AI Engineering stats could show challenge counts, but the profile challenge details view could still be empty because the member stats history response did not expose those rows under the configured AI Engineering rating path. Root cause When history rows were enriched with Challenge API metadata, configured rating-path rows kept their challenge id and name but had their stored track/type overwritten by the source challenge's native Challenge or Marathon Match dimensions. What was changed Preserve configured rating-path dimensions during history metadata enrichment so AI Engineering history remains under DATA_SCIENCE.AI Engineering while still receiving canonical challenge metadata. Any added/updated tests Added a StatisticsService regression test that verifies an AI Engineering rating-path history row stays under DATA_SCIENCE.AI Engineering after challenge metadata enrichment.
1 parent c4db4d7 commit 7a8f91e

2 files changed

Lines changed: 76 additions & 1 deletion

File tree

src/services/StatisticsService.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1265,6 +1265,21 @@ function filterUnifiedHistoryRowsToCompletedChallenges (rows, challengeMetadataB
12651265
})
12661266
}
12671267

1268+
/**
1269+
* Check whether a history row belongs to a configured rating path.
1270+
* Rating-path rows intentionally keep their stored type even when the source
1271+
* challenge was a native Challenge or Marathon Match event.
1272+
* @param {Object} row unified history row annotated with stored track/type names
1273+
* @returns {boolean} true when the row should preserve its stored rating-path dimensions
1274+
*/
1275+
function isConfiguredRatingPathHistoryRow (row) {
1276+
return !!(
1277+
getConfiguredRatingPath(config.RATING_PATHS, row && row.typeName) ||
1278+
getConfiguredRatingPath(config.RATING_PATHS, row && row.typeId) ||
1279+
getConfiguredRatingPathByTypeId(config.RATING_PATHS, row && row.typeId)
1280+
)
1281+
}
1282+
12681283
/**
12691284
* Attach canonical challenge ids and names to unified history rows before shaping
12701285
* the response payload consumed by the profiles UI.
@@ -1292,7 +1307,7 @@ function enrichUnifiedHistoryRowsWithChallengeMetadata (rows, challengeMetadataB
12921307
: _.get(challenge, 'legacyRecord.legacySystemId')
12931308
)
12941309
const preserveLegacyChallengeId = isLegacyNumericMarathonHistoryRow(row)
1295-
const dimension = challenge.trackId && challenge.typeId
1310+
const dimension = !isConfiguredRatingPathHistoryRow(row) && challenge.trackId && challenge.typeId
12961311
? resolveStatsDimensionForChallengeRow({
12971312
trackId: String(challenge.trackId),
12981313
typeId: String(challenge.typeId)

test/unit/StatisticsService.test.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -728,6 +728,66 @@ describe('statistics service unit tests', () => {
728728
}
729729
})
730730

731+
it('getHistoryStats should preserve configured rating path dimensions after challenge metadata enrichment', async () => {
732+
const ratingDate = new Date('2026-06-18T05:41:34.931Z')
733+
const { service, restore } = loadStatisticsService({
734+
ratingPaths: [
735+
{ name: 'AI Engineering', track: 'DATA_SCIENCE', tags: ['AI', 'AI Exponential League'] }
736+
],
737+
prismaStub: {
738+
$queryRaw: async () => [],
739+
memberStats: {
740+
findMany: async () => [{
741+
trackId: 'track-ds-id',
742+
typeId: 'rating-path-ai-engineering',
743+
challenges: 1,
744+
mostRecentEventDate: ratingDate
745+
}]
746+
},
747+
memberStatsHistory: {
748+
findMany: async () => [{
749+
trackId: 'track-ds-id',
750+
typeId: 'rating-path-ai-engineering',
751+
challengeId: 'ai-history-challenge',
752+
challengeName: null,
753+
newRating: 840,
754+
eventDate: ratingDate,
755+
placement: 1,
756+
mostRecent: true
757+
}]
758+
}
759+
},
760+
challengeRows: [{
761+
id: 'ai-history-challenge',
762+
legacyId: null,
763+
name: 'AI Engineering Challenge',
764+
status: 'COMPLETED',
765+
trackId: 'track-dev-id',
766+
typeId: 'type-challenge-id',
767+
endDate: ratingDate,
768+
track: { name: 'Development' },
769+
type: { name: 'Challenge' },
770+
metadata: [],
771+
legacyRecord: null
772+
}]
773+
})
774+
775+
try {
776+
const result = await service.getHistoryStats({ isMachine: true }, 'devtest1400', {})
777+
778+
result.should.have.length(1)
779+
should.exist(result[0].DATA_SCIENCE)
780+
should.exist(result[0].DATA_SCIENCE['AI Engineering'])
781+
result[0].DATA_SCIENCE['AI Engineering'].history.should.have.length(1)
782+
result[0].DATA_SCIENCE['AI Engineering'].history[0].challengeId.should.equal('ai-history-challenge')
783+
result[0].DATA_SCIENCE['AI Engineering'].history[0].challengeName.should.equal('AI Engineering Challenge')
784+
result[0].DATA_SCIENCE['AI Engineering'].history[0].newRating.should.equal(840)
785+
should.not.exist(result[0].DEVELOP)
786+
} finally {
787+
restore()
788+
}
789+
})
790+
731791
it('rerateMemberStats should route configured rating paths to the Marathon Match engine', async () => {
732792
let capturedOptions
733793
const { service, restore } = loadStatisticsService({

0 commit comments

Comments
 (0)