Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
a31e35c
Update AI rating name to match Figma
jmgasper Jun 4, 2026
b3a0193
Updates to rename AI to AI Engineering
jmgasper Jun 4, 2026
6d8a720
PM-5223: include challenge winners in member rerates
jmgasper Jun 5, 2026
03aedc6
PM-5222: rerate Development Marathon Matches as MM
jmgasper Jun 5, 2026
0afc407
PM-5221: rate Data Science Challenge history
jmgasper Jun 5, 2026
1c5c5ac
Stats fixes and member challenge points backfill script
jmgasper Jun 5, 2026
01c6b1b
Merge pull request #125 from topcoder-platform/PM-5221
jmgasper Jun 9, 2026
abdf53e
Merge branch 'develop' into PM-5222
jmgasper Jun 9, 2026
dad95bb
Merge pull request #124 from topcoder-platform/PM-5222
jmgasper Jun 9, 2026
8b67451
Merge develop into PM-5223
jmgasper Jun 9, 2026
1fcf750
PM-5278: Count availability as profile complete
jmgasper Jun 9, 2026
bfffb50
Merge pull request #126 from topcoder-platform/PM-5278
jmgasper Jun 9, 2026
2f30567
Merge pull request #123 from topcoder-platform/PM-5223
jmgasper Jun 9, 2026
956f3f9
PM-5222: Use challenge result scores for Dev MM rerates
jmgasper Jun 10, 2026
045c2ad
Merge pull request #127 from topcoder-platform/PM-5222-1
jmgasper Jun 10, 2026
8910f6e
PM-5221: include Data Science Challenge winners
jmgasper Jun 10, 2026
903834c
Dedupe history script
jmgasper Jun 10, 2026
1d50c59
Ignore duplicate MMs that don't match known challenges
jmgasper Jun 10, 2026
2295e74
Merge pull request #128 from topcoder-platform/PM-5221-1
jmgasper Jun 10, 2026
546eddd
Fix a bug with rating a MM if the previous challenge for a user had n…
jmgasper Jun 10, 2026
cc63dc3
PM-5223: route QA challenges into Data Science ratings
jmgasper Jun 15, 2026
dc2a69d
Merge pull request #130 from topcoder-platform/PM-5223-1
jmgasper Jun 15, 2026
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
3 changes: 2 additions & 1 deletion CUTOVER.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ Recommended rollout:
| D1 | `POST /members/:handle/stats/rerate` | `{ "challengeId": "<earliest-dev-challenge-uuid>", "trackId": "DEVELOP", "typeId": "Challenge" }` | Use the earliest Development/Challenge `challengeId` for a full-history rerate |
| D2 | `POST /members/:handle/stats/rerate` | `{ "challengeId": "<starting-dev-challenge-uuid>", "trackId": "DEVELOP", "typeId": "Challenge" }` | Re-rates Development/Challenge data from a specific challenge forward |
| D3 | `POST /members/:handle/stats/rerate` | `{ "challengeId": "<starting-mm-challenge-uuid>", "trackId": "DATA_SCIENCE", "typeId": "MARATHON_MATCH" }` | Use the earliest Marathon Match `challengeId` for full history, or a later one for a partial rerate |
| D4 | `node src/scripts/rerateMarathonMatches.js --dry-run` then `node src/scripts/rerateMarathonMatches.js --concurrency 5` | n/a | Bulk rerates native Marathon Match ratings for every discovered competitor from the beginning. Challenge discovery uses only the Marathon Match `ChallengeType` id, so Data Science and Development-track MM imports are replayed together into `DATA_SCIENCE / MARATHON_MATCH`; no `MM_DB_URL` or marathon-match-api schema is required |
| D4 | `node src/scripts/rerateMarathonMatches.js --dry-run` then `node src/scripts/rerateMarathonMatches.js --concurrency 5` | n/a | Bulk rerates native Marathon Match ratings for every discovered competitor from the beginning. Challenge discovery uses only the Marathon Match `ChallengeType` id, so Data Science and Development-track MM imports are replayed together into `DATA_SCIENCE / MARATHON_MATCH`; migrated numeric rows are de-duplicated against canonical UUID rows by MM round number during replay; no `MM_DB_URL` or marathon-match-api schema is required |
| D5 | `node src/scripts/dedupeMarathonMatchHistory.js --dry-run` then `node src/scripts/dedupeMarathonMatchHistory.js --apply` | n/a | Physically removes migrated numeric `DATA_SCIENCE / MARATHON_MATCH` `memberStatsHistory` rows that duplicate complete official Marathon Match UUID history rows for the same user and MM round. This requires the members and challenges schemas to be accessible from the same Postgres database and leaves non-MM helper Challenge API records intact |

