fix(growth): MEDIUM-12 ProfileUpdater 补齐 5 维度画像更新
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

根因: ProfileUpdater 只处理 industry 和 communication_style 2/5 维度,
跳过 recent_topic、pain_point、preferred_tool。

修复:
- ProfileFieldUpdate 添加 kind 字段 (SetField | AppendArray)
- collect_updates() 现在处理全部 5 个维度:
  - industry, communication_style → SetField (直接覆盖)
  - recent_topic, pain_point, preferred_tool → AppendArray (追加去重)
- growth.rs 根据 ProfileUpdateKind 分派到不同的 UserProfileStore 方法:
  - SetField → update_field()
  - AppendArray → add_recent_topic() / add_pain_point() / add_preferred_tool()
- ProfileUpdateKind re-exported from lib.rs

测试: test_collect_updates_all_five_dimensions 验证 5 个维度 + 2 种更新类型
This commit is contained in:
iven
2026-04-18 23:07:31 +08:00
parent 2c0602e0e6
commit 1595290db2
3 changed files with 100 additions and 10 deletions

View File

@@ -133,7 +133,7 @@ pub use retrieval::{EmbeddingClient, MemoryCache, QueryAnalyzer, SemanticScorer}
pub use summarizer::SummaryLlmDriver;
pub use experience_extractor::ExperienceExtractor;
pub use json_utils::{extract_json_block, extract_string_array};
pub use profile_updater::{ProfileFieldUpdate, UserProfileUpdater};
pub use profile_updater::{ProfileFieldUpdate, ProfileUpdateKind, UserProfileUpdater};
pub use pattern_aggregator::{AggregatedPattern, PatternAggregator};
pub use skill_generator::{SkillCandidate, SkillGenerator};
pub use quality_gate::{QualityGate, QualityReport};

View File

@@ -4,11 +4,21 @@
use crate::types::CombinedExtraction;
/// 更新类型:字段覆盖 vs 数组追加
#[derive(Debug, Clone, PartialEq)]
pub enum ProfileUpdateKind {
/// 直接覆盖字段值industry, communication_style
SetField,
/// 追加到 JSON 数组字段recent_topic, pain_point, preferred_tool
AppendArray,
}
/// 待更新的画像字段
#[derive(Debug, Clone, PartialEq)]
pub struct ProfileFieldUpdate {
pub field: String,
pub value: String,
pub kind: ProfileUpdateKind,
}
/// 用户画像更新器
@@ -22,11 +32,7 @@ impl UserProfileUpdater {
}
/// 从提取结果中收集需要更新的画像字段
/// 返回 (field, value) 列表,由调用方负责实际的异步写入
///
/// 注意:只收集 UserProfileStore::update_field() 支持的字段。
/// ProfileSignals 中的 recent_topic / pain_point / preferred_tool
/// 需要 update_field 扩展后才能写入,当前跳过。
/// 返回 (field, value, kind) 列表,由调用方根据 kind 选择写入方式
pub fn collect_updates(
&self,
extraction: &CombinedExtraction,
@@ -38,6 +44,7 @@ impl UserProfileUpdater {
updates.push(ProfileFieldUpdate {
field: "industry".to_string(),
value: industry.clone(),
kind: ProfileUpdateKind::SetField,
});
}
@@ -45,6 +52,31 @@ impl UserProfileUpdater {
updates.push(ProfileFieldUpdate {
field: "communication_style".to_string(),
value: style.clone(),
kind: ProfileUpdateKind::SetField,
});
}
if let Some(ref topic) = signals.recent_topic {
updates.push(ProfileFieldUpdate {
field: "recent_topic".to_string(),
value: topic.clone(),
kind: ProfileUpdateKind::AppendArray,
});
}
if let Some(ref pain) = signals.pain_point {
updates.push(ProfileFieldUpdate {
field: "pain_point".to_string(),
value: pain.clone(),
kind: ProfileUpdateKind::AppendArray,
});
}
if let Some(ref tool) = signals.preferred_tool {
updates.push(ProfileFieldUpdate {
field: "preferred_tool".to_string(),
value: tool.clone(),
kind: ProfileUpdateKind::AppendArray,
});
}
@@ -73,6 +105,7 @@ mod tests {
assert_eq!(updates.len(), 1);
assert_eq!(updates[0].field, "industry");
assert_eq!(updates[0].value, "healthcare");
assert_eq!(updates[0].kind, ProfileUpdateKind::SetField);
}
#[test]
@@ -94,4 +127,31 @@ mod tests {
assert_eq!(updates.len(), 2);
}
#[test]
fn test_collect_updates_all_five_dimensions() {
let mut extraction = CombinedExtraction::default();
extraction.profile_signals.industry = Some("healthcare".to_string());
extraction.profile_signals.communication_style = Some("concise".to_string());
extraction.profile_signals.recent_topic = Some("报表自动化".to_string());
extraction.profile_signals.pain_point = Some("手动汇总太慢".to_string());
extraction.profile_signals.preferred_tool = Some("researcher".to_string());
let updater = UserProfileUpdater::new();
let updates = updater.collect_updates(&extraction);
assert_eq!(updates.len(), 5);
let set_fields: Vec<_> = updates
.iter()
.filter(|u| u.kind == ProfileUpdateKind::SetField)
.map(|u| u.field.as_str())
.collect();
let append_fields: Vec<_> = updates
.iter()
.filter(|u| u.kind == ProfileUpdateKind::AppendArray)
.map(|u| u.field.as_str())
.collect();
assert_eq!(set_fields, vec!["industry", "communication_style"]);
assert_eq!(append_fields, vec!["recent_topic", "pain_point", "preferred_tool"]);
}
}