use chrono::{DateTime, Utc};
use serde::Deserialize;
use uuid::Uuid;
use crate::model::Memory;
#[derive(Debug, Deserialize)]
pub(crate) struct ExtractionResponse {
#[serde(default)]
pub entries: Vec<ExtractedEntry>,
}
#[derive(Debug, Deserialize)]
pub(crate) struct ExtractedEntry {
pub lossless_restatement: String,
#[serde(default)]
pub keywords: Vec<String>,
#[serde(default, deserialize_with = "deserialize_nullable_str")]
pub timestamp: Option<String>,
#[serde(default, deserialize_with = "deserialize_nullable_str")]
pub location: Option<String>,
#[serde(default)]
pub persons: Vec<String>,
#[serde(default)]
pub entities: Vec<String>,
#[serde(default, deserialize_with = "deserialize_nullable_str")]
pub topic: Option<String>,
}
impl ExtractedEntry {
#[must_use]
pub(crate) fn into_memory(self) -> Option<Memory> {
if self.lossless_restatement.is_empty() {
return None;
}
let timestamp = self.timestamp.as_deref().and_then(parse_timestamp);
Some(Memory {
id: Uuid::new_v4(),
content: self.lossless_restatement,
keywords: self.keywords,
timestamp,
location: self.location,
persons: self.persons,
entities: self.entities,
topic: self.topic,
created_at: Utc::now(),
updated_at: None,
namespace: None,
})
}
}
#[derive(Debug, Default, Deserialize)]
pub(crate) struct QueryPlan {
#[serde(default)]
pub keywords: Vec<String>,
#[serde(default)]
pub persons: Vec<String>,
#[serde(default, deserialize_with = "deserialize_nullable_str")]
pub time_expression: Option<String>,
#[serde(default, deserialize_with = "deserialize_nullable_str")]
pub location: Option<String>,
#[serde(default)]
pub entities: Vec<String>,
#[serde(default)]
pub required_info: Vec<String>,
#[serde(default)]
pub search_queries: Vec<String>,
}
#[derive(Debug, Deserialize)]
pub(crate) struct ReconcileResponse {
#[serde(default)]
pub actions: Vec<ReconcileAction>,
}
#[derive(Debug, Deserialize)]
pub(crate) struct ReconcileAction {
#[serde(default, deserialize_with = "deserialize_index")]
pub new_index: Option<usize>,
#[serde(default = "default_action")]
pub action: String,
#[serde(default, deserialize_with = "deserialize_index")]
pub existing_index: Option<usize>,
}
#[derive(Debug, Deserialize)]
pub(crate) struct AnswerResponse {
#[serde(default)]
pub answer: String,
}
#[derive(Debug, Deserialize)]
pub(crate) struct CompletenessResponse {
#[serde(default)]
pub assessment: String,
}
#[derive(Debug, Deserialize)]
pub(crate) struct MissingQueriesResponse {
#[serde(default)]
pub targeted_queries: Vec<String>,
}
#[derive(Debug, Deserialize)]
pub(crate) struct ReExtractResponse {
#[serde(default)]
pub keywords: Vec<String>,
#[serde(default, deserialize_with = "deserialize_nullable_str")]
pub timestamp: Option<String>,
#[serde(default, deserialize_with = "deserialize_nullable_str")]
pub location: Option<String>,
#[serde(default)]
pub persons: Vec<String>,
#[serde(default)]
pub entities: Vec<String>,
#[serde(default, deserialize_with = "deserialize_nullable_str")]
pub topic: Option<String>,
}
impl ReExtractResponse {
pub(crate) fn apply_to(self, entry: &mut Memory) {
entry.keywords = self.keywords;
entry.persons = self.persons;
entry.entities = self.entities;
entry.location = self.location;
entry.topic = self.topic;
if let Some(ts) = self.timestamp.as_deref().and_then(parse_timestamp) {
entry.timestamp = Some(ts);
}
}
}
fn default_action() -> String {
"add".to_owned()
}
fn parse_timestamp(s: &str) -> Option<DateTime<Utc>> {
DateTime::parse_from_rfc3339(s)
.ok()
.map(|dt| dt.with_timezone(&Utc))
.or_else(|| {
chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S")
.ok()
.map(|ndt| ndt.and_utc())
})
}
fn deserialize_nullable_str<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
D: serde::Deserializer<'de>,
{
let opt: Option<String> = Option::deserialize(deserializer)?;
Ok(opt.filter(|s| !s.is_empty() && s != "null"))
}
fn deserialize_index<'de, D>(deserializer: D) -> Result<Option<usize>, D::Error>
where
D: serde::Deserializer<'de>,
{
let val: Option<serde_json::Value> = Option::deserialize(deserializer)?;
Ok(val.and_then(|v| {
v.as_u64()
.and_then(|n| usize::try_from(n).ok())
.or_else(|| v.as_str().and_then(|s| s.parse().ok()))
}))
}