#!/usr/bin/env python3
"""
合作听力 (core_listening_order) 多步 LLM prompt 模板

生成流水线：
  Step 1: 学习过程配置 (intermediate text)
  Step 2: 互动描述 (intermediate text)
  Step 3: taskData → JSON
  Step 4: textData → JSON
  Step 5: sequenceData → JSON
  Step 6: learningData → JSON
"""

# ── Step 1: 学习过程配置 ──
STEP1_LEARNING_PROCESS = r"""你是一个少儿英语学习的出题专家。请根据以下任务信息，生成一个结构化的听力学习引导框架，帮助孩子逐步完成一篇听力中重点句子的学习。

# 你的任务
根据{听力材料}理解当前上下文， 针对{学习流程}中的句子生成一个 markdown 格式的教学流程。
这个任务的目标是引导孩子 1). 准确匹配耳中听到的句子和眼前看到的文本（完整匹配或仅匹配词汇/短语/句型），2). 准确匹配英文文本和中文意思，从而准确理解一个听力句子的意思。学习流程中，Brainy 将逐轮引导孩子仔细听音频句子、选择正确匹配的句子英文文本、选择正确的中文意思。

每一轮重点句子的学习包含以下内容：
1.一段对重点句子结尾【xxx】标记的重点内容(如未标记，则自行选择一段重点内容)的讲解文本。注意，重点内容要以 原型 单词/短语 来讲解， 不要增加 单复数、时态等变化。

2. 听音选句
   - Brainy引导出题: 描述题干
   - 选择题包含 2 个英文选项（1个正确选项，内容为当前重点句子，在其后标识出"[正确]" ， 1个干扰项。正确选项与干扰项的差异，需要围绕重点句子末尾用【xxx】标识的重点内容来设计；选项顺序随机）。
   - 每个选项配有 Brainy 的中文反馈：
     - 正确选项：语气积极的正反馈。
     - 错误选项：自然指出错误原因，避免批评，并鼓励再尝试。

3. 听音选意
   - Brainy 播放重点句子的音频（显示对应文本），提出一个选择题。
   - 选择题包含2个选项（1个正确选项，内容为当前重点句子的中文含义，在其后标识出"[正确]" + 1个干扰项。正确选项与干扰项的差异，围绕重点句子末尾用【xxx】标识的重点内容来设计；选项顺序随机）。
   - 每个选项配有 Brainy 的中文反馈：
     - 正确选项：再进一步解析一下正确答案中的重点词汇/短语/句型（非必须），夸奖用户掌握了句子意思。
     - 错误选项：简单说明混淆项哪里不对（避免出现正确选项里的信息），并鼓励再尝试。

 注意: 错误选项中的语义要与正确选项有明显区分。
 例如:
 1)
 正确选项: 无论违法者藏得多深，太空法庭都会找到并惩罚他们！（正确）
 好的错误选项设置: 无论侵入者藏得多深，太空法庭都会找到并惩罚他们！
 不好的错误选项设置(语义区分度差): 无论罪犯藏得多深，太空法庭都会找到并惩罚他们！
 2)
 正确选项: 加入我们，你将有一次激动人心的经历。（正确）
 好的错误选项设置: 加入我们，你将有一次愉快的沟通。
 不好的错误选项设置(语义区分度差): 加入我们，你将有一次愉快的旅行。



全部重点句子都学完后，请附上一个任务完成反馈，用自然语言总结：用户已经完成全部题目，掌握了这篇听力中的所有重点句子。
注意，整个配置为一套线性的学习流程。已经考察过的题目及干扰项，后面需要变换，不要重复出现。

# 输出格式（请严格遵循）
```
# 重点句子 1
音频文本：{重点句子文本内容}
讲解文本：{对重点句子的重点内容的讲解文本}
1. 听音选句
Brainy：{题干，询问那个选项与播放的重点句子音频相对应}
- {选项 A}
  - Brainy：{选项 A 的反馈}
- {选项 B}
  - Brainy：{选项 B 的反馈}

2. 听音选意
Brainy：{题干，询问该句英文对应的正确中文意思}
- {选项 A}
  - Brainy：{选项 A 的反馈}
- {选项 B}
  - Brainy：{选项 B 的反馈}
---
# 重点句子 2
（同上结构）
---
# 重点句子 N
（同上结构）
---
# 完成反馈
Brainy：很好！你已经回答完了全部问题，相信你已经听懂了这个任务中所有的重点句子。现在让我们来查看结果吧!
```

# 注意事项
- 所有英文选项应为 CEFR A1/A2 水平，句子简短自然。英文使用英式拼写。
- Brainy 的反馈语气要亲切、鼓励、富有引导感。
- 正确选项位置随机，不总放第一个。
- 输出格式严格使用 markdown，无需附加其他解释或输入信息。

# 输出样例
以下是一些完整的输出样例。

样例1
```
# 重点句子 1
音频文本： We have entered a storm
讲解文本：刚刚听到的音频中，包含了一个关键单词 storm，意思为暴风雨。正确的发音是 storm。
1. 听音选句
Brainy：接下来，我们来试试，你能不能听出以下哪一句话中，包含了关键词 storm。
- We have entered a storm. [正确]
  Brainy：太棒了！你准确听出了句子中的重点单词 storm！
- We have entered a forest.
  Brainy：Oops，这句话说到了 forest，并没有 storm 哦。再仔细听一听正确的选项吧！
2. 听音选意
Brainy：那这句话是什么意思呢？我们来试试吧！
- 我们进入了一个森林。
  Brainy：这句话中，storm 是 "风暴"的意思，再仔细看看句子内容吧。
- 我们进入了一场风暴。[正确]
  Brainy：没错！你不仅听懂了这句话，还掌握了 enter a storm 这个短语！
# 重点句子 2
音频文本： The ship is broken.
讲解文本：刚刚听到的音频中，包含了一个关键单词 broken，意思为破损的、坏的。正确的发音是 broken。
1. 听音选句
Brainy：接下来，我们来试试，你能不能听出以下哪一句话中，包含了关键词 broken。
- The ship is repaired.
  Brainy：这句话里没有出现 broken，出现了 repaired。再仔细听听正确答案里的音频内容吧！
- The ship is broken. [正确]
  Brainy：没错！你准确听出了句子里包含的关键词 broken。

2. 听音选意
Brainy：那这句话是什么意思呢？
- 船修好了。
  Brainy：句子里说的是 broken（坏了），而不是  repaired（修好了）哦，再选一次试试看～
- 船坏了。[正确]
  Brainy：Bingo！你不仅理解了句子意思，还掌握了  broken 这个词汇！
# 完成反馈
Brainy：很好！你已经回答完了全部问题，相信你已经听懂了这个任务中所有的重点句子。现在让我们来查看结果吧!
```



# 输入
{听力材料}:
$听力材料$

{学习流程}:
$学习流程$

{专有词汇}:
$专有词汇$


# 输出
如果{听力材料}和{学习流程句子}包含有效文本，则按上述要求输出配置内容。
如果{听力材料}或者{学习流程句子}内容为空，则直接输出"无"。"""


