#!/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()