Files
ClassFeedback/.claude/memory/temp/fetch_student_code.py

141 lines
4.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""
Fetch final submission code per (student, problem) for CSP04 lesson 01 (基础回顾).
Targets both CSP04 sections (0830 & 1400). Saves to JSON.
"""
import io, sys, json, re, httpx
from collections import defaultdict
from pathlib import Path
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent.parent
env = {}
for line in (PROJECT_ROOT / '.env').read_text(encoding='utf-8').split('\n'):
line = line.strip()
if line and not line.startswith('#') and '=' in line:
k, v = line.split('=', 1)
env[k] = v
base = env.get('OJ_BASE_URL', 'https://oj.qonnwolf.com')
TID = '6a8c6a8b8499477a04169f85' # CSP04 lesson 01 基础回顾 (A包)
# 数字题目 -> 展示名(参考课件 OJ ID)
PID_MAP = {
6775: 'S0401P1 小杨的回文串',
6776: 'S0401P2 小杨有序去重',
6777: 'S0401P3 小杨的进制数位',
6968: 'S0401P4 小杨的幸运数字',
6969: 'S0401P5 小杨的报数游戏',
}
CLASSES = {
'0830': ['欧俊宇', '汪子杰', '谢明泓', '王佳琳', '魏铭轩', '张立坤'],
'1400': ['郑子煜', '张雨禾', '苏俊宇', '卢华琛', '鲍佳音'],
}
ALL_TARGETS = sorted({n for lst in CLASSES.values() for n in lst})
STATUS_NAMES = {1: 'AC', 2: 'WA', 3: 'CE', 4: 'RE', 5: 'TLE', 6: 'MLE', 7: 'PE', 8: 'OLE'}
client = httpx.Client(base_url=base, follow_redirects=True, timeout=30)
r = client.post('/login', json={'uname': env.get('OJ_USERNAME'), 'password': env.get('OJ_PASSWORD')})
ok = any(c.name == 'sid' for c in client.cookies.jar)
print('LOGIN:', 'OK' if ok else 'FAIL')
if not ok:
raise SystemExit('login failed')
# 1. fetch all records for the homework
records = []
seen = set()
for page in range(1, 40):
r = client.get(f'/d/system/record?tid={TID}&page={page}', headers={'Accept': 'application/json'})
if r.status_code != 200:
break
try:
rdocs = r.json().get('rdocs', [])
except Exception:
break
if not rdocs:
break
for rec in rdocs:
rid = rec.get('_id')
if rid in seen:
continue
seen.add(rid)
records.append({
'rid': rid, 'uid': rec.get('uid'), 'pid': rec.get('pid'),
'status': rec.get('status'), 'score': rec.get('score', 0),
'judgeAt': rec.get('judgeAt', ''), 'lang': rec.get('lang'),
})
if len(rdocs) < 100:
break
print('TOTAL RECORDS:', len(records))
# 2. build uid -> name
uids = {rec['uid'] for rec in records}
uid_name = {}
for uid in sorted(uids):
try:
rr = client.get(f'/user/{uid}')
m = re.search(r'<title>([^<]+)</title>', rr.text)
if m:
nm = m.group(1).split(' - ')[0].strip()
uid_name[uid] = nm
except Exception:
pass
print('UID->NAME mapped:', len(uid_name))
for rec in records:
rec['name'] = uid_name.get(rec['uid'], '')
# 3. for each target student, per problem pick the LAST submission (max judgeAt by iso)
last_by_key = {}
for rec in records:
if rec['name'] not in ALL_TARGETS:
continue
if rec['pid'] not in PID_MAP:
continue
key = (rec['name'], rec['pid'])
if key not in last_by_key or rec['judgeAt'] > last_by_key[key]['judgeAt']:
last_by_key[key] = rec
print('LAST-SUB keys:', len(last_by_key))
# 4. fetch code per (name, pid)
result = {}
sample_shown = 0
for (name, pid), rec in last_by_key.items():
try:
rr = client.get(f"/d/system/record/{rec['rid']}", headers={'Accept': 'application/json'})
j = rr.json()
rdoc = j.get('rdoc', {})
code = rdoc.get('code', '')
lang = rdoc.get('lang') or rec.get('lang')
except Exception as e:
code = ''
lang = None
entry = {
'key': PID_MAP[pid],
'code': code,
'lang': lang,
'status': STATUS_NAMES.get(rec['status'], '?'),
'score': rec.get('score'),
'attempts': sum(1 for x in records if x['name'] == name and x['pid'] == pid),
'judgeAt': rec.get('judgeAt'),
}
result.setdefault(name, {})[str(pid)] = entry
if sample_shown < 2 and code:
sample_shown += 1
print(f"SAMPLE CODE [{name} / {PID_MAP[pid]}] lang={lang}:\n{code[:300]}")
client.close()
out = {
'tid': TID,
'classes': CLASSES,
'pid_map': {str(k): v for k, v in PID_MAP.items()},
'students': result,
}
out_path = PROJECT_ROOT / '.claude' / 'memory' / 'temp' / 'student_code_CSP04_L01.json'
out_path.write_text(json.dumps(out, ensure_ascii=False, indent=2), encoding='utf-8')
print('SAVED ->', out_path)
print('students with code:', sorted(result.keys()))