core: contest.getRawStatus API

This commit is contained in:
undefined committed 2020-12-09 23:38:29 +08:00
1 parent 2cc9c6a06a
commit 68286607e9
5 files changed
+46 -27

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "hydrooj", "name": "hydrooj",
"version": "2.14.4", "version": "2.14.5",
"bin": "bin/hydrooj.js", "bin": "bin/hydrooj.js",
"main": "dist/loader.js", "main": "dist/loader.js",
"typings": "dist/loader.d.ts", "typings": "dist/loader.d.ts",
+8
View File
@@ -143,6 +143,13 @@ class ContestScoreboardHandler extends ContestHandler {
} }
} }
class ContestScoreboardRawHandler extends ContestHandler {
@param('tid', Types.ObjectID)
async get(domainId: string, tid: ObjectID) {
this.response.body = await this.getRawStatus(domainId, tid);
}
}
class ContestScoreboardDownloadHandler extends ContestHandler { class ContestScoreboardDownloadHandler extends ContestHandler {
@param('tid', Types.ObjectID) @param('tid', Types.ObjectID)
@param('ext', Types.String) @param('ext', Types.String)
@@ -414,6 +421,7 @@ export async function apply() {
Route('contest_boardcast', '/contest/:tid/boardcast', ContestBoardcastHandler); Route('contest_boardcast', '/contest/:tid/boardcast', ContestBoardcastHandler);
Route('contest_edit', '/contest/:tid/edit', ContestEditHandler, PERM.PERM_VIEW_CONTEST); Route('contest_edit', '/contest/:tid/edit', ContestEditHandler, PERM.PERM_VIEW_CONTEST);
Route('contest_scoreboard', '/contest/:tid/scoreboard', ContestScoreboardHandler, PERM.PERM_VIEW_CONTEST); Route('contest_scoreboard', '/contest/:tid/scoreboard', ContestScoreboardHandler, PERM.PERM_VIEW_CONTEST);
Route('contest_scoreboard_raw', '/contest/:tid/scoreboard/raw', ContestScoreboardRawHandler, PERM.PERM_VIEW_CONTEST);
Route('contest_scoreboard_download', '/contest/:tid/export/:ext', ContestScoreboardDownloadHandler, PERM.PERM_VIEW_CONTEST); Route('contest_scoreboard_download', '/contest/:tid/export/:ext', ContestScoreboardDownloadHandler, PERM.PERM_VIEW_CONTEST);
Route('contest_detail_problem', '/contest/:tid/p/:pid', ContestProblemHandler, PERM.PERM_VIEW_CONTEST); Route('contest_detail_problem', '/contest/:tid/p/:pid', ContestProblemHandler, PERM.PERM_VIEW_CONTEST);
Route('contest_detail_problem_submit', '/contest/:tid/p/:pid/submit', ContestProblemSubmitHandler, PERM.PERM_VIEW_CONTEST); Route('contest_detail_problem_submit', '/contest/:tid/p/:pid/submit', ContestProblemSubmitHandler, PERM.PERM_VIEW_CONTEST);
+4 -3
View File
@@ -282,6 +282,7 @@ export interface ScoreboardNode {
value: string, value: string,
raw?: any, raw?: any,
} }
export type ScoreboardRow = ScoreboardNode[] & { raw?: any };
export type PenaltyRules = Dictionary<number>; export type PenaltyRules = Dictionary<number>;
@@ -423,9 +424,9 @@ export interface ContestRule {
stat: (tdoc: Tdoc, journal: any[]) => ContestStat, stat: (tdoc: Tdoc, journal: any[]) => ContestStat,
scoreboard: ( scoreboard: (
isExport: boolean, _: (s: string) => string, isExport: boolean, _: (s: string) => string,
tdoc: Tdoc, rankedTsdocs: any[] | Generator<any>, udict: Udict, pdict: Pdict tdoc: Tdoc, rankedTsdocs: any[], udict: Udict, pdict: Pdict
) => ScoreboardNode[][], ) => ScoreboardRow[],
rank: (tsdocs: any[]) => any[] | Generator<any>, rank: (tsdocs: any[]) => any[],
} }
export type ContestRules = Dictionary<ContestRule>; export type ContestRules = Dictionary<ContestRule>;
+4 -2
View File
@@ -1,13 +1,15 @@
function* ranked<T>(diter: T[], equ = (a: T, b: T) => a === b): Generator<[number, T], void> { function ranked<T>(diter: T[], equ = (a: T, b: T) => a === b): [number, T][] {
let last = null; let last = null;
let r = 0; let r = 0;
let count = 0; let count = 0;
const results: [number, T][] = [];
for (const doc of diter) { for (const doc of diter) {
count++; count++;
if (count === 1 || !equ(last, doc)) r = count; if (count === 1 || !equ(last, doc)) r = count;
last = doc; last = doc;
yield [r, doc]; results.push([r, doc]);
} }
return results;
} }
global.Hydro.lib.rank = ranked; global.Hydro.lib.rank = ranked;
+29 -21
View File
@@ -1,4 +1,5 @@
import { ObjectID, FilterQuery } from 'mongodb'; import { ObjectID, FilterQuery } from 'mongodb';
import { filter, sortBy } from 'lodash';
import * as user from './user'; import * as user from './user';
import * as problem from './problem'; import * as problem from './problem';
import * as document from './document'; import * as document from './document';
@@ -8,7 +9,7 @@ import {
ContestNotAttendedError, ContestScoreboardHiddenError, ContestNotAttendedError, ContestScoreboardHiddenError,
} from '../error'; } from '../error';
import { import {
ContestRule, ContestRules, ScoreboardNode, Tdoc, Udict, ContestRule, ContestRules, ScoreboardNode, ScoreboardRow, Tdoc, Udict,
} from '../interface'; } from '../interface';
import * as misc from '../lib/misc'; import * as misc from '../lib/misc';
import ranked from '../lib/rank'; import ranked from '../lib/rank';
@@ -34,25 +35,24 @@ const acm: ContestRule = {
if (!j.accept) naccept[j.pid] = (naccept[j.pid] || 0) + 1; if (!j.accept) naccept[j.pid] = (naccept[j.pid] || 0) + 1;
} }
} }
function _time(jdoc) {
const real = jdoc.rid.generationTime - Math.floor(tdoc.beginAt.getTime() / 1000);
const penalty = 20 * 60 * naccept[jdoc.pid];
return real + penalty;
}
for (const key in effective) { for (const key in effective) {
const j = effective[key]; const j = effective[key];
const real = j.rid.generationTime - Math.floor(tdoc.beginAt.getTime() / 1000);
const penalty = 20 * 60 * naccept[j.pid];
detail.push({ detail.push({
...j, naccept: naccept[j.pid], time: _time(j), ...j, naccept: naccept[j.pid], time: real + penalty, real, penalty,
}); });
} }
for (const d of detail) { for (const d of detail) {
accept += d.accept; if (d.accept) {
if (d.accept) time += d.time; accept += d.accept;
time += d.time;
}
} }
return { accept, time, detail }; return { accept, time, detail };
}, },
scoreboard(isExport, _, tdoc, rankedTsdocs, udict, pdict) { scoreboard(isExport, _, tdoc, rankedTsdocs, udict, pdict) {
const columns: ScoreboardNode[] = [ const columns: ScoreboardRow = [
{ type: 'rank', value: _('Rank') }, { type: 'rank', value: _('Rank') },
{ type: 'user', value: _('User') }, { type: 'user', value: _('User') },
{ type: 'solved_problems', value: _('Solved Problems') }, { type: 'solved_problems', value: _('Solved Problems') },
@@ -88,13 +88,11 @@ const acm: ContestRule = {
}); });
} }
} }
const rows = [columns]; const rows: ScoreboardRow[] = [columns];
for (const [rank, tsdoc] of rankedTsdocs) { for (const [rank, tsdoc] of rankedTsdocs) {
const tsddict = {}; const tsddict = {};
if (tsdoc.detail) { for (const item of tsdoc.detail || []) tsddict[item.pid] = item;
for (const item of tsdoc.detail) tsddict[item.pid] = item; const row: ScoreboardRow = [];
}
const row = [];
row.push( row.push(
{ type: 'string', value: rank }, { type: 'string', value: rank },
{ type: 'user', value: udict[tsdoc.uid].uname, raw: tsdoc.uid }, { type: 'user', value: udict[tsdoc.uid].uname, raw: tsdoc.uid },
@@ -134,6 +132,7 @@ const acm: ContestRule = {
}); });
} }
} }
row.raw = tsdoc;
rows.push(row); rows.push(row);
} }
return rows; return rows;
@@ -233,7 +232,7 @@ const homework: ContestRule = {
if (exceedSeconds < 0) return score; if (exceedSeconds < 0) return score;
let coefficient = 1; let coefficient = 1;
const keys = Object.keys(tdoc.penaltyRules) const keys = Object.keys(tdoc.penaltyRules)
.map((k) => parseFloat(k)).sort((a, b) => a - b); .map(parseFloat).sort((a, b) => a - b);
for (const i of keys) { for (const i of keys) {
if (i * 3600 <= exceedSeconds) coefficient = tdoc.penaltyRules[i]; if (i * 3600 <= exceedSeconds) coefficient = tdoc.penaltyRules[i];
else break; else break;
@@ -498,6 +497,17 @@ export const ContestHandlerMixin = (c) => class extends c {
domainId: string, tid: ObjectID, domainId: string, tid: ObjectID,
isExport = false, docType: 30 | 60 = document.TYPE_CONTEST, isExport = false, docType: 30 | 60 = document.TYPE_CONTEST,
): Promise<[Tdoc, ScoreboardNode[][], Udict]> { ): Promise<[Tdoc, ScoreboardNode[][], Udict]> {
const {
tdoc, udict, pdict, rankedTsdocs,
} = await this.getRawStatus(domainId, tid, docType);
const rows = RULES[tdoc.rule].scoreboard(
isExport, this.translate.bind(this),
tdoc, rankedTsdocs, udict, pdict,
);
return [tdoc, rows, udict];
}
async getRawStatus(domainId: string, tid: ObjectID, docType: 30 | 60 = document.TYPE_CONTEST) {
const tdoc = await get(domainId, tid, docType); const tdoc = await get(domainId, tid, docType);
if (!this.canShowScoreboard(tdoc)) throw new ContestScoreboardHiddenError(tid); if (!this.canShowScoreboard(tdoc)) throw new ContestScoreboardHiddenError(tid);
const tsdocs = await getMultiStatus(domainId, { docId: tid }, docType).sort(RULES[tdoc.rule].statusSort).toArray(); const tsdocs = await getMultiStatus(domainId, { docId: tid }, docType).sort(RULES[tdoc.rule].statusSort).toArray();
@@ -507,11 +517,9 @@ export const ContestHandlerMixin = (c) => class extends c {
problem.getList(domainId, tdoc.pids, true), problem.getList(domainId, tdoc.pids, true),
]); ]);
const rankedTsdocs = RULES[tdoc.rule].rank(tsdocs); const rankedTsdocs = RULES[tdoc.rule].rank(tsdocs);
const rows = RULES[tdoc.rule].scoreboard( return {
isExport, this.translate.bind(this), tdoc, tsdocs, udict, pdict, rankedTsdocs,
tdoc, rankedTsdocs, udict, pdict, };
);
return [tdoc, rows, udict];
} }
// eslint-disable-next-line class-methods-use-this // eslint-disable-next-line class-methods-use-this