A Docker-based Python assignment grading system with Snowboard (LMS) integration.
| ID | Section |
|---|---|
| 86345 | 001분반 |
| 86347 | 003분반 |
This project uses a strict Conda environment policy.
Use requirements.txt or the direct command below.
conda create -n PythonJudgeSystem -c conda-forge python=3.14 pydantic pyyaml pandas requests beautifulsoup4 docker-py lxml pymysql python-dotenv rich fastapi uvicorn jinja2
conda activate PythonJudgeSystemLibraries:
python=3.14: Runtimepydantic: Data validationpyyaml: Configuration parsingpandas: Data handlingrequests,beautifulsoup4,lxml: Snowboard Crawlerdocker-py: Sandbox managementpymysql: Database connectivitypython-dotenv: Environment variable managementrich: CLI status spinner and formattingfastapi,uvicorn,jinja2: Web Server & Dashboard
Create a .env file in the project root:
# Database
DB_HOST=localhost
DB_USER=root
DB_PASSWORD=your_password
DB_NAME=PythonJudgeSystem
# Snowboard Login
SNOWBOARD_USER=your_id
SNOWBOARD_PASSWORD=your_pw
# Telegram Notifications
TELEGRAM_TOKEN=your_bot_token
TELEGRAM_CHAT_ID=your_chat_id
# Mole Challenge (Assignment 13)
# Random 64-hex. 한 번 설정하면 절대 변경 금지 — 기존 학생의 개인/정답 토큰이 모두 바뀐다.
# 생성 예: python -c "import secrets; print(secrets.token_hex(32))"
MOLE_SALT=your_random_64hex두더지 챌린지 (Assignment 13) 운영 노트
MOLE_SALT가 없으면src/icp_mole.py와src/utils/mole_token.py가 import 시점에KeyError로 즉시 실패한다.- 채점 흐름은
assignments/<id>/assignment.yaml에type: "token"으로 선언하며, Docker sandbox 를 우회한다.- 학생은 Online text ("직접 작성") 로 정답 토큰을 제출하고, 채점기가 본문에서 64-hex 정답 토큰을 찾는다.
- 미제출자에게 monitor 가 한 번에 한해 개인 토큰 코멘트를 자동 안내한다 (
mole_prefills테이블로 idempotent).python src/main.py prefill-tokens --lecture <id> --assignment <id>로 수동 prefill 도 가능.
python src/infrastructure/init_db.pyThe system is managed via a single CLI entrypoint.
Continuously fetches new submissions (requiregrading), grades them, and uploads scores.
-
Standard Foreground Mode:
python src/main.py monitor
Runs in the foreground with a rich, live-updating terminal dashboard.
-
Daemon Mode (Infinite Loop Background):
python src/main.py monitor --daemon
Runs without the rich UI, suitable for systemd background services.
-
Viewing Background Status: If the monitor is running in the background, you can view the live dashboard safely in another terminal:
python src/main.py status
-
Viewing Monitor Logs (systemd): If running via the
pythonjudge-monitor.service, you can stream the standard logs:journalctl --user -u pythonjudge-monitor.service -f
-
Manual Override:
python src/main.py monitor --lecture 86347 --assignment 1961959
-
Force Re-Evaluation (Dangerous):
python src/main.py monitor --lecture 86347 --assignment 1961959 --force
Loops while forcing full re-evaluation of ALL history (fetch
submittedstatus). Use with caution oroneshotpreferred.
Perform a single fetch-grade-upload cycle and exit. Ideal for manual triggering or debugging.
-
Standard Run:
python src/main.py oneshot --lecture 86347 --assignment 1961959
-
Force Re-Run (Updates History):
python src/main.py oneshot --lecture 86347 --assignment 1961959 --force
Fetches entire submission history, creating a new "Forced" entry for every student, and re-grades the latest version.
Debug a specific file locally.
python src/main.py eval --assignment 1961959 --submission /tmp/solution_1961959.py --buildTo run the web dashboard in the background automatically as a systemd user service:
-
Create the systemd user directory if it doesn't exist:
mkdir -p ~/.config/systemd/user -
Create the service file (
~/.config/systemd/user/pythonjudge-dashboard.service):[Unit] Description=Python Judge System Dashboard After=network.target [Service] Type=simple WorkingDirectory=%h/2026-PythonJudgeSystem-ICP ExecStart=%h/miniforge3/envs/PythonJudgeSystem/bin/python src/dashboard.py --port 8000 Restart=always RestartSec=3 [Install] WantedBy=default.target
-
Reload the systemd daemon, enable, and start the service:
systemctl --user daemon-reload systemctl --user enable pythonjudge-dashboard --now -
Enable lingering so the service stays running even when you log out:
loginctl enable-linger $USER
The dashboard will be available at http://localhost:8000/<lecture_id>.
- Assignment Rules:
assignments/<id>/assignment.yaml - Monitor Settings:
config/monitor.yamlrefresh_interval: 5 lectures: - 86345 # 001분반 - 86347 # 003분반 blacklist: - 1961998 # 1분반 개발환경 구축 - 1961958 # 3분반 개발환경 구축
The system assigns one of the following verdicts to each submission:
- AC (Accepted): The submission produced correct output for all test cases.
- WA (Wrong Answer): The submission produced incorrect output for at least one test case.
- TLE (Time Limit Exceeded): The submission exceeded the execution time limit.
- MLE (Memory Limit Exceeded): The submission exceeded the memory limit.
- RTE (Runtime Error): The submission raised an unhandled exception (e.g., SyntaxError, ValueError).
- SYS (System Error): An internal system error occurred during grading (e.g., Docker failure).
Assignments in this system are folder-based and support two modes: Standard Judge (I/O) and Special Judge (Custom Logic).
Each assignment must have a dedicated folder in the assignments/ directory named with its Assignment ID.
assignments/
└── [Assignment_ID]/ # e.g., 1802077
├── assignment.yaml # Configuration file (Required)
├── testcases/ # Test Case Directory (Standard Mode)
│ ├── 1/
│ │ ├── input.txt
│ │ └── output.txt
│ ├── 2/
│ │ ├── input.txt
│ │ └── output.txt
│ └── ...
├── run_after.py # Custom Check Script (Optional, fuzzy output matching)
└── evaluator.py # Full Control Script (Special Mode Only)
Every assignment requires an assignment.yaml file defining metadata and resource limits.
You can also specify 3rd Party Conda Libraries required for the assignment.
id: "1808032"
name: "Assignment Name"
type: "standard" # Options: "standard", "special"
resources:
cpu_count: 1
memory_limit: "128m" # e.g., 128m, 512m, 1g
timeout: 5 # Seconds
network_disabled: true
build:
base_image: "condaforge/miniforge3" # Default environment
requirements: # List extra libraries here
- numpy
- pandas
- scipyStandard Judge runs the student's code against input files and compares the output with expected output files.
- Create numbered subdirectories in
testcases/(e.g.,1/,2/,3/). - Each subdirectory contains
input.txtandoutput.txt. - The system automatically discovers and sorts test case directories.
By default, the system performs an Exact Match (strip whitespace).
To implement permissive or fuzzy matching (e.g., ignoring prompts like "Enter number:"), create a run_after.py.
Template:
def check(output, expected):
"""
Args:
output (str): Student's stdout capture.
expected (str): Content of output_X.txt.
Returns:
bool: True if pass, False if fail.
"""
clean_out = output.strip()
clean_exp = expected.strip()
# Example: Check if expected answer appears at the END of output
if clean_out.endswith(clean_exp):
return True
return clean_out == clean_expUse Special Judge when:
- The assignment requires interactive input/output (e.g., a game loop).
- Validity depends on internal state or multiple valid outputs.
- You need to run unit tests (pytest) instead of I/O.
Set type: "special" in assignment.yaml.
You must provide an evaluator.py which fully controls the grading process.
The system executes this script natively (inside the container).
Requirements:
- The script should import/execute the student's code (
Target.py). - It must print JSON-formatted result to stdout (or specific file descriptor) if custom reporting is needed, OR simply raise exceptions on failure.
- (Note: Specific API for Special Judge is defined in
src/core/special_judge.py- currently implemented as running a custom script that returns a Verdict).
The system sends Telegram alerts for monitor start/stop, loop errors, and grading failures. Configure via .env:
TELEGRAM_TOKEN: Bot API tokenTELEGRAM_CHAT_ID: Target chat ID
Notifications fail silently — they never crash the grading system.
Submissions are validated before evaluation:
- Empty file → Score 0, comment in Korean
- Wrong extension (not
.py) → Score 0 - Jupyter Notebook disguised as
.py→ Score 0
