forked from Gitlink/gitlink-cli
508 lines
21 KiB
Bash
508 lines
21 KiB
Bash
#!/usr/bin/env bash
|
||
# ============================================================
|
||
# GitLink 科研辅助 — 场景 3:合规与复现性检查
|
||
# ============================================================
|
||
# 8维度复现性评分:许可证、密钥/PII、README、依赖声明、
|
||
# 构建说明、CI配置、测试证据、数据可用性
|
||
# 输出雷达图 + 仪表盘 HTML 评分卡
|
||
# ============================================================
|
||
|
||
set -euo pipefail
|
||
trap '' PIPE
|
||
|
||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
source "$SCRIPT_DIR/../lib/common.sh"
|
||
|
||
# ── Configuration ────────────────────────────────────────────────────
|
||
OUTPUT_DIR="${SCRIPT_DIR}/../../output"
|
||
OUTPUT_FILE=""
|
||
LOCAL_PATH="."
|
||
|
||
usage() {
|
||
cat <<'EOF'
|
||
Usage: 08-research-compliance.sh --owner <owner> --repo <repo> [options]
|
||
|
||
Options:
|
||
--owner <owner> Repository owner (required)
|
||
--repo <repo> Repository name (required)
|
||
--local-path <path> Local repo path for compliance scan (default: .)
|
||
--output <file> Output HTML file path
|
||
--no-wiki Skip publishing to Wiki
|
||
--dry-run Preview mode
|
||
|
||
Examples:
|
||
08-research-compliance.sh --owner zzx-coder --repo gitlink-cli --local-path .
|
||
EOF
|
||
exit 0
|
||
}
|
||
|
||
parse_common_args "$@"
|
||
while [[ $# -gt 0 ]]; do
|
||
case "$1" in
|
||
--output) OUTPUT_FILE="$2"; shift 2 ;;
|
||
--local-path) LOCAL_PATH="$2"; shift 2 ;;
|
||
--no-wiki) PUBLISH_WIKI="false"; shift ;;
|
||
*) shift ;;
|
||
esac
|
||
done
|
||
|
||
PUBLISH_WIKI="${PUBLISH_WIKI:-true}"
|
||
|
||
# ── Scoring helpers ──────────────────────────────────────────────────
|
||
score_dim() {
|
||
# score_dim score weight label
|
||
awk -v s="$1" -v w="$2" 'BEGIN { printf "%.4f", s * w }'
|
||
}
|
||
|
||
# ── Main ─────────────────────────────────────────────────────────────
|
||
main() {
|
||
log_title "GitLink 科研辅助 — 合规与复现性检查"
|
||
|
||
check_auth
|
||
require_owner_repo
|
||
local today
|
||
today=$(date_today)
|
||
mkdir -p "$OUTPUT_DIR"
|
||
|
||
# Get repo metadata for description and DOI detection
|
||
local repo_json repo_desc
|
||
repo_json=$(gl_check repo +info --owner "$OWNER" --repo "$REPO")
|
||
repo_desc=$(json_get "$repo_json" '.data.description // ""')
|
||
local doi_found=""
|
||
doi_found=$(echo "$repo_desc" | grep -oE '10\.[0-9]{4,}/[a-zA-Z0-9._\-/]+' | head -1 || echo "")
|
||
|
||
local REPO_FULL="${OWNER}/${REPO}"
|
||
OUTPUT_FILE="${OUTPUT_FILE:-${OUTPUT_DIR}/reproducibility-${REPO}-${today}.html}"
|
||
|
||
# Each dimension: score (0, 0.5, 1.0), weight, label, detail
|
||
local dim_license_s=0 dim_nosecret_s=0 dim_readme_s=0 dim_deps_s=0
|
||
local dim_build_s=0 dim_ci_s=0 dim_test_s=0 dim_data_s=0
|
||
local dim_license_d="" dim_nosecret_d="" dim_readme_d="" dim_deps_d=""
|
||
local dim_build_d="" dim_ci_d="" dim_test_d="" dim_data_d=""
|
||
|
||
# ═══ Step 1: Compliance Scan ═══
|
||
log_step "1/7 合规扫描..."
|
||
local comp_json
|
||
if [[ -d "$LOCAL_PATH/.git" ]]; then
|
||
comp_json=$(cd "$LOCAL_PATH" && gl_run compliance +scan --format json 2>/dev/null)
|
||
else
|
||
log_warn "本地仓库路径无 .git 目录,跳过合规扫描"
|
||
comp_json=""
|
||
fi
|
||
|
||
if [[ "$(json_ok "$comp_json")" == "true" ]]; then
|
||
# License check
|
||
local license_ok
|
||
license_ok=$(echo "$comp_json" | jq -r '.data.license.status // "unknown"' 2>/dev/null)
|
||
if [[ "$license_ok" == "ok" || "$license_ok" == "clean" || "$license_ok" == "found" ]]; then
|
||
dim_license_s=1.0; dim_license_d="检测到合规许可证"
|
||
elif [[ "$license_ok" == "warning" ]]; then
|
||
dim_license_s=0.5; dim_license_d="有许可证但类型非标准"
|
||
else
|
||
dim_license_s=0; dim_license_d="未检测到 LICENSE 文件"
|
||
fi
|
||
|
||
# Secrets check
|
||
local secrets_count
|
||
secrets_count=$(echo "$comp_json" | jq -r '.data.secrets.findings | length // 0' 2>/dev/null)
|
||
if [[ "$secrets_count" == "0" || -z "$secrets_count" ]]; then
|
||
dim_nosecret_s=1.0; dim_nosecret_d="未发现硬编码密钥"
|
||
elif [[ "$secrets_count" -le 2 ]]; then
|
||
dim_nosecret_s=0.5; dim_nosecret_d="发现 ${secrets_count} 处可疑密钥"
|
||
else
|
||
dim_nosecret_s=0; dim_nosecret_d="发现 ${secrets_count} 处密钥泄露"
|
||
fi
|
||
|
||
# PII / Exposure
|
||
local pii_count
|
||
pii_count=$(echo "$comp_json" | jq -r '.data.exposure.findings | length // 0' 2>/dev/null)
|
||
if [[ "$pii_count" == "0" || -z "$pii_count" ]]; then
|
||
# Keep secrets score; PII clean doesn't change it
|
||
dim_nosecret_d="${dim_nosecret_d} / 无 PII 泄露"
|
||
else
|
||
dim_nosecret_d="${dim_nosecret_d} / 发现 ${pii_count} 处 PII"
|
||
if [[ ${dim_nosecret_s%%.*} -eq 1 ]]; then
|
||
dim_nosecret_s=0.5
|
||
fi
|
||
fi
|
||
|
||
log_info " License: $( [[ $dim_license_s == "1.0" ]] && echo "OK" || echo "ISSUE")"
|
||
log_info " Secrets/PII: $( [[ $dim_nosecret_s == "1.0" ]] && echo "OK" || echo "ISSUE")"
|
||
else
|
||
log_warn "合规扫描未返回有效结果,跳过该维度"
|
||
dim_license_s=0; dim_license_d="未扫描(无本地仓库)"
|
||
dim_nosecret_s=0; dim_nosecret_d="未扫描(无本地仓库)"
|
||
fi
|
||
|
||
# ═══ Step 2: README Completeness ═══
|
||
log_step "2/7 检查 README 完整性..."
|
||
local readme_text
|
||
readme_text=$(gl_run api GET "raw/$OWNER/$REPO/master/README.md" 2>/dev/null)
|
||
if [[ "$(json_ok "$readme_text")" == "true" ]]; then
|
||
readme_text=$(echo "$readme_text" | jq -r '.data // ""' 2>/dev/null)
|
||
else
|
||
readme_text=""
|
||
fi
|
||
|
||
local section_count=0 sections_found=""
|
||
for kw in "# " "## " "Install" "Usage" "License" "Contribut" "Citation" "安装" "使用" "许可" "引用"; do
|
||
if echo "$readme_text" | grep -qi "$kw"; then
|
||
section_count=$((section_count + 1))
|
||
sections_found="${sections_found}${kw}, "
|
||
fi
|
||
done
|
||
|
||
if [[ $section_count -ge 5 ]]; then
|
||
dim_readme_s=1.0; dim_readme_d="README 结构完整,含 ${section_count} 个关键章节"
|
||
elif [[ $section_count -ge 3 ]]; then
|
||
dim_readme_s=0.5; dim_readme_d="README 部分完整,${section_count} 个关键章节"
|
||
else
|
||
dim_readme_s=0; dim_readme_d="README 缺失或过于简略"
|
||
fi
|
||
log_info " README 章节数: ${section_count}"
|
||
|
||
# ═══ Step 3: Dependency Declaration ═══
|
||
log_step "3/7 检查依赖声明..."
|
||
local sub_json dep_files=0 dep_list="" names=""
|
||
sub_json=$(gl_run api GET "/v1/$OWNER/$REPO/sub_entries?ref=master")
|
||
if [[ "$(json_ok "$sub_json")" == "true" ]]; then
|
||
local data_type
|
||
data_type=$(echo "$sub_json" | jq -r '(.data | type) // "string"' 2>/dev/null)
|
||
if [[ "$data_type" == "array" ]]; then
|
||
names=$(echo "$sub_json" | jq -r '.data[].name // empty' 2>/dev/null)
|
||
fi
|
||
fi
|
||
if [[ -n "$names" ]]; then
|
||
for dep_file in "package.json" "go.mod" "requirements.txt" "pyproject.toml" \
|
||
"Cargo.toml" "CMakeLists.txt" "pom.xml" "build.gradle" "Gemfile" \
|
||
"Makefile" "DESCRIPTION" "Project.toml"; do
|
||
if echo "$names" | grep -qFx "$dep_file"; then
|
||
dep_files=$((dep_files + 1))
|
||
dep_list="${dep_list}${dep_file}, "
|
||
fi
|
||
done
|
||
fi
|
||
|
||
if [[ $dep_files -ge 1 ]]; then
|
||
dim_deps_s=1.0; dim_deps_d="有标准依赖文件: ${dep_list%, }"
|
||
elif echo "$readme_text" | grep -qiE "dependenc|requirement|依赖|安装|install"; then
|
||
dim_deps_s=0.5; dim_deps_d="README 中提及依赖"
|
||
else
|
||
dim_deps_s=0; dim_deps_d="无依赖声明"
|
||
fi
|
||
log_info " 依赖文件数: ${dep_files}"
|
||
|
||
# ═══ Step 4: Build Instructions ═══
|
||
log_step "4/7 检查构建说明..."
|
||
local build_score=0
|
||
# Check README for build keywords
|
||
if echo "$readme_text" | grep -qiE "build|install|compile|make|构建|安装|编译|run|运行"; then
|
||
build_score=$((build_score + 1))
|
||
fi
|
||
# Check for Makefile/Dockerfile
|
||
if echo "$names" | grep -qE "Makefile|Dockerfile|docker-compose"; then
|
||
build_score=$((build_score + 1))
|
||
fi
|
||
# Check for CI config
|
||
if echo "$names" | grep -qE "\.github/workflows|\.gitlab-ci|Jenkinsfile"; then
|
||
build_score=$((build_score + 1))
|
||
fi
|
||
|
||
if [[ $build_score -ge 3 ]]; then
|
||
dim_build_s=1.0; dim_build_d="详细的构建说明和自动化配置"
|
||
elif [[ $build_score -ge 1 ]]; then
|
||
dim_build_s=0.5; dim_build_d="部分构建说明"
|
||
else
|
||
dim_build_s=0; dim_build_d="无构建说明"
|
||
fi
|
||
log_info " 构建说明得分: ${build_score}/3"
|
||
|
||
# ═══ Step 5: CI Configuration ═══
|
||
log_step "5/7 检查 CI 配置..."
|
||
local ci_json ci_builds=0 ci_ok=0
|
||
ci_json=$(gl_run ci +builds --owner "$OWNER" --repo "$REPO" --limit 10)
|
||
if [[ "$(json_ok "$ci_json")" == "true" ]]; then
|
||
ci_builds=$(echo "$ci_json" | jq -r '(.data | length) // 0' 2>/dev/null)
|
||
ci_ok=$(echo "$ci_json" | jq -r '[.data[] | select(.status == "success" or .status == "completed")] | length' 2>/dev/null || echo "0")
|
||
fi
|
||
|
||
if [[ $ci_builds -gt 0 ]] && [[ $ci_ok -ge 1 ]]; then
|
||
dim_ci_s=1.0; dim_ci_d="CI 已配置且通过 (${ci_ok}/${ci_builds})"
|
||
elif [[ $ci_builds -gt 0 ]]; then
|
||
dim_ci_s=0.5; dim_ci_d="CI 存在但最近构建失败"
|
||
else
|
||
dim_ci_s=0; dim_ci_d="无 CI 配置"
|
||
fi
|
||
log_info " CI 构建数: ${ci_builds}"
|
||
|
||
# ═══ Step 6: Test Evidence ═══
|
||
log_step "6/7 检查测试证据..."
|
||
local test_score=0
|
||
# Check for test directories
|
||
if echo "$names" | grep -qE "^test/|^tests/|^spec/|^__tests__/"; then
|
||
test_score=$((test_score + 1))
|
||
fi
|
||
# Check for test files
|
||
if echo "$names" | grep -qE "_test\.|\.test\.|_spec\.|\.spec\.|test_"; then
|
||
test_score=$((test_score + 1))
|
||
fi
|
||
# Check README for test instructions
|
||
if echo "$readme_text" | grep -qiE "test|测试|validate|验证"; then
|
||
test_score=$((test_score + 1))
|
||
fi
|
||
|
||
if [[ $test_score -ge 3 ]]; then
|
||
dim_test_s=1.0; dim_test_d="有测试目录 + 测试文件 + 测试说明"
|
||
elif [[ $test_score -ge 1 ]]; then
|
||
dim_test_s=0.5; dim_test_d="部分测试证据"
|
||
else
|
||
dim_test_s=0; dim_test_d="无测试证据"
|
||
fi
|
||
log_info " 测试证据得分: ${test_score}/3"
|
||
|
||
# ═══ Step 7: Data Availability ═══
|
||
log_step "7/7 检查数据可用性声明..."
|
||
local data_score=0 data_evidence=""
|
||
if echo "$readme_text $repo_desc" | grep -qiE "dataset|data/|数据|zenodo|figshare|kaggle|huggingface"; then
|
||
data_score=$((data_score + 1))
|
||
data_evidence="有关键词提及"
|
||
fi
|
||
if echo "$readme_text $repo_desc" | grep -qiE "https?://[^\s]+(?:zenodo|figshare|data\.|dataset)[^\s]*" 2>/dev/null; then
|
||
data_score=$((data_score + 1))
|
||
data_evidence="${data_evidence}, 有数据链接"
|
||
fi
|
||
if [[ -n "$doi_found" ]]; then
|
||
data_score=$((data_score + 1))
|
||
data_evidence="${data_evidence}, 有 DOI/论文引用"
|
||
fi
|
||
|
||
if [[ $data_score -ge 2 ]]; then
|
||
dim_data_s=1.0; dim_data_d="明确的数据可用性声明${data_evidence}"
|
||
elif [[ $data_score -ge 1 ]]; then
|
||
dim_data_s=0.5; dim_data_d="部分数据声明${data_evidence}"
|
||
else
|
||
dim_data_s=0; dim_data_d="无数据可用性声明"
|
||
fi
|
||
log_info " 数据声明得分: ${data_score}/3"
|
||
|
||
# ═══ Calculate Total Score ═══
|
||
log_step "计算复现性评分..."
|
||
|
||
local total_score
|
||
total_score=$(weighted_sum \
|
||
"$dim_license_s" 0.15 \
|
||
"$dim_nosecret_s" 0.15 \
|
||
"$dim_readme_s" 0.15 \
|
||
"$dim_deps_s" 0.15 \
|
||
"$dim_build_s" 0.10 \
|
||
"$dim_ci_s" 0.10 \
|
||
"$dim_test_s" 0.10 \
|
||
"$dim_data_s" 0.10)
|
||
total_score=$(awk -v s="$total_score" 'BEGIN { printf "%.1f", s * 100 }')
|
||
|
||
local grade
|
||
if awk "BEGIN { exit ($total_score >= 85) ? 0 : 1 }"; then grade="A"
|
||
elif awk "BEGIN { exit ($total_score >= 70) ? 0 : 1 }"; then grade="B"
|
||
elif awk "BEGIN { exit ($total_score >= 55) ? 0 : 1 }"; then grade="C"
|
||
elif awk "BEGIN { exit ($total_score >= 40) ? 0 : 1 }"; then grade="D"
|
||
else grade="F"; fi
|
||
|
||
log_ok "复现性评分: ${total_score}/100 — 等级 ${grade}"
|
||
|
||
# ═══ Generate HTML ═══
|
||
log_step "生成 HTML 评分卡..."
|
||
|
||
if [[ "${DRY_RUN:-false}" != "true" ]]; then
|
||
cat > "$OUTPUT_FILE" <<HTMLEOF
|
||
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>${REPO_FULL} — 复现性评分卡</title>
|
||
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
|
||
<style>
|
||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f7fa; color: #333; }
|
||
.header { background: linear-gradient(135deg, #1a237e 0%, #3949ab 100%); color: #fff; padding: 40px 30px; }
|
||
.header h1 { font-size: 26px; margin-bottom: 6px; }
|
||
.container { max-width: 1200px; margin: 0 auto; padding: 20px; }
|
||
.row { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 20px; margin-bottom: 24px; }
|
||
.panel { background: #fff; border-radius: 12px; padding: 24px; box-shadow: 0 2px 8px rgba(0,0,0,.08); }
|
||
.panel h2 { font-size: 18px; margin-bottom: 16px; color: #1a237e; border-bottom: 2px solid #3949ab; padding-bottom: 8px; }
|
||
.chart { width: 100%; height: 350px; }
|
||
.grade-circle { text-align: center; padding: 20px; }
|
||
.grade-letter { font-size: 72px; font-weight: 900; }
|
||
.grade-A { color: #2e7d32; }
|
||
.grade-B { color: #558b2f; }
|
||
.grade-C { color: #f57c00; }
|
||
.grade-D { color: #e65100; }
|
||
.grade-F { color: #c62828; }
|
||
.grade-score { font-size: 24px; color: #888; }
|
||
table { width: 100%; border-collapse: collapse; }
|
||
th, td { padding: 10px 14px; text-align: left; border-bottom: 1px solid #eee; font-size: 13px; }
|
||
th { background: #f5f7fa; color: #555; }
|
||
.bar { height: 8px; border-radius: 4px; background: #e0e0e0; margin-top: 4px; }
|
||
.bar-fill { height: 100%; border-radius: 4px; }
|
||
.footer { text-align: center; padding: 24px; color: #999; font-size: 12px; }
|
||
@media (max-width: 768px) { .row { grid-template-columns: 1fr; } }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="header">
|
||
<h1>${REPO_FULL} — 科研复现性评分卡</h1>
|
||
<div style="opacity:0.8;font-size:14px;">${today}</div>
|
||
</div>
|
||
<div class="container">
|
||
|
||
<div class="row">
|
||
<div class="panel grade-circle">
|
||
<div class="grade-letter grade-${grade}">${grade}</div>
|
||
<div class="grade-score">${total_score} / 100</div>
|
||
<div style="margin-top:12px;color:#888;">
|
||
$( [[ "$grade" == "A" ]] && echo "优秀 — 高度可复现" )
|
||
$( [[ "$grade" == "B" ]] && echo "良好 — 基本可复现" )
|
||
$( [[ "$grade" == "C" ]] && echo "一般 — 部分可复现" )
|
||
$( [[ "$grade" == "D" ]] && echo "不足 — 复现困难" )
|
||
$( [[ "$grade" == "F" ]] && echo "差 — 几乎不可复现" )
|
||
</div>
|
||
</div>
|
||
<div class="panel">
|
||
<h2>维度雷达图</h2>
|
||
<div id="radarChart" class="chart"></div>
|
||
</div>
|
||
<div class="panel">
|
||
<h2>维度明细</h2>
|
||
<table>
|
||
<tr><th>维度</th><th>评分</th><th>权重</th></tr>
|
||
<tr><td>许可证</td><td>$(awk -v s="$dim_license_s" 'BEGIN {printf "%.0f%%", s*100}')</td><td>15%</td></tr>
|
||
<tr><td>无密钥/PII</td><td>$(awk -v s="$dim_nosecret_s" 'BEGIN {printf "%.0f%%", s*100}')</td><td>15%</td></tr>
|
||
<tr><td>README 完整</td><td>$(awk -v s="$dim_readme_s" 'BEGIN {printf "%.0f%%", s*100}')</td><td>15%</td></tr>
|
||
<tr><td>依赖声明</td><td>$(awk -v s="$dim_deps_s" 'BEGIN {printf "%.0f%%", s*100}')</td><td>15%</td></tr>
|
||
<tr><td>构建说明</td><td>$(awk -v s="$dim_build_s" 'BEGIN {printf "%.0f%%", s*100}')</td><td>10%</td></tr>
|
||
<tr><td>CI 配置</td><td>$(awk -v s="$dim_ci_s" 'BEGIN {printf "%.0f%%", s*100}')</td><td>10%</td></tr>
|
||
<tr><td>测试证据</td><td>$(awk -v s="$dim_test_s" 'BEGIN {printf "%.0f%%", s*100}')</td><td>10%</td></tr>
|
||
<tr><td>数据可用性</td><td>$(awk -v s="$dim_data_s" 'BEGIN {printf "%.0f%%", s*100}')</td><td>10%</td></tr>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="panel">
|
||
<h2>详细评估与改进建议</h2>
|
||
<table>
|
||
<tr><th>维度</th><th>评分</th><th>证据</th><th>建议</th></tr>
|
||
<tr>
|
||
<td>许可证</td>
|
||
<td>$( [[ $dim_license_s == "1.0" ]] && echo "✅" || echo "❌")</td>
|
||
<td>${dim_license_d}</td>
|
||
<td>$( [[ $dim_license_s != "1.0" ]] && echo "建议添加 MIT/Apache-2.0/GPL-3.0 许可证" || echo "—")</td>
|
||
</tr>
|
||
<tr>
|
||
<td>无密钥/PII</td>
|
||
<td>$( [[ $dim_nosecret_s == "1.0" ]] && echo "✅" || echo "⚠️")</td>
|
||
<td>${dim_nosecret_d}</td>
|
||
<td>$( [[ $dim_nosecret_s != "1.0" ]] && echo "立即移除泄露的密钥,使用环境变量管理敏感信息" || echo "—")</td>
|
||
</tr>
|
||
<tr>
|
||
<td>README 完整</td>
|
||
<td>$( [[ $dim_readme_s == "1.0" ]] && echo "✅" || ([[ $dim_readme_s == "0.5" ]] && echo "⚠️" || echo "❌"))</td>
|
||
<td>${dim_readme_d}</td>
|
||
<td>$( [[ $dim_readme_s != "1.0" ]] && echo "补充项目目的、安装、使用、许可和引用章节" || echo "—")</td>
|
||
</tr>
|
||
<tr>
|
||
<td>依赖声明</td>
|
||
<td>$( [[ $dim_deps_s == "1.0" ]] && echo "✅" || ([[ $dim_deps_s == "0.5" ]] && echo "⚠️" || echo "❌"))</td>
|
||
<td>${dim_deps_d}</td>
|
||
<td>$( [[ $dim_deps_s != "1.0" ]] && echo "添加 package.json/go.mod/requirements.txt 等标准依赖文件" || echo "—")</td>
|
||
</tr>
|
||
<tr>
|
||
<td>构建说明</td>
|
||
<td>$( [[ $dim_build_s == "1.0" ]] && echo "✅" || ([[ $dim_build_s == "0.5" ]] && echo "⚠️" || echo "❌"))</td>
|
||
<td>${dim_build_d}</td>
|
||
<td>$( [[ $dim_build_s != "1.0" ]] && echo "添加 Makefile/Dockerfile + README 中的构建步骤" || echo "—")</td>
|
||
</tr>
|
||
<tr>
|
||
<td>CI 配置</td>
|
||
<td>$( [[ $dim_ci_s == "1.0" ]] && echo "✅" || ([[ $dim_ci_s == "0.5" ]] && echo "⚠️" || echo "❌"))</td>
|
||
<td>${dim_ci_d}</td>
|
||
<td>$( [[ $dim_ci_s != "1.0" ]] && echo "配置 GitLink CI 或 GitHub Actions 自动构建和测试" || echo "—")</td>
|
||
</tr>
|
||
<tr>
|
||
<td>测试证据</td>
|
||
<td>$( [[ $dim_test_s == "1.0" ]] && echo "✅" || ([[ $dim_test_s == "0.5" ]] && echo "⚠️" || echo "❌"))</td>
|
||
<td>${dim_test_d}</td>
|
||
<td>$( [[ $dim_test_s != "1.0" ]] && echo "添加单元测试和集成测试,在 README 中说明如何运行" || echo "—")</td>
|
||
</tr>
|
||
<tr>
|
||
<td>数据可用性</td>
|
||
<td>$( [[ $dim_data_s == "1.0" ]] && echo "✅" || ([[ $dim_data_s == "0.5" ]] && echo "⚠️" || echo "❌"))</td>
|
||
<td>${dim_data_d}</td>
|
||
<td>$( [[ $dim_data_s != "1.0" ]] && echo "说明数据集来源,提供 Zenodo/Figshare 链接或生成脚本" || echo "—")</td>
|
||
</tr>
|
||
</table>
|
||
</div>
|
||
|
||
</div>
|
||
<div class="footer">Generated by GitLink Research Assistant — ${today}</div>
|
||
|
||
<script>
|
||
var radarChart = echarts.init(document.getElementById('radarChart'));
|
||
radarChart.setOption({
|
||
radar: {
|
||
indicator: [
|
||
{ name: '许可证', max: 100 },
|
||
{ name: '无密钥', max: 100 },
|
||
{ name: 'README', max: 100 },
|
||
{ name: '依赖', max: 100 },
|
||
{ name: '构建', max: 100 },
|
||
{ name: 'CI', max: 100 },
|
||
{ name: '测试', max: 100 },
|
||
{ name: '数据', max: 100 }
|
||
],
|
||
center: ['50%', '55%'],
|
||
radius: '70%'
|
||
},
|
||
series: [{
|
||
type: 'radar',
|
||
data: [{
|
||
value: [
|
||
$(awk -v s="$dim_license_s" 'BEGIN {printf "%.0f", s*100}'),
|
||
$(awk -v s="$dim_nosecret_s" 'BEGIN {printf "%.0f", s*100}'),
|
||
$(awk -v s="$dim_readme_s" 'BEGIN {printf "%.0f", s*100}'),
|
||
$(awk -v s="$dim_deps_s" 'BEGIN {printf "%.0f", s*100}'),
|
||
$(awk -v s="$dim_build_s" 'BEGIN {printf "%.0f", s*100}'),
|
||
$(awk -v s="$dim_ci_s" 'BEGIN {printf "%.0f", s*100}'),
|
||
$(awk -v s="$dim_test_s" 'BEGIN {printf "%.0f", s*100}'),
|
||
$(awk -v s="$dim_data_s" 'BEGIN {printf "%.0f", s*100}')
|
||
],
|
||
name: '复现性',
|
||
areaStyle: { color: 'rgba(57,73,171,0.3)' },
|
||
lineStyle: { color: '#3949ab' }
|
||
}]
|
||
}]
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|
||
HTMLEOF
|
||
log_ok "HTML 评分卡已生成: $OUTPUT_FILE"
|
||
fi
|
||
|
||
# ═══ Summary ═══
|
||
echo ""
|
||
divider
|
||
log_title "复现性评估摘要"
|
||
echo " 综合评分: ${total_score}/100 (${grade})"
|
||
echo " 许可证: $( [[ $dim_license_s == "1.0" ]] && echo "✅" || echo "❌") ${dim_license_d}"
|
||
echo " 密钥/PII: $( [[ $dim_nosecret_s == "1.0" ]] && echo "✅" || echo "⚠️") ${dim_nosecret_d}"
|
||
echo " README: $( [[ $dim_readme_s == "1.0" ]] && echo "✅" || echo "❌") ${dim_readme_d}"
|
||
echo " 依赖: $( [[ $dim_deps_s == "1.0" ]] && echo "✅" || echo "❌") ${dim_deps_d}"
|
||
echo " 构建: $( [[ $dim_build_s == "1.0" ]] && echo "✅" || echo "❌") ${dim_build_d}"
|
||
echo " CI: $( [[ $dim_ci_s == "1.0" ]] && echo "✅" || echo "❌") ${dim_ci_d}"
|
||
echo " 测试: $( [[ $dim_test_s == "1.0" ]] && echo "✅" || echo "❌") ${dim_test_d}"
|
||
echo " 数据: $( [[ $dim_data_s == "1.0" ]] && echo "✅" || echo "❌") ${dim_data_d}"
|
||
divider
|
||
}
|
||
|
||
main "$@"
|