diff --git a/src/utils/reputation.ts b/src/utils/reputation.ts index 5d06517..61233e8 100644 --- a/src/utils/reputation.ts +++ b/src/utils/reputation.ts @@ -10,7 +10,8 @@ interface ReputationDBResult { votedSum: number, lockedSum: number, semiOldUpvotedSubmissions: number, - oldUpvotedSubmissions: number + oldUpvotedSubmissions: number, + mostUpvotedInLockedVideoSum: number } export async function getReputation(userID: UserID): Promise { @@ -28,43 +29,58 @@ export async function getReputation(userID: UserID): Promise { SUM(CASE WHEN "timeSubmitted" > 1596240000000 THEN "votes" ELSE 0 END) AS "votedSum", SUM(locked) AS "lockedSum", SUM(CASE WHEN "timeSubmitted" < ? AND "timeSubmitted" > 1596240000000 AND "votes" > 0 THEN 1 ELSE 0 END) AS "semiOldUpvotedSubmissions", - SUM(CASE WHEN "timeSubmitted" < ? AND "timeSubmitted" > 1596240000000 AND "votes" > 0 THEN 1 ELSE 0 END) AS "oldUpvotedSubmissions" + SUM(CASE WHEN "timeSubmitted" < ? AND "timeSubmitted" > 1596240000000 AND "votes" > 0 THEN 1 ELSE 0 END) AS "oldUpvotedSubmissions", + SUM(CASE WHEN "votes" > 0 + AND NOT EXISTS ( + SELECT * FROM "sponsorTimes" as c + WHERE c."videoID" = "a"."videoID" AND c."votes" > "a"."votes" LIMIT 1) + AND EXISTS ( + SELECT * FROM "lockCategories" as l + WHERE l."videoID" = "a"."videoID" AND l."category" = "a"."category" LIMIT 1) + THEN 1 ELSE 0 END) AS "mostUpvotedInLockedVideoSum" FROM "sponsorTimes" as "a" WHERE "userID" = ?`, [userID, weekAgo, pastDate, userID]) as Promise; const result = await QueryCacher.get(fetchFromDB, reputationKey(userID)); - // Grace period - if (result.totalSubmissions < 5) { - return 0; - } - - const downvoteRatio = result.downvotedSubmissions / result.totalSubmissions; - if (downvoteRatio > 0.3) { - return convertRange(Math.min(downvoteRatio, 0.7), 0.3, 0.7, -0.5, -2.5); - } - - const nonSelfDownvoteRatio = result.nonSelfDownvotedSubmissions / result.totalSubmissions; - if (nonSelfDownvoteRatio > 0.05) { - return convertRange(Math.min(nonSelfDownvoteRatio, 0.4), 0.05, 0.4, -0.5, -2.5); - } - - if (result.votedSum < 5) { - return 0; - } - - if (result.oldUpvotedSubmissions < 3) { - if (result.semiOldUpvotedSubmissions > 3) { - return convertRange(Math.min(result.votedSum, 150), 5, 150, 0, 2) + convertRange(Math.min(result.lockedSum ?? 0, 50), 0, 50, 0, 5); - } else { - return 0; - } - } - - return convertRange(Math.min(result.votedSum, 150), 5, 150, 0, 7) + convertRange(Math.min(result.lockedSum ?? 0, 50), 0, 50, 0, 20); + return calculateFromMetrics(result); } +// convert a number from one range to another. function convertRange(value: number, currentMin: number, currentMax: number, targetMin: number, targetMax: number): number { const currentRange = currentMax - currentMin; const targetRange = targetMax - targetMin; return ((value - currentMin) / currentRange) * targetRange + targetMin; } + +export function calculateFromMetrics(metrics: ReputationDBResult): number { + // Grace period + if (metrics.totalSubmissions < 5) { + return 0; + } + + const downvoteRatio = metrics.downvotedSubmissions / metrics.totalSubmissions; + if (downvoteRatio > 0.3) { + return convertRange(Math.min(downvoteRatio, 0.7), 0.3, 0.7, -0.5, -2.5); + } + + const nonSelfDownvoteRatio = metrics.nonSelfDownvotedSubmissions / metrics.totalSubmissions; + if (nonSelfDownvoteRatio > 0.05) { + return convertRange(Math.min(nonSelfDownvoteRatio, 0.4), 0.05, 0.4, -0.5, -2.5); + } + + if (metrics.votedSum < 5) { + return 0; + } + + if (metrics.oldUpvotedSubmissions < 3) { + if (metrics.semiOldUpvotedSubmissions > 3) { + return convertRange(Math.min(metrics.votedSum, 150), 5, 150, 0, 2) + + convertRange(Math.min((metrics.lockedSum ?? 0) + (metrics.mostUpvotedInLockedVideoSum ?? 0), 50), 0, 50, 0, 5); + } else { + return 0; + } + } + + return convertRange(Math.min(metrics.votedSum, 150), 5, 150, 0, 7) + + convertRange(Math.min((metrics.lockedSum ?? 0) + (metrics.mostUpvotedInLockedVideoSum ?? 0), 50), 0, 50, 0, 20); +} \ No newline at end of file diff --git a/test/cases/reputation.ts b/test/cases/reputation.ts index 8f09a41..dd41d29 100644 --- a/test/cases/reputation.ts +++ b/test/cases/reputation.ts @@ -2,7 +2,7 @@ import assert from "assert"; import { db } from "../../src/databases/databases"; import { UserID } from "../../src/types/user.model"; import { getHash } from "../../src/utils/getHash"; -import { getReputation } from "../../src/utils/reputation"; +import { getReputation, calculateFromMetrics } from "../../src/utils/reputation"; const userIDLowSubmissions = "reputation-lowsubmissions" as UserID; const userIDHighDownvotes = "reputation-highdownvotes" as UserID; @@ -12,11 +12,13 @@ const userIDLowSum = "reputation-lowsum" as UserID; const userIDHighRepBeforeManualVote = "reputation-oldhighrep" as UserID; const userIDHighRep = "reputation-highrep" as UserID; const userIDHighRepAndLocked = "reputation-highlockedrep" as UserID; +const userIDHaveMostUpvotedInLockedVideo = "reputation-mostupvotedaslocked" as UserID; describe("reputation", () => { before(async function() { this.timeout(5000); // this preparation takes longer then usual const videoID = "reputation-videoID"; + const videoID2 = "reputation-videoID-2"; const sponsorTimesInsertQuery = 'INSERT INTO "sponsorTimes" ("videoID", "startTime", "endTime", "votes", "locked", "UUID", "userID", "timeSubmitted", "views", "category", "service", "videoDuration", "hidden", "shadowHidden", "hashedVideoID") VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)'; await db.prepare("run", sponsorTimesInsertQuery, [videoID, 1, 11, 2, 0, "reputation-0-uuid-0", getHash(userIDLowSubmissions), 1606240000000, 50, "sponsor", "YouTube", 100, 0, 0, getHash(videoID, 1)]); @@ -87,6 +89,28 @@ describe("reputation", () => { await db.prepare("run", sponsorTimesInsertQuery, [videoID, 1, 11, -1, 0, "reputation-6-uuid-5", getHash(userIDHighRepAndLocked), 1606240000000, 50, "sponsor", "YouTube", 100, 0, 0, getHash(videoID, 1)]); await db.prepare("run", sponsorTimesInsertQuery, [videoID, 1, 11, 0, 0, "reputation-6-uuid-6", getHash(userIDHighRepAndLocked), 1606240000000, 50, "sponsor", "YouTube", 100, 0, 0, getHash(videoID, 1)]); await db.prepare("run", sponsorTimesInsertQuery, [videoID, 1, 11, 0, 0, "reputation-6-uuid-7", getHash(userIDHighRepAndLocked), 1606240000000, 50, "sponsor", "YouTube", 100, 0, 0, getHash(videoID, 1)]); + + //Record has most upvoted + await db.prepare("run", sponsorTimesInsertQuery, [videoID, 1, 11, 5, 0, "reputation-7-uuid-0", getHash(userIDHaveMostUpvotedInLockedVideo), 1606240000000, 50, "sponsor", "YouTube", 100, 0, 0, getHash(videoID, 1)]); + await db.prepare("run", sponsorTimesInsertQuery, [videoID, 1, 11, 101, 0, "reputation-7-uuid-1", getHash(userIDHaveMostUpvotedInLockedVideo), 1606240000000, 50, "intro", "YouTube", 100, 0, 0, getHash(videoID, 1)]); + await db.prepare("run", sponsorTimesInsertQuery, [videoID2, 1, 11, 5, 0, "reputation-7-uuid-8", getHash(userIDHaveMostUpvotedInLockedVideo), 1606240000000, 50, "sponsor", "YouTube", 100, 0, 0, getHash(videoID2, 1)]); + await db.prepare("run", sponsorTimesInsertQuery, [videoID2, 1, 11, 0, 0, "reputation-7-uuid-9", getHash(userIDHaveMostUpvotedInLockedVideo), 1606240000000, 50, "sponsor", "YouTube", 100, 0, 0, getHash(videoID2, 1)]); + // other segments + await db.prepare("run", sponsorTimesInsertQuery, [videoID, 1, 11, 2, 0, "reputation-7-uuid-2", getHash(userIDHaveMostUpvotedInLockedVideo), 1606240000000, 50, "sponsor", "YouTube", 100, 0, 0, getHash(videoID, 1)]); + await db.prepare("run", sponsorTimesInsertQuery, [videoID, 1, 11, 2, 0, "reputation-7-uuid-3", getHash(userIDHaveMostUpvotedInLockedVideo), 1606240000000, 50, "sponsor", "YouTube", 100, 0, 0, getHash(videoID, 1)]); + await db.prepare("run", sponsorTimesInsertQuery, [videoID, 1, 11, 2, 0, "reputation-7-uuid-4", getHash(userIDHaveMostUpvotedInLockedVideo), 1606240000000, 50, "sponsor", "YouTube", 100, 0, 0, getHash(videoID, 1)]); + await db.prepare("run", sponsorTimesInsertQuery, [videoID, 1, 11, -1, 0, "reputation-7-uuid-5", getHash(userIDHaveMostUpvotedInLockedVideo), 1606240000000, 50, "sponsor", "YouTube", 100, 0, 0, getHash(videoID, 1)]); + await db.prepare("run", sponsorTimesInsertQuery, [videoID, 1, 11, 0, 0, "reputation-7-uuid-6", getHash(userIDHaveMostUpvotedInLockedVideo), 1606240000000, 50, "sponsor", "YouTube", 100, 0, 0, getHash(videoID, 1)]); + await db.prepare("run", sponsorTimesInsertQuery, [videoID, 1, 11, 0, 0, "reputation-7-uuid-7", getHash(userIDHaveMostUpvotedInLockedVideo), 1606240000000, 50, "sponsor", "YouTube", 100, 0, 0, getHash(videoID, 1)]); + + // lock video + const insertVipUserQuery = 'INSERT INTO "vipUsers" ("userID") VALUES (?)'; + await db.prepare("run", insertVipUserQuery, [getHash("VIPUser-getLockCategories")]); + + const insertLockCategoryQuery = 'INSERT INTO "lockCategories" ("userID", "videoID", "category", "hashedVideoID") VALUES (?, ?, ?, ?)'; + await db.prepare("run", insertLockCategoryQuery, [getHash("VIPUser-getLockCategories"), videoID, "sponsor", getHash(videoID, 1)]); + await db.prepare("run", insertLockCategoryQuery, [getHash("VIPUser-getLockCategories"), videoID, "intro", getHash(videoID, 1)]); + await db.prepare("run", insertLockCategoryQuery, [getHash("VIPUser-getLockCategories"), videoID2, "sponsor", getHash(videoID2, 1)]); }); it("user in grace period", async () => { @@ -94,11 +118,34 @@ describe("reputation", () => { }); it("user with high downvote ratio", async () => { - assert.strictEqual(await getReputation(getHash(userIDHighDownvotes)), -2.125); + // -2.125 + const metrics = { + totalSubmissions: 8, + downvotedSubmissions: 5, + nonSelfDownvotedSubmissions: 0, + votedSum: -7, + lockedSum: 0, + semiOldUpvotedSubmissions: 1, + oldUpvotedSubmissions: 1, + mostUpvotedInLockedVideoSum: 0 + }; + + assert.strictEqual(await getReputation(getHash(userIDHighDownvotes)), calculateFromMetrics(metrics)); }); it("user with high non self downvote ratio", async () => { - assert.strictEqual(await getReputation(getHash(userIDHighNonSelfDownvotes)), -1.6428571428571428); + // -1.6428571428571428 + const metrics = { + totalSubmissions: 8, + downvotedSubmissions: 2, + nonSelfDownvotedSubmissions: 2, + votedSum: -1, + lockedSum: 0, + semiOldUpvotedSubmissions: 1, + oldUpvotedSubmissions: 1, + mostUpvotedInLockedVideoSum: 0 + }; + assert.strictEqual(await getReputation(getHash(userIDHighNonSelfDownvotes)), calculateFromMetrics(metrics)); }); it("user with mostly new submissions", async () => { @@ -114,11 +161,49 @@ describe("reputation", () => { }); it("user with high reputation", async () => { - assert.strictEqual(await getReputation(getHash(userIDHighRep)), 0.19310344827586207); + // 0.19310344827586207 + const metrics = { + totalSubmissions: 8, + downvotedSubmissions: 1, + nonSelfDownvotedSubmissions: 0, + votedSum: 9, + lockedSum: 0, + semiOldUpvotedSubmissions: 5, + oldUpvotedSubmissions: 5, + mostUpvotedInLockedVideoSum: 0 + }; + + assert.strictEqual(await getReputation(getHash(userIDHighRep)), calculateFromMetrics(metrics)); }); it("user with high reputation and locked segments", async () => { - assert.strictEqual(await getReputation(getHash(userIDHighRepAndLocked)), 1.793103448275862); + // 1.793103448275862 + const metrics = { + totalSubmissions: 8, + downvotedSubmissions: 1, + nonSelfDownvotedSubmissions: 0, + votedSum: 9, + lockedSum: 4, + semiOldUpvotedSubmissions: 5, + oldUpvotedSubmissions: 5, + mostUpvotedInLockedVideoSum: 0 + }; + assert.strictEqual(await getReputation(getHash(userIDHighRepAndLocked)), calculateFromMetrics(metrics)); + }); + + it("user with most upvoted segments in locked video", async () => { + // 6.158620689655172 + const metrics = { + totalSubmissions: 10, + downvotedSubmissions: 1, + nonSelfDownvotedSubmissions: 0, + votedSum: 116, + lockedSum: 0, + semiOldUpvotedSubmissions: 6, + oldUpvotedSubmissions: 6, + mostUpvotedInLockedVideoSum: 2 + }; + assert.strictEqual(await getReputation(getHash(userIDHaveMostUpvotedInLockedVideo)), calculateFromMetrics(metrics)); }); });