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
2 changes: 1 addition & 1 deletion CUTOVER.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Recommended rollout:

### 1. Pre-flight checklist

- Confirm all three database env vars are set: `DATABASE_URL`, `CHALLENGES_DB_URL`, `REVIEW_DB_URL`
- Confirm all three database env vars are set: `DATABASE_URL`, `CHALLENGES_DB_URL` or `CHALLENGE_DB_URL`, `REVIEW_DB_URL`
- Confirm `REVIEW_DB_URL` points to the review-api database that contains `challengeResult`; this is separate from the member Prisma schema deployment
- Confirm the `challenge-api-v6` migration that adds hidden legacy challenge types has been deployed so legacy subtracks such as `ARCHITECTURE`, `ASSEMBLY_COMPETITION`, and `SRM` can be resolved during stats backfill
- Confirm `STATS_READ_SOURCE=legacy` is set so reads stay on legacy tables during backfill
Expand Down
2 changes: 1 addition & 1 deletion Verification.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ There are some changes to prisma schema.

- Required env vars:
- `DATABASE_URL` for the member database
- `CHALLENGES_DB_URL` for the challenges database
- `CHALLENGES_DB_URL` or `CHALLENGE_DB_URL` for the challenges database
- `REVIEW_DB_URL` for the review database; `recalculateMemberStats.js` now reads review-api `challengeResult` rows during aggregate backfill and history supplementation, not just during rerates
- Challenge catalog prerequisite:
- Deploy the `challenge-api-v6` migration that seeds hidden legacy `ChallengeType` rows for historical subtracks like `ARCHITECTURE`, `ASSEMBLY_COMPETITION`, and `SRM`
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"migrate-dynamo-data": "node src/scripts/migrate-dynamo-data.js",
"recalculate-member-stats": "node src/scripts/recalculateMemberStats.js",
"rerate-rating-path": "node src/scripts/rerateRatingPath.js",
"rerate-marathon-matches": "node src/scripts/rerateMarathonMatches.js"
"rerate-marathon-matches": "node src/scripts/rerateMarathonMatches.js",
"rerate-development-challenge": "node src/scripts/rerateDevelopmentChallenge.js"
},
"author": "TCSCODER",
"license": "none",
Expand Down
4 changes: 2 additions & 2 deletions prisma/generated/client/edge.js

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions prisma/generated/client/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion prisma/generated/client/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "prisma-client-3c7d51998a1033da8c173f082b2bfbd5503fb034d898b464250acf0907531759",
"name": "prisma-client-7a649551001de7cf62f62fb400ccb23bcaaa84f13014894b825c7925382d2c03",
"main": "index.js",
"types": "index.d.ts",
"browser": "default.js",
Expand Down
2 changes: 2 additions & 0 deletions prisma/generated/client/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ model member {
@@index([status, handleLower])
@@index([lastLoginDate])
@@index([availableForGigs])
@@index([status, availableForGigs])
@@schema("members")
}

Expand All @@ -104,6 +105,7 @@ model memberAddress {

@@index([userId])
@@index([userId, type])
@@index([userId, type, id(sort: Desc)], map: "idx_member_address_user_type")
@@schema("members")
}

Expand Down
4 changes: 2 additions & 2 deletions prisma/generated/client/wasm.js

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions src/common/prisma.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const extractSchemaFromUrl = (dbUrl) => {
}