- Auth: Bearer token with `rerate:member_stats` scope, or admin JWT
- `challengeId` is required for rerates; use the earliest applicable challenge when you need a full-history rerate.
Expand Down
77 changes: 62 additions & 15 deletions ReadMe.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ The following parameters can be set in config files or in env variables:
- MISC_SECURE_FIELDS: Misc identifiable info fields, only admin, M2M, or member himself can fetch these fields
- STATISTICS_SECURE_FIELDS: Member Statistics identifiable info fields, only admin, M2M, or member himself can fetch these fields
- STATS_READ_SOURCE: Controls stats read backend, `unified` (default, new tables) or `legacy` (pre-refactor tables)
- RATING_PATHS: JSON array of tag- and skill-based Development Challenge and Marathon Match rating paths. Each entry has `name`, optional `tags`, optional `skillIds`, and optional `track` (`DATA_SCIENCE`, `DEVELOP`, or `DEVELOPMENT`); at least one of `tags` or `skillIds` is required. Tags match any listed challenge tag, while skill IDs require every listed challenge skill. Defaults to `[{"name":"AI","track":"DATA_SCIENCE","tags":["AI","AI Exponential League"]}]`. Re-rate a path by passing `ratingName` to `POST /members/{handle}/stats/rerate`; rows are stored under `{track} / {name}` in unified stats.
- RATING_PATHS: JSON array of tag- and skill-based Development Challenge and Marathon Match rating paths. Each entry has `name`, optional `tags`, optional `skillIds`, and optional `track` (`DATA_SCIENCE`, `DEVELOP`, or `DEVELOPMENT`); at least one of `tags` or `skillIds` is required. Tags match any listed challenge tag, while skill IDs require every listed challenge skill. Defaults to `[{"name":"AI Engineering","track":"DATA_SCIENCE","tags":["AI","AI Exponential League"]}]`. Re-rate a path by passing `ratingName` to `POST /members/{handle}/stats/rerate`; rows are stored under `{track} / {name}` in unified stats.
- HEALTH_CHECK_TIMEOUT: health check timeout in milliseconds

Set the following environment variables used by bus API to get TC M2M token (use 'set' insted of 'export' for Windows OS):
Expand All @@ -138,21 +138,59 @@ The following test parameters can be set in config file or in env variables:
- M2M_UPDATE_ACCESS_TOKEN: M2M update (including 'delete') access token
- S3_ENDPOINT: endpoint of AWS S3 API, for unit and e2e test only; default to `localhost:9000`

## Challenge Point Profile Data

Point-based challenge prizes are mirrored into member-api so profile pages can show totals and a per-challenge breakdown without reading tc-finance-api.

Autopilot replaces the rows for one challenge with:

```http
PUT /v6/members/challenge-points/{challengeId}
Content-Type: application/json

{
"challengeName": "AI Points Challenge",
"points": [
{ "userId": 123456, "placement": 1, "points": 500 }
]
}
```

`GET /v6/members/{handle}` includes a public `challengePoints` object by default:

```json
{
"challengePoints": {
"total": 2847,
"challenges": 14,
"details": [
{
"challengeId": "challenge-uuid",
"challengeName": "AI Points Challenge",
"userId": 123456,
"placement": 1,
"points": 500
}
]
}
}
```

## Rating Path Operations

Historical rating paths are configured with `RATING_PATHS`. The default AI path is:

```json
[
{
"name": "AI",
"name": "AI Engineering",
"track": "DATA_SCIENCE",
"tags": ["AI", "AI Exponential League"]
}
]
```

The `track` value controls where the rating is stored in unified stats. Use `DATA_SCIENCE` to store rows under `DATA_SCIENCE / AI`, or `DEVELOP` / `DEVELOPMENT` to store rows under `DEVELOP / AI`. The rating engine includes both Marathon Match results and `challenge` type Development Challenge results when the challenges match the configured tags or skill IDs. When a configured rating name does not already exist as a `ChallengeType`, rerate creates a deterministic custom type row so unified `memberStats` foreign keys remain valid.
The `track` value controls where the rating is stored in unified stats. Use `DATA_SCIENCE` to store rows under `DATA_SCIENCE / AI Engineering`, or `DEVELOP` / `DEVELOPMENT` to store rows under `DEVELOP / AI Engineering`. The rating engine includes both Marathon Match results and `challenge` type Development Challenge results when the challenges match the configured tags or skill IDs. When a configured rating name does not already exist as a `ChallengeType`, rerate creates a deterministic custom type row so unified `memberStats` foreign keys remain valid.

### Rating Calculation Details

Expand All @@ -170,7 +208,7 @@ Content-Type: application/json

{
"challengeId": "earliest-tagged-challenge-id-for-that-member",
"ratingName": "AI"
"ratingName": "AI Engineering"
}
```

Expand All @@ -196,38 +234,47 @@ pnpm rerate-marathon-matches -- --dry-run
pnpm rerate-marathon-matches -- --concurrency 5
```

