1
0

test_status_reporter.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. import json
  4. import tempfile
  5. from data_modules.config import DataModulesConfig
  6. from data_modules.index_manager import IndexManager, ChapterReadingPowerMeta
  7. from status_reporter import StatusReporter
  8. def _write_state(project_root, state: dict):
  9. webnovel_dir = project_root / ".webnovel"
  10. webnovel_dir.mkdir(parents=True, exist_ok=True)
  11. (webnovel_dir / "state.json").write_text(
  12. json.dumps(state, ensure_ascii=False, indent=2),
  13. encoding="utf-8",
  14. )
  15. def test_foreshadowing_analysis_uses_real_chapters_and_handles_missing_data():
  16. with tempfile.TemporaryDirectory() as tmpdir:
  17. project_root = DataModulesConfig.from_project_root(tmpdir).project_root
  18. state = {
  19. "progress": {"current_chapter": 120, "total_words": 360000},
  20. "plot_threads": {
  21. "foreshadowing": [
  22. {
  23. "content": "林家宝库铭文的秘密",
  24. "status": "未回收",
  25. "tier": "核心",
  26. "planted_chapter": 20,
  27. "target_chapter": 100,
  28. },
  29. {
  30. "content": "神秘玉佩来历",
  31. "status": "待回收",
  32. "tier": "支线",
  33. "added_chapter": 50,
  34. "target": 150,
  35. },
  36. {
  37. "content": "旧日誓言",
  38. "status": "未回收",
  39. "tier": "装饰",
  40. },
  41. {
  42. "content": "已完成伏笔",
  43. "status": "已回收",
  44. "planted_chapter": 10,
  45. "target_chapter": 20,
  46. },
  47. ]
  48. },
  49. }
  50. _write_state(project_root, state)
  51. reporter = StatusReporter(str(project_root))
  52. assert reporter.load_state() is True
  53. foreshadowing = reporter.analyze_foreshadowing()
  54. assert len(foreshadowing) == 3
  55. records = {item["content"]: item for item in foreshadowing}
  56. assert records["林家宝库铭文的秘密"]["planted_chapter"] == 20
  57. assert records["林家宝库铭文的秘密"]["elapsed"] == 100
  58. assert records["林家宝库铭文的秘密"]["status"] == "🔴 已超期"
  59. assert records["神秘玉佩来历"]["planted_chapter"] == 50
  60. assert records["神秘玉佩来历"]["target_chapter"] == 150
  61. assert records["神秘玉佩来历"]["status"] in {"🟡 轻度超时", "🟢 正常"}
  62. assert records["旧日誓言"]["planted_chapter"] is None
  63. assert records["旧日誓言"]["status"] == "⚪ 数据不足"
  64. urgency = reporter.analyze_foreshadowing_urgency()
  65. urgency_by_content = {item["content"]: item for item in urgency}
  66. assert urgency_by_content["林家宝库铭文的秘密"]["urgency"] is not None
  67. assert urgency_by_content["林家宝库铭文的秘密"]["status"] == "🔴 已超期"
  68. assert urgency_by_content["旧日誓言"]["urgency"] is None
  69. assert urgency_by_content["旧日誓言"]["status"] == "⚪ 数据不足"
  70. def test_pacing_analysis_prefers_real_coolpoint_metadata_over_estimation():
  71. with tempfile.TemporaryDirectory() as tmpdir:
  72. config = DataModulesConfig.from_project_root(tmpdir)
  73. config.ensure_dirs()
  74. project_root = config.project_root
  75. state = {
  76. "progress": {"current_chapter": 3, "total_words": 12000},
  77. "chapter_meta": {
  78. "0003": {
  79. "hook": "下章有变",
  80. "coolpoint_patterns": ["身份掉马", "反派翻车"],
  81. }
  82. },
  83. }
  84. _write_state(project_root, state)
  85. idx = IndexManager(config)
  86. idx.save_chapter_reading_power(
  87. ChapterReadingPowerMeta(
  88. chapter=1,
  89. hook_type="渴望钩",
  90. hook_strength="strong",
  91. coolpoint_patterns=["打脸权威", "身份掉马"],
  92. )
  93. )
  94. idx.save_chapter_reading_power(
  95. ChapterReadingPowerMeta(
  96. chapter=2,
  97. hook_type="悬念钩",
  98. hook_strength="medium",
  99. coolpoint_patterns=["身份掉马"],
  100. )
  101. )
  102. reporter = StatusReporter(str(project_root))
  103. assert reporter.load_state() is True
  104. reporter.chapters_data = [
  105. {"chapter": 1, "word_count": 4000, "cool_point": "", "dominant": "", "characters": []},
  106. {"chapter": 2, "word_count": 3000, "cool_point": "", "dominant": "", "characters": []},
  107. {"chapter": 3, "word_count": 5000, "cool_point": "", "dominant": "", "characters": []},
  108. ]
  109. segments = reporter.analyze_pacing()
  110. assert len(segments) == 1
  111. seg = segments[0]
  112. assert seg["cool_points"] == 5
  113. assert round(seg["words_per_point"], 2) == 2400.00
  114. assert seg["missing_chapters"] == 0
  115. assert seg["dominant_source"] == "chapter_reading_power"
  116. def test_pacing_analysis_marks_missing_data_instead_of_assuming_one_point_per_chapter():
  117. with tempfile.TemporaryDirectory() as tmpdir:
  118. config = DataModulesConfig.from_project_root(tmpdir)
  119. config.ensure_dirs()
  120. project_root = config.project_root
  121. state = {
  122. "progress": {"current_chapter": 1, "total_words": 2000},
  123. "chapter_meta": {},
  124. }
  125. _write_state(project_root, state)
  126. reporter = StatusReporter(str(project_root))
  127. assert reporter.load_state() is True
  128. reporter.chapters_data = [
  129. {"chapter": 1, "word_count": 2000, "cool_point": "", "dominant": "", "characters": []}
  130. ]
  131. seg = reporter.analyze_pacing()[0]
  132. assert seg["cool_points"] == 0
  133. assert seg["words_per_point"] is None
  134. assert seg["rating"] == "数据不足"
  135. assert seg["missing_chapters"] == 1