Spaces:
Running on Zero
Running on Zero
Download app/services/contrib_rules.py from cipher-attacks/Dbu-central-bot: direct link, hf CLI and curl.
- Browser
- Download file 2.63 kB
-
https://huggingface.co/spaces/cipher-attacks/Dbu-central-bot/resolve/main/app/services/contrib_rules.py
- Command line
-
hf download hf://spaces/cipher-attacks/Dbu-central-bot/app/services/contrib_rules.py
-
curl -L -o contrib_rules.py https://huggingface.co/spaces/cipher-attacks/Dbu-central-bot/resolve/main/app/services/contrib_rules.py
2.63 kB
| """Rules for student contributions. Pure, so every rule is unit-tested.""" | |
| import os | |
| from datetime import datetime, timedelta | |
| from typing import Optional, Sequence | |
| ALLOWED_EXT = (".pdf", ".pptx", ".docx") | |
| MAX_BYTES = 20 * 1024 * 1024 # the bot can only read files up to 20 MB (needed for duplicate checks + stamping) | |
| BLOCK_AFTER_REJECTIONS = 3 | |
| REJECTION_WINDOW_DAYS = 14 | |
| BLOCK_DAYS = 7 | |
| SPARSE_COURSE_FILES = 3 # a course with fewer files than this counts as "wanted" | |
| BADGES = ((15, "gold"), (5, "silver"), (1, "bronze")) # approved files needed -> badge | |
| REJECT_REASONS = ("duplicate", "quality", "notallowed") | |
| def check_file(file_name: Optional[str], size: Optional[int]) -> Optional[str]: | |
| """None = fine, else a reason code ('ext' | 'size').""" | |
| if os.path.splitext(file_name or "")[1].lower() not in ALLOWED_EXT: | |
| return "ext" | |
| if size is None or size > MAX_BYTES: | |
| return "size" | |
| return None | |
| def blocked_until(rejected_at: Sequence[datetime], now: datetime) -> Optional[datetime]: | |
| """3 rejections inside 14 days -> no new submissions for 7 days after the last one.""" | |
| recent = sorted(d for d in rejected_at if d >= now - timedelta(days=REJECTION_WINDOW_DAYS)) | |
| if len(recent) < BLOCK_AFTER_REJECTIONS: | |
| return None | |
| until = recent[-1] + timedelta(days=BLOCK_DAYS) | |
| return until if until > now else None | |
| def eligibility( | |
| *, active_days: int, min_days: int, pending: int, pending_limit: int, today: int, daily_limit: int, | |
| blocked: Optional[datetime], | |
| ) -> Optional[str]: | |
| """None = may submit, else a reason code.""" | |
| if active_days < min_days: | |
| return "new" | |
| if blocked is not None: | |
| return "blocked" | |
| if pending >= pending_limit: | |
| return "pending" | |
| if today >= daily_limit: | |
| return "daily" | |
| return None | |
| def badge_for(approved: int) -> Optional[str]: | |
| for needed, tier in BADGES: | |
| if approved >= needed: | |
| return tier | |
| return None | |
| def next_badge(approved: int) -> Optional[tuple[int, str]]: | |
| """(files still needed, tier) for the next badge, or None at the top.""" | |
| for needed, tier in reversed(BADGES): | |
| if approved < needed: | |
| return needed - approved, tier | |
| return None | |
| def reward( | |
| *, rewarded_this_week: int, course_files_before: int, xp: int, bonus: int, weekly_cap: int | |
| ) -> tuple[int, int]: | |
| """(XP, bonus XP). Nothing once the weekly cap is used; the bonus is for courses that are short of material.""" | |
| if rewarded_this_week >= weekly_cap: | |
| return 0, 0 | |
| return xp, (bonus if course_files_before < SPARSE_COURSE_FILES else 0) | |