feat: 第17课课评完成 + 学生档案更新 + 课评网页模板 + 暑假绩效考核

This commit is contained in:
chengzi
2026-07-01 10:26:46 +08:00
parent 42b3c25d20
commit 9bc8e9a0d8
102 changed files with 4962 additions and 25 deletions

85
scripts/fetch_oj_csp17.py Normal file
View File

@@ -0,0 +1,85 @@
import httpx, re, json
from pathlib import Path
import sys
sys.stdout.reconfigure(encoding='utf-8')
env = {}
for line in Path('E:/workSpace/02-ClassFeedback/.env').read_text(encoding='utf-8').split('\n'):
if '=' in line and not line.startswith('#'):
k, v = line.split('=', 1)
env[k.strip()] = v.strip()
client = httpx.Client(base_url=env['OJ_BASE_URL'], follow_redirects=True, timeout=30)
r = client.post('/login', json={'uname': env['OJ_USERNAME'], 'password': env['OJ_PASSWORD']})
print('Login:', r.status_code)
hw_id = '6a35eadbc2880e90d8609759'
# Get problem list
r = client.get(f'/homework/{hw_id}')
problems = re.findall(r'href=\"/p/([^\"]+)\?tid=' + hw_id + r'\"[^>]*><b>([^<]+)</b>(?:&nbsp;|\s)*([^<]*)</a>', r.text)
print('Problems:')
prob_map = {}
for pid, pid_display, pname in problems:
pname_clean = pname.strip() if pname.strip() else pid_display
prob_map[pid] = pname_clean
print(f' {pid}: {pname_clean}')
# Get all submissions
print('\n--- Student Submissions ---')
student_data = {}
for page in range(1, 11):
r2 = client.get(f'/record?tid={hw_id}&page={page}')
if '没有找到相关记录' in r2.text:
break
rows = re.findall(r'<tr[^>]*>(.*?)</tr>', r2.text, re.DOTALL)
found_any = False
for row in rows:
if 'col--status' not in row:
continue
found_any = True
name_match = re.search(r'<a[^>]*href=\"/user/\d+\"[^>]*>([^<]+)</a>', row)
prob_match = re.search(r'href=\"/p/([^\"]+)\?tid=' + hw_id + r'\"[^>]*>([^<]+)</a>', row)
status_match = re.search(r'record-status--text\s+(\w+)\">([^<]+)<', row)
score_match = re.search(r'<span[^>]*>\s*(\d+)\s*</span>', row)
time_match = re.search(r'(\d{2}-\d{2}\s+\d{2}:\d{2})', row)
if name_match:
name = name_match.group(1).strip()
pid = prob_match.group(1) if prob_match else '?'
pname = prob_map.get(pid, pid)
status = status_match.group(2).strip() if status_match else '?'
score = score_match.group(1) if score_match else '?'
time_str = time_match.group(1) if time_match else '?'
if name not in student_data:
student_data[name] = []
student_data[name].append({
'problem': pname,
'status': status,
'score': score,
'time': time_str
})
if not found_any:
break
# Our target students
targets = ['杨林轩', '欧俊宇', '汪子杰', '谢明泓']
with open('E:/workSpace/02-ClassFeedback/oj_csp17_result.txt', 'w', encoding='utf-8') as f:
for name in targets:
if name in student_data:
f.write(f'\n{name}】({len(student_data[name])}次提交):\n')
for rec in student_data[name]:
f.write(f' {rec["problem"]}: {rec["status"]} (得分: {rec["score"]}, 时间: {rec["time"]})\n')
else:
f.write(f'\n{name}】: 无提交记录\n')
f.write('\n--- All students found ---\n')
for name in sorted(student_data.keys()):
f.write(f' {name}: {len(student_data[name])} submissions\n')
print('Done! Results written to oj_csp17_result.txt')

View File

