relay_knowledge/api/operations/
ingestion.rs1use serde::{Deserialize, Serialize};
2
3use crate::{
4 api::ApiMetadata,
5 domain::{
6 CommitReceipt, ConfidenceScore, EvidenceExtractionMetadata, EvidenceModality, EvidenceSpan,
7 ExtractionDiagnostic, FactStatus, GraphVersionRange, IndexStatus, LayoutRegion,
8 },
9};
10
11#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
13pub struct IngestEvidence {
14 #[serde(skip_serializing_if = "Option::is_none")]
15 pub id: Option<String>,
16 #[serde(skip_serializing_if = "Option::is_none")]
17 pub source_path: Option<String>,
18 #[serde(skip_serializing_if = "Option::is_none")]
19 pub span: Option<EvidenceSpan>,
20 #[serde(skip_serializing_if = "Option::is_none")]
21 pub confidence: Option<ConfidenceScore>,
22 #[serde(skip_serializing_if = "Option::is_none")]
23 pub status: Option<FactStatus>,
24 pub content: String,
25 #[serde(default)]
26 pub entity_labels: Vec<String>,
27 #[serde(default, skip_serializing_if = "Option::is_none")]
28 pub extraction: Option<IngestEvidenceExtraction>,
29}
30
31#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
33pub struct IngestEvidenceExtraction {
34 pub modality: EvidenceModality,
35 #[serde(skip_serializing_if = "Option::is_none")]
36 pub source_uri: Option<String>,
37 #[serde(skip_serializing_if = "Option::is_none")]
38 pub source_hash: Option<String>,
39 #[serde(skip_serializing_if = "Option::is_none")]
40 pub media_hash: Option<String>,
41 #[serde(skip_serializing_if = "Option::is_none")]
42 pub extractor: Option<String>,
43 #[serde(skip_serializing_if = "Option::is_none")]
44 pub extractor_version: Option<String>,
45 #[serde(skip_serializing_if = "Option::is_none")]
46 pub observed_at: Option<String>,
47 #[serde(skip_serializing_if = "Option::is_none")]
48 pub parent_evidence_id: Option<String>,
49 #[serde(skip_serializing_if = "Option::is_none")]
50 pub layout_region: Option<LayoutRegion>,
51 #[serde(skip_serializing_if = "Option::is_none")]
52 pub embedding_model: Option<String>,
53 #[serde(skip_serializing_if = "Option::is_none")]
54 pub embedding_dimension: Option<u16>,
55 #[serde(skip_serializing_if = "Option::is_none")]
56 pub diagnostic: Option<ExtractionDiagnostic>,
57}
58
59impl IngestEvidenceExtraction {
60 pub fn into_domain_metadata(self) -> EvidenceExtractionMetadata {
62 EvidenceExtractionMetadata {
63 modality: self.modality,
64 source_uri: self.source_uri,
65 source_hash: self.source_hash,
66 media_hash: self.media_hash,
67 extractor: self.extractor,
68 extractor_version: self.extractor_version,
69 observed_at: self.observed_at,
70 parent_evidence_id: self.parent_evidence_id,
71 layout_region: self.layout_region,
72 embedding_model: self.embedding_model,
73 embedding_dimension: self.embedding_dimension,
74 diagnostic: self
75 .diagnostic
76 .unwrap_or_else(|| EvidenceExtractionMetadata::text_span().diagnostic),
77 }
78 }
79}
80
81#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
83pub struct IngestRelation {
84 pub id: String,
85 pub source_entity_label: String,
86 pub relation_type: String,
87 pub target_entity_label: String,
88 #[serde(default)]
89 pub evidence_ids: Vec<String>,
90 #[serde(skip_serializing_if = "Option::is_none")]
91 pub confidence: Option<ConfidenceScore>,
92 #[serde(skip_serializing_if = "Option::is_none")]
93 pub status: Option<FactStatus>,
94 #[serde(skip_serializing_if = "Option::is_none")]
95 pub version_range: Option<GraphVersionRange>,
96}
97
98#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
100pub struct IngestClaim {
101 pub id: String,
102 pub subject_entity_label: String,
103 pub predicate: String,
104 pub object: String,
105 #[serde(default)]
106 pub evidence_ids: Vec<String>,
107 #[serde(skip_serializing_if = "Option::is_none")]
108 pub confidence: Option<ConfidenceScore>,
109 #[serde(skip_serializing_if = "Option::is_none")]
110 pub status: Option<FactStatus>,
111 #[serde(skip_serializing_if = "Option::is_none")]
112 pub version_range: Option<GraphVersionRange>,
113}
114
115#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
117pub struct IngestEvent {
118 pub id: String,
119 pub event_type: String,
120 #[serde(default)]
121 pub entity_labels: Vec<String>,
122 #[serde(skip_serializing_if = "Option::is_none")]
123 pub occurred_at: Option<String>,
124 #[serde(default)]
125 pub evidence_ids: Vec<String>,
126 #[serde(skip_serializing_if = "Option::is_none")]
127 pub confidence: Option<ConfidenceScore>,
128 #[serde(skip_serializing_if = "Option::is_none")]
129 pub status: Option<FactStatus>,
130 #[serde(skip_serializing_if = "Option::is_none")]
131 pub version_range: Option<GraphVersionRange>,
132}
133
134#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
136pub struct IngestRequest {
137 pub source_scope: String,
138 #[serde(default)]
139 pub evidence: Vec<IngestEvidence>,
140 #[serde(default)]
141 pub relations: Vec<IngestRelation>,
142 #[serde(default)]
143 pub claims: Vec<IngestClaim>,
144 #[serde(default)]
145 pub events: Vec<IngestEvent>,
146}
147
148#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
150pub struct IngestResponse {
151 pub metadata: ApiMetadata,
152 pub receipt: CommitReceipt,
153 pub indexes: Vec<IndexStatus>,
154 #[serde(skip_serializing_if = "Option::is_none")]
155 pub index_refresh_error: Option<String>,
156}
157
158#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
160pub struct MultimodalExtractionRequest {
161 pub source_scope: String,
162 pub parent_evidence_id: String,
163 pub derived_evidence: Vec<IngestEvidence>,
164}
165
166#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
168pub struct MultimodalExtractionResponse {
169 pub metadata: ApiMetadata,
170 pub parent_evidence_id: String,
171 pub derived_evidence_count: usize,
172 pub receipt: CommitReceipt,
173 pub indexes: Vec<IndexStatus>,
174 #[serde(skip_serializing_if = "Option::is_none")]
175 pub index_refresh_error: Option<String>,
176}
177
178#[cfg(test)]
179#[path = "ingestion_tests.rs"]
180mod tests;