# MQL 持久化大结果解析模式（2026-07-24 验证）

适用场景：`search_by_mql` 单页结果超过 ~100KB（50条×多字段的销售订单即触发），Hermes 将完整结果落盘到
`/var/folders/.../T/hermes-results/tool_*.txt`，工具只回传前 ~1500 字符预览 + 文件路径。

## 文件结构

```
{"result": "{...内层JSON字符串...}\nlog_id: 20260724..."}
```

- 外层：`{"result": "<string>"}` — `result` 是被转义的 JSON 字符串，不是对象。
- 内层：`{"list":[...], "session_id":"...", "data":{"<group_id>":[{moql_field_list:[...]}, ...]}}`
- **坑**：内层字符串尾部拼接了 `\nlog_id: <trace>`，直接 `json.loads` 抛 `JSONDecodeError: Extra data`。
  必须先截断：`inner_raw.split("\nlog_id:")[0]`
- **更健壮的替代（2026-07-24 batch2 实测）**：用 `json.JSONDecoder().raw_decode`，不依赖字面标记，外层文件尾部混入额外内容时同样有效：
  ```python
  dec = json.JSONDecoder()
  outer, _ = dec.raw_decode(open(path, encoding="utf-8").read().lstrip())
  try:
      inner = json.loads(outer["result"])
  except json.JSONDecodeError:            # 尾部 \nlog_id: ...
      inner, _ = dec.raw_decode(outer["result"])
  ```

## 字段值三种形状（moql_field_list 元素）

| value_type | 结构 | 提取 |
|---|---|---|
| `string_value` | `value.string_value` | 原文（可能是空格/空串） |
| `double_value` | `value.double_value` | 数值原文（金额/价税合计/单价/销售数量/含税单价等） |
| `long_value` | `value.long_value` | 整数原文 |
| `key_label_value` | `value.key_label_value.{key,label}` | 取 `label`（状态、单据类型、销售部门、关联客户等枚举） |
| `key_label_value_list` | `value.key_label_value_list[].label` | 逗号拼接所有 label |

注意：字段 `name` 是中文显示名（如 `出货批号#`），`key` 是 field_xxx；空值字段可能整条缺席，需 `.get` 兜底。

## 解析脚本模板

```python
import json

def extract(item):
    rec = {}
    for f in item.get("moql_field_list", []):
        name, vt = f.get("name"), f.get("value_type")
        val = f.get("value") or {}
        if vt == "string_value":
            rec[name] = val.get("string_value", "")
        elif vt in ("double_value", "long_value"):
            rec[name] = val.get(vt, 0)
        elif vt == "key_label_value":
            rec[name] = (val.get("key_label_value") or {}).get("label", "")
        elif vt == "key_label_value_list":
            rec[name] = ",".join(x.get("label", "") for x in (val.get("key_label_value_list") or []))
        else:  # 兜底：未知形状序列化保留
            rec[name] = json.dumps(val, ensure_ascii=False) if val else ""
    return rec

orders = []
for path in persisted_files:          # 每页一个 tool_*.txt，按页序排列
    outer = json.loads(open(path, encoding="utf-8").read())
    inner_raw = outer["result"]
    if "\nlog_id:" in inner_raw:      # 关键：剥离 log_id 尾迹
        inner_raw = inner_raw.split("\nlog_id:")[0]
    inner = json.loads(inner_raw)
    for it in inner["data"]["1"]:     # group_id 通常是 "1"
        rec = extract(it)
        # 业务过滤，例如剔除空白物流单号
        if (rec.get("物流单号") or "").strip():
            orders.append(rec)

json.dump({"orders": orders}, open(out_path, "w", encoding="utf-8"),
          ensure_ascii=False, indent=1)
```

## 配套：批量翻页调用序列（session_id 并行模式）

1. 第1页：正常 MQL（WHERE + ORDER BY），不传分页参数 → 拿 `session_id`、`count`。
2. 第2~N页：`mql=""`（必须空字符串）+ `session_id=...` + `group_pagination_list=[{"group_id":"1","page_num":N}]`。
   可在同一轮并发发起全部调用（实测 9 页并发成功）。
3. 后续页响应 `list:null, session_id:null`，数据仍在 `data["1"]`。
4. 实测：500 条（10页）无重复；全量场景建议按单据编号去重兜底。
