This commit is contained in:
2026-04-24 05:47:31 +02:00
parent 6d00c0ddf4
commit a7030d7d0f
18 changed files with 842 additions and 21 deletions

View File

@@ -0,0 +1,89 @@
// file: kb_lib/src/db/dtos/analysis_signal.rs
//! Analysis signal DTO.
/// Application-facing analysis signal DTO.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct KbAnalysisSignalDto {
/// Optional numeric primary key.
pub id: std::option::Option<i64>,
/// Signal kind.
pub signal_kind: std::string::String,
/// Signal severity.
pub severity: crate::KbAnalysisSignalSeverity,
/// Logical object key, for example a mint, signature or pool address.
pub object_key: std::string::String,
/// Optional related on-chain observation id.
pub related_observation_id: std::option::Option<i64>,
/// Optional numeric score.
pub score: std::option::Option<f64>,
/// Signal payload.
pub payload: serde_json::Value,
/// Creation timestamp.
pub created_at: chrono::DateTime<chrono::Utc>,
}
impl KbAnalysisSignalDto {
/// Creates a new analysis signal DTO with the current timestamp.
pub fn new(
signal_kind: std::string::String,
severity: crate::KbAnalysisSignalSeverity,
object_key: std::string::String,
related_observation_id: std::option::Option<i64>,
score: std::option::Option<f64>,
payload: serde_json::Value,
) -> Self {
Self {
id: None,
signal_kind,
severity,
object_key,
related_observation_id,
score,
payload,
created_at: chrono::Utc::now(),
}
}
}
impl TryFrom<crate::KbAnalysisSignalEntity> for KbAnalysisSignalDto {
type Error = crate::KbError;
fn try_from(entity: crate::KbAnalysisSignalEntity) -> Result<Self, Self::Error> {
let severity_result = crate::KbAnalysisSignalSeverity::from_i16(entity.severity);
let severity = match severity_result {
Ok(severity) => severity,
Err(error) => return Err(error),
};
let created_at_result = chrono::DateTime::parse_from_rfc3339(&entity.created_at);
let created_at = match created_at_result {
Ok(created_at) => created_at.with_timezone(&chrono::Utc),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot parse analysis signal created_at '{}': {}",
entity.created_at, error
)));
}
};
let payload_result = serde_json::from_str::<serde_json::Value>(&entity.payload_json);
let payload = match payload_result {
Ok(payload) => payload,
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot parse analysis signal payload_json '{}': {}",
entity.payload_json, error
)));
}
};
Ok(Self {
id: Some(entity.id),
signal_kind: entity.signal_kind,
severity,
object_key: entity.object_key,
related_observation_id: entity.related_observation_id,
score: entity.score,
payload,
created_at,
})
}
}

View File

@@ -0,0 +1,104 @@
// file: kb_lib/src/db/dtos/onchain_observation.rs
//! On-chain observation DTO.
/// Application-facing on-chain observation DTO.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct KbOnchainObservationDto {
/// Optional numeric primary key.
pub id: std::option::Option<i64>,
/// Observation kind.
pub observation_kind: std::string::String,
/// Observation source family.
pub source_kind: crate::KbObservationSourceKind,
/// Optional source endpoint logical name.
pub endpoint_name: std::option::Option<std::string::String>,
/// Logical object key, for example a mint, signature or pool address.
pub object_key: std::string::String,
/// Optional slot number.
pub slot: std::option::Option<u64>,
/// Raw JSON payload.
pub payload: serde_json::Value,
/// Observation timestamp.
pub observed_at: chrono::DateTime<chrono::Utc>,
}
impl KbOnchainObservationDto {
/// Creates a new on-chain observation DTO with the current timestamp.
pub fn new(
observation_kind: std::string::String,
source_kind: crate::KbObservationSourceKind,
endpoint_name: std::option::Option<std::string::String>,
object_key: std::string::String,
slot: std::option::Option<u64>,
payload: serde_json::Value,
) -> Self {
Self {
id: None,
observation_kind,
source_kind,
endpoint_name,
object_key,
slot,
payload,
observed_at: chrono::Utc::now(),
}
}
}
impl TryFrom<crate::KbOnchainObservationEntity> for KbOnchainObservationDto {
type Error = crate::KbError;
fn try_from(entity: crate::KbOnchainObservationEntity) -> Result<Self, Self::Error> {
let source_kind_result = crate::KbObservationSourceKind::from_i16(entity.source_kind);
let source_kind = match source_kind_result {
Ok(source_kind) => source_kind,
Err(error) => return Err(error),
};
let observed_at_result = chrono::DateTime::parse_from_rfc3339(&entity.observed_at);
let observed_at = match observed_at_result {
Ok(observed_at) => observed_at.with_timezone(&chrono::Utc),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot parse on-chain observation observed_at '{}': {}",
entity.observed_at, error
)));
}
};
let payload_result = serde_json::from_str::<serde_json::Value>(&entity.payload_json);
let payload = match payload_result {
Ok(payload) => payload,
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot parse on-chain observation payload_json '{}': {}",
entity.payload_json, error
)));
}
};
let slot = match entity.slot {
Some(slot) => {
let slot_result = u64::try_from(slot);
match slot_result {
Ok(slot) => Some(slot),
Err(error) => {
return Err(crate::KbError::Db(format!(
"cannot convert on-chain observation slot '{}' to u64: {}",
slot, error
)));
}
}
}
None => None,
};
Ok(Self {
id: Some(entity.id),
observation_kind: entity.observation_kind,
source_kind,
endpoint_name: entity.endpoint_name,
object_key: entity.object_key,
slot,
payload,
observed_at,
})
}
}