# ── Step 2: 互动描述 ──
STEP2_INTERACTION_DESC = r"""# 任务目标
你是一个儿童英语学习内容生成助手。现在有一个听力理解互动配置，需要将其转写为对话记录中的叙事化描述，用于回顾剧情。

# 背景知识
- 听力理解是剧情中的核心互动，要求孩子聆听一份或多份音频材料（如录音、语音留言、对话片段等），并回答多个问题才能通过。
- 如果有多份听力材料（材料 1、材料 2…），则每份材料生成一条独立描述。
- 互动内容（即孩子听完材料并作答的过程）需要转写为自然的叙事描述。

# 输入信息
- 任务名称：$任务名称$
- 听力材料：$听力材料$
- 互动题目：$互动题目$

# 输出要求
1. 每份听力材料生成一条自然叙事描述。
2. 多条描述之间用**换行分隔**，不要插入空白行。
3. 每条描述以英文为主，中文放在括号中，必须在同一行里。
4. 每条描述长度约 40–60 英文词，适合总结该材料的关键信息。
5. 每条描述必须包含对应材料中的核心信息，并结合正确答案来组织内容。
6. 描述要自然，像剧情推进，而不是答题总结。
7. 避免教学感，不能出现"问题""选项""正确答案"等字眼。
8. 输出时只返回最终的描述文本，不要解释，不要使用额外的样式。

# 输出示例
输出：
You listened to Edward's recording and learned that the storm was very strong and the ship was broken. (你听了 Edward 的录音，得知风暴非常猛烈，飞船已经损坏。)
You then heard the pilot's last words: they would fall into the forest, and anyone who found the black box must warn the animal arks to stay away from the storm. (你随后听到飞行员的遗言：他们会坠入森林，找到黑匣子的人必须提醒动物方舟远离风暴。)"""


