""" Reader-Pull Checker (追读力检查器) 检查钩子强度、期待管理、追读力: - 章节开头的钩子 - 章节结尾的悬念 - 追读力债务 """ from dataclasses import dataclass, field from typing import List, Dict, Any, Optional @dataclass class ReaderPullIssue: """追读力问题""" chapter: int issue_type: str # weak_hook / no_ending_hook / debt_high description: str severity: str @dataclass class ReaderPullReport: """追读力检查报告""" chapter: int hook_strength: float # 0-1 ending_hook: bool debt_level: float issues: List[ReaderPullIssue] = field(default_factory=list) passed: bool = True summary: str = "" class ReaderPullChecker: """ 追读力检查器 检查: 1. 章节开头的钩子强度 2. 章节结尾的悬念 3. 追读力债务水平 """ # 强钩子关键词 STRONG_HOOK_KEYWORDS = [ "危机", "危险", "困境", "生死", "抉择", "真相", "震惊", "意外", "转折", "揭秘", "冲突", "大战" ] # 结尾悬念关键词 ENDING_HOOK_KEYWORDS = [ "然而", "但是", "就在这时", "突然", "意外", "未完待续", "敬请期待", "欲知后事", "悬念", "却不知道", "更大的" ] # 悬念关键词 SUSPENSE_KEYWORDS = [ "悬念", "伏笔", "暗示", "预示", "危机", "威胁" ] def __init__(self): self.chapter_debts: List[float] = [] def check_chapter( self, chapter_num: int, chapter_text: str, previous_debt: float = 0.0 ) -> ReaderPullReport: """检查单章追读力""" # 分析开头钩子 opening = chapter_text[:500] if len(chapter_text) > 500 else chapter_text hook_strength = self._analyze_hook_strength(opening) # 分析结尾悬念 ending = chapter_text[-500:] if len(chapter_text) > 500 else chapter_text ending_hook = self._has_ending_hook(ending) # 计算本章追读力债务 debt_level = self._calculate_debt_level(chapter_text, hook_strength, ending_hook) report = ReaderPullReport( chapter=chapter_num, hook_strength=hook_strength, ending_hook=ending_hook, debt_level=debt_level ) # 检测问题 if hook_strength < 0.3: report.issues.append(ReaderPullIssue( chapter=chapter_num, issue_type="weak_hook", description="章节开头钩子较弱,可能无法吸引读者", severity="medium" )) if not ending_hook: report.issues.append(ReaderPullIssue( chapter=chapter_num, issue_type="no_ending_hook", description="章节结尾缺少悬念,读者缺乏继续阅读的动力", severity="high" )) # 检查追读力债务 total_debt = previous_debt + debt_level if total_debt > 80: report.issues.append(ReaderPullIssue( chapter=chapter_num, issue_type="debt_high", description=f"追读力债务过高({total_debt:.0f}),建议释放", severity="critical" )) self.chapter_debts.append(debt_level) report.passed = len([i for i in report.issues if i.severity == "critical"]) == 0 if report.passed: report.summary = f"钩子强度 {hook_strength:.0%},追读力债务 {total_debt:.0f}" else: report.summary = f"发现问题: {', '.join(i.issue_type for i in report.issues)}" return report def _analyze_hook_strength(self, opening: str) -> float: """分析钩子强度""" if not opening: return 0.0 strength = 0.3 # 基础分 # 检查强钩子关键词 strong_count = sum(1 for kw in self.STRONG_HOOK_KEYWORDS if kw in opening) strength += min(0.4, strong_count * 0.1) # 检查疑问句 question_count = opening.count('?') + opening.count('?') strength += min(0.2, question_count * 0.1) # 检查省略号(制造悬念) ellipsis_count = opening.count('...') strength += min(0.1, ellipsis_count * 0.05) return min(1.0, strength) def _has_ending_hook(self, ending: str) -> bool: """检查结尾是否有悬念""" if not ending: return False # 检查结尾悬念关键词 for kw in self.ENDING_HOOK_KEYWORDS: if kw in ending: return True # 检查是否以冲突/悬念结尾 suspense_ending = ["?", "!", "……"] if any(ending.strip().endswith(s) for s in suspense_ending): return True return False def _calculate_debt_level( self, text: str, hook_strength: float, ending_hook: bool ) -> float: """计算追读力债务""" debt = 0.0 # 无开头钩子 if hook_strength < 0.3: debt += 20 # 无结尾悬念 if not ending_hook: debt += 30 # 积累的悬念/伏笔 unresolved_count = 0 for kw in self.SUSPENSE_KEYWORDS: unresolved_count += text.count(kw) debt += min(30, unresolved_count * 5) # 钩子强度可以抵消部分债务 debt -= hook_strength * 20 return max(0, min(100, debt)) def get_arc_reader_pull(self, start_chapter: int, end_chapter: int) -> Dict[str, Any]: """分析情节弧的追读力""" if not self.chapter_debts: return {"status": "no_data"} relevant = self.chapter_debts[start_chapter - 1:end_chapter] return { "chapters": f"{start_chapter}-{end_chapter}", "avg_hook_strength": sum(h.hook_strength for h in self.chapter_debts[start_chapter-1:end_chapter]) / len(relevant) if relevant else 0, "avg_debt": sum(relevant) / len(relevant) if relevant else 0, "debt_trend": relevant[-1] - relevant[0] if len(relevant) > 1 else 0 }