feat: support penalty config
All checks were successful
build / trigger-build-image (push) Successful in 13s

This commit is contained in:
张泊明518370910136 2025-06-19 06:37:03 -04:00
parent 07ef6cd5d8
commit c48bc1a304
GPG Key ID: CA088E6D9284F870
3 changed files with 90 additions and 9 deletions

View File

@ -307,6 +307,15 @@ def joj3_all_env(
"#795548", "#795548",
help="label color for the issue created by this command", help="label color for the issue created by this command",
), ),
end_time: Optional[datetime] = Option(None),
penalty_config: str = Option(
"",
help=(
"Configuration for penalties in the format "
"'hours=factor'. "
"Example: --penalty-config 24=0.75,48=0.5"
),
),
) -> None: ) -> None:
app.pretty_exceptions_enable = False app.pretty_exceptions_enable = False
set_settings(Settings(_env_file=env_path)) set_settings(Settings(_env_file=env_path))
@ -322,6 +331,7 @@ def joj3_all_env(
logger.error("missing required env var") logger.error("missing required env var")
raise Exit(code=1) raise Exit(code=1)
submitter_repo_name = env.github_repository.split("/")[-1] submitter_repo_name = env.github_repository.split("/")[-1]
penalty_factor = joj3.get_penalty_factor(end_time, penalty_config)
total_score = joj3.get_total_score(env.joj3_output_path) total_score = joj3.get_total_score(env.joj3_output_path)
res = { res = {
"totalScore": total_score, "totalScore": total_score,
@ -350,6 +360,7 @@ def joj3_all_env(
submitter_repo_name, submitter_repo_name,
issue_label_name, issue_label_name,
issue_label_color, issue_label_color,
penalty_factor,
) )
res["issue"] = issue_number res["issue"] = issue_number
gitea_issue_url = f"{submitter_repo_url}/issues/{issue_number}" gitea_issue_url = f"{submitter_repo_url}/issues/{issue_number}"
@ -462,6 +473,14 @@ def joj3_check_env(
), ),
begin_time: Optional[datetime] = Option(None), begin_time: Optional[datetime] = Option(None),
end_time: Optional[datetime] = Option(None), end_time: Optional[datetime] = Option(None),
penalty_config: str = Option(
"",
help=(
"Configuration for penalties in the format "
"'hours=factor'. "
"Example: --penalty-config 24=0.75,48=0.5"
),
),
) -> None: ) -> None:
app.pretty_exceptions_enable = False app.pretty_exceptions_enable = False
set_settings(Settings(_env_file=env_path)) set_settings(Settings(_env_file=env_path))
@ -477,6 +496,7 @@ def joj3_check_env(
time_msg, time_failed = tea.pot.joj3_check_submission_time( time_msg, time_failed = tea.pot.joj3_check_submission_time(
begin_time, begin_time,
end_time, end_time,
penalty_config,
) )
count_msg, count_failed = tea.pot.joj3_check_submission_count( count_msg, count_failed = tea.pot.joj3_check_submission_count(
env, grading_repo_name, group_config, scoreboard_filename env, grading_repo_name, group_config, scoreboard_filename

View File

@ -240,6 +240,7 @@ class Teapot:
submitter_repo_name: str, submitter_repo_name: str,
issue_label_name: str, issue_label_name: str,
issue_label_color: str, issue_label_color: str,
penalty_factor: float,
) -> int: ) -> int:
title, comment = joj3.generate_title_and_comment( title, comment = joj3.generate_title_and_comment(
env.joj3_output_path, env.joj3_output_path,
@ -251,6 +252,7 @@ class Teapot:
submitter_in_issue_title, submitter_in_issue_title,
env.joj3_run_id, env.joj3_run_id,
max_total_score, max_total_score,
penalty_factor,
) )
title_prefix = joj3.get_title_prefix( title_prefix = joj3.get_title_prefix(
env.joj3_conf_name, env.github_actor, submitter_in_issue_title env.joj3_conf_name, env.github_actor, submitter_in_issue_title
@ -299,15 +301,43 @@ class Teapot:
self, self,
begin_time: Optional[datetime] = None, begin_time: Optional[datetime] = None,
end_time: Optional[datetime] = None, end_time: Optional[datetime] = None,
penalty_config: str = "",
) -> Tuple[str, bool]: ) -> Tuple[str, bool]:
now = datetime.now() now = datetime.now()
if (begin_time and now < begin_time) or (end_time and now > end_time): penalties = joj3.parse_penalty_config(penalty_config)
return ( if penalties and end_time:
"### Submission Time Check Failed\n" penalty_end_time = end_time + timedelta(hours=penalties[-1][0])
f"Current time {now} is not in the valid range " if begin_time and now < begin_time:
f"[{begin_time}, {end_time}].\n", return (
True, "### Submission Time Check Failed\n"
) f"Current time {now} is not in the valid range "
f"[{begin_time}, {end_time}].\n",
True,
)
elif now > penalty_end_time:
return (
"### Submission Time Check Failed\n"
f"Current time {now} is not in the valid range "
f"[{begin_time}, {end_time}], and the penalty range "
f"[{end_time}, {penalty_end_time}].\n",
True,
)
else:
return (
"### Submission Time Check Passed\n"
f"Current time {now} is not in the valid range "
f"[{begin_time}, {end_time}], but in the penalty range "
f"[{end_time}, {penalty_end_time}].\n",
False,
)
else:
if (begin_time and now < begin_time) or (end_time and now > end_time):
return (
"### Submission Time Check Failed\n"
f"Current time {now} is not in the valid range "
f"[{begin_time}, {end_time}].\n",
True,
)
return ( return (
"### Submission Time Check Passed\n" "### Submission Time Check Passed\n"
f"Current time {now} is in the valid range " f"Current time {now} is in the valid range "

View File

@ -2,8 +2,8 @@ import bisect
import csv import csv
import json import json
import os import os
from datetime import datetime from datetime import datetime, timedelta
from typing import Any, Dict, List, Tuple from typing import Any, Dict, List, Optional, Tuple
from pydantic_settings import BaseSettings from pydantic_settings import BaseSettings
@ -215,6 +215,7 @@ def generate_title_and_comment(
submitter_in_title: bool = True, submitter_in_title: bool = True,
run_id: str = "unknown", run_id: str = "unknown",
max_total_score: int = -1, max_total_score: int = -1,
penalty_factor: float = 1.0,
) -> Tuple[str, str]: ) -> Tuple[str, str]:
with open(score_file_path) as json_file: with open(score_file_path) as json_file:
stages: List[Dict[str, Any]] = json.load(json_file) stages: List[Dict[str, Any]] = json.load(json_file)
@ -234,6 +235,8 @@ def generate_title_and_comment(
"Powered by [JOJ3](https://github.com/joint-online-judge/JOJ3) and " "Powered by [JOJ3](https://github.com/joint-online-judge/JOJ3) and "
"[Joint-Teapot](https://github.com/BoYanZh/Joint-Teapot) with ❤️.\n" "[Joint-Teapot](https://github.com/BoYanZh/Joint-Teapot) with ❤️.\n"
) )
if penalty_factor != 1.0:
comment += f"Note: The total score is multiplied by a penalty factor of {penalty_factor}.\n"
for stage in stages: for stage in stages:
if all( if all(
result["score"] == 0 and result["comment"].strip() == "" result["score"] == 0 and result["comment"].strip() == ""
@ -254,6 +257,8 @@ def generate_title_and_comment(
comment += "</details>\n\n" comment += "</details>\n\n"
total_score += result["score"] total_score += result["score"]
comment += "\n" comment += "\n"
if penalty_factor != 1.0:
total_score = round(total_score * penalty_factor)
title = get_title_prefix(exercise_name, submitter, submitter_in_title) title = get_title_prefix(exercise_name, submitter, submitter_in_title)
if max_total_score >= 0: if max_total_score >= 0:
title += f"{total_score} / {max_total_score}" title += f"{total_score} / {max_total_score}"
@ -281,3 +286,29 @@ def get_title_prefix(
if not submitter_in_title: if not submitter_in_title:
title = f"JOJ3 Result for {exercise_name} - Score: " title = f"JOJ3 Result for {exercise_name} - Score: "
return title return title
def parse_penalty_config(penalty_config: str) -> List[Tuple[float, float]]:
res = []
for penalty in penalty_config.split(","):
hour, factor = map(float, penalty.split("="))
res.append((hour, factor))
res.sort(key=lambda x: x[0])
return res
def get_penalty_factor(
end_time: Optional[datetime],
penalty_config: str,
) -> float:
if not end_time or not penalty_config:
return 1.0
penalties = parse_penalty_config(penalty_config)
now = datetime.now()
res = 0.0
for hour, factor in penalties[::-1]:
if now < end_time + timedelta(hours=hour):
res = factor
else:
break
return res