#!/usr/bin/env python3 """Final comprehensive audit - checks all explanation locations.""" import json, requests, time, sys APP_TOKEN = "CMHSbUUjka3TrUsaxxEc297ongf" r = requests.post("https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal", json={"app_id":"cli_a931175d41799cc7","app_secret":"Iw2vEfbjT6GtV0GhbxbZqfQ4nAPtbR14"}) TOKEN = r.json()["tenant_access_token"] ALL = [ ("tbliZAhcc9C43B23", "听力-P1"), ("tblzTLNH7f13uWQN", "听力-P2"), ("tblgxsDn25oSq7WS", "听力-P3"), ("tblVmeDtBDKsAEfz", "听力-P4"), ("tblDssVmhGzc3UKd", "听力-P5"), ("tbly9SvPEa44k3yX", "听力-P7"), ("tblCgfYDnnqwLfgH", "阅读-P1"), ("tblEp820dnatNYbb", "阅读-P2"), ("tbl4q0ZUV3HB54t1", "阅读-P3"), ("tblzKVm1FEukPgnN", "阅读-P4"), ("tblLmUxzzUDe0QAJ", "阅读-P5"), ("tblJc60aO0T163MJ", "阅读-P6"), ("tblweY65jGBiwSdt", "阅读-P7"), ("tblszuk1TeToofBF", "写作-P1"), ("tblSAwlMumKoyjws", "写作-P2"), ("tblFc9TVl2PeM2tg", "写作-P3"), ("tblRGv7k4WH58Jgq", "口语-P1"), ("tblGoWYBmVI0IrvQ", "口语-P2"), ("tblOHgNkNer2hGEp", "口语-P3"), ("tblsD2dxaRpLmkXD", "口语-P4"), ] def fetch_all(token, table_id): records = [] page_token = None while True: params = {"page_size": 100} if page_token: params["page_token"] = page_token r = requests.get( f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{table_id}/records", headers={"Authorization": f"Bearer {token}"}, params=params) data = r.json() if data.get("code") != 0: break d = data.get("data", {}) records.extend(d.get("items", [])) if not d.get("has_more"): break page_token = d.get("page_token") time.sleep(0.3) return records def has_chinese(s): return any('\u4e00' <= c <= '\u9fff' for c in s) if s else False def check_explanations(block, block_name, qsid, qtype): """Extract all explanations from a block and check them.""" issues = [] # 1. Check block-level explanation block_expl = block.get("explanation", "") if block_expl: if not has_chinese(block_expl): issues.append(f"{block_name}.explanation 纯英文") # 2. Check questionSet-level explanations qs = block.get("questionSet", []) for i, q in enumerate(qs): expl = q.get("explanation", "") if not expl or not expl.strip(): issues.append(f"{block_name}[{i+1}] 解析为空") elif not has_chinese(expl): issues.append(f"{block_name}[{i+1}] 解析纯英文") # 3. Check questionList (writing_pic_qa) ql = block.get("questionList", []) for i, q in enumerate(ql): expl = q.get("explanation", "") if not expl or not expl.strip(): issues.append(f"{block_name} questionList[{i+1}] 解析为空") elif not has_chinese(expl): issues.append(f"{block_name} questionList[{i+1}] 解析纯英文") return issues def check_answers(block, block_name, qtype): """Check answer formats.""" issues = [] qs = block.get("questionSet", []) # Check per-question answer for i, q in enumerate(qs): ans = q.get("answer", None) if ans is not None and isinstance(ans, list): options = q.get("options", []) for a in ans: if isinstance(a, int) and options and a >= len(options): issues.append(f"{block_name}[{i+1}] answer索引{a}超出options范围(0-{len(options)-1})") # Check block-level answer block_ans = block.get("answer", []) if block_ans and isinstance(block_ans, list): for i, a in enumerate(block_ans): if i < len(qs): options = qs[i].get("options", []) if options and isinstance(a, int) and a >= len(options): issues.append(f"{block_name}[{i+1}] block.answer索引{a}超出options范围(0-{len(options)-1})") # Check answerSet ans_set = block.get("answerSet", []) opt_set = block.get("optionSetList", []) if ans_set and opt_set: for j, match in enumerate(ans_set): if isinstance(match, list) and len(match) >= 2: idx = match[1] if isinstance(idx, int) and idx >= len(opt_set): issues.append(f"{block_name} answerSet[{j}]索引{idx}超出optionSetList范围({len(opt_set)})") return issues # Main audit print("="*80) print("📊 单元挑战 — 全题型解析+答案审核报告") print("="*80) for table_id, table_name in ALL: records = fetch_all(TOKEN, table_id) empty_json = [] # records with no jsonData ok_records = [] # records that pass problem_records = [] # records with issues for rec in records: fields = rec.get("fields", {}) qsid = fields.get("题目集合 ID", fields.get("题目集合ID", "N/A")) json_str = fields.get("jsonData", "") if not json_str or not json_str.strip(): empty_json.append(qsid) continue try: jd = json.loads(json_str) except: empty_json.append(f"{qsid}(解析失败)") continue record_expl_issues = [] record_ans_issues = [] for bn in ["first", "second"]: blk = jd.get(bn, {}) if not blk: continue qtype = blk.get("type", "unknown") expl_issues = check_explanations(blk, bn, qsid, qtype) record_expl_issues.extend(expl_issues) ans_issues = check_answers(blk, bn, qtype) record_ans_issues.extend(ans_issues) if record_expl_issues or record_ans_issues: problem_records.append({ "qsid": qsid, "expl": record_expl_issues, "ans": record_ans_issues }) else: ok_records.append(qsid) # Print table summary total_valid = len(ok_records) + len(problem_records) expl_only = sum(1 for r in problem_records if r["expl"] and not r["ans"]) ans_only = sum(1 for r in problem_records if r["ans"] and not r["expl"]) both = sum(1 for r in problem_records if r["expl"] and r["ans"]) status = "✅ OK" if not problem_records and not empty_json else "" if problem_records: status = f"⚠️ {len(problem_records)}条有问题" if empty_json: status += f" + {len(empty_json)}条空模板" print(f"\n{'─'*60}") print(f"📋 {table_name} | {status}") print(f" 有效记录: {total_valid} | OK: {len(ok_records)} | 有问题: {len(problem_records)} | 空模板: {len(empty_json)}") for pr in problem_records: print(f"\n [{pr['qsid']}]") for e in pr["expl"]: print(f" 🟡 解析: {e}") for a in pr["ans"]: print(f" 🔴 答案: {a}") print(f"\n{'='*80}") print("审核完成") PYEOF