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
29 changes: 29 additions & 0 deletions src/apps/opportunities/src/utils/marathon-match.utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,35 @@ describe('Marathon Match challenge detail utilities', () => {
.toEqual({ finalScore: 80, provisionalScore: undefined })
})

it.each([
{ testStatus: 'IN_PROGRESS' },
{ testStatus: 'PENDING' },
{ testStatus: 'RUNNING' },
{ testProgress: 0.5 },
{ testProgress: 50 },
{ testProgressDetails: { progress: 0.2, status: 'PROCESSING' } },
])('withholds provisional placeholders and stale fallbacks for %j', metadata => {
expect(marathonSubmissionScores({
id: 'pending',
initialScore: 88,
provisionalScore: 99,
reviewSummation: [{ aggregateScore: 0, isProvisional: true, metadata }],
}).provisionalScore)
.toBeUndefined()
})

it.each([0, 42.5, -1])('preserves completed provisional score %s', score => {
expect(marathonSubmissionScores({
id: 'finished',
reviewSummation: [{
aggregateScore: score,
isProvisional: true,
metadata: { testProgress: score < 0 ? 0 : 1, testStatus: score < 0 ? 'FAILED' : 'COMPLETED' },
}],
}).provisionalScore)
.toBe(score)
})

it('matches community-app final-score release timing and completed non-MM gating', () => {
const finalSubmission = { finalScore: 98.98, id: 'final' }
expect(shouldShowFinalSubmissionScores({
Expand Down
24 changes: 22 additions & 2 deletions src/apps/opportunities/src/utils/marathon-match.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,26 @@ function systemSummationIsComplete(summation: ChallengeReviewSummation): boolean
return true
}

/**
* Withholds provisional placeholders while the latest scorer run is unfinished.
* Failed scores remain visible; completed legacy rows may omit progress metadata.
* @param summation Latest provisional review summation, if available.
* @returns Whether the aggregate and legacy score fallbacks must be hidden.
* @throws Does not throw.
*/
function provisionalScoreIsPending(summation?: ChallengeReviewSummation): boolean {
const metadata = summation?.metadata ?? {}
const details = metadata.testProgressDetails
const detailRecord = details && typeof details === 'object' && !Array.isArray(details)
? details as Record<string, unknown>
: {}
const status = testStatusValue(metadata.testStatus ?? detailRecord.status)
const progress = testProgressValue(metadata.testProgress ?? detailRecord.progress)

return status === 'Cancelled' || status === 'In progress'
|| (status !== 'Failed' && progress !== undefined && progress < 100)
}

/**
* Identifies Marathon Match challenges across v6 names, catalog IDs, and tags.
*
Expand Down Expand Up @@ -342,7 +362,7 @@ export function marathonDashboardIsEnabled(challenge: ChallengeOpportunity): boo
/**
* Resolves provisional and final Marathon Match scores, preferring the latest
* phase-specific review summation over legacy submission-level fields.
* Cancelled phase scores, including placeholder aggregates, are not displayed.
* Unfinished provisional and cancelled phase scores, including legacy fallbacks, are not displayed.
*
* @param submission Review API submission with modern or legacy score fields.
* @returns resolved provisional and final scores.
Expand All @@ -353,7 +373,7 @@ export function marathonSubmissionScores(
): MarathonSubmissionScores {
const provisional = latestPhaseSummation(submission, 'provisional')
const final = latestPhaseSummation(submission, 'final')
const provisionalScore = testStatusValue(provisional?.metadata?.testStatus) === 'Cancelled'
const provisionalScore = provisionalScoreIsPending(provisional)
? undefined
: finiteScore(provisional?.aggregateScore)
?? finiteScore(submission.provisionalScore)
Expand Down
Loading