Skip to main content

qualia_core_db/entity_view/
attribution.rs

1//! Fragment-level category attribution edges (not whole-corpus genre flags).
2//!
3//! Edges are pure descriptors; storage lives in host/graph layers.
4
5use super::entity_id::EntityId;
6use serde::{Deserialize, Serialize};
7
8/// Typed attribution relation (extensible string in cold path; fixed set for product).
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
10#[serde(rename_all = "snake_case")]
11#[repr(u8)]
12pub enum AttributionRel {
13    /// Narrative / fictional presentation.
14    NarrativeFiction = 1,
15    /// Formal STEM / technical evaluation.
16    StemFormal = 2,
17    /// Fragment illustrates a concept (STEM-via-story normal).
18    Illustrates = 3,
19    /// Grounded in commons / measured geo or attested fact.
20    GroundsIn = 4,
21    /// Depicts a social relation pattern.
22    DepictsSocial = 5,
23    /// Legal / statute citation.
24    LegalCite = 6,
25    /// Geographic fact.
26    GeographicFact = 7,
27}
28
29/// One attribution edge: subject fragment - object entity/concept under a relation.
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
31pub struct AttributionEdge {
32    pub subject: EntityId,
33    pub rel: AttributionRel,
34    pub object: EntityId,
35    /// Lamport / unix attribution time (0 = unset).
36    pub attributed_at: u64,
37}
38
39/// Bounded collect: write edges whose subject matches into `out`. Returns count.
40pub fn edges_for_subject(
41    edges: &[AttributionEdge],
42    subject: EntityId,
43    out: &mut [AttributionEdge],
44) -> usize {
45    let mut n = 0;
46    for e in edges {
47        if e.subject == subject {
48            if n >= out.len() {
49                break;
50            }
51            out[n] = *e;
52            n += 1;
53        }
54    }
55    n
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61
62    #[test]
63    fn stem_in_story_two_edges_same_fragment() {
64        let frag = EntityId::from_fragment("urn:script:1", "para:3");
65        let concept = EntityId::from_uri("urn:concept:BayesRule");
66        let edges = [
67            AttributionEdge {
68                subject: frag,
69                rel: AttributionRel::NarrativeFiction,
70                object: EntityId::from_uri("urn:genre:fiction"),
71                attributed_at: 1,
72            },
73            AttributionEdge {
74                subject: frag,
75                rel: AttributionRel::Illustrates,
76                object: concept,
77                attributed_at: 1,
78            },
79        ];
80        let mut out = [AttributionEdge {
81            subject: EntityId::default(),
82            rel: AttributionRel::NarrativeFiction,
83            object: EntityId::default(),
84            attributed_at: 0,
85        }; 4];
86        let n = edges_for_subject(&edges, frag, &mut out);
87        assert_eq!(n, 2);
88    }
89}