141 lines
4.7 KiB
Python
141 lines
4.7 KiB
Python
# -*- 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()))
|