use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum NormalizationMethod {
#[default]
Exact,
Fuzzy,
GraphRank,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct GroundingMetadata {
pub normalized_uri: Option<String>,
pub normalized_label: Option<String>,
pub normalized_prov: Option<String>,
pub normalized_score: Option<f32>,
pub normalized_method: Option<NormalizationMethod>,
}
impl GroundingMetadata {
pub fn new(
uri: String,
label: String,
prov: String,
score: f32,
method: NormalizationMethod,
) -> Self {
Self {
normalized_uri: Some(uri),
normalized_label: Some(label),
normalized_prov: Some(prov),
normalized_score: Some(score),
normalized_method: Some(method),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CoverageSignal {
pub total_categories: usize,
pub matched_categories: usize,
pub coverage_ratio: f32,
pub threshold: f32,
pub needs_review: bool,
}
impl CoverageSignal {
pub fn compute(categories: &[String], matched: usize, threshold: f32) -> Self {
let total = categories.len();
let ratio = if total > 0 {
matched as f32 / total as f32
} else {
0.0
};
Self {
total_categories: total,
matched_categories: matched,
coverage_ratio: ratio,
threshold,
needs_review: ratio < threshold,
}
}
}
#[cfg(feature = "medical")]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EntityType {
CancerDiagnosis,
Tumor,
GenomicVariant,
Biomarker,
Drug,
Treatment,
SideEffect,
}
#[cfg(feature = "medical")]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum RelationshipType {
HasTumor,
HasVariant,
HasBiomarker,
TreatedWith,
Causes,
HasDiagnosis,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExtractedEntity {
pub entity_type: String,
pub raw_value: String,
pub normalized_value: Option<String>,
pub grounding: Option<GroundingMetadata>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExtractedRelationship {
pub relationship_type: String,
pub source: String,
pub target: String,
pub confidence: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SchemaSignal {
pub entities: Vec<ExtractedEntity>,
pub relationships: Vec<ExtractedRelationship>,
pub confidence: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OntologyEntityType {
pub id: String,
pub label: String,
#[serde(default)]
pub uri_prefix: Option<String>,
#[serde(default)]
pub aliases: Vec<String>,
#[serde(default)]
pub category: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OntologyRelationshipType {
pub id: String,
pub label: String,
pub source_type: String,
pub target_type: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OntologyAntiPattern {
pub id: String,
pub description: String,
pub indicators: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OntologySchema {
pub name: String,
pub version: String,
pub entity_types: Vec<OntologyEntityType>,
#[serde(default)]
pub relationship_types: Vec<OntologyRelationshipType>,
#[serde(default)]
pub anti_patterns: Vec<OntologyAntiPattern>,
}
impl OntologySchema {
pub fn load_from_file(path: &str) -> Result<Self, Box<dyn std::error::Error>> {
let content = std::fs::read_to_string(path)?;
let schema: Self = serde_json::from_str(&content)?;
Ok(schema)
}
pub fn to_thesaurus_entries(&self) -> Vec<(String, String, Option<String>)> {
let mut entries = Vec::new();
for entity_type in &self.entity_types {
let url = entity_type
.uri_prefix
.clone()
.unwrap_or_else(|| format!("kg://{}", entity_type.id));
entries.push((
entity_type.id.clone(),
entity_type.label.clone(),
Some(url.clone()),
));
for alias in &entity_type.aliases {
entries.push((entity_type.id.clone(), alias.clone(), Some(url.clone())));
}
}
entries
}
pub fn category_ids(&self) -> Vec<String> {
self.entity_types.iter().map(|e| e.id.clone()).collect()
}
pub fn uri_for(&self, entity_type_id: &str) -> Option<String> {
self.entity_types
.iter()
.find(|e| e.id == entity_type_id)
.and_then(|e| e.uri_prefix.clone())
}
}