"""任务 1 · 造提示词，四样约束一个不少（lab 0027）

这一课要做的助手是：题目文本进 → 判断它像哪一类算法 → 给出候选实现 → 自动跑验证 → 出结论。

第一段就是**提示词**。它看起来只是「把题目裹起来」，其实不是：既然下一步要用**程序**
解析返回的那段文本，提示词里就必须把**格式**钉死。课件里说得清楚，有四样东西缺一样
后面就会出麻烦：

    1. 输出格式（格式固定为：…）——后面要按这个键名取值；
    2. 不许有别的文字、不许用代码块围栏——减少解析要处理的形态；
    3. 取值范围（可选的算法族：…）——不限定的话，模型可能给出项目里根本没有对应实现的名字；
    4. 转义的约定（字符串里的引号必须转义）——这是最难察觉的一条。

第 4 条为什么最难察觉：它是**唯一一条解析器救不了的**。围栏、前后解释这类格式浮动，
解析器多剥一层就处理掉了；而字符串内容里没转义的引号会让 JSON 本身不合法
（`题目说"最小化最大值"` 那对引号），括号配平、位置也对，找大括号的招数一条都用不上。

四样约束的落地就是四个 TODO。四句里缺哪一句，判分点名到哪一句：
判分不是看你的提示词「像不像」，而是逐条去认那四样约束的锚点
（「格式固定」+「JSON」、「只输出」+「不要」、「可选的算法族」+ 八张族名表、「转义」+ 反斜杠写法）。

顺带一句这一课的骨架：`call_model` 是**唯一**的联网点，它在离线环境里直接抛
NotImplementedError。所有不可控的东西（网络、时间、随机）都收在这一处，
其余部分就都成了纯逻辑，可以反复跑、可以对拍——这就是课件里说的那道**缝**。

接口：文件名、函数名、带 print 的行都是判分接口，别改；要写的是四个 TODO。

跑： py task1_prompt.py
判： py run_tests.py
"""

from tools import FAMILIES, FENCE, fence_line_count, show_banner


# ══ TODO 1 ═════════════════════════════════════════════════
# build_prompt(problem)：把题面裹成一条有格式约束的提示词，交回一个字符串。
#
# 四样约束，一样都不能少（判分逐条去认）：
#
#   (1) 输出格式：要有一句「格式固定为：」，紧跟着一行 JSON 的**键名样子**——
#       {"family": "<算法族名>", "confidence": "高|中|低", "why": "<一句话理由>"}
#       下一步就是按 family / confidence / why 这三个键取值的。
#
#   (2) 不许有别的文字、不许用围栏：要有一句「只输出一个 JSON 对象」，
#       并且**明说不许**（“不要”）用代码块围栏。
#
#   (3) 取值范围：要写出「可选的算法族：」并把 tools.FAMILIES 里**八张表的族名全列上**
#       （二分答案、并查集、堆、图的搜索、背包、贪心、前缀和、双指针，用「、」隔开）。
#       不写全的话，模型给的族名在你的项目里可能根本没有对应的候选实现。
#
#   (4) 转义的约定：要有一句「字符串里的引号必须转义」，并且**把 `\` 配 `"` 的写法
#       在提示词里出现一次**（比如写在 `\"` 这样一个示意里）——只写一句中文，
#       判分认不出你真的把转义约定交给了模型。
#
# 还有两条形状上的要求：
#   · 题面要出现在提示词里，而且是 problem.strip() 之后的样子（别把首尾空白带进去）；
#   · `题目：` 那一行与题面之间是一个换行——判分认它。
#
# 参考形状（每一行的内容都可以自己改，四样约束不能少）：
#     你是一个算法助教。读下面的题目，判断它最像哪一类算法。
#     （空行）
#     只输出一个 JSON 对象，不要输出任何别的文字，不要用代码块围栏。
#     格式固定为：
#     {"family": "<算法族名>", "confidence": "高|中|低", "why": "<一句话理由>"}
#     字符串里的引号必须转义。
#     （空行）
#     可选的算法族：二分答案、并查集、堆、图的搜索、背包、贪心、前缀和、双指针。
#     （空行）
#     题目：
#     <题面>
def build_prompt(problem):
    return ''


