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: 2 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ workflows:
- develop
- PM-4931
- improve-member-search-2
tags:
only: /^dev-.*/

# Production builds are exectuted only on tagged commits to the
# master branch.
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"packageManager": "pnpm@11.15.0",
"scripts": {
"build": "nest build",
"deploy:dev": "BRANCH=$(git rev-parse --abbrev-ref HEAD) && TAG=\"dev-${BRANCH}\" && git tag -d \"$TAG\" 2>/dev/null; git push origin \":refs/tags/$TAG\" 2>/dev/null; git tag \"$TAG\" && git push origin \"$TAG\"",
"start": "node dist/main.js",
"start:dev": "nest start --watch",
"start:prod": "node dist/main.js",
Expand Down
218 changes: 218 additions & 0 deletions sql/reports/topcoder/leaderboard-generic.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
WITH challenge_context AS (
SELECT
c.id AS challenge_id,
c.name AS challenge_name,
COALESCE(
cp."actualStartDate",
cp."scheduledStartDate"
) AS start_date,
COALESCE(
cp."actualEndDate",
cp."scheduledEndDate"
) AS end_date,
GREATEST(
1,
CEIL(
EXTRACT(EPOCH FROM COALESCE(cp."actualEndDate", cp."scheduledEndDate") - COALESCE(cp."actualStartDate", cp."scheduledStartDate"))
/ 86400.0
)
) AS duration_days
FROM challenges."Challenge" AS c
JOIN challenges."ChallengePhase" AS cp
ON cp."challengeId" = c.id
AND cp.name = 'Submission'
WHERE c.id = ANY($1::text[])
),
member_submissions AS (
SELECT
cc.challenge_id,
s.id AS submission_id,
s."memberId" AS user_id,
COALESCE(
NULLIF(TRIM(u.handle), ''),
NULLIF(TRIM(mem.handle), ''),
fallback.member_handle
) AS handle,
COALESCE(NULLIF(TRIM(mem."firstName"), ''), NULLIF(TRIM(u.handle), ''), NULLIF(TRIM(mem.handle), '')) AS name,
COALESCE(
home_code.name,
home_id.name,
comp_code.name,
comp_id.name,
NULLIF(TRIM(mem."competitionCountryCode"), ''),
NULLIF(TRIM(mem."homeCountryCode"), '')
) AS country,
COALESCE(
NULLIF(TRIM(mem."competitionCountryCode"), ''),
NULLIF(TRIM(mem."homeCountryCode"), '')
) AS country_code,
mem."photoURL" AS photoURL,
mmr.rating AS rating,
mmr."ratingColor" AS ratingColor,
COALESCE(
CASE
WHEN challenge_reviewers.is_ai_only_challenge
THEN ai_decision."totalScore"
ELSE final_review."aggregateScore"
END,
s."finalScore"::double precision,
s."initialScore"::double precision
) AS score,
COALESCE(s."submittedDate", s."createdAt") AS submitted_date,
cc.duration_days
FROM challenge_context AS cc
JOIN reviews."submission" AS s
ON s."challengeId" = cc.challenge_id
AND s."memberId" IS NOT NULL
LEFT JOIN LATERAL (
SELECT rs."aggregateScore", rs."scorecardId"
FROM reviews."reviewSummation" AS rs
WHERE rs."submissionId" = s.id
AND COALESCE(rs."isFinal", TRUE) = TRUE
AND rs."isProvisional" IS DISTINCT FROM TRUE
ORDER BY COALESCE(rs."reviewedDate", rs."createdAt") DESC NULLS LAST, rs.id DESC
LIMIT 1
) AS final_review ON TRUE
LEFT JOIN LATERAL (
SELECT
COUNT(*) > 0 AS has_reviewers,
BOOL_AND(cr."aiWorkflowId" IS NOT NULL AND cr."isMemberReview" = FALSE) AND COUNT(*) > 0 AS is_ai_only_challenge
FROM challenges."ChallengeReviewer" AS cr
WHERE cr."challengeId" = cc.challenge_id
) AS challenge_reviewers ON TRUE
LEFT JOIN LATERAL (
SELECT d."totalScore", d.status
FROM reviews."aiReviewDecision" AS d
WHERE d."submissionId" = s.id
AND UPPER(d.status::text) != 'PENDING'
ORDER BY d."updatedAt" DESC NULLS LAST
LIMIT 1
) AS ai_decision ON TRUE
LEFT JOIN reviews.scorecard AS sc
ON sc.id = final_review."scorecardId"
LEFT JOIN members."member" AS mem
ON mem."userId" = s."memberId"::bigint
LEFT JOIN LATERAL (
SELECT DISTINCT ON (mmr."userId")
mmr.rating,
mmr."ratingColor"
FROM members."memberMaxRating" AS mmr
WHERE mmr."userId" = s."memberId"::bigint
ORDER BY mmr."userId", mmr.rating DESC
) AS mmr ON TRUE
LEFT JOIN identity."user" AS u
ON s."memberId" ~ '^[0-9]+$'
AND u.user_id = s."memberId"::numeric
LEFT JOIN LATERAL (
SELECT MAX(r."memberHandle") AS member_handle
FROM resources."Resource" AS r
WHERE r."challengeId" = cc.challenge_id
AND r."memberId" = s."memberId"
) AS fallback ON TRUE
LEFT JOIN lookups."Country" AS home_code
ON UPPER(home_code."countryCode") = UPPER(mem."homeCountryCode")
LEFT JOIN lookups."Country" AS home_id
ON UPPER(home_id.id) = UPPER(mem."homeCountryCode")
LEFT JOIN lookups."Country" AS comp_code
ON UPPER(comp_code."countryCode") = UPPER(mem."competitionCountryCode")
LEFT JOIN lookups."Country" AS comp_id
ON UPPER(comp_id.id) = UPPER(mem."competitionCountryCode")
WHERE COALESCE(
CASE
WHEN challenge_reviewers.is_ai_only_challenge
THEN ai_decision."totalScore"
ELSE final_review."aggregateScore"
END,
s."finalScore"::double precision,
s."initialScore"::double precision
) IS NOT NULL
AND (
(challenge_reviewers.is_ai_only_challenge
AND UPPER(ai_decision.status::text) = 'PASSED')
OR (
NOT challenge_reviewers.is_ai_only_challenge
AND COALESCE(final_review."aggregateScore", s."finalScore"::double precision, s."initialScore"::double precision)
>= COALESCE(sc."minimumPassingScore", sc."minScore", 0)
)
)
),
unique_member_submissions AS (
SELECT DISTINCT ON (challenge_id, user_id)
ms.*
FROM member_submissions AS ms
ORDER BY
ms.challenge_id,
ms.user_id,
ms.score DESC NULLS LAST,
ms.submitted_date DESC NULLS LAST,
ms.submission_id DESC
),
challenge_prizes AS (
SELECT
cps."challengeId" AS challenge_id,
ROW_NUMBER() OVER (PARTITION BY cps."challengeId" ORDER BY p.id) AS placement,
p.value
FROM challenges."ChallengePrizeSet" AS cps
JOIN challenges."Prize" AS p
ON p."prizeSetId" = cps.id
WHERE cps.type = 'PLACEMENT'
),
challenge_summary AS (
SELECT
cc.challenge_id,
cc.challenge_name,
cc.duration_days,
COALESCE(SUM(pr.value), 0) AS prize_pool,
COUNT(DISTINCT ums.user_id) AS submissions_count,
JSONB_AGG(
JSONB_BUILD_OBJECT('placement', pr.placement, 'value', pr.value) ORDER BY pr.placement
) FILTER (WHERE pr.value IS NOT NULL) AS placement_prizes
FROM challenge_context AS cc
LEFT JOIN unique_member_submissions AS ums
ON ums.challenge_id = cc.challenge_id
LEFT JOIN challenge_prizes AS pr
ON pr.challenge_id = cc.challenge_id
GROUP BY cc.challenge_id, cc.challenge_name, cc.duration_days
)
SELECT
ums.challenge_id AS "challengeId",
cs.challenge_name AS "challengeName",
cs.duration_days AS "durationDays",
cs.prize_pool AS "prizePool",
cs.submissions_count AS "submissionsCount",
cs.placement_prizes AS "placementPrizes",
ums.user_id AS "userId",
ums.handle AS handle,
ums.name AS name,
COALESCE(
home_code.name,
home_id.name,
comp_code.name,
comp_id.name,
NULLIF(TRIM(ums.country), '')
) AS country,
COALESCE(
NULLIF(TRIM(ums.country_code), ''),
NULLIF(TRIM(ums.country_code), '')
) AS "countryCode",
ums.photoURL AS "photoURL",
ums.rating AS rating,
ums.ratingColor AS "ratingColor",
ums.score AS score,
ums.submitted_date AS submitted_date,
ROW_NUMBER() OVER (
PARTITION BY ums.challenge_id
ORDER BY ums.score DESC NULLS LAST, ums.submitted_date ASC NULLS LAST, ums.user_id ASC
) AS placement
FROM unique_member_submissions AS ums
LEFT JOIN lookups."Country" AS home_code
ON UPPER(home_code."countryCode") = UPPER(ums.country_code)
LEFT JOIN lookups."Country" AS home_id
ON UPPER(home_id.id) = UPPER(ums.country_code)
LEFT JOIN lookups."Country" AS comp_code
ON UPPER(comp_code."countryCode") = UPPER(ums.country_code)
LEFT JOIN lookups."Country" AS comp_id
ON UPPER(comp_id.id) = UPPER(ums.country_code)
JOIN challenge_summary AS cs
ON cs.challenge_id = ums.challenge_id
ORDER BY ums.challenge_id, ums.score DESC NULLS LAST, ums.submitted_date ASC NULLS LAST, ums.user_id ASC;
97 changes: 97 additions & 0 deletions sql/reports/topcoder/leaderboard-mm.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
WITH challenge_context AS (
SELECT
c.id AS challenge_id,
c.name AS challenge_name,
LOWER(ct.name) = 'marathon match' AS is_marathon_match
FROM challenges."Challenge" AS c
JOIN challenges."ChallengeType" AS ct
ON ct.id = c."typeId"
WHERE c.id = ANY($1::text[])
),
submission_metrics AS (
SELECT
cc.challenge_id,
s.id AS submission_id,
s."memberId" AS user_id,
COALESCE(
NULLIF(TRIM(u.handle), ''),
NULLIF(TRIM(mem.handle), ''),
fallback.member_handle
) AS handle,
COALESCE(final_review."aggregateScore", s."finalScore"::double precision) AS standard_score,
provisional_review.provisional_score,
COALESCE(final_review."aggregateScore", s."finalScore"::double precision) AS final_score_raw,
COALESCE(s."submittedDate", s."createdAt") AS submitted_date
FROM challenge_context AS cc
JOIN reviews."submission" AS s
ON s."challengeId" = cc.challenge_id
AND s."memberId" IS NOT NULL
LEFT JOIN LATERAL (
SELECT rs."aggregateScore"
FROM reviews."reviewSummation" AS rs
WHERE rs."submissionId" = s.id
AND COALESCE(rs."isFinal", TRUE) = TRUE
AND rs."isProvisional" IS DISTINCT FROM TRUE
ORDER BY COALESCE(rs."reviewedDate", rs."createdAt") DESC NULLS LAST, rs.id DESC
LIMIT 1
) AS final_review ON TRUE
LEFT JOIN LATERAL (
SELECT rs."aggregateScore" AS provisional_score
FROM reviews."reviewSummation" AS rs
WHERE rs."submissionId" = s.id
AND rs."isProvisional" IS TRUE
ORDER BY COALESCE(rs."reviewedDate", rs."createdAt") DESC NULLS LAST, rs.id DESC
LIMIT 1
) AS provisional_review ON TRUE
LEFT JOIN members."member" AS mem
ON mem."userId" = s."memberId"::bigint
LEFT JOIN identity."user" AS u
ON s."memberId" ~ '^[0-9]+$'
AND u.user_id = s."memberId"::numeric
LEFT JOIN LATERAL (
SELECT MAX(r."memberHandle") AS member_handle
FROM resources."Resource" AS r
WHERE r."challengeId" = cc.challenge_id
AND r."memberId" = s."memberId"
) AS fallback ON TRUE
WHERE COALESCE(final_review."aggregateScore", s."finalScore"::double precision, s."initialScore"::double precision) IS NOT NULL
),
unique_member_submissions AS (
SELECT DISTINCT ON (challenge_id, user_id)
sm.*
FROM submission_metrics AS sm
ORDER BY
sm.challenge_id,
sm.user_id,
sm.submitted_date DESC NULLS LAST,
sm.submission_id DESC
),
ranked_submissions AS (
SELECT
ums.challenge_id,
ums.user_id AS "userId",
ums.submission_id AS "submissionId",
ums.handle AS handle,
ROW_NUMBER() OVER (
PARTITION BY ums.challenge_id
ORDER BY ums.final_score_raw DESC NULLS LAST, ums.submitted_date ASC NULLS LAST, ums.user_id ASC
) AS placement,
ums.provisional_score AS "provisionalScore",
ums.final_score_raw AS "finalScore",
ums.standard_score AS score
FROM unique_member_submissions AS ums
)
SELECT
cc.challenge_id AS "challengeId",
cc.challenge_name AS "challengeName",
rs."userId",
rs."submissionId",
rs.handle,
rs.placement,
rs."provisionalScore",
rs."finalScore",
rs.score
FROM ranked_submissions AS rs
JOIN challenge_context AS cc
ON cc.challenge_id = rs.challenge_id
ORDER BY rs.challenge_id, rs.placement;
1 change: 1 addition & 0 deletions src/app-constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export const Scopes = {
TopgearCancelledChallenge: "reports:topgear-cancelled-challenge",
AllReports: "reports:all",
TopcoderReports: "reports:topcoder",
TopcoderLeaderboardReports: "reports:topcoder-leaderboard",
Member: {
EngagementData: "reports:member-engagement-data",
RecentMemberData: "reports:member-recent-member-data",
Expand Down
42 changes: 42 additions & 0 deletions src/reports/topcoder/dto/leaderboard-generic.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Transform } from "class-transformer";
import { IsArray, IsBoolean, IsNotEmpty, IsNumber, IsOptional, IsString } from "class-validator";

export class LeaderboardGenericQueryDto {
@Transform(({ value }) => {
if (typeof value === "string") {
return value
.split(",")
.map((item) => item.trim())
.filter(Boolean);
}
return value;
})
@IsArray()
@IsString({ each: true })
@IsNotEmpty({ each: true })
challengeIds!: string[];

@Transform(({ value }) => (typeof value === "string" ? Number(value) : value))
@IsOptional()
@IsNumber()
pointsPerDay?: number;

@Transform(({ value }) => {
if (typeof value === "string") {
return value
.split(",")
.map((item) => item.trim())
.filter(Boolean);
}
return value;
})
@IsOptional()
@IsArray()
@IsString({ each: true })
placementPrizeAmounts?: string[];

@Transform(({ value }) => value === "true" || value === true)
@IsOptional()
@IsBoolean()
showHeadingAndSubtitle?: boolean;
}
Loading
Loading