main_decision_text.py 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. from __future__ import annotations
  2. import re
  3. from typing import Any
  4. _HEADING = re.compile(r"(?m)^\s*#{1,6}\s+(.+?)\s*$")
  5. _TARGET_SENTENCE = re.compile(r"(?im)^\s*[-*]?\s*目标语句\s*[::]\s*(.+?)\s*$")
  6. _FIELD_LINE = re.compile(r"(?im)^\s*[-*]?\s*([^::\n]{1,24})\s*[::]\s*(.+?)\s*$")
  7. _BULLET = re.compile(r"^\s*(?:[-*+]\s+|[①②③④⑤⑥⑦⑧⑨⑩]\s*|\d{1,2}[.、)]\s*)")
  8. def parse_objective_text(value: Any) -> dict[str, Any]:
  9. """Extract business sections from the persisted direction without inventing text."""
  10. text = _text(value)
  11. if not text:
  12. return {
  13. "headline": "创作方向未记录",
  14. "targets": [],
  15. "topicValue": None,
  16. "accountValue": None,
  17. "domainDimensions": [],
  18. "constraints": [],
  19. "constraintItems": [],
  20. "constraintGroups": [],
  21. "targetCount": 0,
  22. "domainDimensionCount": 0,
  23. "structured": False,
  24. "raw": "",
  25. }
  26. sections = _markdown_sections(text)
  27. targets = [_clean(match.group(1)) for match in _TARGET_SENTENCE.finditer(text)]
  28. if not targets:
  29. target_section = _first_section(sections, "创作总目标", "创作目标", "总目标")
  30. targets = _meaningful_lines(target_section)[:3]
  31. constraint_groups = _objective_constraint_groups(text)
  32. domain_dimensions = [
  33. str(group.get("dimensionName"))
  34. for group in constraint_groups
  35. if group.get("kind") == "domain" and group.get("dimensionName")
  36. ]
  37. if not domain_dimensions:
  38. domain_section = _first_section(sections, "领域评估维度", "领域维度")
  39. domain_dimensions = [
  40. line
  41. for line in _meaningful_lines(domain_section)
  42. if not re.match(
  43. r"^(?:评估内容|通过标准|强制性|前置条件)\s*[::]", line
  44. )
  45. ]
  46. constraint_items = [
  47. {"group": group["title"], **item}
  48. for group in constraint_groups
  49. for item in group.get("items") or []
  50. if item.get("label") in {"通过标准", "强制性", "前置条件", "约束条件"}
  51. ]
  52. constraints = [item["value"] for item in constraint_items]
  53. topic_value = _section_preview(_first_section(sections, "选题价值主张"))
  54. account_value = _section_preview(_first_section(sections, "账号价值主张"))
  55. headline = targets[0] if targets else _first_business_sentence(text)
  56. return {
  57. "headline": headline or _clean(text),
  58. "targets": _unique(targets),
  59. "topicValue": topic_value,
  60. "accountValue": account_value,
  61. "domainDimensions": _unique(domain_dimensions),
  62. "constraints": _unique(constraints),
  63. "constraintItems": _unique_records(constraint_items),
  64. "constraintGroups": constraint_groups,
  65. "targetCount": len(_unique(targets)),
  66. "domainDimensionCount": len(_unique(domain_dimensions)),
  67. "structured": bool(sections or targets),
  68. "raw": text,
  69. }
  70. def parse_goal_text(value: Any) -> dict[str, Any]:
  71. """Return the persisted round goal as one authoritative business statement."""
  72. text = _text(value)
  73. if not text:
  74. return {
  75. "headline": "本轮目标未记录",
  76. "focusItems": [],
  77. "raw": "",
  78. }
  79. return {
  80. "headline": text,
  81. "focusItems": [],
  82. "raw": text,
  83. }
  84. def plan_path_summary(path: Any, index: int) -> str:
  85. if not isinstance(path, dict):
  86. return _clean(path) or f"方案 {index}"
  87. path_index = path.get("path_index") or index
  88. target = _clean(path.get("target"))
  89. action = _business_plan_term(path.get("action"))
  90. method = _business_plan_term(path.get("method"))
  91. detail = plan_path_detail(path)
  92. return f"方案 {path_index}:{detail}" if detail else f"方案 {path_index}"
  93. def plan_path_detail(path: Any) -> str:
  94. if not isinstance(path, dict):
  95. return _clean(path)
  96. target = _clean(path.get("target"))
  97. action = _business_plan_term(path.get("action"))
  98. method = _business_plan_term(path.get("method"))
  99. return " · ".join(value for value in (action, target, method) if value)
  100. def plan_mode_label(mode: Any, path_count: int) -> str:
  101. if path_count == 1:
  102. return "单路"
  103. value = _clean(mode)
  104. aliases = {"race": "竞争", "赛马": "竞争", "divide": "分工"}
  105. return aliases.get(value.lower(), value or "方式未记录")
  106. def preview(value: Any, limit: int = 140) -> str | None:
  107. text = _clean(value)
  108. if not text:
  109. return None
  110. if len(text) <= limit:
  111. return text
  112. cut = text[: limit + 1]
  113. punctuation = max(cut.rfind(token) for token in "。!?;")
  114. if punctuation >= max(24, int(limit * 0.55)):
  115. return cut[: punctuation + 1]
  116. return text[: limit - 1].rstrip() + "…"
  117. def _markdown_sections(text: str) -> dict[str, str]:
  118. matches = list(_HEADING.finditer(text))
  119. sections: dict[str, str] = {}
  120. for index, match in enumerate(matches):
  121. title = _clean(match.group(1)).replace("**", "")
  122. end = matches[index + 1].start() if index + 1 < len(matches) else len(text)
  123. body = text[match.end() : end].strip()
  124. if title and body:
  125. sections[title] = body
  126. return sections
  127. def _first_section(sections: dict[str, str], *labels: str) -> str | None:
  128. for label in labels:
  129. for title, body in sections.items():
  130. if label in title:
  131. return body
  132. return None
  133. def _objective_constraint_groups(text: str) -> list[dict[str, Any]]:
  134. headings = list(_HEADING.finditer(text))
  135. groups: list[dict[str, Any]] = []
  136. for index, heading in enumerate(headings):
  137. title = _clean(heading.group(1)).replace("**", "")
  138. if not re.match(r"^(?:目标\s*\d+|领域维度\s*\d+)", title):
  139. continue
  140. end = headings[index + 1].start() if index + 1 < len(headings) else len(text)
  141. body = text[heading.end() : end]
  142. fields = [
  143. {"label": label.strip(), "value": _clean(value)}
  144. for label, value in _FIELD_LINE.findall(body)
  145. if label.strip() in {
  146. "name",
  147. "维度名",
  148. "名称",
  149. "评估内容",
  150. "通过标准",
  151. "强制性",
  152. "前置条件",
  153. "约束条件",
  154. }
  155. and _clean(value)
  156. ]
  157. dimension_name = next(
  158. (
  159. item["value"]
  160. for item in fields
  161. if item["label"].lower() in {"name", "维度名", "名称"}
  162. ),
  163. None,
  164. )
  165. business_items = [
  166. item
  167. for item in fields
  168. if item["label"].lower() not in {"name", "维度名", "名称"}
  169. ]
  170. if not business_items and not dimension_name:
  171. continue
  172. groups.append(
  173. {
  174. "title": title,
  175. "kind": "domain" if title.startswith("领域维度") else "target",
  176. "dimensionName": dimension_name,
  177. "items": business_items,
  178. }
  179. )
  180. return groups
  181. def _section_preview(value: str | None) -> str | None:
  182. lines = _meaningful_lines(value)
  183. return lines[0] if lines else None
  184. def _meaningful_lines(value: Any) -> list[str]:
  185. result: list[str] = []
  186. for raw in _text(value).splitlines():
  187. line = _clean(_BULLET.sub("", raw))
  188. if not line or line.startswith("#"):
  189. continue
  190. if re.match(r"^(?:评估维度|评估内容|通过标准|强制性|前置条件|name)\s*[::]", line, re.I):
  191. continue
  192. result.append(line)
  193. return _unique(result)
  194. def _first_business_sentence(text: str) -> str:
  195. without_headings = _HEADING.sub("", text)
  196. lines = _meaningful_lines(without_headings)
  197. return lines[0] if lines else _clean(text)
  198. def _clean(value: Any) -> str:
  199. text = str(value or "").replace("\\n", "\n")
  200. text = re.sub(r"[`*_]", "", text)
  201. text = re.sub(r"\s+", " ", text).strip(" #::")
  202. return text
  203. def _text(value: Any) -> str:
  204. return str(value or "").replace("\r\n", "\n").replace("\\n", "\n").strip()
  205. def _unique(values: list[str]) -> list[str]:
  206. result: list[str] = []
  207. for value in values:
  208. if value and value not in result:
  209. result.append(value)
  210. return result
  211. def _unique_records(values: list[dict[str, str]]) -> list[dict[str, str]]:
  212. result: list[dict[str, str]] = []
  213. seen: set[tuple[str, str, str]] = set()
  214. for value in values:
  215. key = (
  216. value.get("group") or "",
  217. value.get("label") or "",
  218. value.get("value") or "",
  219. )
  220. if key not in seen:
  221. seen.add(key)
  222. result.append(value)
  223. return result
  224. def _business_plan_term(value: Any) -> str:
  225. text = _clean(value)
  226. return {
  227. "增": "新增",
  228. "改": "修改",
  229. "删": "删除",
  230. "组": "组合",
  231. "产生脉络": "形成段落结构",
  232. "产生元素": "补充脚本元素",
  233. }.get(text, text)