use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmphasisWeights {
pub bold: f32,
pub italic: f32,
pub bold_italic: f32,
pub code: f32,
}
impl Default for EmphasisWeights {
fn default() -> Self {
Self {
bold: 1.0,
italic: 0.7,
bold_italic: 1.2,
code: 0.8,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LinkageStrategy {
pub similarity_threshold: f32,
pub co_occurrence_weight: f32,
pub tag_membership_weight: f32,
pub max_edges_per_node: usize,
pub decay_factor: f32,
pub min_node_weight: f32,
}
impl Default for LinkageStrategy {
fn default() -> Self {
Self {
similarity_threshold: 0.85,
co_occurrence_weight: 0.6,
tag_membership_weight: 1.0,
max_edges_per_node: 20,
decay_factor: 0.9,
min_node_weight: 0.5,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchConfig {
pub default_strategy: SearchStrategy,
pub graph_depth: u32,
pub rag_top_k: usize,
pub graph_weight: f32,
pub rag_weight: f32,
}
impl Default for SearchConfig {
fn default() -> Self {
Self {
default_strategy: SearchStrategy::Auto,
graph_depth: 2,
rag_top_k: 5,
graph_weight: 0.6,
rag_weight: 0.4,
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "snake_case")]
pub enum SearchStrategy {
GraphFirst,
RagFirst,
Parallel,
#[default]
Auto,
}
impl std::fmt::Display for SearchStrategy {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SearchStrategy::GraphFirst => write!(f, "graph_first"),
SearchStrategy::RagFirst => write!(f, "rag_first"),
SearchStrategy::Parallel => write!(f, "parallel"),
SearchStrategy::Auto => write!(f, "auto"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct LinkageConfig {
#[serde(default)]
pub emphasis_weights: EmphasisWeights,
#[serde(default)]
pub linkage_strategy: LinkageStrategy,
#[serde(default)]
pub search: SearchConfig,
}
impl LinkageConfig {
pub fn new() -> Self {
Self::default()
}
pub fn strict() -> Self {
Self {
linkage_strategy: LinkageStrategy {
similarity_threshold: 0.92,
max_edges_per_node: 10,
min_node_weight: 0.7,
..Default::default()
},
..Default::default()
}
}
pub fn loose() -> Self {
Self {
linkage_strategy: LinkageStrategy {
similarity_threshold: 0.75,
max_edges_per_node: 50,
min_node_weight: 0.3,
..Default::default()
},
..Default::default()
}
}
pub fn weight_for_style(&self, style: &crate::domain::value_objects::EmphasisStyle) -> f32 {
use crate::domain::value_objects::EmphasisStyle;
match style {
EmphasisStyle::Bold => self.emphasis_weights.bold,
EmphasisStyle::Italic => self.emphasis_weights.italic,
EmphasisStyle::BoldItalic => self.emphasis_weights.bold_italic,
EmphasisStyle::Code => self.emphasis_weights.code,
}
}
pub fn should_create_node(&self, weight: f32) -> bool {
weight >= self.linkage_strategy.min_node_weight
}
pub fn should_create_edge(&self, similarity: f32) -> bool {
similarity >= self.linkage_strategy.similarity_threshold
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::domain::value_objects::EmphasisStyle;
#[test]
fn test_default_config() {
let config = LinkageConfig::new();
assert_eq!(config.emphasis_weights.bold, 1.0);
assert_eq!(config.linkage_strategy.similarity_threshold, 0.85);
assert_eq!(config.search.default_strategy, SearchStrategy::Auto);
}
#[test]
fn test_weight_for_style() {
let config = LinkageConfig::new();
assert_eq!(config.weight_for_style(&EmphasisStyle::Bold), 1.0);
assert_eq!(config.weight_for_style(&EmphasisStyle::Italic), 0.7);
assert_eq!(config.weight_for_style(&EmphasisStyle::BoldItalic), 1.2);
assert_eq!(config.weight_for_style(&EmphasisStyle::Code), 0.8);
}
#[test]
fn test_should_create_node() {
let config = LinkageConfig::new();
assert!(config.should_create_node(0.7));
assert!(config.should_create_node(0.5));
assert!(!config.should_create_node(0.4));
}
#[test]
fn test_should_create_edge() {
let config = LinkageConfig::new();
assert!(config.should_create_edge(0.90));
assert!(config.should_create_edge(0.85));
assert!(!config.should_create_edge(0.80));
}
#[test]
fn test_strict_config() {
let config = LinkageConfig::strict();
assert_eq!(config.linkage_strategy.similarity_threshold, 0.92);
assert_eq!(config.linkage_strategy.max_edges_per_node, 10);
}
#[test]
fn test_loose_config() {
let config = LinkageConfig::loose();
assert_eq!(config.linkage_strategy.similarity_threshold, 0.75);
assert_eq!(config.linkage_strategy.max_edges_per_node, 50);
}
#[test]
fn test_serde_roundtrip() {
let config = LinkageConfig::new();
let json = serde_json::to_string(&config).unwrap();
let parsed: LinkageConfig = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.emphasis_weights.bold, config.emphasis_weights.bold);
}
}