# -*- 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'([^<]+)', 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()))