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
54 changes: 54 additions & 0 deletions __tests__/shared/utils/mm-review-summations.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,60 @@ describe('buildMmSubmissionData', () => {
]);
});

it('derives scores from review summations embedded in raw submissions', () => {
const rawSubmissions = [
{
createdAt: '2026-07-01T00:33:51.464Z',
id: 'submission-with-embedded-summations',
isLatest: true,
memberId: '1003',
registrant: {
memberHandle: 'gamma',
memberId: '1003',
rating: 1700,
},
reviewSummation: [
{
aggregateScore: 68.94,
id: 'summation-example',
isExample: true,
reviewedDate: '2026-07-01T00:35:00.000Z',
submissionId: 'submission-with-embedded-summations',
},
{
aggregateScore: 75.82,
id: 'summation-provisional',
isProvisional: true,
reviewedDate: '2026-07-01T00:36:00.000Z',
submissionId: 'submission-with-embedded-summations',
},
{
aggregateScore: 75.87,
id: 'summation-final',
isFinal: true,
reviewedDate: '2026-07-01T00:37:00.000Z',
submissionId: 'submission-with-embedded-summations',
},
],
status: 'completed',
},
];

const result = buildMmSubmissionData([], rawSubmissions);

expect(result).toHaveLength(1);
expect(result[0].submissions).toEqual([
expect.objectContaining({
finalScore: 75.87,
provisionalScore: 75.82,
reviewSummations: expect.arrayContaining([
expect.objectContaining({ id: 'summation-example' }),
]),
submissionId: 'submission-with-embedded-summations',
}),
]);
});

it('uses v6 submitter fields and submittedDate for imported raw submissions', () => {
const rawSubmissions = [
{
Expand Down
86 changes: 68 additions & 18 deletions src/shared/utils/mm-review-summations.js
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,59 @@ function updateSubmissionEntry(
};
}

/**
* Applies a review summation to a normalized Marathon Match submission entry.
*
* The Review API can return summations either as top-level
* /reviewSummations rows or embedded in /submissions reviewSummation arrays.
* This helper keeps both shapes on the same score normalization path.
*
* @param {Object} existingEntry Existing normalized submission entry.
* @param {Object} options Summation merge options.
* @param {String} options.submissionId Submission id that owns the summation.
* @param {Object} options.summation Review summation returned by the API.
* @param {String|null} options.fallbackTimestamp Timestamp to use if the
* summation does not include one.
* @param {Boolean|null} options.isLatest Latest-submission flag from the
* owning submission or summation, when available.
* @return {Object} Submission entry with score fields updated from summation.
* @throws This function does not throw.
*/
function updateSubmissionEntryFromReviewSummation(
existingEntry,
{
submissionId,
summation,
fallbackTimestamp = null,
isLatest = null,
},
) {
const timestamp = getSummationTimestamp(summation) || fallbackTimestamp;
const timestampValue = toTimestampValue(timestamp);
const normalizedScore = normalizeScoreValue(
_.get(summation, 'aggregateScore'),
);
const scoreType = getSummationScoreClassification(summation);
// Most MM review summations are provisional updates; if an entry does not
// explicitly identify itself as final or example, treat it as provisional.
const isProvisional = scoreType.isProvisional
|| (!scoreType.isFinal && !scoreType.isExample);

return updateSubmissionEntry(
existingEntry,
{
submissionId,
timestamp,
timestampValue,
normalizedScore,
summation,
isFinal: scoreType.isFinal,
isProvisional,
isLatest,
},
);
}

function updateSubmissionEntryFromSubmission(
existingEntry,
{
Expand Down Expand Up @@ -663,31 +716,15 @@ export function buildMmSubmissionData(reviewSummations = [], rawSubmissions = []
const submissionId = rawSubmissionId
? _.toString(rawSubmissionId)
: `unknown-${handle}-${index}`;
const timestamp = getSummationTimestamp(summation);
const timestampValue = toTimestampValue(timestamp);

const normalizedScore = normalizeScoreValue(
_.get(summation, 'aggregateScore'),
);
const scoreType = getSummationScoreClassification(summation);
// Most MM review summations are provisional updates; if an entry does not
// explicitly identify itself as final or example, treat it as provisional.
const isProvisional = scoreType.isProvisional
|| (!scoreType.isFinal && !scoreType.isExample);
const isLatest = _.isNil(summation.isLatest)
? null
: Boolean(summation.isLatest);

const updatedEntry = updateSubmissionEntry(
const updatedEntry = updateSubmissionEntryFromReviewSummation(
memberEntry.submissionsMap.get(submissionId),
{
submissionId,
timestamp,
timestampValue,
normalizedScore,
summation,
isFinal: scoreType.isFinal,
isProvisional,
isLatest,
},
);
Expand Down Expand Up @@ -746,8 +783,21 @@ export function buildMmSubmissionData(reviewSummations = [], rawSubmissions = []
: []),
]);

const updatedEntry = updateSubmissionEntryFromSubmission(
const entryWithEmbeddedSummations = reviewSummation.reduce(
(entry, summation) => updateSubmissionEntryFromReviewSummation(
entry,
{
submissionId,
summation,
fallbackTimestamp: timestamp,
isLatest,
},
),
memberEntry.submissionsMap.get(submissionId),
);

const updatedEntry = updateSubmissionEntryFromSubmission(
entryWithEmbeddedSummations,
{
submissionId,
timestamp,
Expand Down
Loading