rebuild_payloads.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. """从 web/frameworks.json 重建 web/payloads.json(不调 LLM)——确定性。
  2. 顺带两个守卫修正:
  3. · 组件颗的 parent.how_id 必须指向同帖一个 how 颗,否则降级为 orphan 主颗(role=主, parent=null)。
  4. · 组件颗 payload 的「出自」用所属 how 的 title(而非裸 id)。
  5. 用法:python3 scripts/rebuild_payloads.py
  6. """
  7. from __future__ import annotations
  8. import json
  9. from pathlib import Path
  10. ROOT = Path(__file__).resolve().parent.parent
  11. FW = ROOT / "web/frameworks.json"
  12. PL = ROOT / "web/payloads.json"
  13. TYPE2ATTR = {"how": "how", "what": "what", "why": "why"}
  14. KINDS = {"子集", "多维关系", "序列"}
  15. KIND_FIX = {"多维度关系": "多维关系", "多维": "多维关系", "集合": "子集", "序列型": "序列"} # LLM 偶发变体归一
  16. def _sections(blocks) -> list:
  17. out = []
  18. for b in blocks or []:
  19. head = b.get("小标题") or ""
  20. form = b.get("形式")
  21. out.append(f"【{head}】" + (f"({form})" if form else ""))
  22. if b.get("内容"):
  23. out.append(f" {b['内容']}")
  24. for it in b.get("条目") or []:
  25. word = it.get("词") or it.get("要素") or ""
  26. cue = it.get("选择线索")
  27. line = f" - {word}:{it.get('说明','')}" if word else f" - {it.get('说明','')}"
  28. if cue:
  29. line += f"(选用:{cue})"
  30. out.append(line)
  31. return out
  32. def content(k: dict):
  33. """How → 拍平字符串;What → 结构化实体对象 {name,kind,维度拆分规则,body[]};Why → 结构化对象 {name,desc}。"""
  34. t = k["type"]
  35. if t == "how":
  36. L = [f"目标:{k.get('purpose','')}"]
  37. for i, s in enumerate(k.get("steps", []), 1):
  38. L += [f"步骤{i}", f" 输入:{s.get('input','')}",
  39. f" 方法:{s.get('directive','')}", f" 产出:{s.get('output','')}"]
  40. return "\n".join(L)
  41. if t == "what": # 结构化实体;概要走 custom_ext
  42. return {"name": k.get("title") or "", "kind": k.get("kind"),
  43. "维度拆分规则": k.get("维度拆分规则") if k.get("kind") == "子集" else None,
  44. "body": [{"item_name": it.get("item_name", ""), "item_desc": it.get("item_desc", ""),
  45. "作用域": it.get("作用域") or []} for it in (k.get("body") or [])]}
  46. return {"name": k.get("title") or "", "desc": k.get("阐述") or ""}
  47. def main() -> None:
  48. d = json.loads(FW.read_text(encoding="utf-8"))
  49. payloads = []
  50. demoted = 0
  51. for p in d["posts"]:
  52. ks = p["knowledges"]
  53. how_ids = {k["id"] for k in ks if k["type"] == "how"}
  54. how_titles = {k["id"]: k.get("title") for k in ks if k["type"] == "how"}
  55. for k in ks:
  56. k["业务阶段"] = [b for b in (k.get("业务阶段") or []) if b in ("灵感", "选题", "脚本")]
  57. if k.get("type") == "what" and k.get("kind") not in KINDS: # what.kind 受控值归一
  58. k["kind"] = KIND_FIX.get((k.get("kind") or "").strip(), k.get("kind"))
  59. for s in k.get("steps", []):
  60. if s.get("创作阶段") not in ("定向", "构思", "结构", "成文", "打磨"):
  61. s["创作阶段"] = None
  62. if k.get("role") == "组件" and (k.get("parent") or {}).get("how_id") not in how_ids:
  63. k["role"] = "主"
  64. k["parent"] = None
  65. demoted += 1
  66. for k in ks:
  67. t = k["type"]
  68. scopes, seen = [], set()
  69. def add(lst):
  70. for sc in lst:
  71. key = (sc["scope_type"], sc["value"])
  72. if key not in seen:
  73. seen.add(key)
  74. scopes.append({"scope_type": sc["scope_type"], "value": sc["value"]})
  75. if t == "how":
  76. for s in k.get("steps", []):
  77. add(s.get("作用域", []))
  78. else:
  79. add(k.get("作用域", []))
  80. ext = [{"key": "业务阶段", "type": "str", "value": v} for v in (k.get("业务阶段") or [])]
  81. if t == "what" and k.get("概要"):
  82. ext.append({"key": "概要", "type": "str", "value": k["概要"]})
  83. if t == "how":
  84. cs, cseen = [], set()
  85. for s in k.get("steps", []):
  86. c = s.get("创作阶段")
  87. if c and c not in cseen:
  88. cseen.add(c)
  89. cs.append(c)
  90. ext += [{"key": "创作阶段", "type": "str", "value": v} for v in cs]
  91. ext += [{"key": "动作", "type": "str", "value": s["动作"]} for s in k.get("steps", []) if s.get("动作")]
  92. if k.get("role") == "组件" and k.get("parent"):
  93. par = k["parent"]
  94. ext.append({"key": "出自", "type": "str",
  95. "value": f"{how_titles.get(par.get('how_id'), par.get('how_id'))} 第{par.get('step')}步"})
  96. payloads.append({
  97. "source": {"id": p.get("source_id") or f"xhs_{p['post_id']}", "source_type": "post", "title": p.get("title", ""),
  98. "author": "", "source_metadata": {"platform": p.get("platform"), "url": p.get("url")}},
  99. "title": k.get("title"),
  100. "content": (lambda c: c if isinstance(c, str) else json.dumps(c, ensure_ascii=False))(content(k)),
  101. "dim_creations": ["创作"],
  102. "dim_attributes": [TYPE2ATTR.get(t, "how")], "scopes": scopes, "custom_ext": ext})
  103. FW.write_text(json.dumps(d, ensure_ascii=False, indent=1), encoding="utf-8")
  104. PL.write_text(json.dumps(payloads, ensure_ascii=False, indent=2), encoding="utf-8")
  105. print(f"rebuilt {len(payloads)} payloads; 降级组件→orphan: {demoted}")
  106. if __name__ == "__main__":
  107. main()