# ── Step 3: taskData ──
STEP3_TASK_DATA = r"""# 字段内容要求
{
    "cType": "core_listening_order",
    "cId": {ID},
    "title": {用户任务配置}中的任务名称,
    "sceneDesc": {用户任务配置}中的任务背景,
    "img": "",
    "key"(基于{用户任务知识}抽取): [
        {
            "desc": "",
            "keyList": [
                {
                    "type": "default",
                    "content": 知识点内容,
                    "desc": 知识点解释
                },
                 ... ...
            ]
        },
        ...
    ],
    "resourceMapping"(抽取出{角色配置}中的全部 name:id映射 可为空。id必须为int类型):
    {
    样例:
    "Tom":43,
    "Jerry":10
    },
}

# 输出格式
单行json输出。

样例:
{"cType":"core_listening_order","cId":"","title":"通过语音备忘录找到神秘人的线索。","sceneDesc":"在尘封的旧世界电脑中找到了神秘人留下的语音备忘录！看起来好像是说给自己的记录日志？不知道有没有留下关于去向的线索，和同行的伙伴们一起来听听看叭！","img":"","key":[{"desc":"","keyList":[{"type":"default","content":"Journey","desc":"n. 旅行"},{"type":"default","content":"Adventure","desc":"n. 冒险"}]}],"resourceMapping":{}}

# 输入
{ID}:
$ID$

{用户任务配置}:
$用户任务配置$

{用户任务知识}:
$用户任务知识$

{角色配置}:
$角色配置$

# 输出
现在基于输入字段和格式要求，生成***可以直接解析的单行json文本***，***注意前后不要输出任何额外符号！！！***
如果{任务配置}中没有有效文本，则输出"无"。"""


# ── Step 4: textData ──
STEP4_TEXT_DATA = r"""# 要求
将给定的配置数据抽取 为以下格式:
{
        "audio"({听力材料} 中的每个# 材料  为1个item): [{
                "audioType": "xxx", //  在以下取值中选择:File(代表音频文件)，Message(代表语音消息)
                "audioTitle": "xxx", // 材料标题
                "audioSource": "xxx", // 音频文件名
                "audioText": "xxx" // ##材料内容 的  文本 部分
        }],
        "question"( 基于{题目配置} 抽取，每个题目为1个item): [{
                        "type": "npc",
                        "npcName": "xxx",
                        "content": "xxx",
                        "optionList": [{
                                        "option": "xxx",
                                        "feedback": {
                                                "type": "npc",
                                                "npcName": "xxx",
                                                "content": "xxx"
                                        }
                                },
                                ... ...
                        ],
                        "answer": [int], // 正确选项对应的数组索引
                        "meaning": "xxx" // 学习意义
                },
                ... ...
        ]
}

补充规则:
1. 关于*npcName*字段的特殊规定：
a.文本本身要严格与输入文本中一致，包括大小写。
b. 如果 {角色配置}非空，且其中提供了npcName名字对应的id，则npcName字段的取值都要追加id，用#连接。如未提供该npcName对应的id，则不需追加。
c.以上规则仅限于npcName字段。不适用于其他字段的文本(content、desc 等)。
例如:
---
角色配置: Tom: 45
则: npcName: Tom#45
      content: I will tell Tom.
---
角色配置: Jerry: 45
则: npcName: Tom
      content: Jerry is my sister.
---

2. content 、 desc 字段取值应与原始输入保持一致，不做改动。



# 输出格式
单行json输出。
样例:
{"audio": [{"audioType": "File", "audioTitle": "Recording Journal - Jul. 1st, AD 2025", "audioSource": "0000001_音频材料1", "audioText": "A: Do you mind if I close the curtains? I can't see the television. The sun is too bright.\nB: I'd rather you didn't. I'm writing a letter so I need some light. Come and sit on the sofa with me. You can see the screen from here and there is lots of room.\nA: But I like this armchair!\nB: Well, move it into the middle of the room then."}], "question": [{"type": "npc", "npcName": "Sarah", "content": "First off, we need to make sure robots aren't too big. Do you know how big the robots can be in the competition?", "optionList": [{"option": "Robots must be built no larger than 50 cm", "feedback": {"type": "npc", "npcName": "Sarah", "content": "Opps! You need to read carefully and do it again!"}}, {"option": "Robots must be built no larger than 10 cm", "feedback": {"type": "npc", "npcName": "Sarah", "content": "Nice! You are good at finding information!"}}], "answer": [1], "meaning": "能理解文章的整体结构与主旨信息"}, {"type": "npc", "npcName": "Otis", "content": "Do you know how big the robots can be in the competition?", "optionList": [{"option": "Robots must be built no larger than 80 cm", "feedback": {"type": "npc", "npcName": "Otis", "content": "Nice! You are good at finding information!"}}, {"option": "Robots must be built no larger than 100 cm", "feedback": {"type": "npc", "npcName": "Otis", "content": "Opps! You need to read carefully and do it again!"}}], "answer": [0], "meaning": "能提取与分析细节内容、时间顺序和人物行为"}]}


# 输入
{ID}:
$ID$

{听力材料}:
$听力材料$

{题目配置}:
$题目配置$

{角色配置}:
$角色配置$

# 输出
现在基于输入字段和格式要求，生成***可以直接解析的单行json文本***，***注意前后不要输出任何额外符号！！！***，直接以"{"开始输出。
如果{材料内容}中没有有效文本，则输出"无"。"""


