use tonic::Status;
use uuid::Uuid;
use jammi_db::AuditError;
use crate::proto::audit as pb;
use jammi_db::PerQueryAudit;
pub fn parse_query_id(id: &str) -> Result<Uuid, Status> {
if id.is_empty() {
return Err(Status::invalid_argument("query_id is required"));
}
Uuid::parse_str(id).map_err(|e| Status::invalid_argument(format!("invalid query_id: {e}")))
}
impl From<PerQueryAudit> for pb::PerQueryAudit {
fn from(r: PerQueryAudit) -> Self {
pb::PerQueryAudit {
query_id: r.query_id.to_string(),
tenant_id: r.tenant_id.unwrap_or_default(),
model_id: r.model_id,
model_version: r.model_version,
query_lineage: r.query_lineage.to_string(),
top_k_result_ids: r.top_k_result_ids,
retrieval_scores: r.retrieval_scores,
executed_at_micros: r.executed_at.timestamp_micros(),
signature: r.signature,
}
}
}
pub fn record_from_wire(p: pb::PerQueryAudit) -> Result<PerQueryAudit, AuditError> {
let query_id = Uuid::parse_str(&p.query_id)
.map_err(|e| AuditError::Storage(format!("invalid query_id from server: {e}")))?;
let query_lineage: serde_json::Value = if p.query_lineage.is_empty() {
serde_json::Value::Object(serde_json::Map::new())
} else {
serde_json::from_str(&p.query_lineage)?
};
let executed_at = chrono::DateTime::from_timestamp_micros(p.executed_at_micros)
.ok_or_else(|| AuditError::Storage("executed_at_micros out of range".into()))?;
let tenant_id = if p.tenant_id.is_empty() {
None
} else {
Some(p.tenant_id)
};
Ok(PerQueryAudit {
query_id,
tenant_id,
model_id: p.model_id,
model_version: p.model_version,
query_lineage,
top_k_result_ids: p.top_k_result_ids,
retrieval_scores: p.retrieval_scores,
executed_at,
signature: p.signature,
})
}