feat: initial commit
This commit is contained in:
@@ -0,0 +1,207 @@
|
||||
"""
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user