fix: 评分系统修复(镜像检测、年龄默认分、社区健康保底)

1. 检测镜像仓库(mirror=true),自动调整评分权重
2. 创建时间不可用时给默认 5 分(原 0 分)
3. 镜像仓库贡献者/Issue/PR 数据标记为『—』而非低分
4. 镜像仓库的综合评分权重改为成熟度40%+文档30%+社区15%+贡献15%
This commit is contained in:
赵昌 2026-07-07 23:07:31 +08:00
parent c7842c849b
commit 3965579e48
1 changed files with 79 additions and 48 deletions

View File

@ -17,26 +17,28 @@ const prs = readJSON('prs-all.json').data || {};
const langs = readJSON('repo-languages.json').data || {};
const contribs = readJSON('repo-contributors.json').data || {};
// ==== 数据提取(兼容实际字段名)====
// ==== 数据提取 ====
const name = repo.name || 'unknown';
const fullName = repo.full_name || 'unknown';
const isMirror = !!repo.mirror; // 镜像仓库检测
// description: 从 issue 的 project 信息中获取,或从 repo 顶层获取
// description
let description = '';
try { description = issues.issues[0].project.description || ''; } catch(e) {}
if (!description) description = repo.description || repo.about || '';
// 从 repo 的 fork_info 获取描述
if (!description && repo.fork_info) {
try { description = issues.issues[0].project.description || ''; } catch(e) {}
}
// 创建时间:从 issue 的第一条创建时间推断,或从项目信息获取
// 创建时间
let created = repo.created_at || repo.created_on || '';
if (!created && issues.issues && issues.issues.length > 0) {
created = issues.issues[issues.issues.length - 1].created_at || '';
}
// 支持 "2026-06-04 15:35" 格式的日期
if (created) created = created.replace(' ', 'T');
const updated = repo.updated_at || repo.last_updated || '';
// Star/Like — GitLink 使用 praises_count 和 watchers_count
const stars = repo.praises_count || repo.watchers_count || 0;
const forks = repo.forked_count || 0;
const defaultBranch = repo.default_branch || 'master';
@ -45,7 +47,6 @@ const contributorCount = repo.contributor_users_count || 0;
const langEntries = Object.entries(langs).sort((a,b) => parseFloat(b[1]) - parseFloat(a[1]));
const topLangs = langEntries.slice(0, 3).map(e => e[0] + ' ' + e[1]).join(', ');
const topics = (repo.topics || []).join(', ') || '无标签';
// 项目年龄(月)
@ -64,108 +65,138 @@ const closedCount = issues.closed_count || 0;
const openedCount = issues.opened_count || 0;
const totalIssues = closedCount + openedCount || issueList.length || issuesCount;
// PR 数据 — status 是字符串 "open"/"merged"/"closed"
// PR 数据
const prList = prs.pulls || [];
let prOpen = 0, prMerged = 0;
let prOpen = 0, prMerged = 0, prClosed = 0;
prList.forEach(p => {
const s = (p.status || '').toLowerCase();
if (s === 'open' || s === 'opened') prOpen++;
else if (s === 'merged' || s === 'merge') prMerged++;
else if (s === 'closed') prClosed++;
});
const prTotal = repo.pull_requests_count || prList.length || 1;
const prTotal = repo.pull_requests_count || prList.length || 0;
// 贡献者
const contribList = contribs.list || [];
const uniqueContributors = contribList.length || contributorCount;
// ==== 是否有足够的社区活动数据 ====
const hasActivity = totalIssues > 0 || prTotal > 0 || uniqueContributors > 1;
// ==== 维度评分 ====
// 1. 成熟度评分 (0-10)
const ageScore = Math.min(10, Math.max(0, ageMonths / 2));
// 修复:创建时间不可用时给中等分
const ageScore = ageMonths > 0 ? Math.min(10, ageMonths / 2) : 5;
const starScore = stars > 100 ? 10 : stars > 50 ? 7 : stars > 10 ? 5 : Math.max(1, stars);
const forkScore = forks > 50 ? 10 : forks > 20 ? 7 : forks > 5 ? 5 : Math.max(1, forks);
const updateScore = 7;
const updateScore = ageMonths > 0 ? 7 : 5;
const maturityScore = Math.round((ageScore * 0.25 + starScore * 0.25 + forkScore * 0.20 + updateScore * 0.30) * 10) / 10;
// 2. 文档质量评分 (0-10)
let docScore = 3;
if (description && description.length > 10) docScore += 2;
if (description && description.length > 5) docScore += 2;
if (repo.license_short_name || repo.license_name) docScore += 2;
if (repo.open_devops) docScore += 1;
if (repo.has_tests) docScore += 1;
// 镜像仓库且无检测到的文档,给保底分表示"数据不可用"
if (isMirror && docScore < 4) docScore = 4;
docScore = Math.min(10, docScore);
// 3. 贡献模式评分 (0-10)
const contribScore = Math.min(10, Math.max(1, uniqueContributors * 2));
const freqScore = 6;
const contribModeScore = Math.round((contribScore * 0.30 + freqScore * 0.30 + 5 * 0.20 + 5 * 0.20) * 10) / 10;
const freqScore = hasActivity ? 6 : 3;
let contribModeScore;
if (isMirror && uniqueContributors <= 1) {
// 镜像仓库:贡献数据不可用,给中等分
contribModeScore = 5;
} else {
contribModeScore = Math.round((contribScore * 0.30 + freqScore * 0.30 + 5 * 0.20 + 5 * 0.20) * 10) / 10;
}
// 4. 社区健康度评分 (0-10)
const closeRate = totalIssues > 0 ? Math.round(closedCount / totalIssues * 100) : 0;
const closeRateScore = closeRate > 80 ? 10 : closeRate > 50 ? 7 : closeRate > 30 ? 5 : 3;
const mergeRate = prTotal > 0 ? Math.round(prMerged / prTotal * 100) : 0;
const mergeRateScore = mergeRate > 70 ? 10 : mergeRate > 40 ? 7 : 3;
const communityScore = Math.round((closeRateScore * 0.30 + 6 * 0.25 + mergeRateScore * 0.25 + 6 * 0.20) * 10) / 10;
let closeRate = 0, mergeRate = 0;
let closeRateScore, mergeRateScore, communityScore;
if (isMirror && totalIssues === 0 && prTotal === 0) {
closeRateScore = 5; mergeRateScore = 5; communityScore = 5;
} else {
closeRate = totalIssues > 0 ? Math.round(closedCount / totalIssues * 100) : 0;
closeRateScore = closeRate > 80 ? 10 : closeRate > 50 ? 7 : closeRate > 30 ? 5 : totalIssues > 0 ? 3 : 5;
mergeRate = prTotal > 0 ? Math.round(prMerged / prTotal * 100) : 0;
mergeRateScore = mergeRate > 70 ? 10 : mergeRate > 40 ? 7 : prTotal > 0 ? 3 : 5;
communityScore = Math.round((closeRateScore * 0.30 + 6 * 0.25 + mergeRateScore * 0.25 + 6 * 0.20) * 10) / 10;
}
// 5. 综合评分
const totalScore = Math.round((maturityScore * 0.25 + docScore * 0.20 + contribModeScore * 0.30 + communityScore * 0.25) * 10) / 10;
// 镜像仓库:调整权重
let totalScore;
if (isMirror && !hasActivity) {
// 镜像仓库:成熟度和文档权重提高
totalScore = Math.round((maturityScore * 0.40 + docScore * 0.30 + contribModeScore * 0.15 + communityScore * 0.15) * 10) / 10;
} else {
totalScore = Math.round((maturityScore * 0.25 + docScore * 0.20 + contribModeScore * 0.30 + communityScore * 0.25) * 10) / 10;
}
const level = totalScore >= 9 ? '🟢 优秀' : totalScore >= 7 ? '🟢 良好' : totalScore >= 5 ? '🟡 一般' : totalScore >= 3 ? '🔴 堪忧' : '🔴 危险';
// 可复现性
const hasLicense = repo.license_short_name ? '✅' : '❌';
const hasReadme = (description && description.length > 0) ? '✅' : '❌';
const hasDevops = repo.open_devops ? '✅' : '❌';
// 可读的状态显示
const totalIssuesStr = totalIssues > 0 ? `${totalIssues}(关闭 ${closedCount} / 打开 ${totalIssues - closedCount}` : `${totalIssues || '—'}`;
const prTotalStr = prTotal > 0 ? `${prTotal}(合并 ${prMerged} / 打开 ${prOpen}` : `${prTotal || '—'}`;
const contributorsStr = uniqueContributors > 0 ? `${uniqueContributors}` : '—';
// 改进建议
const suggestions = [];
if (stars < 20) suggestions.push('🟡 中优先级 — 项目影响力有限,建议在社区中推广');
if (!repo.license_short_name) suggestions.push('🔴 高优先级 — 缺少开源许可证,建议添加 LICENSE 文件');
if (uniqueContributors < 3) suggestions.push('🔴 高优先级 — 贡献者较少,建议标记 good-first-issue 吸引新贡献者');
if (!repo.license_short_name && !isMirror) suggestions.push('🔴 高优先级 — 缺少开源许可证,建议添加 LICENSE 文件');
if (uniqueContributors < 3 && !isMirror) suggestions.push('🔴 高优先级 — 贡献者较少,建议标记 good-first-issue 吸引新贡献者');
if (closeRate < 50 && totalIssues > 0) suggestions.push('🟡 中优先级 — Issue 关闭率偏低,建议定期清理积压 Issue');
if (mergeRate < 50 && prTotal > 1) suggestions.push('🟡 中优先级 — PR 合并率偏低,建议加快 PR 处理速度');
if (isMirror && !hasActivity) suggestions.push(' 该项目为镜像仓库,部分社区活动数据不可用,评分仅供参考');
if (suggestions.length === 0) suggestions.push('🟢 低优先级 — 项目状态良好,继续保持');
const now = new Date();
const dateStr = now.toISOString().slice(0, 10);
const timeStr = now.toISOString().slice(0, 19).replace('T', ' ');
const ageDisplay = ageMonths > 0 ? `${Math.round(ageMonths)} 个月` : '未知';
const mirrorNote = isMirror ? '\n> 该项目为镜像仓库,部分数据(贡献者/Issue/PR可能不完整\n' : '';
// ==== 生成报告 ====
const report = `# 🔬 科研项目洞察报告 — ${fullName}
**分析日期** ${dateStr}
**综合评分** ${totalScore}/10 ${level}
**综合评分** ${totalScore}/10 ${level}${isMirror ? ' 📦 镜像仓库' : ''}
${mirrorNote}
## 项目画像
| 维度 | 数据 |
|------|------|
| 项目名称 | ${name} |
| 描述 | ${description || '无描述'} |
| 创建时间 | ${created || '未知'}${Math.round(ageMonths)} 个月 |
| 创建时间 | ${created || '未知'}${ageDisplay} |
| Star / Fork | ${stars} / ${forks} |
| 默认分支 | ${defaultBranch} |
| 主要语言 | ${topLangs || '未知'} |
| 研究主题 | ${topics} |
| 贡献者数 | ${uniqueContributors} |
| 贡献者数 | ${contributorsStr} |
| Issue 总数 | ${totalIssues} |
## 项目成熟度${maturityScore}/10
**评分因子**
- 项目年龄${Math.round(ageMonths)} 个月 ${ageScore.toFixed(1)} 权重 25%
- 项目年龄${ageDisplay} ${ageScore.toFixed(1)} 权重 25%
- 社区影响力${stars} Star / ${forks} Fork ${Math.round(starScore)} 权重 25%
- Fork 活跃度${forks} Fork ${Math.round(forkScore)} 权重 20%
- 维护活跃度最近更新 ${updated || '未知'} ${updateScore} 权重 30%
**分析** 项目创建约 ${Math.round(ageMonths)} 个月获得 ${stars} Star ${forks} Fork${stars > 50 ? '项目已有一定社区影响力,处于成长期。' : '项目尚在早期阶段,需加强社区推广。'}
**分析** ${stars > 50 ? '项目已有一定社区影响力,处于成长期。' : '项目尚在早期阶段,需加强社区推广。'}
## 文档质量${docScore}/10
| 检查项 | 状态 | 说明 |
|--------|------|------|
| README | ${hasReadme} | ${description ? '项目描述已填写' : '建议完善 README'} |
| 开源许可证 | ${hasLicense} | ${repo.license_short_name || '未检测到,建议添加'} |
| DevOps CI | ${hasDevops} | ${repo.open_devops ? '已配置' : '建议配置 CI 流水线'} |
| README | ${description && description.length > 5 ? '✅' : '❌'} | ${description ? '项目描述已填写' : '建议完善 README'} |
| 开源许可证 | ${repo.license_short_name ? '✅' : '❌'} | ${repo.license_short_name || '未检测到,建议添加'} |
| DevOps CI | ${repo.open_devops ? '✅' : '❌'} | ${repo.open_devops ? '已配置' : '建议配置 CI 流水线'} |
**改进建议** ${docScore < 6 ? '建议补充 README、LICENSE 和 CI 配置以提升项目规范性' : '文档基本完善'}
@ -173,29 +204,29 @@ const report = `# 🔬 科研项目洞察报告 — ${fullName}
| 指标 | 数据 | 评分 |
|------|------|------|
| 贡献者数量 | ${uniqueContributors} | ${contribScore}/10 |
| 提交活跃度 | 中等 | ${freqScore}/10 |
| 贡献者数量 | ${contributorsStr} | ${isMirror && uniqueContributors <= 1 ? '—(镜像仓库)' : contribScore + '/10'} |
| 提交活跃度 | ${hasActivity ? '中等' : '—(镜像仓库)'} | ${isMirror && !hasActivity ? '—' : freqScore + '/10'} |
**分析** 项目有 ${uniqueContributors} 位贡献者${uniqueContributors < 3 ? '目前贡献者偏少,建议通过 good-first-issue 标签吸引新贡献者。' : '团队协作良好,有多位贡献者共同维护。'}
**分析** ${isMirror && uniqueContributors <= 1 ? '该项目为镜像仓库,贡献者数据不代表实际开发团队。' : uniqueContributors < 3 ? '目前贡献者偏少,建议通过 good-first-issue 标签吸引新贡献者。' : '团队协作良好,有多位贡献者共同维护。'}
## 社区健康度${communityScore}/10
| 指标 | 数据 | 评分 |
|------|------|------|
| Issue 总数 | ${totalIssues}关闭 ${closedCount} / 打开 ${totalIssues - closedCount}| |
| 关闭率 | ${closeRate}% | ${closeRateScore}/10 |
| PR 总数 | ${prTotal}合并 ${prMerged} / 打开 ${prOpen}| |
| 合并率 | ${mergeRate}% | ${mergeRateScore}/10 |
| Issue 总数 | ${totalIssuesStr} | |
| 关闭率 | ${totalIssues > 0 ? Math.round(closedCount / totalIssues * 100) + '%' : '—'} | ${closeRateScore}/10 |
| PR 总数 | ${prTotalStr} | |
| 合并率 | ${prTotal > 0 ? Math.round(prMerged / prTotal * 100) + '%' : '—'} | ${mergeRateScore}/10 |
**分析** Issue 关闭率 ${closeRate}%${closeRate > 50 ? ' ✅ 良好' : ' ⚠️ 偏低'}PR 合并率 ${mergeRate}%${mergeRate > 50 ? ' ✅ 良好' : ' ⚠️ 偏低'}
**分析** ${isMirror && totalIssues === 0 && prTotal === 0 ? '该项目为镜像仓库,没有独立 Issue 和 PR 数据。' : (closeRate > 50 ? 'Issue 关闭率 ✅ 良好' : 'Issue 关闭率 ⚠️ 偏低') + '' + (mergeRate > 50 ? 'PR 合并率 ✅ 良好' : 'PR 合并率 ⚠️ 偏低')}
## 可复现性评估
| 检查项 | 状态 | 说明 |
|--------|------|------|
| 开源许可证 | ${hasLicense} | ${repo.license_short_name || '建议添加以便他人使用'} |
| 依赖文档化 | | 需人工检查 go.mod / package.json |
| 运行说明 | ${hasReadme} | ${description ? 'README 包含基本说明' : 'README 缺少运行说明'} |
| CI 配置 | ${hasDevops} | ${repo.open_devops ? '已有 DevOps 流水线' : '建议配置 CI'} |
| 开源许可证 | ${repo.license_short_name ? '✅' : '❌'} | ${repo.license_short_name || '建议添加以便他人使用'} |
| 依赖文档化 | ${hasActivity ? '—' : '⏭️'} | ${hasActivity ? '需人工检查' : '镜像仓库,跳过'} |
| 运行说明 | ${description && description.length > 5 ? '✅' : '❌'} | ${description ? 'README 包含基本说明' : 'README 缺少运行说明'} |
| CI 配置 | ${repo.open_devops ? '✅' : '❌'} | ${repo.open_devops ? '已有 DevOps 流水线' : '建议配置 CI'} |
## 改进建议
${suggestions.map((s, i) => (i + 1) + '. ' + s).join('\n')}