# ── Step 5: sequenceData ──
STEP5_SEQUENCE_DATA = r"""# 字段内容要求
基于 {流程编排}， 整理成sequence序列。

保持 {流程编排}中的顺序，根据不同类型分别处理:
{
  "sequence": [
    { "dialog": { "type": "npc", "npcName": "xxx", "content": "xxx" } }, // npc对话类型, 按格式抽取
   { "dialog": { "type": "user", "content": "..." } }, // user对话类型
    { "audio": [int] },   // 材料类型：被标记为:[材料x]，注意要转换成数字索引 x-1
    { "question": [int, ...] }, //问题类型list，被标记为:[题目x]，注意要转换成数字索引 x -1
    ... ...
}

补充规则:
1. 关于*npcName*字段的特殊规定：
a.文本本身要严格与输入文本中一致，包括大小写。
b. 如果 {角色配置}非空，且其中提供了npcName名字对应的id，则npcName字段的取值都要追加id，用#连接。如未提供该npcName对应的id，则不需追加。
c.以上规则仅限于npcName字段。不适用于其他字段的文本(content、desc 等)。
例如:
---
角色配置: Tom: 45
则: npcName: Tom#45
      content: I will tell Tom.
---
角色配置: Jerry: 45
则: npcName: Tom
      content: Jerry is my sister.
---

2. content 、 desc 字段取值应与原始输入保持一致，不做改动。

# 输出格式
单行json输出。
样例:
{"sequence": [{"dialog": {"type": "npc", "npcName": "Sarah", "content": "..."}}, {"dialog": {"type": "npc", "npcName": "Otis", "content": "..."}}, {"dialog": {"type": "user", "content": "..."}}, {"audio": [0]}, {"dialog": {"type": "npc", "npcName": "Otis", "content": "..."}}, {"question": [0, 1]}, {"dialog": {"type": "npc", "npcName": "Otis", "content": "..."}}, {"dialog": {"type": "user", "content": "..."}}, {"question": [2]}]}

# 输入
{ID}:
$ID$

{流程编排}:
$流程编排$

{角色配置}:
$角色配置$

# 输出
现在基于输入字段和格式要求，生成***可以直接解析的单行json文本***，***注意前后不要输出任何额外符号！！！***
（如果{材料配置}中没有有效文本，则直接输出"无"。）"""


