198 lines
7.0 KiB
Python
198 lines
7.0 KiB
Python
#!/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
|