fix: 修复飞轮聚合三处数据质量问题

- 最新快照改为按 at 字段取最大值,不再依赖写入顺序
- blocked_reasons 改为只取最新快照,消除历史快照反复计数导致的虚高
- 版本队列排序改为语义版本比较,修复字符串排序下 1.4.9 > 1.4.10 的问题
- 聚合时跳过 conventions_version=unknown 的历史脏数据项目
This commit is contained in:
lanrtop 2026-04-08 02:18:23 +09:00
parent 0065866f6b
commit 69c05380cc

View File

@ -4,10 +4,10 @@ use std::path::Path;
// ── 主节点master CONVENTIONS.md 内容和版本(编译时嵌入)──────────────────── // ── 主节点master CONVENTIONS.md 内容和版本(编译时嵌入)────────────────────
const MASTER_CONVENTIONS: &str = include_str!("../../../.blueprint/CONVENTIONS.md"); const MASTER_CONVENTIONS: &str = include_str!("../../../.blueprint/CONVENTIONS.md");
const MASTER_CONVENTIONS_VERSION: &str = "1.4.0"; const MASTER_CONVENTIONS_VERSION: &str = "1.4.2";
// CLAUDE.md 蓝图管理区模板(版本与 CONVENTIONS 保持一致) // CLAUDE.md 蓝图管理区模板(版本与 CONVENTIONS 保持一致)
const CLAUDE_MANAGED_TEMPLATE: &str = r#"<!-- BLUEPRINT_MANAGED_START version="1.4.0" --> const CLAUDE_MANAGED_TEMPLATE: &str = r#"<!-- BLUEPRINT_MANAGED_START version="1.4.2" -->
## ##
使 `.blueprint/` 使 `.blueprint/`
@ -1192,22 +1192,25 @@ pub fn get_flywheel_stats(project_paths: Vec<String>) -> Result<FlywheelStats, S
}; };
projects_with_data += 1; projects_with_data += 1;
// 所有快照中收集 blocked_reasons // 最新快照:按 at 字段取最大值,不依赖写入顺序
for snap in snapshots.iter() { let latest = snapshots.iter()
if let Some(reasons) = snap["blocked_reasons"].as_array() { .max_by_key(|s| s["at"].as_str().unwrap_or(""))
.unwrap();
let conventions_version = latest["conventions_version"]
.as_str().unwrap_or("unknown").to_string();
// conventions_version 无效时跳过(历史脏数据或未同步规则的项目)
if conventions_version == "unknown" { continue; }
// blocked_reasons 只取最新快照,避免历史快照反复计数导致虚高
if let Some(reasons) = latest["blocked_reasons"].as_array() {
for r in reasons { for r in reasons {
if let Some(s) = r.as_str() { if let Some(s) = r.as_str() {
*reason_counts.entry(s.to_string()).or_insert(0) += 1; *reason_counts.entry(s.to_string()).or_insert(0) += 1;
} }
} }
} }
}
// 最新快照
let latest = snapshots.last().unwrap();
let conventions_version = latest["conventions_version"]
.as_str().unwrap_or("unknown").to_string();
let latest_dt = latest["at"].as_str() let latest_dt = latest["at"].as_str()
.and_then(|s| chrono::DateTime::parse_from_rfc3339(s).ok()) .and_then(|s| chrono::DateTime::parse_from_rfc3339(s).ok())
@ -1312,8 +1315,17 @@ pub fn get_flywheel_stats(project_paths: Vec<String>) -> Result<FlywheelStats, S
} }
}).collect(); }).collect();
// 按版本降序 // 按语义版本降序(解析 "1.4.10" 为 (1,4,10) 再比较,避免字符串排序的 "1.4.9" > "1.4.10" 问题)
cohorts.sort_by(|a, b| b.conventions_version.cmp(&a.conventions_version)); let parse_semver = |s: &str| -> (u32, u32, u32) {
let mut parts = s.splitn(3, '.');
let major = parts.next().and_then(|x| x.parse().ok()).unwrap_or(0);
let minor = parts.next().and_then(|x| x.parse().ok()).unwrap_or(0);
let patch = parts.next().and_then(|x| x.parse().ok()).unwrap_or(0);
(major, minor, patch)
};
cohorts.sort_by(|a, b| {
parse_semver(&b.conventions_version).cmp(&parse_semver(&a.conventions_version))
});
// TOP10 blocked reasons // TOP10 blocked reasons
let mut sorted_reasons: Vec<_> = reason_counts.into_iter().collect(); let mut sorted_reasons: Vec<_> = reason_counts.into_iter().collect();
@ -1507,9 +1519,11 @@ fn append_usage_snapshot(bp_dir: &Path, manifest: &BlueprintManifest, stats: &Bl
.unwrap_or("unknown") .unwrap_or("unknown")
.to_string(); .to_string();
// 读取 CONVENTIONS 版本 // 读取 CONVENTIONS 版本;缺失时跳过快照写入,避免产生 vunknown 脏数据
let conventions_version = read_frontmatter_version(&bp_dir.join("CONVENTIONS.md")) let conventions_version = match read_frontmatter_version(&bp_dir.join("CONVENTIONS.md")) {
.unwrap_or_else(|| "unknown".to_string()); Some(v) => v,
None => return,
};
// UTC 时间 // UTC 时间
let now = chrono::Utc::now(); let now = chrono::Utc::now();