a6f05ab2d5
- Phase 0: AGENTS.md cleanup (dedup quotes, renumber sections, merge qmd) - Phase 1: typed relations (manage-relations.py, graph-search.py, check-staleness.py, detect-conflicts.py) - Phase 2: frontmatter validator, weekly lint, knowledge promotion, git hooks - Fix .gitignore to track tools/ and .githooks/ - Fix git remote URL (remove plaintext token) - New wiki pages: 504 pages, 34 raw sources
612 lines
20 KiB
Python
612 lines
20 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
文献质量评估报告生成器
|
||
|
||
功能:
|
||
1. 交互式生成单篇文献评估报告
|
||
2. 批量生成多篇文献评估报告
|
||
3. 自动计算部分分数(时效性、信源权威性)
|
||
4. 手动输入其他维度分数
|
||
5. 生成标准OFM格式评估报告
|
||
|
||
使用方式:
|
||
python 质量评估报告生成器.py # 交互式单篇评估
|
||
python 质量评估报告生成器.py --batch # 批量评估(从文献库JSON读取)
|
||
python 质量评估报告生成器.py --help # 查看帮助
|
||
"""
|
||
|
||
import sys
|
||
import os
|
||
import json
|
||
import argparse
|
||
from datetime import datetime
|
||
from pathlib import Path
|
||
|
||
# 配置路径
|
||
BASE_DIR = Path(__file__).parent.parent
|
||
LIBRARY_DIR = BASE_DIR / "文献库"
|
||
LIBRARY_JSON = LIBRARY_DIR / "文献索引数据库.json"
|
||
OUTPUT_DIR = BASE_DIR / "文献库"
|
||
|
||
|
||
class QualityAssessment:
|
||
"""文献质量评估系统"""
|
||
|
||
def __init__(self):
|
||
self.current_year = datetime.now().year
|
||
self.assessment_dimensions = {
|
||
"timeliness": {"name": "时效性", "max_score": 15},
|
||
"source_authority": {"name": "信源权威性", "max_score": 20},
|
||
"data_quality": {"name": "数据质量", "max_score": 20},
|
||
"method_rigor": {"name": "方法严谨性", "max_score": 20},
|
||
"empirical_basis": {"name": "实证基础", "max_score": 20},
|
||
"theoretical_framework": {"name": "理论框架", "max_score": 15},
|
||
"impact": {"name": "影响力", "max_score": 15}
|
||
}
|
||
|
||
def calculate_timeliness_score(self, year):
|
||
"""
|
||
计算时效性分数(15分制)
|
||
|
||
规则:
|
||
- 2026年(当年):15分
|
||
- 2025年:12分
|
||
- 2024年:9分
|
||
- 2023年:6分
|
||
- 2022年及以前:3分
|
||
"""
|
||
age = self.current_year - year
|
||
if age == 0:
|
||
return 15
|
||
elif age == 1:
|
||
return 12
|
||
elif age == 2:
|
||
return 9
|
||
elif age == 3:
|
||
return 6
|
||
else:
|
||
return 3
|
||
|
||
def calculate_source_authority(self, source_info):
|
||
"""
|
||
计算信源权威性分数(20分制)
|
||
|
||
规则:
|
||
- Nature/Science/Cell:20分
|
||
- 顶刊/顶会(如Scientific Reports、Computers and Education、AIED):18分
|
||
- 一区期刊(如Frontiers系列、IEEE/ACM):16分
|
||
- 顶级会议(如arXiv预印本):16分
|
||
- ERIC/官方报告:18分
|
||
- 其他知名期刊:14分
|
||
- 未明确来源:12分
|
||
"""
|
||
if not source_info:
|
||
return 12
|
||
|
||
source_lower = source_info.lower()
|
||
|
||
# 顶级期刊
|
||
if any(j in source_lower for j in ["nature", "science", "cell"]):
|
||
return 20
|
||
|
||
# 顶刊/顶会
|
||
if any(j in source_lower for j in ["scientific reports", "computers and education", "aied", "eric"]):
|
||
return 18
|
||
|
||
# 一区期刊/顶会
|
||
if any(j in source_lower for j in ["frontiers", "ieee", "acm", "arxiv", "preprint"]):
|
||
return 16
|
||
|
||
# 官方报告/政策文件
|
||
if any(j in source_lower for j in ["report", "guidelines", "whitepaper"]):
|
||
return 18
|
||
|
||
# 知名期刊
|
||
if any(j in source_lower for j in ["springer", "taylor", "elsevier", "sage"]):
|
||
return 14
|
||
|
||
# 未明确来源
|
||
return 12
|
||
|
||
def input_score(self, dimension_key, auto_score=None):
|
||
"""
|
||
交互式输入分数
|
||
|
||
Args:
|
||
dimension_key: 维度键值
|
||
auto_score: 自动计算的分数(如有)
|
||
|
||
Returns:
|
||
int: 输入的分数
|
||
"""
|
||
dim = self.assessment_dimensions[dimension_key]
|
||
dim_name = dim["name"]
|
||
max_score = dim["max_score"]
|
||
|
||
if auto_score is not None:
|
||
print(f"\n📊 {dim_name}({max_score}分):自动计算得 {auto_score} 分")
|
||
confirm = input("是否确认?(Y/n,默认Y): ").strip().lower()
|
||
if confirm != "n":
|
||
return auto_score
|
||
|
||
while True:
|
||
try:
|
||
score = int(input(f"📊 请输入 {dim_name} 分数(0-{max_score}分): "))
|
||
if 0 <= score <= max_score:
|
||
return score
|
||
else:
|
||
print(f"❌ 分数必须在 0-{max_score} 之间,请重新输入!")
|
||
except ValueError:
|
||
print("❌ 请输入有效的数字!")
|
||
|
||
def generate_single_assessment(self):
|
||
"""
|
||
交互式生成单篇文献评估报告
|
||
"""
|
||
print("\n" + "="*60)
|
||
print("📚 文献质量评估报告生成器")
|
||
print("="*60)
|
||
|
||
# 输入基本信息
|
||
print("\n📝 第一步:输入文献基本信息")
|
||
print("-"*60)
|
||
|
||
paper_id = input("文献ID(如 Entry 1 输入 1,或留空自动编号): ").strip()
|
||
title = input("文献标题: ").strip()
|
||
authors = input("作者(多个作者用逗号分隔): ").strip()
|
||
source = input("来源/期刊/会议: ").strip()
|
||
year_str = input("发表年份(如 2025,留空默认当年): ").strip()
|
||
pdf_path = input(f"PDF路径(相对于 {LIBRARY_DIR},如 PDFs/paper.pdf): ").strip()
|
||
|
||
# 默认值处理
|
||
if not paper_id:
|
||
# 自动生成ID(简单策略:使用时间戳)
|
||
paper_id = f"auto-{datetime.now().strftime('%Y%m%d%H%M%S')}"
|
||
|
||
year = self.current_year if not year_str else int(year_str)
|
||
|
||
# 自动计算部分分数
|
||
print("\n📊 第二步:自动计算分数")
|
||
print("-"*60)
|
||
|
||
timeliness_score = self.calculate_timeliness_score(year)
|
||
source_authority_score = self.calculate_source_authority(source)
|
||
|
||
print(f"✅ 时效性分数:{timeliness_score}/15")
|
||
print(f"✅ 信源权威性分数:{source_authority_score}/20")
|
||
|
||
# 手动输入其他维度分数
|
||
print("\n📊 第三步:输入其他维度分数")
|
||
print("-"*60)
|
||
|
||
scores = {
|
||
"timeliness": timeliness_score,
|
||
"source_authority": source_authority_score,
|
||
"data_quality": self.input_score("data_quality"),
|
||
"method_rigor": self.input_score("method_rigor"),
|
||
"empirical_basis": self.input_score("empirical_basis"),
|
||
"theoretical_framework": self.input_score("theoretical_framework"),
|
||
"impact": self.input_score("impact")
|
||
}
|
||
|
||
# 计算总分和质量等级
|
||
total_score = sum(scores.values())
|
||
quality_grade = self.calculate_quality_grade(total_score)
|
||
|
||
# 输入核心发现与价值
|
||
print("\n📝 第四步:核心发现与价值(选填,可留空)")
|
||
print("-"*60)
|
||
|
||
key_findings = []
|
||
print("输入核心发现(留空结束,最多3条):")
|
||
for i in range(1, 4):
|
||
finding = input(f" {i}. ").strip()
|
||
if not finding:
|
||
break
|
||
key_findings.append(finding)
|
||
|
||
strengths = []
|
||
print("\n输入文献优势(留空结束,最多3条):")
|
||
for i in range(1, 4):
|
||
strength = input(f" {i}. ").strip()
|
||
if not strength:
|
||
break
|
||
strengths.append(strength)
|
||
|
||
limitations = []
|
||
print("\n输入文献局限(留空结束,最多3条):")
|
||
for i in range(1, 4):
|
||
limitation = input(f" {i}. ").strip()
|
||
if not limitation:
|
||
break
|
||
limitations.append(limitation)
|
||
|
||
# 输入适用场景与使用建议
|
||
print("\n📝 第五步:适用场景与使用建议(选填,可留空)")
|
||
print("-"*60)
|
||
|
||
use_cases = []
|
||
print("输入适用场景(留空结束,最多3条):")
|
||
for i in range(1, 4):
|
||
use_case = input(f" {i}. ").strip()
|
||
if not use_case:
|
||
break
|
||
use_cases.append(use_case)
|
||
|
||
recommendations = []
|
||
print("\n输入使用建议(留空结束,最多3条):")
|
||
for i in range(1, 4):
|
||
recommendation = input(f" {i}. ").strip()
|
||
if not recommendation:
|
||
break
|
||
recommendations.append(recommendation)
|
||
|
||
# 生成报告
|
||
report_content = self.generate_report_content(
|
||
paper_id=paper_id,
|
||
title=title,
|
||
authors=authors,
|
||
source=source,
|
||
year=year,
|
||
pdf_path=pdf_path,
|
||
scores=scores,
|
||
total_score=total_score,
|
||
quality_grade=quality_grade,
|
||
key_findings=key_findings,
|
||
strengths=strengths,
|
||
limitations=limitations,
|
||
use_cases=use_cases,
|
||
recommendations=recommendations
|
||
)
|
||
|
||
# 保存报告
|
||
output_filename = f"论文质量评估-{paper_id}-{datetime.now().strftime('%Y%m%d')}.md"
|
||
output_path = OUTPUT_DIR / output_filename
|
||
|
||
with open(output_path, "w", encoding="utf-8") as f:
|
||
f.write(report_content)
|
||
|
||
print(f"\n✅ 评估报告已生成:{output_path}")
|
||
print(f"✅ 总分:{total_score}/105,质量等级:{quality_grade}")
|
||
|
||
return output_path
|
||
|
||
def calculate_quality_grade(self, total_score):
|
||
"""
|
||
根据总分计算质量等级
|
||
|
||
规则:
|
||
- A+级:95分及以上
|
||
- A级:90-94分
|
||
- A-级:85-89分
|
||
- B+级:80-84分
|
||
- B级:70-79分
|
||
- B-级:60-69分
|
||
- C级:60分以下
|
||
"""
|
||
if total_score >= 95:
|
||
return "A+级"
|
||
elif total_score >= 90:
|
||
return "A级"
|
||
elif total_score >= 85:
|
||
return "A-级"
|
||
elif total_score >= 80:
|
||
return "B+级"
|
||
elif total_score >= 70:
|
||
return "B级"
|
||
elif total_score >= 60:
|
||
return "B-级"
|
||
else:
|
||
return "C级"
|
||
|
||
def generate_report_content(self, paper_id, title, authors, source, year, pdf_path,
|
||
scores, total_score, quality_grade,
|
||
key_findings, strengths, limitations,
|
||
use_cases, recommendations):
|
||
"""
|
||
生成评估报告内容(OFM格式)
|
||
"""
|
||
|
||
# 构建评估维度表格
|
||
dimensions_table = "| 评估维度 | 满分 | 得分 | 说明 |\n"
|
||
dimensions_table += "|---------|------|------|------|\n"
|
||
|
||
for key, dim in self.assessment_dimensions.items():
|
||
dim_name = dim["name"]
|
||
max_score = dim["max_score"]
|
||
score = scores[key]
|
||
|
||
# 添加说明
|
||
if key == "timeliness":
|
||
note = f"发表年份:{year}"
|
||
elif key == "source_authority":
|
||
note = f"来源:{source}"
|
||
else:
|
||
note = "手动评估"
|
||
|
||
dimensions_table += f"| {dim_name} | {max_score} | {score} | {note} |\n"
|
||
|
||
dimensions_table += f"| **总分** | **105** | **{total_score}** | **质量等级:{quality_grade}** |\n"
|
||
|
||
# 生成报告内容
|
||
content = f"""---
|
||
created: {datetime.now().strftime('%Y-%m-%d')}
|
||
title: 论文质量评估 - {title}
|
||
tags: [质量评估, {quality_grade}]
|
||
source: {source}
|
||
category: quality_assessment
|
||
---
|
||
|
||
# 论文质量评估报告
|
||
|
||
## 文献基本信息
|
||
|
||
| 字段 | 内容 |
|
||
|------|------|
|
||
| **文献ID** | {paper_id} |
|
||
| **标题** | {title} |
|
||
| **作者** | {authors} |
|
||
| **来源** | {source} |
|
||
| **年份** | {year} |
|
||
| **PDF路径** | `{pdf_path if pdf_path else '未指定'}` |
|
||
| **评估日期** | {datetime.now().strftime('%Y-%m-%d')} |
|
||
|
||
---
|
||
|
||
## 质量评估得分
|
||
|
||
{dimensions_table}
|
||
|
||
---
|
||
|
||
## 核心发现与价值
|
||
|
||
"""
|
||
|
||
# 添加核心发现
|
||
if key_findings:
|
||
for i, finding in enumerate(key_findings, 1):
|
||
content += f"{i}. {finding}\n"
|
||
else:
|
||
content += "> 未提供核心发现\n"
|
||
|
||
content += "\n"
|
||
|
||
# 添加文献优势
|
||
if strengths:
|
||
content += "### 优势\n\n"
|
||
for i, strength in enumerate(strengths, 1):
|
||
content += f"{i}. {strength}\n"
|
||
content += "\n"
|
||
|
||
# 添加文献局限
|
||
if limitations:
|
||
content += "### 局限\n\n"
|
||
for i, limitation in enumerate(limitations, 1):
|
||
content += f"{i}. {limitation}\n"
|
||
content += "\n"
|
||
|
||
# 添加适用场景与使用建议
|
||
content += "---\n\n## 适用场景与使用建议\n\n"
|
||
|
||
if use_cases:
|
||
content += "### 适用场景\n\n"
|
||
for i, use_case in enumerate(use_cases, 1):
|
||
content += f"{i}. {use_case}\n"
|
||
content += "\n"
|
||
|
||
if recommendations:
|
||
content += "### 使用建议\n\n"
|
||
for i, recommendation in enumerate(recommendations, 1):
|
||
content += f"{i}. {recommendation}\n"
|
||
content += "\n"
|
||
|
||
# 添加质量等级说明
|
||
content += "---\n\n## 质量等级说明\n\n"
|
||
content += f"**本次评估总分:{total_score}/105**\n\n"
|
||
content += f"**质量等级:{quality_grade}**\n\n"
|
||
content += f"**评分标准**:\n"
|
||
content += f"- A+级(≥95分):顶尖文献,可作为核心引用源\n"
|
||
content += f"- A级(90-94分):高质量文献,可作为重要引用源\n"
|
||
content += f"- A-级(85-89分):良好文献,可作为辅助引用源\n"
|
||
content += f"- B+级(80-84分):合格文献,特定场景有用\n"
|
||
content += f"- B级(70-79分):一般文献,谨慎使用\n"
|
||
content += f"- B-级(60-69分):较差文献,尽量少用\n"
|
||
content += f"- C级(<60分):低质量文献,不建议引用\n"
|
||
|
||
return content
|
||
|
||
def generate_batch_assessments(self):
|
||
"""
|
||
批量生成多篇文献评估报告(从文献库JSON读取)
|
||
"""
|
||
print("\n" + "="*60)
|
||
print("📚 批量文献质量评估")
|
||
print("="*60)
|
||
|
||
# 检查文献库JSON是否存在
|
||
if not LIBRARY_JSON.exists():
|
||
print(f"❌ 文献库文件不存在:{LIBRARY_JSON}")
|
||
print("请先运行文献库管理器创建文献库。")
|
||
return
|
||
|
||
# 读取文献库JSON
|
||
with open(LIBRARY_JSON, "r", encoding="utf-8") as f:
|
||
library_data = json.load(f)
|
||
|
||
if "entries" not in library_data:
|
||
print("❌ 文献库格式错误:缺少 'entries' 字段")
|
||
return
|
||
|
||
entries = library_data["entries"]
|
||
|
||
# 过滤未评估的文献
|
||
unassessed = []
|
||
for entry in entries:
|
||
if "quality_assessment" not in entry or not entry["quality_assessment"]:
|
||
unassessed.append(entry)
|
||
|
||
if not unassessed:
|
||
print("✅ 所有文献已评估完毕!")
|
||
return
|
||
|
||
print(f"\n📊 发现 {len(unassessed)} 篇未评估文献")
|
||
print("-"*60)
|
||
|
||
# 列出未评估文献
|
||
for i, entry in enumerate(unassessed[:10], 1):
|
||
print(f"{i}. [{entry['id']}] {entry['title'][:60]}...")
|
||
|
||
if len(unassessed) > 10:
|
||
print(f"... 还有 {len(unassessed) - 10} 篇未显示")
|
||
|
||
# 询问是否继续
|
||
confirm = input(f"\n是否开始批量评估前 {min(10, len(unassessed))} 篇文献?(Y/n,默认Y): ").strip().lower()
|
||
if confirm == "n":
|
||
return
|
||
|
||
# 批量评估(限制前10篇,避免时间过长)
|
||
for entry in unassessed[:10]:
|
||
print(f"\n{'='*60}")
|
||
print(f"📚 评估文献 [{entry['id']}]")
|
||
print(f"{'='*60}")
|
||
print(f"标题: {entry['title']}")
|
||
print(f"作者: {entry['authors']}")
|
||
print(f"来源: {entry['source']}")
|
||
print(f"年份: {entry.get('year', '未知')}")
|
||
|
||
# 自动计算分数
|
||
year = entry.get("year", self.current_year)
|
||
timeliness_score = self.calculate_timeliness_score(year)
|
||
source_authority_score = self.calculate_source_authority(entry.get("source", ""))
|
||
|
||
print(f"\n✅ 自动计算:")
|
||
print(f" - 时效性分数:{timeliness_score}/15")
|
||
print(f" - 信源权威性分数:{source_authority_score}/20")
|
||
|
||
# 简化版批量评估:只输入总分
|
||
print(f"\n📊 请输入其他维度分数(简化版,直接输入5个维度的平均分):")
|
||
avg_score = input(" 数据质量/方法严谨性/实证基础/理论框架/影响力 的平均分(0-20): ").strip()
|
||
|
||
try:
|
||
avg_score = int(avg_score)
|
||
if not (0 <= avg_score <= 20):
|
||
print("❌ 分数无效,跳过此篇")
|
||
continue
|
||
except ValueError:
|
||
print("❌ 输入无效,跳过此篇")
|
||
continue
|
||
|
||
scores = {
|
||
"timeliness": timeliness_score,
|
||
"source_authority": source_authority_score,
|
||
"data_quality": avg_score,
|
||
"method_rigor": avg_score,
|
||
"empirical_basis": avg_score,
|
||
"theoretical_framework": avg_score,
|
||
"impact": avg_score
|
||
}
|
||
|
||
total_score = sum(scores.values())
|
||
quality_grade = self.calculate_quality_grade(total_score)
|
||
|
||
# 生成简化版报告
|
||
report_content = f"""---
|
||
created: {datetime.now().strftime('%Y-%m-%d')}
|
||
title: 论文质量评估-{entry['id']}-{entry.get('year', '')}
|
||
tags: [质量评估, {quality_grade}, 批评估]
|
||
source: {entry.get('source', '')}
|
||
category: quality_assessment
|
||
---
|
||
|
||
# 论文质量评估报告(简化版)
|
||
|
||
## 文献基本信息
|
||
|
||
| 字段 | 内容 |
|
||
|------|------|
|
||
| **文献ID** | {entry['id']} |
|
||
| **标题** | {entry['title']} |
|
||
| **作者** | {entry['authors']} |
|
||
| **来源** | {entry.get('source', '')} |
|
||
| **年份** | {entry.get('year', '')} |
|
||
| **评估日期** | {datetime.now().strftime('%Y-%m-%d')} |
|
||
|
||
---
|
||
|
||
## 质量评估得分
|
||
|
||
| 评估维度 | 满分 | 得分 | 说明 |
|
||
|---------|------|------|------|
|
||
| 时效性 | 15 | {timeliness_score} | 发表年份:{year} |
|
||
| 信源权威性 | 20 | {source_authority_score} | 来源:{entry.get('source', '')} |
|
||
| 数据质量 | 20 | {avg_score} | 简化评估 |
|
||
| 方法严谨性 | 20 | {avg_score} | 简化评估 |
|
||
| 实证基础 | 20 | {avg_score} | 简化评估 |
|
||
| 理论框架 | 15 | {avg_score} | 简化评估 |
|
||
| 影响力 | 15 | {avg_score} | 简化评估 |
|
||
| **总分** | **105** | **{total_score}** | **质量等级:{quality_grade}** |
|
||
|
||
---
|
||
|
||
## 简化评估说明
|
||
|
||
此报告为批量评估的简化版本,数据质量、方法严谨性、实证基础、理论框架、影响力五个维度采用相同的平均分评估。如需详细评估,请运行单篇评估模式。
|
||
|
||
---
|
||
|
||
**质量等级:{quality_grade}**
|
||
**总分:{total_score}/105**
|
||
"""
|
||
|
||
# 保存报告
|
||
output_filename = f"论文质量评估-{entry['id']}-{datetime.now().strftime('%Y%m%d')}.md"
|
||
output_path = OUTPUT_DIR / output_filename
|
||
|
||
with open(output_path, "w", encoding="utf-8") as f:
|
||
f.write(report_content)
|
||
|
||
print(f"\n✅ 评估报告已生成:{output_path}")
|
||
print(f"✅ 总分:{total_score}/105,质量等级:{quality_grade}")
|
||
|
||
print(f"\n{'='*60}")
|
||
print("✅ 批量评估完成!")
|
||
print(f"{'='*60}")
|
||
|
||
|
||
def main():
|
||
"""主函数"""
|
||
parser = argparse.ArgumentParser(
|
||
description="文献质量评估报告生成器",
|
||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||
epilog="""
|
||
使用示例:
|
||
python 质量评估报告生成器.py # 交互式单篇评估
|
||
python 质量评估报告生成器.py --batch # 批量评估(从文献库JSON读取)
|
||
python 质量评估报告生成器.py --help # 查看帮助
|
||
"""
|
||
)
|
||
|
||
parser.add_argument(
|
||
"--batch",
|
||
action="store_true",
|
||
help="批量评估模式(从文献库JSON读取)"
|
||
)
|
||
|
||
args = parser.parse_args()
|
||
|
||
# 创建评估器实例
|
||
assessor = QualityAssessment()
|
||
|
||
if args.batch:
|
||
# 批量评估模式
|
||
assessor.generate_batch_assessments()
|
||
else:
|
||
# 交互式单篇评估模式
|
||
assessor.generate_single_assessment()
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|