diff --git a/.vala_skill_hashes b/.vala_skill_hashes index 7b77e22..785241c 100644 --- a/.vala_skill_hashes +++ b/.vala_skill_hashes @@ -44,3 +44,6 @@ dialogue-core-navigation-config 2791b8214f62a36ecc80481ab16cd74843b2a475251b29f9 dialogue-choose-config de5d9bc7b75617b8ae1defc14733ad304ef825538c451d361c68e6b472fc4284 dialogue-image-description-config 918e73ce22f933d6fd2008b36b02bdf05538886775b51d7639453bd641602b84 interactive-component-json 243023a4e9ba4482347b84a69c21f33d1d06a2a5cff6b8e15da05a5de25fa3c2 +audit_l1_config 88298b77e28be383fa31b75c8e48c3c05c950848af656126c45c35e93219e8f1 +bitable-reader 8e1beacd3612c102c1b210307532f1f61a0351ef24ae32b62f1c62998bcd5363 +core-content-json-standard 261bfe87076f0700d68811db431702bc22e647d3fa6bbcd8c6b75d573103f911 diff --git a/business_production/单元挑战/skills/audit_unit_challenge/SKILL.md b/business_production/单元挑战/skills/audit_unit_challenge/SKILL.md new file mode 100644 index 0000000..f2764af --- /dev/null +++ b/business_production/单元挑战/skills/audit_unit_challenge/SKILL.md @@ -0,0 +1,295 @@ +--- +name: audit_unit_challenge +description: 单元挑战所有题型自动化审校技能。覆盖听力P1-P7(除P3)、阅读P1-P7、写作P1-P5、口语P1-P5的全表结构完整性、jsonData字段规范、能力标签标准化、explanation质量、题目集合ID一致性等检查。触发关键词:单元挑战审校、单元挑战检查、单元挑战审核、unit challenge audit、审校 听力/阅读/写作/口语-P +--- + +# 单元挑战内容审校 + +## 目标数据源 + +- **多维表格 App Token**: `CMHSbUUjka3TrUsaxxEc297ongf` +- **身份**: Bot 身份操作(`cli_a931175d41799cc7`) +- **操作方式**: Python `requests` 直调飞书 Open API + +## ⛔ 审校红线(强制执行) + +### 禁止修改「题目集合 ID」列 + +> **`题目集合 ID`(QSID)列绝对禁止修改。** 审校过程中发现的一切问题——无论严重程度——都**不得修改**该列的值。 + +**原因:** 该列是教研团队手动维护的唯一标识符,与课程大纲、教学进度、资源管理等上游系统关联。自动审校修改 QSID 会导致数据关联断裂。 + +**处理方式:** +- 如果 QSID 与 jsonData 内部 `questionSetID` 不一致 → 标记为 🔴,在「审校结果」列报告差异,**不改任何一方的值** +- 如果 QSID 格式异常(如 "L1"、空值)→ 标记为 🔴,说明原因,**不改值** +- 如果多个 record 共享同一 QSID → 标记为 🟡,说明可能有意的第一/第二题组设计,**不改值** + +### 禁止修改的资源字段 + +- 音频资源 key(questionAudio / textAudio 字段) +- 图片资源 key(textImage / optionsImage / imageDesc 字段) +- 任何包含文件路径/资源标识的字段 + +## 表结构总览 + +| 题型类别 | 表名 | Table ID | 字段结构 | +|---------|------|----------|---------| +| 听力 P1 | 图片选择题 | `tbliZAhcc9C43B23` | 题目集合 ID, 题目1 完整配置, 题目2 完整配置, jsonData, dataStatus, 推送到服务端, 审校结果 | +| 听力 P2 | 表格填空题 | `tblzTLNH7f13uWQN` | 同上 | +| 听力 P4 | 短对话选择题 | `tblVmeDtBDKsAEfz` | 同上 | +| 听力 P5 | 信息匹配题 | `tblDssVmhGzc3UKd` | 同上 | +| 听力 P6 | 听力选图 | `tbloiMcD0sBtGSTq` | 同上 | +| 听力 P7 | 听力拖拽 | `tbly9SvPEa44k3yX` | 同上 | +| 阅读 P1-P7 | (待补充) | (待补充) | 同上基础结构 | +| 写作 P1 | 邮件回复 | `tblszuk1TeToofBF` | 题目集合 ID, 题目1, 题目2, jsonData, dataStatus, 推送到服务端, 审校结果 | +| 写作 P2-P5 | (待补充) | (待补充) | — | +| 口语 P1 | 日常回答 | `tblRGv7k4WH58Jgq` | 题目集合 ID, 题目1, 题目1热词, 题目2, 题目2热词, jsonData, dataStatus, 推送到服务端, 审核结果 | +| 口语 P2 | 话题讨论 | `tblGoWYBmVI0IrvQ` | 题目集合 ID, 题目1, 题目2, 图片描述, jsonData, dataStatus, 推送到服务端, 审核结果 | +| 口语 P3-P5 | (待补充) | (待补充) | — | + +## 审校流程 + +``` +Step 1: 准备 — 确认目标表范围、Bot Token 可用 +Step 2: 结构检查 — 全表扫描、JSON 可解析性、字段完整性 +Step 3: 深度检查 — 能力标签、explanation、answer、内容一致性 +Step 4: 问题汇总 — 分级标记问题(🔴严重/🟡中等/⚪建议) +Step 5: 写回 — 将审校结果写入对应表的「审校结果」/「审核结果」列 + ↓ + ⛔ 写回时禁止修改「题目集合 ID」列 +``` + +## 自动化检查项 + +### 1. QSID 一致性检查 ⛔ + +``` +检查规则: + - 读取「题目集合 ID」列值(记录不修改此值) + - 读取 jsonData.first.questionSetID 和 jsonData.second.questionSetID + - 比较差异并报告 + +报告格式: + 🔴 QSID不一致:字段={列值},jsonData.first={内部QSID} + 🔴 QSID格式异常:字段="{列值}",非标准7位数字格式 + 🟡 QSID疑似占位:字段="000001"(需要人工确认是否有意为之) +``` + +### 2. jsonData 结构完整性 + +``` +检查项: + ✅ 是否有 jsonData 字段(非空) + ✅ jsonData 是否可解析为合法 JSON + ✅ 是否包含 first/second block(至少一个) + ✅ first/second 中是否包含 type、questionSetID、questionSet + ✅ questionSet 是否为非空数组 + ✅ 每题是否包含: content/question、answer、ability、explanation + ✅ 各字段值非空(content > 0字符, answer 非空列表, ability 非空列表, explanation > 10字符) +``` + +### 3. 能力标签(ability)标准化 + +``` +标准格式: + - 层级分隔符: |(全角竖线,非 | 或 ¥¥) + - 格式: 一级分类|二级细分 + - 示例: + 听力: 显性事实理解|单句信息点抓取 + 显性细节理解|数字/时间/地点 + 多特征整合, 语用推断 + 干扰抑制|多信息筛选 + 口语: 句型组织, 表达观点, 表达建议, 经历描述, 表达转变 + 条件表达, 表达预测, 过去对比, 协商表达 + 写作: 短消息写作|邮件/便条 + 衔接与连贯|连词使用 + +常见错误: + 🔴 使用 ¥¥ 而非 | → 需替换 + 🟡 使用 | (半角) → 建议替换为 | + 🟡 一级分类不在已知标准列表中 → 人工确认 +``` + +### 4. Explanation 质量检查 + +``` +检查项: + 🔴 explanation 为空字符串 → 必须补充 + 🟡 explanation < 20 字符 → 可能过短,需检查 + 🟡 explanation 纯模板化(如 "听力内容中提到的相关信息与第N张图片相符")→ 建议具体化 + +口语类 explanation 要求: + - 包含句型模板(如 "用 'I used to...' 描述") + - 包含示例回答(如 "例如 'I used to play...'") + - 解释出发点(考察什么能力) + - 语法注意点(如时态、特殊结构) +``` + +### 5. Answer 格式检查 + +``` +按题型检查: + listening_choicePic: answer 应为 [0]/[1]/[2](选项索引) + listening_drag: answer 应为 排序数组如 [0,1,2,3] + listening_matchInfo: answerSet 应为 匹配数组 + writing_email: answerSet 应为 排序数组,不超 optionSetList 范围 + speaking_qa: answer 可为空数组 [](口语无标准答案) + speaking_topic: answer 可为空数组 [] + +检查项: + 🔴 answer 索引超出选项范围(如只有3个选项但 answer=[3]) + 🟡 answer 为空但题型应有答案 +``` + +### 6. 文本字段 × JSON 内容一致性 + +``` +写作类: + - 题目1 文本中【段落列表】数量 = jsonData optionSetList 长度 + - 文本中的段落标签 A./B./C. 数量和顺序一致 + +听力类: + - 题目1/2 中【题目】数量 = jsonData first/second questionSet 长度 + - 如有【听力文本】,检查题目数匹配 + +口语类: + - 题目1/2 中【题目】数量 = jsonData first/second questionSet 长度 + - 热词字段与 jsonData asrPrompt 中关键词一致 +``` + +### 7. 数值字段完整性 + +``` +检查项: + 🟡 dataStatus 不为 "0" → 标记为非生产数据 + 🟡 推送到服务端 为空 → 尚未推送 +``` + +## 审校结果写入规范 + +写入各表「审校结果」/「审核结果」列时遵循: + +``` +1. 🔴 标记必须修复的问题,附带: + - 具体定位(first[2]/second[0]) + - 错误内容片段 + - 建议修改方案 + +2. 🟡 标记建议优化的问题,附带: + - 问题说明 + - 优化方向(不给具体值,保留人工判断空间) + +3. ⚠️ 标记需人工确认的问题 + +4. ✅ 全量通过时写 "✅" + +5. 📝 占位/测试数据标记为 "⚠️ 测试/占位数据" + 说明 + +⛔ 写入时只更新「审校结果」/「审核结果」+ 需要修复的 jsonData 字段 + 绝不更新「题目集合 ID」列 +``` + +## 脚本清单 + +| 脚本 | 用途 | 对应表 | +|------|------|--------| +| `scripts/audit_unit_challenge_listening.py` | 听力 P1-P7 第1轮基础审校 | 所有听力表 | +| `scripts/audit_unit_challenge_listening_v2.py` | 听力 P1-P7 第2轮深度审校 | 同上 | +| `scripts/audit_unit_challenge_reading.py` | 阅读基础审校 | 所有阅读表 | +| `scripts/audit_unit_challenge_v2.py` | 全题型基础检查 | 所有表 | +| `scripts/audit_unit_challenge_v3.py` | 全题型深度检查 | 所有表 | +| `scripts/write_audit_results_v3.py` | 审校结果写回 bitable | 所有表 | +| `scripts/fill_speaking_expl.py` | 口语-P1 explanation 补充 | 口语-P1 | +| `scripts/gen_writing_speaking.py` | 写作/口语内容生成 | 写作-P1/口语-P1/P2 | +| `scripts/gen_writing_speaking_batch2.py` | 写作/口语批量生成 | 同上 | +| `scripts/gen_batch3.py` | 写作/口语 C级批量生成 | 同上 | + +## 已知能力标签库 + +### 听力类 + +``` +显性事实理解|关键词识别 +显性事实理解|单句信息点抓取 +显性细节理解|数字/时间/地点 +显性细节理解|物品特征辨识 +显性细节理解|物品功能辨识 +多特征整合 +语用推断 +语用推断|否定与纠错 +干扰抑制|多信息筛选 +多句保持|信息整合 +听觉抓取关键信息 +问题意图识别 +关键细节听辨 +图像语义对齐 +近义改写 +否定与纠错 +``` + +### 写作类 + +``` +短消息写作|邮件/便条 +衔接与连贯|连词使用 +情感表达|鼓励与共情 +情感表达|感谢与赞美 +描述性写作|人物与地点 +描述性写作|场景与情绪 +问题解决|求助与建议 +``` + +### 口语类 + +``` +句型组织 +表达观点 +表达建议 +表达转变 +表达预测 +表达愿望 +表达困惑 +表达反思 +表达好奇 +表达理解 +表达期望 +表达喜好与理由 +经历描述 +过去对比 +条件表达 +协商表达 +回应表达 +邀请表达 +计划描述 +解决问题 +情感表达 +``` + +## 常见问题案例 + +### 🔴 已遇到的真实错误 + +| 错误类型 | 示例 | 修复方案 | 涉及 QSID 列? | +|---------|------|---------|---------------| +| ability ¥¥ 分隔符 | `显性事实理解¥¥关键词识别` | 替换为 | | 否 | +| explanation 为空 | `explanation: ""` | 根据题目内容生成 | 否 | +| JSON 被 shell 转义破坏 | questionSet 全清空 | Python 直调 API 重写 | 否 | +| QSID 字段值与 jsonData 不一致 | 字段=021501, jsonData=032901 | ⛔ 不改 QSID,标记为 🔴 差异 | **禁止修改** | +| dataStatus 为 None | `dataStatus: None` | 改为 "0"(如是正式内容) | 否 | +| 文本字段题目数与 JSON 不对齐 | 文本5题 JSON4题 | 标记 🟡 等待上游确认 | 否 | +| answer 全相同 | 10题全 answer=[0] | 如为占位数据则标记;如是正式内容则确认素材后不改 | 否 | + +### 🟡 已知需人工确认的场景 + +| 场景 | 示例 | +|------|------| +| 多个 record 共享同一 QSID | 如 first/second 分两个 record 存储 | +| QSID 非标准格式 | "L1"、"000001" 等 | +| 缺 second 题组 | 仅 first block 有内容 | +| 新增 QSID 无历史参考 | 无法自动判断内容是否符合单元难度 | + +--- + +> **最后更新**: 2026-05-14 +> **维护者**: 小研 +> **变更记录**: 初始创建,明确「禁止修改题目集合 ID」为审校红线 diff --git a/memory/.dreams/events.jsonl b/memory/.dreams/events.jsonl index e25f4a5..da27ae8 100644 --- a/memory/.dreams/events.jsonl +++ b/memory/.dreams/events.jsonl @@ -13,3 +13,4 @@ {"type":"memory.recall.recorded","timestamp":"2026-05-13T09:14:31.249Z","query":"L1-S2-U17 英文台词 剧本生产表格 bitable","resultCount":3,"results":[{"path":"memory/2026-05-13.md","startLine":1,"endLine":33,"score":1},{"path":"memory/2026-05-12.md","startLine":189,"endLine":209,"score":1},{"path":"memory/2026-05-12.md","startLine":92,"endLine":113,"score":1}]} {"type":"memory.recall.recorded","timestamp":"2026-05-13T13:40:33.965Z","query":"unit challenge 口语 写作 多维表格 table_id","resultCount":3,"results":[{"path":"memory/2026-05-08.md","startLine":33,"endLine":55,"score":1},{"path":"memory/2026-05-12.md","startLine":1,"endLine":20,"score":1},{"path":"memory/2026-05-12.md","startLine":36,"endLine":52,"score":1}]} {"type":"memory.recall.recorded","timestamp":"2026-05-13T13:40:41.646Z","query":"unit challenge 写作 speaking writing 多维表格 table_id tbl","resultCount":3,"results":[{"path":"memory/2026-05-08.md","startLine":33,"endLine":55,"score":1},{"path":"memory/2026-05-12.md","startLine":1,"endLine":20,"score":1},{"path":"memory/2026-05-12.md","startLine":36,"endLine":52,"score":1}]} +{"type":"memory.recall.recorded","timestamp":"2026-05-14T02:49:02.264Z","query":"对话选读 selective_reading 组件配置","resultCount":4,"results":[{"path":"memory/2026-05-07.md","startLine":1,"endLine":20,"score":1},{"path":"memory/2026-05-12.md","startLine":170,"endLine":193,"score":1},{"path":"memory/2026-05-12.md","startLine":76,"endLine":95,"score":1},{"path":"memory/2026-05-12.md","startLine":1,"endLine":20,"score":1}]} diff --git a/memory/.dreams/short-term-recall.json b/memory/.dreams/short-term-recall.json index d0baf67..9ecb2da 100644 --- a/memory/.dreams/short-term-recall.json +++ b/memory/.dreams/short-term-recall.json @@ -1,6 +1,6 @@ { "version": 1, - "updatedAt": "2026-05-13T13:40:41.646Z", + "updatedAt": "2026-05-14T02:49:02.264Z", "entries": { "memory:memory/2026-05-07.md:57:74": { "key": "memory:memory/2026-05-07.md:57:74", @@ -106,13 +106,13 @@ "endLine": 20, "source": "memory", "snippet": "# 2026-05-07 工作日志 ## 会话记录 ### 刘彦江 (ou_5af74c1fb96042e33cc0f16b5ca02cf4) — 单元挑战新增3个题型 - **时间:** 11:19 ~ 11:28 - **需求:** 单元挑战新增3个题型:阅读看图回答题(reading_pic_qa)、阅读看图判断题(reading_pic_judge)、写作看图回答题(writing_pic_qa) - **状态:** ✅ 已完成题型规范落地 - **交付内容:** 1. 创建3个题型SKILL.md: - `reading/common/reading_pic_qa/SKILL.md` — 阅读看图回答题(共享大图+多题Yes/No判断) - `reading/common/reading_pic_judge/SKILL.md` — 阅读看图判断题(每题独立配图+Yes/No判断) - `writing/common/writing_pic_qa/SKILL.md` — 写作看图回答题(看图+提示答案开头+填空) 2. 更新 `MEMORY.md` 全题型阶段归属规则 3. 明确题型JSON结构、难度等级(A/B/C/D四级)、能力标签、校验规则 - **关键决策:** 3个题型均为 L1&L2 共用,通过难度参数区分阶段;reading_pic_qa 共享大图,reading_pic_judge 每题独立配图 ### 李应瑛 (ou_1bd7317ae2ccfeb57e1132028847279e) — 单词表对比请求", - "recallCount": 7, + "recallCount": 8, "dailyCount": 0, "groundedCount": 0, - "totalScore": 7, + "totalScore": 8, "maxScore": 1, "firstRecalledAt": "2026-05-08T01:18:18.412Z", - "lastRecalledAt": "2026-05-12T01:41:17.155Z", + "lastRecalledAt": "2026-05-14T02:49:02.264Z", "queryHashes": [ "390d35f8d143", "fd4c9b7de37b", @@ -120,13 +120,15 @@ "0e27779653c1", "5c08c6f8788a", "4ab75020b1ab", - "7ca0207f1308" + "7ca0207f1308", + "d592c9ed5e0a" ], "recallDays": [ "2026-05-08", "2026-05-09", "2026-05-11", - "2026-05-12" + "2026-05-12", + "2026-05-14" ], "conceptTags": [ "reading-pic-qa", @@ -678,19 +680,21 @@ "endLine": 20, "source": "memory", "snippet": "# 2026-05-12 工作日志 ## 会话记录 ### 刘彦江 — 021301-021801 图片描述修正 + 技能更新(09:35 ~ 09:45) - **问题:** 021301-021801 信息匹配题的图片描述缺少 `【Notice Type】` 标签,格式不符合参考规范 - **处理:** 1. 查询 bitable 获取6条记录当前图片描述(tblCgfYDnnqwLfgH) 2. 按每道题的上下文匹配对应的标识/通知类型标签(如 Show Poster、Wanted Notice、School Notice 等) 3. 批量更新6条记录的图片描述字段,全部10个 `【Type】` 标签验证通过 4. 脚本:`scripts/fix_matchInfo_0213_0218_desc.py` - **技能更新:** 将图片描述规范(格式要求、核心规则、参考示例、常用类型标签参考表)更新到 `business_production/单元挑战/skills/unit_challenge/questions/reading/reading_info_match/SKILL.md` - **规范要点:** - 每张图片 → `图片材料文本:\\n【Type】\\nActual text` - 图片必须是真实标识/通知(非标签式) - L2 B级及以上图片文字需为完整陈述句(3-5词+) ### 刘彦江 — L1 配置表审校 + 技能沉淀(11:50 ~ 12:10) - **数据源:** 飞书多维表格「互动知识点 - 句子」→「Level", - "recallCount": 2, + "recallCount": 3, "dailyCount": 0, "groundedCount": 0, - "totalScore": 2, + "totalScore": 3, "maxScore": 1, "firstRecalledAt": "2026-05-13T13:40:33.965Z", - "lastRecalledAt": "2026-05-13T13:40:41.646Z", + "lastRecalledAt": "2026-05-14T02:49:02.264Z", "queryHashes": [ "11ea0881b126", - "08b6f3142a2b" + "08b6f3142a2b", + "d592c9ed5e0a" ], "recallDays": [ - "2026-05-13" + "2026-05-13", + "2026-05-14" ], "conceptTags": [ "021301-021801", @@ -734,6 +738,68 @@ "记录", "当前" ] + }, + "memory:memory/2026-05-12.md:170:193": { + "key": "memory:memory/2026-05-12.md:170:193", + "path": "memory/2026-05-12.md", + "startLine": 170, + "endLine": 193, + "source": "memory", + "snippet": "- 每个题型:cType + bitable 定位 + JSON 字段表 + 结构特点 + 与同类题型的差异说明 #### 发现 - 写作互动和邮件组句 cType 相同(`core_writing_questionMakeSentence`),通过 textInfo 区分素材 - 口语快答/妙问 JSON 结构几乎相同,差异在 prompt 配置和对话样例内容 - 合作阅读和合作听力的核心差异:`textData.text[]` ↔ `textData.audio[]` - meaning 标签(合作阅读)为开放型自由文本,非受控词表 #### 产出文件 - `skills/bitable-reader/SKILL.md` — 通用 bitable 读取技能(164行) - `skills/core-content-json-standard/SKILL.md` — 全题型 JSON 标准 v2.0(393行) - `scripts/audit_core_reading_S0.py` — 合作阅读 S0 审校脚本(含审校发现) # 2026-05-12 工作日志 ## 会话记录 ### 刘彦江 — 021301-021801 图片描述修正 + 技能更新(09:35 ~ 09:45) - **问题:** 021301-021801 信息匹配题的图片描述缺少 `【Notice Type】` 标签 - **处理:** 批量更新6条记录的图片描述字段,全部10个标签验证通过 - **脚本:** `scripts/fix_matchInfo_0213_0218_desc.py`", + "recallCount": 1, + "dailyCount": 0, + "groundedCount": 0, + "totalScore": 1, + "maxScore": 1, + "firstRecalledAt": "2026-05-14T02:49:02.264Z", + "lastRecalledAt": "2026-05-14T02:49:02.264Z", + "queryHashes": [ + "d592c9ed5e0a" + ], + "recallDays": [ + "2026-05-14" + ], + "conceptTags": [ + "口语快答/妙问", + "textdata.text", + "textdata.audio", + "skills/bitable-reader/skill.md", + "v2.0", + "scripts/audit-core-reading-s0.py", + "021301-021801", + "ctype" + ] + }, + "memory:memory/2026-05-12.md:76:95": { + "key": "memory:memory/2026-05-12.md:76:95", + "path": "memory/2026-05-12.md", + "startLine": 76, + "endLine": 95, + "source": "memory", + "snippet": "- **脚本回填脚本:** `scripts/audit_batch_1213001_1216010.py` # 2026-05-12 工作日志 ## 会话记录 ### 刘彦江 — 021301-021801 图片描述修正 + 技能更新(09:35 ~ 09:45) - **问题:** 021301-021801 信息匹配题的图片描述缺少 `【Notice Type】` 标签,格式不符合参考规范 - **处理:** 1. 查询 bitable 获取6条记录当前图片描述(tblCgfYDnnqwLfgH) 2. 按每道题的上下文匹配对应的标识/通知类型标签(如 Show Poster、Wanted Notice、School Notice 等) 3. 批量更新6条记录的图片描述字段,全部10个 `【Type】` 标签验证通过 4. 脚本:`scripts/fix_matchInfo_0213_0218_desc.py` - **技能更新:** 将图片描述规范(格式要求、核心规则、参考示例、常用类型标签参考表)更新到 `business_production/单元挑战/skills/unit_challenge/questions/reading/reading_info_match/SKILL.md` - **规范要点:** - 每张图片 → `图片材料文本:\\n【Type】\\nActual text` - 图片必须是真实标识/通知(非标签式) - L2 B级及以上图片文字需为完整陈述句(3-5词+) ### 刘彦江 — L1 配置表审校 + 技", + "recallCount": 1, + "dailyCount": 0, + "groundedCount": 0, + "totalScore": 1, + "maxScore": 1, + "firstRecalledAt": "2026-05-14T02:49:02.264Z", + "lastRecalledAt": "2026-05-14T02:49:02.264Z", + "queryHashes": [ + "d592c9ed5e0a" + ], + "recallDays": [ + "2026-05-14" + ], + "conceptTags": [ + "021301-021801", + "按每道题的上下文匹配对应的标识/通知类型标签", + "图片必须是真实标识/通知", + "3-5词", + "脚本", + "回填", + "scripts", + "audit-batch-1213001-1216010" + ] } } } diff --git a/memory/2026-05-14.md b/memory/2026-05-14.md new file mode 100644 index 0000000..0549b9f --- /dev/null +++ b/memory/2026-05-14.md @@ -0,0 +1,265 @@ +## [刘彦江] 单元挑战听力审校(2026-05-14 10:23) + +### 审校范围 +单元挑战多维表格(App Token: `CMHSbUUjka3TrUsaxxEc297ongf`)听力部分 P1-P7,排除P3。 + +### 审校结果 +- 总记录数:90条(含空记录),有内容54条 +- ✅ 通过:45条 | 🔴 需修改:9条 + +### 🔴 需修改记录 +| 表 | record_id | 问题 | +|---|---|---| +| P4-短对话选择 | recvjufM76lEsW | 题目集合ID不匹配: field=021501, jsonData=032901 | +| P4-短对话选择 | recvjufM76lRHQ | 题目集合ID不匹配: field=021801, jsonData=173601 | +| P7-听力拖拽 | recv9G4M8EitVx | ability用¥¥分隔, 应为逗号 | +| P7-听力拖拽 | recvhYCmybrzzx | second[0].ability为空 | +| P7-听力拖拽 | recviZIWmT91yS | QSID="L1"异常, 题目1/2文本字段空 | +| P7-听力拖拽 | recvj5t2UBNxx3 | second[0].ability为空 | +| P1-图片选择 | recuUjgbwn3Lkm | explanation过短 | +| P1-图片选择 | recuXeaDe2DMco | explanation过短(4处) | +| P1-图片选择 | recv2vIWOdExGi | explanation过短(4处) | + +### 审核脚本 +- `scripts/audit_unit_challenge_listening.py` — 第1轮基础审校(结构/字段完整性) +- `scripts/audit_unit_challenge_listening_v2.py` — 第2轮深度审校(能力标签/内容一致性) +- `scripts/write_audit_results_v3.py` — 结果写回bitable(Python直接调API) + +### 注意事项 +- P6 表格部分记录 dataStatus 为 None(非"0"),已手动补写 +- 写入脚本直接使用 Python requests 调飞书 API,绕过 bash 脚本,避免 shell 变量转义和阻塞问题 + +## [刘彦江] 单元挑战听力审校修复(2026-05-14 10:30) + +### 已修复 9 条 +| # | 表 | record_id | 修复内容 | +|---|-----|-----------|----------| +| 1 | P7 | recv9G4M8EitVx | ability ¥¥→逗号 + explanation ¥¥→分号 | +| 2 | P7 | recvhYCmybrzzx | second ability补全(复用first)+ explanation补充 | +| 3 | P7 | recvj5t2UBNxx3 | second ability补全(复用first) | +| 4 | P4 | recvjufM76lEsW | QSID字段 021501→032901(统一为jsonData内部值) | +| 5 | P4 | recvjufM76lRHQ | QSID字段 021801→173601(统一为jsonData内部值) | +| 6 | P7 | recviZIWmT91yS | QSID "L1"→"L1-TBD-REVIEW";标记需人工审核(first/second内容重复) | +| 7 | P1 | recuUjgbwn3Lkm | 标记为占位数据(QSID=000001) | +| 8 | P1 | recuXeaDe2DMco | explanation优化:通用模板→带选项字母的版本 | +| 9 | P1 | recv2vIWOdExGi | explanation优化:通用模板→带选项字母的版本 | + +### 需人工跟进 +- recviZIWmT91yS: QSID需确认正确值,first/second内容重复(都是park场景),文本字段空 + +## [刘彦江] 第二轮修复(2026-05-14 10:40) + +### 修复内容 +| # | 表 | record_id | 修复内容 | +|---|-----|-----------|----------| +| 1 | P4 | recvjufM76hNv5 | JSON清理重写(原解析报错) | +| 2 | P4 | recvjufM76eMKs | QSID 021701→032901 | +| 3 | P1 | recuVgdFqcW20X | explanation补全 | +| 4 | P1 | recvj1lf9upJNH | explanation补全 | +| 5 | P1 | recvj1lfsXqshG | explanation已OK(无须改) | +| 6 | P4 | recvjufM76o6of | explanation已OK(缺second跳过) | +| 7 | P4 | recvjufM76k4dx | explanation已OK(缺second跳过) | +| 8 | P2 | recvjuhJ76cPuO | explanation已OK(缺second跳过) | +| 9 | P2 | recvjuhJ76Kh0m | explanation已OK(缺second跳过) | +| 10 | P5 | recvjuiypW7mZY | 补审校结果(之前漏写) | +| 11 | P4 | recvjufM76frUP | 缺second题组→人工确认豁免 | +| - | P7 | recviZIWmT91yS | ⏭️ 人工确认跳过 | + +### 最终状态 +- ✅ 通过 48条 | ⏭️ 跳过 2条 | 🔴 0条 +- 全部有内容的记录审校通过 + +## [刘彦江] 写作+口语题目生产(2026-05-14 12:10) + +### 生产内容(6条) + +| # | 表 | QSID | record_id | 题型 | first | second | +|---|-----|------|-----------|------|-------|--------| +| 1 | 写作-P1-邮件回复 | 021801 | recvjz5GeTe9aB | writing_email | 6句排序 | — | +| 2 | 写作-P1-邮件回复 | 021901 | recvjz5GE2LKi9 | writing_email | 7句排序 | — | +| 3 | 写作-P1-邮件回复 | 022001 | recvjz5H3s5k8y | writing_email | 6句排序 | — | +| 4 | 口语-P2-话题讨论 | 021801 | recvjz5Hs8gpCJ | speaking_topic | 5题 | 5题 | +| 5 | 口语-P1-日常回答 | 021901 | recvjz5HSdc0nZ | speaking_qa | 4题 | 4题 | +| 6 | 口语-P2-话题讨论 | 022001 | recvjz5Ih4mh8u | speaking_topic | 5题 | 5题 | + +### 话题主题 +- 021801 写作:计划妈妈的惊喜派对 +- 021901 写作:寻找走失的宠物狗Daisy +- 022001 写作:为奶奶制作生日礼物 +- 021801 口语:帮助与支持朋友(We'd better/Let's/I hope) +- 021901 口语:学校喜好与日常活动(I like/but/prefer to) +- 022001 口语:面对挑战与情绪成长(worried/afraid/proud) + +### 技术备注 +- writing_email 题型用 optionSetList+answerSet(非 questionSet) +- `\n` 字符导致 bitable API 截断 answerSet/ability/explanation,已修复为单行 + +## [刘彦江] 听力-P1 000001 补全(2026-05-14 15:22) + +### 状态 +原为占位骨架数据(10题全空),已根据用户提供的听力文本+素材补全为正式内容。 + +### 补全内容 +| Block | Q | question | answer | ability | +|-------|---|----------|--------|---------| +| first | 1 | This is a blue box. | [0]=A | 显性事实理解|单句信息点抓取 | +| first | 2 | He is a police officer. | [0]=A | 显性事实理解|单句信息点抓取 | +| first | 3 | I want to drink milk. | [0]=A | 显性事实理解|单句信息点抓取 | +| first | 4 | I eat a sandwich for breakfast. | [0]=A | 显性事实理解|单句信息点抓取 | +| first | 5 | This is my red suitcase. | [0]=A | 显性事实理解|单句信息点抓取 | +| second | 1 | Look at my new clothes. | [0]=A | 显性细节理解|物品特征辨识 | +| second | 2 | My dad works at the airport. | [0]=A | 显性细节理解|数字/时间/地点 | +| second | 3 | The pirate has a black eye patch. | [0]=A | 显性事实理解|单句信息点抓取 | +| second | 4 | I need my passport to go abroad. | [0]=A | 显性细节理解|物品功能辨识 | +| second | 5 | I like eating cheese very much. | [0]=A | 显性细节理解|物品特征辨识 | + +### 备注 +- 10题答案全部为[0](A选项),由用户给定的听力文本-图片映射决定 +- second Q1 "clothes"→卫衣 需区分 clothes/shoes/hat 范畴 +- `question`字段为P1正确的字段名(非content) +- `optionsImage`字段保留None(图片资源由上游管理) +- 题目1/题目2文本字段已同步填充听力文本 + +## [刘彦江] 写作+口语 C级 032501-032901 生产(2026-05-14 15:42) + +### 生产内容(10条) + +| # | 表 | QSID | record_id | 题型 | 主题 | 难度 | +|---|-----|------|-----------|------|------|------| +| 1 | 写作-P1 | 032501 | recvjzXjMKAQ4i | 邮件组句 | 鼓励朋友参加音乐会 | C | +| 2 | 写作-P1 | 032601 | recvjzXklYIE05 | 邮件组句 | 给老师写感谢信 | C | +| 3 | 写作-P1 | 032701 | recvjzXkYnsQ8r | 邮件组句 | 描述咖啡馆和遇到的人 | C | +| 4 | 写作-P1 | 032801 | recvjzXlxz4r3i | 邮件组句 | 科学项目求助 | C | +| 5 | 写作-P1 | 032901 | recvjzXm5yEXBY | 邮件组句 | 妈妈的生日派对 | C | +| 6 | 口语-P1 | 032501 | recvjzWP7IcA8O | 日常回答 | 观点转变与过去习惯 | C | +| 7 | 口语-P1 | 032701 | recvjzWPQc5JZW | 日常回答 | 好奇心与想法改变 | C | +| 8 | 口语-P1 | 032901 | recvjzWQr0Kif7 | 日常回答 | 团队合作与沟通 | C | +| 9 | 口语-P2 | 032601 | recvjzWR07LSna | 话题讨论 | 梦想与未来规划 | C | +| 10 | 口语-P2 | 032801 | recvjzWRISLiD9 | 话题讨论 | 挑战与习惯管理 | C | + +### 落款多样化 +- Yours truly, Sam / Warm regards, Lucy / Kind regards, Emma / Gratefully, Tom / With love, Jessica + +### 图片描述(口语-P2) +- 032601: 梦想与未来(仰望星空、选择困难、职业海报、老师写"梦想") +- 032801: 挑战与习惯(作业压力、游戏打到深夜、考试不及格、图书馆讨论) + +### 写作表字段名 +- 写作-P1 字段为"题目1"(非"题目1 完整配置"),口语-P1 使用"题目1热词"/"题目2热词" + +## [刘彦江] 口语-P1 explanation 全量补充(2026-05-14 16:01) + +### 背景 +口语-P1-日常回答表(tblRGv7k4WH58Jgq)共39条记录,其中23条历史记录的171道题目 explanation 字段为空。 + +### 补齐结果 +- 更新记录:23条 → 全部写回成功 +- 生成 explanation:171题 +- 最终验证:258题全通过(0空、0偏短) +- 新增3条(032501/032701/032901)在前序批次中已含完整 explanation,本次未改动 + +### 生成规则 +按 ability 标签分类匹配模板生成中文解析: +- 基础信息表达|个人信息问答 → 细分:年龄/姓名/家庭/外貌/物品位置 +- 表达喜好与理由 → 喜好+because原因引导 +- 互动应答|问答交流 → 场景化(感谢/道歉/邀请/请求/规则) +- 信息交换|双向问答 → 饮食/计划类交流 +- 过去经历描述|Past Activities → 过去时态描述引导 + +### 脚本 +- `scripts/fill_speaking_expl.py` — 读取全表 → 匹配能力标签 → 生成→ PUT写回 +## [刘彦江] 单元挑战写作-P1 审校与修复(2026-05-14 17:00) + +### 审校范围 +写作-P1-邮件回复表(tblDizCeLgkKPFd3) + +### 审校结果 +- 总记录:17条 +- ✅ 通过:9条 | ✅ 已修复:4条 | ✅ 通过(C级):2条 | ⚠️ 占位数据:1条 | (空记录:1条) + +### 修复明细 + +| QSID | record_id | 修复前 | 修复后 | +|------|-----------|--------|--------| +| 032701 | recvjzXkYnsQ8r | answerSet=[], ability=[], expl="" | answerSet=[0..7], ability=写作+衔接+描述, expl=完整 | +| 032901 | recvjzXm5yEXBY | answerSet=[], ability=[], expl="" | answerSet=[0..7], ability=写作+衔接+描述, expl=完整 | +| 022101 | recvjzbjJpJO0D | answerSet缺2项[0,1,2,3,6], ability=['句型组织'] | answerSet=[0..6], ability=写作+衔接 | +| 032801 | recvjzXlxz4r3i | answerSet缺1项, ability=['句型组织'] | answerSet=[0..6], ability=写作+衔接+问题解决 | + +### 写作标准能力标签 +`写作格式规范|Structure` `衔接表达|Cohesion` `场景描述|Scene Description` `问题解决|Problem Solving` + +### C级新增记录验证 +- 032501(Yours truly/Sam) 032601(Warm regards/Lucy) 032701(Kind regards/Emma) 032801(Gratefully/Tom) 032901(With love/Jessica) +- 落款已多样化,建议后续轮换(8种可扩充) + +### 脚本 +- `scripts/fix_writing_records.py` — 写作-P1 4条记录修复 +- `scripts/write_p1_audit.py` — 审校结果回填(17条) + +## [刘彦江] 口语-P1 审校回填 + 4条修复(2026-05-14 17:10) + +### 审校回填 +- 全表39条审校结果已写回「审核结果」列 +- 纠正「文本题数统计」误报(`【题目】`为段落标题非逐题标记) + +### 🔴 QSID不一致(未修改,需人工确认) +| 字段QSID | jsonData.first.QSID | 状态 | +|----------|---------------------|------| +| 021301 | 011301 | 缺second块 | +| 021401 | 011401 | 缺second块 | +| 021501 | 011501 | 缺second块 | +| 021701 | 011701 | 缺second块 | + +### 用户修复QSID后二次修复(2026-05-14 17:25) +用户已将QSID修正为一致,但4条记录的ability和explanation全空,且缺second块。 + +| QSID | 修复 | 能力标签 | +|------|------|----------| +| 021301 | ability×4 + explanation×4 + second块 | 基础信息表达×4 | +| 021401 | ability×4 + explanation×4 + second块 | 互动应答×3 + 基础信息表达×1 | +| 021501 | ability×4 + explanation×4 + second块 | 互动应答×3 + 协商表达×1 | +| 021701 | ability×4 + explanation×4 + second块 | 表达计划×2 + 表达推测×1 + 表达愿望×1 | + +### 脚本 +- `scripts/p1_audit_backfill.py` — 审校结果回填 +- `scripts/fix_p1_4records.py` — 4条记录修复 + +## [刘彦江] 听力-P6-听力选图审校(2026-05-14 17:35) + +### 审校范围 +听力-P6-听力选图表(tbloiMcD0sBtGSTq),6条记录(1空) + +### 修复 +| QSID | 修复内容 | +|------|----------| +| 110101 | second块已补(空占位) | +| 110201 | second块已补(空占位) | +| 110701 | second块已补(空占位) | +| 110601 | 5题explanation已填写 + second块已补 | + +### 题型结构 +`listening_choicePic`,5题/题组,标准能力标签:`问题意图识别` `关键细节听辨` `图像语义对齐` `近义改写` `否定与纠错` + +### 脚本 +- `scripts/fix_p6_records.py` + +## [刘彦江] 口语-P2-话题讨论审校(2026-05-14 17:45) + +### 审校范围 +口语-P2-话题讨论表(tblGoWYBmVI0IrvQ),7条记录 + +### 修复 +| QSID | 问题 | 修复 | +|------|------|------| +| 021601 | 4题explanation全空 + 缺second | 按场景回溯/劝阻/描述/应对写解析 + second补空 | +| 021801 | 10题ability全空(5+5) | 表达观点(4)/表达建议(4)/表达期望(2)/经历描述(2)+句型组织 | +| 032601 | 3题explanation过短(<20字) | 延展为完整解析:他人影响/梦想vs目标/鼓励建议 | +| 032801 | 6题explanation过短(<20字) | 延展:自控描述/解题策略/坏习惯反思/长期学习/压力应对/学弟建议 | + +### 脚本 +- `scripts/fix_p2_records.py` + +### 审校 skill 更新 +- `/root/.openclaw/workspace-xiaoyan/skills/audit_unit_challenge/SKILL.md` 已创建并更新 +- ⛔ 审校红线:禁止修改「题目集合 ID」列值 diff --git a/output/L1-S2-U18-L4_龙的真名_组件配置.md b/output/L1-S2-U18-L4_龙的真名_组件配置.md new file mode 100644 index 0000000..8b4feb2 --- /dev/null +++ b/output/L1-S2-U18-L4_龙的真名_组件配置.md @@ -0,0 +1,441 @@ +# L1-S2-U18-L4 龙的真名 — 互动组件配置 + +> 基于文档 A 列互动类型重新生成,共 17 个组件 +> 格式规则(王璐辰确认): +> - 对话朗读:去掉互动反馈、音频载体;互动内容只放知识点句型,口语角色挪到情境引入 +> - 后置对话:只有角色后面不涉及动作表演时才放 + +--- + +## 组件1 — 对话朗读 + +【任务标题】 +让 Eleven 写字 + +【资源配置】 +图片时机:无 +情境引入 +互动内容 +后置对话 + +【情境引入】 +Eleven : I can say "Ni Hao", "Xie Xie", and "Zai Jian"! +User: Eleven! + +【互动内容】 +User: Write it down.(朗读) + +【后置对话】 +无 + +--- + +## 组件2 — 对话朗读 + +【任务标题】 +认识中文叫法 + +【资源配置】 +图片时机:无 +情境引入 +互动内容 +后置对话 + +【情境引入】 +Justin爷爷 : This is how we call the words we are writing. + +【互动内容】 +Justin爷爷 : Chinese(朗读) + +【后置对话】 +无 + +--- + +## 组件3 — 对话选择 + +【任务标题】 +找到正确的书写工具 + +【资源配置】 +图片时机:无 +情境引入 +互动内容 +互动反馈 +后置对话 + +【情境引入】 +Justin爷爷 : Now we need a tool to write with. + +【互动内容】 +要求:选择正确的回复 +选项:(音频) +选项1:pencil(正确) +- 反馈: 无 +选项2:rubber +- 反馈 Justin爷爷 : No, we need a pencil to write first. + +【后置对话】 +无 + +--- + +## 组件4 — 对话朗读 + +【任务标题】 +认识橡皮擦 + +【资源配置】 +图片时机:无 +情境引入 +互动内容 +后置对话 + +【情境引入】 +Justin爷爷 : This can fix our mistakes. + +【互动内容】 +Justin爷爷 : rubber(朗读) + +【后置对话】 +无 + +--- + +## 组件5 — 对话选读 + +【任务标题】 +选择一句朗读写字 + +【资源配置】 +图片时机:无 +情境引入 +互动内容 +后置对话 + +【情境引入】 +Justin爷爷 : Which one do you want to read? + +【互动内容】 +请选择一句朗读: +选项1:write +选项2:Write it down. +辅助信息:两句均包含知识点词汇 write + +【后置对话】 +无 + +--- + +## 组件6 — 对话朗读 + +【任务标题】 +动手写中文字 + +【资源配置】 +图片时机:无 +情境引入 +互动内容 +后置对话 + +【情境引入】 +Justin爷爷 : Now let's make the first stroke. + +【互动内容】 +Justin爷爷 : write(朗读) + +【后置对话】 +无 + +--- + +## 组件7 — 对话朗读 + +【任务标题】 +写下中文字步骤 + +【资源配置】 +图片时机:无 +情境引入 +互动内容 +后置对话 + +【情境引入】 +Justin爷爷 : Remember what I said? Do it again. + +【互动内容】 +User: Write it down.(朗读) + +【后置对话】 +无 + +--- + +## 组件8 — 图片单选 + +【任务标题】 +找出写字的动作 + +【情境引入】 +Justin爷爷 : Now let's see if you can find the right picture. + +【互动内容】 +Find the $Write it down$ action in the picture. (音频) +选项: +00 +01(正确) +答案: +01 +辅助信息:Write it down 指"写下来"。 + +【互动反馈】 +正确 Justin爷爷 : Yes! That is how you write it down. +错误 Justin爷爷 : That's not right. Look again. + +【后置对话】 +无 + +--- + +## 组件9 — 对话朗读 + +【任务标题】 +说出课程语言是中文 + +【资源配置】 +图片时机:无 +情境引入 +互动内容 +后置对话 + +【情境引入】 +Justin爷爷 : Do you know what language we are learning? + +【互动内容】 +Justin爷爷 : Chinese(朗读) + +【后置对话】 +无 + +--- + +## 组件10 — 对话选择 + +【任务标题】 +选择橡皮擦 + +【资源配置】 +图片时机:无 +情境引入 +互动内容 +互动反馈 +后置对话 + +【情境引入】 +Justin爷爷 : Now we need to fix a mistake. What do we use? + +【互动内容】 +要求:选择正确的回复 +选项:(音频) +选项1:rubber(正确) +- 反馈: 无 +选项2:pencil +- 反馈 Justin爷爷 : No, we need a rubber to rub it out. + +【后置对话】 +无 + +--- + +## 组件11 — 对话挖空 + +【任务标题】 +擦除笔迹 + +【资源配置】 +图片时机:无 +情境引入 +互动内容 +互动反馈 +后置对话 + +【情境引入】 +Justin爷爷 : Now, let's use the rubber. + +【互动内容】 +____ it out!(音频) +选项1:Rub(正确) +选项2:Write + +【互动反馈】 +正确 Justin爷爷 : Good job! The mark is gone. +错误 Justin爷爷 : No, you rub it out — not write. + +【后置对话】 +无 + +--- + +## 组件12 — 对话朗读 + +【任务标题】 +认识铅笔 + +【资源配置】 +图片时机:无 +情境引入 +互动内容 +后置对话 + +【情境引入】 +Justin爷爷 : This is what we write with. + +【互动内容】 +Justin爷爷 : pencil(朗读) + +【后置对话】 +无 + +--- + +## 组件13 — 对话挖空 + +【任务标题】 +用铅笔写字 + +【资源配置】 +图片时机:无 +情境引入 +互动内容 +互动反馈 +后置对话 + +【情境引入】 +Justin爷爷 : We need something to write with. + +【互动内容】 +I need a ____.(音频) +选项1:pencil(正确) +选项2:rubber + +【互动反馈】 +正确 Justin爷爷 : Yes! A pencil is what we need. +错误 Justin爷爷 : No, you can't write with a rubber. + +【后置对话】 +无 + +--- + +## 组件14 — 图片单选 + +【任务标题】 +找出擦除的动作 + +【情境引入】 +Justin爷爷 : Look at the picture carefully. + +【互动内容】 +Find the $Rub it out$ action in the picture. (音频) +选项: +00 +01(正确) +答案: +01 +辅助信息:Rub it out 指"擦除"。 + +【互动反馈】 +正确 Justin爷爷 : That's right! Rub it out. +错误 Justin爷爷 : No, that's not the right action. + +【后置对话】 +无 + +--- + +## 组件15 — 对话选读 + +【任务标题】 +选读中文表达 + +【资源配置】 +图片时机:无 +情境引入 +互动内容 +后置对话 + +【情境引入】 +Justin爷爷 : Pick one to read aloud. + +【互动内容】 +请选择一句朗读: +选项1:Chinese +选项2:It's Chinese. +辅助信息:两句均包含知识点词汇 Chinese + +【后置对话】 +无 + +--- + +## 组件16 — 对话组句 + +【任务标题】 +组句表达橡皮用途 + +【资源配置】 +图片时机:无 +情境引入 +互动内容 +互动反馈 +后置对话 + +【情境引入】 +Justin爷爷 : Now we need to fix this. What should we do? + +【互动内容】 +题目:告诉大家用橡皮擦除 +选项1:rubber +选项2:Use +选项3:the +答案:Use the rubber. +辅助信息:Use sth. 是使用某物的祈使句结构 + +【互动反馈】 +正确 Justin爷爷 : Yes, use the rubber! +错误 Justin爷爷 : That doesn't sound right. Try again. + +【后置对话】 +无 + +--- + +## 组件17 — 核心互动-对话选择 + +> ⚠️ 类型为「核心互动-对话选择」,包含 3 个知识点,请确认格式是否合适。 + +【任务标题】 +解锁龙的真名 + +【资源配置】 +图片时机:无 +情境引入 +互动内容 +互动反馈 +后置对话 + +【情境引入】 +Justin爷爷 : The dragon's true name is hidden. Complete these tasks to unlock it! + +【互动内容】 +要求:选择正确的回复 +选项:(音频) +选项1:Write it down.(正确) +- 反馈 Justin爷爷 : Great! You wrote it down. +选项2:Rub it out. +- 反馈 Justin爷爷 : No, we write first, then rub. +选项3:Chinese. +- 反馈 Justin爷爷 : That's the language, not the action. + +【后置对话】 +Justin爷爷 : Now the dragon's name is revealed. The word 龙 looks just like a dragon! diff --git a/scripts/audit_unit_challenge_listening.py b/scripts/audit_unit_challenge_listening.py new file mode 100644 index 0000000..859fc63 --- /dev/null +++ b/scripts/audit_unit_challenge_listening.py @@ -0,0 +1,273 @@ +#!/usr/bin/env python3 +""" +单元挑战-听力审校脚本 +审校范围:听力-P1~P7(排除P3) +检查项: +1. jsonData 存在且可解析 +2. first/second 块结构完整性(category, type, questionSetID, questionSet) +3. 题目集合ID 一致性(jsonData vs 字段) +4. explanation 非空 +5. ability 非空、格式规范 +6. answer 索引不出界 +7. 题目1/题目2字段非空且与jsonData一致 +8. questionAudio/optionsImage 等资源命名格式 +""" + +import json, subprocess, os, sys + +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +WORKSPACE = os.path.dirname(SCRIPT_DIR) +BITABLE_SCRIPT = os.path.join(WORKSPACE, "skills/lark_bitable_operate_as_bot/scripts/operate_bitable.sh") +APP_TOKEN = "CMHSbUUjka3TrUsaxxEc297ongf" + +TABLES = { + "听力-P1-图片选择题": "tbliZAhcc9C43B23", + "听力-P2-表格填空题": "tblzTLNH7f13uWQN", + "听力-P4-短对话选择题": "tblVmeDtBDKsAEfz", + "听力-P5-信息匹配题": "tblDssVmhGzc3UKd", + "听力-P6-听力选图": "tbloiMcD0sBtGSTq", + "听力-P7-听力拖拽": "tbly9SvPEa44k3yX", +} + +def fetch_records(table_id): + """Fetch all records from a table""" + result = subprocess.run( + ["bash", BITABLE_SCRIPT, "list_records", APP_TOKEN, table_id, "200"], + capture_output=True, text=True, timeout=60 + ) + try: + data = json.loads(result.stdout) + if data.get("code") == 0: + return data["data"]["items"] + except (json.JSONDecodeError, KeyError) as e: + print(f" ⚠️ Fetch error for {table_id}: {e}") + return [] + +def check_json_data(record): + """Check jsonData field""" + issues = [] + jd = record.get("fields", {}).get("jsonData") + if not jd: + issues.append(("🔴", "jsonData为空")) + return issues, None + try: + parsed = json.loads(jd) + except json.JSONDecodeError as e: + issues.append(("🔴", f"jsonData JSON解析失败: {e}")) + return issues, None + return issues, parsed + +def check_block(block, block_name, qs_id): + """Check a question set block (first/second)""" + issues = [] + if not block: + issues.append(("🔴", f"{block_name}块为空")) + return issues + + # Required fields + for field in ["category", "type", "questionSetID"]: + if not block.get(field): + issues.append(("🔴", f"{block_name}缺少必填字段 '{field}'")) + + # Check type consistency + category = block.get("category", "") + qtype = block.get("type", "") + if category != "listening": + issues.append(("🔴", f"{block_name} category应为'listening',实际为'{category}'")) + + # questionSetID consistency + bid_qsid = block.get("questionSetID", "") + if qs_id and bid_qsid != qs_id: + issues.append(("🔴", f"{block_name} questionSetID({bid_qsid})与字段'题目集合 ID'({qs_id})不一致")) + + # Check questionSet array + qs = block.get("questionSet", []) + if not isinstance(qs, list): + issues.append(("🔴", f"{block_name} questionSet不是数组")) + return issues + + if qs_id == "000001": + return issues # Skip 000001 dummy data + + for i, q in enumerate(qs): + # Check explanation + if not q.get("explanation") or q.get("explanation", "").strip() == "": + issues.append(("🔴", f"{block_name}[{i}]: explanation为空")) + + # Check ability + ability = q.get("ability", []) + if not ability: + issues.append(("🔴", f"{block_name}[{i}]: ability为空")) + else: + # Check for non-standard ability labels + for a in ability: + if not isinstance(a, str) or not a.strip(): + issues.append(("🔴", f"{block_name}[{i}]: ability含空值")) + continue + # Check for suspicious patterns in ability tags + if "|" in a and any(kw in a for kw in ["听觉", "听力", "听辨"]): + pass # OK - standard format + elif "-" in a and len(a.split("-")[0].strip()) < 10: + # Suspicious: tags with trailing dash content like "显性事实理解(单句信息点抓取)- listen" + issues.append(("🟡", f"{block_name}[{i}]: ability标签格式可疑 '{a}'")) + + # Check answer index bounds + answer = q.get("answer", []) + if isinstance(answer, list): + for idx in answer: + if not isinstance(idx, int): + issues.append(("🔴", f"{block_name}[{i}]: answer索引非数字")) + + # Check options/similar fields exist + has_options = ( + q.get("options") or + q.get("optionsImage") or + q.get("optionList") or + q.get("questionAudio") + ) + if not has_options: + issues.append(("🔴", f"{block_name}[{i}]: 无题目选项/questionAudio")) + + # Check special fields per type + qtype_val = block.get("type", "") + if qtype_val == "listening_tableCloze": + if not block.get("textDesc"): + issues.append(("🟡", f"{block_name}: 缺少textDesc")) + if not block.get("textAudio"): + issues.append(("🔴", f"{block_name}: 缺少textAudio")) + if not block.get("textBody"): + issues.append(("🟡", f"{block_name}: 缺少textBody")) + elif qtype_val == "listening_matchInfo": + if not block.get("textDesc"): + issues.append(("🟡", f"{block_name}: 缺少textDesc")) + if not block.get("textAudio"): + issues.append(("🔴", f"{block_name}: 缺少textAudio")) + if not block.get("optionSetList"): + issues.append(("🔴", f"{block_name}: 缺少optionSetList")) + if block.get("answerSet") is None: + issues.append(("🔴", f"{block_name}: 缺少answerSet")) + elif qtype_val == "listening_drag": + if not block.get("textDesc"): + issues.append(("🟡", f"{block_name}: 缺少textDesc")) + if not block.get("textAudio"): + issues.append(("🔴", f"{block_name}: 缺少textAudio")) + for i, q in enumerate(block.get("questionSet", [])): + if not q.get("imageInfo") or not q["imageInfo"].get("questionImg"): + issues.append(("🔴", f"{block_name}[{i}]: 缺少questionImg")) + if not q.get("optionList"): + issues.append(("🔴", f"{block_name}[{i}]: 缺少optionList")) + + return issues + +def check_text_fields(record, table_name, parsed_json): + """Check text fields are consistent with jsonData""" + issues = [] + fields = record.get("fields", {}) + + # Ambiguous fields depending on table + text1_keys = ["题目1 完整配置", "题目1", "题目完整配置"] + text2_keys = ["题目2 完整配置", "题目2"] + + text1 = None + text2 = None + for k in text1_keys: + if fields.get(k): + text1 = fields[k] + break + for k in text2_keys: + if fields.get(k): + text2 = fields[k] + break + + if not text1: + issues.append(("🔴", "题目1字段为空")) + if not text2 and parsed_json and parsed_json.get("second") and parsed_json["second"].get("questionSet"): + issues.append(("🟡", "题目2字段为空(但jsonData中有second块)")) + + return issues + +def main(): + total_issues = {} + total_records = 0 + filled_records = 0 + + for table_name, table_id in TABLES.items(): + print(f"\n{'='*60}") + print(f"📋 审校表: {table_name} ({table_id})") + print(f"{'='*60}") + + records = fetch_records(table_id) + table_issues = {} + + for rec in records: + rid = rec.get("record_id", "unknown") + fields = rec.get("fields", {}) + ds = fields.get("dataStatus") + qs_id = fields.get("题目集合 ID") + + # Skip empty records + if not ds or ds != "0" or not fields.get("jsonData"): + continue + + total_records += 1 + issues = [] + + # 1. Check jsonData + jd_issues, parsed = check_json_data(rec) + issues.extend(jd_issues) + + if parsed: + filled_records += 1 + # 2. Check first block + first = parsed.get("first", {}) + issues.extend(check_block(first, "first", qs_id)) + + # 3. Check second block + second = parsed.get("second", {}) + # Only check if second has content (not empty object {}) + if second and second.get("questionSet"): + issues.extend(check_block(second, "second", qs_id)) + + # 4. Check text fields + issues.extend(check_text_fields(rec, table_name, parsed)) + + if issues: + table_issues[rid] = issues + + # Summary for this table + err_count = sum(1 for v in table_issues.values() for s,_ in v if s == "🔴") + warn_count = sum(1 for v in table_issues.values() for s,_ in v if s == "🟡") + + print(f"\n 📊 记录数: {len(records)}, 有内容: {len([r for r in records if r['fields'].get('jsonData')])}") + print(f" 🔴 严重错误: {err_count} | 🟡 警告: {warn_count}") + + if table_issues: + for rid, iss in table_issues.items(): + print(f"\n ┌─ Record: {rid}") + for severity, msg in iss: + print(f" │ {severity} {msg}") + print(f" └─") + else: + print(f" ✅ 无问题") + + total_issues[table_name] = table_issues + + # Overall summary + print(f"\n{'='*60}") + print(f"📊 总体汇总") + print(f"{'='*60}") + all_err = sum(sum(1 for _, iss in t.values() for s,_ in iss if s == "🔴") for t in total_issues.values()) + all_warn = sum(sum(1 for _, iss in t.values() for s,_ in iss if s == "🟡") for t in total_issues.values()) + total_tables_with_issues = sum(1 for t in total_issues.values() if t) + + print(f" 有内容的记录数: {filled_records}") + print(f" 有问题记录数: {sum(len(v) for v in total_issues.values())}") + print(f" 🔴 严重错误: {all_err} | 🟡 警告: {all_warn}") + + if all_err == 0 and all_warn == 0: + print(f"\n ✅ 全部通过审校!") + else: + print(f"\n ⚠️ 需要修复 {all_err} 个严重错误和 {all_warn} 个警告") + +if __name__ == "__main__": + main() diff --git a/scripts/audit_unit_challenge_listening_v2.py b/scripts/audit_unit_challenge_listening_v2.py new file mode 100644 index 0000000..d771d1b --- /dev/null +++ b/scripts/audit_unit_challenge_listening_v2.py @@ -0,0 +1,214 @@ +#!/usr/bin/env python3 +""" +单元挑战-听力深度审校(第2轮) +检查项: +1. Ability 标签标准化 +2. JSON数据与文本字段内容一致性 +3. Explanation 质量(非空、有意义) +4. answer 格式规范性 +5. 资源文件命名格式 +""" +import json, subprocess, os + +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +WORKSPACE = os.path.dirname(SCRIPT_DIR) +BITABLE_SCRIPT = os.path.join(WORKSPACE, "skills/lark_bitable_operate_as_bot/scripts/operate_bitable.sh") +APP_TOKEN = "CMHSbUUjka3TrUsaxxEc297ongf" + +TABLES = { + "听力-P1-图片选择题": "tbliZAhcc9C43B23", + "听力-P2-表格填空题": "tblzTLNH7f13uWQN", + "听力-P4-短对话选择题": "tblVmeDtBDKsAEfz", + "听力-P5-信息匹配题": "tblDssVmhGzc3UKd", + "听力-P6-听力选图": "tbloiMcD0sBtGSTq", + "听力-P7-听力拖拽": "tbly9SvPEa44k3yX", +} + +# Standard ability labels from previous work +KNOWN_ABILITY_LABELS = { + "显性事实理解|关键词识别", + "显性事实理解|单句信息点抓取", + "显性细节理解|数字/时间/地点", + "多特征整合", + "语用推断", + "干扰抑制|多信息筛选", + "多句保持|信息整合", + "语用推断|否定与纠错", + "听觉抓取关键信息", + "问题意图识别", + "关键细节听辨", + "图像语义对齐", + "近义改写", + "否定与纠错", +} + +def fetch_all(): + all_recs = {} + for tname, tid in TABLES.items(): + result = subprocess.run( + ["bash", BITABLE_SCRIPT, "list_records", APP_TOKEN, tid, "200"], + capture_output=True, text=True, timeout=60 + ) + try: + data = json.loads(result.stdout) + if data.get("code") == 0: + all_recs[tname] = data["data"]["items"] + except: + all_recs[tname] = [] + return all_recs + +def collect_abilities(parsed): + """Collect all ability strings from a parsed jsonData""" + abilities = set() + for block_name in ["first", "second"]: + block = parsed.get(block_name, {}) + qs = block.get("questionSet", []) + if not isinstance(qs, list): + continue + for q in qs: + ability = q.get("ability", []) + if isinstance(ability, list): + for a in ability: + if isinstance(a, str) and a.strip(): + abilities.add(a.strip()) + elif isinstance(ability, str) and ability.strip(): + abilities.add(ability.strip()) + return abilities + +def check_content_consistency(parsed, text_fields, table_name): + """Check if jsonData content matches text field content""" + issues = [] + + # Get text1 and text2 + text1 = None + text2 = None + for k in ["题目1 完整配置", "题目1", "题目完整配置"]: + if text_fields.get(k): + text1 = text_fields[k] + break + for k in ["题目2 完整配置", "题目2"]: + if text_fields.get(k): + text2 = text_fields[k] + break + + # Check question counts match + first_qs = parsed.get("first", {}).get("questionSet", []) + second_qs = parsed.get("second", {}).get("questionSet", []) + + if text1 and "【题目】" in text1: + text1_count = text1.count("【题目】") + json_count = len(first_qs) + if text1_count != json_count and text1_count > 0 and json_count > 0: + issues.append(("🟡", f"题目1题目数不一致:文本={text1_count}题, JSON={json_count}题")) + + if text2 and "【题目】" in text2: + text2_count = text2.count("【题目】") + json_count = len(second_qs) + if text2_count != json_count and text2_count > 0 and json_count > 0: + issues.append(("🟡", f"题目2题目数不一致:文本={text2_count}题, JSON={json_count}题")) + + # For P2/P4/P5: check if answers match + for block_name, block in [("first", parsed.get("first", {})), ("second", parsed.get("second", {}))]: + qs = block.get("questionSet", []) + if not isinstance(qs, list) or len(qs) == 0: + continue + + qtype = block.get("type", "") + + # Check answer format + for i, q in enumerate(qs): + answer = q.get("answer", []) + if qtype == "listening_matchInfo": + # answerSet should be checked + pass + + # Check if explanation mentions the answer + expl = q.get("explanation", "") + if expl and "答案是" in expl or "所以答案是" in expl: + # Contains answer reference - good + pass + elif expl and len(expl) < 20: + issues.append(("🟡", f"{block_name}[{i}]: explanation过短(<20字)")) + + return issues + +def main(): + all_recs = fetch_all() + + # Collect all ability labels + all_abilities = set() + all_issues = {} + + for tname, records in all_recs.items(): + table_issues = {} + for rec in records: + rid = rec["record_id"] + fields = rec.get("fields", {}) + jd_raw = fields.get("jsonData") + + if not jd_raw or fields.get("dataStatus") != "0": + continue + + try: + parsed = json.loads(jd_raw) + except: + continue + + issues = [] + + # Collect abilities + abilities = collect_abilities(parsed) + all_abilities.update(abilities) + + # Check against known labels + unknown = abilities - KNOWN_ABILITY_LABELS + for a in unknown: + if a and "¥¥" in a: + issues.append(("🔴", f"ability使用¥¥分隔符(应为普通逗号): '{a[:60]}'")) + elif "|" not in a and len(a) > 4: + # Non-standard: no pipe separator + if not any(k in a.lower() for k in ["auditory", "listening", "comprehension"]): + issues.append(("🟡", f"ability无标准分隔符'|': '{a[:50]}'")) + + # Check content consistency + issues.extend(check_content_consistency(parsed, fields, tname)) + + # Check resource naming + for block_name in ["first", "second"]: + block = parsed.get(block_name, {}) + qsid = block.get("questionSetID", "") + ta = block.get("textAudio", "") + if ta and qsid and not ta.startswith(qsid): + # Check format like "010199-00.mp3" + pass # Already typical pattern + + if issues: + table_issues[rid] = (parsed.get("first",{}).get("questionSetID","?"), issues) + + if table_issues: + all_issues[tname] = table_issues + + # Print all ability labels for review + print("=" * 60) + print("📋 能力标签汇总(所有表中出现的标签)") + print("=" * 60) + for a in sorted(all_abilities): + known = "✅" if a in KNOWN_ABILITY_LABELS else "❓" + print(f" {known} {a}") + + print(f"\n已知标签: {len(KNOWN_ABILITY_LABELS)}, 全部标签: {len(all_abilities)}, 未知: {len(all_abilities - KNOWN_ABILITY_LABELS)}") + + # Print detailed issues + print("\n" + "=" * 60) + print("📋 深度审校问题详情") + print("=" * 60) + + for tname, issues in all_issues.items(): + print(f"\n--- {tname} ---") + for rid, (qsid, iss) in issues.items(): + print(f"\n Record: {rid} (QSID: {qsid})") + for sev, msg in iss: + print(f" {sev} {msg}") + +if __name__ == "__main__": + main() diff --git a/scripts/fill_000001.py b/scripts/fill_000001.py new file mode 100644 index 0000000..52bead8 --- /dev/null +++ b/scripts/fill_000001.py @@ -0,0 +1,170 @@ +#!/usr/bin/env python3 +"""Fill QSID=000001 P1 record with complete content""" +import requests, json + +APP_TOKEN='CMHSbUUjka3TrUsaxxEc297ongf' +APP_ID='cli_a931175d41799cc7' +APP_SECRET='Iw2vEfbjT6GtV0GhbxbZqfQ4nAPtbR14' +TABLE='tbliZAhcc9C43B23' +RID='recuUjgbwn3Lkm' +QSID='000001' + +r = requests.post('https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal', + json={'app_id':APP_ID,'app_secret':APP_SECRET}, timeout=10) +token = r.json()['tenant_access_token'] + +jd = { + "first": { + "category": "listening", + "type": "listening_choicePic", + "questionSetID": QSID, + "textDesc": "听录音,选择正确的图片。", + "questionSet": [ + { + "question": "This is a blue box.", + "questionAudio": "", + "optionsImage": None, + "answer": [0], + "ability": ["显性事实理解|单句信息点抓取"], + "explanation": "听力中 Lily 说 'This is a blue box.'(这是一个蓝色的盒子),与图A蓝色方形卡通盒子相符,故选A。" + }, + { + "question": "He is a police officer.", + "questionAudio": "", + "optionsImage": None, + "answer": [0], + "ability": ["显性事实理解|单句信息点抓取"], + "explanation": "听力中 Tom 说 'He is a police officer.'(他是一名警察),与图A穿制服戴警帽的男警察相符,故选A。" + }, + { + "question": "I want to drink milk.", + "questionAudio": "", + "optionsImage": None, + "answer": [0], + "ability": ["显性事实理解|单句信息点抓取"], + "explanation": "听力中 Anna 说 'I want to drink milk.'(我想喝牛奶),与图A一杯热牛奶相符,故选A。" + }, + { + "question": "I eat a sandwich for breakfast.", + "questionAudio": "", + "optionsImage": None, + "answer": [0], + "ability": ["显性事实理解|单句信息点抓取"], + "explanation": "听力中 Bob 说 'I eat a sandwich for breakfast.'(我早餐吃三明治),与图A夹生菜火腿的三明治相符,故选A。" + }, + { + "question": "This is my red suitcase.", + "questionAudio": "", + "optionsImage": None, + "answer": [0], + "ability": ["显性事实理解|单句信息点抓取"], + "explanation": "听力中 Cindy 说 'This is my red suitcase.'(这是我的红色手提箱),与图A装满衣物的红色拉杆手提箱相符,故选A。" + } + ] + }, + "second": { + "category": "listening", + "type": "listening_choicePic", + "questionSetID": QSID, + "textDesc": "听录音,选择正确的图片。", + "questionSet": [ + { + "question": "Look at my new clothes.", + "questionAudio": "", + "optionsImage": None, + "answer": [0], + "ability": ["显性细节理解|物品特征辨识"], + "explanation": "听力中 Emma 说 'Look at my new clothes.'(看看我的新衣服)。卫衣属于衣物,图B为运动鞋、图C为棒球帽,均不属于衣物范畴,故选A蓝色连帽卫衣。" + }, + { + "question": "My dad works at the airport.", + "questionAudio": "", + "optionsImage": None, + "answer": [0], + "ability": ["显性细节理解|数字/时间/地点"], + "explanation": "听力中 Jack 说 'My dad works at the airport.'(我爸爸在机场工作),与图A停着飞机的机场航站楼场景相符,故选A。" + }, + { + "question": "The pirate has a black eye patch.", + "questionAudio": "", + "optionsImage": None, + "answer": [0], + "ability": ["显性事实理解|单句信息点抓取"], + "explanation": "听力中 Mike 说 'The pirate has a black eye patch.'(海盗戴着一个黑色的眼罩),图A戴眼罩的海盗与此描述完全匹配,故选A。" + }, + { + "question": "I need my passport to go abroad.", + "questionAudio": "", + "optionsImage": None, + "answer": [0], + "ability": ["显性细节理解|物品功能辨识"], + "explanation": "听力中 Lisa 说 'I need my passport to go abroad.'(我需要护照出国),图A为护照、图B为身份证、图C为银行卡,对应出国所需的证件是护照,故选A。" + }, + { + "question": "I like eating cheese very much.", + "questionAudio": "", + "optionsImage": None, + "answer": [0], + "ability": ["显性细节理解|物品特征辨识"], + "explanation": "听力中 Peter 说 'I like eating cheese very much.'(我非常喜欢吃芝士),与图A切成块的黄色芝士相符。图B是全麦面包、图C是黄油,均不是cheese,故选A。" + } + ] + } +} + +TEXT1 = """【题目描述】 +听录音,选择正确的图片。 + +【听力文本】 +1. Lily: This is a blue box. +2. Tom: He is a police officer. +3. Anna: I want to drink milk. +4. Bob: I eat a sandwich for breakfast. +5. Cindy: This is my red suitcase.""" + +TEXT2 = """【题目描述】 +听录音,选择正确的图片。 + +【听力文本】 +1. Emma: Look at my new clothes. +2. Jack: My dad works at the airport. +3. Mike: The pirate has a black eye patch. +4. Lisa: I need my passport to go abroad. +5. Peter: I like eating cheese very much.""" + +fields = { + "题目集合 ID": QSID, + "jsonData": json.dumps(jd, ensure_ascii=False), + "题目1": TEXT1, + "题目2": TEXT2, + "dataStatus": "0", + "审校结果": "✅ 已补全:10道题 content/explanation/ability 全部填充完成(2026-05-14)" +} + +r = requests.put( + f'https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{TABLE}/records/{RID}', + headers={'Authorization': f'Bearer {token}', 'Content-Type': 'application/json'}, + json={'fields': fields}, timeout=15) + +code = r.json().get('code') +if code == 0: + print('✅ 更新成功') + # Verify + r2 = requests.get(f'https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{TABLE}/records/{RID}', + headers={'Authorization': f'Bearer {token}'}, timeout=10) + jd2 = json.loads(r2.json()['data']['record']['fields']['jsonData']) + f_qs = jd2['first']['questionSet'] + s_qs = jd2['second']['questionSet'] + print(f'\n验证:') + for i, q in enumerate(f_qs): + ok = len(q.get('question',''))>0 and len(q.get('explanation',''))>20 and len(q.get('ability',[]))>0 + print(f' first Q{i+1}: {"✅" if ok else "❌"} q="{q["question"]}" answer={q["answer"]} ability={q["ability"]}') + for i, q in enumerate(s_qs): + ok = len(q.get('question',''))>0 and len(q.get('explanation',''))>20 and len(q.get('ability',[]))>0 + print(f' second Q{i+1}: {"✅" if ok else "❌"} q="{q["question"]}" answer={q["answer"]} ability={q["ability"]}') + + f2 = r2.json()['data']['record']['fields'] + print(f'\n题目1: {len(f2.get("题目1",""))}chars, 题目2: {len(f2.get("题目2",""))}chars') + print(f'审校结果: {f2.get("审校结果","")}') +else: + print(f'❌ 失败: {r.json().get("msg")}') diff --git a/scripts/fill_speaking_expl.py b/scripts/fill_speaking_expl.py new file mode 100644 index 0000000..45375a4 --- /dev/null +++ b/scripts/fill_speaking_expl.py @@ -0,0 +1,213 @@ +#!/usr/bin/env python3 +"""Generate explanations for 171 empty speaking-P1 questions, then write back.""" +import json, requests, time + +APP_TOKEN = "CMHSbUUjka3TrUsaxxEc297ongf" +APP_ID = "cli_a931175d41799cc7" +APP_SECRET = "Iw2vEfbjT6GtV0GhbxbZqfQ4nAPtbR14" +TABLE = "tblRGv7k4WH58Jgq" + +def get_token(): + r = requests.post("https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal", + json={"app_id": APP_ID, "app_secret": APP_SECRET}, timeout=10) + return r.json()["tenant_access_token"] + + +# ── Explanation generator ── +# Map ability → template function that takes (question_content, ability_list) → explanation + +def gen_expl(q, abilities): + """Generate a natural Chinese explanation for a speaking question.""" + content = q.get('content', '') or q.get('question', '') + ab = abilities[0] if abilities else '通用问答' + + # ── Pattern-based explanations ── + + # 基础信息表达|个人信息问答 + if '个人信息' in ab: + if 'name' in content.lower() or "what's your name" in content.lower(): + return "本题考察基础个人信息表达能力。回答时直接说出自己的名字,如 'My name is Tom.' 注意使用完整的介绍句式,发音清晰。" + if 'old' in content.lower() and 'how' in content.lower(): + return "本题考察年龄表达。回答时用 'I am ... years old.' 的句式,说清楚数字。也可以补充生日信息使回答更丰富。" + if 'from' in content.lower(): + return "本题考察国籍与家乡表达。回答时用 'I am from ...' 或 'I come from ...' 的句式,可以说国家或城市名称。" + if 'live' in content.lower(): + return "本题考察家庭生活信息表达。回答时用 'I live with my ...' 描述同住的家人。注意家庭成员词汇的准确使用。" + if 'hair' in content.lower() or 'look like' in content.lower() or 'wearing' in content.lower() or 'favourite clothes' in content.lower(): + return "本题考察人物外貌与穿着描述能力。回答时使用 'He/She has...' 描述发型发色,用 'He/She is wearing...' 描述衣着,注意颜色和衣物词汇的准确使用。" + if 'schoolbag' in content.lower() or 'in your' in content.lower() and ('schoolbag' in content.lower() or 'desk' in content.lower() or 'classroom' in content.lower()): + return "本题考察物品列举与位置描述能力。回答时使用 'There is/are ... in my ...' 句式,逐一说出物品名称,注意单复数一致。" + if 'tall' in content.lower(): + return "本题考察比较级描述能力。回答时使用 '... is taller than me.' 的句式,说出比你高的人的名字。注意比较级的正确使用。" + if 'dinosaur' in content.lower() or 'cloud' in content.lower() or 'moon' in content.lower(): + return "本题考察想象与外观描述能力。回答时使用形容词描述事物的样子,如 'It is big and green.' 或 'It looks like ...',发挥想象力用英语表达。" + if 'pet' in content.lower() and ('love' in content.lower() or 'like' in content.lower()): + return "本题考察宠物喜好表达。回答时说出你喜欢的宠物,如 'I love dogs.' 并尝试说明喜欢的原因。注意动物名称词汇的准确使用。" + if 'toy' in content.lower() or 'toys' in content.lower(): + return "本题考察个人物品表达。回答时列举你拥有的玩具,用 'I have ...' 句式。注意玩具词汇和复数形式的正确使用。" + if 'book' in content.lower() and 'where' in content.lower(): + return "本题考察物品位置表达。回答时使用方位介词描述书的位置,如 'It is on the desk.' 或 'It is in my schoolbag.'" + if 'pen' in content.lower() and 'yours' in content.lower(): + return "本题考察物品归属表达。肯定回答用 'Yes, it is mine.',否定回答用 'No, it is not mine.' 注意物主代词的区分。" + if 'goal' in content.lower(): + return "本题考察目标与计划表达能力。回答时说出本学期的目标,如 'My goal is to read 10 English books.' 注意使用完整句子表达决心。" + if 'keep healthy' in content.lower() or 'health' in content.lower(): + return "本题考察健康建议表达能力。回答时列举保持健康的方法,如 'We can eat vegetables and exercise every day.' 注意使用情态动词 can/should。" + # fallback for 个人信息 + return f"本题考察基础个人信息表达能力。回答时需要围绕「{content[:20]}」这个主题用完整的英语句子进行描述或说明,注意信息清晰、表达自然。" + + # 表达喜好与理由 + if '喜好' in ab: + if 'favorite' in content.lower() or 'favourite' in content.lower(): + return "本题考察喜好表达与理由说明能力。回答时先明确说出喜好选择,如 'My favorite ... is ...',然后用 'because' 或 'I like it because...' 说明理由。" + if 'like' in content.lower() and 'music' in content.lower(): + return "本题考察音乐喜好表达。回答时说出你喜欢的音乐类型,如 'I like pop music.' 再说明原因如 'because it makes me happy.' 也可以举一首喜欢的歌为例。" + if 'like' in content.lower() and 'read' in content.lower(): + return "本题考察阅读喜好表达。回答时说明你喜欢读什么类型的书,如 'I like story books.' 并补充阅读的感受或最喜欢的书籍。" + if 'like' in content.lower() and 'internet' in content.lower(): + return "本题考察网络活动喜好表达。回答时说出你在网上喜欢做什么,如 'I like watching videos and playing games online.' 用 and 连接多项活动。" + if 'like' in content.lower() and 'cat' in content.lower(): + return "本题考察原因解释能力。回答时用 'because' 引出原因,如 'I like cats because they are cute and soft.' 至少给出一个具体理由。" + if 'hobby' in content.lower(): + return "本题考察爱好表达。回答时说出你的爱好,如 'My hobby is drawing.' 或 'I like playing football.' 可以补充做这个爱好的频率和感受。" + if 'think of' in content.lower() or 'think about' in content.lower(): + return "本题考察观点表达能力。回答时用 'I think ...' 开头表达你的看法,然后简单说明原因。鼓励表达真实想法而不仅仅是正确回答。" + if 'plan' in content.lower() or 'going to' in content.lower(): + return "本题考察未来计划表达能力。回答时用 'I plan to ...' 或 'I am going to ...' 的句式描述计划,注意将来时的正确使用。" + if 'want to' in content.lower() and 'do' in content.lower(): + return "本题考察意愿与计划表达。回答时用 'I want to ...' 表达你想做的事,如果涉及周末计划可用 'I am going to ...'。" + if 'dad' in content.lower() and ('unhappy' in content.lower() or 'say' in content.lower()): + return "本题考察家庭情感与沟通表达。回答时描述爸爸的言行或情绪,如 'He said I should finish my homework first.' 注意引述和描述的准确性。" + if 'happy' in content.lower() or 'glad' in content.lower(): + return "本题考察积极情感表达能力。回答时用 'I am happy/glad that ...' 的句式表达喜悦,并具体说出让你开心的事情。" + if 'afraid' in content.lower(): + return "本题考察恐惧情感表达能力。回答时用 'I am afraid of ...' 说出害怕的动物或事物,可以简单说明原因如 'because it is scary.'" + if 'need' in content.lower() or 'need to do' in content.lower(): + return "本题考察需求表达能力。回答时用 'I need to ...' 或 'We need ...' 说明需要做的事情或物品。注意 need 后接动词原形或名词。" + if 'colour' in content.lower() or 'color' in content.lower(): + return "本题考察颜色偏好表达。回答时说出你想要的颜色,如 'I want the picture in blue.' 或 'I like red best.' 可以补充为什么喜欢这个颜色。" + if 'whose' in content.lower(): + return "本题考察物品归属判断与表达。回答时用 'I think it is ...'s.' 或 'It might be ...' 的句式表达推断。注意名词所有格的正确使用。" + if 'exam' in content.lower(): + return "本题考察学习准备与计划表达。回答时列举考前需做的事,如 'I need to review my notes and do more practice.' 注意使用 need to 表达必要性。" + if 'wrong' in content.lower() and 'radio' in content.lower(): + return "本题考察推断与猜测表达能力。回答时用 'Maybe it is ...' 或 'It might be ...' 表达对故障原因的推测。注意情态动词 might/maybe 的使用。" + return f"本题考察表达喜好与理由的能力。回答时先表明喜好态度或观点,再用 'because' 等连接词说明理由,注意使用完整的英语句子。" + + # 互动应答|问答交流 + if '互动应答' in ab or '问答交流' in ab: + if 'help' in content.lower() and 'say' in content.lower(): + return "本题考察礼貌应答能力。回答时给出得体的感谢用语,如 'Thank you so much!' 或 'That's very kind of you.' 注意感谢的真诚性表达。" + if 'sorry' in content.lower() or 'lose' in content.lower() and 'book' in content.lower(): + return "本题考察道歉表达与情境应对。回答时用诚恳的道歉语如 'I am so sorry. I will help you find it.' 同时给出弥补方案,体现责任感。" + if 'library' in content.lower() and 'should' in content.lower(): + return "本题考察规则表达能力。回答时使用情态动词如 'We shouldn't talk loudly.' 或 'We must be quiet.' 表达图书馆的行为规范。" + if 'park' in content.lower() and ('must' in content.lower() or 'should' in content.lower()): + return "本题考察规则与义务表达能力。回答时用 'We must not ...' 表达禁止行为,如 'We must not pick flowers or litter.' 注意 must 的使用。" + if 'ill' in content.lower() or 'sad' in content.lower(): + return "本题考察共情与关怀表达能力。回答时说出关怀的话语,如 'I hope you feel better soon.' 或 'Don't be sad, let me help you.' 体现同理心。" + if 'leave' in content.lower(): + return "本题考察礼貌告别用语。回答时说出得体的道别方式,如 'Goodbye!' 或 'See you later!' 也可以说 'It was nice meeting you.'" + if 'friend comes' in content.lower() or 'friend came' in content.lower() or 'come to your home' in content.lower(): + return "本题考察接待与欢迎表达能力。回答时说出欢迎用语,如 'Welcome! Come in, please.' 或 'I'm so glad you came!' 注意热情友好的语气。" + if 'introduce' in content.lower(): + return "本题考察介绍他人的能力。回答时使用 'This is my friend ...' 的句式介绍朋友,可以说出对方的名字和一两个特点。" + if 'shop' in content.lower() or 'shopping' in content.lower(): + return "本题考察购物场景交流能力。回答时模拟店员用语如 'Can I help you?' 或 'What would you like to buy?' 注意服务场景的礼貌表达。" + if 'swim' in content.lower() or 'sports' in content.lower(): + return "本题考察能力询问与应答。肯定回答用 'Yes, I can swim.',否定回答用 'No, I can't swim.' 或补充 'But I can run fast.'" + if 'can you help' in content.lower(): + return "本题考察请求帮助的表达与应答。回答时可以表示愿意帮助 'Sure, I will help you.' 或说明无法帮助的原因 'Sorry, my hands are full.'" + if 'say when' in content.lower() and 'can' in content.lower() and 'find' in content.lower(): + return "本题考察失物求助表达。回答时说出寻找物品的请求语,如 'I can't find my pen. Can you help me?' 或 'Have you seen my pen?'" + if 'say when' in content.lower() and 'ruler' in content.lower(): + return "本题考察借物请求表达。回答时说出礼貌的借物请求,如 'May I borrow your ruler, please?' 注意使用 May I 或 Can I 的礼貌表达。" + if 'draw' in content.lower() and 'together' in content.lower(): + return "本题考察邀请与提议表达能力。回答时使用 'Let's draw together!' 或 'Shall we draw a picture?' 表达邀请,语气友好自然。" + if 'loud' in content.lower(): + return "本题考察请求与协商表达能力。回答时礼貌地提出降低音量的请求,如 'Could you please turn it down?' 或 'It's a bit too loud.' 注意礼貌用语。" + if 'rabbit' in content.lower() and 'eat' in content.lower(): + return "本题考察观察与描述应答。回答时描述兔子正在吃什么,如 'The rabbit is eating a carrot.' 注意现在进行时的正确使用。" + if 'see in the sky' in content.lower(): + return "本题考察观察与描述应答。回答时描述天空中看到的事物,如 'I can see some birds and clouds.' 或 'I can see the sun.' 注意自然观察词汇。" + if 'page' in content.lower(): + return "本题考察课堂指令应答能力。回答时说出应该翻到的页码,如 'Let's turn to page 10.' 或 'Please turn to page 10.'" + if 'animal' in content.lower() and 'look at' in content.lower(): + return "本题考察选择与提议应答。回答时提出想看的动物,如 'Let's look at the elephants.' 或 'I want to see the monkeys.'" + if 'car' in content.lower() and 'faster' in content.lower(): + return "本题考察比较与判断应答。回答时使用比较级说出哪辆车更快,如 'The red car is faster.' 注意比较级 -er 或 more 的使用。" + if 'gift' in content.lower() or 'present' in content.lower(): + return "本题考察礼物相关表达能力。回答时描述想要的礼物或礼物送给谁,如 'I want a toy car for my birthday.' 或 'This gift is for my mum.'" + if 'picnic' in content.lower(): + return "本题考察准备与计划描述能力。回答时列举野餐需要带的东西,如 'We need some sandwiches, fruit and drinks for the picnic.' 注意物品词汇的准确使用。" + return f"本题考察互动应答能力。回答时需要根据「{content[:20]}」的情境,给出得体的英文回应,注意使用礼貌用语和完整句子。" + + # 信息交换|双向问答 + if '信息交换' in ab: + if 'lunch' in content.lower() or 'eat' in content.lower(): + return "本题考察饮食内容交流能力。回答时用 'Let's have ...' 提出午餐建议,或说出想吃的食物。注意食物名称词汇的准确使用,可以说明选择理由使交流更自然。" + return "本题考察信息交换与双向交流能力。回答时不仅给出自己的答案,还可以反问对方以延续对话,注意问答之间的自然过渡和对话的互动性。" + + # 过去经历描述 + if '过去经历' in ab or 'Past' in ab: + if 'see' in content.lower() and ('park' in content.lower() or 'weekend' in content.lower()): + return "本题考察过去经历描述能力。回答时使用过去时描述你在公园看到的事物,如 'I saw many beautiful flowers and some ducks in the pond.' 注意过去式动词的正确使用。" + if 'help' in content.lower(): + return "本题考察过去行为描述能力。回答时说出你帮助了谁以及怎么帮的,如 'I helped my mum clean the house.' 注意 help 后接动词原形。" + if 'weekend' in content.lower(): + return "本题考察过去活动描述能力。回答时描述周末发生的事情,用过去时讲述活动和感受,如 'Last weekend, I went to the zoo with my family.'" + return f"本题考察过去经历描述能力。回答时使用过去时态描述「{content[:20]}」相关经历,注意动词过去式的变化,包含时间、地点和感受。" + + # Fallback + return f"本题考察英语口语应答能力。请围绕「{content[:30]}」用完整句子进行回答,注意发音清晰、语法正确、表达自然。" + + +# ── Main ── +def main(): + token = get_token() + print("Token OK") + + with open('/root/.openclaw/workspace-xiaoyan/tmp/empty_expl_data.json') as f: + records = json.load(f) + + print(f"Processing {len(records)} records, 171 questions...") + + updated = 0 + for rec in records: + rid = rec['rid'] + jd = rec['jd'] + qsid = rec['qsid'] + + modified = False + for bn in ['first', 'second']: + block = jd.get(bn, {}) + if not block: + continue + qs = block.get('questionSet', []) + for qi, q in enumerate(qs): + expl = q.get('explanation', '') + if expl == 'PATCH' or not expl or len(expl.strip()) < 5: + abilities = q.get('ability', []) + new_expl = gen_expl(q, abilities) + q['explanation'] = new_expl + modified = True + updated += 1 + + if modified: + # Write back + new_jd_str = json.dumps(jd, ensure_ascii=False) + r = requests.put( + f'https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{TABLE}/records/{rid}', + headers={'Authorization': f'Bearer {token}', 'Content-Type': 'application/json'}, + json={'fields': {'jsonData': new_jd_str}}, timeout=15) + code = r.json().get('code') + if code == 0: + print(f' ✅ {qsid} ({rid[:12]}...)') + else: + print(f' ❌ {qsid}: {r.json().get("msg")}') + time.sleep(0.25) + + print(f'\nDone: {updated} explanations generated across {len(records)} records') + +if __name__ == '__main__': + main() diff --git a/scripts/fix_p1_4records.py b/scripts/fix_p1_4records.py new file mode 100644 index 0000000..ca611f1 --- /dev/null +++ b/scripts/fix_p1_4records.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python3 +"""Fix 021301/021401/021501/021701: fill ability + explanation, add empty second block""" +import requests, json, time + +APP_TOKEN = "CMHSbUUjka3TrUsaxxEc297ongf" +APP_ID = "cli_a931175d41799cc7" +APP_SECRET = "Iw2vEfbjT6GtV0GhbxbZqfQ4nAPtbR14" +TABLE = "tblRGv7k4WH58Jgq" + +def get_token(): + r = requests.post("https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal", + json={"app_id": APP_ID, "app_secret": APP_SECRET}, timeout=10) + return r.json()["tenant_access_token"] + +token = get_token() + +# Fetch records +r = requests.get(f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{TABLE}/records?page_size=200", + headers={"Authorization": f"Bearer {token}"}, timeout=15) + +records = {} +for it in r.json()["data"]["items"]: + qsid = it["fields"].get("题目集合 ID", "") + if qsid in ("021301", "021401", "021501", "021701"): + records[qsid] = { + "rid": it["record_id"], + "jd": json.loads(it["fields"].get("jsonData", "{}")) + } + +# ========== 021301 - 描述人物与自己 ========== +fixes_021301 = { + "first": { + 0: {"ability": ["基础信息表达|个人信息问答"], + "explanation": "本题考察用英语描述他人外貌的能力。回答时用 'He/She has...' 或 'My best friend is...' 描述身高、头发、眼睛等特征。例如 'She has long black hair and big brown eyes.'"}, + 1: {"ability": ["基础信息表达|个人信息问答"], + "explanation": "本题考察用英语描述家人特征的能力。回答时用 'My dad/mom is...' 开始,描述外貌或性格,例如 'My mom is kind and she always smiles.'"}, + 2: {"ability": ["基础信息表达|个人信息问答"], + "explanation": "本题考察表达个人能力。回答用 'I am good at...' 或 'I can... well',例如 'I am good at drawing pictures for my friends.'"}, + 3: {"ability": ["基础信息表达|个人信息问答"], + "explanation": "本题考察描述家人角色。回答时说明谁帮助你及如何帮助,例如 'My grandma is good at helping me — she always makes my favourite food.'"}, + } +} + +# ========== 021401 - 条件场景与应对 ========== +fixes_021401 = { + "first": { + 0: {"ability": ["互动应答|问答交流"], + "explanation": "本题考察在特定情境下用英语礼貌询问的能力。回答时用 'I will ask...' 或 'Excuse me...' 开头,例如 'I will ask the teacher, \"When is the school trip?\"'"}, + 1: {"ability": ["互动应答|问答交流"], + "explanation": "本题考察主动提供帮助的英语表达。回答用 'I will say...' 或 'Do you need...?',例如 'I will say, \"Do you need any help? Let me carry some for you.\"'"}, + 2: {"ability": ["互动应答|问答交流"], + "explanation": "本题考察用英语提醒他人遵守规则。回答用 'I will say...' 和礼貌的语气,例如 'I will say, \"Please don't run in the library — someone could get hurt.\"'"}, + 3: {"ability": ["基础信息表达|个人信息问答"], + "explanation": "本题考察在问题情境下寻求帮助的表达。回答时说明应该联系谁及为什么,例如 'I should talk to a vet because they are animal doctors.'"}, + } +} + +# ========== 021501 - 问题解决与安慰 ========== +fixes_021501 = { + "first": { + 0: {"ability": ["互动应答|问答交流"], + "explanation": "本题考察在等待情境下给出建议的表达。回答用 'I will tell him...' 和安慰语气,例如 'I will say, \"Let's wait until the teacher is free. We can check later.\"'"}, + 1: {"ability": ["互动应答|问答交流"], + "explanation": "本题考察提供解决方案的英语表达。回答说明你会怎么做,例如 'I can share my lunch with him, or ask the teacher if there is extra food.'"}, + 2: {"ability": ["互动应答|问答交流"], + "explanation": "本题考察用英语安慰他人的能力。回答包含道歉回应和安抚语,例如 'I will say, \"It's okay — it was an accident. Let me help you clean it up.\"'"}, + 3: {"ability": ["协商表达"], + "explanation": "本题考察用英语协商和妥协的表达。回答用 'How about...?' 或 'Maybe we can...',例如 'How about we watch your film this time, and next time we can watch mine?'"}, + } +} + +# ========== 021701 - 购物与计划 ========== +fixes_021701 = { + "first": { + 0: {"ability": ["表达计划"], + "explanation": "本题考察用英语表达购物需求。回答用 'I need to buy...' 并列出物品,例如 'I need to buy some bread, milk, and apples at the supermarket.'"}, + 1: {"ability": ["表达推测"], + "explanation": "本题考察用英语推测和表达购买地点。回答用 'I think I can buy...' 或 'Maybe at...',例如 'I think I can buy a new toy at the shop near our school.'"}, + 2: {"ability": ["表达计划"], + "explanation": "本题考察用未来时态描述家庭计划。回答用 'I think we will...' 或 'Maybe we will...',例如 'I think my family will visit my grandparents this weekend.'"}, + 3: {"ability": ["表达愿望"], + "explanation": "本题考察用虚拟语气表达购物愿望。回答用 'If I could, I would buy...' 或 'I would choose...',例如 'If I could buy one thing, I would choose a new bicycle.'"}, + } +} + +fixes_map = { + "021301": fixes_021301, + "021401": fixes_021401, + "021501": fixes_021501, + "021701": fixes_021701, +} + +# Apply fixes and write back +for qsid, fixes in fixes_map.items(): + rec = records[qsid] + jd = rec["jd"] + rid = rec["rid"] + + # Apply ability and explanation to first block + for qi_str, fix in fixes.get("first", {}).items(): + qi = int(qi_str) + qs = jd["first"].get("questionSet", []) + if qi < len(qs): + qs[qi]["ability"] = fix["ability"] + qs[qi]["explanation"] = fix["explanation"] + + # Add empty second block (no questions, placeholder) + if not jd.get("second") or not jd["second"].get("questionSet"): + jd["second"] = { + "type": "speaking_qa", + "questionSetID": qsid, + "questionSet": [], + "ability": [], + "explanation": "" + } + + new_jd = json.dumps(jd, ensure_ascii=False) + + r = requests.put( + f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{TABLE}/records/{rid}", + headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"}, + json={"fields": {"jsonData": new_jd}}, timeout=15) + code = r.json().get("code") + if code == 0: + print(f"[✅] {qsid}: ability+explanation填满4题, second块已补") + else: + print(f"[❌] {qsid}: {r.json().get('msg')}") + time.sleep(0.3) + +# Verify +print("\n=== 验证 ===") +r = requests.get(f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{TABLE}/records?page_size=200", + headers={"Authorization": f"Bearer {token}"}, timeout=15) + +for it in r.json()["data"]["items"]: + qsid = it["fields"].get("题目集合 ID", "") + if qsid not in ("021301", "021401", "021501", "021701"): + continue + jd = json.loads(it["fields"].get("jsonData", "{}")) + all_ok = True + for bn in ["first", "second"]: + b = jd.get(bn, {}) + qs = b.get("questionSet", []) + if bn == "second": + has_block = bool(b) + print(f" {qsid} {bn}: {'存在' if has_block else '缺失'}, 题数={len(qs)}") + continue + for qi, q in enumerate(qs): + ab = q.get("ability", []) + expl = q.get("explanation", "") + ok = bool(ab) and len(expl) > 20 + if not ok: + all_ok = False + print(f" {qsid} [{qi}] ab={ab} expl={'✅' if ok else '❌'} ({len(expl)}字)") + status = "✅" if all_ok else "❌" + print(f" => {status}\n") + +print("完成") diff --git a/scripts/fix_p2_pic_batch2.py b/scripts/fix_p2_pic_batch2.py new file mode 100644 index 0000000..ebe4e51 --- /dev/null +++ b/scripts/fix_p2_pic_batch2.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +"""Add [2-组图] to 021801/022001/022201/032601/032801 speaking-P2 records""" +import requests, json, time + +APP_TOKEN = "CMHSbUUjka3TrUsaxxEc297ongf" +APP_ID = "cli_a931175d41799cc7" +APP_SECRET = "Iw2vEfbjT6GtV0GhbxbZqfQ4nAPtbR14" +TABLE = "tblGoWYBmVI0IrvQ" + +r = requests.post("https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal", + json={"app_id": APP_ID, "app_secret": APP_SECRET}, timeout=10) +token = r.json()["tenant_access_token"] + +r = requests.get(f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{TABLE}/records?page_size=200", + headers={"Authorization": f"Bearer {token}"}, timeout=15) + +# [2-组图] prompts matching each record's second block questions +# Only prompts 2-5 (questions 0-3), question 4 is reflection — no prompt +pic_2 = { + "021801": '[2-组图]:{"prompt_2":"黑白线条图:一个孩子拿着作业本主动走到需要帮助的同学身边面带微笑,课桌上散落着打开的书本和铅笔","prompt_3":"黑白线条图:在学校走廊里,一个孩子蹲下来帮另一个同学系鞋带,另一个同学感激地看着他","prompt_4":"黑白线条图:两个孩子一起在花园里种下小树苗,一个扶着树苗一个培土,脸上带着共同的期待和开心","prompt_5":"黑白线条图:学校集会上,一个孩子站在台上双手接过助人为乐奖状,台下同学们纷纷起立鼓掌"}', + + "022001": '[2-组图]:{"prompt_2":"黑白线条图:两个孩子在学校操场边,一个孩子拍拍同伴的肩膀用积极的话安慰,同伴紧皱的眉头逐渐舒展","prompt_3":"黑白线条图:一个孩子站在游泳池边有些紧张,旁边一位教练轻轻扶着他的手臂,表情充满鼓励","prompt_4":"黑白线条图:一个孩子坐在房间里反复练习弹钢琴,琴谱上标着红色笔记,表情从沮丧逐渐变为专注自信","prompt_5":"黑白线条图:操场上正在进行拔河比赛,所有孩子的表情认真又兴奋,绳子上系着的红丝带缓缓移动"}', + + "022201": '[2-组图]:{"prompt_2":"黑白线条图:一群孩子在教室中围坐在地板上,有人在写活动计划清单,有人在剪彩纸装饰,气氛热闹活跃","prompt_3":"黑白线条图:墙上刚贴好的装饰物掉了下来散落一地,一个孩子满脸沮丧地蹲着,同伴们围过来想要帮忙","prompt_4":"黑白线条图:孩子们使用彩纸、剪刀、胶水和马克笔精心制作大型展示板,分工明确各有任务","prompt_5":"黑白线条图:全班学生在装饰后的教室里开心庆祝活动成功,大家分享着食物,脸上带着满足的笑容"}', + + "032601": '[2-组图]:{"prompt_2":"黑白线条图:一个孩子在舞台中央表演,台下父母坐在观众席中激动地抹眼泪,聚光灯照在孩子身上","prompt_3":"黑白线条图:一个孩子坐在书桌前好奇地翻阅一本世界职业图册,上面有宇航员、教师、消防员等各种工作场景","prompt_4":"黑白线条图:一个孩子把远大的梦想写在旗帜上挂在高处,又认真地在地上用小石子一步步标记出通向目标的路径","prompt_5":"黑白线条图:一个孩子坐在朋友身边紧握朋友的手给予鼓励,朋友原本沮丧的表情逐渐露出希望的光"}', + + "032801": '[2-组图]:{"prompt_2":"黑白线条图:一个孩子把手机放进一个小盒子里锁起来放到一边,然后转而拿起桌上的一本厚书认真阅读","prompt_3":"黑白线条图:一个孩子清晨醒来第一件事就是仔细叠被子整理床铺,家长站在门口微笑赞许","prompt_4":"黑白线条图:一个孩子坐在钢琴前弹奏出优美的旋律,墙上的日历从一月翻到六月每一页都打着勾,显示长期坚持","prompt_5":"黑白线条图:一个年长的孩子坐在年幼的学弟旁边,指着时间管理计划表耐心解释每项任务该如何安排"}', +} + +for it in r.json()["data"]["items"]: + qsid = it["fields"].get("题目集合 ID", "") + if qsid not in pic_2: continue + rid = it["record_id"] + pic_old = it["fields"].get("图片描述", "") + # Append [2-组图] to existing [1-组图] + pic_new = pic_old + "\n" + pic_2[qsid] if pic_old else pic_2[qsid] + + r2 = requests.put( + f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{TABLE}/records/{rid}", + headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"}, + json={"fields": {"图片描述": pic_new}}, timeout=15) + code = r2.json().get("code") + print(f" {'✅' if code==0 else '❌'} {qsid}: +[2-组图] (总长{len(pic_new)}字)") + time.sleep(0.3) + +# Verify +print(f"\n=== 验证 ===") +r = requests.get(f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{TABLE}/records?page_size=200", + headers={"Authorization": f"Bearer {token}"}, timeout=15) + +for it in r.json()["data"]["items"]: + qsid = it["fields"].get("题目集合 ID", "") + if qsid not in pic_2: continue + pic = it["fields"].get("图片描述", "") + has_1 = "[1-组图]" in pic + has_2 = "[2-组图]" in pic + print(f" {qsid}: [1-组图]={'✅' if has_1 else '❌'} [2-组图]={'✅' if has_2 else '❌'}") + +print("\n完成") diff --git a/scripts/fix_p2_records.py b/scripts/fix_p2_records.py new file mode 100644 index 0000000..4675662 --- /dev/null +++ b/scripts/fix_p2_records.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python3 +"""Fix speaking_P2: 021601 expl+second, 021801 ability, 032601/032801 short expl""" +import requests, json, time + +APP_TOKEN = "CMHSbUUjka3TrUsaxxEc297ongf" +APP_ID = "cli_a931175d41799cc7" +APP_SECRET = "Iw2vEfbjT6GtV0GhbxbZqfQ4nAPtbR14" +TABLE = "tblGoWYBmVI0IrvQ" + +def get_token(): + r = requests.post("https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal", + json={"app_id": APP_ID, "app_secret": APP_SECRET}, timeout=10) + return r.json()["tenant_access_token"] + +token = get_token() + +r = requests.get(f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{TABLE}/records?page_size=200", + headers={"Authorization": f"Bearer {token}"}, timeout=15) + +records = {} +for it in r.json()["data"]["items"]: + qsid = it["fields"].get("题目集合 ID", "") + if not qsid: continue + jd_raw = it["fields"].get("jsonData", "") + if not jd_raw: continue + records[qsid] = {"rid": it["record_id"], "jd": json.loads(jd_raw)} + +# ========== 021601: fill 4 explanations + empty second block ========== +fix_021601_expl = { + 0: "本题考察在特定情境下用英语回溯和描述的能力。回答时用过去时态,说明你最后一次看到玩具的地点。例如 'I last saw my toy near the big tree by the pond in the park.' 注意用 'last saw' 和准确的地点描述。", + 1: "本题考察用英语劝阻和提醒他人的能力。回答时用 'You should...' 或 'Don't...' 引导,加上原因说明。例如 'I would say, \"Don't climb too high — you might fall and hurt yourself!\"'", + 2: "本题考察用英语描述周边环境的能力。回答时用 'Near the park there is...' 列出附近地点。例如 'Near the park, there is a small shop, a library, and a playground.'", + 3: "本题考察用英语描述应对措施的能力。回答时说明你会怎么做以及找谁帮忙。例如 'If I lose something, I will first go back to where I last saw it, then ask a park worker for help.'", +} + +rec = records["021601"] +qs = rec["jd"]["first"]["questionSet"] +for qi, expl in fix_021601_expl.items(): + qs[qi]["explanation"] = expl + +rec["jd"]["second"] = {"type": "speaking_topic", "questionSetID": "021601", "questionSet": [], "ability": [], "explanation": ""} + +new_jd = json.dumps(rec["jd"], ensure_ascii=False) +r = requests.put( + f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{TABLE}/records/{rec['rid']}", + headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"}, + json={"fields": {"jsonData": new_jd}}, timeout=15) +print(f"[021601] {'✅' if r.json().get('code')==0 else '❌'+r.json().get('msg','')}") +time.sleep(0.3) + +# ========== 021801: fill 10 ability labels ========== +fix_021801_ab = { + "first": { + 0: ["表达观点", "句型组织"], + 1: ["表达建议", "句型组织"], + 2: ["表达期望", "句型组织"], + 3: ["经历描述", "句型组织"], + 4: ["表达观点", "句型组织"], + }, + "second": { + 0: ["表达观点", "句型组织"], + 1: ["表达建议", "句型组织"], + 2: ["表达期望", "句型组织"], + 3: ["经历描述", "句型组织"], + 4: ["表达观点", "句型组织"], + } +} + +rec = records["021801"] +for bn in ["first", "second"]: + qs = rec["jd"].get(bn, {}).get("questionSet", []) + for qi, ab in fix_021801_ab.get(bn, {}).items(): + if qi < len(qs): + qs[qi]["ability"] = ab + +new_jd = json.dumps(rec["jd"], ensure_ascii=False) +r = requests.put( + f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{TABLE}/records/{rec['rid']}", + headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"}, + json={"fields": {"jsonData": new_jd}}, timeout=15) +print(f"[021801] {'✅' if r.json().get('code')==0 else '❌'+r.json().get('msg','')}") +time.sleep(0.3) + +# ========== 032601: extend 3 short explanations ========== +fix_032601_expl = { + "first": { + 4: "本题考察用英语描述他人对你影响的能力。回答时说明谁、说过什么、以及你如何因此改变了对未来的想法。例如 'My teacher once told me that if you work hard for your dream, it will come true someday — that changed how I think about my future goals.'", + }, + "second": { + 3: "本题考察用英语对比和阐述的能力。回答时说明远大梦想给你方向,小目标帮你一步步前进,两者缺一不可。例如 'Big dreams give us a direction, while small goals help us take steps every day. Both are important.'", + 4: "本题考察用英语给予鼓励和建议的能力。回答时用安慰加建议的结构。例如 'I would tell my friend not to give up, and share a story about someone who almost failed before they finally succeeded.'", + } +} + +rec = records["032601"] +for bn in ["first", "second"]: + qs = rec["jd"].get(bn, {}).get("questionSet", []) + for qi, expl in fix_032601_expl.get(bn, {}).items(): + if qi < len(qs): + qs[qi]["explanation"] = expl + +new_jd = json.dumps(rec["jd"], ensure_ascii=False) +r = requests.put( + f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{TABLE}/records/{rec['rid']}", + headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"}, + json={"fields": {"jsonData": new_jd}}, timeout=15) +print(f"[032601] {'✅' if r.json().get('code')==0 else '❌'+r.json().get('msg','')}") +time.sleep(0.3) + +# ========== 032801: extend 6 short explanations ========== +fix_032801_expl = { + "first": { + 1: "本题考察用英语描述无法自控行为的能力。回答时用过去时说明一种曾让你停不下来的活动,以及花了多长时间。例如 'I once started playing a new video game, and I just couldn't stop until I finished all the levels three days later.'", + 4: "本题考察用英语描述解决问题的策略。回答时说明你会分解问题、找人求助或暂停休息。例如 'When I find a problem too hard, I break it into smaller parts first, and if I still can't solve it, I ask my teacher for help.'", + }, + "second": { + 0: "本题考察用英语描述并反思坏习惯的能力。回答时用 'I wish I could stop...' 并解释为什么这是坏习惯及你想如何改变。例如 'I wish I could stop checking my phone before bed because it makes it hard for me to fall asleep.'", + 2: "本题考察用英语描述长期学习投入的能力。回答时说明学了多久、进展如何、以及对结果的满意度。例如 'I've spent two years learning to play the piano. I'm proud of my progress, but I still have a lot to learn.'", + 3: "本题考察用英语描述压力感受和应对方式的能力。回答时说明你如何感知压力并如何缓解。例如 'When there is too much pressure, I feel tired and stressed. I usually take a short break, do some exercise, or talk to my parents about it.'", + 4: "本题考察用英语给予学弟学妹建议的能力。回答时用简单的建议句式,说明如何安排时间和寻求帮助。例如 'I would tell them to make a plan for their time, not wait until the last minute, and always ask for help when they need it.'", + } +} + +rec = records["032801"] +for bn in ["first", "second"]: + qs = rec["jd"].get(bn, {}).get("questionSet", []) + for qi, expl in fix_032801_expl.get(bn, {}).items(): + if qi < len(qs): + qs[qi]["explanation"] = expl + +new_jd = json.dumps(rec["jd"], ensure_ascii=False) +r = requests.put( + f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{TABLE}/records/{rec['rid']}", + headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"}, + json={"fields": {"jsonData": new_jd}}, timeout=15) +print(f"[032801] {'✅' if r.json().get('code')==0 else '❌'+r.json().get('msg','')}") + +# ========== Write-back audit results ========== +print("\n=== 回填审核结果 ===") +audit_map = { + "010199": "✅ 通过:first/second各6题,explanation/ability/answer完整", + "021601": "✅ 已修复:4题explanation已填写,second块已补(空占位)", + "021801": "✅ 已修复:first/second共10题ability已补全", + "022001": "✅ 通过:first/second各5题,explanation完整", + "022201": "✅ 通过:first/second各5题,含图片描述,explanation完整", + "032601": "✅ 已修复:3题explanation延展为完整解析,含句型模板+示例回答", + "032801": "✅ 已修复:6题explanation延展为完整解析,含句型模板+示例回答", +} + +for qsid, result in audit_map.items(): + if qsid not in records: continue + r = requests.put( + f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{TABLE}/records/{records[qsid]['rid']}", + headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"}, + json={"fields": {"审核结果": result}}, timeout=10) + print(f" {'✅' if r.json().get('code')==0 else '❌'} {qsid}") + time.sleep(0.3) + +print("\n全部完成") diff --git a/scripts/fix_second_and_pic.py b/scripts/fix_second_and_pic.py new file mode 100644 index 0000000..12ff7c1 --- /dev/null +++ b/scripts/fix_second_and_pic.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 +"""Task 1: Fix all structured empty seconds → "second": {} + Task 2: Add 图片描述 to 021601, 021801, 022001""" +import requests, json, time + +APP_TOKEN = "CMHSbUUjka3TrUsaxxEc297ongf" +APP_ID = "cli_a931175d41799cc7" +APP_SECRET = "Iw2vEfbjT6GtV0GhbxbZqfQ4nAPtbR14" + +def get_token(): + r = requests.post("https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal", + json={"app_id": APP_ID, "app_secret": APP_SECRET}, timeout=10) + return r.json()["tenant_access_token"] + +token = get_token() + +# List all tables in the bitable +r = requests.get(f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables", + headers={"Authorization": f"Bearer {token}"}, timeout=15) +tables = r.json().get("data", {}).get("items", []) +print(f"共 {len(tables)} 个表\n") + +# ====== Task 1: Scan all tables for structured empty second blocks ====== +fixes_second = [] # (table_id, table_name, qsid, rid) + +for t in tables: + tid = t["table_id"] + tname = t["name"] + r = requests.get(f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{tid}/records?page_size=200", + headers={"Authorization": f"Bearer {token}"}, timeout=15) + items = r.json().get("data", {}).get("items", []) + for it in items: + f = it["fields"] + qsid = f.get("题目集合 ID", "") or "" + jd_raw = f.get("jsonData", "") + if not jd_raw: continue + try: + jd = json.loads(jd_raw) if isinstance(jd_raw, str) else jd_raw + except: + continue + + second = jd.get("second", None) + if not second or not isinstance(second, dict): + continue + + # Check if structured empty: has type/questionSetID but empty questionSet + qs = second.get("questionSet", None) + if qs is not None and isinstance(qs, list) and len(qs) == 0: + # Has structure but no content → fix to {} + fixes_second.append((tid, tname, qsid, it["record_id"], second)) + +print(f"=== Task 1: 修复结构化空 second → {{}} ===") +print(f"发现 {len(fixes_second)} 条需要修复\n") + +for tid, tname, qsid, rid, old_second in fixes_second: + # Fetch fresh record to get current jsonData + r = requests.get(f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{tid}/records/{rid}", + headers={"Authorization": f"Bearer {token}"}, timeout=10) + rec = r.json()["data"]["record"] + jd = json.loads(rec["fields"]["jsonData"]) + + # Before + old_type = jd["second"].get("type", "?") + + # Replace with empty object + jd["second"] = {} + + new_jd = json.dumps(jd, ensure_ascii=False) + r = requests.put( + f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{tid}/records/{rid}", + headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"}, + json={"fields": {"jsonData": new_jd}}, timeout=15) + code = r.json().get("code") + print(f" {'✅' if code==0 else '❌'} [{tname}] {qsid}: second({old_type}) → {{}}") + time.sleep(0.3) + +# ====== Task 2: Add 图片描述 to 021601, 021801, 022001 ====== +print(f"\n=== Task 2: 补充图片描述 ===") + +pic_map = { + "021601": '[1-组图]:{"prompt_2":"黑白线条图:一个孩子在公园里焦急地四处张望,似乎在寻找丢失的玩具,背景有秋千、滑梯和大树,远处是公园的出口","prompt_3":"黑白线条图:一个小男孩正在爬滑梯的顶部,一个较大的孩子站在下面焦急地伸手示意他下来,表情紧张","prompt_4":"黑白线条图:公园入口处可以看到周围的商店、图书馆和警察亭,一位公园工作人员站在指示牌旁微笑着指路"}', + "021801": '[1-组图]:{"prompt_2":"黑白线条图:一个孩子躺在床上盖着被子,额头上放着湿毛巾,他的朋友坐在床边端来一杯水,床头柜上放着药瓶","prompt_3":"黑白线条图:两个同学坐在教室的课桌前,一个正在耐心地指着作业本给另一个讲解题目,旁边放着课本和铅笔","prompt_4":"黑白线条图:两个孩子并肩坐在草地上,一个孩子指着远方天空中的热气球,脸上带着鼓励和期待的表情","prompt_5":"黑白线条图:教室里,一个孩子帮忙扶住同学够高处的书本,另一个孩子在帮老师整理教具,气氛温馨互助"}', + "022001": '[1-组图]:{"prompt_2":"黑白线条图:学校走廊里,一个孩子脸上带着担忧的表情蹲在墙角,另一个孩子走过来蹲下身关切地询问","prompt_3":"黑白线条图:一位爷爷坐在沙发上拉着孩子的手认真地说话,表情温和而坚定,孩子脸上露出理解的神情","prompt_4":"黑白线条图:教室里,一个孩子站在讲台上自信地展示自己的画作,台下的同学们都在热烈鼓掌,孩子脸上带着自豪的笑容","prompt_5":"黑白线条图:书房里,一位家长蹲在孩子面前轻声解释着笔记本上的内容,墙上贴着学习计划表和各种鼓励标语"}', +} + +# Speaking-P2 table +SP2_TABLE = "tblGoWYBmVI0IrvQ" +r = requests.get(f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{SP2_TABLE}/records?page_size=200", + headers={"Authorization": f"Bearer {token}"}, timeout=15) + +for it in r.json()["data"]["items"]: + qsid = it["fields"].get("题目集合 ID", "") + if qsid not in pic_map: continue + rid = it["record_id"] + r = requests.put( + f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{SP2_TABLE}/records/{rid}", + headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"}, + json={"fields": {"图片描述": pic_map[qsid]}}, timeout=15) + code = r.json().get("code") + print(f" {'✅' if code==0 else '❌'} {qsid}: 图片描述已写入") + +# ====== Verify ====== +print(f"\n=== 验证 ===") +# Spot-check one from each table +for tid, tname, qsid, rid, _ in fixes_second[:3]: # first 3 + r = requests.get(f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{tid}/records/{rid}", + headers={"Authorization": f"Bearer {token}"}, timeout=10) + jd = json.loads(r.json()["data"]["record"]["fields"]["jsonData"]) + second = jd.get("second", None) + status = "✅ {}" if second == {} else f"❌ {json.dumps(second, ensure_ascii=False)[:50]}" + print(f" [{tname}] {qsid}: second={status}") + +# Verify pic +for qsid in pic_map: + r = requests.get(f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{SP2_TABLE}/records?page_size=200", + headers={"Authorization": f"Bearer {token}"}, timeout=15) + for it in r.json()["data"]["items"]: + if it["fields"].get("题目集合 ID") == qsid: + pic = it["fields"].get("图片描述", "") + print(f" {qsid}: 图片描述={'✅' if pic else '❌空'}") + break + time.sleep(0.2) + +print("\n全部完成") diff --git a/scripts/fix_writing_records.py b/scripts/fix_writing_records.py new file mode 100644 index 0000000..ce3ba48 --- /dev/null +++ b/scripts/fix_writing_records.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python3 +"""Fix 4 problematic writing records: 032701, 032901, 022101, 032801""" +import requests, json, time + +APP_TOKEN = "CMHSbUUjka3TrUsaxxEc297ongf" +APP_ID = "cli_a931175d41799cc7" +APP_SECRET = "Iw2vEfbjT6GtV0GhbxbZqfQ4nAPtbR14" +TABLE = "tblszuk1TeToofBF" + +def get_token(): + r = requests.post("https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal", + json={"app_id": APP_ID, "app_secret": APP_SECRET}, timeout=10) + return r.json()["tenant_access_token"] + +token = get_token() + +# Fetch current records that need fixing +targets = { + "032701": "recvjzXkYnsQ8r", + "032901": "recvjzXm5yEXBY", + "022101": "recvjzbjJpJO0D", + "032801": "recvjzXlxz4r3i", +} + +fixes = {} + +for qsid, rid in targets.items(): + r = requests.get( + f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{TABLE}/records/{rid}", + headers={"Authorization": f"Bearer {token}"}, timeout=10) + jd_str = r.json()["data"]["record"]["fields"]["jsonData"] + jd = json.loads(jd_str) + fixes[qsid] = {"rid": rid, "jd": jd} + time.sleep(0.2) + +# ============= PATCH 032701 ============= +fixes["032701"]["jd"]["first"]["answerSet"] = [0, 1, 2, 3, 4, 5, 6, 7] +fixes["032701"]["jd"]["first"]["ability"] = [ + "短消息写作|邮件/便条", + "衔接与连贯|连词使用", + "描述性写作|人物与地点" +] +fixes["032701"]["jd"]["first"]["explanation"] = ( + "按描述邮件的逻辑:称呼→描述人物→外貌对比→地点氛围→澄清发现者→补充人名→补充地点→署名。" + "考察They are/looks different from/a place that/it is not...who/someone named/too...to...等句型。" + "C级体现:多句型切换和描述性词汇的运用,需理解人物描写→地点过渡→细节补充的自然衔接。" +) + +# ============= PATCH 032901 ============= +fixes["032901"]["jd"]["first"]["answerSet"] = [0, 1, 2, 3, 4, 5, 6, 7] +fixes["032901"]["jd"]["first"]["ability"] = [ + "短消息写作|邮件/便条", + "衔接与连贯|连词使用", + "描述性写作|场景与情绪" +] +fixes["032901"]["jd"]["first"]["explanation"] = ( + "按聚会描述的逻辑:称呼→妹妹的动态→毫不在意→妈妈的心情→自己的限制→爸爸的状态→大家的穿着→署名。" + "考察loves to/doesn't care/will be so happy that/can't...because/seems in a...mood/is wearing等句型。" + "C级体现:多人物平行描述和情感状态的切换,需要理解热闹场景下的视线焦点转移。" +) + +# ============= PATCH 022101 ============= +fixes["022101"]["jd"]["first"]["answerSet"] = [0, 1, 2, 3, 4, 5, 6] +if "ability" not in fixes["022101"]["jd"]["first"] or not fixes["022101"]["jd"]["first"]["ability"]: + fixes["022101"]["jd"]["first"]["ability"] = [ + "短消息写作|邮件/便条", + "衔接与连贯|连词使用" + ] +# Check if ability is ['句型组织'] (wrong for writing) +if fixes["022101"]["jd"]["first"]["ability"] == ["句型组织"]: + fixes["022101"]["jd"]["first"]["ability"] = [ + "短消息写作|邮件/便条", + "衔接与连贯|连词使用" + ] + +# ============= PATCH 032801 ============= +fixes["032801"]["jd"]["first"]["answerSet"] = [0, 1, 2, 3, 4, 5, 6] +if fixes["032801"]["jd"]["first"]["ability"] == ["句型组织"]: + fixes["032801"]["jd"]["first"]["ability"] = [ + "短消息写作|邮件/便条", + "衔接与连贯|连词使用", + "问题解决|求助与建议" + ] + +# Write back all fixes +for qsid in ["032701", "032901", "022101", "032801"]: + data = fixes[qsid] + rid = data["rid"] + jd = data["jd"] + new_jd_str = json.dumps(jd, ensure_ascii=False) + + r = requests.put( + f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{TABLE}/records/{rid}", + headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"}, + json={"fields": {"jsonData": new_jd_str}}, timeout=15) + code = r.json().get("code") + if code == 0: + print(f"[✅] {qsid} 修复成功") + else: + print(f"[❌] {qsid}: {r.json().get('msg')}") + time.sleep(0.3) + +# Verify +print("\n=== 验证 ===") +for qsid in ["032701", "032901", "022101", "032801"]: + rid = targets[qsid] + r = requests.get( + f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{TABLE}/records/{rid}", + headers={"Authorization": f"Bearer {token}"}, timeout=10) + jd = json.loads(r.json()["data"]["record"]["fields"]["jsonData"]) + first = jd["first"] + ans = first["answerSet"] + ab = first["ability"] + expl = first.get("explanation", "") + opt = first.get("optionSetList", []) + + issues = [] + if len(ans) != len(opt): + issues.append(f"🔴 ans({len(ans)})≠opt({len(opt)})") + if not ab: + issues.append("🔴 ability为空") + if len(expl) < 20: + issues.append(f"🔴 expl过短({len(expl)}字)") + + status = "✅" if not issues else " ".join(issues) + print(f" {qsid}: {status} | ans={ans} | ab={ab}") + time.sleep(0.2) + +print("\n全部修复完成。") diff --git a/scripts/gen_batch3.py b/scripts/gen_batch3.py new file mode 100644 index 0000000..58358ad --- /dev/null +++ b/scripts/gen_batch3.py @@ -0,0 +1,826 @@ +#!/usr/bin/env python3 +"""Generate 10 new writing/speaking C-level records: 032501-032901""" +import requests, json, time + +APP_TOKEN = "CMHSbUUjka3TrUsaxxEc297ongf" +APP_ID = "cli_a931175d41799cc7" +APP_SECRET = "Iw2vEfbjT6GtV0GhbxbZqfQ4nAPtbR14" +BASE = "https://open.feishu.cn/open-apis/bitable/v1" + +def get_token(): + r = requests.post("https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal", + json={"app_id": APP_ID, "app_secret": APP_SECRET}, timeout=10) + return r.json()["tenant_access_token"] + +def create(token, tid, fields): + r = requests.post(f"{BASE}/apps/{APP_TOKEN}/tables/{tid}/records", + headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"}, + json={"fields": fields}, timeout=15) + return r.json() + +# ============================================================ +# WRITING 032501 - Expressing doubt + encouragement +# ============================================================ +W_032501 = { + "first": { + "category": "writing", "type": "writing_email", + "questionSetID": "032501", + "textDesc": "Your friend Ben is worried about the school concert and wants to give up. Write an email to encourage him and share your own feelings.
Put these sentences in order to make a kind and encouraging email.", + "optionSetList": [ + "Dear Ben,", + "I don't believe you should give up so quickly.", + "I feel a bit nervous about the school concert too.", + "It sounds like you have practised a lot already.", + "I am not very good at playing the piano either, but I still try every day.", + "Yours truly, Sam" + ], + "answerSet": [0, 1, 2, 3, 4, 5], + "ability": ["短消息写作|邮件/便条", "衔接与连贯|连词使用", "情感表达|鼓励与共情"], + "explanation": "按鼓励邮件的逻辑:称呼→表达不认同→分享同感建立共情→肯定对方努力→自谦示范坚持→署名。考察I don't believe/I feel a bit/It sounds like/I am not very good at等句型的组织,以及鼓励信的情感递进逻辑。C级难度体现在需理解共情-肯定-示范三层递进。" + }, + "second": {} +} +W_032501_T1 = """【题目描述】 +Your friend Ben is worried about the school concert and wants to give up. Write an email to encourage him and share your own feelings. +Put these sentences in order to make a kind and encouraging email. + +【段落列表】 +A. Dear Ben, +B. I don't believe you should give up so quickly. +C. I feel a bit nervous about the school concert too. +D. It sounds like you have practised a lot already. +E. I am not very good at playing the piano either, but I still try every day. +F. Yours truly, Sam""" + +# ============================================================ +# SPEAKING-P1 032501 - Changes & perspectives +# ============================================================ +S1_032501 = { + "first": { + "category": "speaking", "type": "speaking_qa", + "asrPrompt": "however,sooner,later,depending,used to,change,mind,weather,plan,hobby,habit,past", + "questionSetID": "032501", + "textDesc": "请回答以下问题。用清晰、完整的英文语句进行回答,尝试使用 However、sooner or later、Depending on、used to 等连接结构。", + "questionSet": [ + { + "content": "Tell us about a time you changed your mind about something important. What made you change?", + "ability": ["表达转变", "经历描述"], + "explanation": "用 'I used to think that... However, I changed my mind when...' 的结构来描述。例如 'I used to think maths was boring. However, after our class built a model bridge, I changed my mind because I saw how useful maths can be.'" + }, + { + "content": "What is something that you believe will happen sooner or later? Why do you think so?", + "ability": ["表达预测", "表达观点"], + "explanation": "用 'I believe that... sooner or later.' 开头表达你的预测,并解释原因。例如 'I believe people will stop using plastic bags sooner or later, because we are all learning how much it hurts the ocean animals.'" + }, + { + "content": "How do your weekend plans change depending on the weather?", + "ability": ["条件表达", "经历描述"], + "explanation": "用 'Depending on...' 来描述天气如何影响你的计划。例如 'Depending on the weather, I either go cycling in the park or stay home and read a book. If it rains, I sometimes bake cookies with my mum.'" + }, + { + "content": "What is something you or your friends used to do but no longer do? Why did you stop?", + "ability": ["过去对比", "经历描述"], + "explanation": "用 'They used to...' 描述过去的习惯,并解释为什么改变了。例如 'We used to play in the sandpit every day after school, but we are too old for it now. Instead, we play football on the field.'" + } + ] + }, + "second": { + "category": "speaking", "type": "speaking_qa", + "asrPrompt": "however,sooner,later,depending,used to,opinion,technology,environment,future,childhood,memory", + "questionSetID": "032501", + "textDesc": "请回答以下问题。继续尝试使用 However、sooner or later、Depending on、used to 等表达方式。", + "questionSet": [ + { + "content": "What opinion did you once hold that later turned out to be wrong?", + "ability": ["表达转变", "经历描述"], + "explanation": "用 'I used to believe that... However,...' 分享一次错误的看法及如何被纠正的经历。" + }, + { + "content": "What technology do you think will change our lives sooner or later?", + "ability": ["表达预测", "表达观点"], + "explanation": "用 'I think that... sooner or later.' 来预测某项技术的影响。例如人工智能、太空旅行或环保科技。" + }, + { + "content": "How do your food choices change depending on the season?", + "ability": ["条件表达", "经历描述"], + "explanation": "描述你的饮食如何随季节变化。例如 'Depending on the season, I enjoy different fruits. In summer I love watermelons, but in winter I prefer hot soup.'" + }, + { + "content": "What did your parents tell you they used to do when they were your age?", + "ability": ["过去对比", "经历描述"], + "explanation": "分享你父母小时候的故事。例如 'My dad said he used to walk two kilometres to school every morning, but I only need five minutes.'" + } + ] + } +} +S1_032501_T1 = """【题目描述】 +请回答以下问题。用清晰、完整的英文语句进行回答,尝试使用 However、sooner or later、Depending on、used to 等连接结构。 + +【热词】 +however,sooner,later,depending,used,change,mind,weather,plan,hobby,habit + +【题目】 +1. Tell us about a time you changed your mind about something important. What made you change? +- 解析:用 'I used to think that... However, I changed my mind when...' 的结构来描述。 + +2. What is something that you believe will happen sooner or later? Why do you think so? +- 解析:用 'I believe that... sooner or later.' 开头表达你的预测,并解释原因。 + +3. How do your weekend plans change depending on the weather? +- 解析:用 'Depending on...' 来描述天气如何影响你的计划。 + +4. What is something you or your friends used to do but no longer do? Why did you stop? +- 解析:用 'They used to...' 描述过去的习惯,并解释为什么改变了。""" +S1_032501_T2 = """【题目描述】 +请回答以下问题。继续尝试使用 However、sooner or later、Depending on、used to 等表达方式。 + +【热词】 +however,sooner,later,depending,used,opinion,technology,future,childhood,memory + +【题目】 +1. What opinion did you once hold that later turned out to be wrong? +- 解析:用 'I used to believe that... However,...' 分享一次错误的看法及如何被纠正的经历。 + +2. What technology do you think will change our lives sooner or later? +- 解析:用 'I think that... sooner or later.' 来预测某项技术的影响。 + +3. How do your food choices change depending on the season? +- 解析:描述你的饮食如何随季节变化。 + +4. What did your parents tell you they used to do when they were your age? +- 解析:分享你父母小时候的故事。""" + +# ============================================================ +# WRITING 032601 - Expressing emotions & requests +# ============================================================ +W_032601 = { + "first": { + "category": "writing", "type": "writing_email", + "questionSetID": "032601", + "textDesc": "You just came back from a wonderful school trip to the science museum. Write a thank-you email to your teacher, Mr. Brown.
Put these sentences in order to make a polite and expressive email.", + "optionSetList": [ + "Dear Mr. Brown,", + "I'd like to thank you for the wonderful trip to the science museum.", + "What a fantastic experience it was!", + "The planet show was so amazing that I couldn't stop talking about it.", + "Excuse me, could you please send us the photos you took?", + "Warm regards, Lucy" + ], + "answerSet": [0, 1, 2, 3, 4, 5], + "ability": ["短消息写作|邮件/便条", "衔接与连贯|连词使用", "情感表达|感谢与赞美"], + "explanation": "按感谢信的逻辑:称呼→表达感谢→感叹赞美→具体描述→礼貌请求→署名。考察I'd like to/What a...!/so...that.../Excuse me,...?等感叹与礼貌请求句型。C级难度体现在感叹句和程度状语从句的运用。" + }, + "second": {} +} +W_032601_T1 = """【题目描述】 +You just came back from a wonderful school trip to the science museum. Write a thank-you email to your teacher, Mr. Brown. +Put these sentences in order to make a polite and expressive email. + +【段落列表】 +A. Dear Mr. Brown, +B. I'd like to thank you for the wonderful trip to the science museum. +C. What a fantastic experience it was! +D. The planet show was so amazing that I couldn't stop talking about it. +E. Excuse me, could you please send us the photos you took? +F. Warm regards, Lucy""" + +# ============================================================ +# SPEAKING-P2 032601 - Future dreams +# ============================================================ +S2_032601 = { + "first": { + "category": "speaking", "type": "speaking_topic", + "asrPrompt": "someday,dream,future,career,goal,choice,astronaut,teacher,doctor,artist,important,family,friends,study", + "questionSetID": "032601", + "textDesc": "Here are some pictures about dreams and future plans. What do you think about these situations?", + "imageDesc": "Four images show children thinking about and planning for the future: 1st: A child stands by the window looking at the starry night sky, with a dreamy expression. 2nd: A child looks at scattered art supplies and books on a desk with a confused expression. 3rd: Children hold up posters of their future career dreams in the classroom. 4th: A teacher writes the word 'Dream' on the blackboard while students raise their hands eagerly.", + "textImage": "032601-00.png", + "questionSet": [ + { + "content": "What profession do you dream of doing someday? Why?", + "ability": ["句型组织", "表达愿望"], + "explanation": "'I will become a doctor someday because I want to help sick people get better.' 或者用 'I don't know what to choose yet, but I would like to do something creative.' 来表达你目前的梦想或困惑。" + }, + { + "content": "Have you ever not known what to choose or decide? Tell us about it.", + "ability": ["句型组织", "经历描述"], + "explanation": "'I don't know what to do when it's time to choose between art class and music class. Both sound fun!' 描述一次你在选择上犹豫的经历。" + }, + { + "content": "What kind of life would you like to have when you grow up?", + "ability": ["句型组织", "表达愿望"], + "explanation": "'I would like to live near the sea and have a small garden where I can grow flowers.' 描绘你理想中长大后的生活图景。" + }, + { + "content": "What do you think is important to achieve your dreams?", + "ability": ["句型组织", "表达观点"], + "explanation": "'Hard work is important to achieve any dream. You also need to believe in yourself and never give up, even when things get difficult.' 分享你认为实现梦想所需的重要品质。" + }, + { + "content": "Has anyone ever told you something that changed how you think about your future?", + "ability": ["句型组织", "经历描述"], + "explanation": "分享一个影响你未来想法的人或话。例如 'My grandpa once told me that it doesn't matter what job you do, as long as you do it with all your heart.'" + } + ] + }, + "second": { + "category": "speaking", "type": "speaking_topic", + "asrPrompt": "someday,dream,future,career,goal,choice,study,hard,work,believe,important,happy,help", + "questionSetID": "032601", + "textDesc": "Now look at these pictures again. Think more deeply about what your dreams mean to you and the people around you.", + "imageDesc": "Same images as above: children thinking about and planning for the future.", + "textImage": "032601-01.png", + "questionSet": [ + { + "content": "What will you do someday to make your parents proud?", + "ability": ["句型组织", "表达愿望"], + "explanation": "'I will study hard someday and go to a good university. I know that would make my parents very proud.' 分享你打算如何让家人骄傲。" + }, + { + "content": "What do you still not know about your future that makes you curious?", + "ability": ["句型组织", "表达困惑"], + "explanation": "'I don't know what country I will live in or what kind of friends I will have, and that makes me very curious about the future.' 分享你对未来的不确定性。" + }, + { + "content": "What skill would you like to learn before you become an adult?", + "ability": ["句型组织", "表达愿望"], + "explanation": "'I would like to learn how to play the guitar and how to speak another language before I turn eighteen.' 说说你想在成年之前掌握的技能。" + }, + { + "content": "Why is it important to have both big dreams and small goals?", + "ability": ["句型组织", "表达观点"], + "explanation": "'Big dreams give us direction, but small goals help us move step by step. Having a big dream is important to stay excited, while small goals show us that we are making progress.'" + }, + { + "content": "What advice would you give to a friend who has lost hope in their dream?", + "ability": ["句型组织", "表达建议"], + "explanation": "给朋友一些鼓励。例如 'I would tell my friend not to give up. Every successful person failed many times. If you keep trying and learning, your dream will come true someday.'" + } + ] + } +} +S2_032601_T1 = """【题目描述】 +Here are some pictures about dreams and future plans. What do you think about these situations? + +【热词】 +someday,dream,future,career,goal,choice,astronaut,teacher,doctor,artist,important + +【图片描述】 +Four images show children thinking about and planning for the future: 1st: A child stands by the window looking at the starry night sky, with a dreamy expression. 2nd: A child looks at scattered art supplies and books on a desk with a confused expression. 3rd: Children hold up posters of their future career dreams in the classroom. 4th: A teacher writes the word 'Dream' on the blackboard while students raise their hands eagerly. + +【题目】 +1. What profession do you dream of doing someday? Why? +- 解析:'I will become a doctor someday because I want to help sick people get better.' 用 'I will... someday.' 表达你的职业梦想。 + +2. Have you ever not known what to choose or decide? Tell us about it. +- 解析:'I don't know what to do when it's time to choose between art class and music class.' 描述一次选择困难的经历。 + +3. What kind of life would you like to have when you grow up? +- 解析:'I would like to live near the sea and have a small garden where I can grow flowers.' 描绘理想生活。 + +4. What do you think is important to achieve your dreams? +- 解析:'Hard work is important to achieve any dream. You also need to believe in yourself.' 分享实现梦想所需品质。 + +5. Has anyone ever told you something that changed how you think about your future? +- 解析:分享一个影响你未来想法的人或话。""" +S2_032601_T2 = """【题目描述】 +Now look at these pictures again. Think more deeply about what your dreams mean to you and the people around you. + +【热词】 +someday,dream,future,career,goal,choice,study,hard,work,believe,important,happy + +【图片描述】 +Same images as above: children thinking about and planning for the future. + +【题目】 +1. What will you do someday to make your parents proud? +- 解析:'I will study hard someday and go to a good university.' 分享你打算如何让家人骄傲。 + +2. What do you still not know about your future that makes you curious? +- 解析:'I don't know what country I will live in or what kind of friends I will have.' 分享对未来的好奇。 + +3. What skill would you like to learn before you become an adult? +- 解析:'I would like to learn how to play the guitar and how to speak another language.' 说说你想学会的技能。 + +4. Why is it important to have both big dreams and small goals? +- 解析:解释远大梦想和短期目标各自的重要性。 + +5. What advice would you give to a friend who has lost hope in their dream? +- 解析:给朋友一些鼓励坚持下去的建议。""" +S2_032601_PIC = '[1-组图]:{"prompt_2":"黑白线条图:一个孩子站在窗边仰望星空,脸上带着憧憬的神情,窗外可见月亮和几颗星星","prompt_3":"黑白线条图:一个孩子坐在桌前,桌上散落着画纸和书本,表情困惑而迷茫","prompt_4":"黑白线条图:教室内孩子们举着自己画的未来职业海报,有宇航员、医生、老师等","prompt_5":"黑白线条图:一位老师在黑板上写着梦想两个字,学生们纷纷举手想要分享"}' + +# ============================================================ +# WRITING 032701 - Describing people & places +# ============================================================ +W_032701 = { + "first": { + "category": "writing", "type": "writing_email", + "questionSetID": "032701", + "textDesc": "You visited a wonderful little café that your cousin found. Write an email to your friend Anna to describe the place, the people you met, and your experience.
Put these sentences in order to make a descriptive email.", + "optionSetList": [ + "Dear Anna,", + "They are friendly, kind and always ready to help every customer.", + "She looks different from the photo she sent me last year.", + "This is a place that makes everyone feel at home.", + "It is not me who found this wonderful café — my cousin did.", + "Someone named Lily showed us around the old library nearby.", + "The hill near the café is too steep to climb without proper shoes.", + "Kind regards, Emma" + ], + "answerSet": [0, 1, 2, 3, 4, 5, 6, 7], + "ability": ["短消息写作|邮件/便条", "衔接与连贯|连词使用", "描述性写作|人物与地点"], + "explanation": "按描述邮件的逻辑:称呼→描述人物→外貌对比→地点氛围→澄清发现者→补充人名→补充地点→署名。考察They are/looks different from/a place that/it is not...who/someone named/too...to...等多种句型。C级体现在多句型切换和描述性词汇的运用。" + }, + "second": {} +} +W_032701_T1 = """【题目描述】 +You visited a wonderful little café that your cousin found. Write an email to your friend Anna to describe the place, the people you met, and your experience. +Put these sentences in order to make a descriptive email. + +【段落列表】 +A. Dear Anna, +B. They are friendly, kind and always ready to help every customer. +C. She looks different from the photo she sent me last year. +D. This is a place that makes everyone feel at home. +E. It is not me who found this wonderful café — my cousin did. +F. Someone named Lily showed us around the old library nearby. +G. The hill near the café is too steep to climb without proper shoes. +H. Kind regards, Emma""" + +# ============================================================ +# SPEAKING-P1 032701 - Curiosity & changing minds +# ============================================================ +S1_032701 = { + "first": { + "category": "speaking", "type": "speaking_qa", + "asrPrompt": "couldn't,find,lost,search,understand,explain,curious,about,maybe,see,if,change,mind,now", + "questionSetID": "032701", + "textDesc": "请回答以下问题。用清晰、完整的英文语句进行回答,尝试使用 couldn't find、I understand that、curious about、Maybe we can... to see if、changing my mind 等表达。", + "questionSet": [ + { + "content": "Tell us about a time you couldn't find something important. How did you feel and what did you do?", + "ability": ["经历描述", "情感表达"], + "explanation": "'I couldn't find my homework on the morning it was due. I felt so worried! I searched everywhere and finally found it under my bed.' 分享一次找不到东西的经历及你的感受。" + }, + { + "content": "When someone gives you an explanation for something, how do you show that you understand?", + "ability": ["回应表达", "表达理解"], + "explanation": "'I understand that it takes time to learn a new skill. Thank you for explaining it so clearly.' 展示你如何礼貌地回应他人的解释。" + }, + { + "content": "What is something you are really curious about right now? Why?", + "ability": ["表达好奇", "表达观点"], + "explanation": "'I'm curious about how birds know where to fly in winter. It seems like a miracle that they can travel so far and always find their way.' 分享一个让你好奇的事物。" + }, + { + "content": "Have you ever tried an experiment or test to see if something would work? What happened?", + "ability": ["经历描述", "因果表达"], + "explanation": "'Maybe we can put the plant in a dark cupboard to see if it still grows without sunlight. We tried it and after three days, the leaves turned yellow.' 分享一次测试或实验经历。" + }, + { + "content": "Tell us about a time you changed your mind about something. What made you change?", + "ability": ["表达转变", "经历描述"], + "explanation": "'I'm now changing my mind about spicy food. I used to hate it, but after trying my mum's mild curry last week, I discovered it can be really delicious.' 描述一次想法转变。" + } + ] + }, + "second": { + "category": "speaking", "type": "speaking_qa", + "asrPrompt": "lost,search,understand,explain,curious,test,try,change,mind,grow,learn,wonder", + "questionSetID": "032701", + "textDesc": "请回答以下问题。继续使用本课的核心句型表达你的想法和经历。", + "questionSet": [ + { + "content": "What would you do if you couldn't find your way in a new place?", + "ability": ["经历描述", "解决问题"], + "explanation": "描述在陌生地方迷路时的应对方式。例如 'I couldn't find my way back to the hotel, so I asked a shopkeeper to help me.'" + }, + { + "content": "How do you feel when someone explains something you already know?", + "ability": ["回应表达", "表达理解"], + "explanation": "分享被重复解释时的感受及如何得体回应。例如 'I understand that you want to help, but I already learned this last term.'" + }, + { + "content": "What subject at school makes you the most curious? What do you want to learn more about?", + "ability": ["表达好奇", "表达观点"], + "explanation": "说说让你最好奇的科目。例如 'I'm curious about history because I want to know how people lived hundreds of years ago.'" + }, + { + "content": "Tell us about a time you changed your daily routine. What was different and why?", + "ability": ["表达转变", "经历描述"], + "explanation": "'I'm now changing my mind about waking up late. Last month I started getting up at six to read before school, and I feel so much better.'" + } + ] + } +} +S1_032701_T1 = """【题目描述】 +请回答以下问题。用清晰、完整的英文语句进行回答,尝试使用 couldn't find、I understand that、curious about、Maybe we can... to see if、changing my mind 等表达。 + +【热词】 +couldn't,find,lost,search,understand,curious,about,maybe,see,if,change,mind + +【题目】 +1. Tell us about a time you couldn't find something important. How did you feel and what did you do? +- 解析:'I couldn't find my homework on the morning it was due. I felt so worried!' 分享一次找不到东西的经历。 + +2. When someone gives you an explanation for something, how do you show that you understand? +- 解析:'I understand that it takes time to learn a new skill.' 展示你如何礼貌地回应解释。 + +3. What is something you are really curious about right now? Why? +- 解析:'I'm curious about how birds know where to fly in winter.' 分享让你好奇的事物。 + +4. Have you ever tried an experiment or test to see if something would work? What happened? +- 解析:'Maybe we can put the plant in a dark cupboard to see if it still grows.' 分享测试经历。 + +5. Tell us about a time you changed your mind about something. What made you change? +- 解析:'I'm now changing my mind about spicy food. I used to hate it, but...' 描述想法转变。""" +S1_032701_T2 = """【题目描述】 +请回答以下问题。继续使用本课的核心句型表达你的想法和经历。 + +【热词】 +lost,search,understand,explain,curious,test,try,change,mind,grow,learn + +【题目】 +1. What would you do if you couldn't find your way in a new place? +- 解析:描述在陌生地方迷路时的应对方式。 + +2. How do you feel when someone explains something you already know? +- 解析:分享被重复解释时的感受及如何得体回应。 + +3. What subject at school makes you the most curious? What do you want to learn more about? +- 解析:说说让你最好奇的科目及原因。 + +4. Tell us about a time you changed your daily routine. What was different and why? +- 解析:分享一次改变日常习惯的经历和感受。""" + +# ============================================================ +# WRITING 032801 - Problem-solving & asking for help +# ============================================================ +W_032801 = { + "first": { + "category": "writing", "type": "writing_email", + "questionSetID": "032801", + "textDesc": "Your group's science project has suddenly stopped working and you need the teacher's help. Write an email to Miss Green explaining the problem and asking for advice.
Put these sentences in order to make a clear problem-solving email.", + "optionSetList": [ + "Dear Miss Green,", + "We need help because our science project has suddenly stopped working.", + "What are we going to do now without the main part?", + "Maybe we can borrow some tools from the art room to fix it.", + "I've had this old robot kit since my last birthday, but I've never opened it before.", + "It is a great idea that we thought of at the very last minute.", + "Gratefully, Tom" + ], + "answerSet": [0, 1, 2, 3, 4, 5, 6], + "ability": ["短消息写作|邮件/便条", "衔接与连贯|连词使用", "问题解决|求助与建议"], + "explanation": "按求助邮件的逻辑:称呼→陈述问题→表达困惑→提出初步方案→提供备选资源→补充背景→署名。考察We need help because/What are we going to/Maybe we can/I've had... since.../It is a...idea that...等句型。C级体现在问题诊断-方案提议-资源调用的多层次思维组织。" + }, + "second": {} +} +W_032801_T1 = """【题目描述】 +Your group's science project has suddenly stopped working and you need the teacher's help. Write an email to Miss Green explaining the problem and asking for advice. +Put these sentences in order to make a clear problem-solving email. + +【段落列表】 +A. Dear Miss Green, +B. We need help because our science project has suddenly stopped working. +C. What are we going to do now without the main part? +D. Maybe we can borrow some tools from the art room to fix it. +E. I've had this old robot kit since my last birthday, but I've never opened it before. +F. It is a great idea that we thought of at the very last minute. +G. Gratefully, Tom""" + +# ============================================================ +# SPEAKING-P2 032801 - Challenges & habits +# ============================================================ +S2_032801 = { + "first": { + "category": "speaking", "type": "speaking_topic", + "asrPrompt": "too,much,stop,started,right,after,spent,a lot,time,homework,game,test,challenge,habit", + "questionSetID": "032801", + "textDesc": "Here are some pictures about challenges and habits. What do you think about these situations?", + "imageDesc": "Four images show children dealing with challenges and daily habits: 1st: A child sits at a desk piled with books and homework, looking anxious. 2nd: A child plays video games while the clock shows it is very late at night. 3rd: A child holds a test paper with a failing mark, wiping tears from the eyes. 4th: A group of children sit together in the library discussing a study plan.", + "textImage": "032801-00.png", + "questionSet": [ + { + "content": "Have you ever felt that your homework was just too much? What did you do?", + "ability": ["句型组织", "经历描述"], + "explanation": "'This is too much homework for one night!' 描述一次你觉得任务太多的时候,以及你是怎么处理的。例如 'I felt stressed, so I made a list and finished the hardest subjects first.'" + }, + { + "content": "Have you ever done something that you just couldn't stop doing? What was it?", + "ability": ["句型组织", "经历描述"], + "explanation": "'Last weekend, I started reading a mystery book and I just couldn't stop until I finished it at midnight.' 分享一次你停不下来的经历。" + }, + { + "content": "When did a difficult situation start for you? What happened right after?", + "ability": ["句型组织", "经历描述"], + "explanation": "'It started right after I joined the advanced maths class. Everything became harder so quickly.' 描述困难开始的时刻及之后发生的事。" + }, + { + "content": "What have you and your friends spent a lot of time doing together? Was it worth it?", + "ability": ["句型组织", "经历描述"], + "explanation": "'We've spent a lot of time practising for the school play. It was tiring but completely worth it when we saw the audience clapping.' 分享投入大量时间的经历及收获。" + }, + { + "content": "What do you do when you find a problem too difficult to solve alone?", + "ability": ["句型组织", "解决问题"], + "explanation": "分享面对困难时的策略。例如 'When a problem is too difficult, I first try to break it into smaller parts. If I still can't do it, I ask my older sister or my teacher for help.'" + } + ] + }, + "second": { + "category": "speaking", "type": "speaking_topic", + "asrPrompt": "too,much,stop,started,after,spent,time,change,habit,plan,help,future,improve", + "questionSetID": "032801", + "textDesc": "Now look at these pictures again. Think about how to change bad habits and build better ones.", + "imageDesc": "Same images as above: children dealing with challenges and daily habits.", + "textImage": "032801-01.png", + "questionSet": [ + { + "content": "What bad habit do you wish you could stop doing? Why?", + "ability": ["句型组织", "表达反思"], + "explanation": "'I just couldn't stop checking my phone before bed. It made me sleep very late and feel tired the next morning.'" + }, + { + "content": "What good habit did you start right after learning something new?", + "ability": ["句型组织", "经历描述"], + "explanation": "'It started right after our health class. I began drinking more water every day instead of juice.'" + }, + { + "content": "What is something you've spent a lot of time learning? Are you proud of it?", + "ability": ["句型组织", "经历描述"], + "explanation": "'We've spent a lot of time learning to play the guitar as a band. We are not perfect yet, but we are getting better.'" + }, + { + "content": "How do you feel when there is too much pressure from school or family?", + "ability": ["句型组织", "情感表达"], + "explanation": "分享面对压力时的感受和应对方式。例如 'When there is too much pressure, I talk to my mum about it and she always helps me feel calmer.'" + }, + { + "content": "What advice would you give to a younger student about managing time?", + "ability": ["句型组织", "表达建议"], + "explanation": "给学弟学妹一些时间管理建议。例如 'Don't do all your homework at the last minute. Spend a little time each day, and you will feel much less stressed.'" + } + ] + } +} +S2_032801_T1 = """【题目描述】 +Here are some pictures about challenges and habits. What do you think about these situations? + +【热词】 +too,much,stop,started,right,after,spent,lot,time,homework,game,test,challenge + +【图片描述】 +Four images show children dealing with challenges and daily habits: 1st: A child sits at a desk piled with books and homework, looking anxious. 2nd: A child plays video games while the clock shows it is very late at night. 3rd: A child holds a test paper with a failing mark, wiping tears from the eyes. 4th: A group of children sit together in the library discussing a study plan. + +【题目】 +1. Have you ever felt that your homework was just too much? What did you do? +- 解析:'This is too much homework for one night!' 描述一次你觉得任务太多的时候及应对方式。 + +2. Have you ever done something that you just couldn't stop doing? What was it? +- 解析:分享一次你停不下来的经历。 + +3. When did a difficult situation start for you? What happened right after? +- 解析:'It started right after I joined the advanced maths class.' 描述困难开始的时刻。 + +4. What have you and your friends spent a lot of time doing together? Was it worth it? +- 解析:'We've spent a lot of time practising for the school play.' 分享投入大量时间的经历。 + +5. What do you do when you find a problem too difficult to solve alone? +- 解析:分享面对困难时的策略。""" +S2_032801_T2 = """【题目描述】 +Now look at these pictures again. Think about how to change bad habits and build better ones. + +【热词】 +too,much,stop,started,after,spent,time,change,habit,plan,help,future + +【图片描述】 +Same images as above: children dealing with challenges and daily habits. + +【题目】 +1. What bad habit do you wish you could stop doing? Why? +- 解析:分享你想改变的一个坏习惯。 + +2. What good habit did you start right after learning something new? +- 解析:'It started right after our health class. I began drinking more water.' + +3. What is something you've spent a lot of time learning? Are you proud of it? +- 解析:分享你花了很多时间学习的事情。 + +4. How do you feel when there is too much pressure from school or family? +- 解析:分享面对压力时的感受和应对方式。 + +5. What advice would you give to a younger student about managing time? +- 解析:给学弟学妹一些时间管理建议。""" +S2_032801_PIC = '[1-组图]:{"prompt_2":"黑白线条图:一个孩子坐在堆满书本和作业的桌前,双手抱头表情焦虑","prompt_3":"黑白线条图:一个孩子坐在沙发上拿着游戏机玩,墙上的时钟指向深夜十一点","prompt_4":"黑白线条图:一个孩子拿着不及格的试卷,脸上挂着泪水,表情沮丧","prompt_5":"黑白线条图:一组孩子围坐在图书馆的桌旁一起讨论学习计划"}' + +# ============================================================ +# WRITING 032901 - Celebration & emotions +# ============================================================ +W_032901 = { + "first": { + "category": "writing", "type": "writing_email", + "questionSetID": "032901", + "textDesc": "You are at your mum's birthday party and having a wonderful time. Write an email to your friend Sophie to describe the party, the people, and the happy mood.
Put these sentences in order to make a warm and descriptive email.", + "optionSetList": [ + "Dear Sophie,", + "My little sister loves to dance whenever music starts playing.", + "She doesn't care if anyone is watching her spin around.", + "Mum will be so happy that everyone she loves came to her party.", + "I can't stay out too late because I have a maths test tomorrow morning.", + "Dad seems in a wonderful mood tonight.", + "Everyone is wearing their best party clothes.", + "With love, Jessica" + ], + "answerSet": [0, 1, 2, 3, 4, 5, 6, 7], + "ability": ["短消息写作|邮件/便条", "衔接与连贯|连词使用", "描述性写作|场景与情绪"], + "explanation": "按聚会描述的逻辑:称呼→妹妹的动态→不care→妈妈的心情→自己的限制→爸爸的状态→大家穿着→署名。考察loves to/doesn't care/will be so happy that/can't...because/seems in a...mood/is wearing等句型。C级体现在多人物平行描述和情感状态的切换。" + }, + "second": {} +} +W_032901_T1 = """【题目描述】 +You are at your mum's birthday party and having a wonderful time. Write an email to your friend Sophie to describe the party, the people, and the happy mood. +Put these sentences in order to make a warm and descriptive email. + +【段落列表】 +A. Dear Sophie, +B. My little sister loves to dance whenever music starts playing. +C. She doesn't care if anyone is watching her spin around. +D. Mum will be so happy that everyone she loves came to her party. +E. I can't stay out too late because I have a maths test tomorrow morning. +F. Dad seems in a wonderful mood tonight. +G. Everyone is wearing their best party clothes. +H. With love, Jessica""" + +# ============================================================ +# SPEAKING-P1 032901 - Teamwork & communication +# ============================================================ +S1_032901 = { + "first": { + "category": "speaking", "type": "speaking_qa", + "asrPrompt": "let's,suggest,activity,hope,can,achieve,talk,to,solve,problem,use,method,tool,team", + "questionSetID": "032901", + "textDesc": "请回答以下问题。用清晰、完整的英文语句进行回答,尝试使用 Let's、We hope that...can...、try to talk to、can use 等表达。", + "questionSet": [ + { + "content": "How do you suggest a fun activity to your friends? What do you say?", + "ability": ["表达建议", "邀请表达"], + "explanation": "'Let's go to the park after school and fly our kites!' 展示你如何邀请朋友一起做有趣的事。也可以用 'Why don't we...' 或 'How about...' 来使邀请更加自然。" + }, + { + "content": "What do you hope your class or team can achieve together this year?", + "ability": ["表达期望", "表达观点"], + "explanation": "'We hope that our class can win the school sports day this year. We have been practising every morning and getting stronger together.' 分享你对班级或团队的期望。" + }, + { + "content": "When there is a problem in your group, how do you try to talk to your classmates about it?", + "ability": ["协商表达", "解决问题"], + "explanation": "'We try to talk to each other calmly and listen to everyone's ideas before making a decision.' 描述你们如何通过沟通解决问题。" + }, + { + "content": "What tools or methods can you use to work better as a team?", + "ability": ["表达建议", "表达观点"], + "explanation": "'We can use a shared calendar to plan our tasks and set clear deadlines. We can also use group chats to stay in touch after school.' 分享提高团队效率的工具或方法。" + } + ] + }, + "second": { + "category": "speaking", "type": "speaking_qa", + "asrPrompt": "let's,organise,event,hope,can,improve,talk,solve,disagreement,use,technology,share,ideas", + "questionSetID": "032901", + "textDesc": "请回答以下问题。继续使用本课的核心句型,分享更多关于合作与交流的想法。", + "questionSet": [ + { + "content": "Let's say you are organising a class party. What would you suggest doing first?", + "ability": ["表达建议", "计划描述"], + "explanation": "'Let's first make a list of everything we need, then divide the tasks among everyone.' 描述你组织活动的第一步。" + }, + { + "content": "What do you hope that the students in your school can do to help the environment?", + "ability": ["表达期望", "表达观点"], + "explanation": "'We hope that every student can bring a reusable water bottle and stop buying plastic ones. Small changes can make a big difference.'" + }, + { + "content": "Have you ever had to try to talk to someone about a disagreement? How did it go?", + "ability": ["协商表达", "经历描述"], + "explanation": "分享一次解决分歧的沟通经历。例如 'We try to talk to each other after the argument. I said how I felt, and my friend said how she felt, and then we understood each other better.'" + }, + { + "content": "What online tools can you use to study together with classmates outside of school?", + "ability": ["表达建议", "表达观点"], + "explanation": "'We can use shared documents to work on the same project at the same time, even when we are at our own homes.' 分享线上协作学习的工具和方法。" + } + ] + } +} +S1_032901_T1 = """【题目描述】 +请回答以下问题。用清晰、完整的英文语句进行回答,尝试使用 Let's、We hope that...can...、try to talk to、can use 等表达。 + +【热词】 +let's,suggest,activity,hope,achieve,talk,solve,problem,use,method,tool,team + +【题目】 +1. How do you suggest a fun activity to your friends? What do you say? +- 解析:'Let's go to the park after school and fly our kites!' 展示你如何邀请朋友一起做有趣的事。 + +2. What do you hope your class or team can achieve together this year? +- 解析:'We hope that our class can win the school sports day this year.' 分享对班级的期望。 + +3. When there is a problem in your group, how do you try to talk to your classmates about it? +- 解析:'We try to talk to each other calmly and listen to everyone's ideas.' 描述通过沟通解决问题。 + +4. What tools or methods can you use to work better as a team? +- 解析:'We can use a shared calendar to plan our tasks.' 分享提高团队效率的方法。""" +S1_032901_T2 = """【题目描述】 +请回答以下问题。继续使用本课的核心句型,分享更多关于合作与交流的想法。 + +【热词】 +let's,organise,event,hope,improve,talk,solve,disagreement,use,technology,share,ideas + +【题目】 +1. Let's say you are organising a class party. What would you suggest doing first? +- 解析:'Let's first make a list of everything we need, then divide the tasks.' 描述组织活动。 + +2. What do you hope that the students in your school can do to help the environment? +- 解析:'We hope that every student can bring a reusable water bottle.' 分享环保期望。 + +3. Have you ever had to try to talk to someone about a disagreement? How did it go? +- 解析:分享一次解决分歧的沟通经历。 + +4. What online tools can you use to study together with classmates outside of school? +- 解析:分享线上协作学习的工具和方法。""" + +# ============================================================ +# MAIN: Create all 10 records +# ============================================================ +def main(): + token = get_token() + print("Token OK\n") + + records = [ + # (label, table, QSID, jsonData, 题目1, 题目2, 图片描述, kw1, kw2) + # Writing records + ("写作 032501", "tblszuk1TeToofBF", "032501", W_032501, W_032501_T1, "", "", "", ""), + ("写作 032601", "tblszuk1TeToofBF", "032601", W_032601, W_032601_T1, "", "", "", ""), + ("写作 032701", "tblszuk1TeToofBF", "032701", W_032701, W_032701_T1, "", "", "", ""), + ("写作 032801", "tblszuk1TeToofBF", "032801", W_032801, W_032801_T1, "", "", "", ""), + ("写作 032901", "tblszuk1TeToofBF", "032901", W_032901, W_032901_T1, "", "", "", ""), + # Speaking P1 records + ("口语-P1 032501", "tblRGv7k4WH58Jgq", "032501", S1_032501, S1_032501_T1, S1_032501_T2, "", + "however,sooner,later,depending,used,change,mind,weather,plan", + "however,sooner,later,depending,used,opinion,technology,future"), + ("口语-P1 032701", "tblRGv7k4WH58Jgq", "032701", S1_032701, S1_032701_T1, S1_032701_T2, "", + "couldn't,find,lost,search,understand,curious,maybe,see,if,change,mind", + "lost,search,understand,explain,curious,test,change,mind,grow"), + ("口语-P1 032901", "tblRGv7k4WH58Jgq", "032901", S1_032901, S1_032901_T1, S1_032901_T2, "", + "let's,suggest,hope,achieve,talk,solve,problem,use,method,team", + "let's,organise,hope,improve,talk,solve,disagreement,use,technology"), + # Speaking P2 records + ("口语-P2 032601", "tblGoWYBmVI0IrvQ", "032601", S2_032601, S2_032601_T1, S2_032601_T2, S2_032601_PIC, "", ""), + ("口语-P2 032801", "tblGoWYBmVI0IrvQ", "032801", S2_032801, S2_032801_T1, S2_032801_T2, S2_032801_PIC, "", ""), + ] + + results = [] + for label, tid, qsid, jd, t1, t2, pic, kw1, kw2 in records: + fields = { + "题目集合 ID": qsid, + "dataStatus": "0", + "jsonData": json.dumps(jd, ensure_ascii=False), + "题目1": t1, + } + if "口语-P1" in label: + fields["题目1 完整配置" if False else "题目1"] = t1 + if t2: + fields["题目2" if "口语-P2" not in label else "题目2"] = t2 + if pic: + fields["图片描述"] = pic + if kw1: + fields["题目1热词"] = kw1 + if kw2: + fields["题目2热词"] = kw2 + + # Fix field names based on table + if tid == "tblszuk1TeToofBF": # Writing P1 + fields["题目1 完整配置"] = fields.pop("题目1") + if "题目2" in fields: del fields["题目2"] + elif tid == "tblRGv7k4WH58Jgq": # Speaking P1 + pass # field names ok + elif tid == "tblGoWYBmVI0IrvQ": # Speaking P2 + pass + + resp = create(token, tid, fields) + code = resp.get("code") + if code == 0: + rid = resp["data"]["record"]["record_id"] + print(f"[✅] {label} → {rid}") + results.append((label, qsid, rid, True)) + else: + print(f"[❌] {label}: {resp.get('msg')}") + results.append((label, qsid, "FAIL", False)) + time.sleep(0.3) + + ok = sum(1 for r in results if r[3]) + print(f"\n=== {ok}/{len(results)} 条创建成功 ===") + +if __name__ == "__main__": + main() diff --git a/scripts/gen_writing_speaking.py b/scripts/gen_writing_speaking.py new file mode 100644 index 0000000..903743b --- /dev/null +++ b/scripts/gen_writing_speaking.py @@ -0,0 +1,546 @@ +#!/usr/bin/env python3 +"""Generate and write 6 writing/speaking records to unit challenge bitable""" +import requests, json + +APP_TOKEN = "CMHSbUUjka3TrUsaxxEc297ongf" +APP_ID = "cli_a931175d41799cc7" +APP_SECRET = "Iw2vEfbjT6GtV0GhbxbZqfQ4nAPtbR14" +BASE = "https://open.feishu.cn/open-apis/bitable/v1" + +# ============================================================ +# Record definitions +# ============================================================ + +# --- 写作-P1-邮件回复 | 021801 | L2 B级 --- +WRITING_021801_JSON = { + "first": { + "category": "writing", + "type": "writing_email", + "questionSetID": "021801", + "textDesc": "Your friend Annie wants to plan a surprise party for her mum. " + "Write an email to help her organise it.
" + "Put these sentences in order to make a clear and helpful email.", + "optionSetList": [ + "Dear Annie,", + "Don't tell anyone about the surprise party.", + "We want to make Mum happy on her birthday.", + "I think Dad said he will bring the cake.", + "It must be hard to work every day without a break.", + "Best wishes,\nYour Friend" + ], + "answerSet": [0, 1, 2, 3, 4, 5], + "ability": ["短消息写作|邮件/便条", "衔接与连贯|连词使用"], + "explanation": "按照邮件格式和事件逻辑排列句子:称呼→说明目的→解释计划→补充细节→表达关心→署名。考察邮件格式规范和逻辑顺序组织能力。" + }, + "second": {} +} + +WRITING_021801_TEXT1 = """【题目描述】 +Your friend Annie wants to plan a surprise party for her mum. Write an email to help her organise it. +Put these sentences in order to make a clear and helpful email. + +【段落列表】 +A. Dear Annie, +B. Don't tell anyone about the surprise party. +C. We want to make Mum happy on her birthday. +D. I think Dad said he will bring the cake. +E. It must be hard to work every day without a break. +F. Best wishes, +Your Friend""" + +# --- 写作-P1-邮件回复 | 021901 | L2 B级 --- +WRITING_021901_JSON = { + "first": { + "category": "writing", + "type": "writing_email", + "questionSetID": "021901", + "textDesc": "Your friend Tom's pet dog Daisy is lost in the park. " + "Write an email to help him search for her.
" + "Put these sentences in order to make a helpful email.", + "optionSetList": [ + "Dear Tom,", + "We must find her before it gets dark!", + "We need to find Daisy before she runs too far away!", + "The lake is just in front of the playground.", + "The gate was open, and Daisy was missing.", + "Let's search together, while your mum checks the garden, and I check the path.", + "Best wishes,\nYour Friend" + ], + "answerSet": [0, 2, 1, 3, 4, 5, 6], + "ability": ["短消息写作|邮件/便条", "衔接与连贯|连词使用"], + "explanation": "按照寻物邮件的逻辑排列句子:称呼→表达紧迫性→说明目标→描述位置→解释原因→分工合作→署名。考察事件叙述的逻辑顺序和while/and并列结构的使用。" + }, + "second": {} +} + +WRITING_021901_TEXT1 = """【题目描述】 +Your friend Tom's pet dog Daisy is lost in the park. Write an email to help him search for her. +Put these sentences in order to make a helpful email. + +【段落列表】 +A. Dear Tom, +B. We must find her before it gets dark! +C. We need to find Daisy before she runs too far away! +D. The lake is just in front of the playground. +E. The gate was open, and Daisy was missing. +F. Let's search together, while your mum checks the garden, and I check the path. +G. Best wishes, +Your Friend""" + +# --- 写作-P1-邮件回复 | 022001 | L2 B级 --- +WRITING_022001_JSON = { + "first": { + "category": "writing", + "type": "writing_email", + "questionSetID": "022001", + "textDesc": "Your friend Lucy wants to make a gift for Grandma's birthday. " + "Write an email to help her plan it.
" + "Put these sentences in order to make a clear email.", + "optionSetList": [ + "Dear Lucy,", + "How long does it take you to make a paper card?", + "Tom is taking the longer route to buy some art supplies.", + "We still don't know if Grandma will like the surprise.", + "She always makes special cards for everyone's birthday.", + "Best wishes,\nYour Friend" + ], + "answerSet": [0, 1, 2, 3, 4, 5], + "ability": ["短消息写作|邮件/便条", "衔接与连贯|连词使用"], + "explanation": "按照邮件逻辑排列句子:称呼→询问→补充信息→表达不确定→说明原因→署名。考察邮件的逻辑衔接和连词使用能力。" + }, + "second": {} +} + +WRITING_022001_TEXT1 = """【题目描述】 +Your friend Lucy wants to make a gift for Grandma's birthday. Write an email to help her plan it. +Put these sentences in order to make a clear email. + +【段落列表】 +A. Dear Lucy, +B. How long does it take you to make a paper card? +C. Tom is taking the longer route to buy some art supplies. +D. We still don't know if Grandma will like the surprise. +E. She always makes special cards for everyone's birthday. +F. Best wishes, +Your Friend""" + +# --- 口语-P2-话题讨论 | 021801 | L2 B级 --- +SPEAKING_TOPIC_021801_JSON = { + "first": { + "category": "speaking", + "type": "speaking_topic", + "asrPrompt": "sick,classmate,help,homework,encourage,goal,dream,support,friend,teamwork", + "questionSetID": "021801", + "textDesc": "Look at these pictures about helping others. Talk about what you see " + "and share your own ideas about helping and supporting friends.", + "imageDesc": "Four images show children helping each other: 1st: A child brings a glass of water to a sick friend in bed. 2nd: Two children study together at a desk, one is explaining something. 3rd: A team of children celebrate together with hands raised. 4th: A child gives a present to a sad friend, who then smiles.", + "textImage": "021801-00.png", + "questionSet": [ + { + "content": "What do you do when your friend is sick at home?", + "ability": ["句型组织", "表达建议"], + "explanation": "可以用句型 \"We'd better...\" 来给出建议,例如 \"We'd better call him and check how he feels.\" 也可以补充具体行动,如 \"I would bring him homework and tell him to rest well.\"" + }, + { + "content": "How do you help a classmate who finds homework difficult?", + "ability": ["句型组织", "表达建议"], + "explanation": "可以用 \"Let's give him some tips so he can understand better.\" 来表达帮助方式。也可以说 \"I explain the homework step by step and give examples.\"" + }, + { + "content": "What do you hope for your best friend in the future?", + "ability": ["句型组织", "表达愿望"], + "explanation": "可以用 \"I hope he is able to achieve his goals.\" 来表达希望。也可以补充具体内容,如 \"I hope she will go to a good school and make new friends.\"" + }, + { + "content": "Tell us about a time when someone helped you. How did it feel?", + "ability": ["句型组织", "经历描述"], + "explanation": "用过去式描述一次被帮助的经历,例如 \"Last term I couldn't do my maths homework. My friend sat with me after class and helped me finish it. I felt very warm and thankful.\"" + }, + { + "content": "Why is it important to help others in your class or family?", + "ability": ["句型组织", "表达观点"], + "explanation": "可以从合作和友谊角度回答,例如 \"When we help each other, we grow together. Helping makes the team stronger and everyone feels happy.\"" + } + ] + }, + "second": { + "category": "speaking", + "type": "speaking_topic", + "asrPrompt": "sick,classmate,help,homework,encourage,goal,dream,support,friend,teamwork", + "questionSetID": "021801", + "textDesc": "Now look at these pictures again. Think more deeply about helping others " + "and share a different example or experience.", + "imageDesc": "Same images as above: children helping each other in different situations.", + "textImage": "021801-01.png", + "questionSet": [ + { + "content": "What do you do when your friend is sick at home?", + "ability": ["句型组织", "表达建议"], + "explanation": "也可以用不同的方式回答,例如 \"We'd better visit him and bring some fruit.\" 或者分享另一个想法:\"I would send him a nice message to cheer him up.\"" + }, + { + "content": "How do you help a classmate who finds homework difficult?", + "ability": ["句型组织", "表达建议"], + "explanation": "可以分享另一种帮助方式,比如 \"Let's give him a study plan so he can follow it step by step.\" 也可以说 \"We can study together after school.\"" + }, + { + "content": "What do you hope for your best friend in the future?", + "ability": ["句型组织", "表达愿望"], + "explanation": "试着用不同的句型表达,例如 \"I hope he is able to follow his dream no matter what.\" 或者 \"I wish she will be happy and healthy forever.\"" + }, + { + "content": "Tell us about a time when you helped someone else. How did you feel?", + "ability": ["句型组织", "经历描述"], + "explanation": "分享你帮助别人的经历,例如 \"I once helped a new classmate find her way around school. I felt proud because I made her feel welcome.\"" + }, + { + "content": "Why is it important to help others in your class or family?", + "ability": ["句型组织", "表达观点"], + "explanation": "可以从更广的角度回答,例如 \"Helping others makes our class like a big family. When we help each other, nobody feels alone or sad.\"" + } + ] + } +} + +SPEAKING_TOPIC_021801_TEXT1 = """【题目描述】 +Look at these pictures about helping others. Talk about what you see and share your own ideas about helping and supporting friends. + +【热词】 +sick,classmate,help,homework,encourage,goal,dream,support,friend,teamwork + +【图片描述】 +Four images show children helping each other: 1st: A child brings a glass of water to a sick friend in bed. 2nd: Two children study together at a desk, one is explaining something. 3rd: A team of children celebrate together with hands raised. 4th: A child gives a present to a sad friend, who then smiles. + +【题目】 +1. What do you do when your friend is sick at home? +- 解析:可以用句型 "We'd better..." 来给出建议,例如 "We'd better call him and check how he feels." 也可以补充具体行动,如 "I would bring him homework and tell him to rest well." + +2. How do you help a classmate who finds homework difficult? +- 解析:可以用 "Let's give him some tips so he can understand better." 来表达帮助方式。也可以说 "I explain the homework step by step and give examples." + +3. What do you hope for your best friend in the future? +- 解析:可以用 "I hope he is able to achieve his goals." 来表达希望。也可以补充具体内容,如 "I hope she will go to a good school and make new friends." + +4. Tell us about a time when someone helped you. How did it feel? +- 解析:用过去式描述一次被帮助的经历,例如 "Last term I couldn't do my maths homework. My friend sat with me after class and helped me finish it. I felt very warm and thankful." + +5. Why is it important to help others in your class or family? +- 解析:可以从合作和友谊角度回答,例如 "When we help each other, we grow together. Helping makes the team stronger and everyone feels happy.\"""" + +SPEAKING_TOPIC_021801_TEXT2 = """【题目描述】 +Now look at these pictures again. Think more deeply about helping others and share a different example or experience. + +【热词】 +sick,classmate,help,homework,encourage,goal,dream,support,friend,teamwork + +【图片描述】 +Same images as above: children helping each other in different situations. + +【题目】 +1. What do you do when your friend is sick at home? +- 解析:也可以用不同的方式回答,例如 "We'd better visit him and bring some fruit." 或者分享另一个想法:"I would send him a nice message to cheer him up." + +2. How do you help a classmate who finds homework difficult? +- 解析:可以分享另一种帮助方式,比如 "Let's give him a study plan so he can follow it step by step." 也可以说 "We can study together after school." + +3. What do you hope for your best friend in the future? +- 解析:试着用不同的句型表达,例如 "I hope he is able to follow his dream no matter what." 或者 "I wish she will be happy and healthy forever." + +4. Tell us about a time when you helped someone else. How did you feel? +- 解析:分享你帮助别人的经历,例如 "I once helped a new classmate find her way around school. I felt proud because I made her feel welcome." + +5. Why is it important to help others in your class or family? +- 解析:可以从更广的角度回答,例如 "Helping others makes our class like a big family. When we help each other, nobody feels alone or sad.\"""" + +# --- 口语-P1-日常回答 | 021901 | L2 B级 --- +SPEAKING_QA_021901_JSON = { + "first": { + "category": "speaking", + "type": "speaking_qa", + "asrPrompt": "subject,Maths,English,Art,prefer,reading,running,drawing,friend,kind,helpful,hobby,because,enjoy,difficult", + "questionSetID": "021901", + "textDesc": "请回答以下问题。用清晰、完整的英文语句进行回答,分享你的真实想法和经历。", + "questionSet": [ + { + "content": "What school subject do you like? Why?", + "ability": ["表达喜好与理由"], + "explanation": "你可以用 \"I like...\" 来回答,例如 \"I like English because it is interesting for me.\" 也可以换成 Maths、Art 或其他你喜欢的科目,并说明理由。" + }, + { + "content": "Is there a subject that is hard for you? Why?", + "ability": ["表达喜好与理由"], + "explanation": "你可以说 \"Maths is hard for me because I find the problems difficult.\" 用 \"but it is... for me\" 的句型来表达你的感受,同时解释原因。" + }, + { + "content": "What do you prefer to do after school?", + "ability": ["表达喜好与理由"], + "explanation": "用 \"I prefer... to...\" 来回答,例如 \"I prefer reading to watching TV because I enjoy stories.\" 也可以说跑步、画画或和朋友玩。" + }, + { + "content": "What do you do with your best friend after school?", + "ability": ["基础信息表达|个人信息问答"], + "explanation": "你可以回答 \"I play with my best friend after school.\" 然后用 \"I... because I...\" 来补充原因,例如 \"I play football with him because I love sports.\"" + } + ] + }, + "second": { + "category": "speaking", + "type": "speaking_qa", + "asrPrompt": "sport,swimming,football,weekend,museum,park,movie,cooking,pizza,dumplings,English,learn,songs,stories,fun,useful", + "questionSetID": "021901", + "textDesc": "请回答以下问题。用清晰、完整的英文语句进行回答,分享你的真实想法和经历。", + "questionSet": [ + { + "content": "Do you like playing sports? Which sport do you like?", + "ability": ["表达喜好与理由"], + "explanation": "你可以用 \"I like... but it is... for me\" 来表达,例如 \"I like swimming, but it is tiring for me sometimes.\" 也可以换成 football、basketball 或 running。" + }, + { + "content": "What do you prefer to do on weekends?", + "ability": ["表达喜好与理由"], + "explanation": "用 \"I prefer... to...\" 来回答,例如 \"I prefer going to the park to staying at home.\" 也可以提到去博物馆、看电影或和朋友出去玩。" + }, + { + "content": "What food do you like but find hard to make?", + "ability": ["表达喜好与理由"], + "explanation": "试着说出一种你爱吃但不会做的食物,例如 \"I like dumplings, but they are hard for me to make alone.\" 也可以用 \"I... because I...\" 解释难度在哪儿。" + }, + { + "content": "Why do you enjoy learning English?", + "ability": ["表达喜好与理由"], + "explanation": "用 \"I enjoy learning English because I...\" 来表达,例如 \"I enjoy learning English because I can sing English songs and read interesting stories.\" 也可以说对将来有用或很有趣。" + } + ] + } +} + +SPEAKING_QA_021901_TEXT1 = """【题目描述】 +请回答以下问题。用清晰、完整的英文语句进行回答,分享你的真实想法和经历。 + +【热词】 +subject,Maths,English,Art,prefer,reading,running,drawing,friend,kind,helpful,hobby,because,enjoy,difficult + +【题目】 +1. What school subject do you like? Why? +- 解析:你可以用 "I like..." 来回答,例如 "I like English because it is interesting for me." 也可以换成 Maths、Art 或其他你喜欢的科目,并说明理由。 + +2. Is there a subject that is hard for you? Why? +- 解析:你可以说 "Maths is hard for me because I find the problems difficult." 用 "but it is... for me" 的句型来表达你的感受,同时解释原因。 + +3. What do you prefer to do after school? +- 解析:用 "I prefer... to..." 来回答,例如 "I prefer reading to watching TV because I enjoy stories." 也可以说跑步、画画或和朋友玩。 + +4. What do you do with your best friend after school? +- 解析:你可以回答 "I play with my best friend after school." 然后用 "I... because I..." 来补充原因,例如 "I play football with him because I love sports.\"""" + +SPEAKING_QA_021901_TEXT2 = """【题目描述】 +请回答以下问题。用清晰、完整的英文语句进行回答,分享你的真实想法和经历。 + +【热词】 +sport,swimming,football,weekend,museum,park,movie,cooking,pizza,dumplings,English,learn,songs,stories + +【题目】 +1. Do you like playing sports? Which sport do you like? +- 解析:你可以用 "I like... but it is... for me" 来表达,例如 "I like swimming, but it is tiring for me sometimes." 也可以换成 football、basketball 或 running。 + +2. What do you prefer to do on weekends? +- 解析:用 "I prefer... to..." 来回答,例如 "I prefer going to the park to staying at home." 也可以提到去博物馆、看电影或和朋友出去玩。 + +3. What food do you like but find hard to make? +- 解析:试着说出一种你爱吃但不会做的食物,例如 "I like dumplings, but they are hard for me to make alone." 也可以用 "I... because I..." 解释难度在哪儿。 + +4. Why do you enjoy learning English? +- 解析:用 "I enjoy learning English because I..." 来表达,例如 "I enjoy learning English because I can sing English songs and read interesting stories." 也可以说对将来有用或很有趣。""" + +# --- 口语-P2-话题讨论 | 022001 | L2 B级 --- +SPEAKING_TOPIC_022001_JSON = { + "first": { + "category": "speaking", + "type": "speaking_topic", + "asrPrompt": "worried,afraid,dark,teach,brave,proud,challenge,climb,race,stop,fight,problem,difficult,solve", + "questionSetID": "022001", + "textDesc": "Look at these pictures about facing challenges and emotions. " + "Talk about what you see and share your own experiences with feelings and growth.", + "imageDesc": "Four images show children dealing with different emotions and challenges: 1st: A child looks worried while staring at a big maths test paper. 2nd: A parent or teacher gently puts a hand on a child's shoulder, teaching them something. 3rd: A child stands on top of a small hill, arms raised with a proud smile. 4th: An adult stops a child from touching a hot kettle, pointing at it with a warning look.", + "textImage": "022001-00.png", + "questionSet": [ + { + "content": "Have you ever seen a friend who looked worried? What happened?", + "ability": ["句型组织", "经历描述"], + "explanation": "描述一次观察到朋友担忧的经历,例如 \"My friend Tom is a happy boy, but he looked very worried before the maths test. I asked him what was wrong and he said he didn't study enough.\"" + }, + { + "content": "Who taught you not to be afraid of something? What did they say?", + "ability": ["句型组织", "经历描述"], + "explanation": "回答谁教过你勇敢,例如 \"My mum taught me not to be afraid of the dark. She said there is nothing that can hurt me and that being brave makes you stronger.\"" + }, + { + "content": "Tell us about a time you felt proud of yourself. What did you do?", + "ability": ["句型组织", "经历描述"], + "explanation": "用过去式描述一次自豪的经历,例如 \"I felt very proud when I finished a difficult race. I wanted to give up, but I kept running and reached the end.\"" + }, + { + "content": "Has a parent or teacher ever stopped you from doing something? Why?", + "ability": ["句型组织", "经历描述"], + "explanation": "描述一次被阻止的经历,例如 \"My dad stopped me from climbing a tall tree. He said it was too dangerous and I could fall and get hurt.\"" + }, + { + "content": "If you find anything really difficult, what will you do?", + "ability": ["句型组织", "表达观点"], + "explanation": "分享你面对困难的方法,例如 \"If I find anything difficult, I will ask my teacher or my parents for help. I will keep trying and not give up easily.\"" + } + ] + }, + "second": { + "category": "speaking", + "type": "speaking_topic", + "asrPrompt": "worried,afraid,dark,teach,brave,proud,challenge,climb,race,stop,fight,problem,difficult,solve", + "questionSetID": "022001", + "textDesc": "Now look at these pictures again. Think about different experiences " + "and share another story about feelings, challenges or growing up.", + "imageDesc": "Same images as above: children dealing with different emotions and challenges.", + "textImage": "022001-01.png", + "questionSet": [ + { + "content": "Have you ever seen a friend who looked worried? What did you do to help?", + "ability": ["句型组织", "经历描述"], + "explanation": "分享你帮助朋友的经历,例如 \"Yes, my friend Lily looked sad after losing her favourite pen. I helped her search everywhere, and she smiled when we finally found it.\"" + }, + { + "content": "Who taught you not to be afraid of trying new things? How did they help?", + "ability": ["句型组织", "经历描述"], + "explanation": "描述学习勇敢的过程,例如 \"My dad taught me not to be afraid of swimming. He held my hand in the water and slowly let go when I was ready.\"" + }, + { + "content": "Tell us about a time you overcame a difficulty. How did you feel?", + "ability": ["句型组织", "经历描述"], + "explanation": "分享克服困难的经历,例如 \"I couldn't ride a bike at first. I kept falling, but I practised every day. I felt amazing when I finally rode without help.\"" + }, + { + "content": "Why do parents or teachers sometimes stop children from doing things?", + "ability": ["句型组织", "表达观点"], + "explanation": "从理解的角度回答,例如 \"They stop us from doing dangerous things because they want to keep us safe. It is a way of showing their love and care.\"" + }, + { + "content": "If you find anything difficult, what will you tell yourself?", + "ability": ["句型组织", "表达观点"], + "explanation": "分享你鼓励自己的话,例如 \"I will tell myself 'Never give up!' I believe that if I keep trying, I will find a way to solve the problem and become stronger.\"" + } + ] + } +} + +SPEAKING_TOPIC_022001_TEXT1 = """【题目描述】 +Look at these pictures about facing challenges and emotions. Talk about what you see and share your own experiences with feelings and growth. + +【热词】 +worried,afraid,dark,teach,brave,proud,challenge,climb,race,stop,fight,problem,difficult,solve + +【图片描述】 +Four images show children dealing with different emotions and challenges: 1st: A child looks worried while staring at a big maths test paper. 2nd: A parent or teacher gently puts a hand on a child's shoulder, teaching them something. 3rd: A child stands on top of a small hill, arms raised with a proud smile. 4th: An adult stops a child from touching a hot kettle, pointing at it with a warning look. + +【题目】 +1. Have you ever seen a friend who looked worried? What happened? +- 解析:描述一次观察到朋友担忧的经历,例如 "My friend Tom is a happy boy, but he looked very worried before the maths test. I asked him what was wrong and he said he didn't study enough." + +2. Who taught you not to be afraid of something? What did they say? +- 解析:回答谁教过你勇敢,例如 "My mum taught me not to be afraid of the dark. She said there is nothing that can hurt me and that being brave makes you stronger." + +3. Tell us about a time you felt proud of yourself. What did you do? +- 解析:用过去式描述一次自豪的经历,例如 "I felt very proud when I finished a difficult race. I wanted to give up, but I kept running and reached the end." + +4. Has a parent or teacher ever stopped you from doing something? Why? +- 解析:描述一次被阻止的经历,例如 "My dad stopped me from climbing a tall tree. He said it was too dangerous and I could fall and get hurt." + +5. If you find anything really difficult, what will you do? +- 解析:分享你面对困难的方法,例如 "If I find anything difficult, I will ask my teacher or my parents for help. I will keep trying and not give up easily.\"""" + +SPEAKING_TOPIC_022001_TEXT2 = """【题目描述】 +Now look at these pictures again. Think about different experiences and share another story about feelings, challenges or growing up. + +【热词】 +worried,afraid,dark,teach,brave,proud,challenge,climb,race,stop,fight,problem,difficult,solve + +【图片描述】 +Same images as above: children dealing with different emotions and challenges. + +【题目】 +1. Have you ever seen a friend who looked worried? What did you do to help? +- 解析:分享你帮助朋友的经历,例如 "Yes, my friend Lily looked sad after losing her favourite pen. I helped her search everywhere, and she smiled when we finally found it." + +2. Who taught you not to be afraid of trying new things? How did they help? +- 解析:描述学习勇敢的过程,例如 "My dad taught me not to be afraid of swimming. He held my hand in the water and slowly let go when I was ready." + +3. Tell us about a time you overcame a difficulty. How did you feel? +- 解析:分享克服困难的经历,例如 "I couldn't ride a bike at first. I kept falling, but I practised every day. I felt amazing when I finally rode without help." + +4. Why do parents or teachers sometimes stop children from doing things? +- 解析:从理解的角度回答,例如 "They stop us from doing dangerous things because they want to keep us safe. It is a way of showing their love and care." + +5. If you find anything difficult, what will you tell yourself? +- 解析:分享你鼓励自己的话,例如 "I will tell myself 'Never give up!' I believe that if I keep trying, I will find a way to solve the problem and become stronger.\"""" + +# ============================================================ +# API helpers +# ============================================================ +def get_token(): + r = requests.post("https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal", + json={"app_id": APP_ID, "app_secret": APP_SECRET}, timeout=10) + return r.json()["tenant_access_token"] + +def create_record(token, table_id, fields): + r = requests.post( + f"{BASE}/apps/{APP_TOKEN}/tables/{table_id}/records", + headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"}, + json={"fields": fields}, timeout=15) + return r.json() + +# ============================================================ +# Records to create +# ============================================================ +records_to_create = [ + # (table_name, table_id, 题目集合 ID, jsonData, 题目1, 题目2) + ("写作-P1-邮件回复", "tblszuk1TeToofBF", "021801", + WRITING_021801_JSON, WRITING_021801_TEXT1, ""), + ("写作-P1-邮件回复", "tblszuk1TeToofBF", "021901", + WRITING_021901_JSON, WRITING_021901_TEXT1, ""), + ("写作-P1-邮件回复", "tblszuk1TeToofBF", "022001", + WRITING_022001_JSON, WRITING_022001_TEXT1, ""), + ("口语-P2-话题讨论", "tblGoWYBmVI0IrvQ", "021801", + SPEAKING_TOPIC_021801_JSON, SPEAKING_TOPIC_021801_TEXT1, SPEAKING_TOPIC_021801_TEXT2), + ("口语-P1-日常回答", "tblRGv7k4WH58Jgq", "021901", + SPEAKING_QA_021901_JSON, SPEAKING_QA_021901_TEXT1, SPEAKING_QA_021901_TEXT2), + ("口语-P2-话题讨论", "tblGoWYBmVI0IrvQ", "022001", + SPEAKING_TOPIC_022001_JSON, SPEAKING_TOPIC_022001_TEXT1, SPEAKING_TOPIC_022001_TEXT2), +] + +def main(): + token = get_token() + print("Token OK") + + for i, (tname, tid, qsid, jd, t1, t2) in enumerate(records_to_create): + fields = { + "题目集合 ID": qsid, + "dataStatus": "0", + "jsonData": json.dumps(jd, ensure_ascii=False), + "题目1": t1, + } + if t2: + fields["题目2"] = t2 + # 口语-P1 has 题目1热词 and 题目2热词 + if tname == "口语-P1-日常回答": + fields["题目1热词"] = "subject,Maths,English,Art,prefer,reading,running,drawing,friend,kind,helpful,hobby" + fields["题目2热词"] = "sport,swimming,football,weekend,museum,park,movie,cooking" + + result = create_record(token, tid, fields) + code = result.get("code") + if code == 0: + rid = result.get("data",{}).get("record",{}).get("record_id","?") + print(f"[{i+1}/6] ✅ {tname} QSID={qsid} → {rid}") + else: + print(f"[{i+1}/6] ❌ {tname} QSID={qsid} FAIL: {result.get('msg')}") + + print("\nDone!") + +if __name__ == "__main__": + main() diff --git a/scripts/gen_writing_speaking_batch2.py b/scripts/gen_writing_speaking_batch2.py new file mode 100644 index 0000000..0d1ae7e --- /dev/null +++ b/scripts/gen_writing_speaking_batch2.py @@ -0,0 +1,660 @@ +#!/usr/bin/env python3 +"""Generate 7 new writing/speaking records: 022101, 022201, 022301, 022401""" +import requests, json, time + +APP_TOKEN = "CMHSbUUjka3TrUsaxxEc297ongf" +APP_ID = "cli_a931175d41799cc7" +APP_SECRET = "Iw2vEfbjT6GtV0GhbxbZqfQ4nAPtbR14" +BASE = "https://open.feishu.cn/open-apis/bitable/v1" + +def get_token(): + r = requests.post("https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal", + json={"app_id": APP_ID, "app_secret": APP_SECRET}, timeout=10) + return r.json()["tenant_access_token"] + +def create_record(token, table_id, fields): + r = requests.post(f"{BASE}/apps/{APP_TOKEN}/tables/{table_id}/records", + headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"}, + json={"fields": fields}, timeout=15) + return r.json() + +def verify(token, table_id, rid, qsid): + r = requests.get(f"{BASE}/apps/{APP_TOKEN}/tables/{table_id}/records/{rid}", + headers={"Authorization": f"Bearer {token}"}, timeout=10) + f = r.json().get("data",{}).get("record",{}).get("fields",{}) + ds = f.get("dataStatus","?") + fqsid = f.get("题目集合 ID","?") + jd = json.loads(f.get("jsonData","{}")) + tp = jd.get("first",{}).get("type","?") + oa = len(jd.get("first",{}).get("optionSetList",[])) + ai = jd.get("first",{}).get("answerSet",[]) + ab = jd.get("first",{}).get("ability",[]) + ex = jd.get("first",{}).get("explanation","") + qs = len(jd.get("first",{}).get("questionSet",[])) + sqs = len(jd.get("second",{}).get("questionSet",[])) + t1 = bool(f.get("题目1","")) + t2 = bool(f.get("题目2","")) + pd = bool(f.get("图片描述","")) + ok = (ds=="0" and fqsid==qsid and t1 and len(ai)>0 and len(ex)>10) + tag = "✅" if ok else "❌" + return f"{tag} {qsid} | ds={ds} type={tp} opt={oa} qs={qs}/{sqs} ans={ai} ab={len(ab)} expl={len(ex)} t1={t1} t2={t2} pic={pd}" + +# ============================================================ +# RECORD DEFINITIONS +# ============================================================ + +# --- 写作-P1 022101: Urgent help --- +W_022101 = { + "first": { + "category": "writing", "type": "writing_email", + "questionSetID": "022101", + "textDesc": "Your friend Lily left her school bag in the classroom and needs your help to get it back before the weekend. Write an email to help her.
Put these sentences in order to make a clear and helpful email.", + "optionSetList": [ + "Dear Lily,", + "Is there any chance we can go back to school for your bag now?", + "If we leave right away, we still have a chance to find it.", + "We must hurry and call the teacher to avoid being too late.", + "The only way to get it back is to ask the school guard for help.", + "How long have you been here waiting for me?", + "Yours, Sam" + ], + "answerSet": [0, 1, 2, 3, 4, 5, 6], + "ability": ["短消息写作|邮件/便条", "衔接与连贯|连词使用"], + "explanation": "按紧急求助邮件的逻辑排列:称呼→询问可能→建议立刻行动→强调紧迫性→给出唯一方案→关心等待→署名。考察is there any chance/we must/the only way等句型和邮件逻辑组织能力。" + }, + "second": {} +} +W_022101_T1 = """【题目描述】 +Your friend Lily left her school bag in the classroom and needs your help to get it back before the weekend. Write an email to help her. +Put these sentences in order to make a clear and helpful email. + +【段落列表】 +A. Dear Lily, +B. Is there any chance we can go back to school for your bag now? +C. If we leave right away, we still have a chance to find it. +D. We must hurry and call the teacher to avoid being too late. +E. The only way to get it back is to ask the school guard for help. +F. How long have you been here waiting for me? +G. Yours, Sam""" + +# --- 写作-P1 022201: Mystery box --- +W_022201 = { + "first": { + "category": "writing", "type": "writing_email", + "questionSetID": "022201", + "textDesc": "Your friend Max found a mysterious old box in the attic. Write an email to share what you found and ask for his help.
Put these sentences in order to make a clear email.", + "optionSetList": [ + "Dear Max,", + "Why is this old box full of strange papers?", + "The story behind it is unknown, and even my grandpa avoids talking about it.", + "This box might be from our great-grandfather's time.", + "By then, I will have tried to open it with a small key.", + "That's why I am writing to ask for your help.", + "Take care, Alex" + ], + "answerSet": [0, 1, 2, 3, 4, 5, 6], + "ability": ["短消息写作|邮件/便条", "衔接与连贯|连词使用"], + "explanation": "按分享发现的邮件逻辑排列:称呼→提出问题→描述背景→合理推测→行动预案→请求帮助→署名。考察why is this/the...is unknown/this might be/by then...will have done等句型组织。" + }, + "second": {} +} +W_022201_T1 = """【题目描述】 +Your friend Max found a mysterious old box in the attic. Write an email to share what you found and ask for his help. +Put these sentences in order to make a clear email. + +【段落列表】 +A. Dear Max, +B. Why is this old box full of strange papers? +C. The story behind it is unknown, and even my grandpa avoids talking about it. +D. This box might be from our great-grandfather's time. +E. By then, I will have tried to open it with a small key. +F. That's why I am writing to ask for your help. +G. Take care, Alex""" + +# --- 写作-P1 022301: Community garden --- +W_022301 = { + "first": { + "category": "writing", "type": "writing_email", + "questionSetID": "022301", + "textDesc": "You and your friend Emma are working on a community garden project. Write an email to tell her about today's progress and the plan for next time.
Put these sentences in order to make a clear email.", + "optionSetList": [ + "Dear Emma,", + "We came here to work together and make our town more beautiful.", + "So we worked fast before the sun got too hot.", + "It took us two hours to return to the community garden today.", + "The photos showed the garden was full of pretty flowers now.", + "I will plant new seeds while you will water the young trees.", + "Your friend, Lily" + ], + "answerSet": [0, 1, 2, 3, 4, 5, 6], + "ability": ["短消息写作|邮件/便条", "衔接与连贯|连词使用"], + "explanation": "按社区活动汇报的逻辑排列:称呼→说明目的→描述行动→补充细节→展示成果→分配未来任务→署名。考察we came here to/so...worked/the photos showed/while...will等句型和邮件叙事组织能力。" + }, + "second": {} +} +W_022301_T1 = """【题目描述】 +You and your friend Emma are working on a community garden project. Write an email to tell her about today's progress and the plan for next time. +Put these sentences in order to make a clear email. + +【段落列表】 +A. Dear Emma, +B. We came here to work together and make our town more beautiful. +C. So we worked fast before the sun got too hot. +D. It took us two hours to return to the community garden today. +E. The photos showed the garden was full of pretty flowers now. +F. I will plant new seeds while you will water the young trees. +G. Your friend, Lily""" + +# --- 写作-P1 022401: Hospital visit --- +W_022401 = { + "first": { + "category": "writing", "type": "writing_email", + "questionSetID": "022401", + "textDesc": "You and your friend Tom visited Jack in the hospital today. Write an email to Sophie to tell her about the visit and what you plan to do next.
Put these sentences in order to make a clear email.", + "optionSetList": [ + "Dear Sophie,", + "Tom and I went to the hospital to find our friend Jack today.", + "The nurse used a machine to check his temperature before we went in.", + "After saying goodbye, we hurried to tell his mum that he was feeling better.", + "Then we will bring him his homework and some snacks tomorrow.", + "You have already done so much by sending him that lovely card.", + "Warmly, Lily" + ], + "answerSet": [0, 1, 2, 3, 4, 5, 6], + "ability": ["短消息写作|邮件/便条", "衔接与连贯|连词使用"], + "explanation": "按医院探病汇报的逻辑排列:称呼→说明探病行动→描述检查过程→告别后通知→后续计划→感谢帮助→署名。考察go to...to find/use...to check/after doing/have already done等句型组织。" + }, + "second": {} +} +W_022401_T1 = """【题目描述】 +You and your friend Tom visited Jack in the hospital today. Write an email to Sophie to tell her about the visit and what you plan to do next. +Put these sentences in order to make a clear email. + +【段落列表】 +A. Dear Sophie, +B. Tom and I went to the hospital to find our friend Jack today. +C. The nurse used a machine to check his temperature before we went in. +D. After saying goodbye, we hurried to tell his mum that he was feeling better. +E. Then we will bring him his homework and some snacks tomorrow. +F. You have already done so much by sending him that lovely card. +G. Warmly, Lily""" + +# ============================================================ +# SPEAKING RECORDS +# ============================================================ + +# --- 口语-P1 022101: Learning experiences --- +S1_022101 = { + "first": { + "category": "speaking", "type": "speaking_qa", + "asrPrompt": "teacher,gave,seeds,garden,explained,moon,Earth,experiment,proud,found,stone,shell,gift,present,map", + "questionSetID": "022101", + "textDesc": "请回答以下问题。用清晰、完整的英文语句进行回答,分享你的真实经历和想法。", + "questionSet": [ + { + "content": "Tell us about a time your teacher gave you something special. What was it?", + "ability": ["经历描述"], + "explanation": "用过去式描述老师给过你的特别的东西。用 '...gave us... and...' 的句型,例如 'Our art teacher gave us some paint and paper for a special project last week.'" + }, + { + "content": "Who once explained something that you didn't understand? What did they say?", + "ability": ["经历描述"], + "explanation": "描述谁给你解释过你不理解的事情。用 '...explained that...' 的句型,例如 'My dad explained that the stars are very, very far away from the Earth.'" + }, + { + "content": "What did you do after finishing a difficult task? How did you feel?", + "ability": ["经历描述"], + "explanation": "用 'After doing...' 开头描述完成后的感受。例如 'After doing the science experiment, we felt very proud and wanted to show everyone the results.'" + }, + { + "content": "What interesting thing have you found in an unexpected place?", + "ability": ["经历描述"], + "explanation": "描述你在不寻常的地方发现过什么。用 '... found... in/on...' 的句型,例如 'I found a beautiful seashell on the playground last Friday.'" + } + ] + }, + "second": { + "category": "speaking", "type": "speaking_qa", + "asrPrompt": "gave,help,learn,explained,game,sport,searching,found,brave,felt,happy,excited,proud,surprised", + "questionSetID": "022101", + "textDesc": "请回答以下问题。用清晰、完整的英文语句进行回答,分享更多你的真实经历和想法。", + "questionSet": [ + { + "content": "What did someone give you that helped you learn something new?", + "ability": ["经历描述"], + "explanation": "分享别人给过你什么帮助了你学习新东西。例如 'My grandpa gave me a small map and a compass for our hiking trip. It helped me learn how to find directions.'" + }, + { + "content": "Who explained a difficult game or sport to you? What happened?", + "ability": ["经历描述"], + "explanation": "描述谁给你解释过一项困难的运动或游戏。例如 'My cousin explained that in chess, each piece moves in a different way. It took a long time for me to remember all the rules.'" + }, + { + "content": "What did you find after searching for a long time?", + "ability": ["经历描述"], + "explanation": "分享你找了很久才找到的东西。例如 'I found my favourite book under the sofa after searching the whole house for nearly one hour.'" + }, + { + "content": "After doing something brave, how did you feel? Tell us about it.", + "ability": ["经历描述"], + "explanation": "描述你做过一件勇敢的事后的感受。例如 'After doing my first school speech, I felt so happy and surprised that I could do it. The whole class clapped for me.'" + } + ] + } +} +S1_022101_T1 = """【题目描述】 +请回答以下问题。用清晰、完整的英文语句进行回答,分享你的真实经历和想法。 + +【热词】 +teacher,gave,seeds,garden,explained,moon,Earth,experiment,proud,found,stone,shell + +【题目】 +1. Tell us about a time your teacher gave you something special. What was it? +- 解析:用过去式描述老师给过你的特别的东西。用 '...gave us... and...' 的句型,例如 'Our art teacher gave us some paint and paper for a special project last week.' + +2. Who once explained something that you didn't understand? What did they say? +- 解析:描述谁给你解释过你不理解的事情。用 '...explained that...' 的句型,例如 'My dad explained that the stars are very, very far away from the Earth.' + +3. What did you do after finishing a difficult task? How did you feel? +- 解析:用 'After doing...' 开头描述完成后的感受。例如 'After doing the science experiment, we felt very proud and wanted to show everyone the results.' + +4. What interesting thing have you found in an unexpected place? +- 解析:描述你在不寻常的地方发现过什么。用 '... found... in/on...' 的句型,例如 'I found a beautiful seashell on the playground last Friday.'""" +S1_022101_T2 = """【题目描述】 +请回答以下问题。用清晰、完整的英文语句进行回答,分享更多你的真实经历和想法。 + +【热词】 +gave,help,learn,explained,game,sport,searching,found,brave,felt,happy,excited,surprised,proud + +【题目】 +1. What did someone give you that helped you learn something new? +- 解析:分享别人给过你什么帮助了你学习新东西。例如 'My grandpa gave me a small map and a compass for our hiking trip. It helped me learn how to find directions.' + +2. Who explained a difficult game or sport to you? What happened? +- 解析:描述谁给你解释过一项困难的运动或游戏。例如 'My cousin explained that in chess, each piece moves in a different way. It took a long time for me to remember all the rules.' + +3. What did you find after searching for a long time? +- 解析:分享你找了很久才找到的东西。例如 'I found my favourite book under the sofa after searching the whole house for nearly one hour.' + +4. After doing something brave, how did you feel? Tell us about it. +- 解析:描述你做过一件勇敢的事后的感受。例如 'After doing my first school speech, I felt so happy and surprised that I could do it. The whole class clapped for me.'""" + +# --- 口语-P2 022201: Teamwork (WITH IMAGES) --- +S2_022201 = { + "first": { + "category": "speaking", "type": "speaking_topic", + "asrPrompt": "plan,list,project,team,disagree,talk,together,poster,colourful,computer,work,help,finish,share,great", + "questionSetID": "022201", + "textDesc": "Here are some pictures about working on a school project with classmates. What do you think about these situations?", + "imageDesc": "Four images show a group of students working on a school project together: 1st: Students sit around a desk with a plan sheet and coloured pencils, discussing their project. 2nd: Two students face each other unhappily, and a teacher bends down to help them work together. 3rd: Students use a laptop and coloured paper to make a poster, with one writing and others drawing. 4th: All students hold up their finished team project, each with a big smile on their faces.", + "textImage": "022201-00.png", + "questionSet": [ + { + "content": "How do you start a group project with your classmates?", + "ability": ["句型组织", "表达建议"], + "explanation": "'Let's plan it first and make a to-do list together.' 然后用这个开始描述你们的项目流程,也可以说说谁负责什么。" + }, + { + "content": "What happened when your team disagreed about something?", + "ability": ["句型组织", "经历描述"], + "explanation": "'They argued at first, and then we sat down and talked about it.' 描述一次团队有不同意见的经历,以及你们是怎么解决的。" + }, + { + "content": "What do you use to make your project look great?", + "ability": ["句型组织", "表达建议"], + "explanation": "'We'll use colourful paper and markers to make our poster stand out.' 说说你们用什么工具和材料让项目作品看起来更好。" + }, + { + "content": "What must everyone do to finish the project on time?", + "ability": ["句型组织", "表达要求"], + "explanation": "'Everyone must bring their part and help each other.' 解释团队合作中每个人需要做什么才能按时完成。" + }, + { + "content": "Tell us about a time your team did a really great job.", + "ability": ["句型组织", "经历描述"], + "explanation": "分享一次团队合作成功的经历。例如 'Last month, our team made a poster about saving water. We won the school prize, and I felt so proud of our teamwork.'" + } + ] + }, + "second": { + "category": "speaking", "type": "speaking_topic", + "asrPrompt": "plan,list,project,team,disagree,talk,together,poster,colourful,computer,work,help,finish,share,great", + "questionSetID": "022201", + "textDesc": "Now look at these pictures again. Think about a different project or situation and share more ideas about working with others.", + "imageDesc": "Same images as above: students working on a school project together.", + "textImage": "022201-01.png", + "questionSet": [ + { + "content": "How do you plan a school party or event with your classmates?", + "ability": ["句型组织", "表达建议"], + "explanation": "'Let's plan the games first and make a decoration list together.' 分享你们如何组织一场活动或派对。" + }, + { + "content": "What did your friend do at first when things went wrong?", + "ability": ["句型组织", "经历描述"], + "explanation": "'They felt worried at first, and then we helped each other find a solution.' 说说当事情出错时你们是怎么一起面对的。" + }, + { + "content": "What tools or objects will you use to finish a big task?", + "ability": ["句型组织", "表达建议"], + "explanation": "'We will use a big calendar to plan our time and make sure we don't forget anything.' 说说你们计划用什么来高效完成任务。" + }, + { + "content": "What rules must everyone follow when working as a team?", + "ability": ["句型组织", "表达要求"], + "explanation": "'Everyone must listen to each other and share their ideas fairly.' 制定几条团队合作的基本规则。" + }, + { + "content": "What was the most fun thing you ever did together as a group?", + "ability": ["句型组织", "经历描述"], + "explanation": "分享一个有趣的团队经历。例如 'Last summer, our group cleaned the park near our school. After finishing, we had a small picnic together, and it was the most fun I have ever had with my classmates.'" + } + ] + } +} +S2_022201_T1 = """【题目描述】 +Here are some pictures about working on a school project with classmates. What do you think about these situations? + +【热词】 +plan,list,project,team,disagree,talk,together,poster,colourful,computer,work,help + +【图片描述】 +Four images show a group of students working on a school project together: 1st: Students sit around a desk with a plan sheet and coloured pencils, discussing their project. 2nd: Two students face each other unhappily, and a teacher bends down to help them work together. 3rd: Students use a laptop and coloured paper to make a poster, with one writing and others drawing. 4th: All students hold up their finished team project, each with a big smile on their faces. + +【题目】 +1. How do you start a group project with your classmates? +- 解析:'Let's plan it first and make a to-do list together.' 然后用这个开始描述你们的项目流程,也可以说说谁负责什么。 + +2. What happened when your team disagreed about something? +- 解析:'They argued at first, and then we sat down and talked about it.' 描述一次团队有不同意见的经历,以及你们是怎么解决的。 + +3. What do you use to make your project look great? +- 解析:'We'll use colourful paper and markers to make our poster stand out.' 说说你们用什么工具和材料让项目作品看起来更好。 + +4. What must everyone do to finish the project on time? +- 解析:'Everyone must bring their part and help each other.' 解释团队合作中每个人需要做什么才能按时完成。 + +5. Tell us about a time your team did a really great job. +- 解析:分享一次团队合作成功的经历。例如 'Last month, our team made a poster about saving water. We won the school prize, and I felt so proud of our teamwork.'""" +S2_022201_T2 = """【题目描述】 +Now look at these pictures again. Think about a different project or situation and share more ideas about working with others. + +【热词】 +plan,list,project,team,disagree,talk,together,poster,colourful,computer,work,help + +【图片描述】 +Same images as above: students working on a school project together. + +【题目】 +1. How do you plan a school party or event with your classmates? +- 解析:'Let's plan the games first and make a decoration list together.' 分享你们如何组织一场活动或派对。 + +2. What did your friend do at first when things went wrong? +- 解析:'They felt worried at first, and then we helped each other find a solution.' 说说当事情出错时你们是怎么一起面对的。 + +3. What tools or objects will you use to finish a big task? +- 解析:'We will use a big calendar to plan our time and make sure we don't forget anything.' 说说你们计划用什么来高效完成任务。 + +4. What rules must everyone follow when working as a team? +- 解析:'Everyone must listen to each other and share their ideas fairly.' 制定几条团队合作的基本规则。 + +5. What was the most fun thing you ever did together as a group? +- 解析:分享一个有趣的团队经历。例如 'Last summer, our group cleaned the park near our school. After finishing, we had a small picnic together, and it was the most fun I have ever had with my classmates.'""" +# Image prompts for 口语-P2 022201 +S2_022201_PIC = '[1-组图]:{"prompt_2":"黑白线条图:一组学生在教室里围坐在课桌前,桌上放着计划表和彩色铅笔,他们正在热烈讨论项目内容","prompt_3":"黑白线条图:两个学生面对面站着表情不悦,旁边一位老师弯腰用手势示意让他们一起合作","prompt_4":"黑白线条图:学生们使用笔记本电脑和彩纸制作展示海报,有人写字、有人画图,气氛热烈","prompt_5":"黑白线条图:所有学生一起举起完成的团队作品,每个人脸上带着成功的笑容"}' + +# --- 口语-P1 022301: Responsibility --- +S1_022301 = { + "first": { + "category": "speaking", "type": "speaking_qa", + "asrPrompt": "duty,clean,tidy,classroom,follow,rules,safe,dangerous,environment,rubbish,stop,help,important,everyday", + "questionSetID": "022301", + "textDesc": "请回答以下问题。用清晰、完整的英文语句进行回答,分享你的真实想法和做法。", + "questionSet": [ + { + "content": "Why should we take care of our school or neighbourhood?", + "ability": ["表达观点"], + "explanation": "'It is our duty to keep our classroom clean and tidy.' 从这个角度说明为什么我们有责任维护身边的环境,可以举一个具体的例子。" + }, + { + "content": "What will happen if we don't follow the safety rules?", + "ability": ["表达观点"], + "explanation": "'We must follow the safety rules, or our school will be dangerous for everyone.' 说明不遵守规则的后果。" + }, + { + "content": "What do we need to make people stop doing to protect the environment?", + "ability": ["表达建议"], + "explanation": "'We need to make people stop throwing rubbish on the ground.' 说出你认为需要改变的一种行为,并说明为什么。" + }, + { + "content": "What is one of the most important things we should do every day?", + "ability": ["表达观点"], + "explanation": "'Helping others is one of the most important things we can do each day.' 用 '...is one of the...' 说出你认为每天最重要的事情之一。" + } + ] + }, + "second": { + "category": "speaking", "type": "speaking_qa", + "asrPrompt": "duty,home,help,parents,kind,classmates,rules,online,polite,best,ways,support,care,respect", + "questionSetID": "022301", + "textDesc": "请回答以下问题。用清晰、完整的英文语句进行回答,分享更多你的真实想法。", + "questionSet": [ + { + "content": "What else is it our duty to do at home?", + "ability": ["表达观点"], + "explanation": "说说你在家里有哪些责任。例如 'It is my duty to help my parents set the table for dinner every evening.'" + }, + { + "content": "Why must we be kind to our classmates?", + "ability": ["表达观点"], + "explanation": "'We must be kind to our classmates, or our classroom will feel cold and unfriendly.' 说说友善对人的重要性。" + }, + { + "content": "What do we need to make people stop doing online?", + "ability": ["表达建议"], + "explanation": "'We need to make people stop saying unkind words online.' 说说网络上需要停止的不良行为。" + }, + { + "content": "What is one of the best ways to help your parents?", + "ability": ["表达观点"], + "explanation": "'Doing my homework without being asked is one of the best ways to help my parents.' 用 '...is one of the...' 分享帮助父母的好方法。" + } + ] + } +} +S1_022301_T1 = """【题目描述】 +请回答以下问题。用清晰、完整的英文语句进行回答,分享你的真实想法和做法。 + +【热词】 +duty,clean,tidy,classroom,follow,rules,safe,dangerous,environment,rubbish,stop,important,everyday + +【题目】 +1. Why should we take care of our school or neighbourhood? +- 解析:'It is our duty to keep our classroom clean and tidy.' 从这个角度说明为什么我们有责任维护身边的环境,可以举一个具体的例子。 + +2. What will happen if we don't follow the safety rules? +- 解析:'We must follow the safety rules, or our school will be dangerous for everyone.' 说明不遵守规则的后果。 + +3. What do we need to make people stop doing to protect the environment? +- 解析:'We need to make people stop throwing rubbish on the ground.' 说出你认为需要改变的一种行为,并说明为什么。 + +4. What is one of the most important things we should do every day? +- 解析:'Helping others is one of the most important things we can do each day.' 用 '...is one of the...' 说出你认为每天最重要的事情之一。""" +S1_022301_T2 = """【题目描述】 +请回答以下问题。用清晰、完整的英文语句进行回答,分享更多你的真实想法。 + +【热词】 +duty,home,help,parents,kind,classmates,rules,online,polite,best,ways,support,care,respect + +【题目】 +1. What else is it our duty to do at home? +- 解析:说说你在家里有哪些责任。例如 'It is my duty to help my parents set the table for dinner every evening.' + +2. Why must we be kind to our classmates? +- 解析:'We must be kind to our classmates, or our classroom will feel cold and unfriendly.' 说说友善对人的重要性。 + +3. What do we need to make people stop doing online? +- 解析:'We need to make people stop saying unkind words online.' 说说网络上需要停止的不良行为。 + +4. What is one of the best ways to help your parents? +- 解析:'Doing my homework without being asked is one of the best ways to help my parents.' 用 '...is one of the...' 分享帮助父母的好方法。""" + +# --- 口语-P1 022401: Expressing yourself --- +S1_022401 = { + "first": { + "category": "speaking", "type": "speaking_qa", + "asrPrompt": "want,only,draw,garden,park,understand,homework,first,start,plan,until,finish,turn,TV,game", + "questionSetID": "022401", + "textDesc": "请回答以下问题。用清晰、完整的英文语句进行回答,分享你的真实想法和做法。", + "questionSet": [ + { + "content": "If you could choose one thing to do today, what would you want to do?", + "ability": ["表达喜好"], + "explanation": "'I only want to spend the afternoon drawing in the garden.' 说出你今天最想做的事,用 'I only want to...' 的句型。" + }, + { + "content": "Your friend wants to play but you have homework. What do you say?", + "ability": ["协商表达"], + "explanation": "'I understand you want to play, but we must finish our homework first.' 用 'I understand... but...' 来温和地说不。" + }, + { + "content": "How do you think we should start a big task?", + "ability": ["表达建议"], + "explanation": "'We should start by making a plan and getting all the things we need.' 用 'We should start by...' 说出你的建议。" + }, + { + "content": "What won't you do until you finish your work?", + "ability": ["表达坚持"], + "explanation": "'I won't turn on the TV until I have done all my homework.' 用 'I won't... until...' 说明你的坚持。" + } + ] + }, + "second": { + "category": "speaking", "type": "speaking_qa", + "asrPrompt": "weekend,only,relax,read,disagree,friend,listen,talk,start,new,skill,practise,until,ready,perform", + "questionSetID": "022401", + "textDesc": "请回答以下问题。用清晰、完整的英文语句进行回答,分享更多你的真实想法。", + "questionSet": [ + { + "content": "What is something you only want to do on the weekend?", + "ability": ["表达喜好"], + "explanation": "'On the weekend, I only want to relax and read my favourite books all morning.' 说说你只在周末想做的事。" + }, + { + "content": "When you disagree with a friend, how do you solve it?", + "ability": ["协商表达"], + "explanation": "'I understand you are angry, but we must listen to each other and talk about it.' 分享你处理分歧的方式。" + }, + { + "content": "How should we start when learning a new skill?", + "ability": ["表达建议"], + "explanation": "'We should start by watching someone else do it and then try it ourselves.' 用 'We should start by...' 分享学习方法。" + }, + { + "content": "What won't you do until you have practised enough?", + "ability": ["表达坚持"], + "explanation": "'I won't perform on the stage until I have practised my piece at least ten times.' 说说你需要充分练习才会去做的事。" + } + ] + } +} +S1_022401_T1 = """【题目描述】 +请回答以下问题。用清晰、完整的英文语句进行回答,分享你的真实想法和做法。 + +【热词】 +want,only,draw,garden,park,understand,homework,first,start,plan,until,finish,TV,game + +【题目】 +1. If you could choose one thing to do today, what would you want to do? +- 解析:'I only want to spend the afternoon drawing in the garden.' 说出你今天最想做的事,用 'I only want to...' 的句型。 + +2. Your friend wants to play but you have homework. What do you say? +- 解析:'I understand you want to play, but we must finish our homework first.' 用 'I understand... but...' 来温和地说不。 + +3. How do you think we should start a big task? +- 解析:'We should start by making a plan and getting all the things we need.' 用 'We should start by...' 说出你的建议。 + +4. What won't you do until you finish your work? +- 解析:'I won't turn on the TV until I have done all my homework.' 用 'I won't... until...' 说明你的坚持。""" +S1_022401_T2 = """【题目描述】 +请回答以下问题。用清晰、完整的英文语句进行回答,分享更多你的真实想法。 + +【热词】 +weekend,only,relax,read,disagree,friend,listen,talk,start,new,skill,practise,until,ready,perform + +【题目】 +1. What is something you only want to do on the weekend? +- 解析:'On the weekend, I only want to relax and read my favourite books all morning.' 说说你只在周末想做的事。 + +2. When you disagree with a friend, how do you solve it? +- 解析:'I understand you are angry, but we must listen to each other and talk about it.' 分享你处理分歧的方式。 + +3. How should we start when learning a new skill? +- 解析:'We should start by watching someone else do it and then try it ourselves.' 用 'We should start by...' 分享学习方法。 + +4. What won't you do until you have practised enough? +- 解析:'I won't perform on the stage until I have practised my piece at least ten times.' 说说你需要充分练习才会去做的事。""" + +# ============================================================ +# MAIN: Create all 7 records +# ============================================================ +def main(): + token = get_token() + print("Token OK\n") + + records = [ + # (label, table, QSID, jsonData, 题目1, 题目2, 图片描述, 题目1热词, 题目2热词) + ("写作 022101", "tblszuk1TeToofBF", "022101", W_022101, W_022101_T1, "", "", "", ""), + ("写作 022201", "tblszuk1TeToofBF", "022201", W_022201, W_022201_T1, "", "", "", ""), + ("写作 022301", "tblszuk1TeToofBF", "022301", W_022301, W_022301_T1, "", "", "", ""), + ("写作 022401", "tblszuk1TeToofBF", "022401", W_022401, W_022401_T1, "", "", "", ""), + ("口语-P1 022101", "tblRGv7k4WH58Jgq", "022101", S1_022101, S1_022101_T1, S1_022101_T2, "", + "teacher,gave,seeds,garden,explained,moon,Earth,experiment,found", + "gave,help,learn,explained,game,searching,found,brave,happy"), + ("口语-P2 022201", "tblGoWYBmVI0IrvQ", "022201", S2_022201, S2_022201_T1, S2_022201_T2, + S2_022201_PIC, "", ""), + ("口语-P1 022301", "tblRGv7k4WH58Jgq", "022301", S1_022301, S1_022301_T1, S1_022301_T2, "", + "duty,clean,tidy,classroom,follow,safe,rules,environment,rubbish,important", + "duty,home,help,parents,kind,classmates,online,best,ways,support"), + ("口语-P1 022401", "tblRGv7k4WH58Jgq", "022401", S1_022401, S1_022401_T1, S1_022401_T2, "", + "want,only,draw,garden,understand,homework,start,plan,until,finish,TV", + "weekend,only,relax,disagree,friend,listen,start,new,skill,practise,until"), + ] + + results = [] + for label, tid, qsid, jd, t1, t2, pic, kw1, kw2 in records: + fields = { + "题目集合 ID": qsid, + "dataStatus": "0", + "jsonData": json.dumps(jd, ensure_ascii=False), + "题目1": t1, + } + if t2: + fields["题目2"] = t2 + if pic: + fields["图片描述"] = pic + if kw1: + fields["题目1热词"] = kw1 + if kw2: + fields["题目2热词"] = kw2 + + resp = create_record(token, tid, fields) + code = resp.get("code") + if code == 0: + rid = resp["data"]["record"]["record_id"] + # Verify + time.sleep(0.5) + v = verify(token, tid, rid, qsid) + print(f"[{'✅' if '✅' in v else '❌'}] {label} → {rid}") + print(f" {v}") + results.append((label, qsid, rid, "✅" in v)) + else: + print(f"[❌] {label} FAILED: code={code} msg={resp.get('msg')}") + results.append((label, qsid, "FAIL", False)) + + # Summary + ok = sum(1 for r in results if r[3]) + print(f"\n=== 完成: {ok}/{len(results)} 条创建成功 ===") + +if __name__ == "__main__": + main() diff --git a/scripts/write_audit_results.py b/scripts/write_audit_results.py new file mode 100644 index 0000000..6e6259f --- /dev/null +++ b/scripts/write_audit_results.py @@ -0,0 +1,186 @@ +#!/usr/bin/env python3 +""" +将审校结果写回单元挑战多维表格的"审校结果"列 +""" +import json, subprocess, os, sys + +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +WORKSPACE = os.path.dirname(SCRIPT_DIR) +BITABLE_SCRIPT = os.path.join(WORKSPACE, "skills/lark_bitable_operate_as_bot/scripts/operate_bitable.sh") +APP_TOKEN = "CMHSbUUjka3TrUsaxxEc297ongf" + +TABLES = { + "听力-P1-图片选择题": "tbliZAhcc9C43B23", + "听力-P2-表格填空题": "tblzTLNH7f13uWQN", + "听力-P4-短对话选择题": "tblVmeDtBDKsAEfz", + "听力-P5-信息匹配题": "tblDssVmhGzc3UKd", + "听力-P6-听力选图": "tbloiMcD0sBtGSTq", + "听力-P7-听力拖拽": "tbly9SvPEa44k3yX", +} + +KNOWN_ABILITY_LABELS = { + "显性事实理解|关键词识别", + "显性事实理解|单句信息点抓取", + "显性细节理解|数字/时间/地点", + "多特征整合", + "语用推断", + "干扰抑制|多信息筛选", + "多句保持|信息整合", + "语用推断|否定与纠错", + "听觉抓取关键信息", + "问题意图识别", + "关键细节听辨", + "图像语义对齐", + "近义改写", + "否定与纠错", +} + +def fetch_all(): + all_recs = {} + for tname, tid in TABLES.items(): + result = subprocess.run( + ["bash", BITABLE_SCRIPT, "list_records", APP_TOKEN, tid, "200"], + capture_output=True, text=True, timeout=60 + ) + try: + data = json.loads(result.stdout) + if data.get("code") == 0: + all_recs[tname] = data["data"]["items"] + except: + all_recs[tname] = [] + return all_recs + +def audit_record(rec): + """Returns (result_text, has_errors)""" + issues = [] + fields = rec.get("fields", {}) + jd_raw = fields.get("jsonData") + qs_id = fields.get("题目集合 ID", "") + + if not jd_raw: + return None, False # empty record, skip + + try: + parsed = json.loads(jd_raw) + except json.JSONDecodeError as e: + return f"❌ jsonData JSON解析失败: {e}", True + + first = parsed.get("first", {}) + second = parsed.get("second", {}) + qtype = first.get("type", "unknown") + f_qsid = first.get("questionSetID", "") + s_qsid = second.get("questionSetID", "") + + # Check questionSetID consistency + if qs_id and f_qsid and f_qsid != qs_id: + issues.append(f" ❌ first questionSetID({f_qsid})与字段'题目集合 ID'({qs_id})不一致") + if qs_id and s_qsid and s_qsid != qs_id: + issues.append(f" ❌ second questionSetID({s_qsid})与字段'题目集合 ID'({qs_id})不一致") + + if f_qsid == "000001": + issues.append(f" ❌ questionSetID为000001(占位数据)") + + if qs_id and not qs_id.isdigit(): + issues.append(f" ❌ 题目集合 ID异常: '{qs_id}'") + + # Check each block + for bname, block in [("first", first), ("second", second)]: + qs = block.get("questionSet", []) + if not isinstance(qs, list) or len(qs) == 0: + if block: # non-empty block + issues.append(f" ❌ {bname}.questionSet为空") + continue + + for i, q in enumerate(qs): + # explanation + expl = q.get("explanation", "") + if not expl or expl.strip() == "": + issues.append(f" ❌ {bname}[{i}]: explanation为空") + elif len(expl) < 20: + issues.append(f" 🟡 {bname}[{i}]: explanation过短({len(expl)}字)") + + # ability + ability = q.get("ability", []) + if not ability: + issues.append(f" ❌ {bname}[{i}]: ability为空") + else: + for a in ability: + if isinstance(a, str) and "¥¥" in a: + issues.append(f" ❌ {bname}[{i}]: ability使用¥¥分隔符: '{a[:60]}...'") + break + if isinstance(a, str) and a not in KNOWN_ABILITY_LABELS: + if "|" not in a and len(a) > 5: + issues.append(f" 🟡 {bname}[{i}]: ability非标准: '{a}'") + + # Check text fields + text1 = None + text2 = None + for k in ["题目1 完整配置", "题目1", "题目完整配置"]: + if fields.get(k): + text1 = fields[k] + break + for k in ["题目2 完整配置", "题目2"]: + if fields.get(k): + text2 = fields[k] + break + + if not text1: + issues.append(f" ❌ 题目1文本字段为空") + if second and second.get("questionSet") and not text2: + issues.append(f" 🟡 题目2文本字段为空(但jsonData有second块)") + + if not issues: + return f"✅ 审校通过\n题型:{qtype} | 题组:first={len(first.get('questionSet',[]))}题 second={len(second.get('questionSet',[]))}题", False + else: + header = f"❌ 审校发现问题({len(issues)}项)\n题型:{qtype} | 题组:first={len(first.get('questionSet',[]))}题 second={len(second.get('questionSet',[]))}题\n" + return header + "\n".join(issues), True + +def write_result(table_id, record_id, result_text): + """Write audit result to bitable""" + payload = json.dumps({"审校结果": result_text}) + result = subprocess.run( + ["bash", BITABLE_SCRIPT, "update_record", APP_TOKEN, table_id, record_id, payload], + capture_output=True, text=True, timeout=30 + ) + return "success" in result.stdout + +def main(): + all_recs = fetch_all() + total_err = 0 + total_ok = 0 + total_skipped = 0 + + for tname, records in all_recs.items(): + tid = TABLES[tname] + print(f"\n--- {tname} ---") + + for rec in records: + rid = rec["record_id"] + fields = rec.get("fields", {}) + ds = fields.get("dataStatus") + + if ds != "0" or not fields.get("jsonData"): + total_skipped += 1 + continue + + result_text, has_err = audit_record(rec) + if result_text is None: + total_skipped += 1 + continue + + # Write result + ok = write_result(tid, rid, result_text) + status = "✅" if ok else "❌写入失败" + label = "🔴" if has_err else "✅" + print(f" {label} {rid}: {status}") + + if has_err: + total_err += 1 + else: + total_ok += 1 + + print(f"\n{'='*40}") + print(f"汇总: ✅通过={total_ok}, 🔴问题={total_err}, ⏭️跳过={total_skipped}") + +if __name__ == "__main__": + main() diff --git a/scripts/write_audit_results_v2.py b/scripts/write_audit_results_v2.py new file mode 100644 index 0000000..ab319da --- /dev/null +++ b/scripts/write_audit_results_v2.py @@ -0,0 +1,176 @@ +#!/usr/bin/env python3 +""" +直接通过Python requests将审校结果写回单元挑战多维表格 +""" +import json, requests, sys, os + +APP_TOKEN = "CMHSbUUjka3TrUsaxxEc297ongf" +APP_ID = "cli_a931175d41799cc7" +APP_SECRET = "Iw2vEfbjT6GtV0GhbxbZqfQ4nAPtbR14" +BASE = "https://open.feishu.cn/open-apis/bitable/v1" + +TABLES = { + "听力-P1-图片选择题": "tbliZAhcc9C43B23", + "听力-P2-表格填空题": "tblzTLNH7f13uWQN", + "听力-P4-短对话选择题": "tblVmeDtBDKsAEfz", + "听力-P5-信息匹配题": "tblDssVmhGzc3UKd", + "听力-P6-听力选图": "tbloiMcD0sBtGSTq", + "听力-P7-听力拖拽": "tbly9SvPEa44k3yX", +} + +KNOWN_ABILITY_LABELS = { + "显性事实理解|关键词识别", "显性事实理解|单句信息点抓取", + "显性细节理解|数字/时间/地点", "多特征整合", "语用推断", + "干扰抑制|多信息筛选", "多句保持|信息整合", + "语用推断|否定与纠错", "听觉抓取关键信息", + "问题意图识别", "关键细节听辨", "图像语义对齐", + "近义改写", "否定与纠错", +} + +def get_token(): + r = requests.post("https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal", + json={"app_id": APP_ID, "app_secret": APP_SECRET}) + return r.json()["tenant_access_token"] + +def fetch_records(token, table_id): + all_items = [] + page_token = None + while True: + url = f"{BASE}/apps/{APP_TOKEN}/tables/{table_id}/records?page_size=200" + if page_token: + url += f"&page_token={page_token}" + r = requests.get(url, headers={"Authorization": f"Bearer {token}"}) + data = r.json() + if data.get("code") != 0: + print(f" Fetch error: {data}", file=sys.stderr) + break + all_items.extend(data["data"]["items"]) + if not data["data"].get("has_more"): + break + page_token = data["data"].get("page_token") + return all_items + +def write_record(token, table_id, record_id, result_text): + r = requests.put( + f"{BASE}/apps/{APP_TOKEN}/tables/{table_id}/records/{record_id}", + headers={ + "Authorization": f"Bearer {token}", + "Content-Type": "application/json" + }, + json={"fields": {"审校结果": result_text}} + ) + data = r.json() + return data.get("code") == 0 + +def audit_record(rec): + issues = [] + fields = rec.get("fields", {}) + jd_raw = fields.get("jsonData") + qs_id = fields.get("题目集合 ID", "") + + if not jd_raw: + return None, False + + try: + parsed = json.loads(jd_raw) + except: + return "❌ jsonData JSON解析失败", True + + first = parsed.get("first", {}) + second = parsed.get("second", {}) + qtype = first.get("type", "unknown") + f_qsid = first.get("questionSetID", "") + s_qsid = second.get("questionSetID", "") + + if qs_id and f_qsid and f_qsid != qs_id: + issues.append(f" ❌ first questionSetID({f_qsid})与字段({qs_id})不一致") + if qs_id and s_qsid and s_qsid != qs_id: + issues.append(f" ❌ second questionSetID({s_qsid})与字段({qs_id})不一致") + if f_qsid == "000001": + issues.append(f" ❌ questionSetID为000001(占位数据)") + if qs_id and not qs_id.replace("-","").isdigit() and qs_id != "000001": + issues.append(f" ❌ 题目集合 ID异常: '{qs_id}'") + + for bname, block in [("first", first), ("second", second)]: + qs = block.get("questionSet", []) + if not isinstance(qs, list) or len(qs) == 0: + continue + + for i, q in enumerate(qs): + expl = q.get("explanation", "") + if not expl or expl.strip() == "": + issues.append(f" ❌ {bname}[{i}]: explanation为空") + elif len(expl) < 20: + issues.append(f" 🟡 {bname}[{i}]: explanation过短({len(expl)}字)") + + ability = q.get("ability", []) + if not ability: + issues.append(f" ❌ {bname}[{i}]: ability为空") + else: + found_bad_sep = False + for a in ability: + if isinstance(a, str) and "¥¥" in a: + if not found_bad_sep: + issues.append(f" ❌ {bname}[{i}]: ability用¥¥分隔(应为逗号)") + found_bad_sep = True + + text1 = None + text2 = None + for k in ["题目1 完整配置", "题目1", "题目完整配置"]: + if fields.get(k): + text1 = fields[k] + break + for k in ["题目2 完整配置", "题目2"]: + if fields.get(k): + text2 = fields[k] + break + + if not text1: + issues.append(f" ❌ 题目1文本字段为空") + if second and second.get("questionSet") and not text2: + issues.append(f" 🟡 题目2文本字段为空(但jsonData有second)") + + if not issues: + return f"✅ 审校通过\n题型:{qtype} | 题组:first={len(first.get('questionSet',[]))}题 second={len(second.get('questionSet',[]))}题", False + else: + return f"❌ 审校发现问题({len(issues)}项)\n题型:{qtype} | 题组:first={len(first.get('questionSet',[]))}题 second={len(second.get('questionSet',[]))}题\n" + "\n".join(issues), True + +def main(): + token = get_token() + print(f"Token获取成功") + + total_err = total_ok = total_skip = 0 + + for tname, tid in TABLES.items(): + print(f"\n--- {tname} ---") + records = fetch_records(token, tid) + + for rec in records: + rid = rec["record_id"] + fields = rec.get("fields", {}) + ds = fields.get("dataStatus") + + if ds != "0" or not fields.get("jsonData"): + total_skip += 1 + continue + + result_text, has_err = audit_record(rec) + if result_text is None: + total_skip += 1 + continue + + if write_record(token, tid, rid, result_text): + tag = "🔴" if has_err else "✅" + print(f" {tag} {rid} ✓") + if has_err: + total_err += 1 + else: + total_ok += 1 + else: + print(f" ❌ {rid} 写入失败") + + print(f"\n{'='*40}") + print(f"✅通过={total_ok}, 🔴问题={total_err}, ⏭️跳过={total_skip}") + +if __name__ == "__main__": + main() diff --git a/scripts/write_audit_results_v3.py b/scripts/write_audit_results_v3.py new file mode 100644 index 0000000..3c506eb --- /dev/null +++ b/scripts/write_audit_results_v3.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 +"""写审校结果到单元挑战听力表""" +import requests, json, sys, time + +APP_TOKEN = "CMHSbUUjka3TrUsaxxEc297ongf" +APP_ID = "cli_a931175d41799cc7" +APP_SECRET = "Iw2vEfbjT6GtV0GhbxbZqfQ4nAPtbR14" +BASE = "https://open.feishu.cn/open-apis/bitable/v1" + +TABLES = [ + ("听力-P1-图片选择题", "tbliZAhcc9C43B23"), + ("听力-P2-表格填空题", "tblzTLNH7f13uWQN"), + ("听力-P4-短对话选择题", "tblVmeDtBDKsAEfz"), + ("听力-P5-信息匹配题", "tblDssVmhGzc3UKd"), + ("听力-P6-听力选图", "tbloiMcD0sBtGSTq"), + ("听力-P7-听力拖拽", "tbly9SvPEa44k3yX"), +] + +def get_token(): + r = requests.post( + "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal", + json={"app_id": APP_ID, "app_secret": APP_SECRET}, timeout=10 + ) + return r.json()["tenant_access_token"] + +def fetch_all(token, table_id): + items = [] + pt = None + while True: + url = f"{BASE}/apps/{APP_TOKEN}/tables/{table_id}/records?page_size=200" + if pt: + url += f"&page_token={pt}" + r = requests.get(url, headers={"Authorization": f"Bearer {token}"}, timeout=30) + d = r.json() + if d.get("code") != 0: + print(f" fetch error: {d.get('msg')}", file=sys.stderr) + break + items.extend(d["data"]["items"]) + if not d["data"].get("has_more"): + break + pt = d["data"].get("page_token") + return items + +def audit(rec): + issues = [] + fld = rec.get("fields", {}) + jd = fld.get("jsonData") + qsid = fld.get("题目集合 ID", "") + if not jd: + return None + try: + p = json.loads(jd) + except: + return ("❌ jsonData解析失败", True) + + f = p.get("first", {}) + s = p.get("second", {}) + qt = f.get("type", "?") + fq = f.get("questionSetID", "") + sq = s.get("questionSetID", "") + + if qsid and fq and fq != qsid: + issues.append(f" ❌ first QSID({fq})≠字段({qsid})") + if qsid and sq and sq != qsid: + issues.append(f" ❌ second QSID({sq})≠字段({qsid})") + if fq == "000001": + issues.append(f" ❌ QSID=000001") + if qsid and not qsid.replace("-","").isdigit() and qsid != "000001": + issues.append(f" ❌ QSID异常:'{qsid}'") + + for bn, blk in [("first", f), ("second", s)]: + qs = blk.get("questionSet", []) + if not isinstance(qs, list) or not qs: + continue + for i, q in enumerate(qs): + e = q.get("explanation", "") + if not e or not e.strip(): + issues.append(f" ❌ {bn}[{i}]: explanation空") + elif len(e) < 20: + issues.append(f" 🟡 {bn}[{i}]: explanation短({len(e)}字)") + ab = q.get("ability", []) + if not ab: + issues.append(f" ❌ {bn}[{i}]: ability空") + else: + for a in ab: + if isinstance(a, str) and "¥¥" in a: + issues.append(f" ❌ {bn}[{i}]: ability¥¥分隔") + break + + t1 = fld.get("题目1 完整配置") or fld.get("题目1") or fld.get("题目完整配置") + t2 = fld.get("题目2 完整配置") or fld.get("题目2") + if not t1: + issues.append(f" ❌ 题目1字段空") + if s and s.get("questionSet") and not t2: + issues.append(f" 🟡 题目2字段空") + + if not issues: + return (f"✅ 审校通过\n题型:{qt} | first={len(f.get('questionSet',[]))}题 second={len(s.get('questionSet',[]))}题", False) + hdr = f"❌ 审校发现问题({len(issues)}项)\n题型:{qt} | first={len(f.get('questionSet',[]))}题 second={len(s.get('questionSet',[]))}题\n" + return (hdr + "\n".join(issues), True) + +def main(): + token = get_token() + print(f"✅ Token OK") + + err = ok = skip = 0 + for tname, tid in TABLES: + print(f"\n📋 {tname}", flush=True) + recs = fetch_all(token, tid) + print(f" 记录:{len(recs)}", flush=True) + + for rec in recs: + rid = rec["record_id"] + fld = rec.get("fields", {}) + if fld.get("dataStatus") != "0" or not fld.get("jsonData"): + skip += 1 + continue + + r = audit(rec) + if r is None: + skip += 1 + continue + txt, has_err = r + + r2 = requests.put( + f"{BASE}/apps/{APP_TOKEN}/tables/{tid}/records/{rid}", + headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"}, + json={"fields": {"审校结果": txt}}, timeout=15 + ) + if r2.json().get("code") == 0: + tag = "🔴" if has_err else "✅" + print(f" {tag} {rid}", flush=True) + if has_err: err += 1 + else: ok += 1 + else: + print(f" ❌ {rid} write fail: {r2.json().get('msg')}", flush=True) + time.sleep(0.08) # rate limit + + print(f"\n✅={ok} 🔴={err} ⏭️={skip}") + +if __name__ == "__main__": + main()