ai_member_xiaoyan/scripts/update_speaking_p4.py

296 lines
12 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.

#!/usr/bin/env python3
"""
口语-P4-看图识物 调整统一5题/组 + 审校结果写入
"""
import json, subprocess
TOKEN = subprocess.run([
'curl', '-s', '-X', 'POST',
'https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal',
'-H', 'Content-Type: application/json',
'-d', json.dumps({'app_id':'cli_a931175d41799cc7','app_secret':'Iw2vEfbjT6GtV0GhbxbZqfQ4nAPtbR14'})
], capture_output=True, text=True)
TOKEN = json.loads(TOKEN.stdout)['tenant_access_token']
APP_TOKEN = "CMHSbUUjka3TrUsaxxEc297ongf"
TABLE_ID = "tblsD2dxaRpLmkXD"
def update_record(rid, fields):
body = json.dumps({"fields": fields}, ensure_ascii=False)
r = subprocess.run([
'curl', '-s', '-X', 'PUT',
f'https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{TABLE_ID}/records/{rid}',
'-H', f'Authorization: Bearer {TOKEN}',
'-H', 'Content-Type: application/json',
'-d', body
], capture_output=True, text=True)
return json.loads(r.stdout)
def build_explanation(q_text, img_desc, word, is_color):
if is_color:
return (
f"回答要点: {q_text}\n"
f"图片内容: {img_desc}\n"
f"考察能力: 图文匹配\n"
f"评估标准: 语音语调准确性、语言流利度、内容完整性与相关性、语法准确性\n"
f"回答指导: 鼓励学生用完整句子作答,如\"It's {word}.\",根据图片颜色准确说出对应的颜色单词"
)
else:
return (
f"回答要点: {q_text}\n"
f"图片内容: {img_desc}\n"
f"考察能力: 图文匹配\n"
f"评估标准: 语音语调准确性、语言流利度、内容完整性与相关性、语法准确性\n"
f"回答指导: 鼓励学生用完整句子作答,如\"It's a {word}.\",根据图片内容准确说出对应的英文单词"
)
def build_short_exp(img_desc, word, is_color):
obj = img_desc.replace('白色的背景,', '').rstrip('')
if is_color:
return f'{obj},对应的颜色单词是 {word}'
else:
return f'{obj},对应的英文单词是 {word}'
def build_human_config(qsid, asr_prompt, questions, text_title="Look and answer."):
lines = [f"做题要求:{text_title}", "", f"热词:", asr_prompt, "", "问题:"]
for i, q in enumerate(questions, 1):
lines.append(f"{i}. ")
lines.append(f"图片:{q['imageDesc']}")
lines.append(f"题目:{q['question']}")
lines.append(f"能力:{''.join(q['ability'])}")
lines.append(f"解析:{q['shortExplanation']}")
lines.append("")
return "\n".join(lines)
def build_qs(qsid, words, image_descs, question_types, img_start):
"""question_types: list of (is_color_bool, question_text_override_or_None)"""
qs = []
for i, word in enumerate(words):
is_color, q_override = question_types[i] if i < len(question_types) else (False, None)
if q_override:
q_text = q_override
is_color = False
elif is_color:
q_text = "What colour is it?"
else:
q_text = "What's this?"
q_img = f"{qsid}-{img_start + i:02d}.png"
img_desc = image_descs[i]
explanation = build_explanation(q_text, img_desc, word, is_color)
short_exp = build_short_exp(img_desc, word, is_color)
qs.append({
"question": q_text,
"questionImage": q_img,
"imageDesc": img_desc,
"ability": ["图文匹配"],
"explanation": explanation,
"shortExplanation": short_exp,
})
return qs
# ============================================================
# ID: 100001 — first=6→5 (drop purple), second=5 (no change)
# ============================================================
qsid = "100001"
first_words = ["blue", "red", "pink", "green", "orange"]
first_descs = [
"白色的背景,中间是一颗蓝色的星星。",
"白色的背景,中间是一个红色的气球。",
"白色的背景,中间是一朵粉红色的花。",
"白色的背景,中间是一片绿色的叶子。",
"白色的背景,中间是一个橙色的橘子。",
]
first_qtypes = [(True, None)] * 5
first_questions = build_qs(qsid, first_words, first_descs, first_qtypes, 0)
second_words = ["bag", "dress", "jacket", "hat", "T-shirt"]
second_descs = [
"白色的背景,中间是一个书包。",
"白色的背景,中间是一条连衣裙。",
"白色的背景,中间是一件夹克衫。",
"白色的背景,中间是一顶帽子。",
"白色的背景中间是一件T恤衫。",
]
second_qtypes = [(False, None)] * 5
second_questions = build_qs(qsid, second_words, second_descs, second_qtypes, 5)
first_qs_obj = {
"category": "speaking", "type": "speaking_pic_recognize",
"asrPrompt": ", ".join(first_words), "questionSetID": qsid,
"textTitle": "Look and answer.",
"questionSet": [{k: v for k, v in q.items() if k != "shortExplanation"} for q in first_questions],
}
second_qs_obj = {
"category": "speaking", "type": "speaking_pic_recognize",
"asrPrompt": ", ".join(second_words), "questionSetID": qsid,
"textTitle": "Look and answer.",
"questionSet": [{k: v for k, v in q.items() if k != "shortExplanation"} for q in second_questions],
}
json_data = json.dumps({"first": first_qs_obj, "second": second_qs_obj}, ensure_ascii=False)
config1 = build_human_config(qsid, ", ".join(first_words), first_questions)
config2 = build_human_config(qsid, ", ".join(second_words), second_questions)
fields = {
"jsonData": json_data,
"题目1 完整配置": config1,
"题目2 完整配置": config2,
"审校结果": "✅ OK | 2026-05-18 小研审校",
}
resp = update_record("recvjYhcXkYXIM", fields)
print(f"100001: code={resp.get('code')} | first={len(first_words)} second={len(second_words)}")
# ============================================================
# ID: 110101 — first=6→5 (drop monster), second=6→5 (drop duplicate colour)
# ============================================================
qsid = "110101"
first_words = ["hair", "eye", "nose", "foot", "hand"]
first_descs = [
"白色的背景,中间是一个人的头发。",
"白色的背景,中间是一只眼睛。",
"白色的背景,中间是一个鼻子。",
"白色的背景,中间是一只脚。",
"白色的背景,中间是一只手。",
]
first_qtypes = [(False, None)] * 5
first_questions = build_qs(qsid, first_words, first_descs, first_qtypes, 0)
second_words = ["black", "brown", "colour", "white", "yellow"]
second_descs = [
"白色的背景,中间是一只黑色的猫。",
"白色的背景,中间是一只棕色的小狗。",
"白色的背景,中间是一个彩色的调色盘。",
"白色的背景,中间是一只白色的兔子。",
"白色的背景,中间是一朵黄色的向日葵。",
]
# colour is not a color answer → use "What's this?"
second_qtypes = [
(True, None), # black
(True, None), # brown
(False, "What's this?"), # colour — 颜色概念
(True, None), # white
(True, None), # yellow
]
second_questions = build_qs(qsid, second_words, second_descs, second_qtypes, 5)
first_qs_obj = {
"category": "speaking", "type": "speaking_pic_recognize",
"asrPrompt": ", ".join(first_words), "questionSetID": qsid,
"textTitle": "Look and answer.",
"questionSet": [{k: v for k, v in q.items() if k != "shortExplanation"} for q in first_questions],
}
second_qs_obj = {
"category": "speaking", "type": "speaking_pic_recognize",
"asrPrompt": ", ".join(second_words), "questionSetID": qsid,
"textTitle": "Look and answer.",
"questionSet": [{k: v for k, v in q.items() if k != "shortExplanation"} for q in second_questions],
}
json_data = json.dumps({"first": first_qs_obj, "second": second_qs_obj}, ensure_ascii=False)
config1 = build_human_config(qsid, ", ".join(first_words), first_questions)
config2 = build_human_config(qsid, ", ".join(second_words), second_questions)
fields = {
"jsonData": json_data,
"题目1 完整配置": config1,
"题目2 完整配置": config2,
"审校结果": "✅ OK | 2026-05-18 小研审校",
}
resp = update_record("recvjYhdvUxDgs", fields)
print(f"110101: code={resp.get('code')} | first={len(first_words)} second={len(second_words)}")
# ============================================================
# ID: 110201 — first=6→5 (move ice cream to second), second=4→5
# ============================================================
qsid = "110201"
first_words = ["bread", "pie", "cake", "candy", "chocolate"]
first_descs = [
"白色的背景,中间是一片面包。",
"白色的背景,中间是一块馅饼。",
"白色的背景,中间是一块蛋糕。",
"白色的背景,中间是一颗糖果。",
"白色的背景,中间是一块巧克力。",
]
first_qtypes = [(False, None)] * 5
first_questions = build_qs(qsid, first_words, first_descs, first_qtypes, 0)
second_words = ["cat", "dog", "mice", "mouse", "ice cream"]
second_descs = [
"白色的背景,中间是一只猫。",
"白色的背景,中间是一只狗。",
"白色的背景,中间是两只老鼠。",
"白色的背景,中间是一只老鼠。",
"白色的背景,中间是一个冰淇淋。",
]
second_qtypes = [
(False, None), # cat
(False, None), # dog
(False, "What are these?"), # mice (plural)
(False, None), # mouse
(False, None), # ice cream
]
second_questions = build_qs(qsid, second_words, second_descs, second_qtypes, 5)
first_qs_obj = {
"category": "speaking", "type": "speaking_pic_recognize",
"asrPrompt": ", ".join(first_words), "questionSetID": qsid,
"textTitle": "Look and answer.",
"questionSet": [{k: v for k, v in q.items() if k != "shortExplanation"} for q in first_questions],
}
second_qs_obj = {
"category": "speaking", "type": "speaking_pic_recognize",
"asrPrompt": ", ".join(second_words), "questionSetID": qsid,
"textTitle": "Look and answer.",
"questionSet": [{k: v for k, v in q.items() if k != "shortExplanation"} for q in second_questions],
}
json_data = json.dumps({"first": first_qs_obj, "second": second_qs_obj}, ensure_ascii=False)
config1 = build_human_config(qsid, ", ".join(first_words), first_questions)
config2 = build_human_config(qsid, ", ".join(second_words), second_questions)
fields = {
"jsonData": json_data,
"题目1 完整配置": config1,
"题目2 完整配置": config2,
"审校结果": "✅ OK | 2026-05-18 小研审校",
}
resp = update_record("recvjYhe4opOGm", fields)
print(f"110201: code={resp.get('code')} | first={len(first_words)} second={len(second_words)}")
# ============================================================
# VERIFY ALL
# ============================================================
print("\n=== VERIFICATION ===")
for qsid, rid in [("100001","recvjYhcXkYXIM"),("110101","recvjYhdvUxDgs"),("110201","recvjYhe4opOGm")]:
r = subprocess.run([
'curl', '-s', '-X', 'GET',
f'https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{TABLE_ID}/records/{rid}',
'-H', f'Authorization: Bearer {TOKEN}'
], capture_output=True, text=True)
f = json.loads(r.stdout)['data']['record']['fields']
jd = json.loads(f['jsonData'])
f_count = len(jd['first']['questionSet'])
s_count = len(jd['second']['questionSet'])
review = f.get('审校结果', 'N/A')
issues = []
if f_count != 5: issues.append(f"first={f_count}≠5")
if s_count != 5: issues.append(f"second={s_count}≠5")
if not review: issues.append("missing review")
# Spot check: explanations are Chinese
for sec_name, sec in [("first", jd['first']), ("second", jd['second'])]:
for qi, q in enumerate(sec['questionSet']):
exp = q.get('explanation','')
if not any('\u4e00' <= c <= '\u9fff' for c in exp):
issues.append(f"{sec_name}Q{qi} explanation not Chinese")
status = '' if not issues else ''
print(f"{status} {qsid}: first={f_count} second={s_count} review={review}")
if issues:
for iss in issues:
print(f"{iss}")
print("\n✅ VERIFICATION COMPLETE")