use serde::{Deserialize, Serialize};
use std::fmt;
macro_rules! string_id {
($name:ident) => {
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
#[serde(transparent)]
pub struct $name(pub String);
impl $name {
pub fn new(value: impl Into<String>) -> Result<Self, ValidationError> {
let value = value.into();
validate_text(stringify!($name), &value)?;
Ok(Self(value))
}
}
impl fmt::Display for $name {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(formatter)
}
}
};
}
string_id!(TenantId);
string_id!(PersonId);
string_id!(SourceId);
string_id!(EvidenceId);
string_id!(ClaimId);
string_id!(ProfileEntryId);
string_id!(DailyReviewId);
pub type Timestamp = i64;
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct TimeRange {
pub from: Timestamp,
pub until: Option<Timestamp>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct Source {
pub id: SourceId,
pub tenant_id: TenantId,
pub person_id: PersonId,
pub revision: u64,
pub kind: SourceKind,
pub content: String,
pub captured_at: Timestamp,
pub recorded_at: Timestamp,
pub deleted_at: Option<Timestamp>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SourceKind {
Conversation,
Screen,
Audio,
Document,
Integration,
UserCorrection,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct Evidence {
pub id: EvidenceId,
pub tenant_id: TenantId,
pub person_id: PersonId,
pub source_id: SourceId,
pub source_revision: u64,
pub quote: String,
pub byte_range: Option<ByteRange>,
pub recorded_at: Timestamp,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct ByteRange {
pub start: u64,
pub end: u64,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct Claim {
pub id: ClaimId,
pub tenant_id: TenantId,
pub person_id: PersonId,
pub subject: String,
pub predicate: String,
pub value: String,
pub kind: ClaimKind,
pub valid_time: TimeRange,
pub recorded_time: TimeRange,
pub status: ClaimStatus,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ClaimKind {
Fact,
ProfileFact,
Preference,
Task,
Skill,
Recommendation,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ClaimStatus {
Accepted,
Superseded,
Retracted,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct ClaimEvidence {
pub tenant_id: TenantId,
pub person_id: PersonId,
pub claim_id: ClaimId,
pub evidence_id: EvidenceId,
pub relation: EvidenceRelation,
pub confidence_basis_points: u16,
}
impl ClaimEvidence {
pub fn validate(&self) -> Result<(), ValidationError> {
for (field, value) in [
("tenant id", &self.tenant_id.0),
("person id", &self.person_id.0),
("claim id", &self.claim_id.0),
("evidence id", &self.evidence_id.0),
] {
validate_text(field, value)?;
}
if self.confidence_basis_points > 10_000 {
return Err(ValidationError::InvalidConfidence(
self.confidence_basis_points,
));
}
Ok(())
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EvidenceRelation {
Supports,
Contradicts,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct ProfileEntry {
pub id: ProfileEntryId,
pub tenant_id: TenantId,
pub person_id: PersonId,
pub key: String,
pub value: String,
pub stability: ProfileStability,
pub claim_id: ClaimId,
pub recorded_at: Timestamp,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ProfileStability {
Stable,
Current,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct DailyReview {
pub id: DailyReviewId,
pub tenant_id: TenantId,
pub person_id: PersonId,
pub day: String,
pub summary: String,
pub evidence_ids: Vec<EvidenceId>,
pub recorded_at: Timestamp,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct RetrievalPack {
pub query: String,
pub items: Vec<RetrievalItem>,
pub gaps: Vec<String>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct RetrievalItem {
pub memory: MemoryRef,
pub excerpt: String,
pub relevance_basis_points: u16,
pub evidence_ids: Vec<EvidenceId>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(tag = "kind", content = "id", rename_all = "snake_case")]
pub enum MemoryRef {
Source(SourceId),
Evidence(EvidenceId),
Claim(ClaimId),
ProfileEntry(ProfileEntryId),
DailyReview(DailyReviewId),
}
#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
pub enum ValidationError {
#[error("{0} must not be empty")]
EmptyText(&'static str),
#[error("confidence {0} must be at most 10000 basis points")]
InvalidConfidence(u16),
}
fn validate_text(field: &'static str, value: &str) -> Result<(), ValidationError> {
if value.trim().is_empty() {
return Err(ValidationError::EmptyText(field));
}
Ok(())
}