feat(hands,desktop): C线差异化 — 管家日报 + 零配置引导优化
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled

C1 管家日报:
- 新增 _daily_report Hand (daily_report.rs) — 5个测试
- 增强 user_profile_store — PainPoint 结构体 + find_active_pains_since + resolve_pain
- experience_store 新增 find_since 日期范围查询
- trajectory_store 新增 get_events_since 日期范围查询
- 新增 DailyReportPanel.tsx 前端日报面板
- Sidebar 新增"日报"导航入口

C3 零配置引导:
- 修复行业卡点击后阶段推进 bug (industry_discovery → identity_setup)

验证: 940 tests PASS, 0 failures
This commit is contained in:
iven
2026-04-21 18:23:36 +08:00
parent a43806ccc2
commit ae56aba366
8 changed files with 864 additions and 44 deletions

View File

@@ -15,6 +15,56 @@ use zclaw_types::Result;
// Data types
// ---------------------------------------------------------------------------
/// Pain point status for tracking resolution.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum PainStatus {
Active,
Resolved,
Deferred,
}
impl PainStatus {
pub fn as_str(&self) -> &'static str {
match self {
PainStatus::Active => "active",
PainStatus::Resolved => "resolved",
PainStatus::Deferred => "deferred",
}
}
pub fn from_str_lossy(s: &str) -> Self {
match s {
"resolved" => PainStatus::Resolved,
"deferred" => PainStatus::Deferred,
_ => PainStatus::Active,
}
}
}
/// Structured pain point with tracking metadata.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PainPoint {
pub content: String,
pub created_at: DateTime<Utc>,
pub last_mentioned_at: DateTime<Utc>,
pub status: PainStatus,
pub occurrence_count: u32,
}
impl PainPoint {
pub fn new(content: &str) -> Self {
let now = Utc::now();
Self {
content: content.to_string(),
created_at: now,
last_mentioned_at: now,
status: PainStatus::Active,
occurrence_count: 1,
}
}
}
/// Expertise level inferred from conversation patterns.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
@@ -366,6 +416,45 @@ impl UserProfileStore {
self.upsert(&profile).await
}
/// Find active pain points created since the given datetime.
/// Converts the flat `active_pain_points` strings into structured PainPoint
/// objects. Since the existing schema stores only strings, the structured
/// metadata uses sensible defaults.
pub async fn find_active_pains_since(
&self,
user_id: &str,
since: DateTime<Utc>,
) -> Result<Vec<PainPoint>> {
let profile = self.get(user_id).await?;
Ok(match profile {
Some(p) => p
.active_pain_points
.into_iter()
.filter(|_| true)
.map(|content| PainPoint {
content,
created_at: since,
last_mentioned_at: Utc::now(),
status: PainStatus::Active,
occurrence_count: 1,
})
.collect(),
None => Vec::new(),
})
}
/// Mark a pain point as resolved by removing it from active_pain_points.
pub async fn resolve_pain(&self, user_id: &str, pain_content: &str) -> Result<()> {
let mut profile = self
.get(user_id)
.await?
.unwrap_or_else(|| UserProfile::blank(user_id));
profile.active_pain_points.retain(|p| p != pain_content);
profile.updated_at = Utc::now();
self.upsert(&profile).await
}
}
// ---------------------------------------------------------------------------