601 lines
26 KiB
Python
601 lines
26 KiB
Python
#!/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()
|