qualia_core_db/query/
ontology_loader.rs1#![cfg(not(target_arch = "wasm32"))]
2use crate::{q_hash, NQuin};
14
15const RIGHTS_GRAPH: u64 = q_hash("urn:qualia:ontology:rights");
17const COGAI_GRAPH: u64 = q_hash("urn:qualia:ontology:cogai");
18const EPISTEMIC_GRAPH: u64 = q_hash("urn:qualia:ontology:epistemic");
19const UDHR_GRAPH: u64 = q_hash("urn:qualia:ontology:udhr");
20const CRC_GRAPH: u64 = q_hash("urn:qualia:ontology:crc");
21const PLATFORMS_GRAPH: u64 = q_hash("urn:qualia:ontology:platforms");
22const EMOJI_GRAPH: u64 = q_hash("urn:qualia:ontology:emoji");
23const CRC_ANNOTATED_GRAPH: u64 = q_hash("urn:qualia:ontology:crc_annotated");
24const CRPD_ANNOTATED_GRAPH: u64 = q_hash("urn:qualia:ontology:crpd_annotated");
25const ICCPR_ANNOTATED_GRAPH: u64 = q_hash("urn:qualia:ontology:iccpr_annotated");
26const ICESCR_ANNOTATED_GRAPH: u64 = q_hash("urn:qualia:ontology:icescr_annotated");
27const UDHR_ANNOTATED_GRAPH: u64 = q_hash("urn:qualia:ontology:udhr_annotated");
28const CAT_ANNOTATED_GRAPH: u64 = q_hash("urn:qualia:ontology:cat_annotated");
29const LAWYERS_PRINCIPLES_ANNOTATED_GRAPH: u64 =
30 q_hash("urn:qualia:ontology:lawyers_principles_annotated");
31const REMEDY_REPARATION_ANNOTATED_GRAPH: u64 =
32 q_hash("urn:qualia:ontology:remedy_reparation_annotated");
33const RIGHT_DEVELOPMENT_ANNOTATED_GRAPH: u64 =
34 q_hash("urn:qualia:ontology:right_development_annotated");
35const TORTURE_DECLARATION_ANNOTATED_GRAPH: u64 =
36 q_hash("urn:qualia:ontology:torture_declaration_annotated");
37const HR_DEFENDERS_ANNOTATED_GRAPH: u64 = q_hash("urn:qualia:ontology:hr_defenders_annotated");
38const PEOPLES_PEACE_ANNOTATED_GRAPH: u64 = q_hash("urn:qualia:ontology:peoples_peace_annotated");
39const MENTAL_ILLNESS_PRINCIPLES_ANNOTATED_GRAPH: u64 =
40 q_hash("urn:qualia:ontology:mental_illness_principles_annotated");
41const ISTANBUL_PROTOCOL_ANNOTATED_GRAPH: u64 =
42 q_hash("urn:qualia:ontology:istanbul_protocol_annotated");
43const PALERMO_PROTOCOL_ANNOTATED_GRAPH: u64 =
44 q_hash("urn:qualia:ontology:palermo_protocol_annotated");
45const COMMONWEALTH_CHARTER_ANNOTATED_GRAPH: u64 =
46 q_hash("urn:qualia:ontology:commonwealth_charter_annotated");
47
48const STARTUP_ONTOLOGIES: &[(&str, u64)] = &[
50 ("rights_ontology.ttl", RIGHTS_GRAPH),
51 ("cogai_shapes.ttl", COGAI_GRAPH),
52 ("epistemic_shapes.ttl", EPISTEMIC_GRAPH),
53 ("udhr.ttl", UDHR_GRAPH),
54 ("crc.ttl", CRC_GRAPH),
55 ("platforms.ttl", PLATFORMS_GRAPH),
56 ("emoji.n3", EMOJI_GRAPH),
57 ("udhr_annotated.ttl", UDHR_ANNOTATED_GRAPH),
58 ("crc_annotated.ttl", CRC_ANNOTATED_GRAPH),
59 ("crpd_annotated.ttl", CRPD_ANNOTATED_GRAPH),
60 ("iccpr_annotated.ttl", ICCPR_ANNOTATED_GRAPH),
61 ("icescr_annotated.ttl", ICESCR_ANNOTATED_GRAPH),
62 ("cat_annotated.ttl", CAT_ANNOTATED_GRAPH),
63 (
64 "lawyers_principles_annotated.ttl",
65 LAWYERS_PRINCIPLES_ANNOTATED_GRAPH,
66 ),
67 (
68 "remedy_reparation_annotated.ttl",
69 REMEDY_REPARATION_ANNOTATED_GRAPH,
70 ),
71 (
72 "right_development_annotated.ttl",
73 RIGHT_DEVELOPMENT_ANNOTATED_GRAPH,
74 ),
75 (
76 "torture_declaration_annotated.ttl",
77 TORTURE_DECLARATION_ANNOTATED_GRAPH,
78 ),
79 ("hr_defenders_annotated.ttl", HR_DEFENDERS_ANNOTATED_GRAPH),
80 ("peoples_peace_annotated.ttl", PEOPLES_PEACE_ANNOTATED_GRAPH),
81 (
82 "mental_illness_principles_annotated.ttl",
83 MENTAL_ILLNESS_PRINCIPLES_ANNOTATED_GRAPH,
84 ),
85 (
86 "istanbul_protocol_annotated.ttl",
87 ISTANBUL_PROTOCOL_ANNOTATED_GRAPH,
88 ),
89 (
90 "palermo_protocol_annotated.ttl",
91 PALERMO_PROTOCOL_ANNOTATED_GRAPH,
92 ),
93 (
94 "commonwealth_charter_annotated.ttl",
95 COMMONWEALTH_CHARTER_ANNOTATED_GRAPH,
96 ),
97];
98
99pub fn startup_ontology_catalog() -> &'static [(&'static str, u64)] {
101 STARTUP_ONTOLOGIES
102}
103
104pub fn ontology_dir_path() -> Option<std::path::PathBuf> {
106 find_ontology_dir()
107}
108
109fn find_ontology_dir() -> Option<std::path::PathBuf> {
111 if let Ok(p) = std::env::var("QUALIA_ONTOLOGY_PATH") {
113 let pb = std::path::PathBuf::from(p);
114 if pb.is_dir() {
115 return Some(pb);
116 }
117 }
118
119 let cwd = std::path::PathBuf::from("ontologies");
121 if cwd.is_dir() {
122 return Some(cwd);
123 }
124
125 if let Ok(exe) = std::env::current_exe() {
127 let sibling = exe.parent().map(|p| p.join("ontologies"));
128 if let Some(ref s) = sibling {
129 if s.is_dir() {
130 return Some(s.clone());
131 }
132 }
133 }
134
135 None
136}
137
138pub fn parse_ttl_to_quins(path: &std::path::Path, graph_context: u64) -> Vec<NQuin> {
144 use std::fs::File;
145 use std::io::BufReader;
146
147 let file = match File::open(path) {
148 Ok(f) => f,
149 Err(e) => {
150 log::warn!("[ontology_loader] cannot open {:?}: {e}", path);
151 return Vec::new();
152 }
153 };
154
155 let reader = BufReader::new(file);
156 let mut parser = rio_turtle::TurtleParser::new(reader, None);
157 let mut quins = Vec::new();
158
159 let result = {
160 use rio_api::parser::TriplesParser;
161 parser.parse_all(
162 &mut |t: rio_api::model::Triple| -> Result<(), std::io::Error> {
163 let s = q_hash(&t.subject.to_string());
164 let p = q_hash(&t.predicate.to_string());
165 let o = q_hash(&t.object.to_string());
166 quins.push(NQuin {
167 subject: s,
168 predicate: p,
169 object: o,
170 context: graph_context,
171 metadata: 0,
172 parity: s ^ p ^ o ^ graph_context,
173 });
174 Ok(())
175 },
176 )
177 };
178
179 if let Err(e) = result {
180 log::warn!("[ontology_loader] parse error in {:?}: {e}", path);
181 }
182
183 log::info!(
184 "[ontology_loader] loaded {} quins from {:?}",
185 quins.len(),
186 path
187 );
188 quins
189}
190
191pub fn load_q42_file(path: &std::path::Path) -> Vec<NQuin> {
193 let all_quins = match crate::q42_reader::read_q42_quins(path) {
194 Ok(quins) => quins,
195 Err(e) => {
196 log::warn!("[ontology_loader] cannot open q42 volume {:?}: {e}", path);
197 return Vec::new();
198 }
199 };
200 log::info!(
201 "[ontology_loader] loaded {} quins from unified volume {:?}",
202 all_quins.len(),
203 path
204 );
205 all_quins
206}
207
208pub fn load_startup_ontologies() {
212 let dir = match find_ontology_dir() {
213 Some(d) => d,
214 None => {
215 log::info!("[ontology_loader] no ontologies directory found — skipping");
216 return;
217 }
218 };
219
220 log::info!("[ontology_loader] loading ontologies from {:?}", dir);
221
222 let mut all_quins: Vec<NQuin> = Vec::new();
223 for (filename, context) in STARTUP_ONTOLOGIES {
224 let path = dir.join(filename);
225 if !path.exists() {
226 log::warn!("[ontology_loader] {:?} not found — skipping", path);
227 continue;
228 }
229 let quins = parse_ttl_to_quins(&path, *context);
230 all_quins.extend(quins);
231 }
232
233 if let Ok(entries) = std::fs::read_dir(&dir) {
235 for entry in entries.filter_map(|e| e.ok()) {
236 let path = entry.path();
237 if path.extension().and_then(|s| s.to_str()) == Some("q42") {
238 let quins = load_q42_file(&path);
239 all_quins.extend(quins);
240 }
241 }
242 }
243
244 crate::daemon_graph::extend_with_ontology_quins(all_quins);
245 log::info!(
246 "[ontology_loader] daemon graph now has {} quins after ontology seed",
247 crate::daemon_graph::graph_quin_count(),
248 );
249}
250
251#[cfg(test)]
252mod tests {
253 use super::*;
254 use std::io::Write;
255
256 #[test]
257 fn parse_ttl_minimal() {
258 let ttl = b"@prefix ex: <http://example.org/> .\nex:Alice a ex:Person .\n";
259 let tmp = tempfile::NamedTempFile::new().expect("tmp");
260 tmp.as_file().write_all(ttl).unwrap();
261 let quins = parse_ttl_to_quins(tmp.path(), 0xCAFE);
262 assert!(!quins.is_empty());
263 assert!(quins.iter().all(|q| q.context == 0xCAFE));
264 }
265
266 #[test]
267 fn parse_ttl_missing_file_returns_empty() {
268 let quins = parse_ttl_to_quins(std::path::Path::new("/nonexistent/file.ttl"), 0);
269 assert!(quins.is_empty());
270 }
271}