1use crate::model::{Literal, NamedNode, Object, Predicate, Subject, Triple};
9use crate::OxirsError;
10use std::collections::HashMap;
11
12use super::provenance_types::{
13 prov_iri, rdf_iri, AgentType, ProvActivity, ProvAgent, ProvEntity, ProvRelation,
14 ProvRelationKind, PROV_NS, RDF_NS,
15};
16
17#[derive(Debug, Clone)]
24pub struct ProvBundle {
25 pub iri: NamedNode,
27 pub entities: Vec<ProvEntity>,
29 pub activities: Vec<ProvActivity>,
31 pub agents: Vec<ProvAgent>,
33 pub relations: Vec<ProvRelation>,
35}
36
37impl ProvBundle {
38 pub fn new(iri: NamedNode) -> Self {
40 Self {
41 iri,
42 entities: Vec::new(),
43 activities: Vec::new(),
44 agents: Vec::new(),
45 relations: Vec::new(),
46 }
47 }
48
49 pub fn add_entity(&mut self, entity: ProvEntity) {
51 self.entities.push(entity);
52 }
53
54 pub fn add_activity(&mut self, activity: ProvActivity) {
56 self.activities.push(activity);
57 }
58
59 pub fn add_agent(&mut self, agent: ProvAgent) {
61 self.agents.push(agent);
62 }
63
64 pub fn add_relation(&mut self, relation: ProvRelation) {
66 self.relations.push(relation);
67 }
68
69 pub fn to_rdf(&self) -> Vec<Triple> {
74 let mut triples = Vec::new();
75
76 triples.push(Triple::new(
78 self.iri.clone(),
79 rdf_iri("type"),
80 prov_iri("Bundle"),
81 ));
82
83 for entity in &self.entities {
84 triples.extend(entity.to_triples());
85 }
86 for activity in &self.activities {
87 triples.extend(activity.to_triples());
88 }
89 for agent in &self.agents {
90 triples.extend(agent.to_triples());
91 }
92 for relation in &self.relations {
93 triples.push(relation.to_triple());
94 }
95
96 triples
97 }
98
99 pub fn from_rdf(triples: &[Triple]) -> Result<Self, OxirsError> {
104 let mut by_subject: HashMap<String, Vec<&Triple>> = HashMap::new();
106 for triple in triples {
107 let key = match triple.subject() {
108 Subject::NamedNode(n) => n.as_str().to_string(),
109 Subject::BlankNode(b) => b.as_str().to_string(),
110 _ => continue,
111 };
112 by_subject.entry(key).or_default().push(triple);
113 }
114
115 let type_pred_full = format!("{RDF_NS}type");
116 let bundle_type_full = format!("{PROV_NS}Bundle");
117
118 let bundle_iri_str = triples
120 .iter()
121 .find(|t| {
122 matches!(t.predicate(), Predicate::NamedNode(p) if p.as_str() == type_pred_full)
123 && matches!(t.object(), Object::NamedNode(o) if o.as_str() == bundle_type_full)
124 })
125 .and_then(|t| match t.subject() {
126 Subject::NamedNode(n) => Some(n.as_str().to_string()),
127 _ => None,
128 })
129 .ok_or_else(|| OxirsError::Parse("No prov:Bundle declaration found".to_string()))?;
130
131 let bundle_iri = NamedNode::new_unchecked(bundle_iri_str.clone());
132
133 let entity_type = format!("{PROV_NS}Entity");
135 let activity_type = format!("{PROV_NS}Activity");
136 let agent_type_iri_str = format!("{PROV_NS}Agent");
137 let software_type = format!("{PROV_NS}SoftwareAgent");
138 let person_type = format!("{PROV_NS}Person");
139 let org_type = format!("{PROV_NS}Organization");
140
141 let mut entities: Vec<ProvEntity> = Vec::new();
142 let mut activities: Vec<ProvActivity> = Vec::new();
143 let mut agents: Vec<ProvAgent> = Vec::new();
144 let mut relations: Vec<ProvRelation> = Vec::new();
145
146 let relation_kind_map: HashMap<String, ProvRelationKind> = [
148 (
149 format!("{PROV_NS}wasGeneratedBy"),
150 ProvRelationKind::WasGeneratedBy,
151 ),
152 (
153 format!("{PROV_NS}wasDerivedFrom"),
154 ProvRelationKind::WasDerivedFrom,
155 ),
156 (
157 format!("{PROV_NS}wasAttributedTo"),
158 ProvRelationKind::WasAttributedTo,
159 ),
160 (format!("{PROV_NS}used"), ProvRelationKind::Used),
161 (
162 format!("{PROV_NS}wasAssociatedWith"),
163 ProvRelationKind::WasAssociatedWith,
164 ),
165 (
166 format!("{PROV_NS}wasInformedBy"),
167 ProvRelationKind::WasInformedBy,
168 ),
169 (
170 format!("{PROV_NS}actedOnBehalfOf"),
171 ProvRelationKind::ActedOnBehalfOf,
172 ),
173 ]
174 .into_iter()
175 .collect();
176
177 for triple in triples {
179 let subj_iri = match triple.subject() {
180 Subject::NamedNode(n) => n.clone(),
181 _ => continue,
182 };
183 let pred_str = match triple.predicate() {
184 Predicate::NamedNode(p) => p.as_str().to_string(),
185 _ => continue,
186 };
187 let obj_iri = match triple.object() {
188 Object::NamedNode(o) => o.clone(),
189 _ => continue,
190 };
191
192 if let Some(kind) = relation_kind_map.get(&pred_str) {
193 relations.push(ProvRelation::new(kind.clone(), subj_iri, obj_iri));
194 }
195 }
196
197 for (subj_str, subj_triples) in &by_subject {
199 if subj_str == &bundle_iri_str {
200 continue;
201 }
202
203 let types: Vec<String> = subj_triples
205 .iter()
206 .filter(|t| {
207 matches!(t.predicate(), Predicate::NamedNode(p) if p.as_str() == type_pred_full)
208 })
209 .filter_map(|t| match t.object() {
210 Object::NamedNode(o) => Some(o.as_str().to_string()),
211 _ => None,
212 })
213 .collect();
214
215 let iri = NamedNode::new_unchecked(subj_str.clone());
216
217 let attributes: Vec<(NamedNode, Object)> = subj_triples
219 .iter()
220 .filter_map(|t| {
221 if let Predicate::NamedNode(p) = t.predicate() {
222 let p_str = p.as_str();
223 if p_str == type_pred_full {
224 return None;
225 }
226 if relation_kind_map.contains_key(p_str) {
227 return None;
228 }
229 Some((p.clone(), t.object().clone()))
230 } else {
231 None
232 }
233 })
234 .collect();
235
236 if types.contains(&entity_type) {
237 entities.push(ProvEntity::with_attributes(iri, attributes));
238 } else if types.contains(&activity_type) {
239 let start_pred = format!("{PROV_NS}startedAtTime");
240 let end_pred = format!("{PROV_NS}endedAtTime");
241
242 let start = attributes
243 .iter()
244 .find(|(p, _)| p.as_str() == start_pred)
245 .and_then(|(_, o)| match o {
246 Object::Literal(l) => Some(l.value().to_string()),
247 _ => None,
248 });
249 let end = attributes
250 .iter()
251 .find(|(p, _)| p.as_str() == end_pred)
252 .and_then(|(_, o)| match o {
253 Object::Literal(l) => Some(l.value().to_string()),
254 _ => None,
255 });
256 let extra_attrs: Vec<(NamedNode, Object)> = attributes
257 .into_iter()
258 .filter(|(p, _)| p.as_str() != start_pred && p.as_str() != end_pred)
259 .collect();
260 activities.push(ProvActivity::with_times(iri, start, end, extra_attrs));
261 } else if types.contains(&agent_type_iri_str) {
262 let agent_kind = if types.contains(&software_type) {
263 AgentType::SoftwareAgent
264 } else if types.contains(&person_type) {
265 AgentType::Person
266 } else if types.contains(&org_type) {
267 AgentType::Organization
268 } else {
269 AgentType::Person
270 };
271 agents.push(ProvAgent::with_attributes(iri, agent_kind, attributes));
272 }
273 }
274
275 Ok(Self {
276 iri: bundle_iri,
277 entities,
278 activities,
279 agents,
280 relations,
281 })
282 }
283}
284
285#[derive(Debug, Clone)]
292pub struct QueryProvenanceTracker {
293 pub query_iri: NamedNode,
295 pub executed_at: String,
297 pub executed_by: NamedNode,
299 pub input_dataset: NamedNode,
301 pub result_dataset: NamedNode,
303 pub query_text: Option<String>,
305}
306
307impl QueryProvenanceTracker {
308 pub fn new(
310 query_iri: NamedNode,
311 executed_at: String,
312 executed_by: NamedNode,
313 input_dataset: NamedNode,
314 result_dataset: NamedNode,
315 ) -> Self {
316 Self {
317 query_iri,
318 executed_at,
319 executed_by,
320 input_dataset,
321 result_dataset,
322 query_text: None,
323 }
324 }
325
326 pub fn with_query_text(mut self, text: impl Into<String>) -> Self {
328 self.query_text = Some(text.into());
329 self
330 }
331
332 pub fn to_bundle(&self) -> ProvBundle {
340 let bundle_iri =
341 NamedNode::new_unchecked(format!("{}/provenance", self.query_iri.as_str()));
342 let mut bundle = ProvBundle::new(bundle_iri);
343
344 bundle.add_entity(ProvEntity::new(self.input_dataset.clone()));
346 bundle.add_entity(ProvEntity::new(self.result_dataset.clone()));
347
348 let mut activity_attrs: Vec<(NamedNode, Object)> = Vec::new();
350 if let Some(ref text) = self.query_text {
351 activity_attrs.push((
352 prov_iri("value"),
353 Object::Literal(Literal::new(text.as_str())),
354 ));
355 }
356
357 bundle.add_activity(ProvActivity::with_times(
358 self.query_iri.clone(),
359 Some(self.executed_at.clone()),
360 Some(self.executed_at.clone()),
361 activity_attrs,
362 ));
363
364 bundle.add_agent(ProvAgent::new(
366 self.executed_by.clone(),
367 AgentType::SoftwareAgent,
368 ));
369
370 bundle.add_relation(ProvRelation::new(
372 ProvRelationKind::WasGeneratedBy,
373 self.result_dataset.clone(),
374 self.query_iri.clone(),
375 ));
376 bundle.add_relation(ProvRelation::new(
377 ProvRelationKind::Used,
378 self.query_iri.clone(),
379 self.input_dataset.clone(),
380 ));
381 bundle.add_relation(ProvRelation::new(
382 ProvRelationKind::WasAssociatedWith,
383 self.query_iri.clone(),
384 self.executed_by.clone(),
385 ));
386 bundle.add_relation(ProvRelation::new(
387 ProvRelationKind::WasAttributedTo,
388 self.result_dataset.clone(),
389 self.executed_by.clone(),
390 ));
391
392 bundle
393 }
394}