const skillsDbUrl = process.env.SKILLS_DB_URL
const challengesDbUrl = process.env.CHALLENGES_DB_URL
const challengesDbUrl = process.env.CHALLENGES_DB_URL || process.env.CHALLENGE_DB_URL
const academyDbUrl = process.env.ACADEMY_DB_URL
const resourcesDbUrl = process.env.RESOURCES_DB_URL
const engagementsDbUrl = process.env.ENGAGEMENTS_DB_URL
Expand Down Expand Up @@ -94,7 +94,7 @@ const getChallengesClient = () => {
if (!challengesClient) {
if (!challengesDbUrl) {
throw new Error(
'CHALLENGES_DB_URL must be set for challenges Prisma client'
'CHALLENGES_DB_URL or CHALLENGE_DB_URL must be set for challenges Prisma client'
)
}
challengesClient = new ChallengesPrismaClient({
Expand Down
48 changes: 45 additions & 3 deletions src/common/prismaHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,47 @@ function mergeTrackCounters (trackItem, stat) {
}
}

/**
* Merge duplicate unified stats items that normalize to the same API bucket.
* Counters are additive, latest activity dates win, and the latest non-empty
* rank snapshot is retained so stale aggregate rows cannot erase ratings.
* @param {Object|undefined} existingItem previously accumulated response item
* @param {Object} nextItem next response item for the same track/type bucket
* @returns {Object} merged response item for use in member stats responses
*/
function mergeUnifiedStatsItem (existingItem, nextItem) {
if (!existingItem) {
return nextItem
}

const existingEventDate = toNumber(existingItem.mostRecentEventDate)
const nextEventDate = toNumber(nextItem.mostRecentEventDate)
const existingRank = existingItem.rank || {}
const nextRank = nextItem.rank || {}
const useNextRank = !_.isEmpty(nextRank) &&
(_.isEmpty(existingRank) || nextEventDate >= existingEventDate)
const rank = useNextRank ? nextRank : existingRank
const mostRecentEventDate = Math.max(existingEventDate, nextEventDate) || null
const mostRecentSubmission = Math.max(
toNumber(existingItem.mostRecentSubmission),
toNumber(nextItem.mostRecentSubmission)
) || null
const mostRecentEventName = nextEventDate >= existingEventDate
? (nextItem.mostRecentEventName || existingItem.mostRecentEventName)
: (existingItem.mostRecentEventName || nextItem.mostRecentEventName)

return _.omitBy({
...existingItem,
...nextItem,
challenges: toNumber(existingItem.challenges) + toNumber(nextItem.challenges),
wins: toNumber(existingItem.wins) + toNumber(nextItem.wins),
mostRecentSubmission,
mostRecentEventDate,
mostRecentEventName,
rank
}, _.isNil)
}

/**
* Build the maxRating response object while recomputing the rating color from
* the canonical color-band helper instead of trusting persisted color data.
Expand Down Expand Up @@ -574,7 +615,7 @@ function buildUnifiedStatsResponse (member, statsData, fields) {
minimumRating: row.minRating
}, _.isNil)
}
item.DATA_SCIENCE.SRM = srmItem
item.DATA_SCIENCE.SRM = mergeUnifiedStatsItem(item.DATA_SCIENCE.SRM, srmItem)
} else if (typeName === 'MARATHON_MATCH') {
const marathonItem = {
challenges: toNumber(row.challenges),
Expand All @@ -596,9 +637,9 @@ function buildUnifiedStatsResponse (member, statsData, fields) {
topTenFinishes: row.topTenFinishes
}, _.isNil)
}
item.DATA_SCIENCE.MARATHON_MATCH = marathonItem
item.DATA_SCIENCE.MARATHON_MATCH = mergeUnifiedStatsItem(item.DATA_SCIENCE.MARATHON_MATCH, marathonItem)
} else if (typeName) {
item.DATA_SCIENCE[typeName] = {
const dataScienceItem = {
challenges: toNumber(row.challenges),
wins: toNumber(row.wins),
mostRecentSubmission: toUnixTime(row.mostRecentSubmission),
Expand All @@ -618,6 +659,7 @@ function buildUnifiedStatsResponse (member, statsData, fields) {
topTenFinishes: row.topTenFinishes
}, _.isNil)
}
item.DATA_SCIENCE[typeName] = mergeUnifiedStatsItem(item.DATA_SCIENCE[typeName], dataScienceItem)
}
} else if (trackName === 'COPILOT') {
item.COPILOT = _.omitBy({
Expand Down
Loading
Loading