# ══ TODO 2 ═════════════════════════════════════════════════
# call_model(prompt)：真正跑的时候这里发一次 API 请求。
#
# 这一课**离线**：这个函数唯一的职责就是**明确地失败**——
# 用 raise NotImplementedError('...') 抛出去，说明「离线环境不联网，测试时请换掉这个函数」。
#
# 它是整条链上唯一的联网点，也就是那道缝：测试时换成一个假的函数（见 TODO 3），
# 其余部分一行都不用改。别在这里写 try/except 把它吞掉——吞掉了，
# 「忘了换掉这个函数」这件事就会静默地变成「模型返回空」。
def call_model(prompt):
    raise NotImplementedError('离线环境不联网，测试时请换掉这个函数')


# ══ TODO 3 ═════════════════════════════════════════════════
# fake_model(prompt)：假的模型，用来离线跑通链路。
#
# 课件那一版只认一个词——题目里有「木头」就给二分答案，否则给贪心。照写即可：
#
#     if '木头' in prompt:
#         return '{"family": "二分答案", "confidence": "高", "why": "要让最小长度最大化"}'
#     return '{"family": "贪心", "confidence": "中", "why": "没认出来，先猜一个"}'
#
# 两个返回值都必须是**干干净净的 JSON 一行**（没有围栏、没有前后解释）：
# 这一段试的是「缝」通不通，不是解析器的抗噪能力（那是任务 2 的事）。
def fake_model(prompt):
    return ''


# ══ TODO 4 ═════════════════════════════════════════════════
# recommend(problem, model)：题目进、候选算法出。model 就是那道缝。
#
# 三行就够，但**这三行就是整条链的接缝**：
#
#     prompt = build_prompt(problem)   把题面裹成有格式约束的提示词
#     raw = model(prompt)              交给传进来的那个模型函数（真跑时是 call_model，测试时是假的）
#     return raw                       原样交回那段**文本**
#
# 要点：
#   · 这一段只到「拿到那段文本」为止，**不解析**——解析是任务 2 的事。
#     所以它交回的是 str，哪怕那是一段带围栏、带前后解释的文本；
#   · 别在这里写 model = call_model 之类的默认值把缝堵上：模型必须是**参数**，
#     判分第 4 层就是拿它自己的假模型来试这条链通不通的。
def recommend(problem, model):
    return None


if __name__ == '__main__':                      # 别改：判分读的就是下面这几行
    show_banner('0027 提示词长这样')
    problem = '有 n 根木头，要切成 k 段等长的小段。求每小段最长的长度，即让最小长度最大化。'
    prompt = build_prompt(problem)
    print('   —— 提示词长这样 ——')                            # 别改：期望 8 行（含末尾空行）
    for line in prompt.splitlines():
        print('   | ' + line)                                 # 别改
    print()

    show_banner('0027 接上假的模型，链路照样跑')
    try:
        real = recommend(problem, call_model)
        print('   真模型返回: ' + str(real))                   # 别改
    except NotImplementedError as exc:
        print('   真模型还没接: NotImplementedError ' + str(exc))   # 别改
    print('   假模型返回: ' + str(recommend(problem, fake_model)))   # 别改：期望干净 JSON 一行

    show_banner('0027 取值范围与围栏')
    print('   可选算法族共 8 个: ' + '、'.join(name for name, _ in FAMILIES))   # 别改
    print('   围栏那一串的长度 3 个反引号')                                  # 别改
    print('   提示词里的围栏行 ' + str(fence_line_count(prompt)))            # 别改：期望 0