# ── Step 6: learningData ──
STEP6_LEARNING_DATA = r"""# 字段内容要求
基于{学习配置}内容整理成以下结构：
{
"learningPart"(每个重点句子对应的内容为一个item):
        [    {
          "leadIn": {
                "type": "npc",
                "content": "xxx", //音频文本
                "npcName": "Brainy" //默认 除非配置中单独提供了其他角色
            },
          "content": "xxx",  // 讲解文本
          "type": "npc",
          "npcName": "Brainy", //默认 除非配置中单独提供了其他角色
          "learning"(题目内容，每个题目为1个item): [{
               "question": {
                    "type": "npc",
                    "content": "xxx", //题干
                    "npcName": "xxx" //一般为Brainy
            },
                "optionList": [{
                     "option": "xxx", // 选项内容
                     "feedback": {
                            "type": "npc",
                            "content": "xxx", //反馈
                            "npcName": "xxx" //一般为Brainy
            },
                       },
                       ... ...
                ],
                 "answer": [int],  // 正确选项对应的数组索引
                 "meaning": "xxx" // 题目类型
                },
                ... ... (其他题目的解析内容) ]
            },
                ... ... （句子2、句子3 ... 的解析内容）
       ],
"closing": {
                "type": "npc",
                "content": "xxx", //总结
                "npcName": "xxx" //一般为Brainy
            },
}
注意:  双引号字符串文本中如果出现英文双引号，需要使用转义符  确保最终输出是合法的json。
# 输出格式
单行json输出。
样例:
{"closing":{"type":"npc","content":"很好！你已经回答完了全部问题，相信你已经听懂了这个任务中所有的重点句子。现在让我们来查看结果吧!","npcName":"Brainy"},"learningPart":[{"leadIn":{"type":"npc","content":"We have entered a storm.","npcName":"Brainy"},"type":"npc","content":"刚刚听到的音频中，包含了一个关键单词 \"storm\"，意思为暴风雨。正确的发音是","npcName":"Brainy","learning":[{"answer":[0],"meaning":"听音选句","optionList":[{"feedback":{"type":"npc","content":"太棒了！你准确听出了句子中的重点单词 storm！","npcName":"Brainy"},"option":"We have entered a storm"},{"feedback":{"type":"npc","content":"Oops，这句话说到了 forest，并没有 storm 哦。再仔细听一听正确的选项吧！","npcName":"Brainy"},"option":"We have entered a forest"}],"question":{"type":"npc","npcName":"Brainy","content":"接下来，我们来试试，你能不能听出以下那一句话中，包含了关键词 storm。"}},{"answer":[1],"meaning":"听音选意","optionList":[{"feedback":{"type":"npc","content":"这句话中，「storm」是「风暴」的意思，再仔细看看句子内容吧～","npcName":"Brainy"},"option":"我们进入了一个森林。"},{"feedback":{"type":"npc","content":"没错！你不仅听懂了这句话，还掌握了「enter a storm」这个短语！","npcName":"Brainy"},"option":"我们进入了一场风暴"}],"question":{"type":"npc","npcName":"Brainy","content":"那这句话是什么意思呢？我们来试试吧！"}}]},{"leadIn":{"type":"npc","content":"The ship is broken.","npcName":"Brainy"},"content":"刚刚听到的音频中，包含了一个关键单词 broken，意思为破损的、坏的。正确的发音是","npcName":"Brainy","learning":[{"answer":[0],"meaning":"听音选句","optionList":[{"feedback":{"type":"npc","content":"没错！你准确听出了句子里包含的关键词 broken!","npcName":"Brainy"},"option":"The ship is broken"},{"feedback":{"type":"npc","content":"这句话里没有出现 broken，出现了 repaired。再仔细听听正确答案里的音频内容吧！","npcName":"Brainy"},"option":"The ship is repaired"}],"question":{"type":"npc","npcName":"Brainy","content":"接下来，我们来试试，你能不能听出以下那一句话中，包含了关键词 broken。"}},{"answer":[1],"meaning":"听音选意","optionList":[{"feedback":{"type":"npc","content":"句子里说的是「broken」（坏了），而不是「repaired」（修好了）哦，再选一次试试看～","npcName":"Brainy"},"option":"船修好了"},{"feedback":{"type":"npc","content":"Bingo！你不仅理解了句子意思，还掌握了「broken」这个词汇！","npcName":"Brainy"},"option":"船坏了。"}],"question":{"type":"npc","npcName":"Brainy","content":"那这句话是什么意思呢?"}}]}]}

# 输入
{学习配置}:
$学习配置$

# 输出
现在基于输入字段和格式要求，生成***可以直接解析的单行json文本***，***注意前后不要输出任何额外符号！！！*** 以"{"开始输出。
（如果{学习配置}中没有有效文本，则直接输出"无"。）"""


# ── Steps registry ──
CORE_LISTENING_ORDER_STEPS = {
    "step1_learning_process": {
        "template": STEP1_LEARNING_PROCESS,
        "description": "学习过程配置",
        "output_type": "text",
    },
    "step2_interaction_desc": {
        "template": STEP2_INTERACTION_DESC,
        "description": "互动描述",
        "output_type": "text",
    },
    "step3_task_data": {
        "template": STEP3_TASK_DATA,
        "description": "taskData",
        "output_type": "json",
    },
    "step4_text_data": {
        "template": STEP4_TEXT_DATA,
        "description": "textData",
        "output_type": "json",
    },
    "step5_sequence_data": {
        "template": STEP5_SEQUENCE_DATA,
        "description": "sequenceData",
        "output_type": "json",
    },
    "step6_learning_data": {
        "template": STEP6_LEARNING_DATA,
        "description": "learningData",
        "output_type": "json",
    },
}


def build_listening_order_prompt(step_key, replacements):
    """Build a prompt for a given step by replacing $placeholder$ tokens."""
    info = CORE_LISTENING_ORDER_STEPS[step_key]
    prompt = info["template"]
    for key, val in replacements.items():
        prompt = prompt.replace(f"${key}$", str(val) if val else "")
    return prompt
