use chrono::NaiveDate;
use serde::{Deserialize, Serialize};
use crate::case::CaseRef;
use crate::ids::AccountId;
use crate::locale::Locale;
use crate::read::{DataSensitivity, TrustLevel};
use crate::reduce::SourcePolicy;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Citation {
pub source_id: String,
pub label: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub uri: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub locator: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub version: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct KnowledgeRequest {
pub account_id: AccountId,
pub query: String,
pub locale: Locale,
pub case_refs: Vec<CaseRef>,
pub source_policy: SourcePolicy,
pub max_chunks: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub as_of: Option<NaiveDate>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct KnowledgeChunk {
pub chunk_id: String,
pub source_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source_version: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub effective_from: Option<NaiveDate>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub effective_to: Option<NaiveDate>,
#[serde(default)]
pub permissions: Vec<String>,
pub citation: Citation,
pub text: String,
pub trust: TrustLevel,
pub sensitivity: DataSensitivity,
}
impl KnowledgeChunk {
#[must_use]
pub fn is_effective_on(&self, date: NaiveDate) -> bool {
self.effective_from.is_none_or(|from| from <= date)
&& self.effective_to.is_none_or(|to| date <= to)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, thiserror::Error)]
#[serde(tag = "kind", rename_all = "snake_case")]
#[non_exhaustive]
pub enum KnowledgeError {
#[error("knowledge source {source_id} unavailable")]
Unavailable {
source_id: String,
},
#[error("knowledge access unauthorized")]
Unauthorized,
#[error("knowledge retrieval timed out")]
Timeout,
#[error("knowledge provider returned malformed data")]
Malformed,
#[error("knowledge failure {code}")]
Other {
code: String,
},
}
#[async_trait::async_trait]
pub trait KnowledgeProvider: Send + Sync {
async fn retrieve(
&self,
request: KnowledgeRequest,
) -> Result<Vec<KnowledgeChunk>, KnowledgeError>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn effective_window() {
let chunk = KnowledgeChunk {
chunk_id: "c".into(),
source_id: "s".into(),
source_version: None,
effective_from: NaiveDate::from_ymd_opt(2026, 1, 1),
effective_to: NaiveDate::from_ymd_opt(2026, 12, 31),
permissions: vec![],
citation: Citation {
source_id: "s".into(),
label: "Source".into(),
uri: None,
locator: None,
version: None,
},
text: "t".into(),
trust: TrustLevel::Retrieved,
sensitivity: DataSensitivity::Public,
};
assert!(chunk.is_effective_on(NaiveDate::from_ymd_opt(2026, 6, 1).unwrap()));
assert!(!chunk.is_effective_on(NaiveDate::from_ymd_opt(2025, 6, 1).unwrap()));
}
}