The script discovers completed Marathon Match challenges by `ChallengeType` id and also merges distinct challenge IDs already present in `DATA_SCIENCE / MARATHON_MATCH` `memberStatsHistory`, so migrated legacy MM rows that predate ChallengeType classification are scanned too. It discovers competitors from final review summations using both canonical challenge UUIDs and legacy numeric challenge IDs, filters out user IDs that do not exist in member storage, and calls the native MM rerate engine with no starting challenge so each member is replayed from their first MM event. Current MM ranks are recalculated once after the batch rather than once per member. Historical MM replay does not require `MM_DB_URL` or the marathon-match-api schema.
The script discovers completed Marathon Match challenges by `ChallengeType` id and also merges distinct challenge IDs already present in `DATA_SCIENCE / MARATHON_MATCH` `memberStatsHistory`, so migrated legacy MM rows that predate ChallengeType classification are scanned too. Migrated numeric rows such as `MM 145` are merged with canonical Challenge API rows such as `Marathon Match 145` by MM round number before participant discovery, and rerate seeding prefers complete canonical UUID checkpoints over duplicate legacy rows. The script discovers competitors from final review summations using both canonical challenge UUIDs and legacy numeric challenge IDs, filters out user IDs that do not exist in member storage, and calls the native MM rerate engine with no starting challenge so each member is replayed from their first MM event. Current MM ranks are recalculated once after the batch rather than once per member. Historical MM replay does not require `MM_DB_URL` or the marathon-match-api schema.

After a rerate, migrated numeric history rows can still exist beside the canonical UUID rows until they are physically cleaned from `memberStatsHistory`. When the members and challenges schemas are available in the same Postgres database, preview and apply the cleanup with:

```bash
pnpm dedupe-marathon-match-history -- --dry-run --user-id 40562752
pnpm dedupe-marathon-match-history -- --apply --user-id 40562752
```

The cleanup deletes only migrated numeric `DATA_SCIENCE / MARATHON_MATCH` history rows that match a complete official Challenge API Marathon Match UUID row for the same member and MM round number. It leaves Challenge API helper challenges such as writer, tester, task, and placement award records intact because those are separate challenge records, not native Marathon Match rating history. After native MM rerates exist for a member, profile history also hides remaining orphan numeric MM rows that cannot be resolved to a Challenge API UUID or matched to a canonical MM round for that member.

To re-run a configured rating path for every member who participated in the configured challenge set, use the bulk script instead of the member-scoped API:

```bash
pnpm rerate-rating-path -- --rating-name AI --dry-run
pnpm rerate-rating-path -- --rating-name AI --concurrency 5
pnpm rerate-rating-path -- --rating-name "AI Engineering" --dry-run
pnpm rerate-rating-path -- --rating-name "AI Engineering" --concurrency 5
```

The script discovers distinct user IDs from all rated challenges matching the configured path, then rerates each user from the start of the path so complete history is written without requiring individual handles or challenge IDs. It writes successfully processed user IDs to `rerateRatingPath.processedUserIds.json` by default. Marathon Match path events are also read from review summations and do not require `MM_DB_URL`.

Useful script options:

```bash
pnpm rerate-rating-path -- --rating-name AI --limit 100
pnpm rerate-rating-path -- --rating-name AI --user-id 12345
pnpm rerate-rating-path -- --rating-name AI --user-ids 12345,67890
pnpm rerate-rating-path -- --rating-name AI --processed-user-ids-path /tmp/ai-rerated-users.json
pnpm rerate-rating-path -- --rating-name "AI Engineering" --limit 100
pnpm rerate-rating-path -- --rating-name "AI Engineering" --user-id 12345
pnpm rerate-rating-path -- --rating-name "AI Engineering" --user-ids 12345,67890
pnpm rerate-rating-path -- --rating-name "AI Engineering" --processed-user-ids-path /tmp/ai-rerated-users.json
```

To view one member's rating and history for a `DATA_SCIENCE` path:

```http
GET /v6/members/{handle}/stats?trackId=DATA_SCIENCE&typeId=AI
GET /v6/members/{handle}/stats/history?trackId=DATA_SCIENCE&typeId=AI
GET /v6/members/{handle}/stats?trackId=DATA_SCIENCE&typeId=AI%20Engineering
GET /v6/members/{handle}/stats/history?trackId=DATA_SCIENCE&typeId=AI%20Engineering
```

To view one member's rating and history for a `DEVELOPMENT` path, use the canonical unified track name:

```http
GET /v6/members/{handle}/stats?trackId=DEVELOP&typeId=AI
GET /v6/members/{handle}/stats/history?trackId=DEVELOP&typeId=AI
GET /v6/members/{handle}/stats?trackId=DEVELOP&typeId=AI%20Engineering
GET /v6/members/{handle}/stats/history?trackId=DEVELOP&typeId=AI%20Engineering
```

