narrative_graph/heuristic/
mod.rs1mod cooccurrence;
2mod entities;
3mod relations;
4mod segment;
5
6use crate::types::{Options, TripleCandidate};
7use crate::Result;
8use std::collections::BTreeMap;
9
10pub use entities::extract_entities;
11pub use relations::normalize_relation;
12
13pub fn extract_candidate_triples(text: &str, opts: &Options) -> Result<Vec<TripleCandidate>> {
16 if text.is_empty() {
17 return Ok(vec![]);
18 }
19
20 let min_confidence = opts.min_confidence.unwrap_or(0.0);
21 if !(0.0..=1.0).contains(&min_confidence) {
22 return Err(crate::NarrativeGraphError::InvalidConfidenceThreshold(
23 min_confidence,
24 ));
25 }
26
27 let mut candidates = Vec::new();
28
29 for (sent_text, sent_start) in segment::split_sentences(text) {
30 let entities = extract_entities(sent_text, &opts.aliases);
31
32 if entities.is_empty() {
33 continue;
34 }
35
36 let relations = relations::extract_relations(sent_text, &entities, &opts.ontology);
38
39 for rel in relations {
41 let confidence = cooccurrence::score_confidence(rel.base, rel.gap);
42
43 if confidence >= min_confidence {
44 let span = [sent_start + rel.span[0], sent_start + rel.span[1]];
45
46 candidates.push(TripleCandidate {
47 subject: rel.subject,
48 relation: rel.relation,
49 object: rel.object,
50 confidence,
51 span,
52 rule: rel.rule,
53 });
54 }
55 }
56 }
57
58 dedup_candidates(&mut candidates);
60
61 Ok(candidates)
62}
63
64fn dedup_candidates(candidates: &mut Vec<TripleCandidate>) {
65 let mut best: BTreeMap<(String, String, String), TripleCandidate> = BTreeMap::new();
66
67 for candidate in candidates.drain(..) {
68 let key = (
69 candidate.subject.clone(),
70 candidate.relation.clone(),
71 candidate.object.clone(),
72 );
73 best.entry(key)
74 .and_modify(|best_cand| {
75 if candidate.confidence > best_cand.confidence {
76 *best_cand = candidate.clone();
77 }
78 })
79 .or_insert(candidate);
80 }
81
82 *candidates = best.into_values().collect();
83}