ai_member_xiaoyan/scripts/batch_reading_pic_qa.py

601 lines
26 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
"""Batch produce reading_pic_qa questions and write to bitable."""
import json
import subprocess
import sys
import os
APP_TOKEN = "CMHSbUUjka3TrUsaxxEc297ongf"
TABLE_ID = "tblweY65jGBiwSdt" # 阅读-P7-看图回答题
CRED_FILE = "/root/.openclaw/credentials/xiaoyan/config.json"
TEXT_TITLE = "Look and read. Put a tick (Yes) or a cross (No) in the box."
# ============================================================
# All question data
# ============================================================
ALL_QUESTIONS = {
"000001": {
"first": {
"textImage": "000001-00.png",
"questions": [
{
"question": "A boy looks out of the window.",
"answerText": "Yes",
"explanation": "图中男孩正站在窗前往外看与句子描述一致。考察知识点look out of向外看"
},
{
"question": "The boy finds a red ball.",
"answerText": "Yes",
"explanation": "图中男孩从床底下找到一只红色的皮球与句子描述一致。考察知识点find找到"
},
{
"question": "A girl picks up a sock.",
"answerText": "No",
"explanation": "图中是男孩捡起一只袜子并非女孩与句子描述不符「女孩捡起袜子」。考察知识点pick up捡起"
},
{
"question": "The boy puts on a hat.",
"answerText": "No",
"explanation": "图中男孩正在穿鞋并非戴帽子。put on 的对象是 shoe 而非 hat与句子描述不符。考察知识点put on穿上/戴上)。"
},
{
"question": "The boy stops at the door.",
"answerText": "Yes",
"explanation": "图中男孩在门口停下脚步回头看与句子描述一致。考察知识点stop停下"
},
]
},
"second": {
"textImage": "000001-01.png",
"questions": [
{
"question": "A boy fastens his helmet.",
"answerText": "Yes",
"explanation": "图中男孩正在系紧头盔的带子与句子描述一致。考察知识点fasten系紧/扣紧)。"
},
{
"question": "A girl unfastens the rope.",
"answerText": "Yes",
"explanation": "图中女孩在攀爬架上正在解开安全绳与句子描述一致。考察知识点unfasten解开"
},
{
"question": "The playground is dangerous.",
"answerText": "No",
"explanation": "图中游乐场有安全绳、防护垫等安全设施是安全的游戏区域并非危险。考察知识点dangerous危险的"
},
{
"question": "A woman guides the boy.",
"answerText": "Yes",
"explanation": "图中一位女士正在指导男孩如何攀爬与句子描述一致。考察知识点guide指导/引导)。"
},
{
"question": "The pirate is on a ship.",
"answerText": "No",
"explanation": "图中穿海盗服装的男孩在游乐场攀爬架上玩耍并不在船上。考察知识点pirate海盗"
},
]
}
},
"010101": {
"first": {
"textImage": "010101-00.png",
"questions": [
{
"question": "The chicken is on the plate.",
"answerText": "Yes",
"explanation": "图中盘子里放着鸡肉与句子描述一致。考察知识点chicken鸡肉"
},
{
"question": "The girl reads a menu.",
"answerText": "Yes",
"explanation": "图中女孩正在看菜单与句子描述一致。考察知识点menu菜单"
},
{
"question": "The boy cooks the steak.",
"answerText": "No",
"explanation": "图中是厨师在烹饪牛排并非男孩在烹饪与句子描述不符。考察知识点cook烹饪"
},
{
"question": "The steak is on the table.",
"answerText": "Yes",
"explanation": "图中桌上放着一块牛排与句子描述一致。考察知识点steak牛排"
},
{
"question": "A chef has a red hat.",
"answerText": "No",
"explanation": "图中厨师帽是白色的并非红色与句子描述不符。考察知识点chef厨师"
},
]
},
"second": {
"textImage": "010101-01.png",
"questions": [
{
"question": "The onion is white.",
"answerText": "Yes",
"explanation": "图中摊位上放着白色的洋葱与句子描述一致。考察知识点onion洋葱"
},
{
"question": "The curry is yellow.",
"answerText": "Yes",
"explanation": "图中碗里的咖喱是黄色的与句子描述一致。考察知识点curry咖喱"
},
{
"question": "The mushroom is blue.",
"answerText": "No",
"explanation": "图中蘑菇是棕色/白色的并非蓝色。自然界没有蓝色的蘑菇与句子描述不符。考察知识点mushroom蘑菇"
},
{
"question": "The garlic is green.",
"answerText": "No",
"explanation": "图中大蒜是白色的并非绿色与句子描述不符。考察知识点garlic大蒜"
},
{
"question": "The chilli is red.",
"answerText": "Yes",
"explanation": "图中摊位上放着红色的辣椒与句子描述一致。考察知识点chilli辣椒"
},
]
}
},
"010201": {
"first": {
"textImage": "010201-00.png",
"questions": [
{
"question": "A robot is in the room.",
"answerText": "Yes",
"explanation": "图中房间中央站着一个机器人与句子描述一致。考察知识点robot机器人"
},
{
"question": "The robot has a big head.",
"answerText": "Yes",
"explanation": "图中机器人的头部很大与句子描述一致。考察知识点head"
},
{
"question": "The robot has four legs.",
"answerText": "No",
"explanation": "图中机器人只有两条腿并非四条与句子描述不符。考察知识点leg"
},
{
"question": "The robot has two arms.",
"answerText": "Yes",
"explanation": "图中机器人有两条手臂与句子描述一致。考察知识点arm手臂"
},
{
"question": "The robot's body is green.",
"answerText": "No",
"explanation": "图中机器人身体是银灰色的并非绿色与句子描述不符。考察知识点body身体"
},
]
},
"second": {
"textImage": "010201-01.png",
"questions": [
{
"question": "A story is on the wall.",
"answerText": "Yes",
"explanation": "图中布告栏上贴着一篇故事与句子描述一致。考察知识点story故事"
},
{
"question": "A poster is on the door.",
"answerText": "No",
"explanation": "图中海报贴在布告栏上并非贴在门上与句子描述不符。考察知识点poster海报"
},
{
"question": "A report is on the desk.",
"answerText": "Yes",
"explanation": "图中桌上放着一份报告与句子描述一致。考察知识点report报告"
},
{
"question": "The news is on TV.",
"answerText": "No",
"explanation": "图中场景是教室布告栏没有电视。新闻印在纸上贴在布告栏上并非在电视上播放。考察知识点news新闻"
},
{
"question": "A picture is on the board.",
"answerText": "Yes",
"explanation": "图中布告板上贴着一张图片与句子描述一致。考察知识点picture图片"
},
]
}
},
"010301": {
"first": {
"textImage": "010301-00.png",
"questions": [
{
"question": "The lion has a mane.",
"answerText": "Yes",
"explanation": "图中狮子有一圈鬃毛与句子描述一致。考察知识点lion狮子"
},
{
"question": "The animal is a dog.",
"answerText": "No",
"explanation": "图中动物是狮子并非狗与句子描述不符。考察知识点animal动物"
},
{
"question": "A bear is in the tree.",
"answerText": "Yes",
"explanation": "图中树洞里有一只熊与句子描述一致。考察知识点bear"
},
{
"question": "The butterfly is pink.",
"answerText": "No",
"explanation": "图中蝴蝶是黄色和黑色的并非粉色与句子描述不符。考察知识点butterfly蝴蝶"
},
{
"question": "The fish is in water.",
"answerText": "Yes",
"explanation": "图中鱼在水中游动与句子描述一致。考察知识点fish"
},
]
},
"second": {
"textImage": "010301-01.png",
"questions": [
{
"question": "The pool is blue.",
"answerText": "Yes",
"explanation": "图中游泳池的水是蓝色的与句子描述一致。考察知识点pool水池/游泳池)。"
},
{
"question": "A storm is in the sky.",
"answerText": "No",
"explanation": "图中天空晴朗有太阳并没有暴风雨与句子描述不符。考察知识点storm暴风雨"
},
{
"question": "The lake is big.",
"answerText": "Yes",
"explanation": "图中的湖泊很大与句子描述一致。考察知识点lake湖泊"
},
{
"question": "The grass is brown.",
"answerText": "No",
"explanation": "图中草地是绿色的并非棕色与句子描述不符。考察知识点grass草地"
},
{
"question": "A forest has many trees.",
"answerText": "Yes",
"explanation": "图中森林里有很多树与句子描述一致。考察知识点forest森林"
},
]
}
},
"010401": {
"first": {
"textImage": "010401-00.png",
"questions": [
{
"question": "A purple hat is on the chair.",
"answerText": "No",
"explanation": "图中紫色帽子在桌上并不在椅子上与句子描述不符。考察知识点purple紫色的"
},
{
"question": "The pink flower is big.",
"answerText": "Yes",
"explanation": "图中有朵粉色的花很大与句子描述一致。考察知识点pink粉色的"
},
{
"question": "The picture is colourful.",
"answerText": "Yes",
"explanation": "图中的画色彩丰富与句子描述一致。考察知识点colourful色彩丰富的"
},
{
"question": "The spoon is silver.",
"answerText": "No",
"explanation": "图中勺子为木色的并非银色与句子描述不符。考察知识点silver银色的"
},
{
"question": "A special cake is on the desk.",
"answerText": "Yes",
"explanation": "图中桌中央有一个装饰特别的生日蛋糕与其他物品不同。考察知识点special特别的"
},
]
},
"second": {
"textImage": "010401-01.png",
"questions": [
{
"question": "A sour lemon is on the table.",
"answerText": "Yes",
"explanation": "图中桌上放着一个柠檬柠檬是酸的与句子描述一致。考察知识点sour酸的"
},
{
"question": "The mango is yellow.",
"answerText": "Yes",
"explanation": "图中芒果是黄色的与句子描述一致。考察知识点mango芒果"
},
{
"question": "The grape is purple.",
"answerText": "Yes",
"explanation": "图中葡萄是紫色的与句子描述一致。考察知识点grape葡萄"
},
{
"question": "The lemon is sweet.",
"answerText": "No",
"explanation": "柠檬是酸的并非甜的与句子描述不符。考察知识点lemon柠檬"
},
{
"question": "The juice is orange.",
"answerText": "No",
"explanation": "图中果汁是紫色的葡萄汁并非橙色的橙汁与句子描述不符。考察知识点juice果汁"
},
]
}
},
"010501": {
"first": {
"textImage": "010501-00.png",
"questions": [
{
"question": "The company has an office.",
"answerText": "Yes",
"explanation": "图中公司设有办公室与句子描述一致。考察知识点company公司"
},
{
"question": "A card is on the desk.",
"answerText": "Yes",
"explanation": "图中桌面上放着一张卡片与句子描述一致。考察知识点card卡片"
},
{
"question": "The receptionist is a man.",
"answerText": "No",
"explanation": "图中前台接待员是女性并非男性与句子描述不符。考察知识点receptionist接待员"
},
{
"question": "The office is small.",
"answerText": "Yes",
"explanation": "图中的办公室不大/是小的与句子描述一致。考察知识点office办公室"
},
{
"question": "A meeting is in the park.",
"answerText": "No",
"explanation": "图中会议在办公室会议室举行不在公园里与句子描述不符。考察知识点meeting会议"
},
]
},
"second": {
"textImage": "010501-01.png",
"questions": [
{
"question": "The bathroom is clean.",
"answerText": "Yes",
"explanation": "图中卫生间干净整洁与句子描述一致。考察知识点bathroom卫生间"
},
{
"question": "A bookshelf is on the wall.",
"answerText": "Yes",
"explanation": "图中书柜靠墙摆放与句子描述一致。考察知识点bookshelf书架"
},
{
"question": "The toilet is in the kitchen.",
"answerText": "No",
"explanation": "图中马桶在卫生间里不在厨房里与句子描述不符。考察知识点toilet马桶"
},
{
"question": "The lamp is on the table.",
"answerText": "Yes",
"explanation": "图中台灯放在桌上与句子描述一致。考察知识点lamp台灯"
},
{
"question": "A laptop is on the bed.",
"answerText": "No",
"explanation": "图中笔记本电脑在书桌上不在床上与句子描述不符。考察知识点laptop笔记本电脑"
},
]
}
},
"010601": {
"first": {
"textImage": "010601-00.png",
"questions": [
{
"question": "A big stone is on the ground.",
"answerText": "Yes",
"explanation": "图中地上一块大石头与句子描述一致。考察知识点stone石头"
},
{
"question": "A dinosaur has big bones.",
"answerText": "Yes",
"explanation": "图中恐龙骨架有巨大的骨头与句子描述一致。考察知识点dinosaur恐龙"
},
{
"question": "The sky is gray.",
"answerText": "No",
"explanation": "图中天空是蓝色的并非灰色与句子描述不符。考察知识点gray灰色的"
},
{
"question": "The rock is very big.",
"answerText": "Yes",
"explanation": "图中的岩石非常大与句子描述一致。考察知识点rock岩石"
},
{
"question": "The flower is fresh.",
"answerText": "No",
"explanation": "图中场景是岩石挖掘区没有鲜花。花朵不存在于场景中与句子描述不符。考察知识点fresh新鲜的"
},
]
},
"second": {
"textImage": "010601-01.png",
"questions": [
{
"question": "A tool is on the desk.",
"answerText": "Yes",
"explanation": "图中桌上放着一件工具与句子描述一致。考察知识点tool工具"
},
{
"question": "A scientist has a white coat.",
"answerText": "Yes",
"explanation": "图中科学家穿着白色实验服与句子描述一致。考察知识点scientist科学家"
},
{
"question": "A brain is in the jar.",
"answerText": "Yes",
"explanation": "图中罐子里放着一个大脑模型与句子描述一致。考察知识点brain大脑"
},
{
"question": "The boy dives in the sea.",
"answerText": "No",
"explanation": "图中男孩在实验室里看潜水设备并没有在海里潜水与句子描述不符。考察知识点dive潜水"
},
{
"question": "The adventure is on TV.",
"answerText": "No",
"explanation": "图中实验室里没有电视探险装备是实物放在桌上不在电视上。考察知识点adventure探险/冒险)。"
},
]
}
},
}
def build_json(question_set_id, data):
"""Build the full JSON structure for one questionSetID."""
result = {
"first": {
"category": "reading",
"type": "reading_pic_qa",
"questionSetID": question_set_id,
"textTitle": TEXT_TITLE,
"textImage": data["first"]["textImage"],
"questionSet": [
{
"question": q["question"],
"answerText": q["answerText"],
"ability": ["图文判断", "句图一致性"],
"explanation": q["explanation"],
}
for q in data["first"]["questions"]
]
},
"second": {
"category": "reading",
"type": "reading_pic_qa",
"questionSetID": question_set_id,
"textTitle": TEXT_TITLE,
"textImage": data["second"]["textImage"],
"questionSet": [
{
"question": q["question"],
"answerText": q["answerText"],
"ability": ["图文判断", "句图一致性"],
"explanation": q["explanation"],
}
for q in data["second"]["questions"]
]
}
}
return result
def get_token():
"""Get tenant_access_token from credentials."""
with open(CRED_FILE) as f:
cred = json.load(f)
app_id = cred["apps"][0]["appId"]
app_secret = cred["apps"][0]["appSecret"]
import requests
resp = requests.post(
"https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal",
json={"app_id": app_id, "app_secret": app_secret},
)
return resp.json()["tenant_access_token"]
def create_record(token, question_set_id, json_data):
"""Create a record in the bitable."""
import requests
fields = {
"题目集合 ID": question_set_id,
"jsonData": json.dumps(json_data, ensure_ascii=False),
"dataStatus": "0",
}
resp = requests.post(
f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{TABLE_ID}/records",
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
},
json={"fields": fields},
)
return resp.json()
def verify_record(token, record_id):
"""Verify a record by reading it back."""
import requests
resp = requests.get(
f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{TABLE_ID}/records/{record_id}",
headers={"Authorization": f"Bearer {token}"},
)
return resp.json()
def main():
token = get_token()
results = []
for qid in sorted(ALL_QUESTIONS.keys()):
print(f"\n{'='*60}")
print(f"Processing ID: {qid}")
print(f"{'='*60}")
data = ALL_QUESTIONS[qid]
json_data = build_json(qid, data)
# Count Yes/No per 题组
for grp_name in ("first", "second"):
qs = json_data[grp_name]["questionSet"]
yes_count = sum(1 for q in qs if q["answerText"] == "Yes")
no_count = sum(1 for q in qs if q["answerText"] == "No")
print(f" {grp_name}: {yes_count}Y / {no_count}N")
# Create record
resp = create_record(token, qid, json_data)
if resp.get("code") != 0:
print(f" ❌ 创建失败: {resp.get('msg', resp)}")
results.append((qid, "FAIL", resp.get("msg", "")))
continue
record_id = resp["data"]["record"]["record_id"]
print(f" ✅ 创建成功, record_id: {record_id}")
# Verify
verify_resp = verify_record(token, record_id)
if verify_resp.get("code") != 0:
print(f" ⚠️ 回读失败: {verify_resp.get('msg', verify_resp)}")
results.append((qid, "CREATED_NO_VERIFY", record_id))
continue
# Check jsonData完整性
stored_fields = verify_resp["data"]["record"]["fields"]
stored_json = stored_fields.get("jsonData", "")
try:
parsed = json.loads(stored_json)
f_count = len(parsed.get("first", {}).get("questionSet", []))
s_count = len(parsed.get("second", {}).get("questionSet", []))
print(f" ✅ 回读验证: first={f_count}题, second={s_count}题, jsonData完整")
except Exception as e:
print(f" ⚠️ 回读JSON解析失败: {e}")
results.append((qid, "OK", record_id))
print(f"\n{'='*60}")
print(f"汇总")
print(f"{'='*60}")
for qid, status, detail in results:
print(f" {qid}: {status} ({detail})")
if __name__ == "__main__":
main()