@@ -0,0 +1,154 @@
import httpx, json, re
from pathlib import Path
from collections import defaultdict
env = {}
for line in Path('E:/workSpace/02-ClassFeedback/.env').read_text(encoding='utf-8').split('\n'):
if '=' in line and not line.startswith('#'):
k, v = line.split('=', 1)
env[k.strip()] = v.strip()
client = httpx.Client(base_url=env['OJ_BASE_URL'], follow_redirects=True, timeout=30)
client.post('/login', json={'uname': env['OJ_USERNAME'], 'password': env['OJ_PASSWORD']})
hw_id = '6a35eadbc2880e90d8609759'
# Step 1: Get problem names
r = client.get(f'/homework/{hw_id}')
problems_html = re.findall(r'href=\"/p/([^\"]+)\?tid=' + hw_id + r'\"[^>]*><b>([^<]+)</b>(?:&nbsp;|\s)*([^<]*)</a>', r.text)
pid_map = {}
for pid, pid_display, pname in problems_html:
pname_clean = pname.strip() if pname.strip() else pid_display
pid_map[pid] = pname_clean
print(f'Problem: {pid} -> {pname_clean}')
# Step 2: Try to get problem IDs from API
# First get all records to find actual pids
all_records = []
for page in range(1, 20):
r = client.get(f'/d/system/record?tid={hw_id}&page={page}', headers={'Accept': 'application/json'})
data = r.json()
rdocs = data.get('rdocs', [])
if not rdocs:
break
all_records.extend(rdocs)
print(f'Page {page}: {len(rdocs)} records')
print(f'\nTotal records: {len(all_records)}')
# Step 3: Collect unique pids and build lookup
# Get problem info from API
unique_pids = set(r['pid'] for r in all_records)
print(f'Unique pids: {unique_pids}')
# Map pid -> problem name using pdict
# The pid_map from homework page uses string IDs, API uses numeric pids
# Try to get problem info from the homework page's pdict
r = client.get(f'/homework/{hw_id}')
pdict_match = re.search(r'pdict\s*=\s*({[^;]+})', r.text)
if pdict_match:
try:
pdict = json.loads(pdict_match.group(1))
print(f'\npdict found: {len(pdict)} entries')
for k, v in list(pdict.items())[:5]:
print(f' {k}: {v}')
except:
pass
# Also try to get problem title from problem page
pid_name_map = {}
for pid in unique_pids:
try:
r = client.get(f'/p/{pid}')
title_match = re.search(r'<title>([^<]+)</title>', r.text)
if title_match:
title = title_match.group(1).split(' - ')[0].strip()
pid_name_map[pid] = title
print(f' pid {pid} -> {title}')
except:
pass
# Step 4: Map uid -> name using user API
uid_names = {}
unique_uids = set(r['uid'] for r in all_records)
for uid in unique_uids:
try:
r = client.get(f'/user/{uid}')
name_match = re.search(r'<title>([^<]+)</title>', r.text)
if name_match:
name = name_match.group(1).split(' - ')[0].strip()
uid_names[uid] = name
print(f' uid {uid} -> {name}')
except:
pass
# Step 5: Status codes
STATUS_MAP = {1: '通过', 2: '答案错误', 3: '编译错误', 4: '运行错误', 5: '超时', 6: '超内存', 7: '输出格式错误', 8: '运行时错误'}
# Step 6: Organize per student per problem
targets = ['杨林轩', '欧俊宇', '汪子杰', '谢明泓']
print('\n' + '='*60)
print('CSP03-17 OJ 做题数据 (A包 - GESP真题训练)')
print('='*60)
# Build reverse map: name -> uid
name_uid = {v: k for k, v in uid_names.items()}
for name in targets:
uid = name_uid.get(name)
if not uid:
print(f'\n{name}】: 未找到匹配的OJ用户')
continue
records = [r for r in all_records if r['uid'] == uid]
if not records:
print(f'\n{name}】: 无提交记录')
continue
# Group by pid
by_pid = defaultdict(list)
for rec in records:
by_pid[rec['pid']].append(rec)
print(f'\n{name}】共 {len(records)} 次提交:')
total_score = 0
for pid, recs in sorted(by_pid.items()):
pname = pid_name_map.get(pid, f'题目{pid}')
# Get best score
best = max(recs, key=lambda r: r['score'])
status_text = STATUS_MAP.get(best['status'], f'状态{best["status"]}')
# Check if any AC
has_ac = any(r['status'] == 1 for r in recs)
ac_mark = '' if has_ac else ''
print(f' {ac_mark} {pname}: {status_text} | 最高分: {best["score"]} | 提交 {len(recs)}')
# Show score progression if multiple attempts
if len(recs) > 1:
scores = [r['score'] for r in sorted(recs, key=lambda r: r['judgeAt'])]
print(f' 分数变化: {scores}')
total_score += best['score']
print(f' 📊 总分: {total_score}')
# Also show per-problem summary
print('\n' + '='*60)
print('按题目汇总')
print('='*60)
for pid in sorted(unique_pids):
pname = pid_name_map.get(pid, f'题目{pid}')
print(f'\n{pname}:')
for name in targets:
uid = name_uid.get(name)
if not uid:
continue
records = [r for r in all_records if r['uid'] == uid and r['pid'] == pid]
if not records:
print(f' {name}: 未提交')
else:
best = max(records, key=lambda r: r['score'])
status_text = STATUS_MAP.get(best['status'], f'状态{best["status"]}')
ac = '' if best['status'] == 1 else ''
print(f' {ac} {name}: {best["score"]}分 ({len(records)}次提交) - {status_text}')