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",
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:
app.pretty_exceptions_enable = False
set_settings(Settings(_env_file=env_path))
@ -322,6 +331,7 @@ def joj3_all_env(
logger.error("missing required env var")
raise Exit(code=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)
res = {
"totalScore": total_score,
@ -350,6 +360,7 @@ def joj3_all_env(
submitter_repo_name,
issue_label_name,
issue_label_color,
penalty_factor,
)
res["issue"] = 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),
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:
app.pretty_exceptions_enable = False
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(
begin_time,
end_time,
penalty_config,
)
count_msg, count_failed = tea.pot.joj3_check_submission_count(
env, grading_repo_name, group_config, scoreboard_filename

View File

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

View File

@ -2,8 +2,8 @@ import bisect
import csv
import json
import os
from datetime import datetime
from typing import Any, Dict, List, Tuple
from datetime import datetime, timedelta
from typing import Any, Dict, List, Optional, Tuple
from pydantic_settings import BaseSettings
@ -215,6 +215,7 @@ def generate_title_and_comment(
submitter_in_title: bool = True,
run_id: str = "unknown",
max_total_score: int = -1,
penalty_factor: float = 1.0,
) -> Tuple[str, str]:
with open(score_file_path) as 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 "
"[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:
if all(
result["score"] == 0 and result["comment"].strip() == ""
@ -254,6 +257,8 @@ def generate_title_and_comment(
comment += "</details>\n\n"
total_score += result["score"]
comment += "\n"
if penalty_factor != 1.0:
total_score = round(total_score * penalty_factor)
title = get_title_prefix(exercise_name, submitter, submitter_in_title)
if max_total_score >= 0:
title += f"{total_score} / {max_total_score}"
@ -281,3 +286,29 @@ def get_title_prefix(
if not submitter_in_title:
title = f"JOJ3 Result for {exercise_name} - Score: "
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