| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382 |
- #!/usr/bin/env python3
- # -*- coding: utf-8 -*-
- """
- IndexReadingMixin extracted from IndexManager.
- """
- from __future__ import annotations
- import json
- import sys
- from datetime import datetime
- from typing import Any, Dict, List, Optional
- class IndexReadingMixin:
- def save_chapter_reading_power(self, meta: ChapterReadingPowerMeta):
- """保存章节追读力元数据"""
- with self._get_conn() as conn:
- cursor = conn.cursor()
- cursor.execute(
- """
- INSERT OR REPLACE INTO chapter_reading_power
- (chapter, hook_type, hook_strength, coolpoint_patterns,
- micropayoffs, hard_violations, soft_suggestions,
- is_transition, override_count, debt_balance)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
- """,
- (
- meta.chapter,
- meta.hook_type,
- meta.hook_strength,
- json.dumps(meta.coolpoint_patterns, ensure_ascii=False),
- json.dumps(meta.micropayoffs, ensure_ascii=False),
- json.dumps(meta.hard_violations, ensure_ascii=False),
- json.dumps(meta.soft_suggestions, ensure_ascii=False),
- 1 if meta.is_transition else 0,
- meta.override_count,
- meta.debt_balance,
- ),
- )
- conn.commit()
- def get_chapter_reading_power(self, chapter: int) -> Optional[Dict]:
- """获取章节追读力元数据"""
- with self._get_conn() as conn:
- cursor = conn.cursor()
- cursor.execute(
- "SELECT * FROM chapter_reading_power WHERE chapter = ?", (chapter,)
- )
- row = cursor.fetchone()
- if row:
- return self._row_to_dict(
- row,
- parse_json=[
- "coolpoint_patterns",
- "micropayoffs",
- "hard_violations",
- "soft_suggestions",
- ],
- )
- return None
- def get_recent_reading_power(self, limit: int = 10) -> List[Dict]:
- """获取最近章节的追读力元数据"""
- with self._get_conn() as conn:
- cursor = conn.cursor()
- cursor.execute(
- """
- SELECT * FROM chapter_reading_power
- ORDER BY chapter DESC
- LIMIT ?
- """,
- (limit,),
- )
- return [
- self._row_to_dict(
- row,
- parse_json=[
- "coolpoint_patterns",
- "micropayoffs",
- "hard_violations",
- "soft_suggestions",
- ],
- )
- for row in cursor.fetchall()
- ]
- def get_pattern_usage_stats(self, last_n_chapters: int = 20) -> Dict[str, int]:
- """获取最近N章的爽点模式使用统计"""
- with self._get_conn() as conn:
- cursor = conn.cursor()
- cursor.execute(
- """
- SELECT coolpoint_patterns FROM chapter_reading_power
- ORDER BY chapter DESC
- LIMIT ?
- """,
- (last_n_chapters,),
- )
- stats = {}
- for row in cursor.fetchall():
- if row["coolpoint_patterns"]:
- try:
- patterns = json.loads(row["coolpoint_patterns"])
- for p in patterns:
- stats[p] = stats.get(p, 0) + 1
- except json.JSONDecodeError as exc:
- print(
- f"[index_manager] failed to parse JSON in chapter_reading_power.coolpoint_patterns: {exc}",
- file=sys.stderr,
- )
- return stats
- def get_hook_type_stats(self, last_n_chapters: int = 20) -> Dict[str, int]:
- """获取最近N章的钩子类型使用统计"""
- with self._get_conn() as conn:
- cursor = conn.cursor()
- cursor.execute(
- """
- SELECT hook_type FROM chapter_reading_power
- WHERE hook_type IS NOT NULL AND hook_type != ''
- ORDER BY chapter DESC
- LIMIT ?
- """,
- (last_n_chapters,),
- )
- stats = {}
- for row in cursor.fetchall():
- hook = row["hook_type"]
- stats[hook] = stats.get(hook, 0) + 1
- return stats
- # ==================== v5.4 审查指标 ====================
- def save_review_metrics(self, metrics: ReviewMetrics) -> None:
- """保存审查指标记录"""
- with self._get_conn() as conn:
- cursor = conn.cursor()
- cursor.execute(
- """
- INSERT INTO review_metrics
- (start_chapter, end_chapter, overall_score, dimension_scores,
- severity_counts, critical_issues, report_file, notes, created_at, updated_at)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
- ON CONFLICT(start_chapter, end_chapter)
- DO UPDATE SET
- overall_score = excluded.overall_score,
- dimension_scores = excluded.dimension_scores,
- severity_counts = excluded.severity_counts,
- critical_issues = excluded.critical_issues,
- report_file = excluded.report_file,
- notes = excluded.notes,
- updated_at = CURRENT_TIMESTAMP
- """,
- (
- metrics.start_chapter,
- metrics.end_chapter,
- metrics.overall_score,
- json.dumps(metrics.dimension_scores, ensure_ascii=False),
- json.dumps(metrics.severity_counts, ensure_ascii=False),
- json.dumps(metrics.critical_issues, ensure_ascii=False),
- metrics.report_file,
- metrics.notes,
- ),
- )
- conn.commit()
- def get_recent_review_metrics(self, limit: int = 5) -> List[Dict]:
- """获取最近审查记录"""
- with self._get_conn() as conn:
- cursor = conn.cursor()
- cursor.execute(
- """
- SELECT * FROM review_metrics
- ORDER BY end_chapter DESC, start_chapter DESC
- LIMIT ?
- """,
- (limit,),
- )
- return [
- self._row_to_dict(
- row,
- parse_json=["dimension_scores", "severity_counts", "critical_issues"],
- )
- for row in cursor.fetchall()
- ]
- def get_review_trend_stats(self, last_n: int = 5) -> Dict[str, Any]:
- """获取审查趋势统计"""
- records = self.get_recent_review_metrics(last_n)
- if not records:
- return {
- "count": 0,
- "overall_avg": 0.0,
- "dimension_avg": {},
- "severity_totals": {},
- "recent_ranges": [],
- }
- overall_scores: List[float] = []
- dimension_totals: Dict[str, float] = {}
- dimension_counts: Dict[str, int] = {}
- severity_totals: Dict[str, int] = {}
- for record in records:
- score = record.get("overall_score")
- if score is not None:
- try:
- overall_scores.append(float(score))
- except (TypeError, ValueError):
- pass
- dimensions = record.get("dimension_scores") or {}
- if isinstance(dimensions, dict):
- for key, value in dimensions.items():
- try:
- val = float(value)
- except (TypeError, ValueError):
- continue
- dimension_totals[key] = dimension_totals.get(key, 0.0) + val
- dimension_counts[key] = dimension_counts.get(key, 0) + 1
- severities = record.get("severity_counts") or {}
- if isinstance(severities, dict):
- for key, value in severities.items():
- try:
- count = int(value)
- except (TypeError, ValueError):
- continue
- severity_totals[key] = severity_totals.get(key, 0) + count
- overall_avg = round(sum(overall_scores) / len(overall_scores), 2) if overall_scores else 0.0
- dimension_avg = {
- key: round(dimension_totals[key] / dimension_counts[key], 2)
- for key in dimension_totals
- if dimension_counts.get(key, 0) > 0
- }
- recent_ranges = [
- {
- "start_chapter": record.get("start_chapter"),
- "end_chapter": record.get("end_chapter"),
- "overall_score": record.get("overall_score", 0),
- }
- for record in records
- ]
- return {
- "count": len(records),
- "overall_avg": overall_avg,
- "dimension_avg": dimension_avg,
- "severity_totals": severity_totals,
- "recent_ranges": recent_ranges,
- }
- # ==================== 写作清单评分(Phase F) ====================
- def save_writing_checklist_score(self, meta: WritingChecklistScoreMeta) -> None:
- """保存章节写作清单评分。"""
- with self._get_conn() as conn:
- cursor = conn.cursor()
- cursor.execute(
- """
- INSERT INTO writing_checklist_scores (
- chapter, template, total_items, required_items,
- completed_items, completed_required,
- total_weight, completed_weight, completion_rate, score,
- score_breakdown, pending_items, source, notes
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
- ON CONFLICT(chapter) DO UPDATE SET
- template=excluded.template,
- total_items=excluded.total_items,
- required_items=excluded.required_items,
- completed_items=excluded.completed_items,
- completed_required=excluded.completed_required,
- total_weight=excluded.total_weight,
- completed_weight=excluded.completed_weight,
- completion_rate=excluded.completion_rate,
- score=excluded.score,
- score_breakdown=excluded.score_breakdown,
- pending_items=excluded.pending_items,
- source=excluded.source,
- notes=excluded.notes,
- updated_at=CURRENT_TIMESTAMP
- """,
- (
- meta.chapter,
- meta.template,
- meta.total_items,
- meta.required_items,
- meta.completed_items,
- meta.completed_required,
- meta.total_weight,
- meta.completed_weight,
- meta.completion_rate,
- meta.score,
- json.dumps(meta.score_breakdown, ensure_ascii=False),
- json.dumps(meta.pending_items, ensure_ascii=False),
- meta.source,
- meta.notes,
- ),
- )
- conn.commit()
- def get_writing_checklist_score(self, chapter: int) -> Optional[Dict[str, Any]]:
- """获取指定章节的写作清单评分。"""
- with self._get_conn() as conn:
- cursor = conn.cursor()
- cursor.execute(
- "SELECT * FROM writing_checklist_scores WHERE chapter = ?",
- (chapter,),
- )
- row = cursor.fetchone()
- if not row:
- return None
- return self._row_to_dict(row, parse_json=["score_breakdown", "pending_items"])
- def get_recent_writing_checklist_scores(self, limit: int = 10) -> List[Dict[str, Any]]:
- """获取最近章节写作清单评分。"""
- with self._get_conn() as conn:
- cursor = conn.cursor()
- cursor.execute(
- """
- SELECT * FROM writing_checklist_scores
- ORDER BY chapter DESC
- LIMIT ?
- """,
- (limit,),
- )
- return [
- self._row_to_dict(row, parse_json=["score_breakdown", "pending_items"])
- for row in cursor.fetchall()
- ]
- def get_writing_checklist_score_trend(self, last_n: int = 10) -> Dict[str, Any]:
- """获取写作清单评分趋势统计。"""
- records = self.get_recent_writing_checklist_scores(limit=max(1, int(last_n)))
- if not records:
- return {
- "count": 0,
- "score_avg": 0.0,
- "completion_avg": 0.0,
- "required_completion_avg": 0.0,
- "recent": [],
- }
- scores: List[float] = []
- completion_rates: List[float] = []
- required_rates: List[float] = []
- for row in records:
- try:
- scores.append(float(row.get("score", 0.0)))
- except (TypeError, ValueError):
- pass
- try:
- completion_rates.append(float(row.get("completion_rate", 0.0)))
- except (TypeError, ValueError):
- pass
- required_items = int(row.get("required_items") or 0)
- completed_required = int(row.get("completed_required") or 0)
- if required_items > 0:
- required_rates.append(completed_required / required_items)
- else:
- required_rates.append(1.0)
- return {
- "count": len(records),
- "score_avg": round(sum(scores) / len(scores), 2) if scores else 0.0,
- "completion_avg": round(sum(completion_rates) / len(completion_rates), 4) if completion_rates else 0.0,
- "required_completion_avg": round(sum(required_rates) / len(required_rates), 4) if required_rates else 0.0,
- "recent": [
- {
- "chapter": row.get("chapter"),
- "score": row.get("score"),
- "completion_rate": row.get("completion_rate"),
- }
- for row in records
- ],
- }
|