gitlink-cli/workflows/academic/08-research-compliance.sh

508 lines
21 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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 "$@"