To view aggregate rating distribution data:
Expand Down
2 changes: 1 addition & 1 deletion config/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ if (!communicationSecureFields.includes('email')) {

const defaultRatingPaths = [
{
name: 'AI',
name: 'AI Engineering',
track: 'DATA_SCIENCE',
tags: ['AI', 'AI Exponential League']
}
Expand Down
107 changes: 106 additions & 1 deletion docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,49 @@ paths:
description: Internal server error
schema:
$ref: '#/definitions/ErrorModel'
'/members/challenge-points/{challengeId}':
put:
tags:
- Basic
description: |-
Replace the stored point awards for one challenge. This endpoint mirrors point-based challenge prize payouts into member-api so member profiles can return challenge point totals and per-challenge details.

Authorization:
- M2M scopes: `update:user_profiles` or `all:user_profiles`.
security:
- bearer: []
parameters:
- in: path
name: challengeId
required: true
type: string
description: Challenge identifier whose point rows should be replaced.
- in: body
name: body
required: true
schema:
$ref: '#/definitions/ChallengePointsUpdateRequest'
responses:
'200':
description: OK
schema:
$ref: '#/definitions/ChallengePointsUpdateResult'
'400':
description: Bad request data
schema:
$ref: '#/definitions/ErrorModel'
'401':
description: Miss or wrong authentication credentials
schema:
$ref: '#/definitions/ErrorModel'
'403':
description: No permission
schema:
$ref: '#/definitions/ErrorModel'
'500':
description: Internal server error
schema:
$ref: '#/definitions/ErrorModel'
'/members/{handle}':
get:
tags:
Expand Down Expand Up @@ -1857,7 +1900,7 @@ paths:
description: UUID or legacy numeric challenge identifier that starts the rerate request.
ratingName:
type: string
description: Optional configured rating path name, for example `AI` or `Java MySQL`.
description: Optional configured rating path name, for example `AI Engineering` or `Java MySQL`.
trackId:
type: string
enum:
Expand Down Expand Up @@ -2232,6 +2275,68 @@ definitions:
description: 'ISO-8601 formatted date times (YYYY-MM-DDTHH:mm:ss.sssZ)'
updatedBy:
type: string
challengePoints:
$ref: '#/definitions/MemberChallengePointsSummary'
MemberChallengePointsSummary:
type: object
properties:
total:
type: integer
description: Total challenge points earned by the member.
challenges:
type: integer
description: Number of challenge point award rows returned for the member.
details:
type: array
items:
$ref: '#/definitions/MemberChallengePointsDetail'
MemberChallengePointsDetail:
type: object
properties:
challengeId:
type: string
challengeName:
type: string
userId:
type: integer
placement:
type: integer
points:
type: integer
ChallengePointsUpdateRequest:
type: object
required:
- challengeName
- points
properties:
challengeName:
type: string
points:
type: array
items:
$ref: '#/definitions/ChallengePointAward'
ChallengePointAward:
type: object
required:
- userId
- placement
- points
properties:
userId:
type: integer
placement:
type: integer
points:
type: integer
ChallengePointsUpdateResult:
type: object
properties:
challengeId:
type: string
challengeName:
type: string
updated:
type: integer
MemberHandleUpdate:
type: object
required:
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"migrate-dynamo-data": "node src/scripts/migrate-dynamo-data.js",
"recalculate-member-stats": "node src/scripts/recalculateMemberStats.js",
"backfill-challenge-results": "node src/scripts/backfillChallengeResults.js",
"dedupe-marathon-match-history": "node src/scripts/dedupeMarathonMatchHistory.js",
"rerate-rating-path": "node src/scripts/rerateRatingPath.js",
"rerate-marathon-matches": "node src/scripts/rerateMarathonMatches.js",
"rerate-development-challenge": "node src/scripts/rerateDevelopmentChallenge.js"
Expand Down Expand Up @@ -87,7 +88,8 @@
"mocha"
],
"ignore": [
"/prisma/"
"/prisma/",
"/packages/"
]
}
}
1 change: 1 addition & 0 deletions packages/member-prisma-client/client.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./index"
5 changes: 5 additions & 0 deletions packages/member-prisma-client/client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
// biome-ignore-all lint: generated file
module.exports = { ...require('.') }
1 change: 1 addition & 0 deletions packages/member-prisma-client/default.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./index"
5 changes: 5 additions & 0 deletions packages/member-prisma-client/default.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
// biome-ignore-all lint: generated file
module.exports = { ...require('#main-entry-point') }
1 change: 1 addition & 0 deletions packages/member-prisma-client/edge.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./default"
Loading
Loading