From 3a5d8ca59badb769f270f2d8d168e2d712146579 Mon Sep 17 00:00:00 2001 From: ai_member_only Date: Thu, 14 May 2026 08:10:01 +0800 Subject: [PATCH] auto backup: 2026-05-14 08:10:01 --- MEMORY.md | 3 + business_knowledge/ability_tags.md | 77 +++ .../review/kids-english-script-production.md | 58 ++ .../final/11-4组件生成结果.md | 0 .../final/11-5_组件配置已填充.xlsx | Bin .../11-5_组件配置已填充_GPT5.4.xlsx | Bin .../final/12-4_已填充组件.xlsx | Bin .../skills/component-design/SKILL.md | 21 +- .../dialogue-interaction-config/SKILL.md | 0 .../script-component-production/SKILL.md | 238 ++++++++ .../assets/component_rules.yaml | 105 ++++ .../assets/review_checklist.yaml | 140 +++++ .../assets/skill.yml | 50 ++ .../vala-interactive-components/SKILL.md | 0 .../interactive-component-json/SKILL.md | 0 .../interactive-component-json/config.json | 0 .../db/components.db | Bin ...eball Showdown 副本_20260508_180543.html | 0 ...eball Showdown 副本_20260508_192352.html | 0 ...录入测试-全量__20260507_104229.html | 0 ...录入测试-全量__20260507_105232.html | 0 ...录入测试-全量__20260507_113133.html | 0 ...录入测试-全量__20260507_121327.html | 0 ...录入测试-全量__20260508_123414.html | 0 ...录入测试-全量__20260508_154553.html | 0 ...录入测试-全量__20260508_160739.html | 0 ...录入测试-全量__20260508_170257.html | 0 ...录入测试-全量__20260508_172722.html | 0 ...录入测试-全量__20260508_193259.html | 0 ...录入测试-全量__20260509_103808.html | 0 ...录入测试-全量__20260509_105913.html | 0 ...录入测试-全量__20260509_111203.html | 0 ...录入测试-全量__20260509_144029.html | 0 ...入测试-非全量__20260506_181630.html | 0 ...入测试-非全量__20260506_191146.html | 0 ...入测试-非全量__20260506_192024.html | 0 ...入测试-非全量__20260506_195832.html | 0 ...入测试-非全量__20260506_201143.html | 0 ...入测试-非全量__20260506_202855.html | 0 ...入测试-非全量__20260506_203642.html | 0 ...入测试-非全量__20260506_225623.html | 0 ...入测试-非全量__20260506_230815.html | 0 ...入测试-非全量__20260506_231232.html | 0 ...入测试-非全量__20260507_103630.html | 0 ... _组件录入测试__20260430_121620.html | 0 ... _组件录入测试__20260430_122912.html | 0 ... _组件录入测试__20260430_124310.html | 0 ... _组件录入测试__20260430_124455.html | 0 ... _组件录入测试__20260430_125241.html | 0 ... _组件录入测试__20260430_134502.html | 0 ... _组件录入测试__20260430_141051.html | 0 ... _组件录入测试__20260430_141550.html | 0 ... _组件录入测试__20260430_142631.html | 0 ... _组件录入测试__20260430_142814.html | 0 ... _组件录入测试__20260430_143640.html | 0 ... _组件录入测试__20260430_144357.html | 0 ...14-L1 到你上学啦_20260428_152354.html | 0 ...14-L1 到你上学啦_20260428_153018.html | 0 ...2-U19-L1 客厅游戏_20260508_184341.html | 0 ...2-U14-L1 测试文档_20260429_175719.html | 0 ... imageDrag _ speaking_20260428_192548.html | 0 .../outputs/_20260429_175651.html | 0 ... - S3 Record 0325001_20260428_174634.html | 0 .../outputs/v5_both_validation.json | 0 .../outputs/v5_imagedrag_validation.json | 0 .../outputs/v5_speaking_validation.json | 0 .../outputs/validate_image.json | 0 .../outputs/validate_inquiry.json | 0 .../interactive-component-json/prd.md | 0 .../interactive-component-json/prd/v10.md | 0 .../interactive-component-json/prd/v11.md | 0 .../interactive-component-json/prd/v3.md | 0 .../interactive-component-json/prd/v4.md | 0 .../interactive-component-json/prd/v5.md | 0 .../interactive-component-json/prd/v6.md | 0 .../interactive-component-json/prd/v7.md | 0 .../interactive-component-json/prd/v8.md | 0 .../interactive-component-json/prd/v9.md | 0 .../prd/xiaoyan_comp_json_skill.md | 0 .../prd/xiaoyan_comp_json_skill_v2.md | 0 .../interactive-component-json/project.md | 0 .../prompts/README.md | 0 .../prompts/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin .../core_imagedrag_prompts.cpython-312.pyc | Bin ...e_listening_choose_prompts.cpython-312.pyc | Bin ...ore_listening_drag_prompts.cpython-312.pyc | Bin ...re_listening_order_prompts.cpython-312.pyc | Bin .../core_reading_prompts.cpython-312.pyc | Bin ...ore_speaking_image_prompts.cpython-312.pyc | Bin ...e_speaking_inquiry_prompts.cpython-312.pyc | Bin .../core_speaking_prompts.cpython-312.pyc | Bin .../core_writing_img_prompts.cpython-312.pyc | Bin ...e_writing_question_prompts.cpython-312.pyc | Bin .../prompt_registry.cpython-312.pyc | Bin .../prompts/core_imagedrag_prompts.py | 0 .../prompts/core_listening_choose_prompts.py | 0 .../prompts/core_listening_drag_prompts.py | 0 .../prompts/core_listening_order_prompts.py | 0 .../prompts/core_reading_prompts.py | 0 .../prompts/core_speaking_image_prompts.py | 0 .../prompts/core_speaking_inquiry_prompts.py | 0 .../prompts/core_speaking_prompts.py | 0 .../prompts/core_writing_img_prompts.py | 0 .../prompts/core_writing_question_prompts.py | 0 .../prompts/prompt_registry.py | 0 .../references/component_registry.md | 0 .../examples/all_types_examples.json | 0 .../references/json_output_spec.md | 0 .../references/json_structure_guide.md | 0 .../references/script_format_spec.md | 0 .../__pycache__/db_manager.cpython-312.pyc | Bin .../__pycache__/feishu_client.cpython-312.pyc | Bin .../__pycache__/generate_json.cpython-312.pyc | Bin .../__pycache__/html_report.cpython-312.pyc | Bin .../__pycache__/kp_matcher.cpython-312.pyc | Bin .../__pycache__/llm_client.cpython-312.pyc | Bin .../match_component.cpython-312.pyc | Bin .../__pycache__/parse_script.cpython-312.pyc | Bin .../__pycache__/pipeline.cpython-312.pyc | Bin .../scripts/db_manager.py | 0 .../scripts/feishu_client.py | 0 .../scripts/generate_json.py | 0 .../scripts/html_report.py | 0 .../scripts/kp_matcher.py | 0 .../scripts/llm_client.py | 0 .../scripts/match_component.py | 0 .../scripts/parse_script.py | 0 .../scripts/pipeline.py | 0 .../scripts/proxy_server.py | 0 .../scripts/validate_core_v5.py | 0 .../final/11-5_script.xlsx | Bin .../final/11-5_剧本定稿.xlsx | Bin .../final/script_meixu.xlsx | Bin .../final/unit11_content.md | 0 .../production/11-5_jiaoyantu.txt | 0 .../production/11-5_美需已填写.xlsx | Bin .../production/jiaoyantu.txt | 0 .../feishu-table-translate-fill/SKILL.md | 0 .../references/usage.md | 0 .../scripts/feishu_io.py | 0 .../scripts/translate_fill.py | 0 .../scripts/write_translations.py | 0 .../kids-english-script-production/SKILL.md | 15 +- .../assets/expression_map.yaml | 0 .../assets/prompt_config.yaml | 0 .../assets/sci_fi_map.yaml | 0 .../assets/skill.yml | 9 +- .../assets/stage_config.yaml | 0 .../assets/validation_config.yaml | 0 .../assets/调优指南.md | 0 .../examples/sample_output.txt | 0 .../examples/sample_script.txt | 0 .../references/l1_word_list.json | 0 .../scripts/gen_script.py | 0 .../课程组件/review/component-design.md | 9 + .../review/script-component-production.md | 73 +++ memory/.dreams/events.jsonl | 6 + memory/.dreams/short-term-recall.json | 389 +++++++++++- memory/2026-05-13-1230.md | 149 +++++ memory/2026-05-13-1538.md | 131 ++++ memory/2026-05-13.md | 44 ++ scripts/audit_listening_full.py | 278 +++++++++ scripts/audit_unit_challenge_reading.py | 135 ++++ scripts/audit_unit_challenge_v2.py | 246 ++++++++ scripts/audit_unit_challenge_v3.py | 206 +++++++ scripts/backfill_audit.py | 93 +++ scripts/backfill_fixed.py | 110 ++++ scripts/backfill_listening_audit.py | 56 ++ scripts/backfill_unit_challenge.py | 78 +++ scripts/dialogue_backfill.py | 264 ++++++++ scripts/dialogue_backfill_v2.py | 235 +++++++ scripts/final_audit_all.py | 144 +++++ scripts/final_fix.py | 156 +++++ scripts/fix_ability_tags.py | 253 ++++++++ scripts/fix_ability_tags_v2.py | 230 +++++++ scripts/fix_and_backfill.py | 194 ++++++ scripts/fix_and_backfill_v2.py | 187 ++++++ scripts/fix_and_backfill_v3.py | 175 ++++++ scripts/fix_json_and_p5p6.py | 332 ++++++++++ scripts/fix_remaining.py | 138 +++++ scripts/gen_unit_challenge_spk_wrt.py | 574 ++++++++++++++++++ scripts/generate_explanations.py | 177 ++++++ scripts/scan_missing_explanations.py | 132 ++++ 184 files changed, 5901 insertions(+), 9 deletions(-) create mode 100644 business_knowledge/ability_tags.md create mode 100644 business_production/剧本翻译/review/kids-english-script-production.md rename business_production/{课程组件 => 组件生产}/final/11-4组件生成结果.md (100%) rename business_production/{课程组件 => 组件生产}/final/11-5_组件配置已填充.xlsx (100%) rename business_production/{课程组件 => 组件生产}/final/11-5_组件配置已填充_GPT5.4.xlsx (100%) rename business_production/{课程组件 => 组件生产}/final/12-4_已填充组件.xlsx (100%) rename business_production/{课程组件 => 组件生产}/skills/component-design/SKILL.md (87%) rename business_production/{课程组件 => 组件生产}/skills/dialogue-interaction-config/SKILL.md (100%) create mode 100644 business_production/组件生产/skills/script-component-production/SKILL.md create mode 100644 business_production/组件生产/skills/script-component-production/assets/component_rules.yaml create mode 100644 business_production/组件生产/skills/script-component-production/assets/review_checklist.yaml create mode 100644 business_production/组件生产/skills/script-component-production/assets/skill.yml rename business_production/{课程组件 => 组件生产}/skills/vala-interactive-components/SKILL.md (100%) rename {skills => business_production/组件配置}/interactive-component-json/SKILL.md (100%) rename {skills => business_production/组件配置}/interactive-component-json/config.json (100%) rename {skills => business_production/组件配置}/interactive-component-json/db/components.db (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/L1-S1-U11-L4 王牌棒球 Ace Baseball Showdown 副本_20260508_180543.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/L1-S1-U11-L4 王牌棒球 Ace Baseball Showdown 副本_20260508_192352.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260507_104229.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260507_105232.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260507_113133.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260507_121327.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260508_123414.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260508_154553.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260508_160739.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260508_170257.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260508_172722.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260508_193259.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260509_103808.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260509_105913.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260509_111203.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260509_144029.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_181630.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_191146.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_192024.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_195832.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_201143.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_202855.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_203642.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_225623.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_230815.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_231232.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260507_103630.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_121620.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_122912.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_124310.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_124455.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_125241.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_134502.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_141051.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_141550.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_142631.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_142814.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_143640.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_144357.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦_20260428_152354.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦_20260428_153018.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/L1-S2-U19-L1 客厅游戏_20260508_184341.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/L2-S2-U14-L1 测试文档_20260429_175719.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/V5核心互动验证 - imageDrag _ speaking_20260428_192548.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/_20260429_175651.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/core_reading_order 测试 - S3 Record 0325001_20260428_174634.html (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/v5_both_validation.json (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/v5_imagedrag_validation.json (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/v5_speaking_validation.json (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/validate_image.json (100%) rename {skills => business_production/组件配置}/interactive-component-json/outputs/validate_inquiry.json (100%) rename {skills => business_production/组件配置}/interactive-component-json/prd.md (100%) rename {skills => business_production/组件配置}/interactive-component-json/prd/v10.md (100%) rename {skills => business_production/组件配置}/interactive-component-json/prd/v11.md (100%) rename {skills => business_production/组件配置}/interactive-component-json/prd/v3.md (100%) rename {skills => business_production/组件配置}/interactive-component-json/prd/v4.md (100%) rename {skills => business_production/组件配置}/interactive-component-json/prd/v5.md (100%) rename {skills => business_production/组件配置}/interactive-component-json/prd/v6.md (100%) rename {skills => business_production/组件配置}/interactive-component-json/prd/v7.md (100%) rename {skills => business_production/组件配置}/interactive-component-json/prd/v8.md (100%) rename {skills => business_production/组件配置}/interactive-component-json/prd/v9.md (100%) rename {skills => business_production/组件配置}/interactive-component-json/prd/xiaoyan_comp_json_skill.md (100%) rename {skills => business_production/组件配置}/interactive-component-json/prd/xiaoyan_comp_json_skill_v2.md (100%) rename {skills => business_production/组件配置}/interactive-component-json/project.md (100%) rename {skills => business_production/组件配置}/interactive-component-json/prompts/README.md (100%) rename {skills => business_production/组件配置}/interactive-component-json/prompts/__init__.py (100%) rename {skills => business_production/组件配置}/interactive-component-json/prompts/__pycache__/__init__.cpython-312.pyc (100%) rename {skills => business_production/组件配置}/interactive-component-json/prompts/__pycache__/core_imagedrag_prompts.cpython-312.pyc (100%) rename {skills => business_production/组件配置}/interactive-component-json/prompts/__pycache__/core_listening_choose_prompts.cpython-312.pyc (100%) rename {skills => business_production/组件配置}/interactive-component-json/prompts/__pycache__/core_listening_drag_prompts.cpython-312.pyc (100%) rename {skills => business_production/组件配置}/interactive-component-json/prompts/__pycache__/core_listening_order_prompts.cpython-312.pyc (100%) rename {skills => business_production/组件配置}/interactive-component-json/prompts/__pycache__/core_reading_prompts.cpython-312.pyc (100%) rename {skills => business_production/组件配置}/interactive-component-json/prompts/__pycache__/core_speaking_image_prompts.cpython-312.pyc (100%) rename {skills => business_production/组件配置}/interactive-component-json/prompts/__pycache__/core_speaking_inquiry_prompts.cpython-312.pyc (100%) rename {skills => business_production/组件配置}/interactive-component-json/prompts/__pycache__/core_speaking_prompts.cpython-312.pyc (100%) rename {skills => business_production/组件配置}/interactive-component-json/prompts/__pycache__/core_writing_img_prompts.cpython-312.pyc (100%) rename {skills => business_production/组件配置}/interactive-component-json/prompts/__pycache__/core_writing_question_prompts.cpython-312.pyc (100%) rename {skills => business_production/组件配置}/interactive-component-json/prompts/__pycache__/prompt_registry.cpython-312.pyc (100%) rename {skills => business_production/组件配置}/interactive-component-json/prompts/core_imagedrag_prompts.py (100%) rename {skills => business_production/组件配置}/interactive-component-json/prompts/core_listening_choose_prompts.py (100%) rename {skills => business_production/组件配置}/interactive-component-json/prompts/core_listening_drag_prompts.py (100%) rename {skills => business_production/组件配置}/interactive-component-json/prompts/core_listening_order_prompts.py (100%) rename {skills => business_production/组件配置}/interactive-component-json/prompts/core_reading_prompts.py (100%) rename {skills => business_production/组件配置}/interactive-component-json/prompts/core_speaking_image_prompts.py (100%) rename {skills => business_production/组件配置}/interactive-component-json/prompts/core_speaking_inquiry_prompts.py (100%) rename {skills => business_production/组件配置}/interactive-component-json/prompts/core_speaking_prompts.py (100%) rename {skills => business_production/组件配置}/interactive-component-json/prompts/core_writing_img_prompts.py (100%) rename {skills => business_production/组件配置}/interactive-component-json/prompts/core_writing_question_prompts.py (100%) rename {skills => business_production/组件配置}/interactive-component-json/prompts/prompt_registry.py (100%) rename {skills => business_production/组件配置}/interactive-component-json/references/component_registry.md (100%) rename {skills => business_production/组件配置}/interactive-component-json/references/examples/all_types_examples.json (100%) rename {skills => business_production/组件配置}/interactive-component-json/references/json_output_spec.md (100%) rename {skills => business_production/组件配置}/interactive-component-json/references/json_structure_guide.md (100%) rename {skills => business_production/组件配置}/interactive-component-json/references/script_format_spec.md (100%) rename {skills => business_production/组件配置}/interactive-component-json/scripts/__pycache__/db_manager.cpython-312.pyc (100%) rename {skills => business_production/组件配置}/interactive-component-json/scripts/__pycache__/feishu_client.cpython-312.pyc (100%) rename {skills => business_production/组件配置}/interactive-component-json/scripts/__pycache__/generate_json.cpython-312.pyc (100%) rename {skills => business_production/组件配置}/interactive-component-json/scripts/__pycache__/html_report.cpython-312.pyc (100%) rename {skills => business_production/组件配置}/interactive-component-json/scripts/__pycache__/kp_matcher.cpython-312.pyc (100%) rename {skills => business_production/组件配置}/interactive-component-json/scripts/__pycache__/llm_client.cpython-312.pyc (100%) rename {skills => business_production/组件配置}/interactive-component-json/scripts/__pycache__/match_component.cpython-312.pyc (100%) rename {skills => business_production/组件配置}/interactive-component-json/scripts/__pycache__/parse_script.cpython-312.pyc (100%) rename {skills => business_production/组件配置}/interactive-component-json/scripts/__pycache__/pipeline.cpython-312.pyc (100%) rename {skills => business_production/组件配置}/interactive-component-json/scripts/db_manager.py (100%) rename {skills => business_production/组件配置}/interactive-component-json/scripts/feishu_client.py (100%) rename {skills => business_production/组件配置}/interactive-component-json/scripts/generate_json.py (100%) rename {skills => business_production/组件配置}/interactive-component-json/scripts/html_report.py (100%) rename {skills => business_production/组件配置}/interactive-component-json/scripts/kp_matcher.py (100%) rename {skills => business_production/组件配置}/interactive-component-json/scripts/llm_client.py (100%) rename {skills => business_production/组件配置}/interactive-component-json/scripts/match_component.py (100%) rename {skills => business_production/组件配置}/interactive-component-json/scripts/parse_script.py (100%) rename {skills => business_production/组件配置}/interactive-component-json/scripts/pipeline.py (100%) rename {skills => business_production/组件配置}/interactive-component-json/scripts/proxy_server.py (100%) rename {skills => business_production/组件配置}/interactive-component-json/scripts/validate_core_v5.py (100%) rename business_production/{剧本翻译 => 英文台词}/final/11-5_script.xlsx (100%) rename business_production/{剧本翻译 => 英文台词}/final/11-5_剧本定稿.xlsx (100%) rename business_production/{剧本翻译 => 英文台词}/final/script_meixu.xlsx (100%) rename business_production/{剧本翻译 => 英文台词}/final/unit11_content.md (100%) rename business_production/{剧本翻译 => 英文台词}/production/11-5_jiaoyantu.txt (100%) rename business_production/{剧本翻译 => 英文台词}/production/11-5_美需已填写.xlsx (100%) rename business_production/{剧本翻译 => 英文台词}/production/jiaoyantu.txt (100%) rename business_production/{剧本翻译 => 英文台词}/skills/feishu-table-translate-fill/SKILL.md (100%) rename business_production/{剧本翻译 => 英文台词}/skills/feishu-table-translate-fill/references/usage.md (100%) rename business_production/{剧本翻译 => 英文台词}/skills/feishu-table-translate-fill/scripts/feishu_io.py (100%) rename business_production/{剧本翻译 => 英文台词}/skills/feishu-table-translate-fill/scripts/translate_fill.py (100%) rename business_production/{剧本翻译 => 英文台词}/skills/feishu-table-translate-fill/scripts/write_translations.py (100%) rename business_production/{剧本翻译 => 英文台词}/skills/kids-english-script-production/SKILL.md (86%) rename business_production/{剧本翻译 => 英文台词}/skills/kids-english-script-production/assets/expression_map.yaml (100%) rename business_production/{剧本翻译 => 英文台词}/skills/kids-english-script-production/assets/prompt_config.yaml (100%) rename business_production/{剧本翻译 => 英文台词}/skills/kids-english-script-production/assets/sci_fi_map.yaml (100%) rename business_production/{剧本翻译 => 英文台词}/skills/kids-english-script-production/assets/skill.yml (73%) rename business_production/{剧本翻译 => 英文台词}/skills/kids-english-script-production/assets/stage_config.yaml (100%) rename business_production/{剧本翻译 => 英文台词}/skills/kids-english-script-production/assets/validation_config.yaml (100%) rename business_production/{剧本翻译 => 英文台词}/skills/kids-english-script-production/assets/调优指南.md (100%) rename business_production/{剧本翻译 => 英文台词}/skills/kids-english-script-production/examples/sample_output.txt (100%) rename business_production/{剧本翻译 => 英文台词}/skills/kids-english-script-production/examples/sample_script.txt (100%) rename business_production/{剧本翻译 => 英文台词}/skills/kids-english-script-production/references/l1_word_list.json (100%) rename business_production/{剧本翻译 => 英文台词}/skills/kids-english-script-production/scripts/gen_script.py (100%) create mode 100644 business_production/课程组件/review/component-design.md create mode 100644 business_production/课程组件/review/script-component-production.md create mode 100644 memory/2026-05-13-1230.md create mode 100644 memory/2026-05-13-1538.md create mode 100644 memory/2026-05-13.md create mode 100644 scripts/audit_listening_full.py create mode 100644 scripts/audit_unit_challenge_reading.py create mode 100644 scripts/audit_unit_challenge_v2.py create mode 100644 scripts/audit_unit_challenge_v3.py create mode 100644 scripts/backfill_audit.py create mode 100644 scripts/backfill_fixed.py create mode 100644 scripts/backfill_listening_audit.py create mode 100644 scripts/backfill_unit_challenge.py create mode 100644 scripts/dialogue_backfill.py create mode 100644 scripts/dialogue_backfill_v2.py create mode 100644 scripts/final_audit_all.py create mode 100644 scripts/final_fix.py create mode 100644 scripts/fix_ability_tags.py create mode 100644 scripts/fix_ability_tags_v2.py create mode 100644 scripts/fix_and_backfill.py create mode 100644 scripts/fix_and_backfill_v2.py create mode 100644 scripts/fix_and_backfill_v3.py create mode 100644 scripts/fix_json_and_p5p6.py create mode 100644 scripts/fix_remaining.py create mode 100644 scripts/gen_unit_challenge_spk_wrt.py create mode 100644 scripts/generate_explanations.py create mode 100644 scripts/scan_missing_explanations.py diff --git a/MEMORY.md b/MEMORY.md index c8c74c6..8ead0b5 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -69,6 +69,9 @@ > 规则说明:L1题目仅使用L1词库词汇,L2题目可使用L1+L2词库词汇,禁止跨阶段超纲出题 ### 【教研规则】对话台词优化通用规则 +#### 前置规则(强制执行) +0. **知识点保留原则**:润色前必须先确认当前台词是否含有知识点词汇/句型。含有知识点的句子中,**知识点词汇/句型本身必须保留**(不得替换、增删、调整词序),但语法错误、拼写错误、标点错误可以修正。仅可调整非知识点部分的表达。 + #### 核心修改规则 1. **精简原则**:删除所有冗余重复表述(重复提到的核心名词可省略,前文已交代的背景信息不再重复) 2. **口语化原则**:所有台词符合日常对话习惯,避免书面化、生硬的表达,适当增加低龄友好的语气词(Haha、Oops、Uh-oh、Oh boy等) diff --git a/business_knowledge/ability_tags.md b/business_knowledge/ability_tags.md new file mode 100644 index 0000000..4ba0642 --- /dev/null +++ b/business_knowledge/ability_tags.md @@ -0,0 +1,77 @@ +# Vala 英语能力标签体系 + +> 来源:飞书知识库 "Vala 英语能力标签终极版本" +> 更新时间:2026-05-13 + +## 二、听力能力标签(11个) + +| # | 标签名称 | 瓦拉级别 | CEFR | 剑桥 | +|---|---------|---------|------|------| +| 1 | 显性事实理解|关键词识别 | L1 | A1 | Starters | +| 2 | 显性事实理解|单句信息点抓取 | L1 | A1 | Starters | +| 3 | 基础语境理解|场景/物品/动作识别 | L1 | A1 | Starters | +| 4 | 显性细节理解|数字/时间/地点 | L1 | A1 | Movers | +| 5 | 同义替换识别|词/短语级 | L1 | A1+ | Movers | +| 6 | 目的/偏好识别|显性 to/for/like | L1 | A1+ | Movers | +| 7 | 干扰抑制|多信息筛选 | L2 | A2 | Flyers | +| 8 | 语用推断|否定与纠错 | L2 | A2 | Flyers | +| 9 | 多句保持|信息整合 | L2 | A2 | Flyers | +| 10 | 情绪/态度理解 | L2 | A2 | KET | +| 11 | 长对话理解|主旨+细节 | L2 | A2 | KET | + +## 三、口语能力标签(10个) + +| # | 标签名称 | 瓦拉级别 | CEFR | 剑桥 | +|---|---------|---------|------|------| +| 1 | 基础信息表达|个人信息问答 | L1 | A1 | Starters | +| 2 | 图片描述|人物/物品/动作 | L1 | A1 | Starters | +| 3 | 图片描述|位置与方位 | L1 | A1+ | Movers | +| 4 | 表达喜好与理由 | L1 | A1+ | Movers | +| 5 | 图片对比|找不同 | L2 | A2 | Flyers | +| 6 | 故事讲述|看图说话 | L2 | A2 | Flyers | +| 7 | 信息交换|双向问答 | L2 | A2 | Flyers | +| 8 | 过去经历描述|Past Activities | L2 | A2 | Flyers/KET | +| 9 | 互动应答|问答交流 | L2 | A2 | KET | +| 10 | 发音与语调|单词级 | L2 | A1-A2 | All | + +## 四、阅读能力标签(13个) + +| # | 标签名称 | 瓦拉级别 | CEFR | 剑桥 | +|---|---------|---------|------|------| +| 1 | 显性信息定位|关键词识别 | L1 | A1 | Starters | +| 2 | 基础语境理解|场景/行为理解 | L1 | A1 | Starters | +| 3 | 图文判断|句图一致性 | L1 | A1 | Starters | +| 4 | 扫读定位|信息匹配 | L1 | A1+ | Movers | +| 5 | 主旨理解|段落/文本大意 | L1 | A1+ | Movers | +| 6 | 释义选词|定义匹配 | L1 | A1+ | Movers | +| 7 | 对话理解|问答匹配 | L1 | A1+ | Movers | +| 8 | 标识与通知理解|Signs & Notices | L1 | A1 | Movers | +| 9 | 细节理解|事实信息提取 | L2 | A2 | Flyers | +| 10 | 词义理解|语境义判断 | L2 | A2 | Flyers | +| 11 | 同义替换|词/短语级 | L2 | A2 | Flyers | +| 12 | 语法结构识别|完形填空 | L2 | A2 | KET | +| 13 | 推理判断|原因/结果 | L2 | A2 | KET | + +## 五、写作能力标签(11个) + +| # | 标签名称 | 瓦拉级别 | CEFR | 剑桥 | +|---|---------|---------|------|------| +| 1 | 字母书写|认写英文字母 | L1 | Pre-A1 | Starters | +| 2 | 单词拼写|看图写词 | L1 | A1 | Starters | +| 3 | 抄写能力|词组/短句 | L1 | A1 | Starters | +| 4 | 简短作答|一词答案 | L1 | A1 | Movers | +| 5 | 简单句表达|个人信息 | L1 | A1+ | Movers | +| 6 | 叙述故事|看图写故事 | L2 | A2 | Flyers | +| 7 | 短消息写作|邮件/便条 | L2 | A2 | Flyers/KET | +| 8 | 衔接与连贯|连词使用 | L2 | A2 | KET | +| 9 | 描述写作|人物/物体/场景 | L2 | A2 | KET | +| 10 | 表格信息填写|Form Completion | L2 | A2 | KET | +| 11 | 语法准确度|时态/介词/冠词 | L2 | A1-A2 | All | + +## 按级别汇总 + +| 级别 | 听力 | 口语 | 阅读 | 写作 | 合计 | +|------|------|------|------|------|------| +| L1 | 6 | 4 | 8 | 5 | 23 | +| L2 | 5 | 6 | 5 | 6 | 22 | +| 合计 | 11 | 10 | 13 | 11 | 45 | diff --git a/business_production/剧本翻译/review/kids-english-script-production.md b/business_production/剧本翻译/review/kids-english-script-production.md new file mode 100644 index 0000000..218a5e2 --- /dev/null +++ b/business_production/剧本翻译/review/kids-english-script-production.md @@ -0,0 +1,58 @@ +# kids-english-script-production — 业务侧思考 + +> 来源:[刘彦江] 2026-05-13 对话 + +## 为什么需要独立的触发机制 + +### 问题 +教研生产中有多个与"台词"相关的指令场景: +- "翻译台词" → 应该触发翻译工具,不是英文台词生产 +- "台词润色" → 应该是审校/优化,不是从零生产 +- "英文台词" → 这才是本技能要做的事 + +如果不做区分,"台词""翻译"等模糊关键词会误触发技能,导致输出结果与用户期望严重不符。 + +### 决策 +- 使用精确命令 `/英文台词` 作为主触发器 +- 禁止仅含"翻译""台词""英文"的消息触发本技能 +- 短语触发仅保留"英文台词生产"作为第二通道 + +### 设计原则 +1. **精确优先于覆盖**:宁可少触发,不可误触发 +2. **语义清晰**:`/英文台词` 明确表达"英文台词生产",与"翻译"语义区分 +3. **与其他技能隔离**:每个技能有独立不重叠的触发词空间 + +--- + +## 为什么是"英语思维重构"而不是"翻译" + +### 业务场景 +- 目标用户:4-8岁儿童 +- 英语教育内容,不是成人翻译 +- 需要的是地道的、符合儿童认知水平的英文表达 + +### 与翻译的本质区别 +| 维度 | 翻译 | 英语思维重构 | +|------|------|------------| +| 目标 | 语义对等 | 场景表达对等 | +| 方法 | 逐词/逐句映射 | 用英语母语儿童的方式重新表达 | +| 关注点 | 准确性 | 自然度 + 难度适配 | +| 结果 | 可能生硬/成人化 | 自然、符合儿童语感 | + +### 决策 +技能定位为"英语思维重构"而非"翻译",在 description 中明确标注"不是翻译!"。 + +--- + +## 为什么要与 `/组件生产` 协作但分开 + +### 业务事实 +一个剧本的两个目标列由不同技能负责: +- 列E→列F:英文台词(`/英文台词`) +- 列G:组件内容(`/组件生产`) + +### 决策 +两个技能独立触发、各自回填不同列、互不覆盖。原因是: +1. 英文台词和组件内容的生产节奏不同(台词先做,组件后做) +2. 两个技能依赖不同的规则体系(台词优化规则 vs 组件设计规范) +3. 分开执行便于教研员分阶段检查 diff --git a/business_production/课程组件/final/11-4组件生成结果.md b/business_production/组件生产/final/11-4组件生成结果.md similarity index 100% rename from business_production/课程组件/final/11-4组件生成结果.md rename to business_production/组件生产/final/11-4组件生成结果.md diff --git a/business_production/课程组件/final/11-5_组件配置已填充.xlsx b/business_production/组件生产/final/11-5_组件配置已填充.xlsx similarity index 100% rename from business_production/课程组件/final/11-5_组件配置已填充.xlsx rename to business_production/组件生产/final/11-5_组件配置已填充.xlsx diff --git a/business_production/课程组件/final/11-5_组件配置已填充_GPT5.4.xlsx b/business_production/组件生产/final/11-5_组件配置已填充_GPT5.4.xlsx similarity index 100% rename from business_production/课程组件/final/11-5_组件配置已填充_GPT5.4.xlsx rename to business_production/组件生产/final/11-5_组件配置已填充_GPT5.4.xlsx diff --git a/business_production/课程组件/final/12-4_已填充组件.xlsx b/business_production/组件生产/final/12-4_已填充组件.xlsx similarity index 100% rename from business_production/课程组件/final/12-4_已填充组件.xlsx rename to business_production/组件生产/final/12-4_已填充组件.xlsx diff --git a/business_production/课程组件/skills/component-design/SKILL.md b/business_production/组件生产/skills/component-design/SKILL.md similarity index 87% rename from business_production/课程组件/skills/component-design/SKILL.md rename to business_production/组件生产/skills/component-design/SKILL.md index 68a3a5e..9fb388e 100644 --- a/business_production/课程组件/skills/component-design/SKILL.md +++ b/business_production/组件生产/skills/component-design/SKILL.md @@ -1,4 +1,23 @@ +--- +name: component-design +version: 1.2.0 +description: 教研互动组件设计规范(结构化文本格式,不涉及JSON)。触发方式:(1) `/组件生产 --feishu-url <链接>` (2) 飞书链接 + "组件"/"设计组件"/"组件回填"。不会误触发:仅含"互动"不触发;含"组件配置-json"不触发(属于 interactive-component-json 技能)。标准化组件生产流程:设计→生成(结构化文本)→校验→回填。 +--- + # SKILL.md - 教研互动组件设计规范 + +## ⚡ 触发方式 + +| 触发方式 | 格式 | 说明 | +|---------|------|------| +| 命令触发(推荐) | **`/组件生产 --feishu-url <链接>`** | 精确匹配,调用 script-component-production 技能 | +| 自然语言 | 飞书链接 + "组件"/"设计组件"/"组件回填" | 自动路由 | + +**不会触发的情况:** +- ❌ 仅说"互动" → 不触发 +- ❌ 消息含 **"组件配置-json"** → 不触发(属于 `interactive-component-json` 技能) +- ✅ 正确使用:`/组件生产 --feishu-url https://... --mode backfill` + ## 技能概述 标准化教研互动组件生产流程,严格按照既定规则输出符合要求的组件配置,可直接用于生产环境或填充到Excel表格对应列。 @@ -7,7 +26,7 @@ 1. 根据给定剧本设计教研互动组件 2. 填充Excel表格的【组件】列内容 3. 审核/校验已有教研组件的格式合规性 -4. 关键词触发:「设计组件」「生成组件」「填充组件列」「按规则生成组件」「组件审核」 +4. 关键词触发:「`/组件生产`」「设计组件」「生成组件」「填充组件列」「按规则生成组件」「组件审核」 --- ## 核心规则(强制执行) diff --git a/business_production/课程组件/skills/dialogue-interaction-config/SKILL.md b/business_production/组件生产/skills/dialogue-interaction-config/SKILL.md similarity index 100% rename from business_production/课程组件/skills/dialogue-interaction-config/SKILL.md rename to business_production/组件生产/skills/dialogue-interaction-config/SKILL.md diff --git a/business_production/组件生产/skills/script-component-production/SKILL.md b/business_production/组件生产/skills/script-component-production/SKILL.md new file mode 100644 index 0000000..c09eb50 --- /dev/null +++ b/business_production/组件生产/skills/script-component-production/SKILL.md @@ -0,0 +1,238 @@ +--- +name: script-component-production +description: 剧本互动组件内容生产/审校/回填技能(不涉及JSON转换)。触发方式:(1) `/组件生产 --feishu-url <链接>` (2) 飞书链接 + "组件生产"/"组件回填"。不会误触发:仅含"互动"不触发;含"组件配置-json"不触发(属于 interactive-component-json 技能)。覆盖27种中互动+14种核心互动,从剧本sheet读取→识别→生成组件内容(结构化文本格式)→审校→回填。 +--- + +# 剧本互动组件内容生产技能 + +## ⚡ 触发方式(防误触发机制) + +| 触发方式 | 格式 | 说明 | +|---------|------|------| +| 命令触发(推荐) | **`/组件生产 --feishu-url <链接>`** | 精确匹配,绝不会误触发 | +| 链接触发 | 飞书文档链接 + **"组件生产"/"组件回填"** | 自然语言触发 | + +**不会触发的情况:** +- ❌ 仅说"互动" → 不触发(太模糊) +- ❌ 仅说"组件" → 不触发(需要结合文档链接) +- ❌ 消息含 **"组件配置-json"** → 不触发(属于 `interactive-component-json` 技能,由它独立响应) +- ✅ 正确使用:`/组件生产 --feishu-url https://... --mode backfill` + +--- + +## 核心功能 + +- ✅ **智能组件识别**:读取剧本sheet,根据列A「类型」字段识别组件行(互动 / 核心互动-xxx) +- ✅ **组件类型自动匹配**:根据剧情上下文、前后台词自动匹配27种中互动或14种核心互动类型 +- ✅ **组件内容生成**:调用LLM生成结构化文本格式的组件内容(【任务标题】【资源配置】... 等字段) +- ✅ **自动审校**:校验ID格式/字段完整性/题型匹配/知识点一致性/格式规范 +- ✅ **飞书回填**:将生成的组件内容写入剧本sheet的「组件配置」列(列G),**以结构化文本格式存储,非JSON** + +--- + +## 职责边界(重要) + +``` +/组件生产(本技能) /组件配置-json(interactive-component-json 技能) + │ │ + 剧本组件内容生产 JSON配置格式转换 + 输出:结构化文本 输入:结构化文本 + │ │ + ├─ 教研员二轮审校 ────────────────────→ 确认无误后触发 + │ + 不生成JSON! 生成标准JSON配置 +``` + +- **本技能产出**:结构化文本格式的组件内容(见下方「输出格式」) +- **本技能不负责**:将组件内容转换为JSON、定义JSON schema、生成JSON配置 +- **下游流程**:教研员审校确认后,通过 `/组件配置-json`(`interactive-component-json` 技能)转换 + +--- + +## 执行流程(6个节点) + +``` +读取飞书文档 → 解析Sheet列结构 → 识别组件行 → 匹配组件类型 → LLM生成组件内容 → 审校 → 回填 +``` + +### 节点1:文档读取 +- 解析 feishu-url → 获取 wiki_token → 读取文档内容 +- 识别嵌入的 Sheet token(如 ``) +- 读取 Sheet 元数据(sheet_id / 行数 / 列数 / 冻结行) + +### 节点2:列结构解析 +标准剧本表格列: +| A | B | C | D | E | F | G | H | +|---|---|---|---|---|---|---|---| +| **类型** | 配置信息 | 剧情描述 | 角色名 | 编剧台词 | 英文台词 | **组件配置** | 知识点 | + +- 列A「类型」:TL / 互动 / 核心互动-XXX / 空 +- 列G「组件配置」:目标回填列(存入结构化文本,非JSON) + +### 节点3:组件行识别 +遍历列A,提取所有组件标记行: +- `互动` → 中互动组件(对话朗读/对话表达/对话选择/对话选读/对话挖空/对话组句/看图说话/物品互动/图片单选/... 共27种) +- `核心互动-听力` → 核心互动-听力组件(合作听力/听力拖拽/听力选择) +- `核心互动-口语` → 核心互动-口语组件(口语快答/口语独白/口语妙问/看图说话/口语探讨) +- `核心互动-阅读` → 核心互动-阅读组件(合作阅读/...) +- `核心互动-写作` → 核心互动-写作组件(写作互动/看图组句/看图撰写/邮件组句/邮件撰写) + +### 节点4:组件类型匹配 +根据上下文信息综合判断组件类型: + +**输入信号:** +1. 列A的「类型」值(最优先) +2. 剧情描述(列C)中的动作/场景提示 +3. 前后台词(列E/F)的语义内容 +4. 知识点参考表(zbfYO4或文档内的知识点区) + +**匹配逻辑:** +- `核心互动-听力` → 匹配听力类核心互动(合作听力/听力拖拽/听力选择) +- `核心互动-口语` → 匹配口语类核心互动 +- `核心互动-阅读` → 匹配阅读类核心互动 +- `核心互动-写作` → 匹配写作类核心互动 +- `互动`(中互动)→ 根据对话场景匹配: + - 台词含"(朗读)" → 对话朗读 + - 台词含"选项"/"choose" → 对话选择/对话选读 + - 台词含"__"(挖空)→ 对话挖空 + - 台词含"组句"/"拼句子" → 对话组句 + - 剧情描述含"表达"/"说出" → 对话表达 + - 剧情描述含"看图"/"图片" → 看图说话 + +### 节点5:LLM生成组件内容(结构化文本) + +调用大模型生成结构化文本格式的组件内容,遵循 `component-design` 技能规范。 + +**输出格式(结构化文本,非JSON):** + +所有组件内容按以下字段顺序输出,无内容的字段填「无」: + +``` +【组件ID】020501 +【任务标题】介绍照片 +【资源配置】 +图片时机:无 +音频载体:无 + +【情境引入】 +Johnny : What's That? + +【互动内容】 +This is my picture. It's ___, green and orange.(音频) +选项1:blue(正确) +选项2:black + +【互动反馈】 +正确:无 +错误:Johnny : Come on! It's not what you said. + +【后置对话】 +无 + +【知识点】 +单词:blue, orange +句型:It is... +``` + +**各组件类型的字段模板参考:** +- 中互动 → 遵循 `component-design` 技能中各类型专属规则 +- 核心互动 → 遵循 `core-content-json-standard` 技能中的内容结构描述 +- 对话类互动 → 遵循 `dialogue-interaction-config` 技能规范 + +### 节点6:审校 & 回填 + +#### 审校检查项 +| # | 检查项 | 规则 | +|---|--------|------| +| 1 | 组件ID | `[2位季][2位单元][3位自增]`,同类型不重复、序号连续 | +| 2 | 字段完整 | 组件类型对应必填字段全部存在,无内容填「无」 | +| 3 | 题型匹配 | 生成的字段结构与列A标注的组件类型一致 | +| 4 | 知识点一致 | 组件内容中的知识点与知识点参考表匹配 | +| 5 | 任务标题 | 无序号、无"-"连接、10字以内 | +| 6 | 选项格式 | 对话类用「选项1:XXX」、图片类用00/01/02编号 | +| 7 | 反馈格式 | 错误反馈带角色名、自然对话、非指令式 | +| 8 | 高亮规则 | 图片类仅知识点词加$、对话类不加 | +| 9 | 情境引入 | 无内容填「无」,多句换行,格式为「角色名: 台词」 | +| 10 | 后置对话 | 取组件行之后的紧接台词,无则填「无」 | +| 11 | 超纲检查 | 词汇/句型在对应级别词库/句型库范围内 | +| 12 | 标点符号 | 统一使用标准英文标点,禁止「~」「!!!」等非标准标点 | + +#### 回填 +- 将生成的结构化文本写入列G(组件配置) +- 组件内容写入其ID对应行(TAxx编号行) +- 写入后用PUT API回填,写入后回读验证 +- **回填格式为结构化文本,不是JSON** + +--- + +## 组件类型速查 + +### 中互动(27类) +``` +对话互动、对话朗读、对话表达、对话选择、对话选读、对话挖空、对话组句、 +信息描写、信息拼词、信息组句、信息补词、信息填词、信息填句、 +物品互动、图片互动、图片单选、图片多选、图片有序、 +材料互动、指令互动、挖空互动、组句互动、填词互动、 +语音互动、造句互动、发音互动 +``` + +### 核心互动(14类) +``` +合作阅读、口语快答、口语独白、口语妙问、看图说话、口语探讨、 +合作听力、听力拖拽、听力选择、 +写作互动、看图组句、看图撰写、邮件组句、邮件撰写 +``` + +--- + +## 与上下游技能的协作 + +``` +/英文台词 → /组件生产(本技能) → [教研员审校] → /组件配置-json +(kids-english-) (script-component-) (interactive-component-json) + │ │ + 列E→列F 列G(结构化文本) 列G→标准JSON +``` + +1. **先**:`/英文台词` → 生产列E→列F的英文台词 +2. **后**:`/组件生产` → 生产列G的组件内容(结构化文本) +3. **然后**:教研员二轮审校,确认无误 +4. **最后**:`/组件配置-json` → 将确认后的组件内容转为标准JSON配置 + +--- + +## 使用示例 + +```bash +# 1. 完整流程:生产 + 审校 + 回填 +/组件生产 --feishu-url https://makee-interactive.feishu.cn/wiki/FkKgw4jBPix9lNkRemLcSwYOnid + +# 2. 仅生产,不回填 +/组件生产 --feishu-url https://makee-interactive.feishu.cn/wiki/xxx --mode produce + +# 3. 仅审校已有内容 +/组件生产 --feishu-url https://makee-interactive.feishu.cn/wiki/xxx --mode audit + +# 4. 指定难度等级 +/组件生产 --feishu-url https://makee-interactive.feishu.cn/wiki/xxx --stage S2 +``` + +--- + +## 错误处理 + +| 场景 | 处理 | +|------|------| +| 文档无权限 | 提示Bot需要加入知识空间 | +| 文档无内嵌Sheet | 提示文档不包含剧本表格 | +| 无组件行 | 提示未发现需生产的组件行 | +| LLM生成失败 | 重试1次,仍失败则跳过该行并在报告中标注 | +| 飞书写入失败 | 自动备份组件内容到本地output目录 | +| ID冲突 | 自动递增查找下一个可用ID | + +--- + +## 版本历史 + +- v1.1.0(2026-05-13):明确职责边界,产出格式从JSON改为结构化文本,与 interactive-component-json 解耦 +- v1.0.0(2026-05-13):初始版本,覆盖27种中互动 + 14种核心互动,完整6节点流程 diff --git a/business_production/组件生产/skills/script-component-production/assets/component_rules.yaml b/business_production/组件生产/skills/script-component-production/assets/component_rules.yaml new file mode 100644 index 0000000..5d24d38 --- /dev/null +++ b/business_production/组件生产/skills/script-component-production/assets/component_rules.yaml @@ -0,0 +1,105 @@ +# 组件类型自动匹配规则 +# 用于节点4:根据列A类型 + 上下文信息匹配具体组件类型 + +# === 核心互动匹配 === +core_interaction: + listening: # 列A = "核心互动-听力" + candidates: + - 合作听力 + - 听力拖拽 + - 听力选择 + signals: + - 剧情描述含"听"/"listen"/"音频" → 合作听力 + - 剧情描述含"拖拽"/"drag"/"排序" → 听力拖拽 + - 剧情描述含"选择"/"choose"/"选项" → 听力选择 + + speaking: # 列A = "核心互动-口语" + candidates: + - 口语快答 + - 口语独白 + - 口语妙问 + - 看图说话 + - 口语探讨 + signals: + - 台词短促/一问一答 → 口语快答 + - 台词长篇/角色独白 → 口语独白 + - 台词含问句/角色提问 → 口语妙问 + - 剧情描述含"看图"/"图片"/"image" → 看图说话 + - 剧情描述含"讨论"/"探讨"/"debate" → 口语探讨 + + reading: # 列A = "核心互动-阅读" + candidates: + - 合作阅读 + signals: + - 剧情描述含"阅读"/"read"/"课文" → 合作阅读 + + writing: # 列A = "核心互动-写作" + candidates: + - 写作互动 + - 看图组句 + - 看图撰写 + - 邮件组句 + - 邮件撰写 + signals: + - 剧情含"写作"/"write" + "图片" → 看图撰写/看图组句 + - 剧情含"邮件"/"email"/"letter" + "组句" → 邮件组句 + - 剧情含"邮件"/"email"/"letter" + "写" → 邮件撰写 + - 剧情含"写作"/"write" → 写作互动 + +# === 中互动匹配 === +medium_interaction: # 列A = "互动" + candidates: + - 对话朗读 + - 对话表达 + - 对话选择 + - 对话选读 + - 对话挖空 + - 对话组句 + - 看图说话 + - 物品互动 + - 图片单选 + - 图片多选 + - 图片有序 + - 信息描写 + - 信息拼词 + - 信息组句 + - 信息补词 + - 信息填词 + - 信息填句 + - 材料互动 + - 指令互动 + - 挖空互动 + - 组句互动 + - 填词互动 + - 语音互动 + - 造句互动 + - 发音互动 + signals: + # 对话类 + - 台词E/F类含"(朗读)" → 对话朗读 + - 剧情描述含"表达"/"说出"/"say" + 非选择题模式 → 对话表达 + - 台词含"选项1"/"选项2" + "正确"标注 → 对话选择 + - 台词含"选项1"/"选项2" + 无正确标注 → 对话选读 + - 台词含"___" → 对话挖空 + - 剧情描述含"组句"/"拼单词"/"put together" → 对话组句 + # 图片类 + - 剧情描述含"看图"/"图片" + "选择" → 图片单选/图片多选 + - 剧情描述含"看图"/"图片" + "排序" → 图片有序 + # 物品类 + - 剧情描述含"物品"/"object"/"点击"/"tap" → 物品互动 + # 信息类 + - 剧情描述含"描写"/"describe" → 信息描写 + - 剧情描述含"拼词"/"spell" → 信息拼词 + - 剧情描述含"组句"/"sentence" → 信息组句 + - 剧情描述含"补词"/"fill" + "缺词" → 信息补词 + - 剧情描述含"填词"/"blank" → 信息填词 + - 剧情描述含"填句"/"complete sentence" → 信息填句 + # 其他 + - 剧情描述含"材料"/"material" → 材料互动 + - 剧情描述含"指令"/"command"/"direction" → 指令互动 + - 剧情描述含"发音"/"pronounce" → 发音互动 + - 剧情描述含"语音"/"voice"/"speak" → 语音互动 + - 剧情描述含"造句"/"make sentence" → 造句互动 + - 剧情描述含"挖空"/"cloze" + 非对话 → 挖空互动 + - 剧情描述含"组句" + 非对话 → 组句互动 + - 剧情描述含"填词" + 非对话 → 填词互动 diff --git a/business_production/组件生产/skills/script-component-production/assets/review_checklist.yaml b/business_production/组件生产/skills/script-component-production/assets/review_checklist.yaml new file mode 100644 index 0000000..d75fd99 --- /dev/null +++ b/business_production/组件生产/skills/script-component-production/assets/review_checklist.yaml @@ -0,0 +1,140 @@ +# 组件配置审校清单 +# 用于节点6:对生成的组件配置进行多维度校验 + +checks: + # === 基础信息 === + - id: "ID_FORMAT" + name: "ID格式" + rule: "格式为 [2位季度][2位单元][3位自增],如020501。同组件类型下ID不重复,序号连续。" + severity: critical + auto_check: true + + - id: "TASK_TITLE" + name: "任务标题" + rule: "10字以内,无序号(含-连接/(一)等),无多余标点。直接填写清晰简洁的任务描述。" + severity: critical + auto_check: false + + - id: "COMPONENT_TYPE" + name: "组件类型匹配" + rule: "componentType/cType 与列A的「类型」值一致。核心互动必须匹配core_xxx_xxx格式,中互动匹配dialogue_xxx格式。" + severity: critical + auto_check: true + + # === 字段完整性 === + - id: "FIELD_COMPLETENESS" + name: "必填字段完整性" + rule: | + 核心互动必填:ID, dataStatus, kpInfo, taskData(taskInfo), jsonData(questionData), 互动描述, 关联知识点 + 中互动必填:ID, dataStatus, kpInfo, taskData, jsonData, 互动描述 + severity: critical + auto_check: true + + - id: "KPINFO_MATCH" + name: "知识点匹配" + rule: "kpInfo中的知识点(kpId/kpTitle)必须与文档的知识点参考表一致。知识点的kpType正确(vocab/pattern)。" + severity: critical + auto_check: false + + # === 互动内容 === + - id: "OPTION_FORMAT" + name: "选项格式" + rule: | + 对话类互动:选项使用「选项1:XXX」「选项2:XXX」格式 + 图片类互动:选项仅保留00/01/02编号,无文字 + 所有选项单词统一小写开头(除非是专有名词) + severity: high + auto_check: true + + - id: "HIGHLIGHT_RULE" + name: "高亮规则" + rule: | + 对话类互动:不需要$符号 + 图片类互动:仅知识点单词前后加$符号高亮,非知识点单词不加 + 例:Close the $window$.(window为知识点) + severity: high + auto_check: true + + - id: "BLANK_FORMAT" + name: "挖空格式" + rule: "挖空统一使用3个英文下划线___,前后各空一格(如 'It is ___.')。" + severity: high + auto_check: true + + - id: "FEEDBACK_FORMAT" + name: "反馈格式" + rule: | + 正确反馈:统一填「无」 + 错误反馈:必须是符合当前剧情场景的自然角色对话,带角色名 + 禁止指令式/说教式表达(如"Try to pronounce clearly, one more time.") + severity: high + auto_check: false + + # === 上下文 === + - id: "CONTEXT_INTRO" + name: "情境引入" + rule: "无内容填「无」。多句时每行一句,格式为「角色名: 台词」。不得使用组件行之前的台词。" + severity: medium + auto_check: true + + - id: "POST_DIALOGUE" + name: "后置对话" + rule: "组件行之后紧接的角色台词需放入后置对话。无内容填「无」。" + severity: medium + auto_check: false + + - id: "RESOURCE_CONFIG" + name: "资源配置" + rule: | + 图片时机:无教研图填「图片时机:无」,有图填出现阶段 + 音频载体:无特殊要求填「无」 + 需图片资源时,图片命名格式:组件ID.png + severity: medium + auto_check: true + + # === 难度适配 === + - id: "DIFFICULTY_CHECK" + name: "难度等级适配" + rule: | + 词库限制:L1仅用L1词库,L2可用L1+L2词库 + 句型限制:句型必须在对应级别句型库范围内 + 句长限制:核心知识点句≤7词,通用句≤10词 + severity: high + auto_check: true + + - id: "VOCAB_CHECK" + name: "超纲词检查" + rule: "所有词汇在对应级别词库范围内,L1级禁止使用L2词汇。超纲词需标记并替换为大纲内词。" + severity: high + auto_check: true + + - id: "PATTERN_CHECK" + name: "超纲句型检查" + rule: "所有句型在对应级别句型库范围内。出现超纲句型需标记。" + severity: high + auto_check: true + + # === 格式规范 === + - id: "PUNCTUATION" + name: "标点符号" + rule: "统一使用标准英文标点符号。禁止使用「~」「!!!」「。。。 」等非标准标点。" + severity: low + auto_check: true + + - id: "DISABLED_CONTENT" + name: "禁用内容" + rule: | + - 完全删除所有【图片资源】行 + - 音频载体无特殊要求时填「无」 + - 禁止在组件中使用教学指令式语言 + severity: medium + auto_check: true + +# === 审校报告格式 === +report_format: + sections: + - "表格概况(行数/列数/组件行数/组件类型分布)" + - "🔴 必须修改(critical级问题,按行号列出)" + - "🟡 建议关注(high/medium级问题)" + - "⚠️ 缺失项(需要的组件但未生成)" + - "✅ 整体评价(覆盖率/通过率/建议)" diff --git a/business_production/组件生产/skills/script-component-production/assets/skill.yml b/business_production/组件生产/skills/script-component-production/assets/skill.yml new file mode 100644 index 0000000..2d12bf5 --- /dev/null +++ b/business_production/组件生产/skills/script-component-production/assets/skill.yml @@ -0,0 +1,50 @@ +name: script-component-production +description: | + 剧本互动组件内容生产/审校/回填技能(不涉及JSON转换,产出结构化文本格式)。 + 【触发方式】 + (1) `/组件生产 --feishu-url <链接>` — 命令触发(推荐) + (2) 飞书文档链接 + "组件生产"/"组件回填" 关键词 + 【不会触发】 + 仅含"互动"不触发 + 含"组件配置-json"不触发(属于 interactive-component-json 技能) + 覆盖全部27种中互动 + 14种核心互动组件类型。 + 从飞书剧本wiki文档出发:读取sheet数据 → 识别组件行 → 匹配组件类型 → 生成组件内容(结构化文本)→ 审校校验 → 回填。 + 典型场景:飞书剧本表格列G组件内容回填、组件审校、批量组件生产。 +version: 1.1.0 +author: xiaoyan +dependencies: + - component-design + - dialogue-interaction-config + - core-content-json-standard + - vala-interactive-components + - feishu-embedded-sheet +parameters: + - name: feishu-url + type: string + description: 飞书剧本wiki文档链接 + required: true + - name: mode + type: string + description: 执行模式 produce/audit/backfill(默认backfill=生产+审校+回填) + required: false + default: backfill + - name: stage + type: string + description: 难度等级 S1/S2/S3/S4(默认从文档标题推断) + required: false + - name: source-col + type: string + description: 源类型列名称(默认"类型") + required: false + default: "类型" + - name: target-col + type: string + description: 目标组件配置列(默认"组件配置") + required: false + default: "组件配置" +tags: + - 内容生产 + - 互动组件 + - 组件内容 + - 审校 + - 飞书集成 diff --git a/business_production/课程组件/skills/vala-interactive-components/SKILL.md b/business_production/组件生产/skills/vala-interactive-components/SKILL.md similarity index 100% rename from business_production/课程组件/skills/vala-interactive-components/SKILL.md rename to business_production/组件生产/skills/vala-interactive-components/SKILL.md diff --git a/skills/interactive-component-json/SKILL.md b/business_production/组件配置/interactive-component-json/SKILL.md similarity index 100% rename from skills/interactive-component-json/SKILL.md rename to business_production/组件配置/interactive-component-json/SKILL.md diff --git a/skills/interactive-component-json/config.json b/business_production/组件配置/interactive-component-json/config.json similarity index 100% rename from skills/interactive-component-json/config.json rename to business_production/组件配置/interactive-component-json/config.json diff --git a/skills/interactive-component-json/db/components.db b/business_production/组件配置/interactive-component-json/db/components.db similarity index 100% rename from skills/interactive-component-json/db/components.db rename to business_production/组件配置/interactive-component-json/db/components.db diff --git a/skills/interactive-component-json/outputs/L1-S1-U11-L4 王牌棒球 Ace Baseball Showdown 副本_20260508_180543.html b/business_production/组件配置/interactive-component-json/outputs/L1-S1-U11-L4 王牌棒球 Ace Baseball Showdown 副本_20260508_180543.html similarity index 100% rename from skills/interactive-component-json/outputs/L1-S1-U11-L4 王牌棒球 Ace Baseball Showdown 副本_20260508_180543.html rename to business_production/组件配置/interactive-component-json/outputs/L1-S1-U11-L4 王牌棒球 Ace Baseball Showdown 副本_20260508_180543.html diff --git a/skills/interactive-component-json/outputs/L1-S1-U11-L4 王牌棒球 Ace Baseball Showdown 副本_20260508_192352.html b/business_production/组件配置/interactive-component-json/outputs/L1-S1-U11-L4 王牌棒球 Ace Baseball Showdown 副本_20260508_192352.html similarity index 100% rename from skills/interactive-component-json/outputs/L1-S1-U11-L4 王牌棒球 Ace Baseball Showdown 副本_20260508_192352.html rename to business_production/组件配置/interactive-component-json/outputs/L1-S1-U11-L4 王牌棒球 Ace Baseball Showdown 副本_20260508_192352.html diff --git a/skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260507_104229.html b/business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260507_104229.html similarity index 100% rename from skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260507_104229.html rename to business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260507_104229.html diff --git a/skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260507_105232.html b/business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260507_105232.html similarity index 100% rename from skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260507_105232.html rename to business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260507_105232.html diff --git a/skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260507_113133.html b/business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260507_113133.html similarity index 100% rename from skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260507_113133.html rename to business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260507_113133.html diff --git a/skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260507_121327.html b/business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260507_121327.html similarity index 100% rename from skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260507_121327.html rename to business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260507_121327.html diff --git a/skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260508_123414.html b/business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260508_123414.html similarity index 100% rename from skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260508_123414.html rename to business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260508_123414.html diff --git a/skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260508_154553.html b/business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260508_154553.html similarity index 100% rename from skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260508_154553.html rename to business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260508_154553.html diff --git a/skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260508_160739.html b/business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260508_160739.html similarity index 100% rename from skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260508_160739.html rename to business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260508_160739.html diff --git a/skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260508_170257.html b/business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260508_170257.html similarity index 100% rename from skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260508_170257.html rename to business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260508_170257.html diff --git a/skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260508_172722.html b/business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260508_172722.html similarity index 100% rename from skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260508_172722.html rename to business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260508_172722.html diff --git a/skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260508_193259.html b/business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260508_193259.html similarity index 100% rename from skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260508_193259.html rename to business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260508_193259.html diff --git a/skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260509_103808.html b/business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260509_103808.html similarity index 100% rename from skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260509_103808.html rename to business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260509_103808.html diff --git a/skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260509_105913.html b/business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260509_105913.html similarity index 100% rename from skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260509_105913.html rename to business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260509_105913.html diff --git a/skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260509_111203.html b/business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260509_111203.html similarity index 100% rename from skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260509_111203.html rename to business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260509_111203.html diff --git a/skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260509_144029.html b/business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260509_144029.html similarity index 100% rename from skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260509_144029.html rename to business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-全量__20260509_144029.html diff --git a/skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_181630.html b/business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_181630.html similarity index 100% rename from skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_181630.html rename to business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_181630.html diff --git a/skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_191146.html b/business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_191146.html similarity index 100% rename from skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_191146.html rename to business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_191146.html diff --git a/skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_192024.html b/business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_192024.html similarity index 100% rename from skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_192024.html rename to business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_192024.html diff --git a/skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_195832.html b/business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_195832.html similarity index 100% rename from skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_195832.html rename to business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_195832.html diff --git a/skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_201143.html b/business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_201143.html similarity index 100% rename from skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_201143.html rename to business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_201143.html diff --git a/skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_202855.html b/business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_202855.html similarity index 100% rename from skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_202855.html rename to business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_202855.html diff --git a/skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_203642.html b/business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_203642.html similarity index 100% rename from skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_203642.html rename to business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_203642.html diff --git a/skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_225623.html b/business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_225623.html similarity index 100% rename from skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_225623.html rename to business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_225623.html diff --git a/skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_230815.html b/business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_230815.html similarity index 100% rename from skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_230815.html rename to business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_230815.html diff --git a/skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_231232.html b/business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_231232.html similarity index 100% rename from skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_231232.html rename to business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260506_231232.html diff --git a/skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260507_103630.html b/business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260507_103630.html similarity index 100% rename from skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260507_103630.html rename to business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试-非全量__20260507_103630.html diff --git a/skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_121620.html b/business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_121620.html similarity index 100% rename from skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_121620.html rename to business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_121620.html diff --git a/skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_122912.html b/business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_122912.html similarity index 100% rename from skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_122912.html rename to business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_122912.html diff --git a/skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_124310.html b/business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_124310.html similarity index 100% rename from skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_124310.html rename to business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_124310.html diff --git a/skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_124455.html b/business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_124455.html similarity index 100% rename from skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_124455.html rename to business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_124455.html diff --git a/skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_125241.html b/business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_125241.html similarity index 100% rename from skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_125241.html rename to business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_125241.html diff --git a/skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_134502.html b/business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_134502.html similarity index 100% rename from skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_134502.html rename to business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_134502.html diff --git a/skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_141051.html b/business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_141051.html similarity index 100% rename from skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_141051.html rename to business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_141051.html diff --git a/skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_141550.html b/business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_141550.html similarity index 100% rename from skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_141550.html rename to business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_141550.html diff --git a/skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_142631.html b/business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_142631.html similarity index 100% rename from skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_142631.html rename to business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_142631.html diff --git a/skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_142814.html b/business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_142814.html similarity index 100% rename from skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_142814.html rename to business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_142814.html diff --git a/skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_143640.html b/business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_143640.html similarity index 100% rename from skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_143640.html rename to business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_143640.html diff --git a/skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_144357.html b/business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_144357.html similarity index 100% rename from skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_144357.html rename to business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦 _组件录入测试__20260430_144357.html diff --git a/skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦_20260428_152354.html b/business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦_20260428_152354.html similarity index 100% rename from skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦_20260428_152354.html rename to business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦_20260428_152354.html diff --git a/skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦_20260428_153018.html b/business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦_20260428_153018.html similarity index 100% rename from skills/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦_20260428_153018.html rename to business_production/组件配置/interactive-component-json/outputs/L1-S2-U14-L1 到你上学啦_20260428_153018.html diff --git a/skills/interactive-component-json/outputs/L1-S2-U19-L1 客厅游戏_20260508_184341.html b/business_production/组件配置/interactive-component-json/outputs/L1-S2-U19-L1 客厅游戏_20260508_184341.html similarity index 100% rename from skills/interactive-component-json/outputs/L1-S2-U19-L1 客厅游戏_20260508_184341.html rename to business_production/组件配置/interactive-component-json/outputs/L1-S2-U19-L1 客厅游戏_20260508_184341.html diff --git a/skills/interactive-component-json/outputs/L2-S2-U14-L1 测试文档_20260429_175719.html b/business_production/组件配置/interactive-component-json/outputs/L2-S2-U14-L1 测试文档_20260429_175719.html similarity index 100% rename from skills/interactive-component-json/outputs/L2-S2-U14-L1 测试文档_20260429_175719.html rename to business_production/组件配置/interactive-component-json/outputs/L2-S2-U14-L1 测试文档_20260429_175719.html diff --git a/skills/interactive-component-json/outputs/V5核心互动验证 - imageDrag _ speaking_20260428_192548.html b/business_production/组件配置/interactive-component-json/outputs/V5核心互动验证 - imageDrag _ speaking_20260428_192548.html similarity index 100% rename from skills/interactive-component-json/outputs/V5核心互动验证 - imageDrag _ speaking_20260428_192548.html rename to business_production/组件配置/interactive-component-json/outputs/V5核心互动验证 - imageDrag _ speaking_20260428_192548.html diff --git a/skills/interactive-component-json/outputs/_20260429_175651.html b/business_production/组件配置/interactive-component-json/outputs/_20260429_175651.html similarity index 100% rename from skills/interactive-component-json/outputs/_20260429_175651.html rename to business_production/组件配置/interactive-component-json/outputs/_20260429_175651.html diff --git a/skills/interactive-component-json/outputs/core_reading_order 测试 - S3 Record 0325001_20260428_174634.html b/business_production/组件配置/interactive-component-json/outputs/core_reading_order 测试 - S3 Record 0325001_20260428_174634.html similarity index 100% rename from skills/interactive-component-json/outputs/core_reading_order 测试 - S3 Record 0325001_20260428_174634.html rename to business_production/组件配置/interactive-component-json/outputs/core_reading_order 测试 - S3 Record 0325001_20260428_174634.html diff --git a/skills/interactive-component-json/outputs/v5_both_validation.json b/business_production/组件配置/interactive-component-json/outputs/v5_both_validation.json similarity index 100% rename from skills/interactive-component-json/outputs/v5_both_validation.json rename to business_production/组件配置/interactive-component-json/outputs/v5_both_validation.json diff --git a/skills/interactive-component-json/outputs/v5_imagedrag_validation.json b/business_production/组件配置/interactive-component-json/outputs/v5_imagedrag_validation.json similarity index 100% rename from skills/interactive-component-json/outputs/v5_imagedrag_validation.json rename to business_production/组件配置/interactive-component-json/outputs/v5_imagedrag_validation.json diff --git a/skills/interactive-component-json/outputs/v5_speaking_validation.json b/business_production/组件配置/interactive-component-json/outputs/v5_speaking_validation.json similarity index 100% rename from skills/interactive-component-json/outputs/v5_speaking_validation.json rename to business_production/组件配置/interactive-component-json/outputs/v5_speaking_validation.json diff --git a/skills/interactive-component-json/outputs/validate_image.json b/business_production/组件配置/interactive-component-json/outputs/validate_image.json similarity index 100% rename from skills/interactive-component-json/outputs/validate_image.json rename to business_production/组件配置/interactive-component-json/outputs/validate_image.json diff --git a/skills/interactive-component-json/outputs/validate_inquiry.json b/business_production/组件配置/interactive-component-json/outputs/validate_inquiry.json similarity index 100% rename from skills/interactive-component-json/outputs/validate_inquiry.json rename to business_production/组件配置/interactive-component-json/outputs/validate_inquiry.json diff --git a/skills/interactive-component-json/prd.md b/business_production/组件配置/interactive-component-json/prd.md similarity index 100% rename from skills/interactive-component-json/prd.md rename to business_production/组件配置/interactive-component-json/prd.md diff --git a/skills/interactive-component-json/prd/v10.md b/business_production/组件配置/interactive-component-json/prd/v10.md similarity index 100% rename from skills/interactive-component-json/prd/v10.md rename to business_production/组件配置/interactive-component-json/prd/v10.md diff --git a/skills/interactive-component-json/prd/v11.md b/business_production/组件配置/interactive-component-json/prd/v11.md similarity index 100% rename from skills/interactive-component-json/prd/v11.md rename to business_production/组件配置/interactive-component-json/prd/v11.md diff --git a/skills/interactive-component-json/prd/v3.md b/business_production/组件配置/interactive-component-json/prd/v3.md similarity index 100% rename from skills/interactive-component-json/prd/v3.md rename to business_production/组件配置/interactive-component-json/prd/v3.md diff --git a/skills/interactive-component-json/prd/v4.md b/business_production/组件配置/interactive-component-json/prd/v4.md similarity index 100% rename from skills/interactive-component-json/prd/v4.md rename to business_production/组件配置/interactive-component-json/prd/v4.md diff --git a/skills/interactive-component-json/prd/v5.md b/business_production/组件配置/interactive-component-json/prd/v5.md similarity index 100% rename from skills/interactive-component-json/prd/v5.md rename to business_production/组件配置/interactive-component-json/prd/v5.md diff --git a/skills/interactive-component-json/prd/v6.md b/business_production/组件配置/interactive-component-json/prd/v6.md similarity index 100% rename from skills/interactive-component-json/prd/v6.md rename to business_production/组件配置/interactive-component-json/prd/v6.md diff --git a/skills/interactive-component-json/prd/v7.md b/business_production/组件配置/interactive-component-json/prd/v7.md similarity index 100% rename from skills/interactive-component-json/prd/v7.md rename to business_production/组件配置/interactive-component-json/prd/v7.md diff --git a/skills/interactive-component-json/prd/v8.md b/business_production/组件配置/interactive-component-json/prd/v8.md similarity index 100% rename from skills/interactive-component-json/prd/v8.md rename to business_production/组件配置/interactive-component-json/prd/v8.md diff --git a/skills/interactive-component-json/prd/v9.md b/business_production/组件配置/interactive-component-json/prd/v9.md similarity index 100% rename from skills/interactive-component-json/prd/v9.md rename to business_production/组件配置/interactive-component-json/prd/v9.md diff --git a/skills/interactive-component-json/prd/xiaoyan_comp_json_skill.md b/business_production/组件配置/interactive-component-json/prd/xiaoyan_comp_json_skill.md similarity index 100% rename from skills/interactive-component-json/prd/xiaoyan_comp_json_skill.md rename to business_production/组件配置/interactive-component-json/prd/xiaoyan_comp_json_skill.md diff --git a/skills/interactive-component-json/prd/xiaoyan_comp_json_skill_v2.md b/business_production/组件配置/interactive-component-json/prd/xiaoyan_comp_json_skill_v2.md similarity index 100% rename from skills/interactive-component-json/prd/xiaoyan_comp_json_skill_v2.md rename to business_production/组件配置/interactive-component-json/prd/xiaoyan_comp_json_skill_v2.md diff --git a/skills/interactive-component-json/project.md b/business_production/组件配置/interactive-component-json/project.md similarity index 100% rename from skills/interactive-component-json/project.md rename to business_production/组件配置/interactive-component-json/project.md diff --git a/skills/interactive-component-json/prompts/README.md b/business_production/组件配置/interactive-component-json/prompts/README.md similarity index 100% rename from skills/interactive-component-json/prompts/README.md rename to business_production/组件配置/interactive-component-json/prompts/README.md diff --git a/skills/interactive-component-json/prompts/__init__.py b/business_production/组件配置/interactive-component-json/prompts/__init__.py similarity index 100% rename from skills/interactive-component-json/prompts/__init__.py rename to business_production/组件配置/interactive-component-json/prompts/__init__.py diff --git a/skills/interactive-component-json/prompts/__pycache__/__init__.cpython-312.pyc b/business_production/组件配置/interactive-component-json/prompts/__pycache__/__init__.cpython-312.pyc similarity index 100% rename from skills/interactive-component-json/prompts/__pycache__/__init__.cpython-312.pyc rename to business_production/组件配置/interactive-component-json/prompts/__pycache__/__init__.cpython-312.pyc diff --git a/skills/interactive-component-json/prompts/__pycache__/core_imagedrag_prompts.cpython-312.pyc b/business_production/组件配置/interactive-component-json/prompts/__pycache__/core_imagedrag_prompts.cpython-312.pyc similarity index 100% rename from skills/interactive-component-json/prompts/__pycache__/core_imagedrag_prompts.cpython-312.pyc rename to business_production/组件配置/interactive-component-json/prompts/__pycache__/core_imagedrag_prompts.cpython-312.pyc diff --git a/skills/interactive-component-json/prompts/__pycache__/core_listening_choose_prompts.cpython-312.pyc b/business_production/组件配置/interactive-component-json/prompts/__pycache__/core_listening_choose_prompts.cpython-312.pyc similarity index 100% rename from skills/interactive-component-json/prompts/__pycache__/core_listening_choose_prompts.cpython-312.pyc rename to business_production/组件配置/interactive-component-json/prompts/__pycache__/core_listening_choose_prompts.cpython-312.pyc diff --git a/skills/interactive-component-json/prompts/__pycache__/core_listening_drag_prompts.cpython-312.pyc b/business_production/组件配置/interactive-component-json/prompts/__pycache__/core_listening_drag_prompts.cpython-312.pyc similarity index 100% rename from skills/interactive-component-json/prompts/__pycache__/core_listening_drag_prompts.cpython-312.pyc rename to business_production/组件配置/interactive-component-json/prompts/__pycache__/core_listening_drag_prompts.cpython-312.pyc diff --git a/skills/interactive-component-json/prompts/__pycache__/core_listening_order_prompts.cpython-312.pyc b/business_production/组件配置/interactive-component-json/prompts/__pycache__/core_listening_order_prompts.cpython-312.pyc similarity index 100% rename from skills/interactive-component-json/prompts/__pycache__/core_listening_order_prompts.cpython-312.pyc rename to business_production/组件配置/interactive-component-json/prompts/__pycache__/core_listening_order_prompts.cpython-312.pyc diff --git a/skills/interactive-component-json/prompts/__pycache__/core_reading_prompts.cpython-312.pyc b/business_production/组件配置/interactive-component-json/prompts/__pycache__/core_reading_prompts.cpython-312.pyc similarity index 100% rename from skills/interactive-component-json/prompts/__pycache__/core_reading_prompts.cpython-312.pyc rename to business_production/组件配置/interactive-component-json/prompts/__pycache__/core_reading_prompts.cpython-312.pyc diff --git a/skills/interactive-component-json/prompts/__pycache__/core_speaking_image_prompts.cpython-312.pyc b/business_production/组件配置/interactive-component-json/prompts/__pycache__/core_speaking_image_prompts.cpython-312.pyc similarity index 100% rename from skills/interactive-component-json/prompts/__pycache__/core_speaking_image_prompts.cpython-312.pyc rename to business_production/组件配置/interactive-component-json/prompts/__pycache__/core_speaking_image_prompts.cpython-312.pyc diff --git a/skills/interactive-component-json/prompts/__pycache__/core_speaking_inquiry_prompts.cpython-312.pyc b/business_production/组件配置/interactive-component-json/prompts/__pycache__/core_speaking_inquiry_prompts.cpython-312.pyc similarity index 100% rename from skills/interactive-component-json/prompts/__pycache__/core_speaking_inquiry_prompts.cpython-312.pyc rename to business_production/组件配置/interactive-component-json/prompts/__pycache__/core_speaking_inquiry_prompts.cpython-312.pyc diff --git a/skills/interactive-component-json/prompts/__pycache__/core_speaking_prompts.cpython-312.pyc b/business_production/组件配置/interactive-component-json/prompts/__pycache__/core_speaking_prompts.cpython-312.pyc similarity index 100% rename from skills/interactive-component-json/prompts/__pycache__/core_speaking_prompts.cpython-312.pyc rename to business_production/组件配置/interactive-component-json/prompts/__pycache__/core_speaking_prompts.cpython-312.pyc diff --git a/skills/interactive-component-json/prompts/__pycache__/core_writing_img_prompts.cpython-312.pyc b/business_production/组件配置/interactive-component-json/prompts/__pycache__/core_writing_img_prompts.cpython-312.pyc similarity index 100% rename from skills/interactive-component-json/prompts/__pycache__/core_writing_img_prompts.cpython-312.pyc rename to business_production/组件配置/interactive-component-json/prompts/__pycache__/core_writing_img_prompts.cpython-312.pyc diff --git a/skills/interactive-component-json/prompts/__pycache__/core_writing_question_prompts.cpython-312.pyc b/business_production/组件配置/interactive-component-json/prompts/__pycache__/core_writing_question_prompts.cpython-312.pyc similarity index 100% rename from skills/interactive-component-json/prompts/__pycache__/core_writing_question_prompts.cpython-312.pyc rename to business_production/组件配置/interactive-component-json/prompts/__pycache__/core_writing_question_prompts.cpython-312.pyc diff --git a/skills/interactive-component-json/prompts/__pycache__/prompt_registry.cpython-312.pyc b/business_production/组件配置/interactive-component-json/prompts/__pycache__/prompt_registry.cpython-312.pyc similarity index 100% rename from skills/interactive-component-json/prompts/__pycache__/prompt_registry.cpython-312.pyc rename to business_production/组件配置/interactive-component-json/prompts/__pycache__/prompt_registry.cpython-312.pyc diff --git a/skills/interactive-component-json/prompts/core_imagedrag_prompts.py b/business_production/组件配置/interactive-component-json/prompts/core_imagedrag_prompts.py similarity index 100% rename from skills/interactive-component-json/prompts/core_imagedrag_prompts.py rename to business_production/组件配置/interactive-component-json/prompts/core_imagedrag_prompts.py diff --git a/skills/interactive-component-json/prompts/core_listening_choose_prompts.py b/business_production/组件配置/interactive-component-json/prompts/core_listening_choose_prompts.py similarity index 100% rename from skills/interactive-component-json/prompts/core_listening_choose_prompts.py rename to business_production/组件配置/interactive-component-json/prompts/core_listening_choose_prompts.py diff --git a/skills/interactive-component-json/prompts/core_listening_drag_prompts.py b/business_production/组件配置/interactive-component-json/prompts/core_listening_drag_prompts.py similarity index 100% rename from skills/interactive-component-json/prompts/core_listening_drag_prompts.py rename to business_production/组件配置/interactive-component-json/prompts/core_listening_drag_prompts.py diff --git a/skills/interactive-component-json/prompts/core_listening_order_prompts.py b/business_production/组件配置/interactive-component-json/prompts/core_listening_order_prompts.py similarity index 100% rename from skills/interactive-component-json/prompts/core_listening_order_prompts.py rename to business_production/组件配置/interactive-component-json/prompts/core_listening_order_prompts.py diff --git a/skills/interactive-component-json/prompts/core_reading_prompts.py b/business_production/组件配置/interactive-component-json/prompts/core_reading_prompts.py similarity index 100% rename from skills/interactive-component-json/prompts/core_reading_prompts.py rename to business_production/组件配置/interactive-component-json/prompts/core_reading_prompts.py diff --git a/skills/interactive-component-json/prompts/core_speaking_image_prompts.py b/business_production/组件配置/interactive-component-json/prompts/core_speaking_image_prompts.py similarity index 100% rename from skills/interactive-component-json/prompts/core_speaking_image_prompts.py rename to business_production/组件配置/interactive-component-json/prompts/core_speaking_image_prompts.py diff --git a/skills/interactive-component-json/prompts/core_speaking_inquiry_prompts.py b/business_production/组件配置/interactive-component-json/prompts/core_speaking_inquiry_prompts.py similarity index 100% rename from skills/interactive-component-json/prompts/core_speaking_inquiry_prompts.py rename to business_production/组件配置/interactive-component-json/prompts/core_speaking_inquiry_prompts.py diff --git a/skills/interactive-component-json/prompts/core_speaking_prompts.py b/business_production/组件配置/interactive-component-json/prompts/core_speaking_prompts.py similarity index 100% rename from skills/interactive-component-json/prompts/core_speaking_prompts.py rename to business_production/组件配置/interactive-component-json/prompts/core_speaking_prompts.py diff --git a/skills/interactive-component-json/prompts/core_writing_img_prompts.py b/business_production/组件配置/interactive-component-json/prompts/core_writing_img_prompts.py similarity index 100% rename from skills/interactive-component-json/prompts/core_writing_img_prompts.py rename to business_production/组件配置/interactive-component-json/prompts/core_writing_img_prompts.py diff --git a/skills/interactive-component-json/prompts/core_writing_question_prompts.py b/business_production/组件配置/interactive-component-json/prompts/core_writing_question_prompts.py similarity index 100% rename from skills/interactive-component-json/prompts/core_writing_question_prompts.py rename to business_production/组件配置/interactive-component-json/prompts/core_writing_question_prompts.py diff --git a/skills/interactive-component-json/prompts/prompt_registry.py b/business_production/组件配置/interactive-component-json/prompts/prompt_registry.py similarity index 100% rename from skills/interactive-component-json/prompts/prompt_registry.py rename to business_production/组件配置/interactive-component-json/prompts/prompt_registry.py diff --git a/skills/interactive-component-json/references/component_registry.md b/business_production/组件配置/interactive-component-json/references/component_registry.md similarity index 100% rename from skills/interactive-component-json/references/component_registry.md rename to business_production/组件配置/interactive-component-json/references/component_registry.md diff --git a/skills/interactive-component-json/references/examples/all_types_examples.json b/business_production/组件配置/interactive-component-json/references/examples/all_types_examples.json similarity index 100% rename from skills/interactive-component-json/references/examples/all_types_examples.json rename to business_production/组件配置/interactive-component-json/references/examples/all_types_examples.json diff --git a/skills/interactive-component-json/references/json_output_spec.md b/business_production/组件配置/interactive-component-json/references/json_output_spec.md similarity index 100% rename from skills/interactive-component-json/references/json_output_spec.md rename to business_production/组件配置/interactive-component-json/references/json_output_spec.md diff --git a/skills/interactive-component-json/references/json_structure_guide.md b/business_production/组件配置/interactive-component-json/references/json_structure_guide.md similarity index 100% rename from skills/interactive-component-json/references/json_structure_guide.md rename to business_production/组件配置/interactive-component-json/references/json_structure_guide.md diff --git a/skills/interactive-component-json/references/script_format_spec.md b/business_production/组件配置/interactive-component-json/references/script_format_spec.md similarity index 100% rename from skills/interactive-component-json/references/script_format_spec.md rename to business_production/组件配置/interactive-component-json/references/script_format_spec.md diff --git a/skills/interactive-component-json/scripts/__pycache__/db_manager.cpython-312.pyc b/business_production/组件配置/interactive-component-json/scripts/__pycache__/db_manager.cpython-312.pyc similarity index 100% rename from skills/interactive-component-json/scripts/__pycache__/db_manager.cpython-312.pyc rename to business_production/组件配置/interactive-component-json/scripts/__pycache__/db_manager.cpython-312.pyc diff --git a/skills/interactive-component-json/scripts/__pycache__/feishu_client.cpython-312.pyc b/business_production/组件配置/interactive-component-json/scripts/__pycache__/feishu_client.cpython-312.pyc similarity index 100% rename from skills/interactive-component-json/scripts/__pycache__/feishu_client.cpython-312.pyc rename to business_production/组件配置/interactive-component-json/scripts/__pycache__/feishu_client.cpython-312.pyc diff --git a/skills/interactive-component-json/scripts/__pycache__/generate_json.cpython-312.pyc b/business_production/组件配置/interactive-component-json/scripts/__pycache__/generate_json.cpython-312.pyc similarity index 100% rename from skills/interactive-component-json/scripts/__pycache__/generate_json.cpython-312.pyc rename to business_production/组件配置/interactive-component-json/scripts/__pycache__/generate_json.cpython-312.pyc diff --git a/skills/interactive-component-json/scripts/__pycache__/html_report.cpython-312.pyc b/business_production/组件配置/interactive-component-json/scripts/__pycache__/html_report.cpython-312.pyc similarity index 100% rename from skills/interactive-component-json/scripts/__pycache__/html_report.cpython-312.pyc rename to business_production/组件配置/interactive-component-json/scripts/__pycache__/html_report.cpython-312.pyc diff --git a/skills/interactive-component-json/scripts/__pycache__/kp_matcher.cpython-312.pyc b/business_production/组件配置/interactive-component-json/scripts/__pycache__/kp_matcher.cpython-312.pyc similarity index 100% rename from skills/interactive-component-json/scripts/__pycache__/kp_matcher.cpython-312.pyc rename to business_production/组件配置/interactive-component-json/scripts/__pycache__/kp_matcher.cpython-312.pyc diff --git a/skills/interactive-component-json/scripts/__pycache__/llm_client.cpython-312.pyc b/business_production/组件配置/interactive-component-json/scripts/__pycache__/llm_client.cpython-312.pyc similarity index 100% rename from skills/interactive-component-json/scripts/__pycache__/llm_client.cpython-312.pyc rename to business_production/组件配置/interactive-component-json/scripts/__pycache__/llm_client.cpython-312.pyc diff --git a/skills/interactive-component-json/scripts/__pycache__/match_component.cpython-312.pyc b/business_production/组件配置/interactive-component-json/scripts/__pycache__/match_component.cpython-312.pyc similarity index 100% rename from skills/interactive-component-json/scripts/__pycache__/match_component.cpython-312.pyc rename to business_production/组件配置/interactive-component-json/scripts/__pycache__/match_component.cpython-312.pyc diff --git a/skills/interactive-component-json/scripts/__pycache__/parse_script.cpython-312.pyc b/business_production/组件配置/interactive-component-json/scripts/__pycache__/parse_script.cpython-312.pyc similarity index 100% rename from skills/interactive-component-json/scripts/__pycache__/parse_script.cpython-312.pyc rename to business_production/组件配置/interactive-component-json/scripts/__pycache__/parse_script.cpython-312.pyc diff --git a/skills/interactive-component-json/scripts/__pycache__/pipeline.cpython-312.pyc b/business_production/组件配置/interactive-component-json/scripts/__pycache__/pipeline.cpython-312.pyc similarity index 100% rename from skills/interactive-component-json/scripts/__pycache__/pipeline.cpython-312.pyc rename to business_production/组件配置/interactive-component-json/scripts/__pycache__/pipeline.cpython-312.pyc diff --git a/skills/interactive-component-json/scripts/db_manager.py b/business_production/组件配置/interactive-component-json/scripts/db_manager.py similarity index 100% rename from skills/interactive-component-json/scripts/db_manager.py rename to business_production/组件配置/interactive-component-json/scripts/db_manager.py diff --git a/skills/interactive-component-json/scripts/feishu_client.py b/business_production/组件配置/interactive-component-json/scripts/feishu_client.py similarity index 100% rename from skills/interactive-component-json/scripts/feishu_client.py rename to business_production/组件配置/interactive-component-json/scripts/feishu_client.py diff --git a/skills/interactive-component-json/scripts/generate_json.py b/business_production/组件配置/interactive-component-json/scripts/generate_json.py similarity index 100% rename from skills/interactive-component-json/scripts/generate_json.py rename to business_production/组件配置/interactive-component-json/scripts/generate_json.py diff --git a/skills/interactive-component-json/scripts/html_report.py b/business_production/组件配置/interactive-component-json/scripts/html_report.py similarity index 100% rename from skills/interactive-component-json/scripts/html_report.py rename to business_production/组件配置/interactive-component-json/scripts/html_report.py diff --git a/skills/interactive-component-json/scripts/kp_matcher.py b/business_production/组件配置/interactive-component-json/scripts/kp_matcher.py similarity index 100% rename from skills/interactive-component-json/scripts/kp_matcher.py rename to business_production/组件配置/interactive-component-json/scripts/kp_matcher.py diff --git a/skills/interactive-component-json/scripts/llm_client.py b/business_production/组件配置/interactive-component-json/scripts/llm_client.py similarity index 100% rename from skills/interactive-component-json/scripts/llm_client.py rename to business_production/组件配置/interactive-component-json/scripts/llm_client.py diff --git a/skills/interactive-component-json/scripts/match_component.py b/business_production/组件配置/interactive-component-json/scripts/match_component.py similarity index 100% rename from skills/interactive-component-json/scripts/match_component.py rename to business_production/组件配置/interactive-component-json/scripts/match_component.py diff --git a/skills/interactive-component-json/scripts/parse_script.py b/business_production/组件配置/interactive-component-json/scripts/parse_script.py similarity index 100% rename from skills/interactive-component-json/scripts/parse_script.py rename to business_production/组件配置/interactive-component-json/scripts/parse_script.py diff --git a/skills/interactive-component-json/scripts/pipeline.py b/business_production/组件配置/interactive-component-json/scripts/pipeline.py similarity index 100% rename from skills/interactive-component-json/scripts/pipeline.py rename to business_production/组件配置/interactive-component-json/scripts/pipeline.py diff --git a/skills/interactive-component-json/scripts/proxy_server.py b/business_production/组件配置/interactive-component-json/scripts/proxy_server.py similarity index 100% rename from skills/interactive-component-json/scripts/proxy_server.py rename to business_production/组件配置/interactive-component-json/scripts/proxy_server.py diff --git a/skills/interactive-component-json/scripts/validate_core_v5.py b/business_production/组件配置/interactive-component-json/scripts/validate_core_v5.py similarity index 100% rename from skills/interactive-component-json/scripts/validate_core_v5.py rename to business_production/组件配置/interactive-component-json/scripts/validate_core_v5.py diff --git a/business_production/剧本翻译/final/11-5_script.xlsx b/business_production/英文台词/final/11-5_script.xlsx similarity index 100% rename from business_production/剧本翻译/final/11-5_script.xlsx rename to business_production/英文台词/final/11-5_script.xlsx diff --git a/business_production/剧本翻译/final/11-5_剧本定稿.xlsx b/business_production/英文台词/final/11-5_剧本定稿.xlsx similarity index 100% rename from business_production/剧本翻译/final/11-5_剧本定稿.xlsx rename to business_production/英文台词/final/11-5_剧本定稿.xlsx diff --git a/business_production/剧本翻译/final/script_meixu.xlsx b/business_production/英文台词/final/script_meixu.xlsx similarity index 100% rename from business_production/剧本翻译/final/script_meixu.xlsx rename to business_production/英文台词/final/script_meixu.xlsx diff --git a/business_production/剧本翻译/final/unit11_content.md b/business_production/英文台词/final/unit11_content.md similarity index 100% rename from business_production/剧本翻译/final/unit11_content.md rename to business_production/英文台词/final/unit11_content.md diff --git a/business_production/剧本翻译/production/11-5_jiaoyantu.txt b/business_production/英文台词/production/11-5_jiaoyantu.txt similarity index 100% rename from business_production/剧本翻译/production/11-5_jiaoyantu.txt rename to business_production/英文台词/production/11-5_jiaoyantu.txt diff --git a/business_production/剧本翻译/production/11-5_美需已填写.xlsx b/business_production/英文台词/production/11-5_美需已填写.xlsx similarity index 100% rename from business_production/剧本翻译/production/11-5_美需已填写.xlsx rename to business_production/英文台词/production/11-5_美需已填写.xlsx diff --git a/business_production/剧本翻译/production/jiaoyantu.txt b/business_production/英文台词/production/jiaoyantu.txt similarity index 100% rename from business_production/剧本翻译/production/jiaoyantu.txt rename to business_production/英文台词/production/jiaoyantu.txt diff --git a/business_production/剧本翻译/skills/feishu-table-translate-fill/SKILL.md b/business_production/英文台词/skills/feishu-table-translate-fill/SKILL.md similarity index 100% rename from business_production/剧本翻译/skills/feishu-table-translate-fill/SKILL.md rename to business_production/英文台词/skills/feishu-table-translate-fill/SKILL.md diff --git a/business_production/剧本翻译/skills/feishu-table-translate-fill/references/usage.md b/business_production/英文台词/skills/feishu-table-translate-fill/references/usage.md similarity index 100% rename from business_production/剧本翻译/skills/feishu-table-translate-fill/references/usage.md rename to business_production/英文台词/skills/feishu-table-translate-fill/references/usage.md diff --git a/business_production/剧本翻译/skills/feishu-table-translate-fill/scripts/feishu_io.py b/business_production/英文台词/skills/feishu-table-translate-fill/scripts/feishu_io.py similarity index 100% rename from business_production/剧本翻译/skills/feishu-table-translate-fill/scripts/feishu_io.py rename to business_production/英文台词/skills/feishu-table-translate-fill/scripts/feishu_io.py diff --git a/business_production/剧本翻译/skills/feishu-table-translate-fill/scripts/translate_fill.py b/business_production/英文台词/skills/feishu-table-translate-fill/scripts/translate_fill.py similarity index 100% rename from business_production/剧本翻译/skills/feishu-table-translate-fill/scripts/translate_fill.py rename to business_production/英文台词/skills/feishu-table-translate-fill/scripts/translate_fill.py diff --git a/business_production/剧本翻译/skills/feishu-table-translate-fill/scripts/write_translations.py b/business_production/英文台词/skills/feishu-table-translate-fill/scripts/write_translations.py similarity index 100% rename from business_production/剧本翻译/skills/feishu-table-translate-fill/scripts/write_translations.py rename to business_production/英文台词/skills/feishu-table-translate-fill/scripts/write_translations.py diff --git a/business_production/剧本翻译/skills/kids-english-script-production/SKILL.md b/business_production/英文台词/skills/kids-english-script-production/SKILL.md similarity index 86% rename from business_production/剧本翻译/skills/kids-english-script-production/SKILL.md rename to business_production/英文台词/skills/kids-english-script-production/SKILL.md index bf1639f..d3c8e3d 100644 --- a/business_production/剧本翻译/skills/kids-english-script-production/SKILL.md +++ b/business_production/英文台词/skills/kids-english-script-production/SKILL.md @@ -1,10 +1,23 @@ --- name: kids-english-script-production -description: 4-8岁儿童英文台词生产工具,支持中英混合/纯英文/纯中文输入,通过意群拆解+英语思维重构生成自然地道的分级英文台词(不是翻译)。典型场景:动画/课程台词批量生产、已有英文剧本难度适配、中英混合剧本标准化、台词自动审校。 +description: 用英语母语儿童思维生成自然地道的分级英文台词(不是翻译!)。触发方式:(1) `/英文台词` 命令 (2) 消息包含"英文台词生产"。不会误触发:仅含"台词"或"翻译"的消息不触发。支持中英混合/纯英文/纯中文输入,通过意群拆解+英语思维重构生成分级英文台词,适配S1-S4难度等级。典型场景:飞书剧本表格列F补英文台词、动画/课程台词批量生产。 --- # 儿童英文台词生产技能 +## ⚡ 触发方式(防误触发机制) + +| 触发方式 | 格式 | 说明 | +|---------|------|------| +| 命令触发(推荐) | **`/英文台词`** + 参数 | 精确匹配,绝不会误触发 | +| 短语触发 | 消息包含 **"英文台词生产"** | 第二通道,等效于命令触发 | + +**不会触发的情况:** +- ❌ 仅说"翻译"、"翻译台词" → 不触发(这是翻译工具的概念,不是本技能) +- ❌ 仅说"台词"、"对话" → 不触发(太模糊) +- ❌ 仅说"英文"、"英语" → 不触发(太泛化) +- ✅ 正确使用:`/英文台词 --feishu-url https://... --stage S2` + ## 核心功能 - ✅ 智能输入分流:支持中英混合(主要)/纯英文/纯中文输入,自动检测语言类型并走不同处理路径 - ✅ 意群拆解:将台词拆解为最小语义意群,为英语思维重构做准备,不丢剧情 diff --git a/business_production/剧本翻译/skills/kids-english-script-production/assets/expression_map.yaml b/business_production/英文台词/skills/kids-english-script-production/assets/expression_map.yaml similarity index 100% rename from business_production/剧本翻译/skills/kids-english-script-production/assets/expression_map.yaml rename to business_production/英文台词/skills/kids-english-script-production/assets/expression_map.yaml diff --git a/business_production/剧本翻译/skills/kids-english-script-production/assets/prompt_config.yaml b/business_production/英文台词/skills/kids-english-script-production/assets/prompt_config.yaml similarity index 100% rename from business_production/剧本翻译/skills/kids-english-script-production/assets/prompt_config.yaml rename to business_production/英文台词/skills/kids-english-script-production/assets/prompt_config.yaml diff --git a/business_production/剧本翻译/skills/kids-english-script-production/assets/sci_fi_map.yaml b/business_production/英文台词/skills/kids-english-script-production/assets/sci_fi_map.yaml similarity index 100% rename from business_production/剧本翻译/skills/kids-english-script-production/assets/sci_fi_map.yaml rename to business_production/英文台词/skills/kids-english-script-production/assets/sci_fi_map.yaml diff --git a/business_production/剧本翻译/skills/kids-english-script-production/assets/skill.yml b/business_production/英文台词/skills/kids-english-script-production/assets/skill.yml similarity index 73% rename from business_production/剧本翻译/skills/kids-english-script-production/assets/skill.yml rename to business_production/英文台词/skills/kids-english-script-production/assets/skill.yml index 034793d..fe153e1 100644 --- a/business_production/剧本翻译/skills/kids-english-script-production/assets/skill.yml +++ b/business_production/英文台词/skills/kids-english-script-production/assets/skill.yml @@ -1,5 +1,12 @@ name: kids-english-script-production -description: 4-8岁儿童英文台词生产工具,支持中英混合/纯英文/纯中文输入,通过意群拆解+英语思维重构生成自然地道的分级英文台词 +description: | + 用英语母语儿童思维生成自然地道的分级英文台词(不是翻译!) + 【触发方式】 + (1) `/英文台词` — 命令触发(推荐) + (2) 消息包含 "英文台词生产" — 短语触发 + 【不会触发】仅含"台词"或"翻译"的消息不会触发,避免误匹配 + 支持中英混合/纯英文/纯中文输入,通过意群拆解+英语思维重构生成分级英文台词,适配S1-S4难度等级 + 典型场景:飞书剧本表格列F补英文台词、动画/课程台词批量生产 version: 2.1.0 author: shark entry: python3 scripts/gen_script.py diff --git a/business_production/剧本翻译/skills/kids-english-script-production/assets/stage_config.yaml b/business_production/英文台词/skills/kids-english-script-production/assets/stage_config.yaml similarity index 100% rename from business_production/剧本翻译/skills/kids-english-script-production/assets/stage_config.yaml rename to business_production/英文台词/skills/kids-english-script-production/assets/stage_config.yaml diff --git a/business_production/剧本翻译/skills/kids-english-script-production/assets/validation_config.yaml b/business_production/英文台词/skills/kids-english-script-production/assets/validation_config.yaml similarity index 100% rename from business_production/剧本翻译/skills/kids-english-script-production/assets/validation_config.yaml rename to business_production/英文台词/skills/kids-english-script-production/assets/validation_config.yaml diff --git a/business_production/剧本翻译/skills/kids-english-script-production/assets/调优指南.md b/business_production/英文台词/skills/kids-english-script-production/assets/调优指南.md similarity index 100% rename from business_production/剧本翻译/skills/kids-english-script-production/assets/调优指南.md rename to business_production/英文台词/skills/kids-english-script-production/assets/调优指南.md diff --git a/business_production/剧本翻译/skills/kids-english-script-production/examples/sample_output.txt b/business_production/英文台词/skills/kids-english-script-production/examples/sample_output.txt similarity index 100% rename from business_production/剧本翻译/skills/kids-english-script-production/examples/sample_output.txt rename to business_production/英文台词/skills/kids-english-script-production/examples/sample_output.txt diff --git a/business_production/剧本翻译/skills/kids-english-script-production/examples/sample_script.txt b/business_production/英文台词/skills/kids-english-script-production/examples/sample_script.txt similarity index 100% rename from business_production/剧本翻译/skills/kids-english-script-production/examples/sample_script.txt rename to business_production/英文台词/skills/kids-english-script-production/examples/sample_script.txt diff --git a/business_production/剧本翻译/skills/kids-english-script-production/references/l1_word_list.json b/business_production/英文台词/skills/kids-english-script-production/references/l1_word_list.json similarity index 100% rename from business_production/剧本翻译/skills/kids-english-script-production/references/l1_word_list.json rename to business_production/英文台词/skills/kids-english-script-production/references/l1_word_list.json diff --git a/business_production/剧本翻译/skills/kids-english-script-production/scripts/gen_script.py b/business_production/英文台词/skills/kids-english-script-production/scripts/gen_script.py similarity index 100% rename from business_production/剧本翻译/skills/kids-english-script-production/scripts/gen_script.py rename to business_production/英文台词/skills/kids-english-script-production/scripts/gen_script.py diff --git a/business_production/课程组件/review/component-design.md b/business_production/课程组件/review/component-design.md new file mode 100644 index 0000000..9efe5c5 --- /dev/null +++ b/business_production/课程组件/review/component-design.md @@ -0,0 +1,9 @@ +# component-design — 业务侧思考 + +> 本技能为已有的组件设计规范文档,本次仅做了触发机制对齐更新(跟随 script-component-production 的触发词变更),无独立的业务侧讨论输入。 +> +> 更新范围:frontmatter description 中的触发器描述(`/组件配置` → `/组件生产`,排除 `组件配置-json`) + +## 无独立业务讨论 + +本次对话中,`component-design` 作为 `script-component-production` 的依赖被联动更新。所有业务决策(触发器命名、职责边界、工作流)均以 `script-component-production` 为主体讨论,见 `../review/script-component-production.md`。 diff --git a/business_production/课程组件/review/script-component-production.md b/business_production/课程组件/review/script-component-production.md new file mode 100644 index 0000000..5a255b5 --- /dev/null +++ b/business_production/课程组件/review/script-component-production.md @@ -0,0 +1,73 @@ +# script-component-production — 业务侧思考 + +> 来源:[刘彦江] 2026-05-13 对话 + +## 为什么触发器用"组件生产"而非"组件配置" + +### 问题 +最初设计的触发器是 `/组件配置`。但这是错误的命名——"配置"暗示本技能负责格式定义、JSON结构等技术性工作,而实际上本技能的职责是**内容生产**。 + +### 决策(刘彦江明确要求) +- 触发器改为 `/组件生产` +- "生产"准确描述了本技能的职责:从剧本中识别组件行 → 生成组件内容 → 回填 + +--- + +## 为什么本技能不碰 JSON + +### 业务事实 +组件从无到有的完整链路是: +``` +剧本 → 组件内容(结构化文本)→ [教研员审校] → JSON配置 → 配置库 + ← 本技能负责 → ← interactive-component-json 负责 → +``` + +### 决策(刘彦江明确要求) +- 本技能产出**结构化文本格式**(【任务标题】【资源配置】【互动内容】...) +- 不生成 JSON,不定义 JSON schema +- JSON 转换是 `interactive-component-json` 技能的职责 + +### 设计原因 +1. **人的参与节点**:教研员需要审校组件内容(文本格式更可读),确认无误后再进JSON +2. **职责单一**:内容生产和技术格式转换是两个独立环节,不应耦合 +3. **可追溯**:文本格式的中间产物便于 git diff、版本对比、问题定位 + +--- + +## 为什么要教研员二轮审校后才进 JSON + +### 业务事实 +刘彦江明确:业务流程里有教研员对产出的组件内容进行二轮审校。确认没问题,才能进入配置 json 的生产。 + +### 设计含义 +1. `/组件生产` 产出 → 先不改任何数据格式 → 等待人工确认 +2. 确认后 → 由 `interactive-component-json`(`/组件配置-json`)将结构化文本转为标准JSON +3. 两道工序之间有明确的人审节点,不能全自动带过 + +--- + +## 为什么"组件配置-json"必须排除 + +### 问题 +最初设计中包含"组件配置-json"作为短语触发通道。但这是 `interactive-component-json` 技能的唯一触发器。两个技能共用触发词会导致: +- 用户说"组件配置-json",两个技能同时响应 +- 结果冲突、覆盖、数据不稳定 + +### 决策(刘彦江明确要求) +- "组件配置-json"必须从本技能触发词中删除 +- 在本技能的防误触发声明中明确标注:含"组件配置-json"不触发 +- 两个技能零重叠触发词空间 + +--- + +## 技能间协作关系 + +``` +/英文台词 /组件生产 /组件配置-json +(kids-english-) (script-component-) (interactive-component-json) + │ │ │ + 列E→列F 列G(文本) 列G→JSON + 英文台词 组件内容 配置入库 + │ + [教研员审校] +``` diff --git a/memory/.dreams/events.jsonl b/memory/.dreams/events.jsonl index faa252d..e25f4a5 100644 --- a/memory/.dreams/events.jsonl +++ b/memory/.dreams/events.jsonl @@ -7,3 +7,9 @@ {"type":"memory.recall.recorded","timestamp":"2026-05-11T02:51:47.112Z","query":"reading_pic_qa bitable table_id 单元挑战 阅读","resultCount":1,"results":[{"path":"memory/2026-05-07.md","startLine":1,"endLine":20,"score":1}]} {"type":"memory.recall.recorded","timestamp":"2026-05-12T01:33:44.968Z","query":"reading information matching 图片 阅读信息匹配题","resultCount":3,"results":[{"path":"memory/2026-05-07.md","startLine":1,"endLine":20,"score":1},{"path":"memory/2026-05-11.md","startLine":39,"endLine":55,"score":1},{"path":"memory/2026-05-11.md","startLine":20,"endLine":43,"score":1}]} {"type":"memory.recall.recorded","timestamp":"2026-05-12T01:41:17.155Z","query":"reading_matchInfo skill SKILL.md 阅读信息匹配题","resultCount":4,"results":[{"path":"memory/2026-05-07.md","startLine":1,"endLine":20,"score":1},{"path":"memory/2026-05-11.md","startLine":66,"endLine":83,"score":1},{"path":"memory/2026-05-11.md","startLine":51,"endLine":68,"score":1},{"path":"memory/2026-05-11.md","startLine":39,"endLine":55,"score":1}]} +{"type":"memory.recall.recorded","timestamp":"2026-05-13T03:09:54.362Z","query":"审校输出格式 台词润色 剧本表格","resultCount":3,"results":[{"path":"memory/2026-05-12.md","startLine":206,"endLine":226,"score":1},{"path":"memory/2026-04-30.md","startLine":50,"endLine":63,"score":1},{"path":"memory/2026-04-30.md","startLine":23,"endLine":41,"score":1}]} +{"type":"memory.recall.recorded","timestamp":"2026-05-13T03:18:48.153Z","query":"对话台词优化通用规则 精简原则 口语化原则 拆分原则","resultCount":1,"results":[{"path":"memory/2026-04-02.md","startLine":1,"endLine":16,"score":1}]} +{"type":"memory.recall.recorded","timestamp":"2026-05-13T07:40:28.541Z","query":"kids-english-script-production skill","resultCount":3,"results":[{"path":"memory/2026-05-13.md","startLine":88,"endLine":108,"score":1},{"path":"memory/2026-05-13.md","startLine":71,"endLine":92,"score":1},{"path":"memory/2026-05-13.md","startLine":105,"endLine":115,"score":1}]} +{"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}]} diff --git a/memory/.dreams/short-term-recall.json b/memory/.dreams/short-term-recall.json index 49b03c8..d0baf67 100644 --- a/memory/.dreams/short-term-recall.json +++ b/memory/.dreams/short-term-recall.json @@ -1,6 +1,6 @@ { "version": 1, - "updatedAt": "2026-05-12T01:41:17.155Z", + "updatedAt": "2026-05-13T13:40:41.646Z", "entries": { "memory:memory/2026-05-07.md:57:74": { "key": "memory:memory/2026-05-07.md:57:74", @@ -177,19 +177,22 @@ "endLine": 55, "source": "memory", "snippet": "**图片资源新规:** - 每个题组共享一张图(非每题独立配图) - 产出 JSON 同时输出图片描述 prompt(英文,卡通简线条风格) - prompt 写入 bitable `图片描述` 列,格式 `{\"first\": \"...\", \"second\": \"...\"}` **通用化写入流程:** - 单元挑战全题型适用:定位 bitable 表 → 创建/更新记录 → 写入 `题目集合 ID` + `jsonData` + `图片描述` - 各题型仅 jsonData 结构不同,写入流程统一 **重写文件:** - `business_production/单元挑战/skills/unit_challenge/questions/writing/common/writing_pic_qa/SKILL.md`(11节完整规范) - `business_production/单元挑战/skills/unit_challenge/questions/writing/writing_pic_qa/SKILL.md`(简化为快速入口) **bitable 更新:** 已将新 JSON(每题组1图2题)+ 图片描述写入 `recviZlAxxXlAb` ### 刘彦江 (ou_5af74c1fb96042e33cc0f16b5ca02cf4) — 图片描述格式修正 - **时间:** 09:46 ~ 09:51 - **修正:** 用户给出中文样例格式,要求图片描述从英文 prompt 改为纯中文结构化列表 - **新格式:** 编号 `1.` / `2.` 自上而下,每段", - "recallCount": 2, + "recallCount": 4, "dailyCount": 0, "groundedCount": 0, - "totalScore": 2, + "totalScore": 4, "maxScore": 1, "firstRecalledAt": "2026-05-11T02:19:37.985Z", - "lastRecalledAt": "2026-05-11T02:37:23.203Z", + "lastRecalledAt": "2026-05-13T13:40:41.646Z", "queryHashes": [ "6f88450e9f9b", - "be437071312f" + "be437071312f", + "11ea0881b126", + "08b6f3142a2b" ], "recallDays": [ - "2026-05-11" + "2026-05-11", + "2026-05-13" ], "conceptTags": [ "创建/更新记录", @@ -357,6 +360,380 @@ "自动", "问题" ] + }, + "memory:memory/2026-05-12.md:206:226": { + "key": "memory:memory/2026-05-12.md:206:226", + "path": "memory/2026-05-12.md", + "startLine": 206, + "endLine": 226, + "source": "memory", + "snippet": "- **10 条 sentenceMeaningMeaning JSON 修复:** - 根因:explanation 中 ASCII `\"` 被用作中文引号 - 修复策略演变:状态机拆分失败 → 正则重建 → 发现Q2丢失 → 从中文列完整重建 - 最终:10/10 可正确解析,审校结果同步更新 - 受影响:1213004/1213006/1213010/1214008/1215005/1216001/1216004/1216007/1216008/1216010 ### 刘彦江 — 核心互动全题型 JSON 配置标准沉淀(17:05 ~ 17:50) - **产出 Skill 1:** `skills/bitable-reader/SKILL.md` — 通用 bitable 读取(任何 bitable 通用) - **产出 Skill 2:** `skills/core-content-json-standard/SKILL.md` v2.0 — 全题型 JSON 标准(393行) - **架构:** 通用字段在前(ID/kpInfo/taskData)+ 5大题型分类(📖阅读2 🎧听力3 🗣口语4 ✏️写作5)+ 审校规则 + 扩展指南 - **覆盖率:** 14/15 种题型(口语探讨 S0 无数据) - **产出脚本:** `scripts/audit_core_reading_S0.py` — 合作阅读 S0 审校 ## 经验教训 ### bitable 写入需严格流程管控(2026-05-12) - 批量更新 JSON:写入前完整提取", + "recallCount": 1, + "dailyCount": 0, + "groundedCount": 0, + "totalScore": 1, + "maxScore": 1, + "firstRecalledAt": "2026-05-13T03:09:54.362Z", + "lastRecalledAt": "2026-05-13T03:09:54.362Z", + "queryHashes": [ + "f151bc633ad1" + ], + "recallDays": [ + "2026-05-13" + ], + "conceptTags": [ + "10/10", + "skills/bitable-reader/skill.md", + "v2.0", + "id/kpinfo/taskdata", + "14/15", + "scripts/audit-core-reading-s0.py", + "sentencemeaningmeaning", + "修复" + ] + }, + "memory:memory/2026-04-30.md:50:63": { + "key": "memory:memory/2026-04-30.md:50:63", + "path": "memory/2026-04-30.md", + "startLine": 50, + "endLine": 63, + "source": "memory", + "snippet": "格式: 【任务标题】【情境引入】【互动内容】【互动反馈】【后置对话】。目标词用$...$包裹,选项为图片区域编号(00/01/02)。 #### 5. 对话选择 (1条) — 规则: 参考 teaching_config 示例格式 + mid_dialog_choose prompt | ID | 任务标题 | 选项 | 知识点 | |---|---------|------|--------| | 1115514 | 判断蕃茄的好坏 | It is a good one. / It is a bad one. | one | 格式: 【任务标题】【资源配置】【情境引入】【互动内容】【后置对话】。互动内容含要求+选项+反馈。 ### 关键经验 - **组件配置列位置变更**: 本轮操作中发现sheet新增了「配置信息」列(C列),导致组件配置从F列移至G列。后续操作需先确认列结构。 - **Bot身份操作**: 所有飞书 sheet 写入均使用 Bot 身份(App ID: cli_a931175d41799cc7),Feishu API Token: t-g1044ubUXXMPFXBW75RILL7YZKRGAKQGWWDV2R3D - **对话选择组件**: 无独立 SKILL.md,需参照 interactive-component-json 的 prompt_registry.py 中 mid_dialog_choose 模板 + bitable 中已有的 teaching_config 示例格式", + "recallCount": 1, + "dailyCount": 0, + "groundedCount": 0, + "totalScore": 1, + "maxScore": 1, + "firstRecalledAt": "2026-05-13T03:09:54.362Z", + "lastRecalledAt": "2026-05-13T03:09:54.362Z", + "queryHashes": [ + "f151bc633ad1" + ], + "recallDays": [ + "2026-05-13" + ], + "conceptTags": [ + "00/01/02", + "teaching-config", + "mid-dialog-choose", + "cli-a931175d41799cc7", + "skill.md", + "interactive-component-json", + "prompt-registry.py", + "格式" + ] + }, + "memory:memory/2026-04-30.md:23:41": { + "key": "memory:memory/2026-04-30.md:23:41", + "path": "memory/2026-04-30.md", + "startLine": 23, + "endLine": 41, + "source": "memory", + "snippet": "| 8 | 对话朗读 | 我来刷漆吧 | Let me paint it. | 格式: 【任务标题】【资源配置】【情景引入】【互动内容】【后置对话】。情景引入和后置对话默认「无」。 #### 2. 对话挖空 (2条) — 规则: dialogue-fill-in-blanks-config | ID | 任务标题 | 挖空句 | 正确选项 | 干扰项 | 知识点 | |---|---------|--------|---------|--------|--------| | 9 | 告诉May这刷子是我的 | It is ___. | mine | May | mine | | 11 | 给稻草人John画张脸 | We need to ___ a face on John! | paint | make | paint | 格式: 【任务标题】【资源配置】【情境引入】【互动内容】【互动反馈】【后置对话】。挖空句结尾标注(音频),选项标注(正确),反馈台词贴合剧情。 #### 3. 对话组句 (3条) — 规则: dialogue-sentence-building-config | ID | 任务标题 | 答案 | 选项词 | 知识点 | |---|---------|------|-------|--------| | 10 | 制止BoBo拿我的刷子 | BoBo stop, it is mine! | BoBo/stop/it/is/mine | mine | | 12 | 告诉May我也有一顶帽子 | I have one too! | I/have/one/", + "recallCount": 1, + "dailyCount": 0, + "groundedCount": 0, + "totalScore": 1, + "maxScore": 1, + "firstRecalledAt": "2026-05-13T03:09:54.362Z", + "lastRecalledAt": "2026-05-13T03:09:54.362Z", + "queryHashes": [ + "f151bc633ad1" + ], + "recallDays": [ + "2026-05-13" + ], + "conceptTags": [ + "dialogue-fill-in-blanks-config", + "bobo/stop/it/is/mine", + "i/have/one", + "对话", + "朗读", + "我来", + "let", + "paint" + ] + }, + "memory:memory/2026-04-02.md:1:16": { + "key": "memory:memory/2026-04-02.md:1:16", + "path": "memory/2026-04-02.md", + "startLine": 1, + "endLine": 16, + "source": "memory", + "snippet": "# 2026-04-02 工作日志 ## 【规则沉淀】对话台词优化通用规则(来自11-4棒球单元案例) ### 适用场景:所有英语教研内容对话台词润色、优化、修改 --- #### 核心修改规则 1. **精简原则**:删除所有冗余重复表述(重复提到的核心名词如baseball可省略,前文已交代的背景信息不再重复) 2. **口语化原则**:所有台词符合日常对话习惯,避免书面化、生硬的表达,适当增加语气词(Haha、Oops、Uh-oh、Oh boy等) 3. **拆分原则**:长度超过8个单词的长句拆分为2-3个短句,每句对应一个动作/情绪节点,增强交互节奏 4. **情绪匹配原则**:台词语气严格匹配角色当前情绪(惊讶时拆分句子加问号,欢呼时用短词,失落时用省略号) 5. **场景适配原则**:可通过动画/画面表达的内容,不再用台词重复表述,减少信息冗余 --- #### 统一风格标准 整体风格定位:低龄友好、活泼生动、节奏感强、代入感高 - 单句最长不超过10个单词,核心知识点相关句子不超过7个单词 - 每个角色的台词符合其人设(Matt阳光直率、Tom活泼有趣、Amber骄傲好胜) - 对话节奏快,每2-3句台词对应一个剧情节点或互动环节", + "recallCount": 1, + "dailyCount": 0, + "groundedCount": 0, + "totalScore": 1, + "maxScore": 1, + "firstRecalledAt": "2026-05-13T03:18:48.153Z", + "lastRecalledAt": "2026-05-13T03:18:48.153Z", + "queryHashes": [ + "633af3858f54" + ], + "recallDays": [ + "2026-05-13" + ], + "conceptTags": [ + "来自11-4棒球单元案例", + "uh-oh", + "长度超过8个单词的长句拆分为2-3个短句", + "每句对应一个动作/情绪节点", + "可通过动画/画面表达的内容", + "每2-3句台词对应一个剧情节点或互动环节", + "工作", + "日志" + ] + }, + "memory:memory/2026-05-13.md:88:108": { + "key": "memory:memory/2026-05-13.md:88:108", + "path": "memory/2026-05-13.md", + "startLine": 88, + "endLine": 108, + "source": "memory", + "snippet": "### kids-english-script-production 触发机制 - [刘彦江] 要求增加防误触发 - 更新文件: - `business_production/剧本翻译/skills/kids-english-script-production/assets/skill.yml` — 注册描述 - `business_production/剧本翻译/skills/kids-english-script-production/SKILL.md` — 新增触发方式章节 - 触发指令:`/英文台词 --feishu-url <链接> --stage ` (命令触发,推荐) - 短语触发:消息含\"英文台词生产\" - 不会触发:仅含\"翻译\"/\"台词\"/\"英文\" (防误匹配) ## 刘彦江 — 课程组件生产/审校/回填技能创建 ### script-component-production 技能 - 路径:`business_production/课程组件/skills/script-component-production/` - 覆盖:27种中互动 + 14种核心互动组件类型 - 6节点流程:读取文档→解析Sheet→识别组件行→匹配组件类型→LLM生成JSON→审校→回填 - 触发:`/组件配置 --feishu-url <链接>` (命令触发,推荐) - 防误触发:仅含\"互动\"/\"配置\"/\"JSON\"不触发 - 与 /英文台词 技能协作:先英文台词回填列F,后组件配置回填列G - 创建文件: - `SKILL.md` — 完整技能文档(", + "recallCount": 1, + "dailyCount": 0, + "groundedCount": 0, + "totalScore": 1, + "maxScore": 1, + "firstRecalledAt": "2026-05-13T07:40:28.541Z", + "lastRecalledAt": "2026-05-13T07:40:28.541Z", + "queryHashes": [ + "3beb30f546c9" + ], + "recallDays": [ + "2026-05-13" + ], + "conceptTags": [ + "kids-english-script-production", + "feishu-url", + "课程组件生产/审校/回填技能创建", + "script-component-production", + "skill.md", + "kids", + "english", + "script" + ] + }, + "memory:memory/2026-05-13.md:71:92": { + "key": "memory:memory/2026-05-13.md:71:92", + "path": "memory/2026-05-13.md", + "startLine": 71, + "endLine": 92, + "source": "memory", + "snippet": "## 刘彦江 — 教学大纲文档结构解析 + 英文台词回填 + skill触发机制 ### S1/S2 大纲文档结构 - 文档:https://makee-interactive.feishu.cn/wiki/IOmNwV55Si6JTykCAM5c1HM1nSd - 类型:独立电子表格,含 2 个子表 - S1 大纲:264行×22列,Unit 0~12,2行冻结表头 - S2 大纲:204行×22列,Unit 13~18 - 列结构:A=Unit / B=Lesson / C~F=Vocabulary(知识模块/单词/词性/词义) / G~I=Sentences(知识模块/句型/例句) / J~V=未使用 - 每Unit有 Lesson 1~5,每行=一条词汇+可选句型(知识模块维度独立) ### 英文台词生产并回填:L1-S2-U17-L3 紧急拜访 - 文档:https://makee-interactive.feishu.cn/wiki/FkKgw4jBPix9lNkRemLcSwYOnid - spreadsheet_token: DA7csROG6hmnQMt59oScOLRfnWc, sheet: Nr3Ovs - 知识点:grandmother, grandfather, visit, today + 句型 Today is... / ...visit+sb - 操作:清空列F → 按MEMORY.md对话台词优化规则重新生产116条 → 写入并验证通过 - 规则应用:知识点保留/精简/口语化(收缩)/拆分(>8词)/情绪匹配/正向引导(mad→up", + "recallCount": 1, + "dailyCount": 0, + "groundedCount": 0, + "totalScore": 1, + "maxScore": 1, + "firstRecalledAt": "2026-05-13T07:40:28.541Z", + "lastRecalledAt": "2026-05-13T07:40:28.541Z", + "queryHashes": [ + "3beb30f546c9" + ], + "recallDays": [ + "2026-05-13" + ], + "conceptTags": [ + "s1/s2", + "知识模块/单词/词性/词义", + "知识模块/句型/例句", + "l1-s2-u17-l3", + "spreadsheet-token", + "按memory.md对话台词优化规则重新生产116条", + "知识点保留/精简/口语化", + "情绪匹配/正向引导" + ] + }, + "memory:memory/2026-05-13.md:105:115": { + "key": "memory:memory/2026-05-13.md:105:115", + "path": "memory/2026-05-13.md", + "startLine": 105, + "endLine": 115, + "source": "memory", + "snippet": "- 与 /英文台词 技能协作:先英文台词回填列F,后组件配置回填列G - 创建文件: - `SKILL.md` — 完整技能文档(触发/流程/组件类型速查/审校项/使用示例) - `assets/skill.yml` — 技能注册(依赖 interactive-component-json/core-content-json-standard 等) - `assets/component_rules.yaml` — 组件类型自动匹配信号规则 - `assets/review_checklist.yaml` — 15项审校清单(ID/字段/题型/知识点/选项/高亮/反馈/超纲等) ### 已更新文件 - `component-design/SKILL.md` — 增加触发方式章节(防误触发) - `kids-english-script-production/assets/skill.yml` + `SKILL.md` — 增加 /英文台词 触发(上轮)", + "recallCount": 1, + "dailyCount": 0, + "groundedCount": 0, + "totalScore": 1, + "maxScore": 1, + "firstRecalledAt": "2026-05-13T07:40:28.541Z", + "lastRecalledAt": "2026-05-13T07:40:28.541Z", + "queryHashes": [ + "3beb30f546c9" + ], + "recallDays": [ + "2026-05-13" + ], + "conceptTags": [ + "skill.md", + "触发/流程/组件类型速查/审校项/使用示例", + "assets/skill.yml", + "assets/component-rules.yaml", + "assets/review-checklist.yaml", + "id/字段/题型/知识点/选项/高亮/反馈/超纲等", + "component-design/skill.md", + "英文" + ] + }, + "memory:memory/2026-05-13.md:1:33": { + "key": "memory:memory/2026-05-13.md:1:33", + "path": "memory/2026-05-13.md", + "startLine": 1, + "endLine": 33, + "source": "memory", + "snippet": "# 2026-05-13 工作记忆 ## 王璐辰 — 剧本台词润色审校 ### 审校#1: L1-S2-U18-L1 中国周 - 文档:https://makee-interactive.feishu.cn/wiki/PgWrwllW2ikQKgkLTYKcmKFonXc - 🔴 必须修改 4 项(行70句子过长、行72 you→your、行157 It this→Is this、行159全角?) - 🟡 建议关注 9 项 | ⚠️ 缺失润色 2 项(行47图片单选4、行185核心听力17) ### 审校#2: L1-S2-U18-L2 登龙秘笈 - 文档:https://makee-interactive.feishu.cn/wiki/KNAZwTYXsib2Ijk6BrGciRelnSf - 🔴 必须修改 1 项(行120空格缺失) - 🟡 建议关注:全文 dragon/loong 混用 - ⚠️ 缺失润色 2 项 ### 格式约定 [王璐辰确认] 后续\"审校\"请求默认输出格式: 1. 表格概况(行数/覆盖) 2. 🔴 必须修改 → 表格列出(行号/角色/问题/当前值/建议修改) 3. 🟡 建议关注 → 表格列出 4. ⚠️ 缺失项(如有) 5. ✅ 整体评价 # 2026-05-13 工作记忆 ## 王璐辰 — 剧本台词润色审校 ### 审校#1: L1-S2-U18-L1 中国周 - 文档:https://makee-interactive.feishu.cn/wiki/PgWrwllW2ikQKgkLTYKcmKFonXc - 🔴 必须修改", + "recallCount": 1, + "dailyCount": 0, + "groundedCount": 0, + "totalScore": 1, + "maxScore": 1, + "firstRecalledAt": "2026-05-13T09:14:31.249Z", + "lastRecalledAt": "2026-05-13T09:14:31.249Z", + "queryHashes": [ + "4906de25dd2a" + ], + "recallDays": [ + "2026-05-13" + ], + "conceptTags": [ + "l1-s2-u18-l1", + "l1-s2-u18-l2", + "dragon/loong", + "行数/覆盖", + "行号/角色/问题/当前值/建议修改", + "工作", + "记忆", + "剧本" + ] + }, + "memory:memory/2026-05-12.md:189:209": { + "key": "memory:memory/2026-05-12.md:189:209", + "path": "memory/2026-05-12.md", + "startLine": 189, + "endLine": 209, + "source": "memory", + "snippet": "- **脚本:** `scripts/fix_matchInfo_0213_0218_desc.py` - **技能更新:** 图片描述规范写入 `business_production/单元挑战/skills/unit_challenge/questions/reading/reading_info_match/SKILL.md` ### 刘彦江 — L1 配置表审校 + 技能沉淀(11:50 ~ 12:10) - **数据源:** `Nq3Zb258aae7SRs2QfXcqsQYnxJ` / `tblTxGpf6GQ5c7DZ` - **ID 编码:** 7位 = S(2位) + U(2位) + 序列号(3位) - **技能沉淀:** `scripts/audit_l1_config.py` + `skills/audit_l1_config/SKILL.md` ### 刘彦江 — 1213001-1216010 批量审校 40 条(12:10 ~ 14:00) - **范围:** L1S2 Units 13-16 - **脚本修复(过程中):** sort 大小写误报 / ID 末尾空格 / 朗读跟读题误报 - **结果:** 系统性 classificationInfo 互换(已标记不视为错误) + 17条具体错误 + 23条通过 - **脚本:** `scripts/audit_l1_config.py` + `scripts/audit_batch_1213001_1216010.py` ### 刘彦江 — 审校结果修正 + JSON 修复(14:00 ~", + "recallCount": 1, + "dailyCount": 0, + "groundedCount": 0, + "totalScore": 1, + "maxScore": 1, + "firstRecalledAt": "2026-05-13T09:14:31.249Z", + "lastRecalledAt": "2026-05-13T09:14:31.249Z", + "queryHashes": [ + "4906de25dd2a" + ], + "recallDays": [ + "2026-05-13" + ], + "conceptTags": [ + "scripts/audit-l1-config.py", + "skills/audit-l1-config/skill.md", + "1213001-1216010", + "13-16", + "脚本", + "scripts", + "fix-matchinfo-0213-0218-desc.py", + "技能" + ] + }, + "memory:memory/2026-05-12.md:92:113": { + "key": "memory:memory/2026-05-12.md:92:113", + "path": "memory/2026-05-12.md", + "startLine": 92, + "endLine": 113, + "source": "memory", + "snippet": "- L2 B级及以上图片文字需为完整陈述句(3-5词+) ### 刘彦江 — L1 配置表审校 + 技能沉淀(11:50 ~ 12:10) - **数据源:** 飞书多维表格「互动知识点 - 句子」→「Level 1 配置表」(`Nq3Zb258aae7SRs2QfXcqsQYnxJ` / `tblTxGpf6GQ5c7DZ`) - **ID 编码规则:** 7位 = 前2位(Season) + 中2位(Unit) + 后3位(序列号) - 例: 1214001 = L1S2(12) + U14(14) + 001 - **审校记录 1214001 (You need to...) 发现:** 1. ❌ classificationInfo 值互换: cambridgeLevel=A1 vs cefrLevel=YLE(与列字段反了) 2. ❌ sentenceStructureSort 题2: 单词库有need无needs,三单主语Otis语法错误 3. ⚠️ sentenceStructureClozeWordMcq: \"put your toys\" 省略 away 不够自然 - **技能沉淀:** - 脚本: `scripts/audit_l1_config.py` — 7项自动检查 - SKILL.md: `skills/audit_l1_config/SKILL.md` — 含完整审校流程 + 4项人工审核清单 ### 刘彦江 — 1213001-1216010 批量审校 40 条(12:10 ~ 14:00) - **范围:** L1", + "recallCount": 1, + "dailyCount": 0, + "groundedCount": 0, + "totalScore": 1, + "maxScore": 1, + "firstRecalledAt": "2026-05-13T09:14:31.249Z", + "lastRecalledAt": "2026-05-13T09:14:31.249Z", + "queryHashes": [ + "4906de25dd2a" + ], + "recallDays": [ + "2026-05-13" + ], + "conceptTags": [ + "3-5词", + "scripts/audit-l1-config.py", + "skill.md", + "skills/audit-l1-config/skill.md", + "1213001-1216010", + "以上", + "图片", + "文字" + ] + }, + "memory:memory/2026-05-12.md:1:20": { + "key": "memory:memory/2026-05-12.md:1:20", + "path": "memory/2026-05-12.md", + "startLine": 1, + "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, + "dailyCount": 0, + "groundedCount": 0, + "totalScore": 2, + "maxScore": 1, + "firstRecalledAt": "2026-05-13T13:40:33.965Z", + "lastRecalledAt": "2026-05-13T13:40:41.646Z", + "queryHashes": [ + "11ea0881b126", + "08b6f3142a2b" + ], + "recallDays": [ + "2026-05-13" + ], + "conceptTags": [ + "021301-021801", + "按每道题的上下文匹配对应的标识/通知类型标签", + "图片必须是真实标识/通知", + "3-5词", + "工作", + "日志", + "会话", + "记录" + ] + }, + "memory:memory/2026-05-12.md:36:52": { + "key": "memory:memory/2026-05-12.md:36:52", + "path": "memory/2026-05-12.md", + "startLine": 36, + "endLine": 52, + "source": "memory", + "snippet": "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 1 配置表」(`Nq3Zb258aae7SRs2QfXcqsQYnxJ` / `tblTxGpf6GQ5c7DZ`) - **ID 编码规则:** 7位 = 前2位(Season) + 中2位(Unit) + 后3位(序列号) - 例: 1214001 = L1S2(12) + U14(14) + 001", + "recallCount": 2, + "dailyCount": 0, + "groundedCount": 0, + "totalScore": 2, + "maxScore": 1, + "firstRecalledAt": "2026-05-13T13:40:33.965Z", + "lastRecalledAt": "2026-05-13T13:40:41.646Z", + "queryHashes": [ + "11ea0881b126", + "08b6f3142a2b" + ], + "recallDays": [ + "2026-05-13" + ], + "conceptTags": [ + "按每道题的上下文匹配对应的标识/通知类型标签", + "图片必须是真实标识/通知", + "3-5词", + "查询", + "bitable", + "获取", + "记录", + "当前" + ] } } } diff --git a/memory/2026-05-13-1230.md b/memory/2026-05-13-1230.md new file mode 100644 index 0000000..7ea43b8 --- /dev/null +++ b/memory/2026-05-13-1230.md @@ -0,0 +1,149 @@ +# Session: 2026-05-13 12:30:53 GMT+8 + +- **Session Key**: agent:xiaoyan:feishu:direct:ou_5af74c1fb96042e33cc0f16b5ca02cf4 +- **Session ID**: 676679b6-fede-4ae5-8536-f53d4d795983 +- **Source**: feishu + +## Conversation Summary + +user: 去审核单元挑战阅读P2段落匹配题 +assistant: 审校完成。以下为阅读-P2段落匹配题全部14个子题目集的审校结果: + +--- + +## 📋 阅读-P2 段落匹配题 审校报告 + +### 🚨 严重问题 + +**1. QID 010199「Club Members」— 题型格式不统一** + +7题中有5题(Q2/Q3/Q4/Q6/Q7)不是Who问句格式,每道题使用了各自独立的选项(如 professions/ingredients/clubs),而非统一的3个人名选项。这与段落匹配题的标准格式(所有题目共用3个段落标题作选项)不一致。 + +| 题号 | 当前问句 | 当前选项 | 建议改为 | +|------|---------|---------|---------| +| Q2 | What does Anna want to be? | A pioneer / A chef / A teammate | Who wants to become a chef? | +| Q3 | Which spicy ingredient does Anna use? | Coconut / Mushroom / Chilli | Who uses chillies in cooking? | +| Q4 | What is Ben's club about? | Cooking / Football / History | Who is in the football club? | +| Q6 | What is Ben's main goal? | To pass exam / To cook / To learn | Who wants to pass a sports exam? | +| Q7 | What does Cindy learn about? | Recipes / Skills / Pioneers | Who learns about famous pioneers? | + +**2. QID 010199「School gardens competition」— 缺2题** +- 当前仅5题,标准为7题 +- 答案分布:0:1, 1:2, 2:2(Robot Club仅1题) +- 需补齐2题,确保答案分布2-2-3或2-3-2 + +### 🔴 高优先级 + +**3. 全部14个子题目集的能力标签不合规 — 182处** + +所有题目均使用非官方阅读能力标签,其中: +- `听觉抓取关键信息` ×54 — 含"听觉"二字,属听力标签误用 +- `多特征整合` ×33 — 未定义标签 +- `语用推断` ×11 — 属听力标签(语用推断|否定与纠错) +- `信息定位` ×28 / `细节理解` ×24 / `信息提取` ×14 等 — 非标准命名 + +**替换映射表:** + +| 当前标签 | 应替换为 | +|---------|---------| +| 听觉抓取关键信息 / 信息提取 / 细节匹配 / 信息定位 | 细节理解|事实信息提取 或 显性信息定位|关键词识别 | +| 多特征整合 | 细节理解|事实信息提取 | +| 语用推断 / 因果推断 / 条件推断 / 观点态度 / 态度推断 | 推理判断|原因/结果 | +| 主旨概括 | 主旨理解|段落/文本大意 | +| 时间信息匹配 | 细节理解|事实信息提取 | + +### 🟡 中优先级 + +**4. QID 021901「A Day at the Office」— 答案分布不均衡** +- Ms. Carter 答对4题,Mr. Evans仅1题 (分布 1:4:2) +- 建议调整使每人占2-3题 + +### ✅ 通过项 + +- JSON结构14/14完整可解析 +- answer索引均在options范围内 +- explanation均有内容 +- 12/14子题目集答案分布均衡 +- 其余题目集问句格式统一(全部Who问句) + +--- + +需要我帮你批量修正这些问题并回写到多维表格吗? +user: 010199 的内容忽略,是测试数据 +其他的问题,先不修复,回填到审核结果中,有问题写问题,没问题写✅ +assistant: 全部6条回写成功。回读验证一下: +assistant: 回写验证完毕,6条审核结果已写入"审核结果"字段: + +| QID | 子题目集 | 审核结论 | +|-----|---------|---------| +| 010199 | Club Members / School gardens | 空白(测试数据,已跳过) | +| 021901 | My First Adventure Race / A Day at the Office | ❌ 能力标签 + 答案分布不均 | +| 022001 | Winter Boots Shopping / Yellow and Green | ❌ 能力标签不合规 | +| 022101 | Winter Weather / A Day at the Port | ❌ 能力标签不合规 | +| 022201 | My Grandson and I / School Web | ❌ 能力标签不合规 | +| 022301 | How I Feel / Joining and Leaving Clubs | ❌ 能力标签不合规 | +| 022401 | Different Sounds / Favourite Things | ❌ 能力标签不合规 | + +每条记录均标注了具体不合规标签名及替换映射方案。 +user: 1. 将所有的 tag 改为 45 个标准 tag 中对应的 tag +2. 答案分布不均匀的问题,可以调整一下 +3. 调整题量为 5 题,删除哪两个,你来定 +assistant: 全部修正完成。再回读验证一条确保数据完整性: +assistant: 验证通过。再更新审核结果: +assistant: 全部修正完成。汇总: + +| QID | 子题目集 | 能力标签 | 题量 | 答案分布 | +|-----|---------|---------|------|---------| +| 021901 | My First Adventure Race | 细节理解|事实信息提取 | 7→5 | Leo:1 Maya:2 Sam:2 | +| | A Day at the Office | 细节理解|事实信息提取 | 7→5 | Evans:1 Carter:2 Lee:2 ✅分布已修复 | +| 022001 | Winter Boots Shopping | 细节理解事实信息提取 / 推理判断原因结果 | 7→5 | Anna:1 Ben:2 Cindy:2 | +| | Yellow and Green Adventures | 同上 | 7→5 | Mia:1 Leo:2 Sam:2 | +| 022101 | Winter Weather Adventures | 显性信息定位关键词识别 / 细节理解事实信息提取 | 7→5 | Jake:1 Maya:2 Leo:2 | +| | A Day at the Port | +主旨理解段落文本大意 / 推理判断原因结果 | 7→5 | Boat:2 Skate:1 Fish:2 | +| 022201 | My Grandson and I | 显性信息定位关键词识别 / 细节理解事实信息提取 | 7→5 | Tom:2 Leo:2 Ben:1 | +| | Our School Web Project | 同上 | 7→5 | Web:2 Lib:1 Coding:2 | +| 022301 | How I Feel About Different Things | 细节理解事实信息提取 / 推理判断原因结果 | 7→5 | Anna:2 Ben:1 Cindy:2 | +| | Joining and Leaving Clubs | 细节理解|事实信息提取 | 7→5 | Anna:2 Ben:2 Cindy:1 | +| 022401 | Different Sounds in My Life | 细节理解事实信息提取 / 推理判断原因结果 | 7→5 | Thunder:2 Concert:1 Park:2 | +| | People and Their Favourite Things | 同上 | 7→5 | Green:2 Hill:1 Tommy:2 | + +三项修正全部回写完毕:①能力标签已替换为官方标签 ②答案分布已均衡 ③每集7→5题。 +user: 把目前核心互动、中互动、单元挑战 等相关的,所有相关的 skills,全部输出,打包成一个 zip +assistant: 打包完成。`skills_教研相关.zip`(3.5M),共23个skills: + +**个人 skills(18个)— 教研内容生产核心:** + +| 分类 | Skill | 说明 | +|------|-------|------| +| 互动组件生产 | `interactive-component-json` | 互动组件配置JSON生成器(27种中互动+12种核心互动) | +| | `task-router` | 中互动6种对话组件标准化(生产+审校) | +| | `core-content-json-standard` | 14种核心互动题型JSON标准 | +| | `dialogue-choose-config` | 对话选择组件规范 | +| | `dialogue-core-navigation-config` | 核心互动导览组件规范 | +| | `dialogue-fill-in-blanks-config` | 对话挖空组件规范 | +| | `dialogue-image-description-config` | 看图说话组件规范 | +| | `dialogue-reading-config` | 对话朗读组件规范 | +| | `dialogue-sentence-building-config` | 对话组句组件规范 | +| | `dialogue-single-choice-image-config` | 图片单选组件规范 | +| 审校校验 | `audit_l1_config` | L1配置表(句子知识点)自动化审校 | +| | `cambridge-yle-ket-exam-library` | 剑桥YLE/KET考试题型库 | +| 数据操作 | `bitable-reader` | 多维表格结构读取 | +| | `feishu-embedded-sheet` | 文档内嵌Sheet读写 | +| | `feishu-bitable-translate-fill` | 多维表格翻译填充 | +| | `lark_bitable_operate_as_bot` | Bot身份多维表格操作 | +| | `lark_wiki_operate_as_bot` | Bot身份知识库操作 | +| 数据分析 | `knowledge-mastery-calculator` | 知识点掌握度计算 | + +**全局 skills(5个)— 基础能力支撑:** + +| Skill | 说明 | +|-------|------| +| `lark-action-as-bot` | Bot身份操作指南 | +| `lark-identify-sender` | 飞书发送者身份识别 | +| `lark-send-message-as-bot` | Bot身份消息发送 | +| `multi-model-assistant` | 多模型协助路由 | +| `feishu-chat-troubleshooting` | 飞书群消息故障排查 | +user: Continue the OpenClaw runtime event. +assistant: NO_REPLY +user: zip 输出在对话框 +assistant: 已发送。 diff --git a/memory/2026-05-13-1538.md b/memory/2026-05-13-1538.md new file mode 100644 index 0000000..0a96e25 --- /dev/null +++ b/memory/2026-05-13-1538.md @@ -0,0 +1,131 @@ +# Session: 2026-05-13 15:38:17 GMT+8 + +- **Session Key**: agent:xiaoyan:feishu:direct:ou_1bd7317ae2ccfeb57e1132028847279e +- **Session ID**: d3811bad-8c9d-4ed3-86c5-359fc8ae3e5c +- **Source**: feishu + +## Conversation Summary + +user: 14不接受:parents是生词;30不接受,bring诗生词;34不接受, grandfather 和 grandmother 是知识点,必须完整呈现。其余全部接受,请修改后填入【英文翻译】列 +assistant: 明白,保留 14、30、34 原文。另外 grandfather/grandmother 是知识点词汇,全文统一不改短。现在写入最终版本。 +assistant: 全部写入成功。验证几处关键行: +assistant: 全部验证通过 ✅ + +--- + +## 📊 最终汇总 + +| 项目 | 数量 | +|------|------| +| 写入总行数 | **117 行** | +| 已润色修改 | **47 处** | +| 保持原文不变 | **70 行** | +| 按您要求保留原文 | 14 (No-Mom-and-Dad day)、30 (give)、34 (完整 Grandfather and grandmother) | + +### 修改分类明细 + +| 类别 | 数量 | 示例 | +|------|------|------| +| 🔴 语法/拼写修正 | 14处 | `gandfather→grandfather`、`is→are`、`in→on`、`want→wants` | +| 🟡 长句拆分精简 | 8处 | 行23 (16词→8词)、行73 (14词→7词)、行94 (13词→8词) | +| 🟢 口语化/风格优化 | 25处 | `satisfied→happy`、`make trouble→mess or not`、`full of joy→so much fun` | + +文档链接:https://makee-interactive.feishu.cn/wiki/FkKgw4jBPix9lNkRemLcSwYOnid +user: MEMORY.md 中的对话台词优化通用规则具体是什么?能在哪一天的记录里面找到? +assistant: 对话台词优化通用规则就在 `MEMORY.md` 中,我已加载到当前会话。完整内容如下: + +--- + +### 📋 【教研规则】对话台词优化通用规则 + +**核心修改规则:** + +1. **精简原则** — 删除所有冗余重复表述(重复提到的核心名词可省略,前文已交代的背景信息不再重复) +2. **口语化原则** — 所有台词符合日常对话习惯,避免书面化、生硬的表达,适当增加低龄友好的语气词(Haha、Oops、Uh-oh、Oh boy等) +3. **拆分原则** — 长度超过8个单词的长句拆分为2-3个短句,每句对应一个动作/情绪节点,增强交互节奏 +4. **情绪匹配原则** — 台词语气严格匹配角色当前情绪(惊讶时拆分句子加问号,欢呼时用短词,失落时用省略号) +5. **场景适配原则** — 可通过动画/画面表达的内容,不再用台词重复表述,减少信息冗余 + +**统一风格标准:** +- 整体风格:低龄友好、活泼生动、节奏感强、代入感高 +- 单句最长不超过10个单词,核心知识点相关句子不超过7个单词 +- 每个角色的台词符合其人设 +- 对话节奏快,每2-3句台词对应一个剧情节点或互动环节 +- 弱化说教感,所有指令和引导都以自然对话的方式呈现 + +--- + +关于记录来源,让我查一下是哪天写入的: +assistant: 找到了。来源分两层: + +### 📅 原始记录 +**`memory/2026-04-02.md`** — 当日工作日志中首次沉淀该规则,来自「11-4棒球单元」案例实践。当时规则中的具体案例还是 baseball 相关(如"Matt阳光直率、Tom活泼有趣"),后来提炼到 MEMORY.md 时去掉了案例绑定,做成了通用规则。 + +### 📌 当前版本 +**`MEMORY.md`**(第63-79行)— 经提炼后的通用版本,去掉了具体案例绑定,适用于所有英语教研内容对话台词润色。 + +也就是说:规则最早在 **2026年4月2日** 从棒球单元案例中总结出来,之后提炼通用化写入了 MEMORY.md 长期记忆。 +user: 请在这份memory中加入:润色前确认知识点,含有知识点的句子全部保留,不得更改内容。 +assistant: 已完成。在「对话台词优化通用规则」最前面新增了**前置规则第0条**: + +``` +0. 知识点保留原则:润色前必须先确认当前台词是否含有知识点词汇/句型。 + 含有知识点的句子全文保留、不得修改任何内容(含词序、拼写、标点), + 仅可调整非知识点部分的表达。 +``` + +这条规则的优先级高于后面5条修改规则——先判断是否含知识点,含知识的句子整句冻结,其余部分再应用精简/口语化/拆分等规则。 +user: 再次修改:知识点本身保留,但语法/拼写/标点错误可以修改 +assistant: 已修改。现在第0条规则为: + +``` +0. 知识点保留原则: + 润色前必须先确认当前台词是否含有知识点词汇/句型。 + 含有知识点的句子中,知识点词汇/句型本身必须保留 + (不得替换、增删、调整词序), + 但语法错误、拼写错误、标点错误可以修正。 + 仅可调整非知识点部分的表达。 +``` +user: 使用【润色】memory,润色台词。填入【英文台词】列:https://makee-interactive.feishu.cn/wiki/DAPhw8L1Ui3yJJkeM5ZcyPwDnad?from=from_copylink +assistant: 已获取全部数据。知识点:`open`(adj)、`closed`(adj)、`door`(n)、`home`(adv) + 句型 `The door is open/closed.` + `get home`。现在逐句润色并写入。 +assistant: 全部写入,验证知识点保留情况: +assistant: 全部验证通过 ✅。知识点 `open` / `closed` / `door` / `home` 以及句型 `The door is open/closed.` / `get home` 全部完整保留。 + +--- + +## 📊 润色汇总 + +| 项目 | 数量 | +|------|------| +| 总行数 | **125 行** | +| 已润色 | **22 处** | +| 未改动 | 103 行 | + +### 修改明细 + +| 行 | 角色 | 原文 | 润色后 | 类型 | +|----|------|------|--------|------| +| 15 | User | I **can not** get home... | I **can't** get home... | 语法+缩写 | +| 19 | Peter | We **can not** get home now! | We **can't** get home now! | 语法+缩写 | +| 22 | Eva | ...in the house. **He will** be scared! | ...**still inside**. **He'll** be scared! | 口语化 | +| 24 | Alex | ...**I only want to read books** in my room. | ...**I want to read them** in my room. | 精简重复 | +| 40 | Vicky | ...it **is** closed this morning. | ...it **was** closed this morning. | 时态修正 | +| 59 | User | Great! **You are** home now. | Great! **You're** home now! | 缩写 | +| 63 | Peter | Mom and Dad **are not** home. | Mom and Dad **aren't** home. | 缩写 | +| 67 | Eva | **I will** catch you! | **I'll** catch you! | 缩写 | +| 93 | Alex | Oh no... **He will** cry. | Oh no... **He's going to** cry. | 口语化 | +| 100 | Alex | Not me. **I am** reading. | Not me. **I'm** reading. | 缩写 | +| 108 | Alex | We can go and seek on the second floor. | **Let's check upstairs!** | 自然化 | +| 111 | Alex | ...**It is** always open. | ...**It's** always open. | 缩写 | +| 120 | Eva | my **and Vicky's** room | **me and** Vicky's room | 儿童口语 | +| 130 | User | Only one **last** door **is left**. | Only one door left. | 精简 | +| 137 | User | I can't open it. | I can't open it! | 情绪(紧迫) | +| 145 | User | _leading-space_ Now, the door... | Now, the door is open. | 空格修正 | +| 147 | Eva | Oh no! The door is broken. | Oh no! The door is broken! | 情绪 | +| 171 | User | But now **it is** open. | But now **it's** open. | 缩写 | +| 173 | User | Look! **There is** a footprint here! | Look! **There's** a footprint... | 缩写 | +| 184 | Dan | **We are** home! | **We're** home! | 缩写 | +| 188 | Peter | **You are** finally home! | **You're** finally home! | 缩写 | +| 200 | User | Okay, **I am** coming! | Okay, **I'm** coming! | 缩写 | + +文档链接:https://makee-interactive.feishu.cn/wiki/DAPhw8L1Ui3yJJkeM5ZcyPwDnad diff --git a/memory/2026-05-13.md b/memory/2026-05-13.md new file mode 100644 index 0000000..a90e2d9 --- /dev/null +++ b/memory/2026-05-13.md @@ -0,0 +1,44 @@ + + +## [刘彦江] 单元挑战口语写作内容生产(2026-05-13 21:40) + +### 生产范围(10条记录,全部单题组,全部回填验证通过 ✅) + +| ID | 写作-P1 | 口语-P1 | 写作-P2 | 口语-P2 | 状态 | +|----|---------|---------|---------|---------|------| +| 011301 | ✅ 邮件回复 | ✅ 日常回答 | - | - | 全部通过 | +| 011401 | ✅ 邮件回复 | ✅ 日常回答 | - | - | 全部通过 | +| 011501 | ✅ 邮件回复 | ✅ 日常回答 | - | - | 全部通过 | +| 011701 | ✅ 邮件回复 | ✅ 日常回答 | - | - | 全部通过 | +| 011601 | - | - | ✅ 看图写作 | ✅ 话题讨论 | 全部通过 | + +### 题型结构总结 + +**写作-P1 (writing_email)**: +- 6句段落排序(含3句知识点作为范文核心句) +- 能力标签:短消息写作|邮件/便条, 衔接与连贯|连词使用 +- 场景:火灾求助/宠物照顾/惊喜派对/故事分享 + +**口语-P1 (speaking_qa)**: +- 4个引导问题,每问引导1个知识点句型 +- 能力标签:基础信息表达|个人信息问答, 表达喜好与理由, 互动应答|问答交流 +- 问题设计引导关键词:外貌描述/主动帮忙/耐心等待/购物清单 + +**写作-P2 (writing_picWrite)**: +- 6句故事排序(含3句知识点作为故事核心句) +- 能力标签:叙述故事|看图写故事, 衔接与连贯|连词使用 +- 场景:运动会雨天故事 + +**口语-P2 (speaking_topic)**: +- 4个话题讨论问题(含图片场景) +- 能力标签:故事讲述|看图说话, 互动应答|问答交流 +- 场景:社区公园安全 + +### 关键脚本 +- `scripts/gen_unit_challenge_spk_wrt.py` → 数据生成(JSON文件) +- `scripts/backfill_unit_challenge.py` → 回填到飞书多维表格 + +### 注意事项 +- 口语-P2 011601的初始回填因表格已有旧记录(题目集合ID未设置),需额外更新 +- 写作-P2 011601的能力标签中`|`字符曾出现存储问题,已修正 +- 题目1字段内容已全部填入(含题目描述+题目/段落列表+答案+解析+能力项) diff --git a/scripts/audit_listening_full.py b/scripts/audit_listening_full.py new file mode 100644 index 0000000..36d3b80 --- /dev/null +++ b/scripts/audit_listening_full.py @@ -0,0 +1,278 @@ +""" +Unit Challenge 听力审校脚本:P1/P2/P4/P7(P5/P6无数据,P3跳过) +""" +import json, subprocess, sys, os + +APP_TOKEN = "CMHSbUUjka3TrUsaxxEc297ongf" +SKILL_SCRIPT = "/root/.openclaw/workspace-xiaoyan/skills/lark_bitable_operate_as_bot/scripts/operate_bitable.sh" +CRED_FILE = "/root/.openclaw/credentials/xiaoyan/config.json" + +# Standard ability tags (from ability_tags.md) +ALL_TAGS = { + # L1 + "显性信息定位|关键词识别", "基础语境理解|场景/行为理解", "图文判断|句图一致性", + "扫读定位|信息匹配", "主旨理解|段落/文本大意", "释义选词|定义匹配", + "对话理解|问答匹配", "标识与通知理解|Signs & Notices", + # L2 + "细节理解|事实信息提取", "词义理解|语境义判断", "同义替换|词/短语级", + "语法结构识别|完形填空", "推理判断|原因/结果", +} + +TAG_MAP = { + "信息提取": "细节理解|事实信息提取", + "信息定位": "扫读定位|信息匹配", + "信息定位与提取": "扫读定位|信息匹配", + "细节理解": "细节理解|事实信息提取", + "因果推断": "推理判断|原因/结果", + "推理判断": "推理判断|原因/结果", + "主旨归纳": "主旨理解|段落/文本大意", + "主旨概括": "主旨理解|段落/文本大意", + "信息匹配": "扫读定位|信息匹配", + "同义替换": "同义替换|词/短语级", + "词义理解": "词义理解|语境义判断", + "语法结构": "语法结构识别|完形填空", +} + +# Per-type required question fields +TYPE_FIELDS = { + "listening_choicePic": ["question", "questionAudio", "optionsImage", "answer", "ability", "explanation"], + "listening_choiceShort": ["question", "questionAudio", "options", "answer", "ability", "explanation"], + "listening_tableCloze": ["options", "answer", "ability", "explanation"], # no question field + "listening_drag": ["imageInfo", "optionList", "answer", "ability", "explanation"], + "listening_matchInfo": ["question", "options", "answer", "ability", "explanation"], + "listening_choosePic": ["question", "questionAudio", "optionsImage", "answer", "ability", "explanation"], +} + +def get_bot_token(): + with open(CRED_FILE) as f: + cfg = json.load(f) + app_id = cfg['apps'][0]['appId'] + app_secret = cfg['apps'][0]['appSecret'] + import urllib.request + req = urllib.request.Request( + "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal", + data=json.dumps({"app_id": app_id, "app_secret": app_secret}).encode(), + headers={"Content-Type": "application/json"} + ) + resp = urllib.request.urlopen(req) + return json.loads(resp.read())['tenant_access_token'] + +def backfill_by_curl(record_id, table_id, audit_text): + """Use direct curl to backfill audit result""" + token = get_bot_token() + import urllib.request + + body = json.dumps({"fields": {"审校结果": audit_text}}) + url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{table_id}/records/{record_id}" + + req = urllib.request.Request(url, data=body.encode(), method="PUT", headers={ + "Authorization": f"Bearer {token}", + "Content-Type": "application/json" + }) + resp = urllib.request.urlopen(req) + result = json.loads(resp.read()) + return result.get('code') == 0, result + +def audit_question(q, req_fields, ftype): + """Audit a single question""" + errors = [] + warnings = [] + tag_issues = [] + + for req in req_fields: + if req not in q: + errors.append(f"缺少必填字段 '{req}'") + elif q[req] is None or q[req] == '': + errors.append(f"必填字段 '{req}' 为空") + + # answer bounds check + answer = q.get('answer', []) + options = q.get('options', []) or q.get('optionsImage', []) or q.get('optionList', []) + if isinstance(answer, list) and options: + for ai in answer: + if isinstance(ai, int) and (ai < 0 or ai >= len(options)): + errors.append(f"answer索引{ai}超出options范围(0-{len(options)-1})") + + # ability tags + abilities = q.get('ability', []) + if not abilities: + warnings.append("能力标签为空,需补充") + + # Handle "¥¥" delimiter in P7 data + cleaned_abilities = [] + for a in abilities: + if isinstance(a, str) and '¥¥' in a: + cleaned_abilities.extend(a.split('¥¥')) + else: + cleaned_abilities.append(a) + + for a in cleaned_abilities: + a_stripped = a.strip() + if a_stripped in ALL_TAGS: + continue + # Check if it's an old-style tag + if '听觉' in a_stripped or '听力' in a_stripped: + tag_issues.append(f"'{a_stripped}' 含'听觉/听力'非标准标签,需替换") + elif a_stripped in TAG_MAP: + tag_issues.append(f"'{a_stripped}' → '{TAG_MAP[a_stripped]}'") + elif a_stripped: + tag_issues.append(f"'{a_stripped}' 不在标准标签库中") + + # explanation + expl = q.get('explanation', '') + if expl is None or expl == '': + warnings.append("解析为空,需补充") + elif isinstance(expl, str) and len(expl) < 10: + warnings.append(f"解析过短({len(expl)}字)") + + return errors, warnings, tag_issues + +def audit_record(fields, record_id, table_name): + jd_str = fields.get('jsonData', '{}') + sid = fields.get('题目集合 ID', '') or '' + + # Parse JSON + try: + jd = json.loads(jd_str) + except json.JSONDecodeError as e: + return [f"jsonData JSON解析失败: {str(e)}"], [], [], {}, "unknown", 0, 0, sid + + first = jd.get('first', {}) + second = jd.get('second', {}) + qs1 = first.get('questionSet', []) + qs2 = second.get('questionSet', []) + ftype = first.get('type', '') or second.get('type', '') + + req_fields = TYPE_FIELDS.get(ftype, ["question", "options", "answer", "ability", "explanation"]) + + errors = [] + warnings = [] + notes = [] + + if not qs1 and not qs2: + errors.append("first和second题组均为空(无题目数据)") + elif qs1 and not qs2: + notes.append("只有一道题组(first),缺少second题组") + elif qs2 and not qs1: + notes.append("只有一道题组(second),缺少first题组") + + all_tag_issues = [] + for set_name, qset in [('first', qs1), ('second', qs2)]: + for qi, q in enumerate(qset): + prefix = f"{set_name}[{qi}]" + q_errors, q_warnings, q_tag_issues = audit_question(q, req_fields, ftype) + for e in q_errors: + errors.append(f"{prefix}: {e}") + for w in q_warnings: + warnings.append(f"{prefix}: {w}") + all_tag_issues.extend(q_tag_issues) + + # Deduplicate and count tag issues + tag_summary = {} + for ti in all_tag_issues: + tag_summary[ti] = tag_summary.get(ti, 0) + 1 + + return errors, warnings, notes, tag_summary, ftype, len(qs1), len(qs2), sid + +def format_result(errors, warnings, notes, tag_summary, ftype, n1, n2, sid): + lines = [] + + if errors: + lines.append(f"❌ 审校发现问题({len(errors)}项错误)") + elif warnings or notes: + lines.append(f"⚠️ 审校通过({len(warnings)}项建议 + {len(notes)}项备注)") + else: + lines.append("✅ 审校通过(无问题)") + + lines.append(f"题型:{ftype} | 题组:first={n1}题 second={n2}题") + + for n in notes: + lines.append(f"📝 备注:{n}") + + if errors: + lines.append(f"\n🔴 必须修改:") + for e in errors: + lines.append(f" - {e}") + + if tag_summary: + lines.append(f"\n🟡 能力标签修正建议:") + for tag, count in sorted(tag_summary.items()): + lines.append(f" - {tag}({count}处)") + + if warnings: + lines.append(f"\n🟡 其他建议:") + for w in warnings: + lines.append(f" - {w}") + + return '\n'.join(lines) + +# ===== Main ===== +targets = { + "听力-P1": {"table_id": "tbliZAhcc9C43B23", "file": "/tmp/listening_P1.json"}, + "听力-P2": {"table_id": "tblzTLNH7f13uWQN", "file": "/tmp/listening_P2.json"}, + "听力-P4": {"table_id": "tblVmeDtBDKsAEfz", "file": "/tmp/listening_P4.json"}, + "听力-P7": {"table_id": "tbly9SvPEa44k3yX", "file": "/tmp/listening_P7.json"}, +} + +all_results = [] +skip_count = 0 + +for name, config in targets.items(): + try: + with open(config['file']) as f: + data = json.load(f) + except FileNotFoundError: + print(f"⚠️ {name}: 数据文件未找到") + continue + + if data.get('code') != 0: + print(f"⚠️ {name}: API error") + continue + + for item in data['data']['items']: + fields = item.get('fields', {}) + sid = fields.get('题目集合 ID', '') or '' + + # Skip 010199 + if '010199' in str(sid): + skip_count += 1 + continue + + # Skip empty jsonData + jd_str = fields.get('jsonData', '{}') + if not jd_str or jd_str == 'None': + continue + + if not sid: + continue + + errors, warnings, notes, tag_summary, ftype, n1, n2, sid2 = audit_record(fields, item['record_id'], name) + result_text = format_result(errors, warnings, notes, tag_summary, ftype, n1, n2, sid) + + print(f"\n{'='*60}") + print(f"{name} | ID={sid}") + print(f"{'='*60}") + print(result_text) + + all_results.append({ + "table_name": name, + "table_id": config['table_id'], + "record_id": item['record_id'], + "sid": sid, + "audit_text": result_text, + "has_errors": len(errors) > 0, + }) + +print(f"\n\n===== 审校汇总 =====") +print(f"总记录数: {len(all_results)}") +print(f"跳过(010199): {skip_count}") + +# Save results +with open("/tmp/listening_audit_results.json", "w") as f: + json.dump(all_results, f, ensure_ascii=False, indent=2) +print(f"结果已保存到 /tmp/listening_audit_results.json") + +# Also print compact summary +for r in all_results: + status = "❌" if r['has_errors'] else "⚠️" if "备注" in r['audit_text'] else "✅" + print(f"{status} {r['table_name']:10s} | ID={r['sid']:10s} | {r['audit_text'].split(chr(10))[0]}") diff --git a/scripts/audit_unit_challenge_reading.py b/scripts/audit_unit_challenge_reading.py new file mode 100644 index 0000000..5757f60 --- /dev/null +++ b/scripts/audit_unit_challenge_reading.py @@ -0,0 +1,135 @@ +""" +单元挑战审校脚本:阅读-P1/P3/P4/P5 +跳过 ID=010199 的记录 +""" +import json, subprocess, sys + +APP_TOKEN = "CMHSbUUjka3TrUsaxxEc297ongf" +SKILL_SCRIPT = "/root/.openclaw/workspace-xiaoyan/skills/lark_bitable_operate_as_bot/scripts/operate_bitable.sh" + +def exec_bash(cmd): + result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=30) + return json.loads(result.stdout) if result.stdout else {} + +# Target records +targets = { + "阅读-P1": {"table_id": "tblCgfYDnnqwLfgH", "filter_ids": ["032501"]}, + "阅读-P3": {"table_id": "tbl4q0ZUV3HB54t1", "filter_ids": None}, + "阅读-P4": {"table_id": "tblzKVm1FEukPgnN", "filter_ids": None}, + "阅读-P5": {"table_id": "tblLmUxzzUDe0QAJ", "filter_ids": None}, +} + +all_audit_results = {} + +for name, config in targets.items(): + print(f"\n{'='*60}") + print(f" {name} (table: {config['table_id']})") + print(f"{'='*60}") + + cmd = f"bash {SKILL_SCRIPT} list_records {APP_TOKEN} {config['table_id']} 500" + data = exec_bash(cmd) + + if data.get('code') != 0: + print(f" ERROR: {data}") + continue + + items = data['data']['items'] + audit_records = [] + + for item in items: + fields = item.get('fields', {}) + sid = fields.get('题目集合 ID', '') or '' + + # Skip 010199 + if '010199' in str(sid): + print(f" SKIP: {sid} (test data)") + continue + + # For P1, only filter 032501 + if config['filter_ids'] and sid not in config['filter_ids']: + continue + + if not sid: + # Check if record has data but no ID + jd = fields.get('jsonData', '') + if jd and jd != '{}': + print(f" WARN: record {item['record_id']} has jsonData but no 题目集合 ID") + continue + + audit_records.append(item) + jd_str = fields.get('jsonData', '{}') + try: + jd = json.loads(jd_str) + except: + jd = {"_parse_error": str(jd_str)[:200]} + + # Count question sets + first = jd.get('first', {}) + second = jd.get('second', {}) + qs1 = first.get('questionSet', []) + qs2 = second.get('questionSet', []) + + has_single_set = (len(qs1) > 0 and len(qs2) == 0) or (not first) + + print(f"\n Record: {item['record_id']} | ID={sid} | dataStatus={fields.get('dataStatus','?')}") + print(f" Question sets: first={len(qs1)}题, second={len(qs2)}题") + if has_single_set and not (len(qs1) == 0 and len(qs2) == 0): + print(f" ⚠️ 只有一道题组(缺少second题组)") + + # Check ability tags + first_type = first.get('type', '') + first_cat = first.get('category', '') + + issues = [] + + for set_name, qset in [('first', qs1), ('second', qs2)]: + for qi, q in enumerate(qset): + abilities = q.get('ability', []) + # Check for hearing-related abilities in reading + if '听觉' in str(abilities) or '听力' in str(abilities): + issues.append(f"{set_name}[{qi}]: 能力标签含'听觉/听力'但题型为{first_cat}/{first_type}") + + # Check non-standard ability tags + non_std = [a for a in abilities if '|' not in a and a not in ('信息提取', '细节理解', '信息定位与提取')] + if non_std: + issues.append(f"{set_name}[{qi}]: 能力标签格式不规范: {non_std}") + + # Check answer bounds + answer = q.get('answer', []) + options = q.get('options', []) + if isinstance(answer, list): + for ai in answer: + if isinstance(ai, int) and (ai < 0 or ai >= len(options)): + issues.append(f"{set_name}[{qi}]: answer索引{ai}超出options范围(0-{len(options)-1})") + elif isinstance(answer, int): + if answer < 0 or answer >= len(options): + issues.append(f"{set_name}[{qi}]: answer索引{answer}超出options范围(0-{len(options)-1})") + + if issues: + print(f" 🔴 发现问题:") + for issue in issues: + print(f" - {issue}") + else: + print(f" ✅ 基础检查通过") + + all_audit_results[f"{name}|{sid}|{item['record_id']}"] = { + "table_name": name, + "table_id": config['table_id'], + "record_id": item['record_id'], + "sid": sid, + "dataStatus": fields.get('dataStatus', '?'), + "has_single_set": has_single_set, + "issues": issues, + "first_count": len(qs1), + "second_count": len(qs2), + "first_type": f"{first_cat}/{first_type}", + "existing_audit": (fields.get('审校结果', '') or '')[:80], + } + +print(f"\n{'='*60}") +print(f" SUMMARY: {len(all_audit_results)} records to audit") +print(f"{'='*60}") + +# Output JSON for next step +print("\n---JSON_OUTPUT---") +print(json.dumps(all_audit_results, ensure_ascii=False, indent=2)) diff --git a/scripts/audit_unit_challenge_v2.py b/scripts/audit_unit_challenge_v2.py new file mode 100644 index 0000000..3b61546 --- /dev/null +++ b/scripts/audit_unit_challenge_v2.py @@ -0,0 +1,246 @@ +""" +完整审校脚本:单元挑战 阅读-P1/P3/P4/P5 +""" +import json, subprocess, copy + +APP_TOKEN = "CMHSbUUjka3TrUsaxxEc297ongf" +SKILL_SCRIPT = "/root/.openclaw/workspace-xiaoyan/skills/lark_bitable_operate_as_bot/scripts/operate_bitable.sh" + +# ===== Standard Reading Ability Tags ===== +STANDARD_TAGS_L1 = { + "显性信息定位|关键词识别", + "基础语境理解|场景/行为理解", + "图文判断|句图一致性", + "扫读定位|信息匹配", + "主旨理解|段落/文本大意", + "释义选词|定义匹配", + "对话理解|问答匹配", + "标识与通知理解|Signs & Notices", +} +STANDARD_TAGS_L2 = { + "细节理解|事实信息提取", + "词义理解|语境义判断", + "同义替换|词/短语级", + "语法结构识别|完形填空", + "推理判断|原因/结果", +} +ALL_STANDARD = STANDARD_TAGS_L1 | STANDARD_TAGS_L2 + +# Non-standard → standard mapping +TAG_MAP = { + "信息提取": "细节理解|事实信息提取", + "细节理解": "细节理解|事实信息提取", + "信息定位": "扫读定位|信息匹配", # depends on context, default for reading + "信息定位与提取": "扫读定位|信息匹配", + "因果推断": "推理判断|原因/结果", + "主旨归纳": "主旨理解|段落/文本大意", + "主旨概括": "主旨理解|段落/文本大意", + "推理判断": "推理判断|原因/结果", + "信息匹配": "扫读定位|信息匹配", +} + +def exec_bash(cmd): + result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=30) + return json.loads(result.stdout) if result.stdout else {} + +def audit_ability_tags(abilities, qtype, qcat): + """Check and map ability tags""" + issues = [] + mapped = [] + for a in abilities: + if a in ALL_STANDARD: + mapped.append(a) + elif a in TAG_MAP: + mapped.append(TAG_MAP[a]) + issues.append(f"能力标签 '{a}' → '{TAG_MAP[a]}'(非标准标签,已建议映射)") + else: + mapped.append(a) + issues.append(f"能力标签 '{a}' 不在标准标签库中,需人工确认") + return mapped, issues + +def audit_record(fields, record_id, table_name): + """Deep audit of a single record""" + jd_str = fields.get('jsonData', '{}') + sid = fields.get('题目集合 ID', '') + + try: + jd = json.loads(jd_str) + except: + return {"errors": [f"jsonData 解析失败"], "warnings": [], "notes": []} + + errors = [] + warnings = [] + notes = [] + + first = jd.get('first', {}) + second = jd.get('second', {}) + qs1 = first.get('questionSet', []) + qs2 = second.get('questionSet', []) + + ftype = first.get('type', '') + fcat = first.get('category', '') + + # 1. Check question set count + if not qs1 and not qs2: + errors.append("first和second题组均为空") + elif qs1 and not qs2: + notes.append("只有一道题组(first),缺少second题组") + elif qs2 and not qs1: + notes.append("只有一道题组(second),缺少first题组") + + # 2. Check each question set + all_ability_issues = [] + for set_name, qset in [('first', qs1), ('second', qs2)]: + if not qset: + continue + + for qi, q in enumerate(qset): + prefix = f"{set_name}[{qi}]" + + # Check required fields + for req in ['question', 'options', 'answer', 'ability', 'explanation']: + if req not in q or not q[req]: + errors.append(f"{prefix}: 缺少必填字段 '{req}'") + + # Check answer bounds + answer = q.get('answer', []) + options = q.get('options', []) + if isinstance(answer, list): + for ai in answer: + if isinstance(ai, int) and (ai < 0 or ai >= len(options)): + errors.append(f"{prefix}: answer索引{ai}超出options范围(0-{len(options)-1})") + elif isinstance(answer, int): + if answer < 0 or answer >= len(options): + errors.append(f"{prefix}: answer索引{answer}超出options范围(0-{len(options)-1})") + + # Check ability tags + abilities = q.get('ability', []) + # Hearing tags in reading + if any('听觉' in str(a) or '听力' in str(a) for a in abilities): + errors.append(f"{prefix}: 能力标签含'听觉/听力'但题型为{ftype}") + + # Standard check + mapped, tag_issues = audit_ability_tags(abilities, ftype, fcat) + for ti in tag_issues: + warnings.append(f"{prefix}: {ti}") + all_ability_issues.extend(tag_issues) + + # Check explanation quality + expl = q.get('explanation', '') + if len(expl) < 10: + warnings.append(f"{prefix}: 解析过短({len(expl)}字)") + + # Check questionImage naming + qimg = q.get('questionImage', '') + if qimg and not qimg.startswith(sid): + warnings.append(f"{prefix}: questionImage '{qimg}' 与题目集合ID '{sid}' 不匹配") + + # 3. Check type consistency + second_has_type = second and second.get('type') + if second_has_type and second.get('type') != ftype: + errors.append(f"first type={ftype} 与 second type={second.get('type')} 不一致") + + # 4. Check dataStatus + ds = fields.get('dataStatus', '') + if ds and ds != '1' and ds != '0': + warnings.append(f"dataStatus='{ds}' 非标准值") + + return { + "sid": sid, + "table_name": table_name, + "record_id": record_id, + "type": f"{fcat}/{ftype}", + "dataStatus": ds, + "first_count": len(qs1), + "second_count": len(qs2), + "errors": errors, + "warnings": warnings, + "notes": notes, + "existing_audit": (fields.get('审校结果', '') or ''), + } + +def format_audit_result(audit): + """Format audit result as a structured string for backfill""" + lines = [] + + # Title line + has_errors = len(audit['errors']) > 0 + has_warnings = len(audit['warnings']) > 0 + has_notes = len(audit['notes']) > 0 + + if has_errors: + lines.append(f"❌ 审校发现问题({len(audit['errors'])}项错误)") + elif has_warnings: + lines.append(f"⚠️ 审校通过({len(audit['warnings'])}项建议)") + else: + lines.append("✅ 审校通过(无问题)") + + lines.append(f"题型:{audit['type']} | 题组:first={audit['first_count']}题 second={audit['second_count']}题") + + if has_notes: + for n in audit['notes']: + lines.append(f"📝 备注:{n}") + + if has_errors: + lines.append(f"\n🔴 必须修改:") + for e in audit['errors']: + lines.append(f" - {e}") + + if has_warnings: + lines.append(f"\n🟡 建议修改:") + for w in audit['warnings']: + lines.append(f" - {w}") + + return '\n'.join(lines) + +# ===== Main ===== +targets = { + "阅读-P1": {"table_id": "tblCgfYDnnqwLfgH", "filter_ids": ["032501"]}, + "阅读-P3": {"table_id": "tbl4q0ZUV3HB54t1", "filter_ids": None}, + "阅读-P4": {"table_id": "tblzKVm1FEukPgnN", "filter_ids": None}, + "阅读-P5": {"table_id": "tblLmUxzzUDe0QAJ", "filter_ids": None}, +} + +all_results = [] + +for name, config in targets.items(): + cmd = f"bash {SKILL_SCRIPT} list_records {APP_TOKEN} {config['table_id']} 500" + data = exec_bash(cmd) + if data.get('code') != 0: + continue + + for item in data['data']['items']: + fields = item.get('fields', {}) + sid = fields.get('题目集合 ID', '') or '' + + if '010199' in str(sid): + continue + if config['filter_ids'] and sid not in config['filter_ids']: + continue + if not sid: + jd = fields.get('jsonData', '') + if jd and jd != '{}': + # Record with data but no ID - skip, not valid + pass + continue + + audit = audit_record(fields, item['record_id'], name) + result_text = format_audit_result(audit) + + print(f"\n{'='*60}") + print(f"{name} | ID={sid}") + print(f"{'='*60}") + print(result_text) + + all_results.append({ + "table_name": name, + "table_id": config['table_id'], + "record_id": item['record_id'], + "sid": sid, + "audit_text": result_text, + "has_errors": len(audit['errors']) > 0, + }) + +# Output backfill JSON +print(f"\n\n===== BACKFILL DATA ({len(all_results)} records) =====") +print(json.dumps(all_results, ensure_ascii=False, indent=2)) diff --git a/scripts/audit_unit_challenge_v3.py b/scripts/audit_unit_challenge_v3.py new file mode 100644 index 0000000..319257c --- /dev/null +++ b/scripts/audit_unit_challenge_v3.py @@ -0,0 +1,206 @@ +""" +修正版审校脚本:按题型区分字段要求 +""" +import json, subprocess, sys + +APP_TOKEN = "CMHSbUUjka3TrUsaxxEc297ongf" +SKILL_SCRIPT = "/root/.openclaw/workspace-xiaoyan/skills/lark_bitable_operate_as_bot/scripts/operate_bitable.sh" + +# Standard ability tags +TAG_L1 = {"显性信息定位|关键词识别","基础语境理解|场景/行为理解","图文判断|句图一致性", + "扫读定位|信息匹配","主旨理解|段落/文本大意","释义选词|定义匹配", + "对话理解|问答匹配","标识与通知理解|Signs & Notices"} +TAG_L2 = {"细节理解|事实信息提取","词义理解|语境义判断","同义替换|词/短语级", + "语法结构识别|完形填空","推理判断|原因/结果"} +ALL_TAGS = TAG_L1 | TAG_L2 + +TAG_MAP = { + "信息提取":"细节理解|事实信息提取", "细节理解":"细节理解|事实信息提取", + "信息定位":"扫读定位|信息匹配", "信息定位与提取":"扫读定位|信息匹配", + "因果推断":"推理判断|原因/结果", "主旨归纳":"主旨理解|段落/文本大意", + "主旨概括":"主旨理解|段落/文本大意", "推理判断":"推理判断|原因/结果", + "信息匹配":"扫读定位|信息匹配", +} + +# Per-type required question fields +# reading_cloze and reading_openCloze don't need individual 'question' field +TYPE_FIELDS = { + "reading_matchInfo": ["question", "options", "answer", "ability", "explanation"], + "reading_choiceLong": ["question", "options", "answer", "ability", "explanation"], + "reading_cloze": ["options", "answer", "ability", "explanation"], # no question + "reading_openCloze": ["options", "answer", "ability", "explanation"], # no question +} + +def exec_bash(cmd): + result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=30) + return json.loads(result.stdout) if result.stdout else {} + +def audit_question(q, req_fields, type_name): + """Audit a single question, returns (errors, warnings, tag_issues)""" + errors = [] + warnings = [] + tag_issues = [] + + for req in req_fields: + if req not in q: + errors.append(f"缺少必填字段 '{req}'") + elif q[req] is None: + errors.append(f"必填字段 '{req}' 为 null") + + # answer bounds + answer = q.get('answer', []) + options = q.get('options', []) + if isinstance(answer, list) and options: + for ai in answer: + if isinstance(ai, int) and (ai < 0 or ai >= len(options)): + errors.append(f"answer索引{ai}超出options范围(0-{len(options)-1})") + + # ability tags + abilities = q.get('ability', []) + if not abilities: + warnings.append("能力标签为空,需补充") + + for a in abilities: + if a in ALL_TAGS: + continue + if a in TAG_MAP: + tag_issues.append(f"'{a}' → '{TAG_MAP[a]}'") + else: + tag_issues.append(f"'{a}' 不在标准标签库中") + + if any('听觉' in str(a) or '听力' in str(a) for a in abilities): + errors.append(f"能力标签含'听觉/听力'但题型为{type_name}") + + # explanation + expl = q.get('explanation', '') + if expl is None or expl == '': + warnings.append("解析为空,需补充") + elif isinstance(expl, str) and len(expl) < 10: + warnings.append(f"解析过短({len(expl)}字)") + + return errors, warnings, tag_issues + +def audit_record(fields, record_id, table_name): + jd_str = fields.get('jsonData', '{}') + sid = fields.get('题目集合 ID', '') + + try: + jd = json.loads(jd_str) + except: + return [f"jsonData 解析失败"], [], [f"只有一道题组(缺少second题组)"] + + first = jd.get('first', {}) + second = jd.get('second', {}) + qs1 = first.get('questionSet', []) + qs2 = second.get('questionSet', []) + ftype = first.get('type', 'unknown') + + req_fields = TYPE_FIELDS.get(ftype, ["question", "options", "answer", "ability", "explanation"]) + + errors = [] + warnings = [] + notes = [] + + if not qs1 and not qs2: + errors.append("first和second题组均为空") + elif qs1 and not qs2: + notes.append("只有一道题组(first),缺少second题组") + elif qs2 and not qs1: + notes.append("只有一道题组(second),缺少first题组") + + all_tag_issues = [] + for set_name, qset in [('first', qs1), ('second', qs2)]: + for qi, q in enumerate(qset): + prefix = f"{set_name}[{qi}]" + q_errors, q_warnings, q_tag_issues = audit_question(q, req_fields, ftype) + for e in q_errors: + errors.append(f"{prefix}: {e}") + for w in q_warnings: + warnings.append(f"{prefix}: {w}") + all_tag_issues.extend(q_tag_issues) + + # Consolidate tag issues + tag_summary = {} + for ti in all_tag_issues: + tag_summary[ti] = tag_summary.get(ti, 0) + 1 + + return errors, warnings, notes, tag_summary, ftype, len(qs1), len(qs2), sid + +def format_result(errors, warnings, notes, tag_summary, ftype, n1, n2, sid): + lines = [] + + if errors: + lines.append(f"❌ 审校发现问题({len(errors)}项错误)") + elif warnings or notes: + lines.append(f"⚠️ 审校通过({len(warnings)}项建议 + {len(notes)}项备注)") + else: + lines.append("✅ 审校通过(无问题)") + + lines.append(f"题型:{ftype} | 题组:first={n1}题 second={n2}题") + + for n in notes: + lines.append(f"📝 备注:{n}") + + if errors: + lines.append(f"\n🔴 必须修改:") + for e in errors: + lines.append(f" - {e}") + + if tag_summary: + lines.append(f"\n🟡 能力标签映射建议:") + for tag, count in sorted(tag_summary.items()): + lines.append(f" - {tag}({count}处)") + + if warnings: + lines.append(f"\n🟡 其他建议:") + for w in warnings: + lines.append(f" - {w}") + + return '\n'.join(lines) + +# ===== Main ===== +targets = { + "阅读-P1": {"table_id": "tblCgfYDnnqwLfgH", "filter_ids": ["032501"]}, + "阅读-P3": {"table_id": "tbl4q0ZUV3HB54t1", "filter_ids": None}, + "阅读-P4": {"table_id": "tblzKVm1FEukPgnN", "filter_ids": None}, + "阅读-P5": {"table_id": "tblLmUxzzUDe0QAJ", "filter_ids": None}, +} + +all_results = [] + +for name, config in targets.items(): + cmd = f"bash {SKILL_SCRIPT} list_records {APP_TOKEN} {config['table_id']} 500" + data = exec_bash(cmd) + if data.get('code') != 0: + continue + + for item in data['data']['items']: + fields = item.get('fields', {}) + sid = fields.get('题目集合 ID', '') or '' + if '010199' in str(sid): + continue + if config['filter_ids'] and sid not in config['filter_ids']: + continue + if not sid: + continue + + errors, warnings, notes, tag_summary, ftype, n1, n2, sid2 = audit_record(fields, item['record_id'], name) + result_text = format_result(errors, warnings, notes, tag_summary, ftype, n1, n2, sid) + + print(f"\n{'='*60}") + print(f"{name} | ID={sid}") + print(f"{'='*60}") + print(result_text) + + all_results.append({ + "table_name": name, + "table_id": config['table_id'], + "record_id": item['record_id'], + "sid": sid, + "audit_text": result_text, + "has_errors": len(errors) > 0, + }) + +# Output backfill data +print(f"\n\n===== BACKFILL ({len(all_results)} records) =====") +print(json.dumps(all_results, ensure_ascii=False, indent=2)) diff --git a/scripts/backfill_audit.py b/scripts/backfill_audit.py new file mode 100644 index 0000000..7827618 --- /dev/null +++ b/scripts/backfill_audit.py @@ -0,0 +1,93 @@ +""" +回填审校结果到 bitable +""" +import json, subprocess + +APP_TOKEN = "CMHSbUUjka3TrUsaxxEc297ongf" +SKILL_SCRIPT = "/root/.openclaw/workspace-xiaoyan/skills/lark_bitable_operate_as_bot/scripts/operate_bitable.sh" + +records = [ + { + "table_id": "tblCgfYDnnqwLfgH", + "record_id": "recvjt0HzBBhYu", + "sid": "032501", + "text": """⚠️ 审校通过(0项错误) +题型:reading_matchInfo | 题组:first=6题 second=0题 +📝 备注:只有一道题组(first),缺少second题组 + +🟡 能力标签映射建议(12处): + - '信息定位与提取' → '扫读定位|信息匹配'(6处) + - '细节理解' → '细节理解|事实信息提取'(6处) + +✅ 题目内容、选项、答案、解析均无问题""" + }, + { + "table_id": "tbl4q0ZUV3HB54t1", + "record_id": "recvjug1hWz2oG", + "sid": "032601", + "text": """⚠️ 审校通过(0项错误) +题型:reading_choiceLong | 题组:first=5题 second=0题 +📝 备注:只有一道题组(first),缺少second题组 + +🟡 能力标签映射建议(10处): + - '信息定位' → '扫读定位|信息匹配'(4处) + - '细节理解' → '细节理解|事实信息提取'(3处) + - '主旨归纳' → '主旨理解|段落/文本大意'(1处) + - '因果推断' → '推理判断|原因/结果'(1处) + - '推理判断' → '推理判断|原因/结果'(1处) + +✅ 题目内容、选项、答案、解析均无问题""" + }, + { + "table_id": "tblzKVm1FEukPgnN", + "record_id": "recvjueHm15HPu", + "sid": "032701", + "text": """⚠️ 审校通过(0项错误) +题型:reading_cloze | 题组:first=6题 second=0题 +📝 备注:只有一道题组(first),缺少second题组 + +🟡 需补充内容(12项): + - 全部6题的 ability 能力标签为空,需补充(建议:语法结构识别|完形填空) + - 全部6题的 explanation 解析为空,需补充 + - 选项与答案已填写,结构完整""" + }, + { + "table_id": "tblzKVm1FEukPgnN", + "record_id": "recvjueN5QT1c5", + "sid": "032801", + "text": """⚠️ 审校通过(0项错误) +题型:reading_cloze | 题组:first=6题 second=0题 +📝 备注:只有一道题组(first),缺少second题组 + +🟡 需补充内容(12项): + - 全部6题的 ability 能力标签为空,需补充(建议:语法结构识别|完形填空) + - 全部6题的 explanation 解析为空,需补充 + - 选项与答案已填写,结构完整""" + }, + { + "table_id": "tblLmUxzzUDe0QAJ", + "record_id": "recvjueULrufNg", + "sid": "032901", + "text": """⚠️ 审校通过(0项错误) +题型:reading_openCloze | 题组:first=8题 second=6题 + +🟡 需补充内容(28项): + - 全部14题的 ability 能力标签为空,需补充(建议:语法结构识别|完形填空) + - 全部14题的 explanation 解析为空,需补充 + - 选项与答案已填写,题组完整(first+second均有)""" + }, +] + +for r in records: + # Escape the text for shell/JSON + fields = json.dumps({"审校结果": r['text']}, ensure_ascii=False) + cmd = f"bash {SKILL_SCRIPT} update_record {APP_TOKEN} {r['table_id']} {r['record_id']} '{fields}'" + result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=30) + try: + resp = json.loads(result.stdout) + if resp.get('code') == 0: + print(f"✅ {r['sid']} ({r['table_id'][-8:]}): 回填成功") + else: + print(f"❌ {r['sid']}: {resp}") + except: + print(f"❌ {r['sid']}: parse error — {result.stdout[:200]}") diff --git a/scripts/backfill_fixed.py b/scripts/backfill_fixed.py new file mode 100644 index 0000000..a002ad8 --- /dev/null +++ b/scripts/backfill_fixed.py @@ -0,0 +1,110 @@ +""" +Backfill fixed JSONs to Feishu bitable +""" +import json, urllib.request + +APP_TOKEN = "CMHSbUUjka3TrUsaxxEc297ongf" +CRED_FILE = "/root/.openclaw/credentials/xiaoyan/config.json" + +def get_token(): + with open(CRED_FILE) as f: + cfg = json.load(f) + req = urllib.request.Request( + "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal", + data=json.dumps({"app_id": cfg['apps'][0]['appId'], "app_secret": cfg['apps'][0]['appSecret']}).encode(), + headers={"Content-Type": "application/json"}) + return json.loads(urllib.request.urlopen(req).read())['tenant_access_token'] + +def api_call(url, method='GET', body=None): + token = get_token() + headers = {"Authorization": f"Bearer {token}"} + data = json.dumps(body).encode() if body else None + if data: + headers["Content-Type"] = "application/json" + req = urllib.request.Request(url, data=data, method=method, headers=headers) + return json.loads(urllib.request.urlopen(req).read()) + +def update_record(table_id, record_id, fields): + url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{table_id}/records/{record_id}" + return api_call(url, 'PUT', {"fields": fields}) + +# Mapping of fixed JSONs to tables +backfill = { + "021901": "tblzTLNH7f13uWQN", + "022301": "tblzTLNH7f13uWQN", + "021301": "tblVmeDtBDKsAEfz", + "021601": "tblVmeDtBDKsAEfz", + "021801": "tblVmeDtBDKsAEfz", +} + +for sid, table_id in backfill.items(): + with open(f'/tmp/fixed_{sid}.json') as f: + new_jd = f.read() + + try: + json.loads(new_jd) + except json.JSONDecodeError as e: + print(f"❌ {sid}: fixed JSON still invalid: {e}") + continue + + # Find record_id + url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{table_id}/records?page_size=50" + resp = api_call(url) + for item in resp['data']['items']: + if item['fields'].get('题目集合 ID', '') == sid: + rid = item['record_id'] + result = update_record(table_id, rid, {"jsonData": new_jd}) + if result.get('code') == 0: + # Verify + v_url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{table_id}/records/{rid}" + v_resp = api_call(v_url) + v_jd = v_resp['data'].get('record', v_resp['data'].get('items',[{}])[0])['fields'].get('jsonData', '') + try: + json.loads(v_jd) + print(f"✅ {sid}: backfilled + verified ({len(new_jd)} chars)") + except: + print(f"⚠️ {sid}: written but verify failed") + else: + print(f"❌ {sid}: API error - {result.get('msg')}") + break + else: + print(f"⚠️ {sid}: record not found in table") + +# Also fix P5 032801 ability + P6 abilities +print("\n=== P5 032801 ===") +url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/tblDssVmhGzc3UKd/records?page_size=50" +resp = api_call(url) +for item in resp['data']['items']: + if item['fields'].get('题目集合 ID', '') == '032801': + rid = item['record_id'] + jd = json.loads(item['fields']['jsonData']) + first = jd.get('first', {}) + if first.get('ability') == ['听觉抓取关键信息']: + first['ability'] = ['显性事实理解|关键词识别'] + new_jd = json.dumps(jd, ensure_ascii=False) + result = update_record("tblDssVmhGzc3UKd", rid, {"jsonData": new_jd}) + print(f" {'✅' if result.get('code')==0 else '❌'} ability: 听觉抓取关键信息 → 显性事实理解|关键词识别") + else: + print(f" ⏭️ ability already: {first.get('ability')}") + +print("\n=== P6 ===") +url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/tbloiMcD0sBtGSTq/records?page_size=50" +resp = api_call(url) +for item in resp['data']['items']: + sid = item['fields'].get('题目集合 ID', '') + if '010199' in str(sid): + continue + jd = json.loads(item['fields']['jsonData']) + rid = item['record_id'] + qs = jd.get('questionSet', []) + changed = False + for q in qs: + if not q.get('ability'): + q['ability'] = ['显性事实理解|关键词识别'] + changed = True + if changed: + new_jd = json.dumps(jd, ensure_ascii=False) + result = update_record("tbloiMcD0sBtGSTq", rid, {"jsonData": new_jd}) + print(f" {'✅' if result.get('code')==0 else '❌'} P6 {sid}: ability filled") + +print("\n✅ Backfill complete!") diff --git a/scripts/backfill_listening_audit.py b/scripts/backfill_listening_audit.py new file mode 100644 index 0000000..4e4f134 --- /dev/null +++ b/scripts/backfill_listening_audit.py @@ -0,0 +1,56 @@ +""" +回填听力审校结果到飞书多维表格 +""" +import json, urllib.request, sys + +APP_TOKEN = "CMHSbUUjka3TrUsaxxEc297ongf" +CRED_FILE = "/root/.openclaw/credentials/xiaoyan/config.json" + +def get_token(): + with open(CRED_FILE) as f: + cfg = json.load(f) + app_id = cfg['apps'][0]['appId'] + app_secret = cfg['apps'][0]['appSecret'] + req = urllib.request.Request( + "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal", + data=json.dumps({"app_id": app_id, "app_secret": app_secret}).encode(), + headers={"Content-Type": "application/json"} + ) + resp = json.loads(urllib.request.urlopen(req).read()) + return resp['tenant_access_token'] + +def backfill(record_id, table_id, audit_text): + token = get_token() + body = json.dumps({"fields": {"审校结果": audit_text}}).encode() + url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{table_id}/records/{record_id}" + req = urllib.request.Request(url, data=body, method="PUT", headers={ + "Authorization": f"Bearer {token}", + "Content-Type": "application/json" + }) + resp = json.loads(urllib.request.urlopen(req).read()) + return resp.get('code') == 0, resp + +# Load audit results +with open("/tmp/listening_audit_results.json") as f: + results = json.load(f) + +print(f"Total records to backfill: {len(results)}") +success = 0 +failed = [] + +for r in results: + ok, resp = backfill(r['record_id'], r['table_id'], r['audit_text']) + if ok: + success += 1 + status = "✅" if "通过" in r['audit_text'].split('\n')[0] else "📝" + print(f"{status} {r['table_name']:10s} | ID={r['sid']:10s} | OK") + else: + failed.append(r) + print(f"❌ {r['table_name']:10s} | ID={r['sid']:10s} | FAIL: {resp}") + +print(f"\n===== 回填完成 =====") +print(f"成功: {success}/{len(results)}") +if failed: + print(f"失败: {len(failed)}") + for f in failed: + print(f" {f['table_name']} ID={f['sid']}") diff --git a/scripts/backfill_unit_challenge.py b/scripts/backfill_unit_challenge.py new file mode 100644 index 0000000..dabaaf1 --- /dev/null +++ b/scripts/backfill_unit_challenge.py @@ -0,0 +1,78 @@ +""" +Backfill 10 unit challenge records to Feishu bitable +Reads from /tmp/unit_challenge_records.json +""" +import json, urllib.request + +APP_TOKEN = "CMHSbUUjka3TrUsaxxEc297ongf" +CRED_FILE = "/root/.openclaw/credentials/xiaoyan/config.json" + +TABLES = { + "writing_email": "tblszuk1TeToofBF", + "writing_picWrite": "tblSAwlMumKoyjws", + "speaking_qa": "tblRGv7k4WH58Jgq", + "speaking_topic": "tblGoWYBmVI0IrvQ", +} + +with open(CRED_FILE) as f: + cfg = json.load(f) +req = urllib.request.Request( + "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal", + data=json.dumps({"app_id": cfg['apps'][0]['appId'], "app_secret": cfg['apps'][0]['appSecret']}).encode(), + headers={"Content-Type": "application/json"}) +token = json.loads(urllib.request.urlopen(req).read())['tenant_access_token'] + +def api(url, method='GET', body=None): + h = {"Authorization": f"Bearer {token}"} + d = json.dumps(body).encode() if body else None + if d: h["Content-Type"] = "application/json" + r = urllib.request.Request(url, data=d, method=method, headers=h) + return json.loads(urllib.request.urlopen(r).read()) + +with open('/tmp/unit_challenge_records.json') as f: + records = json.load(f) + +ok = 0 +fail = 0 + +for rec in records: + tid = TABLES[rec['table']] + sid = rec['sid'] + + # Find existing record + url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{tid}/records?page_size=100" + resp = api(url) + + rid = None + for item in resp['data']['items']: + if item['fields'].get('题目集合 ID', '') == sid: + rid = item['record_id'] + break + + new_jd = json.dumps(rec['json'], ensure_ascii=False) + fields = { + "题目集合 ID": sid, + "jsonData": new_jd, + "题目1": rec.get('t1', '') + } + + if rid: + result = api( + f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{tid}/records/{rid}", + 'PUT', {"fields": fields} + ) + st = "UPD" if result.get('code') == 0 else f"ERR:{result.get('msg')}" + else: + result = api( + f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{tid}/records", + 'POST', {"fields": fields} + ) + st = "NEW" if result.get('code') == 0 else f"ERR:{result.get('msg')}" + + if result.get('code') == 0: + ok += 1 + else: + fail += 1 + print(f" {st} {rec['type']} {sid}") + +print(f"\n✅ OK={ok} FAIL={fail}") diff --git a/scripts/dialogue_backfill.py b/scripts/dialogue_backfill.py new file mode 100644 index 0000000..1b247e0 --- /dev/null +++ b/scripts/dialogue_backfill.py @@ -0,0 +1,264 @@ +#!/usr/bin/env python3 +""" +英文台词优化 & 回填脚本 +基于 MEMORY.md 的「对话台词优化通用规则」对列 E(编剧台词)进行优化,写入列 F +知识点:grandmother, grandfather, visit (v.), today (n.) +句型知识点:... visit+sb, Today is... +""" +import json, subprocess, sys + +# Rules: +# 0. 知识点词汇/句型必须保留 (grandmother/grandfather/visit/today / Today is... / ...visit+sb) +# 1. 精简:删除冗余 +# 2. 口语化:自然对话,低龄友好,增语气词 +# 3. 拆分:>8词拆为2-3短句 +# 4. 情绪匹配:匹配角色情绪 +# 5. 场景适配:不重复画面信息 +# 6. 单句≤10词,知识点句≤7词 + +# Knowledge-point words to preserve +KP_WORDS = {"grandmother", "grandfather", "visit", "today", "today's"} + +# Mapping: (row_index_0based_sheet) → optimized F value +# Only rows where D (role) and E (script line) are non-null +# Row index = sheet row - 1 (0-based from API) +# Sheet row 1 = header → skip +# Sheet row 2 = API row index 1 + +optimizations = {} + +def add(row_sheet, f_value): + """row_sheet is 1-indexed sheet row number""" + optimizations[row_sheet - 1] = f_value # convert to 0-indexed + +# === SECTION 1: 开场 - 门口 === +add(3, "Open up!") +add(4, "Your letter is here!") +add(6, "Nobody answers?") +add(7, "Alright... I'll go in myself.") +add(9, "Whoa! What a mess!") +add(11, "What's wrong?") +add(12, "Vicky? Can you hear me?") + +# === SECTION 2: 发现家里很乱 === +add(14, "Today is a No-Mom-and-Dad day!") # 知识点 Today is +add(15, "Our parents aren't home!") # 精简 + 口语化 +add(16, "So we can do whatever we want!") +add(17, "Really?") +add(19, "Yes!") +add(20, "Today is a good day!") # 知识点 Today is +add(21, "Huh?") +add(22, "Are you sure today is a good day?") # 知识点 today + +# === SECTION 3: 撞到用户,道歉 === +add(23, "So on a good day, nobody says sorry?") # 精简 14→8词 +add(24, "Eva, Peter, say sorry.") +add(25, "Okay. We're sorry.") # 口语化 + +# === SECTION 4: 继续对话 === +add(27, "Ah, kids.") +add(28, "Come visit us!") # 知识点 visit,修正"Welcome to visit us"不自然表达 +add(29, "Oh, I'm not here to visit.") # 知识点 visit,口语化 +add(30, "I'm here to give you something.") # 口语化 + +# === SECTION 5: 电话 === +add(33, "Hello? Grandfather and grandmother, how are you?") # 知识点 grandfather/grandmother +add(34, "Good, good! We're great!") # 口语化 +add(35, "How are you all, my lovely grandchildren?") +add(36, "We're great! Today is so much fun!") # 知识点 Today is, 口语化 +add(37, "Great to hear that!") +add(38, "Are you ready for us?") # 精简 +add(39, "I'm so excited to visit you all!") # 知识点 visit, 口语化 +add(40, "Sure! When will you visit us?") # 知识点 visit +add(41, "Today is the day, of course!") # 知识点 Today is +add(42, "What? You're both coming today?") # 口语化修复 +add(43, "Yes! Me and Justin. Your grandmother and grandfather.") # 知识点, 修正 typo gandfather +add(44, "We sent you a letter. Remember?") # 口语化 +add(45, "See you later!") +add(46, "I love you! Bye!") + +# === SECTION 6: 惊慌 === +add(48, "Oh no! What did they say on the phone?") # 修正语法 in→on +add(50, "Your grandfather and grandmother are coming to visit you!") # 知识点, 修正 is→are +add(51, "On your good day.") + +# === SECTION 7: 紧急动员 === +add(54, "Eva! Peter! Alex! We're in trouble!") # 口语化 +add(56, "What happened?") +add(57, "Grandfather and grandmother are coming to visit us!") # 知识点, 修正 is→are +add(58, "So what?") +add(59, "Today! Right now!") # 精简 On today→Today +add(62, "No, not today...") # 修正 on today→today +add(63, "Look at this place!") + +# === SECTION 8: 用户互动 === +add(65, "Yes, today is a total mess.") # 知识点 today +add(66, "It's not the best day for visitors.") # 知识点 visit→visitors 可接受变体, 口语化 +add(68, "They could be here at any minute!") # 口语化 + +# === SECTION 9: 爷爷奶奶会生气 === +add(70, "How will they feel about this?") +add(72, "Grandfather and grandmother will be so upset!") # 知识点, 精简 mad→upset (正向引导) +add(74, "What should we do?") +add(76, "Don't worry! I'll help you clean up!") # 增加具体动作词 +add(77, "Okay, first pick up the toys from the floor.") + +# === SECTION 10: 收拾 === +add(80, "Oh no! No time to put these away!") # 精简 +add(81, "Put them in the cupboard!") # 修正 it→them (玩具是复数) +add(83, "Your grandfather and grandmother won't see!") # 知识点 +add(85, "Good idea!") +add(88, "All done!") +add(89, "We need to clean the floor!") +add(90, "I'll do it!") # 口语化 + +# === SECTION 11: 扫地机器人 === +add(94, "Will Grandfather and Grandmother be happy?") # 知识点, satisfied→happy (低龄词) +add(95, "Well... this will do for now.") # 口语化 +add(96, "Before your grandfather and grandmother come to visit.") # 知识点 + +# === SECTION 12: 沙发污渍 === +add(99, "Alex! The sofa is a mess!") # 精简, 正向化 (不说 you made it dirty) +add(100, "I'll clean it.") # 口语化 +add(103, "I can't get the chocolate off!") +add(104, "I'm good at many things. Just not housework!") # 拆分 >8词, 口语化 + +# === SECTION 13: 更乱 === +add(106, "Oh no! We can't clean up in time!") # 精简+口语化 +add(107, "I wish we had more time for this!") +add(108, "Why today, of all days?") # 口语化 +add(109, "I don't want them to see our house like this.") # 口语化修正 +add(110, "Why didn't they tell us earlier?") # 口语化修正 + +# === SECTION 14: 信 === +add(113, "I think they did tell you...") # 精简+口语化 +add(114, "In this letter.") +add(116, "What is in this letter?") + +# === SECTION 15: 核心互动-听力 (Justin念信) === +add(119, "Hi, your grandfather is writing this letter.") # 知识点 grandfather +add(120, "I will come to visit you guys.") # 知识点 visit +add(121, "Your grandmother will visit you too!") # 知识点 grandmother, visit +add(122, "We love you, see you soon!") + +# === SECTION 16: 反应 === +add(124, "They were so happy to visit you!") # 知识点 visit +add(125, "Yes. We should be happy too.") +add(127, "We should not be grumpy like this.") +add(128, "Let's do something to welcome them!") # 口语化 +add(130, "You can make something they like!") # 知识点, 精简 +add(131, "What is their favourite food?") + +# === SECTION 17: 苹果派 === +add(133, "Grandmother likes apples.") # 知识点 grandmother, 修正 apple→apples +add(134, "Grandfather likes pie.") # 知识点 grandfather +add(135, "Then you can make apple pie for them!") +add(136, "I remember the recipe.") +add(137, "I can fix the cooking robot.") +add(139, "Okay! Apple pie is today's welcome gift!") # 知识点 today + +# === SECTION 18: 完成了 === +add(142, "You did it!") # 口语化 (替换 You finished) +add(143, "Now we just wait for them to arrive.") # 精简 + +# === SECTION 19: 爷爷奶奶来了 === +add(146, "They're coming!") # 口语化 +add(148, "Who wants a big hug from Grandfather?") # 知识点, 修正语法 +add(149, "Me! Me!") +add(150, "Who wants Grandmother's homemade candy?") # 知识点, 修正 grandmother→Grandmother's, handmade→homemade(低龄) +add(151, "Me! Me!") +add(153, "We made something for you.") +add(154, "Mmm! What's that smell? Is that a pie?") # 口语化 +add(155, "I smell apples!") # 修正 apple→apples +add(156, "Whoa! I know that smell! Apple pie!") # 口语化 +add(157, "That is our favourite!") +add(159, "Oh, you have a friend over!") # 修正 in home→over +add(161, "Hello! I am here to visit Vicky.") # 知识点 visit +add(162, "Nice to meet you again!") +add(163, "I guess you helped out today too.") # 知识点 today, 口语化修正 + +# === SECTION 20: 结尾 === +add(165, "Today seems pretty busy.") # 知识点 today +add(167, "Yes, we made a little mess before you came.") # 知识点 visit, 口语化修正 +add(168, "Today was not easy.") # 知识点 today, 修正时态 is→was +add(170, "That's okay.") # 口语化 +add(172, "Grandfather and Grandmother can fix anything.") # 知识点, 口语化 everything→anything +add(173, "That's what family is for!") # 口语化重构 +add(174, "Mess or not, we love seeing you!") # 口语化重构 + + +# Print summary and build write payload +print(f"Total lines to write: {len(optimizations)}") + +# Build values payload per row +# We need to identify continuous ranges for efficiency +# But since the API supports writing individual ranges, let's batch by nearby rows + +# Sort by row index +sorted_rows = sorted(optimizations.items()) + +# Build the complete column F data: for each row, either the optimized value or empty string ("") +# We need all rows 2-149 (sheet rows) +all_values = [] +for row_idx in range(1, 150): # 1 = sheet row 2, 149 = sheet row 150 + if row_idx in optimizations: + all_values.append([optimizations[row_idx]]) + else: + all_values.append([""]) + +# Write back - use PUT to write the entire column at once +# We write empty strings for non-dialogue rows to clear previous content + +import os +import json as jmod + +app_id = "cli_a931175d41799cc7" +config_path = "/root/.openclaw/credentials/xiaoyan/config.json" +with open(config_path) as f: + cfg = jmod.load(f) +app_secret = cfg["apps"][0]["appSecret"] + +# Get token +result = subprocess.run([ + "curl", "-s", "-X", "POST", + "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal", + "-H", "Content-Type: application/json", + "-d", jmod.dumps({"app_id": app_id, "app_secret": app_secret}) +], capture_output=True, text=True) +token = jmod.loads(result.stdout)["tenant_access_token"] + +spreadsheet_token = "DA7csROG6hmnQMt59oScOLRfnWc" +sheet_id = "Nr3Ovs" + +# Write in batches of 50 rows to avoid too-large payloads +batch_size = 50 +for batch_start in range(0, len(all_values), batch_size): + batch = all_values[batch_start:batch_start + batch_size] + # Sheet rows: batch_start + 2 to batch_start + 1 + len(batch) + start_row = batch_start + 2 + end_row = batch_start + 1 + len(batch) + + range_str = f"{sheet_id}!F{start_row}:F{end_row}" + + payload = { + "valueRange": { + "range": range_str, + "values": batch + } + } + + result = subprocess.run([ + "curl", "-s", "-X", "PUT", + f"https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/{spreadsheet_token}/values", + "-H", f"Authorization: Bearer {token}", + "-H", "Content-Type: application/json", + "-d", jmod.dumps(payload) + ], capture_output=True, text=True) + + resp = jmod.loads(result.stdout) + print(f"Batch F{start_row}:F{end_row} → code={resp.get('code')}, msg={resp.get('msg')}") + if resp.get("code") != 0: + print(f" ERROR: {result.stdout[:200]}") + sys.exit(1) + +print("All batches written successfully!") diff --git a/scripts/dialogue_backfill_v2.py b/scripts/dialogue_backfill_v2.py new file mode 100644 index 0000000..6f5e5a4 --- /dev/null +++ b/scripts/dialogue_backfill_v2.py @@ -0,0 +1,235 @@ +#!/usr/bin/env python3 +""" +英文台词优化 & 回填脚本 v2 — 使用精确行号 +规则:MEMORY.md 对话台词优化通用规则 +知识点:grandmother, grandfather, visit, today +句型知识点:... visit+sb, Today is... +""" +import json, subprocess + +# (sheet_row, optimized_F_value) — exact sheet row numbers +rows = [ + (3, "Open up!"), + (4, "Your letter is here!"), + (6, "Nobody answers?"), + (7, "Alright... I'll go in myself."), + (9, "Whoa! What a mess!"), + (11, "What's wrong?"), + (12, "Vicky? Can you hear me?"), + (14, "Today is a No-Mom-and-Dad day!"), + (15, "Our parents aren't home!"), + (16, "So we can do whatever we want!"), + (17, "Really?"), + (19, "Yes!"), + (20, "Today is a good day!"), + (21, "Huh?"), + (22, "Are you sure today is a good day?"), + (23, "So on a good day, nobody says sorry?"), + (24, "Eva, Peter, say sorry."), + (25, "Okay. We're sorry."), + (27, "Ah, kids."), + (28, "Come visit us!"), + (29, "Oh, I'm not here to visit."), + (30, "I'm here to give you something."), + (32, "Wait a moment."), + (34, "Hello? Grandfather and grandmother, how are you?"), + (35, "Good, good! We're great!"), + (36, "How are you all, my lovely grandchildren?"), + (37, "We're great! Today is so much fun!"), + (38, "Great to hear that!"), + (39, "Are you ready for us?"), + (40, "I'm so excited to visit you all!"), + (41, "Sure! When will you visit us?"), + (42, "Today is the day, of course!"), + (43, "What? You're both coming today?"), + (44, "Yes! Me and Justin. Your grandmother and grandfather."), + (45, "We sent you a letter. Remember?"), + (46, "See you later!"), + (47, "I love you! Bye!"), + (49, "Oh no! What did they say on the phone?"), + (50, "Your grandfather and grandmother are coming to visit you!"), + (51, "On your good day."), + (53, "Eva! Peter! Alex! We're in trouble!"), + (55, "What happened?"), + (56, "Grandfather and grandmother are coming to visit us!"), + (57, "So what?"), + (58, "Today! Right now!"), + (60, "No, not today..."), + (61, "Look at this place!"), + (62, "Yes, today is a total mess."), + (63, "It's not the best day for visitors."), + (64, "They could be here at any minute!"), + (65, "How will they feel about this?"), + (67, "Grandfather and grandmother will be so upset!"), + (69, "What should we do?"), + (70, "Don't worry! I'll help you clean up!"), + (71, "Okay, first pick up the toys from the floor."), + (73, "Oh no! No time to put these away!"), + (74, "Put them in the cupboard!"), + (75, "Your grandfather and grandmother won't see!"), + (76, "Good idea!"), + (79, "All done!"), + (80, "We need to clean the floor!"), + (81, "I'll do it!"), + (84, "Will Grandfather and Grandmother be happy?"), + (85, "Well... this will do for now."), + (86, "Before your grandfather and grandmother come to visit."), + (88, "Alex! The sofa is a mess!"), + (89, "I'll clean it."), + (91, "I can't get the chocolate off!"), + (92, "I'm good at many things. Just not housework!"), + (94, "Oh no! We can't clean up in time!"), + (95, "I wish we had more time for this!"), + (96, "Why today, of all days?"), + (97, "I don't want them to see our house like this."), + (98, "Why didn't they tell us earlier?"), + (100, "I think they did tell you..."), + (101, "In this letter."), + (102, "What is in this letter?"), + (104, "Hi, your grandfather is writing this letter."), + (105, "I will come to visit you guys."), + (106, "Your grandmother will visit you too!"), + (107, "We love you, see you soon!"), + (108, "They were so happy to visit you!"), + (109, "Yes. We should be happy too."), + (110, "We should not be grumpy like this."), + (111, "Let's do something to welcome them!"), + (112, "You can make something they like!"), + (113, "What is their favourite food?"), + (114, "Grandmother likes apples."), + (115, "Grandfather likes pie."), + (116, "Then you can make apple pie for them!"), + (117, "I remember the recipe."), + (118, "I can fix the cooking robot."), + (119, "Okay! Apple pie is today's welcome gift!"), + (121, "You did it!"), + (122, "Now we just wait for them to arrive."), + (124, "They're coming!"), + (126, "Who wants a big hug from Grandfather?"), + (127, "Me! Me!"), + (128, "Who wants Grandmother's homemade candy?"), + (129, "Me! Me!"), + (131, "We made something for you."), + (132, "Mmm! What's that smell? Is that a pie?"), + (133, "I smell apples!"), + (134, "Whoa! I know that smell! Apple pie!"), + (135, "That is our favourite!"), + (137, "Oh, you have a friend over!"), + (138, "Hello! I am here to visit Vicky."), + (139, "Nice to meet you again!"), + (140, "I guess you helped out today too."), + (142, "Today seems pretty busy."), + (143, "Yes, we made a little mess before you came."), + (144, "Today was not easy."), + (145, "That's okay."), + (146, "Grandfather and Grandmother can fix anything."), + (147, "That's what family is for!"), + (148, "Mess or not, we love seeing you!"), +] + +print(f"Total dialogue lines: {len(rows)}") + +# Build the complete column F data (one value per row, 1-based sheet rows 2-149) +# We need to clear the ENTIRE column first, then fill only dialogue rows. +# The API PUT overwrites the specified range. Write all rows 2-149. +# Use a dict for fast lookup +f_map = {r: v for r, v in rows} + +# Get token +with open("/root/.openclaw/credentials/xiaoyan/config.json") as f: + cfg = json.load(f) +app_id = "cli_a931175d41799cc7" +app_secret = cfg["apps"][0]["appSecret"] + +result = subprocess.run([ + "curl", "-s", "-X", "POST", + "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal", + "-H", "Content-Type: application/json", + "-d", json.dumps({"app_id": app_id, "app_secret": app_secret}) +], capture_output=True, text=True) +token = json.loads(result.stdout)["tenant_access_token"] + +spreadsheet_token = "DA7csROG6hmnQMt59oScOLRfnWc" +sheet_id = "Nr3Ovs" + +# Step 1: Clear entire column F (rows 2-149) +print("Clearing column F (rows 2-149)...") +empty_payload = { + "valueRange": { + "range": f"{sheet_id}!F2:F149", + "values": [[""] for _ in range(148)] # 149-2+1 = 148 rows + } +} +result = subprocess.run([ + "curl", "-s", "-X", "PUT", + f"https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/{spreadsheet_token}/values", + "-H", f"Authorization: Bearer {token}", + "-H", "Content-Type: application/json", + "-d", json.dumps(empty_payload) +], capture_output=True, text=True) +resp = json.loads(result.stdout) +print(f" Clear: code={resp.get('code')}, msg={resp.get('msg')}") +assert resp.get("code") == 0, f"Clear failed: {result.stdout[:200]}" + +# Step 2: Write optimized values to each dialogue row +# Group consecutive rows for batching +batch_start = None +batch_end = None +batch_values = [] + +def flush_batch(): + global batch_start, batch_end, batch_values + if not batch_values: + return + range_str = f"{sheet_id}!F{batch_start}:F{batch_end}" + payload = {"valueRange": {"range": range_str, "values": batch_values}} + result = subprocess.run([ + "curl", "-s", "-X", "PUT", + f"https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/{spreadsheet_token}/values", + "-H", f"Authorization: Bearer {token}", + "-H", "Content-Type: application/json", + "-d", json.dumps(payload) + ], capture_output=True, text=True) + resp = json.loads(result.stdout) + print(f" Write {range_str}: code={resp.get('code')}, msg={resp.get('msg')}") + assert resp.get("code") == 0, f"Write failed: {result.stdout[:200]}" + batch_start = None + batch_end = None + batch_values = [] + +# Also need to fill empty rows between dialogue rows +# Strategy: write row by row, merging consecutive rows into batches +for sheet_row in range(2, 150): + value = f_map.get(sheet_row, "") + if batch_start is None: + batch_start = sheet_row + batch_end = sheet_row + batch_values = [[value]] + else: + batch_end = sheet_row + batch_values.append([value]) + +flush_batch() + +print("Done! Verifying...") + +# Step 3: Verify key rows +check_rows = [3, 7, 14, 23, 28, 34, 44, 49, 56, 64, 92, 97, 111, 121, 134, 147] +for r in check_rows: + result = subprocess.run([ + "curl", "-s", "-X", "GET", + f"https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/{spreadsheet_token}/values/{sheet_id}!D{r}:F{r}?valueRenderOption=ToString", + "-H", f"Authorization: Bearer {token}" + ], capture_output=True, text=True) + vals = json.loads(result.stdout).get("data", {}).get("valueRange", {}).get("values", [[]])[0] + role = vals[0] if len(vals) > 0 else "-" + e_val = vals[1] if len(vals) > 1 else "-" + f_val = vals[2] if len(vals) > 2 else "-" + expected = f_map.get(r, "-") + status = "✓" if f_val == (expected or "") else "✗ MISMATCH" + if status != "✓": + print(f" Row {r}: [{role}] E='{e_val}' F='{f_val}' expected='{expected}' {status}") + else: + print(f" Row {r}: [{role}] F='{f_val}' {status}") + +print("\nAll verified!") diff --git a/scripts/final_audit_all.py b/scripts/final_audit_all.py new file mode 100644 index 0000000..68c0b0e --- /dev/null +++ b/scripts/final_audit_all.py @@ -0,0 +1,144 @@ +""" +最终审校:单元挑战听力全部表格(P1-P7除P3) +""" +import json, urllib.request + +APP_TOKEN = "CMHSbUUjka3TrUsaxxEc297ongf" +CRED_FILE = "/root/.openclaw/credentials/xiaoyan/config.json" + +STD_HEARING_TAGS = { + "显性事实理解|关键词识别", "显性事实理解|单句信息点抓取", + "基础语境理解|场景/物品/动作识别", "显性细节理解|数字/时间/地点", + "同义替换识别|词/短语级", "目的/偏好识别|显性 to/for/like", + "干扰抑制|多信息筛选", "语用推断|否定与纠错", + "多句保持|信息整合", "情绪/态度理解", "长对话理解|主旨+细节", +} + +def get_token(): + with open(CRED_FILE) as f: + cfg = json.load(f) + req = urllib.request.Request( + "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal", + data=json.dumps({"app_id": cfg['apps'][0]['appId'], "app_secret": cfg['apps'][0]['appSecret']}).encode(), + headers={"Content-Type": "application/json"}) + return json.loads(urllib.request.urlopen(req).read())['tenant_access_token'] + +def api_call(url): + token = get_token() + req = urllib.request.Request(url, headers={"Authorization": f"Bearer {token}"}) + return json.loads(urllib.request.urlopen(req).read()) + +tables = { + "P1": ("tbliZAhcc9C43B23", "listening_choicePic"), + "P2": ("tblzTLNH7f13uWQN", "listening_tableCloze"), + "P4": ("tblVmeDtBDKsAEfz", "listening_choiceShort"), + "P5": ("tblDssVmhGzc3UKd", "listening_matchInfo"), + "P6": ("tbloiMcD0sBtGSTq", "listening_choicePic"), + "P7": ("tbly9SvPEa44k3yX", "listening_drag"), +} + +results = {} + +for name, (tid, etype) in tables.items(): + url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{tid}/records?page_size=100" + resp = api_call(url) + items = resp['data']['items'] + + ok = 0 + missing_expl = 0 + bad_tags = 0 + json_err = 0 + empty = 0 + single_group = 0 + skip_010199 = 0 + + for item in items: + sid = item['fields'].get('题目集合 ID', '') + if '010199' in str(sid): + skip_010199 += 1 + continue + + jd_str = item['fields'].get('jsonData', '') + if not jd_str or jd_str == 'None': + empty += 1 + continue + + try: + jd = json.loads(jd_str) + except: + json_err += 1 + continue + + has_issues = False + + # Check first/second questionSet + for section in ['first', 'second']: + sect = jd.get(section, {}) + if not sect: + continue + + # Check section-level explanation (P5 style) + if 'explanation' in sect: + if not sect['explanation']: + missing_expl += 1 + has_issues = True + + # Check section-level ability (P5 style) + if 'ability' in sect and isinstance(sect['ability'], list): + for a in sect['ability']: + a_clean = a.split('¥¥')[0].strip() + if a_clean not in STD_HEARING_TAGS: + bad_tags += 1 + has_issues = True + + # Check question-level + qs = sect.get('questionSet', []) + if not qs: + continue + + for q in qs: + # Explanation + if not q.get('explanation'): + missing_expl += 1 + has_issues = True + # Ability + for a in q.get('ability', []): + if isinstance(a, str): + a_clean = a.split('¥¥')[0].strip() + if a_clean not in STD_HEARING_TAGS: + bad_tags += 1 + has_issues = True + + # Check root questionSet (P6 style) + qs = jd.get('questionSet', []) + for q in qs: + if not q.get('explanation'): + missing_expl += 1 + has_issues = True + for a in q.get('ability', []): + if isinstance(a, str) and a.split('¥¥')[0].strip() not in STD_HEARING_TAGS: + bad_tags += 1 + has_issues = True + + # Check single group + has_first = jd.get('first', {}).get('questionSet') or jd.get('first', {}).get('answerSet') + has_second = jd.get('second', {}).get('questionSet') + if has_first and not has_second and jd.get('second') is not None: + single_group += 1 + + if not has_issues: + ok += 1 + + results[name] = { + 'total': len(items), 'skip_010199': skip_010199, 'empty': empty, + 'ok': ok, 'missing_expl': missing_expl, 'bad_tags': bad_tags, + 'json_err': json_err, 'single_group': single_group + } + +print(f"{'Table':<8} {'Total':>5} {'Skip':>5} {'Empty':>5} {'Pass':>5} {'!Expl':>5} {'!Tag':>5} {'!JSON':>5} {'1Grp':>5}") +print("-" * 58) +for name in ["P1","P2","P4","P5","P6","P7"]: + r = results[name] + print(f"{name:<8} {r['total']:>5} {r['skip_010199']:>5} {r['empty']:>5} {r['ok']:>5} {r['missing_expl']:>5} {r['bad_tags']:>5} {r['json_err']:>5} {r['single_group']:>5}") + +print(f"\nTotal: OK={sum(r['ok'] for r in results.values())}, MissingExpl={sum(r['missing_expl'] for r in results.values())}, BadTags={sum(r['bad_tags'] for r in results.values())}, JSONerr={sum(r['json_err'] for r in results.values())}") diff --git a/scripts/final_fix.py b/scripts/final_fix.py new file mode 100644 index 0000000..472050a --- /dev/null +++ b/scripts/final_fix.py @@ -0,0 +1,156 @@ +""" +最终修复:P1/P3/P4/P5 的能力标签+解析,使用直接 curl(tempfile 入参,无 shell 转义风险) +""" +import json, subprocess + +cred_file = '/root/.openclaw/credentials/xiaoyan/config.json' +with open(cred_file) as f: + cfg = json.load(f) +app_id = cfg['apps'][0]['appId'] +app_secret = cfg['apps'][0]['appSecret'] +body = json.dumps({'app_id': app_id, 'app_secret': app_secret}) +r = subprocess.run(f"curl -s -X POST 'https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal' -H 'Content-Type: application/json' -d '{body}'", shell=True, capture_output=True, text=True, timeout=10) +token = json.loads(r.stdout)['tenant_access_token'] + +APP_TOKEN = 'CMHSbUUjka3TrUsaxxEc297ongf' + +def fetch(tid): + r = subprocess.run(f"curl -s -X GET 'https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{tid}/records?page_size=500' -H 'Authorization: Bearer {token}'", shell=True, capture_output=True, text=True, timeout=15) + return json.loads(r.stdout) + +def update(tid, rid, fields): + tmpfile = '/tmp/bitable_fix.json' + with open(tmpfile, 'w') as f: + json.dump({"fields": fields}, f, ensure_ascii=False) + r = subprocess.run(f"curl -s -X PUT 'https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{tid}/records/{rid}' -H 'Authorization: Bearer {token}' -H 'Content-Type: application/json' -d @{tmpfile}", shell=True, capture_output=True, text=True, timeout=15) + return json.loads(r.stdout) + +# ============ P1 032501 ============ +data = fetch('tblCgfYDnnqwLfgH') +for item in data['data']['items']: + if item['record_id'] == 'recvjt0HzBBhYu' and item['fields'].get('题目集合 ID','') == '032501': + jd = json.loads(item['fields']['jsonData']) + for q in jd['first']['questionSet']: + ab = [{'信息定位与提取':'扫读定位|信息匹配','细节理解':'细节理解|事实信息提取'}.get(a,a) for a in q['ability']] + q['ability'] = ab + resp = update('tblCgfYDnnqwLfgH', 'recvjt0HzBBhYu', {'jsonData': json.dumps(jd, ensure_ascii=False)}) + print(f"P1 032501: {'✅' if resp.get('code')==0 else '❌'+str(resp.get('msg',''))}") + +# ============ P3 032601 ============ +data = fetch('tbl4q0ZUV3HB54t1') +for item in data['data']['items']: + if item['record_id'] == 'recvjug1hWz2oG': + jd = json.loads(item['fields']['jsonData']) + TAG = { + '信息定位':'扫读定位|信息匹配','细节理解':'细节理解|事实信息提取', + '因果推断':'推理判断|原因/结果','主旨归纳':'主旨理解|段落/文本大意', + '推理判断':'推理判断|原因/结果', + } + # Also fix any corrupted tags by looking at original context + # Q0: what did they plan → 细节理解|事实信息提取 + # Q1: why late → 推理判断|原因/结果 + # Q2: what did they do → 细节理解|事实信息提取 + # Q3: what was favorite → 细节理解|事实信息提取 + # Q4: what lesson → 主旨理解|段落/文本大意 + CORRECT = [ + ['扫读定位|信息匹配', '细节理解|事实信息提取'], + ['扫读定位|信息匹配', '推理判断|原因/结果'], + ['扫读定位|信息匹配', '细节理解|事实信息提取'], + ['扫读定位|信息匹配', '细节理解|事实信息提取'], + ['主旨理解|段落/文本大意', '推理判断|原因/结果'], + ] + for qi, q in enumerate(jd['first']['questionSet']): + q['ability'] = CORRECT[qi] + resp = update('tbl4q0ZUV3HB54t1', 'recvjug1hWz2oG', {'jsonData': json.dumps(jd, ensure_ascii=False)}) + print(f"P3 032601: {'✅' if resp.get('code')==0 else '❌'+str(resp.get('msg',''))}") + +# ============ P4 032701 & 032801 ============ +EXPL_032701 = [ + "空白处需要表示赚钱的名词。get some cash 是地道表达,cash(现金)最符合学生售卖手链的情境。cheque(支票)语义过正式,bracelet(手链)是销售的商品而非获取的目标。考查名词在语境中的最佳语义选择。", + "修饰可数名词bracelets应用many(许多)。much修饰不可数名词,only(只有)在此处语义不流畅,不符合带了一些手链去给朋友看的语境。考查可数名词数量修饰词的语法规则。", + "表语位置需形容词。They were very popular(它们很受欢迎)才是完整句子。cash和sell均非形容词,不能直接作表语。考查词性辨别和上下文语义连贯。", + "students是可数名词复数,需用many修饰表示很多学生。much用于不可数名词,only在so many结构后不合适。考查可数名词数量表达。", + "bracelets是可数名词复数,需用many表示数量。didn't have many bracelets表示手链不够多,与后文promised to make more呼应。考查上下文逻辑与可数名词修饰。", + "空白处需动词作谓语。She could sell a few(她能卖出几条)中sell是唯一动词。cheque和popular均为非动词。考查句子主干成分(谓语动词)的语法识别。", +] +EXPL_032801 = [ + "根据后文a local band was playing music和it was very noisy,可推断是一场派对。party最能描述这种热闹场景,car和sleep与乐队演奏、噪音等语境不符。考查名词的语境语义选择。", + "I tried to sleep, but the sound was too loud——噪音太大导致睡不着,sleep是最合理的选择。drive和sing都偏离语境。考查动词的语义逻辑匹配。", + "描述派对参与者身份,adult people(成年人)与午夜派对的情境相符,也为后文decided to be more understanding提供合理性。考查修饰词的语境判断。", + "The party went on until midnight——派对持续到午夜是最符合逻辑的时间终点。morning和afternoon时间太早,不符合嘈杂派对的情境。考查时间名词的合理推断。", + "前后分句存在转折关系:I was annoyed because I couldn't rest, but then I remembered...。but引导转折,because表示原因,so表示结果。考查连词的逻辑关系。", + "此处需引导原因的连词。because the band members cleaned up解释为什么第二天心情好转。although表示让步,until表示时间。考查原因连词的选择。", +] + +data = fetch('tblzKVm1FEukPgnN') +for sid, rid, expls in [('032701','recvjueHm15HPu',EXPL_032701),('032801','recvjueN5QT1c5',EXPL_032801)]: + for item in data['data']['items']: + if item['record_id'] == rid: + jd = json.loads(item['fields']['jsonData']) + t1 = item['fields'].get('题目1','') or '' + for qi, q in enumerate(jd['first']['questionSet']): + q['ability'] = ['语法结构识别|完形填空'] + q['explanation'] = expls[qi] + # Build 题目1 with explanations + if '【解析】' not in t1: + t1_lines = [f"{qi+1}. {expls[qi]}" for qi in range(len(expls))] + t1_new = t1 + '\n\n【解析】\n' + '\n'.join(t1_lines) + else: + t1_new = t1 + update_fields = { + 'jsonData': json.dumps(jd, ensure_ascii=False), + '题目1': t1_new, + } + resp = update('tblzKVm1FEukPgnN', rid, update_fields) + print(f"P4 {sid}: {'✅' if resp.get('code')==0 else '❌'+str(resp.get('msg',''))}") + +# ============ P5 032901 ============ +EXPL_032901_first = [ + "前后是因果关系:去野生动物园是因为想看小象。because引导原因状语从句最合适。but表示转折,so表示结果,均不符合语义。考查原因连词。", + "had just been born是被动语态,表示刚刚出生。born是bear的过去分词,与a few weeks ago的时间状语呼应。grow up(长大)和top(顶端)语义不通。考查词义辨析与被动语态。", + "reach the top of a tall tree——大象想够到高树的顶端。top表示顶部位置,与tall tree形成语义对应。bottom与reach矛盾,nose不是位置。考查方位词的选择。", + "大象的长鼻子是标志性特征。nose是正确选项,banana和bottom明显不符。考查动物特征相关的核心词汇。", + "could only touch the bottom——虽然用长鼻子去够,但只碰到了底部。bottom与top形成对比,体现够不到的落差感。考查反义词对(top↔bottom)的理解。", + "小象吃黄色的香蕉是最自然的食物搭配。banana是常见动物投喂食物,elephant和nose均不符合吃的语义。考查动物食物相关词汇。", + "when I grow up(等我长大)是固定表达。grow up表示成长、长大,born与when从句时态矛盾,bottom不相关。考查固定短语的掌握。", + "根据上下文描述的美好回忆,bottom指从心底的比喻义。from the bottom of my heart是常见表达。top与情感表达不相配,nose属于干扰项。考查固定搭配和比喻义。", +] +EXPL_032901_second = [ + "前后分句是因果关系:姐姐喜欢美丽的东西,所以总戴首饰。so引导结果,because引导原因,but表示转折。考查结果连词。", + "lost one of her favourite silver earrings——在首饰语境中,银色的earrings(耳环)是最典型的可丢失物品。painting和language与silver修饰和wear搭配都不符。考查语境词义推断。", + "couldn't find it anywhere else——在否定句中else表示别的地方/其他任何地方,except含义不符,again重复逻辑不通。考查否定句中的词汇用法。", + "I decided to make..., although I'm not very good——前后存在让步转折关系,虽然不擅长但还是决定做。although正确,because和so均表示因果。考查让步连词。", + "She showed me how to use small silver pieces——朋友擅长的是首饰制作(jewellery),与silver pieces和earrings主题一致。painting和language偏离主题。考查上下文主题关联。", + "I made a mistake, so I had to start again——犯错后重新开始,again表示再一次。else用于否定句表另外,except表除了。考查副词选择。", +] + +data = fetch('tblLmUxzzUDe0QAJ') +for item in data['data']['items']: + if item['record_id'] == 'recvjueULrufNg': + jd = json.loads(item['fields']['jsonData']) + t1 = item['fields'].get('题目1','') or '' + t2 = item['fields'].get('题目2','') or '' + + for qi, q in enumerate(jd['first']['questionSet']): + q['ability'] = ['语法结构识别|完形填空'] + q['explanation'] = EXPL_032901_first[qi] + for qi, q in enumerate(jd['second']['questionSet']): + q['ability'] = ['语法结构识别|完形填空'] + q['explanation'] = EXPL_032901_second[qi] + + if '【解析】' not in t1: + t1_new = t1 + '\n\n【解析】\n' + '\n'.join(f"{i+1}. {e}" for i,e in enumerate(EXPL_032901_first)) + else: + t1_new = t1 + if '【解析】' not in t2: + t2_new = t2 + '\n\n【解析】\n' + '\n'.join(f"{i+1}. {e}" for i,e in enumerate(EXPL_032901_second)) + else: + t2_new = t2 + + update_fields = { + 'jsonData': json.dumps(jd, ensure_ascii=False), + '题目1': t1_new, + '题目2': t2_new, + } + resp = update('tblLmUxzzUDe0QAJ', 'recvjueULrufNg', update_fields) + print(f"P5 032901: {'✅' if resp.get('code')==0 else '❌'+str(resp.get('msg',''))}") diff --git a/scripts/fix_ability_tags.py b/scripts/fix_ability_tags.py new file mode 100644 index 0000000..910848b --- /dev/null +++ b/scripts/fix_ability_tags.py @@ -0,0 +1,253 @@ +""" +批量修正单元挑战听力能力标签 + JSON修复 +""" +import json, urllib.request, re, sys, os + +APP_TOKEN = "CMHSbUUjka3TrUsaxxEc297ongf" +CRED_FILE = "/root/.openclaw/credentials/xiaoyan/config.json" + +# === 标准听力标签 === +STD_HEARING_TAGS = { + "显性事实理解|关键词识别", + "显性事实理解|单句信息点抓取", + "基础语境理解|场景/物品/动作识别", + "显性细节理解|数字/时间/地点", + "同义替换识别|词/短语级", + "目的/偏好识别|显性 to/for/like", + "干扰抑制|多信息筛选", + "语用推断|否定与纠错", + "多句保持|信息整合", + "情绪/态度理解", + "长对话理解|主旨+细节", +} + +# === 标签映射规则(正则) === +TAG_PATTERNS = [ + # P1 style: "显性事实理解(关键词识别)- bag" + (r'显性事实理解(关键词识别)', '显性事实理解|关键词识别'), + (r'显性事实理解(单句信息点抓取)', '显性事实理解|单句信息点抓取'), + (r'显性细节理解(地点)', '显性细节理解|数字/时间/地点'), + (r'显性细节理解(数字)', '显性细节理解|数字/时间/地点'), + (r'显性细节理解(时间)', '显性细节理解|数字/时间/地点'), + (r'显性细节理解(复现)', '显性细节理解|数字/时间/地点'), + (r'干扰抑制(多信息筛选)', '干扰抑制|多信息筛选'), + (r'干扰抑制(多信息筛选:.*?)', '干扰抑制|多信息筛选'), + (r'偏好识别(显性\s*like)', '目的/偏好识别|显性 to/for/like'), + (r'偏好识别(显性\s*love)', '目的/偏好识别|显性 to/for/like'), + (r'偏好识别(显性like)', '目的/偏好识别|显性 to/for/like'), + (r'目的识别(显性\s*for)', '目的/偏好识别|显性 to/for/like'), + (r'目的识别(显性\s*to)', '目的/偏好识别|显性 to/for/like'), + (r'问题意图识别', '语用推断|否定与纠错'), + (r'关键细节听辨', '显性细节理解|数字/时间/地点'), + (r'否定与纠错', '语用推断|否定与纠错'), + (r'图像语义对齐', '基础语境理解|场景/物品/动作识别'), + (r'近义改写', '同义替换识别|词/短语级'), + (r'多句保持(信息整合)', '多句保持|信息整合'), + # P2/P4 style + (r'听觉抓取关键信息', '显性事实理解|关键词识别'), + (r'多特征整合', '多句保持|信息整合'), + (r'语用推断', '语用推断|否定与纠错'), + # P7 extras + (r'显性细节理解(地点:.*?)', '显性细节理解|数字/时间/地点'), + (r'显性细节理解(数字:.*?)', '显性细节理解|数字/时间/地点'), + (r'干扰抑制(多信息筛选:.*?)', '干扰抑制|多信息筛选'), + (r'语用推断(句式理解)', '语用推断|否定与纠错'), + (r'语境理解(场景推断)', '基础语境理解|场景/物品/动作识别'), + (r'语用推断(否定与纠错)', '语用推断|否定与纠错'), + (r'语用推断(语气识别)', '语用推断|否定与纠错'), + (r'显性事实理解(单句信息点抓取:.*?)', '显性事实理解|单句信息点抓取'), + (r'情绪态度理解', '情绪/态度理解'), + (r'长对话主旨+细节', '长对话理解|主旨+细节'), +] + +def map_tag(tag): + """Map a single old tag to standard format. Returns None if no match.""" + tag = tag.strip() + if tag in STD_HEARING_TAGS: + return tag + for pattern, replacement in TAG_PATTERNS: + if re.match(pattern + r'.*$', tag): + return replacement + return None + +def map_ability_list(abilities): + """Map a list of ability tags. Handles '¥¥' delimiters.""" + if not abilities: + return [] + result = [] + for a in abilities: + if not isinstance(a, str): + result.append(a) + continue + # Handle ¥¥ delimiter + parts = a.split('¥¥') + for part in parts: + part = part.strip() + if not part: + continue + mapped = map_tag(part) + if mapped and mapped not in result: + result.append(mapped) + elif mapped is None: + # Try to match anyway + mapped2 = map_tag(part) + if mapped2 and mapped2 not in result: + result.append(mapped2) + return result + +def get_token(): + with open(CRED_FILE) as f: + cfg = json.load(f) + app_id = cfg['apps'][0]['appId'] + app_secret = cfg['apps'][0]['appSecret'] + req = urllib.request.Request( + "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal", + data=json.dumps({"app_id": app_id, "app_secret": app_secret}).encode(), + headers={"Content-Type": "application/json"}) + return json.loads(urllib.request.urlopen(req).read())['tenant_access_token'] + +def api_get(url): + token = get_token() + req = urllib.request.Request(url, headers={"Authorization": f"Bearer {token}"}) + return json.loads(urllib.request.urlopen(req).read()) + +def api_put(url, body): + token = get_token() + req = urllib.request.Request(url, data=json.dumps(body).encode(), method="PUT", + headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"}) + return json.loads(urllib.request.urlopen(req).read()) + +def update_record(table_id, record_id, fields): + url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{table_id}/records/{record_id}" + return api_put(url, {"fields": fields}) + +def fix_tags_in_json(jd): + """Fix ability tags in jsonData - handles first/second questionSet and root questionSet""" + changed = False + + # Handle first/second structure + for section in ['first', 'second']: + sect = jd.get(section, {}) + if not sect: + continue + qs = sect.get('questionSet', []) + for q in qs: + old_ability = q.get('ability', []) + new_ability = map_ability_list(old_ability) + if new_ability != old_ability: + q['ability'] = new_ability + changed = True + + # Handle root questionSet (P6 style) + qs = jd.get('questionSet', []) + for q in qs: + old_ability = q.get('ability', []) + new_ability = map_ability_list(old_ability) + if new_ability != old_ability: + q['ability'] = new_ability + changed = True + + # Handle P5/P7 style: ability at root level + if 'ability' in jd: + old_ability = jd.get('ability', []) + new_ability = map_ability_list(old_ability) + if new_ability != old_ability: + jd['ability'] = new_ability + changed = True + + return jd, changed + +def fix_tags_for_record(record_id, table_id, table_name): + """Fix ability tags for a single record""" + url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{table_id}/records/{record_id}" + data = api_get(url) + record = data['data']['items'][0] + fields = record['fields'] + + jd_str = fields.get('jsonData', '{}') + if not jd_str or jd_str == 'None': + return False, "empty jsonData" + + try: + jd = json.loads(jd_str) + except json.JSONDecodeError as e: + return False, f"JSON parse error: {e}" + + jd_fixed, changed = fix_tags_in_json(jd) + + if not changed: + return True, "no changes needed" + + # Update the record with fixed jsonData + new_jd_str = json.dumps(jd_fixed, ensure_ascii=False) + result = update_record(table_id, record_id, {"jsonData": new_jd_str}) + + if result.get('code') == 0: + return True, "tags updated" + else: + return False, f"API error: {result}" + +# ===== Main ===== +targets = { + "听力-P1": "tbliZAhcc9C43B23", + "听力-P2": "tblzTLNH7f13uWQN", + "听力-P4": "tblVmeDtBDKsAEfz", + "听力-P7": "tbly9SvPEa44k3yX", + "听力-P5": "tblDssVmhGzc3UKd", + "听力-P6": "tbloiMcD0sBtGSTq", +} + +all_results = [] + +for name, table_id in targets.items(): + # Get all records + url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{table_id}/records?page_size=500" + data = api_get(url) + + if data.get('code') != 0: + print(f"❌ {name}: API error - {data.get('msg')}") + continue + + items = data['data']['items'] + print(f"\n{'='*50}") + print(f"{name}: {len(items)} records") + + total = 0 + skipped = 0 + success = 0 + failed = 0 + + for item in items: + fields = item.get('fields', {}) + sid = fields.get('题目集合 ID', '') or '' + rid = item['record_id'] + + total += 1 + + # Skip 010199 + if '010199' in str(sid): + skipped += 1 + continue + + # Skip empty + jd_str = fields.get('jsonData', '{}') + if not jd_str or jd_str == 'None': + skipped += 1 + continue + + ok, msg = fix_tags_for_record(rid, table_id, name) + if ok: + success += 1 + print(f" {'✅' if 'updated' in msg else '⏭️'} ID={sid:10s} | {msg}") + else: + failed += 1 + print(f" ❌ ID={sid:10s} | {msg}") + + all_results.append({ + 'name': name, 'total': total, 'skipped': skipped, + 'success': success, 'failed': failed + }) + +print(f"\n\n===== 标签修正汇总 =====") +for r in all_results: + print(f"{r['name']:10s}: total={r['total']} skipped={r['skipped']} success={r['success']} failed={r['failed']}") diff --git a/scripts/fix_ability_tags_v2.py b/scripts/fix_ability_tags_v2.py new file mode 100644 index 0000000..5f17d97 --- /dev/null +++ b/scripts/fix_ability_tags_v2.py @@ -0,0 +1,230 @@ +""" +批量修正单元挑战听力能力标签 v2 (使用 list_records + update) +""" +import json, urllib.request, re, sys + +APP_TOKEN = "CMHSbUUjka3TrUsaxxEc297ongf" +CRED_FILE = "/root/.openclaw/credentials/xiaoyan/config.json" + +STD_HEARING_TAGS = { + "显性事实理解|关键词识别", "显性事实理解|单句信息点抓取", + "基础语境理解|场景/物品/动作识别", "显性细节理解|数字/时间/地点", + "同义替换识别|词/短语级", "目的/偏好识别|显性 to/for/like", + "干扰抑制|多信息筛选", "语用推断|否定与纠错", + "多句保持|信息整合", "情绪/态度理解", "长对话理解|主旨+细节", +} + +TAG_PATTERNS = [ + (r'显性事实理解(关键词识别)', '显性事实理解|关键词识别'), + (r'显性事实理解(单句信息点抓取)', '显性事实理解|单句信息点抓取'), + (r'显性事实理解(单句信息点抓取:.*?)', '显性事实理解|单句信息点抓取'), + (r'显性细节理解(地点)', '显性细节理解|数字/时间/地点'), + (r'显性细节理解(地点:.*?)', '显性细节理解|数字/时间/地点'), + (r'显性细节理解(数字)', '显性细节理解|数字/时间/地点'), + (r'显性细节理解(数字:.*?)', '显性细节理解|数字/时间/地点'), + (r'显性细节理解(时间)', '显性细节理解|数字/时间/地点'), + (r'显性细节理解(复现)', '显性细节理解|数字/时间/地点'), + (r'干扰抑制(多信息筛选)', '干扰抑制|多信息筛选'), + (r'干扰抑制(多信息筛选:.*?)', '干扰抑制|多信息筛选'), + (r'偏好识别(显性\s*like\s*)', '目的/偏好识别|显性 to/for/like'), + (r'偏好识别(显性\s*love\s*)', '目的/偏好识别|显性 to/for/like'), + (r'目的识别(显性\s*for\s*)', '目的/偏好识别|显性 to/for/like'), + (r'目的识别(显性\s*to\s*)', '目的/偏好识别|显性 to/for/like'), + (r'问题意图识别', '语用推断|否定与纠错'), + (r'关键细节听辨', '显性细节理解|数字/时间/地点'), + (r'否定与纠错', '语用推断|否定与纠错'), + (r'图像语义对齐', '基础语境理解|场景/物品/动作识别'), + (r'近义改写', '同义替换识别|词/短语级'), + (r'多句保持(信息整合)', '多句保持|信息整合'), + (r'听觉抓取关键信息', '显性事实理解|关键词识别'), + (r'多特征整合', '多句保持|信息整合'), + (r'语用推断(句式理解)', '语用推断|否定与纠错'), + (r'语用推断(否定与纠错)', '语用推断|否定与纠错'), + (r'语用推断(语气识别)', '语用推断|否定与纠错'), + (r'语境理解(场景推断)', '基础语境理解|场景/物品/动作识别'), + (r'情绪态度理解', '情绪/态度理解'), + (r'长对话主旨\+细节', '长对话理解|主旨+细节'), + (r'语用推断', '语用推断|否定与纠错'), + (r'显性细节理解', '显性细节理解|数字/时间/地点'), + (r'显性事实理解', '显性事实理解|关键词识别'), + (r'干扰抑制', '干扰抑制|多信息筛选'), +] + +def map_tag(tag): + tag = tag.strip() + if not tag: + return None + if tag in STD_HEARING_TAGS: + return tag + for pattern, replacement in TAG_PATTERNS: + if re.match(pattern, tag): + return replacement + # Last resort: try partial match + for pattern, replacement in TAG_PATTERNS: + if re.search(pattern, tag): + return replacement + return None + +def map_ability_list(abilities): + if not abilities: + return [] + result = [] + for a in abilities: + if not isinstance(a, str): + continue + # Handle ¥¥ delimiter + for part in a.split('¥¥'): + part = part.strip() + if not part: + continue + # Remove trailing " - X" annotations like " - red", " - blue" + part = re.sub(r'\s*-\s*\w+\s*$', '', part) + part = part.strip() + mapped = map_tag(part) + if mapped and mapped not in result: + result.append(mapped) + return result + +def get_token(): + with open(CRED_FILE) as f: + cfg = json.load(f) + app_id = cfg['apps'][0]['appId'] + app_secret = cfg['apps'][0]['appSecret'] + req = urllib.request.Request( + "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal", + data=json.dumps({"app_id": app_id, "app_secret": app_secret}).encode(), + headers={"Content-Type": "application/json"}) + return json.loads(urllib.request.urlopen(req).read())['tenant_access_token'] + +def api_call(url, method='GET', body=None): + token = get_token() + headers = {"Authorization": f"Bearer {token}"} + data = json.dumps(body).encode() if body else None + if data: + headers["Content-Type"] = "application/json" + req = urllib.request.Request(url, data=data, method=method, headers=headers) + return json.loads(urllib.request.urlopen(req).read()) + +def fix_tags_in_json(jd): + changed = False + for section in ['first', 'second']: + sect = jd.get(section, {}) + if not sect: + continue + qs = sect.get('questionSet', []) + for q in qs: + old = q.get('ability', []) + new = map_ability_list(old) + if new != old: + q['ability'] = new + changed = True + # Root questionSet (P6) + qs = jd.get('questionSet', []) + for q in qs: + old = q.get('ability', []) + new = map_ability_list(old) + if new != old: + q['ability'] = new + changed = True + # Root ability (P5/P7) + if 'ability' in jd: + old = jd.get('ability', []) + new = map_ability_list(old) + if new != old: + jd['ability'] = new + changed = True + # answerSet ability (P5) + # (already handled by root ability) + return jd, changed + +# ===== Main ===== +targets = { + "听力-P1": "tbliZAhcc9C43B23", + "听力-P2": "tblzTLNH7f13uWQN", + "听力-P4": "tblVmeDtBDKsAEfz", + "听力-P7": "tbly9SvPEa44k3yX", + "听力-P5": "tblDssVmhGzc3UKd", + "听力-P6": "tbloiMcD0sBtGSTq", +} + +all_summary = [] + +for name, table_id in targets.items(): + print(f"\n{'='*50}") + print(f"{name}") + + # List all records + url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{table_id}/records?page_size=500" + resp = api_call(url) + if resp.get('code') != 0: + print(f" ❌ API error: {resp.get('msg')}") + continue + + items = resp['data']['items'] + total = len(items) + skip010199 = 0 + skip_empty = 0 + no_change = 0 + updated = 0 + failed = 0 + + for item in items: + fields = item.get('fields', {}) + sid = fields.get('题目集合 ID', '') or '' + rid = item['record_id'] + + if '010199' in str(sid): + skip010199 += 1 + continue + + jd_str = fields.get('jsonData', '{}') + if not jd_str or jd_str == 'None': + skip_empty += 1 + continue + + try: + jd = json.loads(jd_str) + except json.JSONDecodeError as e: + print(f" ⚠️ ID={sid}: JSON parse error, skipping") + failed += 1 + continue + + jd_fixed, changed = fix_tags_in_json(jd) + + if not changed: + no_change += 1 + continue + + # Update record + new_jd_str = json.dumps(jd_fixed, ensure_ascii=False) + update_url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{table_id}/records/{rid}" + upd_resp = api_call(update_url, 'PUT', {"fields": {"jsonData": new_jd_str}}) + + if upd_resp.get('code') == 0: + updated += 1 + old_tags = [] + for s in ['first','second']: + for q in jd.get(s,{}).get('questionSet',[]): + old_tags.extend(q.get('ability',[])) + new_tags = [] + for s in ['first','second']: + for q in jd_fixed.get(s,{}).get('questionSet',[]): + new_tags.extend(q.get('ability',[])) + if old_tags != new_tags: + print(f" ✅ ID={sid}: tags updated [{','.join(new_tags[:3])}...]") + else: + print(f" ✅ ID={sid}: tags updated") + else: + failed += 1 + print(f" ❌ ID={sid}: update failed - {upd_resp.get('msg','?')}") + + all_summary.append({ + 'name': name, 'total': total, 'skip010199': skip010199, + 'skip_empty': skip_empty, 'no_change': no_change, + 'updated': updated, 'failed': failed + }) + print(f" Total={total} Skip010199={skip010199} SkipEmpty={skip_empty} NoChange={no_change} Updated={updated} Failed={failed}") + +print(f"\n\n===== 能力标签修正汇总 =====") +for r in all_summary: + print(f"{r['name']:10s}: total={r['total']:2d} skip010199={r['skip010199']} skip_empty={r['skip_empty']} unchanged={r['no_change']} ✅updated={r['updated']} ❌failed={r['failed']}") diff --git a/scripts/fix_and_backfill.py b/scripts/fix_and_backfill.py new file mode 100644 index 0000000..3fdb39a --- /dev/null +++ b/scripts/fix_and_backfill.py @@ -0,0 +1,194 @@ +""" +修正能力标签 + 补充解析,回填 jsonData 和 题目1/题目2 +""" +import json, subprocess, copy + +APP_TOKEN = 'CMHSbUUjka3TrUsaxxEc297ongf' +SKILL = 'skills/lark_bitable_operate_as_bot/scripts/operate_bitable.sh' + +# ===== Ability tag mappings ===== +TAG_MAP = { + '信息定位与提取': '扫读定位|信息匹配', + '信息定位': '扫读定位|信息匹配', + '细节理解': '细节理解|事实信息提取', + '因果推断': '推理判断|原因/结果', + '主旨归纳': '主旨理解|段落/文本大意', + '推理判断': '推理判断|原因/结果', +} + +# ===== Explanations per record (qset_index, question_index) ===== +EXPLANATIONS = { + # P4 032701 - reading_cloze: Selling Bracelets at School + ('032701', 'first', 0): '空白处需要表示"赚钱"的名词。"get some cash"(挣点零花钱)是地道表达,cash(现金)最符合学生售卖手链的情境。cheque(支票)语义过正式,bracelet(手链)是销售的商品而非获取的目标。考查名词在语境中的最佳语义选择。', + ('032701', 'first', 1): '修饰可数名词bracelets应用many(许多)。much修饰不可数名词,only(只有)在此处语义不流畅,不符合"带了一些手链去给朋友看"的语境。考查可数名词数量修饰词的语法规则。', + ('032701', 'first', 2): '表语位置需形容词。"They were very popular"(它们很受欢迎)才是完整句子。cash和sell均非形容词,不能直接作表语。考查词性辨别和上下文语义连贯。', + ('032701', 'first', 3): 'students是可数名词复数,需用many修饰表示"很多学生"。much用于不可数名词,only在"so many"结构后不合适。考查可数名词数量表达。', + ('032701', 'first', 4): "bracelets是可数名词复数,需用many表示数量。didn't have many bracelets 表示手链不够多,与后文 promised to make more 呼应。考查上下文逻辑与可数名词修饰。", + ('032701', 'first', 5): '空白处需动词作谓语。"She could sell a few"(她能卖出几条)中sell是唯一动词。cheque和popular均为非动词。考查句子主干成分(谓语动词)的语法识别。', + + # P4 032801 - reading_cloze: The Midnight Party in the Car Park + ('032801', 'first', 0): '根据后文"a local band was playing music"和"it was very noisy",可推断是一场"派对"。party最能描述这种热闹场景,car和sleep与乐队演奏、噪音等语境不符。考查名词的语境语义选择。', + ('032801', 'first', 1): '"I tried to sleep, but the sound was too loud"——噪音太大导致睡不着,sleep是最合理的选择。drive(开车)和sing(唱歌)都偏离语境。考查动词的语义逻辑匹配。', + ('032801', 'first', 2): '描述派对参与者身份,"adult people"(成年人)与午夜派对的情境相符,也为后文"decided to be more understanding"提供合理性。young语义对等但看答案选择,考查修饰词的语境判断。', + ('032801', 'first', 3): '"The party went on until midnight"——派对持续到午夜是最符合逻辑的时间终点。morning和afternoon太早,不符合嘈杂派对的情境。考查时间名词的合理推断。', + ('032801', 'first', 4): "前后分句存在转折关系:I was annoyed because I couldn't rest, but then I remembered...。but引导转折,because表示原因(前后因果不成立),so表示结果。考查连词的逻辑关系。", + ('032801', 'first', 5): '此处需引导原因的连词。"because the band members cleaned up"解释为什么第二天心情好转。although表示让步(语义不通),until表示时间(不够自然)。考查原因连词的选择。', + + # P5 032901 first set - reading_openCloze: A Visit to the Wildlife Park + ('032901', 'first', 0): '前后是因果关系:去野生动物园"是因为"想看小象。because引导原因状语从句最合适。but表示转折,so表示结果,均不符合语义。考查原因连词。', + ('032901', 'first', 1): '"had just been born"是被动语态,表示"刚刚出生"。born是bear(出生)的过去分词,与"a few weeks ago"的时间状语呼应。grow up(长大)和top(顶端)语义不通。考查词义辨析与被动语态。', + ('032901', 'first', 2): '"reach the top of a tall tree"——大象想够到高树的顶端。top表示顶部位置,与tall tree(高树)形成语义对应。bottom(底部)与reach矛盾,nose(鼻子)不是位置。考查方位词的选择。', + ('032901', 'first', 3): '大象的"长鼻子"是标志性特征。nose是正确选项,banana和bottom明显不符。考查动物特征相关的核心词汇。', + ('032901', 'first', 4): '"could only touch the bottom"——虽然用长鼻子去够,但只碰到了"底部"。bottom与top形成对比,体现够不到的落差感。考查反义词对(top↔bottom)的理解。', + ('032901', 'first', 5): '小象吃黄色的"香蕉"是最自然的食物搭配。banana是常见动物投喂食物,elephant和nose均不符合"吃"的语义。考查动物食物相关词汇。', + ('032901', 'first', 6): '"when I grow up"(等我长大)是固定表达。grow up表示成长、长大,born(出生)与when从句时态矛盾,bottom不相关。考查固定短语的掌握。', + ('032901', 'first', 7): '根据上下文描述的美好回忆,bottom指"从心底"的比喻义。"from the bottom of my heart"是常见表达。top(顶部)与情感表达不相配,nose属于干扰项。考查固定搭配和比喻义。', + + # P5 032901 second set - reading_openCloze: A Special Gift + ('032901', 'second', 0): '前后分句是因果关系:姐姐喜欢美丽的东西,"所以"总戴首饰。so引导结果,because引导原因(方向反了),but表示转折(不成立)。考查结果连词。', + ('032901', 'second', 1): '"lost one of her favourite silver earrings"——在首饰语境中,银色的earrings(耳环)是最典型的可丢失物品。painting(画)和language(语言)与silver修饰和"wear"搭配都不符。考查语境词义推断。', + ('032901', 'second', 2): "couldn't find it anywhere else——在否定句中else表示别的地方/其他任何地方,except含义不符,again重复逻辑不通。考查否定句中的词汇用法。", + ('032901', 'second', 3): "I decided to make..., although I'm not very good——前后存在让步转折关系,虽然不擅长但还是决定做。although正确,because和so均表示因果。考查让步连词。", + ('032901', 'second', 4): '"She showed me how to use small silver pieces"——朋友擅长的是首饰制作(jewellery),与silver pieces和earrings主题一致。painting(绘画)和language(语言)偏离主题。考查上下文主题关联。', + ('032901', 'second', 5): '"I made a mistake, so I had to start again"——犯错后重新开始,again表示"再一次"。else用于否定句表"另外",except表"除了",都不符合重新来过的语义。考查副词选择。', +} + +def exec_bash(cmd): + r = subprocess.run(cmd, shell=True, capture_output=True, text=True) + try: + return json.loads(r.stdout) + except: + print(f"PARSE ERROR: {r.stdout[:200]}") + return {} + +def fix_record(tname, tid, rid, sid, record_fields): + """Fix ability tags and explanations, return updated jsonData + text fields""" + jd = json.loads(record_fields.get('jsonData', '{}')) + modified = False + + for set_key in ['first', 'second']: + qset = jd.get(set_key, {}).get('questionSet', []) + for qi, q in enumerate(qset): + # Fix ability tags + abilities = q.get('ability', []) + new_abilities = [] + ab_changed = False + for a in abilities: + if a in TAG_MAP: + new_abilities.append(TAG_MAP[a]) + ab_changed = True + else: + new_abilities.append(a) + + # For P4/P5: if ability is empty, add the cloze tag + if not new_abilities: + new_abilities = ['语法结构识别|完形填空'] + ab_changed = True + + if ab_changed: + q['ability'] = new_abilities + modified = True + + # Fix explanation + expl_key = (sid, set_key, qi) + if expl_key in EXPLANATIONS: + old_expl = q.get('explanation', '') + if not old_expl or old_expl.strip() == '': + q['explanation'] = EXPLANATIONS[expl_key] + modified = True + + if not modified: + return None, None, None + + new_jd_str = json.dumps(jd, ensure_ascii=False) + + # Also update 题目1/题目2 for P4/P5 where explanation was in the text description + t1 = record_fields.get('题目1', '') or '' + t2 = record_fields.get('题目2', '') or '' + new_t1 = t1 + new_t2 = t2 + + # For records where 题目 fields contain answer markers like (正确) + # Add explanation notes to the text fields + has_expl_added = False + first_qs = jd.get('first', {}).get('questionSet', []) + second_qs = jd.get('second', {}).get('questionSet', []) + + for set_idx, (qs, t_field, t_orig) in enumerate([(first_qs, '题目1', t1), (second_qs, '题目2', t2)]): + if not qs or not t_orig: + continue + # Check if this text field already has explanations + if '【解析】' in t_orig: + continue + # Add explanations section + expl_lines = ['\n\n【解析】'] + for qi, q in enumerate(qs): + expl = q.get('explanation', '') + if expl: + expl_lines.append(f'{qi+1}. {expl}') + if len(expl_lines) > 1: + new_text = t_orig + '\n'.join(expl_lines) + if t_field == '题目1': + new_t1 = new_text + else: + new_t2 = new_text + has_expl_added = True + + return new_jd_str, new_t1, new_t2 + +# ===== Main ===== +targets = [ + ('P1','tblCgfYDnnqwLfgH','recvjt0HzBBhYu','032501'), + ('P3','tbl4q0ZUV3HB54t1','recvjug1hWz2oG','032601'), + ('P4','tblzKVm1FEukPgnN','recvjueHm15HPu','032701'), + ('P4','tblzKVm1FEukPgnN','recvjueN5QT1c5','032801'), + ('P5','tblLmUxzzUDe0QAJ','recvjueULrufNg','032901'), +] + +for tname, tid, rid, sid in targets: + # Fetch record + cmd = f'bash {SKILL} list_records {APP_TOKEN} {tid} 500' + data = exec_bash(cmd) + if data.get('code') != 0: + print(f"❌ {sid}: fetch failed") + continue + + fields = None + for item in data['data']['items']: + if item['record_id'] == rid: + fields = item['fields'] + break + if not fields: + print(f"❌ {sid}: record not found") + continue + + new_jd, new_t1, new_t2 = fix_record(tname, tid, rid, sid, fields) + if new_jd is None: + print(f"⏭️ {sid}: no changes needed") + continue + + # Build update payload + update_fields = {'jsonData': new_jd} + if new_t1 != (fields.get('题目1', '') or ''): + update_fields['题目1'] = new_t1 + if new_t2 != (fields.get('题目2', '') or ''): + update_fields['题目2'] = new_t2 + + payload = json.dumps(update_fields, ensure_ascii=False) + cmd = f"bash {SKILL} update_record {APP_TOKEN} {tid} {rid} '{payload}'" + result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=30) + try: + resp = json.loads(result.stdout) + if resp.get('code') == 0: + changes = [] + if 'jsonData' in update_fields: + changes.append('jsonData') + if '题目1' in update_fields: + changes.append('题目1') + if '题目2' in update_fields: + changes.append('题目2') + print(f"✅ {tname} {sid}: updated {', '.join(changes)}") + else: + print(f"❌ {tname} {sid}: API error — {resp}") + except Exception as e: + print(f"❌ {tname} {sid}: {e} — {result.stdout[:200]}") diff --git a/scripts/fix_and_backfill_v2.py b/scripts/fix_and_backfill_v2.py new file mode 100644 index 0000000..de1c2ac --- /dev/null +++ b/scripts/fix_and_backfill_v2.py @@ -0,0 +1,187 @@ +""" +修正能力标签 + 补充解析 v2(Python subprocess curl,避免 shell 转义) +""" +import json, subprocess, os + +APP_TOKEN = 'CMHSbUUjka3TrUsaxxEc297ongf' +# Get token same way as operate_bitable.sh +import io +def get_token(): + cred_file = '/root/.openclaw/credentials/xiaoyan/config.json' + with open(cred_file) as f: + cfg = json.load(f) + app_id = cfg['apps'][0]['appId'] + app_secret = cfg['apps'][0]['appSecret'] + body = json.dumps({"app_id": app_id, "app_secret": app_secret}) + r = subprocess.run(["curl", "-s", "-X", "POST", + "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal", + "-H", "Content-Type: application/json", "-d", body], + capture_output=True, text=True, timeout=10) + return json.loads(r.stdout).get('tenant_access_token', '') + +token = get_token() + +TAG_MAP = { + '信息定位与提取': '扫读定位|信息匹配', + '信息定位': '扫读定位|信息匹配', + '细节理解': '细节理解|事实信息提取', + '因果推断': '推理判断|原因/结果', + '主旨归纳': '主旨理解|段落/文本大意', + '推理判断': '推理判断|原因/结果', +} + +EXPLANATIONS = { + ('032701', 'first', 0): "空白处需要表示赚钱的名词。get some cash 是地道表达,cash(现金)最符合学生售卖手链的情境。cheque(支票)语义过正式,bracelet(手链)是销售的商品而非获取的目标。考查名词在语境中的最佳语义选择。", + ('032701', 'first', 1): "修饰可数名词bracelets应用many(许多)。much修饰不可数名词,only(只有)在此处语义不流畅,不符合带了一些手链去给朋友看的语境。考查可数名词数量修饰词的语法规则。", + ('032701', 'first', 2): "表语位置需形容词。They were very popular(它们很受欢迎)才是完整句子。cash和sell均非形容词,不能直接作表语。考查词性辨别和上下文语义连贯。", + ('032701', 'first', 3): "students是可数名词复数,需用many修饰表示很多学生。much用于不可数名词,only在so many结构后不合适。考查可数名词数量表达。", + ('032701', 'first', 4): "bracelets是可数名词复数,需用many表示数量。didn't have many bracelets表示手链不够多,与后文promised to make more呼应。考查上下文逻辑与可数名词修饰。", + ('032701', 'first', 5): "空白处需动词作谓语。She could sell a few(她能卖出几条)中sell是唯一动词。cheque和popular均为非动词。考查句子主干成分(谓语动词)的语法识别。", + + ('032801', 'first', 0): "根据后文a local band was playing music和it was very noisy,可推断是一场派对。party最能描述这种热闹场景,car和sleep与乐队演奏、噪音等语境不符。考查名词的语境语义选择。", + ('032801', 'first', 1): "I tried to sleep, but the sound was too loud——噪音太大导致睡不着,sleep是最合理的选择。drive和sing都偏离语境。考查动词的语义逻辑匹配。", + ('032801', 'first', 2): "描述派对参与者身份,adult people(成年人)与午夜派对的情境相符,也为后文decided to be more understanding提供合理性。考查修饰词的语境判断。", + ('032801', 'first', 3): "The party went on until midnight——派对持续到午夜是最符合逻辑的时间终点。morning和afternoon时间太早,不符合嘈杂派对的情境。考查时间名词的合理推断。", + ('032801', 'first', 4): "前后分句存在转折关系:I was annoyed because I couldn't rest, but then I remembered...。but引导转折,because表示原因(前后因果不成立),so表示结果。考查连词的逻辑关系。", + ('032801', 'first', 5): "此处需引导原因的连词。because the band members cleaned up解释为什么第二天心情好转。although表示让步(语义不通),until表示时间(不够自然)。考查原因连词的选择。", + + ('032901', 'first', 0): "前后是因果关系:去野生动物园是因为想看小象。because引导原因状语从句最合适。but表示转折,so表示结果,均不符合语义。考查原因连词。", + ('032901', 'first', 1): "had just been born是被动语态,表示刚刚出生。born是bear的过去分词,与a few weeks ago的时间状语呼应。grow up(长大)和top(顶端)语义不通。考查词义辨析与被动语态。", + ('032901', 'first', 2): "reach the top of a tall tree——大象想够到高树的顶端。top表示顶部位置,与tall tree形成语义对应。bottom与reach矛盾,nose不是位置。考查方位词的选择。", + ('032901', 'first', 3): "大象的长鼻子是标志性特征。nose是正确选项,banana和bottom明显不符。考查动物特征相关的核心词汇。", + ('032901', 'first', 4): "could only touch the bottom——虽然用长鼻子去够,但只碰到了底部。bottom与top形成对比,体现够不到的落差感。考查反义词对(top↔bottom)的理解。", + ('032901', 'first', 5): "小象吃黄色的香蕉是最自然的食物搭配。banana是常见动物投喂食物,elephant和nose均不符合吃的语义。考查动物食物相关词汇。", + ('032901', 'first', 6): "when I grow up(等我长大)是固定表达。grow up表示成长、长大,born与when从句时态矛盾,bottom不相关。考查固定短语的掌握。", + ('032901', 'first', 7): "根据上下文描述的美好回忆,bottom指从心底的比喻义。from the bottom of my heart是常见表达。top与情感表达不相配,nose属于干扰项。考查固定搭配和比喻义。", + + ('032901', 'second', 0): "前后分句是因果关系:姐姐喜欢美丽的东西,所以总戴首饰。so引导结果,because引导原因(方向反了),but表示转折(不成立)。考查结果连词。", + ('032901', 'second', 1): "lost one of her favourite silver earrings——在首饰语境中,银色的earrings(耳环)是最典型的可丢失物品。painting和language与silver修饰和wear搭配都不符。考查语境词义推断。", + ('032901', 'second', 2): "couldn't find it anywhere else——在否定句中else表示别的地方/其他任何地方,except含义不符,again重复逻辑不通。考查否定句中的词汇用法。", + ('032901', 'second', 3): "I decided to make..., although I'm not very good——前后存在让步转折关系,虽然不擅长但还是决定做。although正确,because和so均表示因果。考查让步连词。", + ('032901', 'second', 4): "She showed me how to use small silver pieces——朋友擅长的是首饰制作(jewellery),与silver pieces和earrings主题一致。painting和language偏离主题。考查上下文主题关联。", + ('032901', 'second', 5): "I made a mistake, so I had to start again——犯错后重新开始,again表示再一次。else用于否定句表另外,except表除了,都不符合重新来过的语义。考查副词选择。", +} + +def get_fields(tid, rid): + """Get record fields via API""" + url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{tid}/records?page_size=500" + headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + r = subprocess.run(["curl", "-s", url, "-H", headers["Authorization"], "-H", headers["Content-Type"]], + capture_output=True, text=True, timeout=15) + data = json.loads(r.stdout) + if data.get('code') != 0: + return None, data + for item in data['data']['items']: + if item['record_id'] == rid: + return item['fields'], data + return None, data + +def update_record(tid, rid, update_fields): + """Update record via curl""" + url = 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"} + body = json.dumps({"fields": update_fields}, ensure_ascii=False) + r = subprocess.run(["curl", "-s", "-X", "PUT", url, + "-H", headers["Authorization"], "-H", headers["Content-Type"], + "-d", body], + capture_output=True, text=True, timeout=15) + return json.loads(r.stdout) + +def fix_record(sid, fields): + """Fix ability + explanations, return (new_jsonData, new_题目1, new_题目2, changed)""" + jd = json.loads(fields.get('jsonData', '{}')) + changed = False + + for set_key in ['first', 'second']: + qset = jd.get(set_key, {}).get('questionSet', []) + for qi, q in enumerate(qset): + # Fix ability tags + abilities = q.get('ability', []) + new_abilities = [] + ab_changed = False + for a in abilities: + if a in TAG_MAP: + new_abilities.append(TAG_MAP[a]) + ab_changed = True + else: + new_abilities.append(a) + + # For P4/P5: empty ability → add cloze tag + if not new_abilities: + new_abilities = ['语法结构识别|完形填空'] + ab_changed = True + + if ab_changed: + q['ability'] = new_abilities + changed = True + + # Fix explanation + expl_key = (sid, set_key, qi) + if expl_key in EXPLANATIONS: + old_expl = q.get('explanation', '') + if not old_expl or old_expl.strip() == '': + q['explanation'] = EXPLANATIONS[expl_key] + changed = True + + if not changed: + return None, None, None + + new_jd = json.dumps(jd, ensure_ascii=False) + + # Update 题目1/题目2 with explanations + t1 = fields.get('题目1', '') or '' + t2 = fields.get('题目2', '') or '' + new_t1, new_t2 = t1, t2 + + for t_field, t_orig, qs_set_key in [('题目1', t1, 'first'), ('题目2', t2, 'second')]: + qs = jd.get(qs_set_key, {}).get('questionSet', []) + if not qs or not t_orig: + continue + if '【解析】' in t_orig: + continue + expl_lines = [] + for qi, q in enumerate(qs): + expl = q.get('explanation', '') + if expl: + expl_lines.append(f"{qi+1}. {expl}") + if expl_lines: + if t_field == '题目1': + new_t1 = t_orig + '\n\n【解析】\n' + '\n'.join(expl_lines) + else: + new_t2 = t_orig + '\n\n【解析】\n' + '\n'.join(expl_lines) + + return new_jd, new_t1, new_t2 + +# ===== Main ===== +targets = [ + ('P1','tblCgfYDnnqwLfgH','recvjt0HzBBhYu','032501'), + ('P3','tbl4q0ZUV3HB54t1','recvjug1hWz2oG','032601'), + ('P4','tblzKVm1FEukPgnN','recvjueHm15HPu','032701'), + ('P4','tblzKVm1FEukPgnN','recvjueN5QT1c5','032801'), + ('P5','tblLmUxzzUDe0QAJ','recvjueULrufNg','032901'), +] + +for tname, tid, rid, sid in targets: + fields, _ = get_fields(tid, rid) + if not fields: + print(f"❌ {tname} {sid}: fetch failed") + continue + + new_jd, new_t1, new_t2 = fix_record(sid, fields) + if new_jd is None: + print(f"⏭️ {tname} {sid}: no changes needed (already fixed)") + continue + + # Build update payload + update_fields = {'jsonData': new_jd} + if new_t1 != (fields.get('题目1', '') or ''): + update_fields['题目1'] = new_t1 + if new_t2 != (fields.get('题目2', '') or ''): + update_fields['题目2'] = new_t2 + + changed_keys = list(update_fields.keys()) + resp = update_record(tid, rid, update_fields) + if resp.get('code') == 0: + print(f"✅ {tname} {sid}: updated {', '.join(changed_keys)}") + else: + print(f"❌ {tname} {sid}: code={resp.get('code')} msg={resp.get('msg','?')[:100]}") diff --git a/scripts/fix_and_backfill_v3.py b/scripts/fix_and_backfill_v3.py new file mode 100644 index 0000000..537cd83 --- /dev/null +++ b/scripts/fix_and_backfill_v3.py @@ -0,0 +1,175 @@ +""" +修正能力标签 + 补充解析 v3 — 使用 shell=True curl(已验证可行) +""" +import json, subprocess, os, shlex + +APP_TOKEN = 'CMHSbUUjka3TrUsaxxEc297ongf' + +def get_token(): + cred_file = '/root/.openclaw/credentials/xiaoyan/config.json' + with open(cred_file) as f: + cfg = json.load(f) + app_id = cfg['apps'][0]['appId'] + app_secret = cfg['apps'][0]['appSecret'] + body = json.dumps({'app_id': app_id, 'app_secret': app_secret}) + cmd = f"curl -s -X POST 'https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal' -H 'Content-Type: application/json' -d '{body}'" + r = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=10) + return json.loads(r.stdout).get('tenant_access_token', '') + +def api_get(token, url): + cmd = f"curl -s -X GET '{url}' -H 'Authorization: Bearer {token}'" + r = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=15) + return json.loads(r.stdout) + +def api_put(token, url, body): + """PUT request with JSON body, using temp file for large payloads""" + tmpfile = '/tmp/bitable_update.json' + with open(tmpfile, 'w') as f: + json.dump(body, f, ensure_ascii=False) + cmd = f"curl -s -X PUT '{url}' -H 'Authorization: Bearer {token}' -H 'Content-Type: application/json' -d @{tmpfile}" + r = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=15) + return json.loads(r.stdout) + +TAG_MAP = { + '信息定位与提取': '扫读定位|信息匹配', + '信息定位': '扫读定位|信息匹配', + '细节理解': '细节理解|事实信息提取', + '因果推断': '推理判断|原因/结果', + '主旨归纳': '主旨理解|段落/文本大意', + '推理判断': '推理判断|原因/结果', +} + +EXPLANATIONS = { + ('032701', 'first', 0): "空白处需要表示赚钱的名词。get some cash 是地道表达,cash(现金)最符合学生售卖手链的情境。cheque(支票)语义过正式,bracelet(手链)是销售的商品而非获取的目标。考查名词在语境中的最佳语义选择。", + ('032701', 'first', 1): "修饰可数名词bracelets应用many(许多)。much修饰不可数名词,only(只有)在此处语义不流畅,不符合带了一些手链去给朋友看的语境。考查可数名词数量修饰词的语法规则。", + ('032701', 'first', 2): "表语位置需形容词。They were very popular(它们很受欢迎)才是完整句子。cash和sell均非形容词,不能直接作表语。考查词性辨别和上下文语义连贯。", + ('032701', 'first', 3): "students是可数名词复数,需用many修饰表示很多学生。much用于不可数名词,only在so many结构后不合适。考查可数名词数量表达。", + ('032701', 'first', 4): "bracelets是可数名词复数,需用many表示数量。didn't have many bracelets表示手链不够多,与后文promised to make more呼应。考查上下文逻辑与可数名词修饰。", + ('032701', 'first', 5): "空白处需动词作谓语。She could sell a few(她能卖出几条)中sell是唯一动词。cheque和popular均为非动词。考查句子主干成分(谓语动词)的语法识别。", + ('032801', 'first', 0): "根据后文a local band was playing music和it was very noisy,可推断是一场派对。party最能描述这种热闹场景,car和sleep与乐队演奏、噪音等语境不符。考查名词的语境语义选择。", + ('032801', 'first', 1): "I tried to sleep, but the sound was too loud——噪音太大导致睡不着,sleep是最合理的选择。drive和sing都偏离语境。考查动词的语义逻辑匹配。", + ('032801', 'first', 2): "描述派对参与者身份,adult people(成年人)与午夜派对的情境相符,也为后文decided to be more understanding提供合理性。考查修饰词的语境判断。", + ('032801', 'first', 3): "The party went on until midnight——派对持续到午夜是最符合逻辑的时间终点。morning和afternoon时间太早,不符合嘈杂派对的情境。考查时间名词的合理推断。", + ('032801', 'first', 4): "前后分句存在转折关系:I was annoyed because I couldn't rest, but then I remembered...。but引导转折,because表示原因(前后因果不成立),so表示结果。考查连词的逻辑关系。", + ('032801', 'first', 5): "此处需引导原因的连词。because the band members cleaned up解释为什么第二天心情好转。although表示让步(语义不通),until表示时间(不够自然)。考查原因连词的选择。", + ('032901', 'first', 0): "前后是因果关系:去野生动物园是因为想看小象。because引导原因状语从句最合适。but表示转折,so表示结果,均不符合语义。考查原因连词。", + ('032901', 'first', 1): "had just been born是被动语态,表示刚刚出生。born是bear的过去分词,与a few weeks ago的时间状语呼应。grow up(长大)和top(顶端)语义不通。考查词义辨析与被动语态。", + ('032901', 'first', 2): "reach the top of a tall tree——大象想够到高树的顶端。top表示顶部位置,与tall tree形成语义对应。bottom与reach矛盾,nose不是位置。考查方位词的选择。", + ('032901', 'first', 3): "大象的长鼻子是标志性特征。nose是正确选项,banana和bottom明显不符。考查动物特征相关的核心词汇。", + ('032901', 'first', 4): "could only touch the bottom——虽然用长鼻子去够,但只碰到了底部。bottom与top形成对比,体现够不到的落差感。考查反义词对(top↔bottom)的理解。", + ('032901', 'first', 5): "小象吃黄色的香蕉是最自然的食物搭配。banana是常见动物投喂食物,elephant和nose均不符合吃的语义。考查动物食物相关词汇。", + ('032901', 'first', 6): "when I grow up(等我长大)是固定表达。grow up表示成长、长大,born与when从句时态矛盾,bottom不相关。考查固定短语的掌握。", + ('032901', 'first', 7): "根据上下文描述的美好回忆,bottom指从心底的比喻义。from the bottom of my heart是常见表达。top与情感表达不相配,nose属于干扰项。考查固定搭配和比喻义。", + ('032901', 'second', 0): "前后分句是因果关系:姐姐喜欢美丽的东西,所以总戴首饰。so引导结果,because引导原因(方向反了),but表示转折(不成立)。考查结果连词。", + ('032901', 'second', 1): "lost one of her favourite silver earrings——在首饰语境中,银色的earrings(耳环)是最典型的可丢失物品。painting和language与silver修饰和wear搭配都不符。考查语境词义推断。", + ('032901', 'second', 2): "couldn't find it anywhere else——在否定句中else表示别的地方/其他任何地方,except含义不符,again重复逻辑不通。考查否定句中的词汇用法。", + ('032901', 'second', 3): "I decided to make..., although I'm not very good——前后存在让步转折关系,虽然不擅长但还是决定做。although正确,because和so均表示因果。考查让步连词。", + ('032901', 'second', 4): "She showed me how to use small silver pieces——朋友擅长的是首饰制作(jewellery),与silver pieces和earrings主题一致。painting和language偏离主题。考查上下文主题关联。", + ('032901', 'second', 5): "I made a mistake, so I had to start again——犯错后重新开始,again表示再一次。else用于否定句表另外,except表除了,都不符合重新来过的语义。考查副词选择。", +} + +def fix_record(sid, fields): + """Fix ability + explanations, return (new_jsonData, new_题目1, new_题目2) or None""" + jd = json.loads(fields.get('jsonData', '{}')) + changed = False + + for set_key in ['first', 'second']: + qset = jd.get(set_key, {}).get('questionSet', []) + for qi, q in enumerate(qset): + abilities = q.get('ability', []) + new_abilities = [] + ab_changed = False + for a in abilities: + if a in TAG_MAP: + new_abilities.append(TAG_MAP[a]) + ab_changed = True + else: + new_abilities.append(a) + + if not new_abilities: + new_abilities = ['语法结构识别|完形填空'] + ab_changed = True + + if ab_changed: + q['ability'] = new_abilities + changed = True + + expl_key = (sid, set_key, qi) + if expl_key in EXPLANATIONS: + old_expl = q.get('explanation', '') + if not old_expl or old_expl.strip() == '': + q['explanation'] = EXPLANATIONS[expl_key] + changed = True + + if not changed: + return None, None, None + + new_jd = json.dumps(jd, ensure_ascii=False) + + t1 = fields.get('题目1', '') or '' + t2 = fields.get('题目2', '') or '' + new_t1, new_t2 = t1, t2 + + for t_field, t_orig, qs_set_key in [('题目1', t1, 'first'), ('题目2', t2, 'second')]: + qs = jd.get(qs_set_key, {}).get('questionSet', []) + if not qs or not t_orig or '【解析】' in t_orig: + continue + expl_lines = [f"{qi+1}. {q.get('explanation','')}" for qi, q in enumerate(qs) if q.get('explanation','')] + if expl_lines: + if t_field == '题目1': + new_t1 = t_orig + '\n\n【解析】\n' + '\n'.join(expl_lines) + else: + new_t2 = t_orig + '\n\n【解析】\n' + '\n'.join(expl_lines) + + return new_jd, new_t1, new_t2 + +# ===== Main ===== +token = get_token() +if not token: + print("❌ Failed to get token") + exit(1) + +targets = [ + ('P1','tblCgfYDnnqwLfgH','recvjt0HzBBhYu','032501'), + ('P3','tbl4q0ZUV3HB54t1','recvjug1hWz2oG','032601'), + ('P4','tblzKVm1FEukPgnN','recvjueHm15HPu','032701'), + ('P4','tblzKVm1FEukPgnN','recvjueN5QT1c5','032801'), + ('P5','tblLmUxzzUDe0QAJ','recvjueULrufNg','032901'), +] + +for tname, tid, rid, sid in targets: + # Fetch + url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{tid}/records?page_size=500" + data = api_get(token, url) + if data.get('code') != 0: + print(f"❌ {tname} {sid}: fetch error {data.get('code')} {data.get('msg','')[:60]}") + continue + + fields = None + for item in data['data']['items']: + if item['record_id'] == rid: + fields = item['fields'] + break + if not fields: + print(f"❌ {tname} {sid}: record not found") + continue + + new_jd, new_t1, new_t2 = fix_record(sid, fields) + if new_jd is None: + print(f"⏭️ {tname} {sid}: already fixed, no changes") + continue + + # Update + update_fields = {'jsonData': new_jd} + if new_t1 != (fields.get('题目1', '') or ''): + update_fields['题目1'] = new_t1 + if new_t2 != (fields.get('题目2', '') or ''): + update_fields['题目2'] = new_t2 + + changed_keys = list(update_fields.keys()) + url2 = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{tid}/records/{rid}" + resp = api_put(token, url2, {"fields": update_fields}) + if resp.get('code') == 0: + print(f"✅ {tname} {sid}: updated {', '.join(changed_keys)}") + else: + print(f"❌ {tname} {sid}: code={resp.get('code')} msg={resp.get('msg','?')[:100]}") diff --git a/scripts/fix_json_and_p5p6.py b/scripts/fix_json_and_p5p6.py new file mode 100644 index 0000000..306fede --- /dev/null +++ b/scripts/fix_json_and_p5p6.py @@ -0,0 +1,332 @@ +""" +修复 5 条 JSON 解析失败记录 + P5/P6 能力标签修正 + 回填 +""" +import json, urllib.request, re, sys + +APP_TOKEN = "CMHSbUUjka3TrUsaxxEc297ongf" +CRED_FILE = "/root/.openclaw/credentials/xiaoyan/config.json" + +def get_token(): + with open(CRED_FILE) as f: + cfg = json.load(f) + app_id = cfg['apps'][0]['appId'] + app_secret = cfg['apps'][0]['appSecret'] + req = urllib.request.Request( + "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal", + data=json.dumps({"app_id": app_id, "app_secret": app_secret}).encode(), + headers={"Content-Type": "application/json"}) + return json.loads(urllib.request.urlopen(req).read())['tenant_access_token'] + +def api_call(url, method='GET', body=None): + token = get_token() + headers = {"Authorization": f"Bearer {token}"} + data = json.dumps(body).encode() if body else None + if data: + headers["Content-Type"] = "application/json" + req = urllib.request.Request(url, data=data, method=method, headers=headers) + return json.loads(urllib.request.urlopen(req).read()) + +def update_record(table_id, record_id, fields): + url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{table_id}/records/{record_id}" + return api_call(url, 'PUT', {"fields": fields}) + +def fix_json_str(jd_str): + """Attempt to fix a broken JSON string.""" + # Try direct parse first + try: + jd = json.loads(jd_str) + return jd, "already_valid" + except json.JSONDecodeError as e: + pass + + # Strategy: try raw_decode to find where valid JSON ends + decoder = json.JSONDecoder() + # Try multiple positions - walk back from error to find fix point + + # Common fix 1: Insert missing closing bracket(s) + # Check bracket balance + open_brackets = jd_str.count('[') - jd_str.count(']') + open_braces = jd_str.count('{') - jd_str.count('}') + + if open_brackets > 0 or open_braces > 0: + fixed = jd_str.rstrip() + # Remove trailing chars that might be wrong + fixed = fixed.rstrip('}') + fixed += ']' * open_brackets + '}' * open_braces + try: + jd = json.loads(fixed) + return jd, "bracket_balance_fix" + except: + pass + + # Common fix 2: Unescaped quotes in explanation (P4_021301) + # Find all "explanation":"..." patterns and fix internal quotes + # Use regex to find explanation values containing broken quotes + def fix_expl_quotes(s): + """Replace Chinese-context double quotes inside explanations with special chars""" + # Find: ...提到"text text." pattern inside explanation values + # The quote after 提到/说/告诉 etc are typical Chinese quotation + result = [] + i = 0 + in_explanation = False + expl_start = -1 + while i < len(s): + # Detect start of explanation value + if not in_explanation and s[i:i+15] == '"explanation":"': + in_explanation = True + expl_start = i + 15 # after '"explanation":"' + result.append(s[i:i+15]) + i += 15 + continue + + if in_explanation: + # Walk until we find the closing quote of explanation + if s[i] == '"' and (i == 0 or s[i-1] != '\\'): + # Check if this is closing quote (followed by , or } or ]) + j = i + 1 + while j < len(s) and s[j] in ' \t': + j += 1 + if j < len(s) and s[j] in ',}]': + result.append(s[expl_start:i]) + result.append('"') + in_explanation = False + i += 1 + expl_start = -1 + continue + # Not closing quote - replace with ' + result.append(s[expl_start:i]) + result.append("'") + expl_start = i + 1 + i += 1 + continue + + result.append(s[i]) + i += 1 + + if in_explanation: + result.append(s[expl_start:]) + + return ''.join(result) + + fixed = fix_expl_quotes(jd_str) + try: + jd = json.loads(fixed) + return jd, "quote_fix" + except: + pass + + # Common fix 3: Replace problematic characters + replacements = [ + ('\u201c', "'"), ('\u201d', "'"), # smart quotes + ('\uff0c', ','), ('\uff1a', ':'), # fullwidth punctuation + ('\u2019', "'"), # right single quote + ] + fixed = jd_str + for old, new in replacements: + fixed = fixed.replace(old, new) + try: + jd = json.loads(fixed) + return jd, "char_replace" + except: + pass + + return None, "unfixable: all strategies exhausted" + +def fix_ability_tags(jd): + """Fix ability tags in jsonData - check all levels""" + changed = False + + for section in ['first', 'second']: + sect = jd.get(section, {}) + if not sect: + continue + + # Fix section-level ability (P5 style: first.ability) + if 'ability' in sect and isinstance(sect['ability'], list): + new_ab = [] + for a in sect['ability']: + if a == '听觉抓取关键信息': + new_ab.append('显性事实理解|关键词识别') + elif a == '多特征整合': + new_ab.append('多句保持|信息整合') + else: + new_ab.append(a) + if new_ab != sect['ability']: + sect['ability'] = new_ab + changed = True + + # Fix question-level ability (first.questionSet[i].ability) + qs = sect.get('questionSet', []) + for q in qs: + if 'ability' in q and isinstance(q['ability'], list): + new_ab = [] + for a in q['ability']: + if a == '听觉抓取关键信息': + new_ab.append('显性事实理解|关键词识别') + elif a == '多特征整合': + new_ab.append('多句保持|信息整合') + else: + new_ab.append(a) + if new_ab != q['ability']: + q['ability'] = new_ab + changed = True + + # Fix root questionSet ability (P6 style) + qs = jd.get('questionSet', []) + for q in qs: + if 'ability' in q and isinstance(q['ability'], list): + new_ab = [] + for a in q['ability']: + if a == '听觉抓取关键信息': + new_ab.append('显性事实理解|关键词识别') + elif a == '多特征整合': + new_ab.append('多句保持|信息整合') + else: + new_ab.append(a) + if new_ab != q['ability']: + q['ability'] = new_ab + changed = True + + return jd, changed + + +# ===== STEP 1: Fix 5 broken JSON records ===== +print("=" * 60) +print("STEP 1: Fixing 5 broken JSON records") +print("=" * 60) + +broken = { + "021901": ("tblzTLNH7f13uWQN", "P2"), + "022301": ("tblzTLNH7f13uWQN", "P2"), + "021301": ("tblVmeDtBDKsAEfz", "P4"), + "021601": ("tblVmeDtBDKsAEfz", "P4"), + "021801": ("tblVmeDtBDKsAEfz", "P4"), +} + +for sid, (table_id, label) in broken.items(): + url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{table_id}/records?page_size=50" + resp = api_call(url) + for item in resp['data']['items']: + if item['fields'].get('题目集合 ID', '') == sid: + rid = item['record_id'] + jd_str = item['fields']['jsonData'] + + jd, msg = fix_json_str(jd_str) + if jd is None: + print(f" ❌ {label} {sid}: {msg}") + continue + + # Also fix ability tags + jd, ab_changed = fix_ability_tags(jd) + + new_jd = json.dumps(jd, ensure_ascii=False) + result = update_record(table_id, rid, {"jsonData": new_jd}) + + # Verify + url2 = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{table_id}/records/{rid}" + v_resp = api_call(url2) + v_jd = v_resp['data']['items'][0]['fields'].get('jsonData', '') + try: + json.loads(v_jd) + detail = f"json={msg}, ab={'fixed' if ab_changed else 'ok'}" + print(f" ✅ {label} {sid}: {detail}") + except json.JSONDecodeError as e: + print(f" ⚠️ {label} {sid}: written but re-verify failed: {e}") + break + +# ===== STEP 2: Fix P5 032801 ===== +print(f"\n{'='*60}") +print("STEP 2: Fix P5 032801") +print("=" * 60) + +url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/tblDssVmhGzc3UKd/records?page_size=50" +resp = api_call(url) +for item in resp['data']['items']: + sid = item['fields'].get('题目集合 ID', '') + if sid == '032801': + rid = item['record_id'] + jd_str = item['fields']['jsonData'] + + try: + jd = json.loads(jd_str) + except: + jd, _ = fix_json_str(jd_str) + + if jd is None: + print(f" ❌ P5 {sid}: JSON parse failed") + break + + jd, ab_changed = fix_ability_tags(jd) + if not ab_changed: + print(f" ⏭️ P5 {sid}: no ability tag changes needed") + else: + new_jd = json.dumps(jd, ensure_ascii=False) + result = update_record("tblDssVmhGzc3UKd", rid, {"jsonData": new_jd}) + print(f" {'✅' if result.get('code')==0 else '❌'} P5 {sid}: ability tags updated") + break + +# ===== STEP 3: Fix P6 records ===== +print(f"\n{'='*60}") +print("STEP 3: Fix P6 records (add ability tags)") +print("=" * 60) + +url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/tbloiMcD0sBtGSTq/records?page_size=50" +resp = api_call(url) +for item in resp['data']['items']: + sid = item['fields'].get('题目集合 ID', '') + if '010199' in str(sid): + continue + + jd_str = item['fields'].get('jsonData', '{}') + if not jd_str or jd_str == 'None': + continue + + try: + jd = json.loads(jd_str) + except: + jd, _ = fix_json_str(jd_str) + + if jd is None: + print(f" ❌ P6 {sid}: JSON parse failed") + continue + + # P6 has root-level questionSet + qs = jd.get('questionSet', []) + changed = False + for q in qs: + if not q.get('ability') or len(q.get('ability', [])) == 0: + # Assign default based on question content + question = q.get('question', '') + answer = q.get('answer', []) + + # Determine ability tag + if any(w in question.lower() for w in ['where', 'location', 'place']): + tag = '显性细节理解|数字/时间/地点' + elif any(w in question.lower() for w in ['how many', 'how much', 'number']): + tag = '显性细节理解|数字/时间/地点' + elif any(w in question.lower() for w in ['what color', 'which one']): + tag = '显性事实理解|关键词识别' + elif any(w in question.lower() for w in ['why', 'because']): + tag = '情绪/态度理解' + elif any(w in question.lower() for w in ['like', 'love', 'want']): + tag = '目的/偏好识别|显性 to/for/like' + else: + tag = '显性事实理解|关键词识别' + + q['ability'] = [tag] + changed = True + + if changed: + new_jd = json.dumps(jd, ensure_ascii=False) + result = update_record("tbloiMcD0sBtGSTq", item['record_id'], {"jsonData": new_jd}) + if result.get('code') == 0: + tags_assigned = [q.get('ability', []) for q in jd.get('questionSet', [])] + print(f" ✅ P6 {sid}: ability filled ({[t[0] for t in tags_assigned if t]})") + else: + print(f" ❌ P6 {sid}: update failed - {result.get('msg')}") + else: + print(f" ⏭️ P6 {sid}: no changes needed") + +print(f"\n{'='*60}") +print("All fixes complete!") +print("=" * 60) diff --git a/scripts/fix_remaining.py b/scripts/fix_remaining.py new file mode 100644 index 0000000..53c2a3e --- /dev/null +++ b/scripts/fix_remaining.py @@ -0,0 +1,138 @@ +""" +Fix remaining records: P5 ability tag, P6 ability tags, JSON fixes for 5 broken records +""" +import json, urllib.request, re + +APP_TOKEN = "CMHSbUUjka3TrUsaxxEc297ongf" +CRED_FILE = "/root/.openclaw/credentials/xiaoyan/config.json" + +STD_HEARING_TAGS = { + "显性事实理解|关键词识别", "显性事实理解|单句信息点抓取", + "基础语境理解|场景/物品/动作识别", "显性细节理解|数字/时间/地点", + "同义替换识别|词/短语级", "目的/偏好识别|显性 to/for/like", + "干扰抑制|多信息筛选", "语用推断|否定与纠错", + "多句保持|信息整合", "情绪/态度理解", "长对话理解|主旨+细节", +} + +# Tag mapping for section-level ability (P5 style) +SECTION_TAG_MAP = { + "听觉抓取关键信息": "显性事实理解|关键词识别", +} + +def get_token(): + with open(CRED_FILE) as f: + cfg = json.load(f) + app_id = cfg['apps'][0]['appId'] + app_secret = cfg['apps'][0]['appSecret'] + req = urllib.request.Request( + "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal", + data=json.dumps({"app_id": app_id, "app_secret": app_secret}).encode(), + headers={"Content-Type": "application/json"}) + return json.loads(urllib.request.urlopen(req).read())['tenant_access_token'] + +def api_call(url, method='GET', body=None): + token = get_token() + headers = {"Authorization": f"Bearer {token}"} + data = json.dumps(body).encode() if body else None + if data: + headers["Content-Type"] = "application/json" + req = urllib.request.Request(url, data=data, method=method, headers=headers) + return json.loads(urllib.request.urlopen(req).read()) + +def update_record(table_id, record_id, fields): + url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{table_id}/records/{record_id}" + return api_call(url, 'PUT', {"fields": fields}) + +# 1. Fix P5 032801 - update section-level ability +print("=== P5 032801 ===") +url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/tblDssVmhGzc3UKd/records?page_size=50" +resp = api_call(url) +for item in resp['data']['items']: + sid = item['fields'].get('题目集合 ID', '') + if sid == '032801': + rid = item['record_id'] + jd = json.loads(item['fields']['jsonData']) + old_ability = jd.get('first', {}).get('ability', []) + new_ability = [SECTION_TAG_MAP.get(a, a) for a in old_ability] + if new_ability != old_ability: + jd['first']['ability'] = new_ability + # Also fix per-question ability if it exists + qs = jd.get('first', {}).get('questionSet', []) + for q in qs: + if 'ability' in q: + old = q['ability'] + if isinstance(old, list): + q['ability'] = [SECTION_TAG_MAP.get(a, a) for a in old] + new_jd = json.dumps(jd, ensure_ascii=False) + result = update_record("tblDssVmhGzc3UKd", rid, {"jsonData": new_jd}) + print(f" Updated: {result.get('code')}") + else: + print(f" No change needed") + break + +# 2. Fix P6 records - add ability tags based on question content +print("\n=== P6 ===") +url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/tbloiMcD0sBtGSTq/records?page_size=50" +resp = api_call(url) +for item in resp['data']['items']: + sid = item['fields'].get('题目集合 ID', '') + if '010199' in str(sid): + continue + jd_str = item['fields'].get('jsonData', '{}') + if not jd_str or jd_str == 'None': + continue + jd = json.loads(jd_str) + qs = jd.get('questionSet', []) + changed = False + for q in qs: + if not q.get('ability'): + q['ability'] = ["显性事实理解|关键词识别"] # Default for P6 listening_choosePic + changed = True + if changed: + new_jd = json.dumps(jd, ensure_ascii=False) + result = update_record("tbloiMcD0sBtGSTq", item['record_id'], {"jsonData": new_jd}) + print(f" ID={sid}: {'✅' if result.get('code')==0 else '❌'} ability filled") + +# 3. Fix 5 broken JSON records +print("\n=== Fixing broken JSONs ===") +broken_mapping = { + "P2_021901": {"table_id": "tblzTLNH7f13uWQN", "sid": "021901"}, + "P2_022301": {"table_id": "tblzTLNH7f13uWQN", "sid": "022301"}, + "P4_021301": {"table_id": "tblVmeDtBDKsAEfz", "sid": "021301"}, + "P4_021601": {"table_id": "tblVmeDtBDKsAEfz", "sid": "021601"}, + "P4_021801": {"table_id": "tblVmeDtBDKsAEfz", "sid": "021801"}, +} + +for key, info in broken_mapping.items(): + with open(f'/tmp/fixed_{key}.txt') as f: + fixed_jd = f.read() + + # Update ability tags in fixed JSON too + jd = json.loads(fixed_jd) + changed = False + for section in ['first', 'second']: + sect = jd.get(section, {}) + if not sect: + continue + qs = sect.get('questionSet', []) + for q in qs: + old = q.get('ability', []) + if isinstance(old, list): + new = [SECTION_TAG_MAP.get(a, a) for a in old] + if new != old: + q['ability'] = new + changed = True + if changed: + fixed_jd = json.dumps(jd, ensure_ascii=False) + + # Find record_id + url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{info['table_id']}/records?page_size=50" + resp = api_call(url) + for item in resp['data']['items']: + if item['fields'].get('题目集合 ID', '') == info['sid']: + rid = item['record_id'] + result = update_record(info['table_id'], rid, {"jsonData": fixed_jd}) + print(f" {key}: {'✅' if result.get('code')==0 else '❌'} fixed+tags") + break + +print("\nDone!") diff --git a/scripts/gen_unit_challenge_spk_wrt.py b/scripts/gen_unit_challenge_spk_wrt.py new file mode 100644 index 0000000..2f6eb14 --- /dev/null +++ b/scripts/gen_unit_challenge_spk_wrt.py @@ -0,0 +1,574 @@ +""" +单元挑战 口语写作内容生产:10条记录(写作P1×4, 口语P1×4, 写作P2×1, 口语P2×1) +""" +import json, urllib.request + +APP_TOKEN = "CMHSbUUjka3TrUsaxxEc297ongf" +CRED_FILE = "/root/.openclaw/credentials/xiaoyan/config.json" + +TABLES = { + "writing_email": "tblszuk1TeToofBF", + "writing_picWrite": "tblSAwlMumKoyjws", + "speaking_qa": "tblRGv7k4WH58Jgq", + "speaking_topic": "tblGoWYBmVI0IrvQ", +} + +RECORDS = [] + +# ==================================================================== +# 写作-P1-011301: We need to..., Let me..., It's on fire! +# ==================================================================== +RECORDS.append({ + "table": "writing_email", "sid": "011301", "type": "写作-P1", + "json": { + "first": { + "category": "writing", + "type": "writing_email", + "questionSetID": "011301", + "textDesc": "Your friend Mark saw smoke coming from a building. Write an email to help him know what to do.
Put these sentences in order to make a telling email.", + "optionSetList": [ + "Dear Mark,", + "We need to call for help at once.", + "Let me check if anyone is still inside.", + "It's on fire! Call 119 right away.", + "Please stay safe and don't go too near the building.", + "Best wishes, Your Friend" + ], + "answerSet": [0, 1, 2, 3, 4, 5], + "ability": ["短消息写作|邮件/便条", "衔接与连贯|连词使用"], + "explanation": "根据事件发生的时间逻辑排列句子顺序:先确认情况紧急,再行动呼救,确认安全后表达关心。此题考察邮件的格式规范和事件叙述的逻辑顺序。" + }, + "second": {} + }, + "t1": """【题目描述】 +Your friend Mark saw smoke coming from a building. Write an email to help him know what to do. +Put these sentences in order to make a telling email. + +【段落列表】 +A. Dear Mark, +B. We need to call for help at once. +C. Let me check if anyone is still inside. +D. It's on fire! Call 119 right away. +E. Please stay safe and don't go too near the building. +F. Best wishes, Your Friend + +【答案】 +A, D, B, C, E, F + +【解析】 +根据事件发生的时间逻辑排列句子顺序:先确认情况紧急,再行动呼救,确认安全后表达关心。此题考察邮件的格式规范和事件叙述的逻辑顺序。 + +【能力项】 +短消息写作|邮件/便条,衔接与连贯|连词使用""", + "t2": "" +}) + +# ==================================================================== +# 写作-P1-011401: ...promise..., We can't..., ...can take care of... +# ==================================================================== +RECORDS.append({ + "table": "writing_email", "sid": "011401", "type": "写作-P1", + "json": { + "first": { + "category": "writing", + "type": "writing_email", + "questionSetID": "011401", + "textDesc": "Your friend Lily needs someone to take care of her puppy while she is away. Write an email to promise her you can help.
Put these sentences in order to make a proper reply.", + "optionSetList": [ + "Dear Lily,", + "I promise I will take good care of your puppy.", + "We can't let the little one stay alone at home.", + "My mom can take care of feeding time every morning.", + "I will walk the puppy after school each day.", + "Looking forward to seeing you! Best, Your Friend" + ], + "answerSet": [0, 1, 2, 3, 4, 5], + "ability": ["短消息写作|邮件/便条", "衔接与连贯|连词使用"], + "explanation": "按照邮件格式:问候→表达承诺→说明不能放任→描述照顾计划→结尾。考察日常承诺和安排说明的书面表达能力。" + }, + "second": {} + }, + "t1": """【题目描述】 +Your friend Lily needs someone to take care of her puppy while she is away. Write an email to promise her you can help. +Put these sentences in order to make a proper reply. + +【段落列表】 +A. Dear Lily, +B. I promise I will take good care of your puppy. +C. We can't let the little one stay alone at home. +D. My mom can take care of feeding time every morning. +E. I will walk the puppy after school each day. +F. Looking forward to seeing you! Best, Your Friend + +【答案】 +A, B, C, D, E, F + +【解析】 +按照邮件格式:问候→表达承诺→说明不能放任→描述照顾计划→结尾。考察日常承诺和安排说明的书面表达能力。 + +【能力项】 +短消息写作|邮件/便条,衔接与连贯|连词使用""", + "t2": "" +}) + +# ==================================================================== +# 写作-P1-011501: How can we...?, I think I found...!, This must be a... +# ==================================================================== +RECORDS.append({ + "table": "writing_email", "sid": "011501", "type": "写作-P1", + "json": { + "first": { + "category": "writing", + "type": "writing_email", + "questionSetID": "011501", + "textDesc": "You and your friend are planning a surprise party for your teacher. Write an email to discuss the plan.
Put these sentences in order to make a clear message.", + "optionSetList": [ + "Dear Friend,", + "How can we make the surprise party really special?", + "I think I found a perfect gift at the shop near school!", + "This must be a secret, so don't tell anyone else.", + "Let's meet after school tomorrow to talk more.", + "Can't wait for the big day! Best, Your Friend" + ], + "answerSet": [0, 1, 2, 3, 4, 5], + "ability": ["短消息写作|邮件/便条", "衔接与连贯|连词使用"], + "explanation": "按照计划讨论的逻辑:问候→提问如何做→分享发现→提醒保密→约定讨论时间→表达期待。考察疑问句和计划表达在书面语中的应用。" + }, + "second": {} + }, + "t1": """【题目描述】 +You and your friend are planning a surprise party for your teacher. Write an email to discuss the plan. +Put these sentences in order to make a clear message. + +【段落列表】 +A. Dear Friend, +B. How can we make the surprise party really special? +C. I think I found a perfect gift at the shop near school! +D. This must be a secret, so don't tell anyone else. +E. Let's meet after school tomorrow to talk more. +F. Can't wait for the big day! Best, Your Friend + +【答案】 +A, B, C, D, E, F + +【解析】 +按照计划讨论的逻辑:问候→提问如何做→分享发现→提醒保密→约定讨论时间→表达期待。考察疑问句和计划表达在书面语中的应用。 + +【能力项】 +短消息写作|邮件/便条,衔接与连贯|连词使用""", + "t2": "" +}) + +# ==================================================================== +# 写作-P1-011701: Because it's..., We should talk to... right now., It tells the story of... a long time ago. +# ==================================================================== +RECORDS.append({ + "table": "writing_email", "sid": "011701", "type": "写作-P1", + "json": { + "first": { + "category": "writing", + "type": "writing_email", + "questionSetID": "011701", + "textDesc": "Your friend Sam wants to know about a story you made. Write an email to tell him.
Put these sentences in order to make a complete description.", + "optionSetList": [ + "Dear Sam,", + "I'm so glad you asked about my story!", + "Because it's about a brave little dragon named Spark.", + "We should talk to the teacher right now about showing it to our class.", + "It tells the story of Spark finding a lost treasure a long time ago.", + "Looking forward to hearing from you! Best, Your Friend" + ], + "answerSet": [0, 1, 2, 3, 4, 5], + "ability": ["短消息写作|邮件/便条", "衔接与连贯|连词使用"], + "explanation": "按照邮件和叙述逻辑:问候→表达高兴→说明原因→提议后续行动→补充故事内容→结尾。考察because引导的原因句和故事背景描述能力。" + }, + "second": {} + }, + "t1": """【题目描述】 +Your friend Sam wants to know about a story you made. Write an email to tell him. +Put these sentences in order to make a complete description. + +【段落列表】 +A. Dear Sam, +B. I'm so glad you asked about my story! +C. Because it's about a brave little dragon named Spark. +D. We should talk to the teacher right now about showing it to our class. +E. It tells the story of Spark finding a lost treasure a long time ago. +F. Looking forward to hearing from you! Best, Your Friend + +【答案】 +A, B, C, D, E, F + +【解析】 +按照邮件和叙述逻辑:问候→表达高兴→说明原因→提议后续行动→补充故事内容→结尾。考察because引导的原因句和故事背景描述能力。 + +【能力项】 +短消息写作|邮件/便条,衔接与连贯|连词使用""", + "t2": "" +}) + +# ==================================================================== +# 口语-P1-011301: He looks like..., You are so..., I am good at... +# ==================================================================== +RECORDS.append({ + "table": "speaking_qa", "sid": "011301", "type": "口语-P1", + "json": { + "first": { + "category": "speaking", + "type": "speaking_qa", + "asrPrompt": "best,friend,look,like,tall,kind,good,at,drawing,running", + "questionSetID": "011301", + "textDesc": "Answer these questions about your friends and yourself. Use full English sentences.", + "questionSet": [ + { + "content": "What does your best friend look like?", + "ability": ["基础信息表达|个人信息问答"], + "explanation": "你可以用"He looks like..."或"She looks like..."来描述朋友的外貌特征,例如 tall、short、funny 等。试着说出2-3个特征,让描述更完整。" + }, + { + "content": "How would you describe your dad or mom?", + "ability": ["表达喜好与理由"], + "explanation": "你可以用"You are so..."来表达对家人的描述和感受,例如 kind、strong、helpful 等。用完整的句子加上一个简单的理由会更好。" + }, + { + "content": "What are you good at doing?", + "ability": ["基础信息表达|个人信息问答"], + "explanation": "你可以用"I am good at..."来回答,然后说出你擅长的具体事情,例如 drawing、running 或 playing football。试着用 because 加一句解释。" + }, + { + "content": "Who is good at helping you at home?", + "ability": ["互动应答|问答交流"], + "explanation": "你可以回答家人擅长的家务,例如"My dad is good at cooking"或"My mom is good at...",用完整句子描述具体帮助你的方式。" + } + ] + }, + "second": {} + }, + "t1": """【题目描述】 +Answer these questions about your friends and yourself. Use full English sentences. + +【题目】 +1. What does your best friend look like? +2. How would you describe your dad or mom? +3. What are you good at doing? +4. Who is good at helping you at home?""", + "t2": "" +}) + +# ==================================================================== +# 口语-P1-011401: We should ask... about..., Let's/Let me..., Don't... in... +# ==================================================================== +RECORDS.append({ + "table": "speaking_qa", "sid": "011401", "type": "口语-P1", + "json": { + "first": { + "category": "speaking", + "type": "speaking_qa", + "asrPrompt": "ask,teacher,school,trip,help,friend,library,run,rules", + "questionSetID": "011401", + "textDesc": "Answer these questions. Think about how to ask for help, offer help, or give rules in English.", + "questionSet": [ + { + "content": "If you want to know about the school trip, what will you say to your teacher?", + "ability": ["互动应答|问答交流"], + "explanation": "你可以用"We should ask our teacher about..."来提出建议。例如"We should ask our teacher about the school trip time." 这个句型适合用于礼貌地提出询问。" + }, + { + "content": "If you see your friend carrying heavy books, what will you say?", + "ability": ["互动应答|问答交流"], + "explanation": "你可以用"Let me..."来主动提供帮助,例如"Let me help you with those books." 这个句型展示了友善和主动帮忙的态度。" + }, + { + "content": "If you see someone running in the library, what will you say to them?", + "ability": ["互动应答|问答交流"], + "explanation": "你可以用"Don't... in the library." 来表达规则提醒,例如"Don't run in the library." 这是一个礼貌提醒他人遵守规则的好方式。" + }, + { + "content": "If your dog is sick, who should you talk to?", + "ability": ["互动应答|问答交流"], + "explanation": "你需要回答应该找谁帮忙,可以尝试用"We should ask..." 来造句。例如"We should ask the vet about our dog." 考察如何表达寻求合适帮助的能力。" + } + ] + }, + "second": {} + }, + "t1": """【题目描述】 +Answer these questions. Think about how to ask for help, offer help, or give rules in English. + +【题目】 +1. If you want to know about the school trip, what will you say to your teacher? +2. If you see your friend carrying heavy books, what will you say? +3. If you see someone running in the library, what will you say to them? +4. If your dog is sick, who should you talk to?""", + "t2": "" +}) + +# ==================================================================== +# 口语-P1-011501: Let's wait and see..., Please give him..., He didn't mean to... +# ==================================================================== +RECORDS.append({ + "table": "speaking_qa", "sid": "011501", "type": "口语-P1", + "json": { + "first": { + "category": "speaking", + "type": "speaking_qa", + "asrPrompt": "wait,see,tomorrow,scores,brother,hungry,lunch,mom,broke,cup,accident", + "questionSetID": "011501", + "textDesc": "Answer these questions about everyday situations. Think about patience, asking nicely, and explaining accidents.", + "questionSet": [ + { + "content": "Your friend wants to check the test scores, but the teacher says the scores come out tomorrow. What will you say to your friend?", + "ability": ["互动应答|问答交流"], + "explanation": "你可以用"Let's wait and see..."来安慰朋友不要着急。例如"Let's wait and see the scores tomorrow." 这个句型展现出耐心和积极的态度。" + }, + { + "content": "Your little brother is hungry, but he forgot his lunch at home. What will you ask your mom?", + "ability": ["互动应答|问答交流"], + "explanation": "你可以用"Please give him..."来礼貌地请求妈妈帮忙。例如"Please give him some of my lunch." 或者"Please give him something to eat." 这是礼貌请求的好方式。" + }, + { + "content": "Your friend broke a cup by accident. He feels very sorry. What will you tell your mom?", + "ability": ["互动应答|问答交流"], + "explanation": "你可以用"He didn't mean to..."来解释这不是故意的。例如"He didn't mean to break the cup." 这是帮助别人解释无心之失的好表达。" + }, + { + "content": "You want to see a film with your friend. Your friend says he is busy. What will you say?", + "ability": ["互动应答|问答交流"], + "explanation": "用"Let's wait and see..."来表达理解并建议改时间。例如"Let's wait and see if we can go next weekend." 展现出灵活和体贴的态度。" + } + ] + }, + "second": {} + }, + "t1": """【题目描述】 +Answer these questions about everyday situations. Think about patience, asking nicely, and explaining accidents. + +【题目】 +1. Your friend wants to check the test scores, but the teacher says the scores come out tomorrow. What will you say to your friend? +2. Your little brother is hungry, but he forgot his lunch at home. What will you ask your mom? +3. Your friend broke a cup by accident. He feels very sorry. What will you tell your mom? +4. You want to see a film with your friend. Your friend says he is busy. What will you say?""", + "t2": "" +}) + +# ==================================================================== +# 口语-P1-011701: On my shopping list, there are... and..., I think we can go to the..., I think we're going to... +# ==================================================================== +RECORDS.append({ + "table": "speaking_qa", "sid": "011701", "type": "口语-P1", + "json": { + "first": { + "category": "speaking", + "type": "speaking_qa", + "asrPrompt": "shopping,list,milk,bread,eggs,supermarket,toy,shop,weekend,park,zoo", + "questionSetID": "011701", + "textDesc": "Answer these questions about shopping and weekend plans. Use full English sentences.", + "questionSet": [ + { + "content": "What do you need to buy at the supermarket?", + "ability": ["基础信息表达|个人信息问答"], + "explanation": "你可以用"On my shopping list, there are... and..."来列出你要买的物品。例如"On my shopping list, there are milk and eggs." 用这个句型你就能说出购物清单了。" + }, + { + "content": "Where do you think you can buy a new toy?", + "ability": ["互动应答|问答交流"], + "explanation": "你可以用\"I think we can go to the..."来表达你的建议。例如"I think we can go to the toy shop." 用I think来表达你的想法,这是很自然的英语表达。" + }, + { + "content": "What do you think your family will do this weekend?", + "ability": ["基础信息表达|个人信息问答"], + "explanation": "你可以用\"I think we're going to..."来猜测和表达周末计划。例如"I think we're going to the park." 或者"I think we're going to visit my grandparents." 这是一个表达未来计划的常用句型。" + }, + { + "content": "If you could buy one special thing today, what would it be?", + "ability": ["表达喜好与理由"], + "explanation": "用完整句子描述你想买的特别物品和原因。例如"I think I would buy a new book because I love reading." 试着用because来解释你的选择。" + } + ] + }, + "second": {} + }, + "t1": """【题目描述】 +Answer these questions about shopping and weekend plans. Use full English sentences. + +【题目】 +1. What do you need to buy at the supermarket? +2. Where do you think you can buy a new toy? +3. What do you think your family will do this weekend? +4. If you could buy one special thing today, what would it be?""", + "t2": "" +}) + +# ==================================================================== +# 写作-P2-011601: What time does the... start?, Why must we...?, It was caused by... +# ==================================================================== +RECORDS.append({ + "table": "writing_picWrite", "sid": "011601", "type": "写作-P2", + "json": { + "first": { + "category": "writing", + "type": "writing_picWrite", + "questionSetID": "011601", + "textImage": "011601-00.png", + "textDesc": "Look at the three pictures. They tell a story about a school sports day. Put these sentences in order to tell the story.", + "optionSetList": [ + "Tom asked, \"What time does the sports day start?\" His teacher told him it would start at 9 o'clock.", + "Everyone was excited. But soon, the sky got very dark. It was caused by a big rain cloud coming in.", + "Just as the race was about to start, it began to rain hard! Why must we stop? Tom was very sad.", + "The head teacher said, \"Don't worry! Let's move the races to the big hall inside.\"", + "Everyone ran to the hall. Tom won the first race and everyone cheered!", + "After the sports day, Tom's mom said it was the best day ever, even with the rain!" + ], + "answerSet": [0, 1, 2, 3, 4, 5], + "ability": ["叙述故事|看图写故事", "衔接与连贯|连词使用"], + "explanation": "根据三张图片的时间顺序排列句子:询问开始时间→天空变暗→下雨停止比赛→转移场地→比赛继续→总结美好一天。考察叙事逻辑和事件衔接能力。" + }, + "second": {} + }, + "t1": """【题目描述】 +Look at the three pictures. They tell a story about a school sports day. Put these sentences in order to tell the story. + +【段落列表】 +A. Tom asked, "What time does the sports day start?" His teacher told him it would start at 9 o'clock. +B. Everyone was excited. But soon, the sky got very dark. It was caused by a big rain cloud coming in. +C. Just as the race was about to start, it began to rain hard! Why must we stop? Tom was very sad. +D. The head teacher said, "Don't worry! Let's move the races to the big hall inside." +E. Everyone ran to the hall. Tom won the first race and everyone cheered! +F. After the sports day, Tom's mom said it was the best day ever, even with the rain! + +【答案】 +A, B, C, D, E, F + +【解析】 +根据三张图片的时间顺序排列句子:询问开始时间→天空变暗→下雨停止比赛→转移场地→比赛继续→总结美好一天。考察叙事逻辑和事件衔接能力。 + +【能力项】 +叙述故事|看图写故事,衔接与连贯|连词使用""", + "t2": "" +}) + +# ==================================================================== +# 口语-P2-011601: Where did you last see...?, Be careful, or you might..., There is a... and a... nearby. +# ==================================================================== +RECORDS.append({ + "table": "speaking_topic", "sid": "011601", "type": "口语-P2", + "json": { + "first": { + "category": "speaking", + "type": "speaking_topic", + "asrPrompt": "park,community,lost,found,careful,slide,swing,bench,shop,cafe", + "questionSetID": "011601", + "textDesc": "Look at the picture of a community park. Talk about what you see. Think about safety and what is around you.", + "imageDesc": "A community park scene with children playing. There is a playground with a slide and swings. A small shop and a cafe are nearby. A child is looking for something he lost. A parent is reminding another child to be careful near the playground equipment.", + "textImage": "011601-00.png", + "questionSet": [ + { + "content": "Where did you last see your toy in the park?", + "ability": ["故事讲述|看图说话"], + "explanation": "你可以用"Where did you last see..."来说出你最后在哪里见过某物。例如"I last saw my toy near the slide." 具体描述位置信息能让回答更完整。" + }, + { + "content": "What should you say to your little brother if he climbs too high on the slide?", + "ability": ["互动应答|问答交流"], + "explanation": "你可以用"Be careful, or you might..."来善意提醒他人。例如"Be careful, or you might fall." 这是一个既表达关心又能给出安全提醒的好句子。" + }, + { + "content": "What places are near the park?", + "ability": ["故事讲述|看图说话"], + "explanation": "你可以用"There is a... and a... nearby."来描述附近的设施。例如"There is a shop and a cafe nearby." 这个句型能帮你清楚地描述周边环境。" + }, + { + "content": "What should you do if you lose something in the park?", + "ability": ["互动应答|问答交流"], + "explanation": "试着用完整句子描述你会怎么做。例如"First I would look around the playground, then I would ask my parents for help." 用first和then来组织语言会更有条理。" + } + ] + }, + "second": {} + }, + "t1": """【题目描述】 +Look at the picture of a community park. Talk about what you see. Think about safety and what is around you. + +【题目】 +1. Where did you last see your toy in the park? +2. What should you say to your little brother if he climbs too high on the slide? +3. What places are near the park? +4. What should you do if you lose something in the park?""", + "t2": "" +}) + +# ==================================================================== +# BACKFILL +# ==================================================================== +print(f"Generated {len(RECORDS)} records") + +# Get token +with open(CRED_FILE) as f: + cfg = json.load(f) +req = urllib.request.Request( + "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal", + data=json.dumps({"app_id": cfg['apps'][0]['appId'], "app_secret": cfg['apps'][0]['appSecret']}).encode(), + headers={"Content-Type": "application/json"}) +token = json.loads(urllib.request.urlopen(req).read())['tenant_access_token'] + +def api_call(url, method='GET', body=None): + headers = {"Authorization": f"Bearer {token}"} + data = json.dumps(body).encode() if body else None + if data: + headers["Content-Type"] = "application/json" + req = urllib.request.Request(url, data=data, method=method, headers=headers) + return json.loads(urllib.request.urlopen(req).read()) + +# For each record, find if exists in bitable, update or create +for rec in RECORDS: + table_id = TABLES[rec['table']] + sid = rec['sid'] + + # List records to find matching ID + url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{table_id}/records?page_size=100" + resp = api_call(url) + + existing_rid = None + for item in resp['data']['items']: + if item['fields'].get('题目集合 ID', '') == sid: + existing_rid = item['record_id'] + break + + new_jd = json.dumps(rec['json'], ensure_ascii=False) + fields = { + "题目集合 ID": sid, + "jsonData": new_jd + } + + # Add 题目1 field + t1_key = next((k for k in item['fields'] if k in ['题目1']), '题目1') if existing_rid else '题目1' + if rec.get('t1'): + fields[t1_key] = rec['t1'] + + # Try to add 题目2 if table supports it + if rec.get('t2'): + # Check if field exists + if existing_rid: + for k in item['fields']: + if '题目2' in k: + fields[k] = rec['t2'] + break + + if existing_rid: + result = api_call( + f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{table_id}/records/{existing_rid}", + 'PUT', {"fields": fields} + ) + status = "UPD" if result.get('code') == 0 else f"ERR:{result.get('msg')}" + else: + result = api_call( + f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{table_id}/records", + 'POST', {"fields": fields} + ) + status = "NEW" if result.get('code') == 0 else f"ERR:{result.get('msg')}" + + print(f" {status} {rec['type']} {sid}: {rec['table']}") + if result.get('code') != 0: + print(f" ERROR: {result.get('msg')}") + +print(f"\n✅ All {len(RECORDS)} records processed!") diff --git a/scripts/generate_explanations.py b/scripts/generate_explanations.py new file mode 100644 index 0000000..cc68501 --- /dev/null +++ b/scripts/generate_explanations.py @@ -0,0 +1,177 @@ +""" +批量生成197条缺失的explanation并回填到飞书多维表格 +""" +import json, urllib.request, sys + +APP_TOKEN = "CMHSbUUjka3TrUsaxxEc297ongf" +CRED_FILE = "/root/.openclaw/credentials/xiaoyan/config.json" + +def get_token(): + with open(CRED_FILE) as f: + cfg = json.load(f) + req = urllib.request.Request( + "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal", + data=json.dumps({"app_id": cfg['apps'][0]['appId'], "app_secret": cfg['apps'][0]['appSecret']}).encode(), + headers={"Content-Type": "application/json"}) + return json.loads(urllib.request.urlopen(req).read())['tenant_access_token'] + +def api_call(url, method='GET', body=None): + token = get_token() + headers = {"Authorization": f"Bearer {token}"} + data = json.dumps(body).encode() if body else None + if data: + headers["Content-Type"] = "application/json" + req = urllib.request.Request(url, data=data, method=method, headers=headers) + return json.loads(urllib.request.urlopen(req).read()) + +TABLE_MAP = {"P1": "tbliZAhcc9C43B23", "P2": "tblzTLNH7f13uWQN", "P4": "tblVmeDtBDKsAEfz", "P7": "tbly9SvPEa44k3yX"} + +def _opt_label(idx): + return chr(65 + idx) + +def generate_expl(question, answer_idx, options, options_image, + text_desc, text_body, text_title, table_name): + """Generate a Chinese explanation for a listening question.""" + has_context = bool(text_desc or text_body) + title_hint = (text_title or text_desc or '')[0:40] if has_context else '' + + # Answer text + if options and answer_idx < len(options): + ans_text = options[answer_idx] + else: + ans_text = f"第{answer_idx + 1}个选项" + + if table_name == "P1": + # listening_choicePic + img_label = f"第{answer_idx + 1}张图片" + question_lower = question.lower() + if "what is this" in question_lower or "what's this" in question_lower: + return f"听力内容中描述的物品特征与{img_label}相符,因此选择该图片。" + elif "where" in question_lower: + return f"听力内容中提到了地点信息,与{img_label}所描绘的场景一致,因此选择该图片。" + elif "what does" in question_lower or "what is" in question_lower: + return f"听力内容中提到的相关信息与{img_label}相符,因此选择该图片。" + elif "which" in question_lower: + return f"听力内容中的描述与{img_label}对应,因此选择该图片。" + elif "how many" in question_lower: + return f"听力内容中提到了数量信息,与{img_label}一致,因此选择该图片。" + elif "what color" in question_lower or "what colour" in question_lower: + return f"听力内容中描述了相关颜色信息,与{img_label}对应,因此选择该图片。" + elif "who" in question_lower: + return f"听力内容中提到了人物信息,与{img_label}对应,因此选择该图片。" + else: + return f"根据听力内容,正确答案对应{img_label}。" + + elif table_name == "P2": + if has_context: + return f"在"{title_hint}"对话中,听力内容提到了相关信息,空白处应填入「{ans_text}」。" + return f"根据听力对话内容,空白处应填入「{ans_text}」。" + + elif table_name == "P4": + lbl = _opt_label(answer_idx) + if has_context: + return f"在"{title_hint}"短对话中,根据听力内容,正确答案为{lbl}选项「{ans_text}」。" + return f"根据听力短对话内容,正确答案为{lbl}选项「{ans_text}」。" + + elif table_name == "P7": + return f"根据听力内容,匹配信息对应{_opt_label(answer_idx)}选项。" + + return f"根据听力内容,正确答案为「{ans_text}」。" + +# Load missing explanations +with open('/tmp/missing_explanations.json') as f: + missing = json.load(f) + +from collections import defaultdict +groups = defaultdict(list) +for m in missing: + groups[(m['sid'], m['table'])].append(m) + +print(f"Total missing: {len(missing)}") +print(f"Grouped into {len(groups)} record groups") + +total_fixed = 0 +total_failed = 0 + +for (sid, table_name), items in groups.items(): + table_id = TABLE_MAP[table_name] + + url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{table_id}/records?page_size=100" + resp = api_call(url) + + record = None + for item in resp['data']['items']: + if item['fields'].get('题目集合 ID', '') == sid: + record = item + break + + if not record: + print(f"⚠️ {table_name} {sid}: record not found") + total_failed += len(items) + continue + + rid = record['record_id'] + + try: + jd = json.loads(record['fields']['jsonData']) + except: + print(f"⚠️ {table_name} {sid}: JSON parse error") + total_failed += len(items) + continue + + changed = False + fixed_count = 0 + + for entry in items: + section = entry['section'] + q_idx = entry['q_index'] + + if section == 'root': + qs = jd.get('questionSet', []) + else: + sect = jd.get(section, {}) + qs = sect.get('questionSet', []) + + if q_idx >= len(qs): + continue + q = qs[q_idx] + + if q.get('explanation', '') not in (None, ''): + continue + + question = entry.get('question', '') + answer_idx = entry.get('answer', [0]) + answer_idx = answer_idx[0] if isinstance(answer_idx, list) and answer_idx else 0 + options = entry.get('options', []) + options_image = entry.get('optionsImage', []) + + q['explanation'] = generate_expl( + question=question, + answer_idx=answer_idx, + options=options, + options_image=options_image, + text_desc=entry.get('text_desc', ''), + text_body=entry.get('text_body', ''), + text_title=entry.get('text_title', ''), + table_name=table_name + ) + changed = True + fixed_count += 1 + + if changed: + new_jd = json.dumps(jd, ensure_ascii=False) + result = api_call( + f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{table_id}/records/{rid}", + 'PUT', {"fields": {"jsonData": new_jd}} + ) + if result.get('code') == 0: + total_fixed += fixed_count + print(f"✅ {table_name} {sid}: {fixed_count} explanations") + else: + total_failed += fixed_count + print(f"❌ {table_name} {sid}: {result.get('msg')}") + else: + print(f"⏭️ {table_name} {sid}: no changes") + +print(f"\n{'='*50}") +print(f"Total fixed: {total_fixed}, Failed: {total_failed}") diff --git a/scripts/scan_missing_explanations.py b/scripts/scan_missing_explanations.py new file mode 100644 index 0000000..b9fe88c --- /dev/null +++ b/scripts/scan_missing_explanations.py @@ -0,0 +1,132 @@ +""" +扫描所有听力表格,列出所有explanation为空的题目 +""" +import json, urllib.request + +APP_TOKEN = "CMHSbUUjka3TrUsaxxEc297ongf" +CRED_FILE = "/root/.openclaw/credentials/xiaoyan/config.json" + +def get_token(): + with open(CRED_FILE) as f: + cfg = json.load(f) + req = urllib.request.Request( + "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal", + data=json.dumps({"app_id": cfg['apps'][0]['appId'], "app_secret": cfg['apps'][0]['appSecret']}).encode(), + headers={"Content-Type": "application/json"}) + return json.loads(urllib.request.urlopen(req).read())['tenant_access_token'] + +def api_call(url): + token = get_token() + req = urllib.request.Request(url, headers={"Authorization": f"Bearer {token}"}) + return json.loads(urllib.request.urlopen(req).read()) + +tables = { + "P1": "tbliZAhcc9C43B23", + "P2": "tblzTLNH7f13uWQN", + "P4": "tblVmeDtBDKsAEfz", + "P5": "tblDssVmhGzc3UKd", + "P6": "tbloiMcD0sBtGSTq", + "P7": "tbly9SvPEa44k3yX", +} + +missing = [] +total_questions = 0 + +for name, tid in tables.items(): + url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{tid}/records?page_size=100" + resp = api_call(url) + if resp.get('code') != 0: + print(f"❌ {name}: {resp.get('msg')}") + continue + + for item in resp['data']['items']: + sid = item['fields'].get('题目集合 ID', '') + if '010199' in str(sid): + continue + + jd_str = item['fields'].get('jsonData', '{}') + if not jd_str or jd_str == 'None': + continue + + try: + jd = json.loads(jd_str) + except: + print(f" ⚠️ {name} {sid}: still can't parse JSON") + continue + + # Check first/second structure + for section in ['first', 'second']: + sect = jd.get(section, {}) + if not sect or not sect.get('questionSet'): + continue + + text_desc = sect.get('textDesc', '') + text_audio = sect.get('textAudio', '') + text_title = sect.get('textTitle', '') + text_body = sect.get('textBody', '') + + qs = sect.get('questionSet', []) + total_questions += len(qs) + + for i, q in enumerate(qs): + expl = q.get('explanation', '') + if expl is None or expl == '': + missing.append({ + 'table': name, + 'sid': sid, + 'section': section, + 'q_index': i, + 'question': q.get('question', q.get('questionDesc', '')), + 'answer': q.get('answer', []), + 'options': q.get('options', []), + 'optionsImage': q.get('optionsImage', []), + 'text_desc': text_desc, + 'text_audio': text_audio, + 'text_title': text_title, + 'text_body': text_body, + }) + + # Check root questionSet (P6 style) + qs = jd.get('questionSet', []) + if qs: + text_desc = jd.get('textDesc', '') + text_audio = jd.get('textAudio', '') + for i, q in enumerate(qs): + expl = q.get('explanation', '') + if expl is None or expl == '': + total_questions += len(qs) + missing.append({ + 'table': name, + 'sid': sid, + 'section': 'root', + 'q_index': i, + 'question': q.get('question', ''), + 'answer': q.get('answer', []), + 'optionsImages': q.get('optionsImage', []), + 'text_desc': text_desc, + 'text_audio': text_audio, + 'text_title': jd.get('textTitle', ''), + 'text_body': '', + }) + +print(f"Total questions scanned: {total_questions}") +print(f"Missing explanations: {len(missing)}") +print() + +# Show summary by table +from collections import Counter +by_table = Counter(m['table'] for m in missing) +for t in ["P1","P2","P4","P5","P6","P7"]: + print(f" {t}: {by_table.get(t, 0)} missing") + +# Save for generation +with open('/tmp/missing_explanations.json', 'w') as f: + json.dump(missing, f, ensure_ascii=False, indent=2) + +print(f"\nSaved {len(missing)} missing explanations to /tmp/missing_explanations.json") + +# Show a few samples +for m in missing[:3]: + print(f"\n [{m['table']}] {m['sid']}/{m['section']}[{m['q_index']}]") + print(f" Question: {m['question'][:80]}") + print(f" Answer: {m['answer']}")